【问题标题】:Java HttpURLConnection InputStream.close() hangs (or works too long?)Java HttpURLConnection InputStream.close() 挂起(或工作时间过长?)
【发布时间】:2013-01-17 12:16:14
【问题描述】:

首先,一些背景知识。有一个工人扩展/解析一堆短网址:

http://t.co/example -> http://example.com

所以,我们只关注重定向。就是这样。我们不会从连接中读取任何数据。在我们得到 200 之后,我们立即返回最终 URL 并关闭 InputStream。

现在,问题本身。在生产服务器上,其中一个解析器线程挂在 InputStream.close() 调用中:

"ProcessShortUrlTask" prio=10 tid=0x00007f8810119000 nid=0x402b runnable [0x00007f882b044000]
   java.lang.Thread.State: RUNNABLE
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
        at java.io.BufferedInputStream.skip(BufferedInputStream.java:352)
        - locked <0x0000000561293aa0> (a java.io.BufferedInputStream)
        at sun.net.www.MeteredStream.skip(MeteredStream.java:134)
        - locked <0x0000000561293a70> (a sun.net.www.http.KeepAliveStream)
        at sun.net.www.http.KeepAliveStream.close(KeepAliveStream.java:76)
        at java.io.FilterInputStream.close(FilterInputStream.java:155)
        at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.close(HttpURLConnection.java:2735)
        at ru.twitter.times.http.URLProcessor.resolve(URLProcessor.java:131)
        at ru.twitter.times.http.URLProcessor.resolve(URLProcessor.java:55)
        at ...

经过简短的研究,我了解到在将流发送回连接池之前调用skip() 来清理流(如果设置了keep-alive?)。我仍然不明白如何避免这种情况。此外,我怀疑我们的代码中是否存在一些糟糕的设计,或者 JDK 中是否存在问题。

所以,问题是:

  1. 是否可以避免挂在close() 上?保证一些合理的 例如,超时。
  2. 是否可以完全避免从连接中读取数据? 请记住,我只想要最终 URL。其实我觉得我不想 skip() 将被调用...

更新:

KeepAliveStream,第 79 行,close() 方法:

    // Skip past the data that's left in the Inputstream because
    // some sort of error may have occurred.
    // Do this ONLY if the skip won't block. The stream may have
    // been closed at the beginning of a big file and we don't want
    // to hang around for nothing. So if we can't skip without blocking
    // we just close the socket and, therefore, terminate the keepAlive
    // NOTE: Don't close super class
    try {
        if (expected > count) {
        long nskip = (long) (expected - count);
        if (nskip <= available()) {
            long n = 0;
            while (n < nskip) {
            nskip = nskip - n;
            n = skip(nskip);} ...

在我看来,JDK 本身存在一个错误。不幸的是,这很难重现......

【问题讨论】:

    标签: java http tcp httpurlconnection


    【解决方案1】:

    您已链接的KeepAliveStream 的实现违反了保证available()skip() 是非阻塞的合同,因此可能确实会阻塞。

    The contract of available()保证单个非阻塞skip()

    返回可读取的字节数的估计值(或 跳过)从此输入流中没有被下一个阻塞 此输入流的方法的调用者。下一个来电者可能是 同一个线程或另一个线程。单读或跳过此 许多字节不会阻塞,但可以读取或跳过更少的字节。

    在每次调用available() 时,实现会多次调用skip()

        if (nskip <= available()) {
            long n = 0;
            // The loop below can iterate several times,
            // only the first call is guaranteed to be non-blocking. 
            while (n < nskip) { 
            nskip = nskip - n;
            n = skip(nskip);
            }
    

    这并不能证明您的应用程序阻塞是因为KeepAliveStream 错误地使用了InputStreamInputStream 的一些实现可能会提供更强大的非阻塞保证,但我认为这很可能是一个嫌疑犯。

    编辑:经过更多研究,这是 JDK 中最近修复的错误:https://bugs.openjdk.java.net/browse/JDK-8004863?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel。错误报告中提到了无限循环,但阻塞skip() 也可能是结果。该修复似乎解决了这两个问题(每个available() 只有一个skip()

    【讨论】:

      【解决方案2】:

      我猜close() 上的这个skip() 是为了支持 Keep-Alive。

      http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html

      在 Java SE 6 之前,如果应用程序在以下情况下关闭 HTTP InputStream 多于少量数据仍有待读取,则 连接必须关闭,而不是被缓存。现在在 Java SE 6、行为是在一次连接中读取最多 512 KB 后台线程,从而允许重用连接。这 可以读取的确切数据量可通过 http.KeepAlive.remainingData系统属性。

      所以可以使用http.KeepAlive.remainingData=0http.keepAlive=false 有效地禁用保持活动。 但是,如果您总是寻址到同一个 http://t.co 主机,这可能会对性能产生负面影响。

      正如@artbristol 所建议的,在这里使用 HEAD 而不是 GET 似乎是更可取的解决方案。

      【讨论】:

        【解决方案3】:

        当我尝试发出“HEAD”请求时,我遇到了类似的问题。为了修复它,我删除了“HEAD”方法,因为我只是想 ping url

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-11
          • 2022-08-20
          • 2013-09-16
          相关资源
          最近更新 更多