fix: avoid EOF hang on unread pipe output (#189)
At EOF, pv waits for bytes already written to an output pipe to be consumed before finishing. If the downstream reader exits without reading those bytes, the pipe can keep reporting unread data forever. Since pv has no more input, there is no later write that would fail with EPIPE and break the wait. Detect this EOF drain-wait case with a non-blocking poll() on the output pipe. When the pipe reports that its readers are gone, mark the pipe closed and stop waiting for bytes that can no longer be consumed. While a reader is still present, keep the existing behaviour and continue waiting for the pipe buffer to drain. Add regression coverage for both ways pv can reach this state: direct input that leaves unread output bytes behind, and a pipeline where the original producer fails but an intermediate filter such as gzip still emits a valid empty stream. The latter matters because pv can still have pending output at EOF even when the command at the start of the pipeline failed immediately. Signed-off-by: Wolfgang Hoschek <wolfgang.hoschek@mac.com>
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Check that EOF does not wait forever when the output pipe reader exits
|
||||
# without consuming bytes already written into the pipe.
|
||||
|
||||
# Allow all tests to be skipped, e.g. during a release build
|
||||
test "${SKIP_ALL_TESTS}" = "1" && exit 77
|
||||
|
||||
true "${testSubject:?not set - call this from 'make check'}"
|
||||
|
||||
TIMEOUT=""
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
TIMEOUT="timeout"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
TIMEOUT="gtimeout"
|
||||
else
|
||||
echo "timeout command not available"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# The reader keeps the pipe open briefly but reads nothing. When it exits,
|
||||
# PV has no further input, so it must notice the closed reader without
|
||||
# relying on a later write returning EPIPE.
|
||||
if ! "${TIMEOUT}" 4 sh <<EOF
|
||||
printf "x" | "${testSubject}" -q -B 1 | sleep 1
|
||||
EOF
|
||||
then
|
||||
echo "PV did not finish after the output pipe reader exited"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Check that EOF does not wait forever when the upstream producer exits with
|
||||
# failure and the output pipe reader exits without consuming buffered bytes.
|
||||
|
||||
# Allow all tests to be skipped, e.g. during a release build
|
||||
test "${SKIP_ALL_TESTS}" = "1" && exit 77
|
||||
|
||||
true "${testSubject:?not set - call this from 'make check'}"
|
||||
|
||||
TIMEOUT=""
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
TIMEOUT="timeout"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
TIMEOUT="gtimeout"
|
||||
else
|
||||
echo "timeout command not available"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if ! command -v gzip >/dev/null 2>&1; then
|
||||
echo "gzip command not available"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# The producer fails immediately and writes no data. gzip still emits a small
|
||||
# valid empty stream, so PV writes bytes to its output pipe before reaching EOF.
|
||||
# The reader keeps the pipe open briefly but consumes nothing.
|
||||
if ! "${TIMEOUT}" 4 sh <<EOF
|
||||
false | gzip -1 -c -n | "${testSubject}" -q -B 1 | sleep 1
|
||||
EOF
|
||||
then
|
||||
echo "PV did not finish after the input producer failed and the output reader exited"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user