2015-01-02 7 views
7

Ho riscontrato uno strano comportamento del metodo CompletableFuture thenCompose di Java 8. Ho due test che differiscono solo in ordine di esecuzione. Entrambi i test simulano il fallimento di CompletableFuture generato in thenCose.Perché Java 8 CompletableFuture thenCompose genera un'eccezione diversa in base all'ordine di completamento?

@Test 
public void completedAfter() { 
    CompletableFuture<String> future1 = new CompletableFuture<>(); 
    CompletableFuture<String> future2 = new CompletableFuture<>(); 

    future1.thenCompose(x -> future2).whenComplete((r, e) -> System.out.println("After: " + e)); 

    future1.complete("value"); 
    future2.completeExceptionally(new RuntimeException()); 
} 

@Test 
public void completedBefore() { 
    CompletableFuture<String> future1 = new CompletableFuture<>(); 
    CompletableFuture<String> future2 = new CompletableFuture<>(); 

    future1.complete("value"); 
    future2.completeExceptionally(new RuntimeException()); 

    future1.thenCompose(x -> future2).whenComplete((r, e) -> System.out.println("Before: " +e)); 
} 

L'output è:

After: java.util.concurrent.CompletionException: java.lang.RuntimeException 
Before: java.lang.RuntimeException 

La domanda è: perché è l'eccezione avvolto in CompletionException in un caso ma non nell'altro?

Aggiornamento: Here è un bug report correlato. È stato contrassegnato e risolto come un bug in JDK.

+1

Hai guardato ListenableFuture di Guava? Se non sei bloccato nel framework, ti ​​suggerisco di utilizzare l'implementazione di Guava. Nella mia visione totalmente opinata, è una funzionalità completa e molto più facile da usare. Disclamer, non sono in alcun modo affiliato con Google. –

+1

C'è stato un sacco di traffico su Completablefuture sulla lista degli interessi di concorrenza. Le liste di novembre potrebbero interessarti: http://cs.oswego.edu/pipermail/concurrency-interest/2014-November/subject.html – edharned

risposta

2

Sembra un errore nella libreria jdk.

Nel caso "Dopo", .thenCompose aggiunge un nodo di completamento ThenCopy al futuro di destinazione, la cui esecuzione viene successivamente attivata da .completeExceptionally. Il metodo run del nodo di completamento trova l'eccezione nel futuro e chiama .internalComplete nella destinazione, che include tutte le eccezioni in CompletionException. Vedere here come viene creato il nodo e here per il punto in cui avviene il wrapping.

Ora, nel caso Before, il percorso del codice è completamente diverso. Poiché il futuro è già completato, .thenCompose non crea nodi aggiuntivi, ma il richiamo immediato è invokes e restituisce semplicemente un (già completato secondo futuro), sul quale si chiama quindi .whenComplete, che, ancora una volta, non si preoccupa di creare un nuovo nodo di completamento, ma semplicemente invokes the callback, dandogli l'eccezione originale dal secondo futuro.

ragazzo, guardando a questo codice, ci sono tanti esempi che voglio mostrare ai miei studenti di ciò che non dovrebbe mai fare ...

+0

Perché un bug? Il comportamento viola qualsiasi contratto? –

+0

@MarkoTopolnik Non è chiaro. Questo ha a che fare con 'CompletionStage' dipendente. _Le dipendenze su un singolo stadio sono organizzate usando metodi con prefisso 'then'._ e _se il calcolo di uno stadio termina bruscamente con un'eccezione o un errore (non verificato), quindi tutte le fasi dipendenti che richiedono il suo completamento sono completate eccezionalmente, con una tenuta 'CompletionException' l'eccezione come sua causa._ –

+0

@MarkoTopolnik Non l'ho visto menzionato esplicitamente da nessuna parte in cui tutte le eccezioni, lanciate dal futuro, saranno incluse in una 'CompletableException', ma dubito che l'intento sia che sono * qualche volta * avviluppate, e a volte non lo sono, a seconda del ... cosa? velocità della CPU? Voglio dire, questo comportamento non è deterministico. Forse, un "bug" era troppo forte di una parola. Chiamiamolo "caratteristica indesiderata"? – Dima

Problemi correlati