2015-02-19 8 views
16

Non sono sicuro, ma sono abbastanza certo di aver trovato un bug (o una funzionalità non documentata) nell'implementazione di Oracle Java (1.7.0_67 e 1.8.0_31 potrei verificare come interessato).Ho trovato un bug in java.io.PipedInputStream?

il sintomo

Quando il tubo è pieno, una scrittura al tubo potrebbe attendere fino a un secondo in più del necessario per il tubo di diventare di nuovo libero. Un esempio minimo del problema è il seguente (ho spinto l'esempio mostrato qui a repository on GitHub):

private static void threadA() throws IOException, InterruptedException { 
    logA("Filling pipe..."); 
    pos.write(new byte[5]); 
    logA("Pipe full. Writing one more byte..."); 
    pos.write(0); 
    logA("Done."); 
} 

private static void threadB() throws IOException, InterruptedException { 
    logB("Sleeping a bit..."); 
    Thread.sleep(100); 
    logB("Making space in pipe..."); 
    pis.read(); 
    logB("Done."); 
} 

pis e pos sono collegati PipedInputStream e PipedOutputStream casi, rispettivamente. logA e logB sono funzioni di supporto che generano il nome del thread (A o B), un timestamp in millisecondi e il messaggio. L'uscita è il seguente:

 0 A: Filling pipe... 
    6 B: Sleeping a bit... 
    7 A: Pipe full. Writing one more byte... 
    108 B: Making space in pipe... 
    109 B: Done. 
    1009 A: Done. 

Come si può vedere, v'è un secondo (1000 ms) tra B: Done e A: Done. Ciò è causato dalla realizzazione di PipedInputStream in Oracle Java 1.7.0_67, che è la seguente:

private void awaitSpace() throws IOException { 
    while (in == out) { 
     checkStateForReceive(); 

     /* full: kick any waiting readers */ 
     notifyAll(); 
     try { 
      wait(1000); 
     } catch (InterruptedException ex) { 
      throw new java.io.InterruptedIOException(); 
     } 
    } 
} 

Il wait(1000) è interrotto soltanto da uno colpendo il timeout (1000 ms, come visto sopra), o una chiamata a notifyAll(), che avviene solo nei seguenti casi:

  • in awaitSpace(), prima wait(1000), come si può seein frammento di sopra
  • in receivedLast(), che viene chiamata quando il flusso è chiuso (non appli Cavo qui)
  • In read(), ma solo se read() è in attesa di un buffer vuoto da riempire - anche non applicabile qui

La questione

Qualcuno ha abbastanza esperienza con Java per dirmi se questo dovrebbe essere un comportamento previsto? Il metodo awaitSpace() viene utilizzato da PipedOutputStream.write(...) di aspettare per lo spazio libero, e il contratto dichiara semplicemente:

Questo metodo blocca fino a quando tutti i byte vengono scritti nel flusso di output.

Mentre questo è rigorosamente non violato, 1 secondo del tempo di attesa sembra abbastanza lungo. Se dovessi risolvere questo problema (ridurre al minimo/ridurre i tempi di attesa), ti suggerirei di inserire uno notifyAll() alla fine di ogni lettura per assicurarti che un writer in attesa venga avvisato. Per evitare un ulteriore sovraccarico di tempo per la sincronizzazione, potrebbe essere utilizzato un semplice flag booleano (che non danneggerebbe la sicurezza del thread).

versioni di Java interessate

Finora, ho potuto verificare questo su Java 7 e Java 8, le seguenti versioni per essere precisi:

$ java -version 
java version "1.7.0_67" 
Java(TM) SE Runtime Environment (build 1.7.0_67-b01) 
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode) 

$ java -version 
java version "1.8.0_31" 
Java(TM) SE Runtime Environment (build 1.8.0_31-b13) 
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode) 
+1

Sì, sembra un bug. – immibis

risposta

10

Questo problema è ben noto nel Piped*Stream e finale la risoluzione (per JDK-8014239) è "Non risolverà".

JDK-4545831: problemi di prestazioni PipedInputStream

I blocchi classe di lettura quando il buffer è vuoto e blocchi per scrivere allora il buffer è pieno. Blocca da calling wait (1000), tuttavia un lettore verrà solo svegliato da uno scrittore che incontra un buffer completo (o il timeout di attesa) e uno scrittore solo verrà riattivato da un lettore che incontra un buffer vuoto (o i tempi di attesa out).

Soluzione cliente: Notify() ing l'PipedInputStream dopo ogni read()/write() sarebbe probabilmente risolvere il problema, ma si traduce ancora in prestazioni non ottimali come molti inutili notificare() chiamate vengono fatta.


JDK-4404700: PipedInputStream troppo lento a causa di polling (attuazione alt proposto)

Il java.io.PipedInputStream è troppo lento perché i sondaggi per verificare la presenza di nuovi dati. Ogni secondo verifica se sono disponibili nuovi dati. Quando i dati sono disponibili, lo potenzialmente consuma quasi un secondo. Ha anche un piccolo buffer non memorizzabile. Propongo di considerare la seguente implementazione di PipedInputStream e di PipedOutputStream, che è più semplice e molto più veloce.

BT2: VALUTAZIONE

Dobbiamo tenere questo in giro come un obiettivo di opportunità per merlin e la tigre. A causa dell'età delle classi il codice inviato è progettato per sostituire , potrebbero esserci problemi di compatibilità nel suo utilizzo.


JDK-8014239: PipedInputStream non notificare i lettori in attesa di ricevere

Durante la lettura/scrittura da coppia di PipedInputStream/PipedOutputStream, read() blocchi esattamente per un secondo quando nuovi dati è scritto in PipedOutputStream. La ragione di ciò è che PipedInputStream riattiva solo i lettori in attesa, quando durante la ricezione() il buffer viene riempito. La soluzione è molto semplice, aggiungi un notifyAll() alla fine di entrambi i metodi receive() in PipedInputStream.

Non è ovvio come la maggior parte degli scenari di vita reale possa beneficiare della modifica proposta . Le notifiche per scrittura possono portare a una stalla non necessaria . Sconfiggendo così uno degli scopi principali di un pipe - time-disaccoppiare i lettori da scrittori e buffering. PipedInputStream/PipedWriter API ci offre un modo flessibile per controllare la frequenza con cui vorremmo che i lettori venissero notificati su nuovi dati. Vale a dire, flush(). Chiamando flush() al momento giusto possiamo controllare la latenza e il throughput.

+0

Durante la ricerca di segnalazioni di bug esistenti, non ho deliberatamente considerato OpenJDK poiché ho pensato che fosse un'implementazione JVM diversa. Condividono gli stessi stati dei bug, es. il "Will not Fix" si applica anche all'implementazione di Oracle? –

+0

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014239, guarda anche la risposta per http://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-open -jdk-and-garbage-collection – Nikolay

+0

Grazie per il collegamento al bug - questo risponde pienamente alla mia domanda. Inoltre, grazie per aver fornito chiarimenti in merito a OpenJDK e Oracle JDK. –

Problemi correlati