1212memory drops below six percent, so this is the common failure there and it is
1313completely silent.
1414
15- The test replaces worker construction with a lightweight signal injection, so
16- it exercises the real handler and outer exception path without initializing a
17- distributed process group or model.
15+ The tests replace startup and worker construction with lightweight signal
16+ injection, so they exercise the real handler and outer exception path without
17+ initializing a distributed process group or model.
1818"""
1919from __future__ import annotations
2020
@@ -30,6 +30,10 @@ class _ReadyPipe:
3030
3131 def __init__ (self ) -> None :
3232 self .closed = False
33+ self .messages = []
34+
35+ def send (self , message ) -> None :
36+ self .messages .append (message )
3337
3438 def close (self ) -> None :
3539 self .closed = True
@@ -48,22 +52,40 @@ def parent(self):
4852 (signal .SIGINT , "user interrupted" , "out-of-memory daemon" ),
4953 ],
5054)
55+ @pytest .mark .parametrize ("injection_point" , ["startup" , "worker_construction" , "worker_loop" ])
5156def test_worker_main_reports_received_signal (monkeypatch : pytest .MonkeyPatch , signum : int , expected_reason : str ,
52- unexpected_reason : str ) -> None :
57+ unexpected_reason : str , injection_point : str ) -> None :
5358 installed_handlers = {}
5459 logged_messages = []
60+ workers = []
5561 ready_pipe = _ReadyPipe ()
5662
5763 def install_handler (installed_signum , handler ):
5864 installed_handlers [installed_signum ] = handler
5965
66+ def inject_signal () -> None :
67+ installed_handlers [signum ](signum , None )
68+
6069 class SignalledWorker :
6170
71+ READY_STR = "READY"
72+
6273 def __init__ (self , * args , ** kwargs ) -> None :
63- installed_handlers [signum ](signum , None )
74+ self .shutdown_called = False
75+ workers .append (self )
76+ if injection_point == "worker_construction" :
77+ inject_signal ()
78+
79+ def worker_busy_loop (self ) -> None :
80+ assert injection_point == "worker_loop"
81+ inject_signal ()
82+
83+ def shutdown (self ) -> None :
84+ self .shutdown_called = True
6485
6586 monkeypatch .setattr (multiproc_executor .signal , "signal" , install_handler )
66- monkeypatch .setattr (multiproc_executor , "kill_itself_when_parent_died" , lambda : None )
87+ monkeypatch .setattr (multiproc_executor , "kill_itself_when_parent_died" ,
88+ inject_signal if injection_point == "startup" else lambda : None )
6789 monkeypatch .setattr (multiproc_executor .faulthandler , "enable" , lambda : None )
6890 monkeypatch .setattr (multiproc_executor .psutil , "Process" , _ParentlessProcess )
6991 monkeypatch .setattr (multiproc_executor , "WorkerMultiprocProc" , SignalledWorker )
@@ -80,3 +102,31 @@ def __init__(self, *args, **kwargs) -> None:
80102 assert f"Worker 7 received { signal .Signals (signum ).name } ({ signum } )" in logged_messages [0 ]
81103 assert expected_reason in logged_messages [0 ]
82104 assert unexpected_reason not in logged_messages [0 ]
105+ if injection_point == "worker_loop" :
106+ assert ready_pipe .messages == [{"status" : "READY" }]
107+ assert workers [0 ].shutdown_called
108+
109+
110+ def test_worker_main_preserves_unrelated_system_exit (monkeypatch : pytest .MonkeyPatch ) -> None :
111+ logged_messages = []
112+ ready_pipe = _ReadyPipe ()
113+
114+ class ExitingWorker :
115+
116+ def __init__ (self , * args , ** kwargs ) -> None :
117+ raise SystemExit (23 )
118+
119+ monkeypatch .setattr (multiproc_executor .signal , "signal" , lambda * args : None )
120+ monkeypatch .setattr (multiproc_executor , "kill_itself_when_parent_died" , lambda : None )
121+ monkeypatch .setattr (multiproc_executor .faulthandler , "enable" , lambda : None )
122+ monkeypatch .setattr (multiproc_executor .psutil , "Process" , _ParentlessProcess )
123+ monkeypatch .setattr (multiproc_executor , "WorkerMultiprocProc" , ExitingWorker )
124+ monkeypatch .setattr (multiproc_executor .logger , "exception" ,
125+ lambda message , * args : logged_messages .append (message % args ))
126+
127+ with pytest .raises (SystemExit ) as exc_info :
128+ WorkerMultiprocProc .worker_main (ready_pipe = ready_pipe , rank = 7 )
129+
130+ assert exc_info .value .code == 23
131+ assert ready_pipe .closed
132+ assert logged_messages == []
0 commit comments