I'm not sure, but I'm pretty certain that I've found a bug (or a non-documented feature) in the Oracle Java implementation (1.7.0_67 and 1.8.0_31 I could verify as affected).
The Symptom
When the pipe is full, a write to the pipe might wait up to one second longer than needed for the pipe to become free again. A minimal example of the problem is as follows (I've pushed the sample shown here to 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
and pos
are connected PipedInputStream
and PipedOutputStream
instances, respectively. logA
and logB
are helper functions which output the thread name (A or B), a timestamp in milliseconds and the message. The output is as follows:
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.
As one can see, there is one second (1000 ms) between B: Done
and A: Done
. This is caused by the implementation of PipedInputStream
in the Oracle Java 1.7.0_67, which is as follows:
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();
}
}
}
The wait(1000)
is only interrupted by either hitting the timeout (1000 ms, as seen above), or a call to notifyAll()
, which only happens in the following cases:
- In
awaitSpace()
, beforewait(1000)
, as we can seein the snippet above - In
receivedLast()
, which is called when the stream is closed (not applicable here) - In
read()
, but only ifread()
is waiting for an empty buffer to fill up -- also not applicable here
The Question
Does anyone have enough experience with Java to tell me whether this is supposed to be intended behavior? The method awaitSpace()
is used by PipedOutputStream.write(...)
to wait for free space, and their contract simply states:
This method blocks until all the bytes are written to the output stream.
While this is strictly not violated, 1 second of waiting time seems quite long. If I were to fix this (minimize/lower waiting time), I'd suggest inserting a notifyAll()
at the end of each read to make sure that a waiting writer gets notified. To avoid additional time overhead for synchronization, a simple boolean flag could be used (and wouldn't hurt thread safety).
Affected Java Versions
So far, I could verify this on Java 7 and Java 8, the following versions to be precise:
$ 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)