Hello!
I have been using Python to launch child processes, to read from their output and to write to their input. I have been almost successfully doing that for a while, until I encountered some problems with graceful shutdown of the parent process. I decided then to use select in order to avoid blocking reads, but I’m having difficulties with that as well.
My problem is that select returns negative after a call to read on stdout, even though I know for a fact that there is still data available to read.
Here is an example:
child.py
#! /usr/bin/env python3
if __name__ == "__main__":
print("READY", flush=True)
input()
parent.py
#! /usr/bin/env python3
import subprocess
import select
def main():
process = subprocess.Popen(["./child.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
# First char read
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.read(1)
print(data)
# Second char read (select returns false)
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.read(1)
print(data)
# Third char read (select returns false)
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.read(1)
print(data)
process.stdin.write("QUIT\n")
process.stdin.flush()
process.wait()
if __name__ == "__main__":
main()
You can see that I erroneously ignore the result of select and read from the pipe anyway. The reading operation doesn’t block, as there is data. But select tells me that the read should block, that there is no data.
This was just an example. Actually I need to read whole terminated lines (with readline) and write whole terminated lines. The child program may sometimes output multiple lines per command from parent (one line of input). This issue makes it impossible to read more than one line of output without blocking, because after reading the first line, or even just one character, select returns false. select correctly returns true after I read the single line of output from the child.
child2.py
#! /usr/bin/env python3
if __name__ == "__main__":
print("READY", flush=True)
input()
print("THING1", flush=True)
input()
print("THING2", flush=True)
input()
print("THING3", flush=True)
# These two lines don't exist for select
print("OH_NO1", flush=True)
print("OH_NO2", flush=True)
input()
parent2.py
#! /usr/bin/env python3
import subprocess
import select
def main():
process = subprocess.Popen(["./child2.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
# Read READY
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
process.stdin.write("NEXT\n")
process.stdin.flush()
# Read first THING
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
process.stdin.write("NEXT\n")
process.stdin.flush()
# Read second THING
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
process.stdin.write("NEXT\n")
process.stdin.flush()
# Read third THING
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
# Read first OH_NO (select returns false)
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
# Read second OH_NO (select returns false)
result, *_ = select.select([process.stdout.fileno()], [], [], 1.0)
print(bool(result))
data = process.stdout.readline()
print(data)
process.stdin.write("QUIT\n")
process.stdin.flush()
process.wait()
if __name__ == "__main__":
main()
In my situation I don’t know in advance how many lines of output will the child send after a certain input. In the end, select becomes useless.
My question is if this is the normal behavior for select in Python, because it seems very odd to me. I have written subprocessing code in C and C++ as well in the past and the select system call wasn’t behaving like that.
I’m using Python 3.12.7, Linux 6.11.5.

