@@ -33,13 +33,31 @@ We wait for a child created using the ``subprocess`` module::
33
33
>>> p.poll() # p should be finished
34
34
0
35
35
36
- Now using the ``multiprocessing`` module::
36
+ Now using the ``multiprocessing`` module with ANY start method ::
37
37
38
38
>>> from cysignals.pselect import PSelecter
39
- >>> from multiprocessing import *
40
- >>> import time
39
+ >>> from multiprocessing import get_context
40
+ >>> import time, sys
41
+ >>> # Works with any start method - uses process sentinel
42
+ >>> ctx = get_context() # Uses default (forkserver on 3.14+, fork on older)
43
+ >>> with PSelecter() as sel:
44
+ ... p = ctx.Process(target=time.sleep, args=(0.5,))
45
+ ... p.start()
46
+ ... # Monitor process.sentinel instead of SIGCHLD
47
+ ... r, w, x, t = sel.pselect(rlist=[p.sentinel], timeout=2)
48
+ ... p.is_alive() # p should be finished
49
+ False
50
+
51
+ For SIGCHLD-based monitoring (requires 'fork' on Python 3.14+)::
52
+
53
+ >>> import signal
54
+ >>> def dummy_handler(sig, frame):
55
+ ... pass
56
+ >>> _ = signal.signal(signal.SIGCHLD, dummy_handler)
57
+ >>> # Use 'fork' method for SIGCHLD to work properly
58
+ >>> ctx = get_context('fork') if sys.version_info >= (3, 14) else get_context()
41
59
>>> with PSelecter([signal.SIGCHLD]) as sel:
42
- ... p = Process(target=time.sleep, args=(1 ,))
60
+ ... p = ctx. Process(target=time.sleep, args=(0.5 ,))
43
61
... p.start()
44
62
... _ = sel.sleep()
45
63
... p.is_alive() # p should be finished
@@ -289,12 +307,14 @@ cdef class PSelecter:
289
307
290
308
Start a process which will cause a ``SIGCHLD`` signal::
291
309
292
- >>> import time
293
- >>> from multiprocessing import *
310
+ >>> import time, sys
311
+ >>> from multiprocessing import get_context
294
312
>>> from cysignals.pselect import PSelecter, interruptible_sleep
313
+ >>> # For SIGCHLD, must use 'fork' on Python 3.14+
314
+ >>> ctx = get_context('fork') if sys.version_info >= (3, 14) else get_context()
295
315
>>> w = PSelecter([signal.SIGCHLD])
296
316
>>> with w:
297
- ... p = Process(target=time.sleep, args=(0.25,))
317
+ ... p = ctx. Process(target=time.sleep, args=(0.25,))
298
318
... t0 = time.time()
299
319
... p.start()
300
320
@@ -501,3 +521,66 @@ cdef class PSelecter:
501
521
502
522
"""
503
523
return self .pselect(timeout = timeout)[3 ]
524
+
525
+ def wait_for_process (self , process , timeout = None ):
526
+ """
527
+ Wait until a multiprocessing.Process exits or timeout occurs.
528
+
529
+ This works with ANY multiprocessing start method (fork, spawn, forkserver)
530
+ by monitoring the process sentinel file descriptor instead of SIGCHLD.
531
+
532
+ NOTE: This is POSIX-only. On Windows, use ``multiprocessing.connection.wait()``
533
+ instead.
534
+
535
+ INPUT:
536
+
537
+ - ``process`` -- a ``multiprocessing.Process`` object
538
+
539
+ - ``timeout`` -- (default: ``None``) a timeout in seconds,
540
+ where ``None`` stands for no timeout.
541
+
542
+ OUTPUT: A boolean which is ``True`` if the call timed out,
543
+ False if the process exited.
544
+
545
+ EXAMPLES:
546
+
547
+ Works with any multiprocessing start method::
548
+
549
+ >>> from cysignals.pselect import PSelecter
550
+ >>> from multiprocessing import get_context
551
+ >>> import time
552
+ >>> # Use default start method (forkserver on 3.14+)
553
+ >>> ctx = get_context()
554
+ >>> sel = PSelecter()
555
+ >>> p = ctx.Process(target=time.sleep, args=(0.1,))
556
+ >>> p.start()
557
+ >>> timed_out = sel.wait_for_process(p, timeout=1)
558
+ >>> timed_out # Should be False (process exited)
559
+ False
560
+ >>> p.is_alive()
561
+ False
562
+
563
+ Can also be used in a with block::
564
+
565
+ >>> with PSelecter() as sel:
566
+ ... p = ctx.Process(target=time.sleep, args=(0.1,))
567
+ ... p.start()
568
+ ... timed_out = sel.wait_for_process(p, timeout=1)
569
+ >>> timed_out
570
+ False
571
+
572
+ TESTS:
573
+
574
+ Timeout case::
575
+
576
+ >>> p = ctx.Process(target=time.sleep, args=(10,))
577
+ >>> p.start()
578
+ >>> timed_out = sel.wait_for_process(p, timeout=0.1)
579
+ >>> timed_out # Should be True (timeout)
580
+ True
581
+ >>> p.terminate()
582
+ >>> p.join()
583
+
584
+ """
585
+ _, _, _, timed_out = self .pselect(rlist = [process.sentinel], timeout = timeout)
586
+ return timed_out
0 commit comments