Skip to content

Commit a468d07

Browse files
committed
try adding a test
1 parent 21ae510 commit a468d07

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
15+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t"]
1616
os: [ubuntu-latest, windows-latest, macOS-latest]
1717

1818
steps:

test_duct.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,3 +1005,47 @@ def test_zombies_reaped():
10051005
while ps_observes_pid(pid):
10061006
tries += 1
10071007
assert tries < 100, f"child #{i} (PID {pid}) never went away?"
1008+
1009+
1010+
def test_pipe_inheritance():
1011+
"""Spawn 300 child processes: 100 writers, 100 readers, and 100 waiters.
1012+
The goal is to see whether any of the waiters inherits a write end of one
1013+
of the pipes. If so, it will deadlock a reader and deadlock this test."""
1014+
waiters = []
1015+
threads = []
1016+
1017+
def reader_writer_fn():
1018+
# Open a pipe for the child to read from. This can deadlock if a waiter
1019+
# inherits this pipe and keeps it open.
1020+
reader_fd, writer_fd = os.pipe()
1021+
reader = os.fdopen(reader_fd, "rb")
1022+
writer = os.fdopen(writer_fd, "wb")
1023+
writer_child = echo_cmd("foo").stdout_file(writer).start()
1024+
writer.close()
1025+
output = cat_cmd().stdin_file(reader).read()
1026+
reader.close()
1027+
writer_child.wait()
1028+
assert output == "foo"
1029+
1030+
def waiter_fn():
1031+
# Spawn a child that won't exit until we kill it. This is intended to
1032+
# keep pipes open if they're inherited unintentionally. Without
1033+
# something like this, we'd need an inheritance *cycle* between
1034+
# readers, which might be unlikely.
1035+
waiters.append(sleep_cmd(365 * 24 * 60 * 60).unchecked().start())
1036+
1037+
for _ in range(100):
1038+
thread1 = threading.Thread(target=reader_writer_fn)
1039+
thread1.start()
1040+
threads.append(thread1)
1041+
1042+
thread2 = threading.Thread(target=waiter_fn)
1043+
thread2.start()
1044+
threads.append(thread2)
1045+
1046+
for thread in threads:
1047+
thread.join()
1048+
1049+
for waiter in waiters:
1050+
waiter.kill()
1051+
waiter.wait()

0 commit comments

Comments
 (0)