@@ -85,34 +85,54 @@ async def send_signal(self, signum: int) -> None:
85
85
os .killpg (self .pgid , signum )
86
86
return
87
87
except OSError :
88
- pass
89
- try :
90
- self .process .send_signal (signum )
91
- except OSError :
92
- pass
88
+ pass # We'll retry sending the signal to only the process below
89
+
90
+ # If we're here, send the signal to the process and let caller handle exceptions
91
+ self .process .send_signal (signum )
93
92
return
94
93
95
94
async def kill (self , restart : bool = False ) -> None :
96
95
if self .process :
96
+ if hasattr (signal , "SIGKILL" ):
97
+ # If available, give preference to signalling the process-group over `kill()`.
98
+ try :
99
+ await self .send_signal (signal .SIGKILL )
100
+ return
101
+ except OSError :
102
+ pass
97
103
try :
98
104
self .process .kill ()
99
105
except OSError as e :
100
- # In Windows, we will get an Access Denied error if the process
101
- # has already terminated. Ignore it.
102
- if sys .platform == 'win32' :
103
- if e .winerror != 5 :
104
- raise
105
- # On Unix, we may get an ESRCH error if the process has already
106
- # terminated. Ignore it.
107
- else :
108
- from errno import ESRCH
109
-
110
- if e .errno != ESRCH :
111
- raise
106
+ LocalProvisioner ._tolerate_no_process (e )
112
107
113
108
async def terminate (self , restart : bool = False ) -> None :
114
109
if self .process :
115
- return self .process .terminate ()
110
+ if hasattr (signal , "SIGTERM" ):
111
+ # If available, give preference to signalling the process group over `terminate()`.
112
+ try :
113
+ await self .send_signal (signal .SIGTERM )
114
+ return
115
+ except OSError :
116
+ pass
117
+ try :
118
+ self .process .terminate ()
119
+ except OSError as e :
120
+ LocalProvisioner ._tolerate_no_process (e )
121
+
122
+ @staticmethod
123
+ def _tolerate_no_process (os_error : OSError ):
124
+ # In Windows, we will get an Access Denied error if the process
125
+ # has already terminated. Ignore it.
126
+ if sys .platform == 'win32' :
127
+ if os_error .winerror != 5 :
128
+ raise
129
+ # On Unix, we may get an ESRCH error (or ProcessLookupError instance) if
130
+ # the process has already terminated. Ignore it.
131
+ else :
132
+ from errno import ESRCH
133
+
134
+ if not isinstance (os_error , ProcessLookupError ) or os_error .errno != ESRCH :
135
+ raise
116
136
117
137
async def cleanup (self , restart : bool = False ) -> None :
118
138
if self .ports_cached and not restart :
0 commit comments