2015-03-26 4 views
1

У меня есть проект, в котором я загружаю много страниц одновременно во многих задачах, которые обрабатываются через ThreadPool (size = 200). Все эти задачи используют один и тот же метод getPage для загрузки страницы (с Apache Commons HttpClient и Apache Commons IO):Застревание в SocketInputStream.socketRead0

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: похоже, это немного помогает, но все же у меня есть проблемы с постоянным запуском, которые возникают примерно один раз в день.

+0

Thread, который делает 'Socket.read' будет показано вверх как' Runnable' см это SO сообщение: http://stackoverflow.com/questions/12544212. Скорее всего, удаленная сторона закрывает конец разъема, поэтому ваши задачи не могут быть завершены. Например, вы отправили больше задач Исполнителю, чем необходимо для загрузки удаленных ресурсов, а оставшаяся часть задач оставлена ​​в ожидании. –

+0

@VictorSorokin, который не должен происходить, поскольку я установил таймауты (см. Инициализатор для 'HTTP_CLIENT_BUILDER') –

+0

Да, забыл, извините. Затем я рассмотрю соединение с tcpdump или аналогичным, чтобы понять, что поддерживает TCP-соединение. Возможно, серверные журналы также могут быть полезны. –

ответ

0

К сожалению, я не смог найти какой-нибудь простой обходной путь (или реальное решение), так что я менеджер, чтобы написать свой собственный обходной путь, я надеюсь, это поможет кому-то с этой ошибкой:

Создать класс 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! 
}