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()
, primawait(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 seread()
è 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)
Sì, sembra un bug. – immibis