Skip to content

Commit 219be4f

Browse files
Andy-Jostclaude
andcommitted
test(cuda.core): cover the forkserver start method for VMM IPC buffers
The forkserver is a fresh interpreter and children fork from it, so it is safe after CUDA initialization, and it is the Linux default since Python 3.14. Its Popen collects file descriptors during pickling and sends them afterwards, like spawn, so the Process-argument path in VirtualMemoryBuffer.__reduce__ has to work there too. Add a Process argument test and a Queue round trip under mp.get_context("forkserver"), skipped where that start method is unavailable. Closes #3111 Co-Authored-By: Claude Fable 5.1 <[email protected]>
1 parent 2224499 commit 219be4f

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

‎cuda_core/tests/memory_ipc/test_vmm_ipc.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,71 @@ def child_main(device_id, to_child, from_child):
649649
from_child.put(counts)
650650

651651

652+
_FORKSERVER_UNAVAILABLE = "forkserver" not in mp.get_all_start_methods()
653+
654+
655+
def _forkserver_child(device_id, buffer, report):
656+
"""Read the parent's pattern from the imported buffer, then write a new one."""
657+
device = Device(device_id)
658+
device.set_current()
659+
report.put(_read(buffer, 0, 16))
660+
_fill(buffer, 0x6C)
661+
buffer.close()
662+
663+
664+
@pytest.mark.skipif(_FORKSERVER_UNAVAILABLE, reason="the forkserver start method is not available on this platform")
665+
class TestForkserver:
666+
"""The conftest forces ``spawn``; a forkserver context passes descriptors the same way and must work too."""
667+
668+
@pytest.mark.agent_authored(model="claude-sonnet-5-5")
669+
def test_process_argument(self, vmm_ipc_device):
670+
"""A buffer passed as a forkserver Process argument imports in the child, and writes go both ways."""
671+
device = vmm_ipc_device
672+
buf = _resource(device).allocate(1)
673+
_fill(buf, 0x5D)
674+
ctx = mp.get_context("forkserver")
675+
report = ctx.Queue()
676+
process = ctx.Process(target=_forkserver_child, args=(device.device_id, buf, report))
677+
process.start()
678+
seen = report.get(timeout=CHILD_TIMEOUT_SEC)
679+
process.join(timeout=CHILD_TIMEOUT_SEC)
680+
survivors = kill_subprocesses(process)
681+
assert not survivors, "child did not exit within timeout"
682+
assert process.exitcode == 0, f"child exited with {process.exitcode}"
683+
assert seen == b"\x5d" * 16
684+
assert _read(buf, 0, 16) == b"\x6c" * 16
685+
buf.close()
686+
687+
@pytest.mark.agent_authored(model="claude-sonnet-5-5")
688+
def test_queue_round_trip(self, vmm_ipc_device):
689+
"""A buffer sent through a forkserver Queue imports in the child, and writes go both ways."""
690+
device = vmm_ipc_device
691+
buf = _resource(device).allocate(1)
692+
_fill(buf, 0x5E)
693+
ctx = mp.get_context("forkserver")
694+
to_child, from_child = ctx.Queue(), ctx.Queue()
695+
process = ctx.Process(target=self.child_main, args=(device.device_id, to_child, from_child))
696+
process.start()
697+
to_child.put(buf)
698+
seen = from_child.get(timeout=CHILD_TIMEOUT_SEC)
699+
process.join(timeout=CHILD_TIMEOUT_SEC)
700+
survivors = kill_subprocesses(process)
701+
assert not survivors, "child did not exit within timeout"
702+
assert process.exitcode == 0, f"child exited with {process.exitcode}"
703+
assert seen == b"\x5e" * 16
704+
assert _read(buf, 0, 16) == b"\x6d" * 16
705+
buf.close()
706+
707+
@staticmethod
708+
def child_main(device_id, to_child, from_child):
709+
device = Device(device_id)
710+
device.set_current()
711+
buffer = to_child.get(timeout=CHILD_TIMEOUT_SEC)
712+
from_child.put(_read(buffer, 0, 16))
713+
_fill(buffer, 0x6D)
714+
buffer.close()
715+
716+
652717
class TestFileDescriptorLimit:
653718
@pytest.mark.agent_authored(model="claude-fable-5-1")
654719
@pytest.mark.thread_unsafe(reason="changes the process-wide file descriptor limit")

0 commit comments

Comments
 (0)