88from avocado .core .nrunner .runnable import RUNNERS_REGISTRY_STANDALONE_EXECUTABLE
99from avocado .core .plugin_interfaces import RunnableRunner
1010from avocado .core .utils import messages
11+ from avocado .utils import process
1112
1213#: The amount of time (in seconds) between each internal status check
1314RUNNER_RUN_CHECK_INTERVAL = 0.01
@@ -99,14 +100,30 @@ class PythonBaseRunner(BaseRunner, abc.ABC):
99100 Base class for Python runners
100101 """
101102
102- @staticmethod
103- def signal_handler (signum , frame ): # pylint: disable=W0613
103+ def __init__ (self ):
104+ super ().__init__ ()
105+ self .proc = None
106+ self .sigtstp = multiprocessing .Lock ()
107+ self .sigstopped = False
108+ self .timeout = float ("inf" )
109+
110+ def signal_handler (self , signum , frame ): # pylint: disable=W0613
104111 if signum == signal .SIGTERM .value :
105112 raise TestInterrupt ("Test interrupted: Timeout reached" )
106-
107- @staticmethod
108- def _monitor (proc , time_started , queue ):
109- timeout = float ("inf" )
113+ elif signum == signal .SIGTSTP .value :
114+ if self .sigstopped :
115+ self .sigstopped = False
116+ sign = signal .SIGCONT
117+ else :
118+ self .sigstopped = True
119+ sign = signal .SIGSTOP
120+ if not self .proc : # Ignore ctrl+z when proc not yet started
121+ return
122+ with self .sigtstp :
123+ self .timeout = float ("inf" )
124+ process .kill_process_tree (self .proc .pid , sign , False )
125+
126+ def _monitor (self , time_started , queue ):
110127 next_status_time = None
111128 while True :
112129 time .sleep (RUNNER_RUN_CHECK_INTERVAL )
@@ -115,37 +132,42 @@ def _monitor(proc, time_started, queue):
115132 if next_status_time is None or now > next_status_time :
116133 next_status_time = now + RUNNER_RUN_STATUS_INTERVAL
117134 yield messages .RunningMessage .get ()
118- if (now - time_started ) > timeout :
119- proc .terminate ()
135+ if (now - time_started ) > self . timeout :
136+ self . proc .terminate ()
120137 else :
121138 message = queue .get ()
122139 if message .get ("type" ) == "early_state" :
123- timeout = float (message .get ("timeout" ) or float ("inf" ))
140+ self . timeout = float (message .get ("timeout" ) or float ("inf" ))
124141 else :
125142 yield message
126143 if message .get ("status" ) == "finished" :
127144 break
145+ while self .sigstopped :
146+ time .sleep (RUNNER_RUN_CHECK_INTERVAL )
128147
129148 def run (self , runnable ):
130- # pylint: disable=W0201
149+ signal . signal ( signal . SIGTSTP , signal . SIG_IGN )
131150 signal .signal (signal .SIGTERM , self .signal_handler )
151+ signal .signal (signal .SIGTSTP , self .signal_handler )
152+ # pylint: disable=W0201
132153 self .runnable = runnable
133154 yield messages .StartedMessage .get ()
134155 try :
135156 queue = multiprocessing .SimpleQueue ()
136- process = multiprocessing .Process (
157+ self . proc = multiprocessing .Process (
137158 target = self ._run , args = (self .runnable , queue )
138159 )
139-
140- process .start ()
141-
160+ while self .sigstopped :
161+ pass
162+ with self .sigtstp :
163+ self .proc .start ()
142164 time_started = time .monotonic ()
143- for message in self ._monitor (process , time_started , queue ):
165+ for message in self ._monitor (time_started , queue ):
144166 yield message
145167
146168 except TestInterrupt :
147- process .terminate ()
148- for message in self ._monitor (process , time_started , queue ):
169+ self . proc .terminate ()
170+ for message in self ._monitor (time_started , queue ):
149171 yield message
150172 except Exception as e :
151173 yield messages .StderrMessage .get (traceback .format_exc ())
0 commit comments