如何防止在Java中的SocketInputStream.socketRead0上挂起?

使用不同的Java库执行数百万个HTTP请求会给我带来挂起的线程:

java.net.SocketInputStream.socketRead0()

这是功能。native

我试图设置Apche Http客户端,并在(我希望)每个可能的超时上都有超时,但是我仍然(可能是无限的)挂在socketRead0上。如何摆脱它们?RequestConfig

挂起比率约为每10000个请求(对10000个不同的主机)约1个,它可能永远持续下去(我已经确认线程挂起在10小时后仍然有效)。

Windows 7 上的 JDK 1.8。

我的工厂:HttpClient

SocketConfig socketConfig = SocketConfig.custom()
            .setSoKeepAlive(false)
            .setSoLinger(1)
            .setSoReuseAddress(true)
            .setSoTimeout(5000)
            .setTcpNoDelay(true).build();

    HttpClientBuilder builder = HttpClientBuilder.create();
    builder.disableAutomaticRetries();
    builder.disableContentCompression();
    builder.disableCookieManagement();
    builder.disableRedirectHandling();
    builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
    builder.setDefaultSocketConfig(socketConfig);

    return HttpClientBuilder.create().build();

我的工厂:RequestConfig

    HttpGet request = new HttpGet(url);

    RequestConfig config = RequestConfig.custom()
            .setCircularRedirectsAllowed(false)
            .setConnectionRequestTimeout(8000)
            .setConnectTimeout(4000)
            .setMaxRedirects(1)
            .setRedirectsEnabled(true)
            .setSocketTimeout(5000)
            .setStaleConnectionCheckEnabled(true).build();
    request.setConfig(config);

    return new HttpGet(url);

OpenJDK socketRead0 source

注意:实际上我有一些“技巧” - 我可以在其他情况下安排取消请求是否正确完成,但它被消除并且还会杀死整个,而不仅仅是单个请求。.getConnectionManager().shutdown()ThreadFutureHttpClient


答案 1

虽然这个问题提到了Windows,但我在Linux上也有同样的问题。JVM 实现阻塞套接字超时的方式似乎存在缺陷:

总而言之,阻止套接字的超时是通过在 Linux(和 Windows)上调用来确定数据在调用 之前可用的来实现的。但是,至少在Linux上,这两种方法都可以虚假地指示数据可用,而数据不可用,从而导致无限期阻塞。pollselectrecvrecv

来自 poll(2) 手册页 BUGS 部分:

请参阅 select(2) 的 BUGS 部分下对虚假就绪通知的讨论。

从 select(2) 手册页 BUGS 部分:

在 Linux 下,select() 可能会将套接字文件描述符报告为“准备读取”,同时又会报告后续的读取块。例如,当数据到达但经过检查时校验和错误并被丢弃时,可能会发生这种情况。在其他情况下,文件描述符可能会被虚假地报告为就绪。因此,在不应阻塞的套接字上使用O_NONBLOCK可能更安全。

Apache HTTP客户端代码有点难以遵循,但似乎连接过期仅针对HTTP保持活动状态连接(您已禁用)设置,并且除非服务器另有指定,否则是无限期的。因此,正如oleg所指出的那样,连接驱逐策略方法不适用于您的情况,并且通常不能依赖。


答案 2

正如Clint所说,你应该考虑一个非阻塞HTTP客户端,或者(看到你正在使用Apache Httpclient)实现一个多线程请求执行,以防止主应用程序线程可能挂起(这不能解决问题,但比重新启动你的应用程序更好,因为被冻结了)。无论如何,您设置了该属性,但过时的连接检查不是100%可靠,来自Apache Httpclient教程:setStaleConnectionCheckEnabled

经典阻塞 I/O 模型的一个主要缺点是,网络套接字只有在 I/O 操作中被阻止时才能对 I/O 事件做出反应。当连接被释放回管理器时,它可以保持活动状态,但它无法监视套接字的状态并对任何 I/O 事件做出反应。如果连接在服务器端关闭,则客户端连接无法检测到连接状态的变化(并通过关闭其端的套接字来做出适当的反应)。

HttpClient试图通过测试连接是否“过时”来缓解这个问题,该连接不再有效,因为它在使用连接执行HTTP请求之前在服务器端关闭。过时的连接检查不是 100% 可靠的,并且会给每个请求执行增加 10 到 30 毫秒的开销。

Apache HttpComponents 团队建议实施连接逐出策略

对于空闲连接,唯一不涉及每个套接字一个线程模型的可行解决方案是专用的监视线程,用于逐出由于长时间不活动而被视为已过期的连接。监视线程可以定期调用 ClientConnectionManager#closeExpiredConnections() 方法来关闭所有过期的连接,并从池中逐出已关闭的连接。它还可以选择调用 ClientConnectionManager#closeIdleConnections() 方法来关闭在给定时间段内处于空闲状态的所有连接。

查看连接逐出策略部分的示例代码,并尝试在应用程序中实现它以及多线程请求执行,我认为这两种机制的实现将防止意外挂起。


推荐