【问题标题】:Getting stuck at SocketInputStream.socketRead0卡在 SocketInputStream.socketRead0
【发布时间】:2015-03-26 09:33:48
【问题描述】:

我有一个项目,在该项目中,我在许多任务中同时下载了许多页面,这些页面通过ThreadPool(大小 = 200)进行处理。所有这些任务都使用相同的方法getPage 下载页面(使用 Apache Commons HttpClient 和 Apache Commons IO):

public static String getPage(String url)
        throws IOException {

    HttpUriRequest request = new HttpGet(url);

    HttpResponse response = HTTP_CLIENT_BUILDER.build().execute(request);
    try (InputStream content = response.getEntity().getContent()) {
        return IOUtils.toString(content, "UTF-8");
    }
}

HTTP_CLIENT_BUILDER 是这样初始化的静态字段:

 private static final HttpClientBuilder HTTP_CLIENT_BUILDER = HttpClients.custom()
        .setDefaultRequestConfig(RequestConfig.custom()
                .setSocketTimeout(SOCKET_TIMEOUT_MS) // 60_000
                .setConnectTimeout(CONNECTION_TIMEOUT_MS) // 5_000
                .build());

问题陈述:在某个时刻(大部分任务完成时)所有剩余线程都卡在本机方法SocketInputStream.socketRead0,所以jdb 说,他们'重新运行(嗯,是的,我希望本机方法运行时的行为:-)):

> threads
Group system:
  (java.lang.ref.Reference$ReferenceHandler)0xac4 Reference Handler cond. waiting
  (java.lang.ref.Finalizer$FinalizerThread)0xac5  Finalizer         cond. waiting
  (java.lang.Thread)0xac6                         Signal Dispatcher running
  (java.lang.Thread)0xac7                         Java2D Disposer   cond. waiting
Group main:
  (java.lang.Thread)0xac9                         pool-1-thread-5   running
  (java.lang.Thread)0xaca                         pool-1-thread-12  running
  (... 12 more threads from ThreadPool ...)
  (java.lang.Thread)0xad7                         DestroyJavaVM     running
> where 0xac9
  [1] java.net.SocketInputStream.socketRead0 (native method)
  [2] java.net.SocketInputStream.read (SocketInputStream.java:150)
  [3] java.net.SocketInputStream.read (SocketInputStream.java:121)
  [4] sun.security.ssl.InputRecord.readFully (InputRecord.java:465)
  [5] sun.security.ssl.InputRecord.read (InputRecord.java:503)
  [6] sun.security.ssl.SSLSocketImpl.readRecord (SSLSocketImpl.java:961)
  [7] sun.security.ssl.SSLSocketImpl.performInitialHandshake (SSLSocketImpl.java:1,363)
  [8] sun.security.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.java:1,391)
  [9] sun.security.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.java:1,375)
  [10] org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket (SSLConnectionSocketFactory.java:275)
  [11] org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket (SSLConnectionSocketFactory.java:254)
  [12] org.apache.http.impl.conn.HttpClientConnectionOperator.connect (HttpClientConnectionOperator.java:117)
  [13] org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect (PoolingHttpClientConnectionManager.java:314)
  [14] org.apache.http.impl.execchain.MainClientExec.establishRoute (MainClientExec.java:363)
  [15] org.apache.http.impl.execchain.MainClientExec.execute (MainClientExec.java:219)
  [16] org.apache.http.impl.execchain.ProtocolExec.execute (ProtocolExec.java:195)
  [17] org.apache.http.impl.execchain.RetryExec.execute (RetryExec.java:86)
  [18] org.apache.http.impl.execchain.RedirectExec.execute (RedirectExec.java:108)
  [19] org.apache.http.impl.client.InternalHttpClient.doExecute (InternalHttpClient.java:186)
  [20] org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:82)
  [21] org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:106)
  [22] <package>.Utils.getPage (Utils.java:122)
  [23...] <internal details>
> # the same picture for all of them

我不明白为什么会发生这种情况,但我找到了Java bug,这可能与问题有关。所以也许我不是在寻找真正的解决方案,而是在寻找一些解决方法。

由于该错误是针对 Linux 提交的,我应该说,我也在使用运行 Ubuntu 14.04 x86_64 的虚拟机

UPD:好的,我现在尝试使用setConnectionRequestTimeout 添加新的超时(只是为了确保它不起作用)添加finally 阻止getPage

...
try (InputStream content = response.getEntity().getContent()) {
    return IOUtils.toString(content, "UTF-8");
} finally {
   httpClient.getConnectionManager().closeIdleConnections(0, TimeUnit.NANOSECONDS);
}

让我们看看,这是否有帮助。

UPD2:这似乎有点帮助,但我仍然有这个永久运行的任务大约每天卡住一次。

【问题讨论】:

  • 执行Socket.read 的线程将显示为Runnable,请参阅此SO 帖子:stackoverflow.com/questions/12544212。最有可能的是,远程端保持其套接字末端打开,这就是您的任务无法完成的原因。例如,您向 Executor 提交的任务多于下载远程资源所需的任务,而其余空闲任务则处于等待状态。
  • @VictorSorokin 这不应该发生,因为我设置了超时(请参阅 HTTP_CLIENT_BUILDER 的初始化程序)
  • 是的,忽略了,抱歉。然后我会检查与 tcpdump 或类似的连接,以了解是什么让 TCP 连接保持活动状态。也许,服务器端日志也可以很方便。
  • 然后,一旦您知道(不知何故)所有内容都已下载,请关闭您身边打开的套接字。这将释放池线程并取消卡住的任务(有例外)。这意味着您需要将打开的套接字放在getPage 内的某个共享集合中,这些集合应该可用于取消任务。
  • @VictorSorokin 套接字本身是 HttpClient 引擎盖下的实现细节,所以我不确定,我怎样才能得到它们。此外,在我看来,创建这样一个集合有点过头了。

标签: java sockets apache-commons-httpclient


【解决方案1】:

不幸的是,我没有找到任何简单的解决方法(或真正的解决方案),所以我让经理编写自己的解决方法,我希望它能帮助解决该错误的人:

创建类ConnectionSupervisor:

private static class ConnectionsSupervisor extends Thread {
    private Set<RequestEntry> streams = new CopyOnWriteArraySet<>();

    public ConnectionsSupervisor() {
        setDaemon(true);
        setName("Connections supervisor");
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(CONNECTIONS_SUPERVISOR_WAIT_MS);
            } catch (InterruptedException ignored) {
            }
            long time = timestamp();
            streams.stream().filter(entry -> time > entry.timeoutBorder).forEach(entry -> {
                HttpUriRequest request = entry.request;
                System.err.format("HttpUriRequest killed after timeout (%d sec.) exceeded: %s%n",
                        FULL_CONNECTION_TIMEOUT_S,
                        request);
                request.abort();
            });
        }
    }

    public void addRequest(HttpUriRequest request) {
        streams.add(new RequestEntry(timestamp() + FULL_CONNECTION_TIMEOUT_S, request));
    }

    public void removeRequest(HttpUriRequest request) {
        streams.removeIf(entry -> entry.request == request);
    }

    private static class RequestEntry {
        private long timeoutBorder;
        private HttpUriRequest request;

        public RequestEntry(long timeoutBorder, HttpUriRequest request) {
            this.timeoutBorder = timeoutBorder;
            this.request = request;
        }
    }
}


public static long timestamp() {
    return Instant.now().getEpochSecond();
}

某处应该有ConnectionSupervisor 的实例,例如:

private static final ConnectionsSupervisor connectionsSupervisor = new ConnectionsSupervisor();
static {
    connectionsSupervisor.start();
}

类似getPage 方法:

HttpUriRequest request = ...;

// ...

connectionsSupervisor.addRequest(request);

try (InputStream content = httpClient.execute(request).getEntity().getContent()) {
    return IOUtils.toString(content, "UTF-8");
    // or any other usage
} finally {
    connectionsSupervisor.removeRequest(request);
    // highly important!
}

【讨论】:

    猜你喜欢
    • 2015-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-17
    • 2019-10-10
    • 1970-01-01
    相关资源
    最近更新 更多