Java HttpURLConnection InputStream.close()挂起(或工作时间太长?)

时间:2013-01-17 12:16:15

标签: java http tcp httpurlconnection

首先,一些背景知识。有一个工作人员可以扩展/解决一堆短网址:

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()完全被召唤......
  3. 更新

    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本身存在一个错误。不幸的是,重现这个很难......

3 个答案:

答案 0 :(得分:5)

您已关联的KeepAliveStream的实施违反了available()skip()保证不会阻止的合同,因此可能会阻止。

The contract of available()保证单一非阻止skip()

  

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

每次调用skip()时多次执行调用available()

    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()也可能是结果。该解决方案似乎解决了这两个问题(每skip()只有一个available()

答案 1 :(得分:2)

我想skip()close()的{​​{1}}用于支持Keep-Alive。

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

  

在Java SE 6之前,如果应用程序何时关闭HTTP InputStream   超过少量数据仍然需要读取,然后   必须关闭连接,而不是缓存。现在在Java SE中   6,行为是在a中读取最多512 KB的连接   后台线程,从而允许重用连接。该   可读取的确切数据量可通过以下方式配置   http.KeepAlive.remainingData系统属性。

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

正如@artbristol建议的那样,使用HEAD而不是GET似乎是最好的解决方案。

答案 2 :(得分:0)

当我试图提出“HEAD”请求时,我遇到了类似的问题。为了解决这个问题,我删除了“HEAD”方法,因为我只想ping网址