2015-03-26 1 views
1

J'ai le projet, dans lequel je télécharge simultanément de nombreuses pages dans de nombreuses tâches, qui sont traitées via ThreadPool (size = 200). Toutes ces tâches utilisent la même méthode getPage pour le téléchargement de la page (avec Apache Commons HttpClient et Apache Commons IO):Se coincer à 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"); 
    } 
} 

tout HTTP_CLIENT_BUILDER est un champ statique initialisé cette façon:

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()); 

problème instruction: à un moment donné (lorsque la plupart des tâches sont terminées) tous les threads restants sont bloqués à la méthode native SocketInputStream.socketRead0, donc jdb dit, qu'ils sont tous en cours d'exécution (hmm, ouais, je m'attends à ce comportement avec natif rencontré Hod course :-)):

> 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 

Je ne comprends pas, pourquoi cela peut arriver, mais je l'ai trouvé Java bug, qui est peut-être liée à la question. Alors peut-être que je ne cherche pas de solution réelle, mais pour une solution de contournement.

Étant donné que le bug est déposée contre Linux, je dois dire que je suis aussi en utilisant la machine virtuelle en cours d'exécution Ubuntu 14.04 x86_64

UPD: OK, ce que j'ai essayé est maintenant ajouter un nouveau délai d'attente avec setConnectionRequestTimeout (juste pour se assurer, il ne fonctionne pas) ajouter finally bloc getPage sont acceptés dans les:

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

Voyons voir, si cela aide.

UPD2: cela semble aider un petit peu, mais encore, j'ai cette permanence des tâches en cours d'exécution coincé environ une fois par jour.

+0

fil faisant 'Socket.read' sera montré comme' Runnable', voir ce SO après: http://stackoverflow.com/questions/12544212. Très probablement, le côté distant garde sa fin de socket ouverte, c'est pourquoi vos tâches ne peuvent pas finir. Par exemple, vous avez envoyé plus de tâches à un Executor que nécessaire pour télécharger des ressources distantes, et le reste des tâches inactives est laissé en attente. –

+0

@VictorSorokin qui ne devrait pas se produire car j'ai défini des délais d'attente (voir initialiseur pour 'HTTP_CLIENT_BUILDER') –

+0

Oui, négligé, désolé. Ensuite, j'examinerais la connexion avec tcpdump ou similaire pour comprendre ce qui maintient la connexion TCP en vie. Peut-être, les journaux côté serveur peuvent être utiles aussi. –

Répondre

0

Malheureusement, je n'ai pas réussi à trouver une solution simple (ou la vraie solution), donc j'ai gestionnaire d'écrire ma propre solution, je l'espère que ça va aider quelqu'un avec cette erreur:

Créer une classe 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(); 
} 

quelque part il devrait y avoir une instance de ConnectionSupervisor, quelque chose comme:

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

quelque chose comme getPage méthode:

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! 
}