Skip to content

Commit 10decd2

Browse files
authored
Merge pull request #743 from kevin-bates/prefer-process-groups
2 parents fc9b688 + db2d7c8 commit 10decd2

File tree

1 file changed

+38
-18
lines changed

1 file changed

+38
-18
lines changed

jupyter_client/provisioning/local_provisioner.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,34 +85,54 @@ async def send_signal(self, signum: int) -> None:
8585
os.killpg(self.pgid, signum)
8686
return
8787
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)
9392
return
9493

9594
async def kill(self, restart: bool = False) -> None:
9695
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
97103
try:
98104
self.process.kill()
99105
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)
112107

113108
async def terminate(self, restart: bool = False) -> None:
114109
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
116136

117137
async def cleanup(self, restart: bool = False) -> None:
118138
if self.ports_cached and not restart:

0 commit comments

Comments
 (0)