Skip to content

Commit 4848319

Browse files
committed
Apply review feedback
1 parent a6af899 commit 4848319

File tree

5 files changed

+48
-23
lines changed

5 files changed

+48
-23
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
214214
`local_pool`, hoping it will fix a race condition that can occurs when
215215
Ninja defers to SCons to build.
216216

217+
From Adam Simpkins:
218+
- Fixed a hang in `wait_for_process_to_die()` on Windows, affecting
219+
clean-up of the SCons daemon used for Ninja builds.
220+
217221

218222
RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700
219223

RELEASE.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ FIXES
183183
- Handle case of "memoizer" as one member of a comma-separated
184184
--debug string - this was previously missed.
185185

186+
- Fixed a hang in `wait_for_process_to_die()` on Windows, affecting
187+
clean-up of the SCons daemon used for Ninja builds.
188+
186189
IMPROVEMENTS
187190
------------
188191

SCons/Tool/ninja_tool/NinjaState.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,9 @@ def check_generated_source_deps(build):
694694
pass
695695

696696
# wait for the server process to fully killed
697-
# TODO: catch TimeoutException here and possibly do something smart.
698-
wait_for_process_to_die(pid, timeout=10)
697+
# TODO: update wait_for_process_to_die() to handle timeout and then catch exception
698+
# here and do something smart.
699+
wait_for_process_to_die(pid)
699700

700701
if os.path.exists(scons_daemon_dirty):
701702
os.unlink(scons_daemon_dirty)

SCons/Util/UtilTests.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import unittest.mock
3434
import warnings
3535
from collections import UserDict, UserList, UserString, namedtuple
36+
from typing import Callable
3637

3738
import TestCmd
3839

@@ -75,6 +76,7 @@
7576
to_bytes,
7677
to_str,
7778
wait_for_process_to_die,
79+
_wait_for_process_to_die_non_psutil,
7880
)
7981
from SCons.Util.envs import is_valid_construction_var
8082
from SCons.Util.hashes import (
@@ -84,6 +86,13 @@
8486
_set_allowed_viable_default_hashes,
8587
)
8688

89+
try:
90+
import psutil
91+
has_psutil = True
92+
except ImportError:
93+
has_psutil = False
94+
95+
8796
# These Util classes have no unit tests. Some don't make sense to test?
8897
# DisplayEngine, Delegate, MethodWrapper, UniqueList, Unbuffered, Null, NullSeq
8998

@@ -849,26 +858,20 @@ def test_intern(self) -> None:
849858
s4 = silent_intern("spam")
850859
assert id(s1) == id(s4)
851860

852-
def test_wait_for_process_to_die_success(self) -> None:
853-
cmd = [sys.executable, "-c", ""]
854-
p = subprocess.Popen(cmd)
855-
p.wait()
856-
wait_for_process_to_die(p.pid, timeout=10.0)
861+
@unittest.skipUnless(has_psutil, "requires psutil")
862+
def test_wait_for_process_to_die_success_psutil(self) -> None:
863+
self._test_wait_for_process(wait_for_process_to_die)
857864

858-
def test_wait_for_process_to_die_timeout(self) -> None:
859-
# Run a python script that will keep running until we close it's stdin
860-
cmd = [sys.executable, "-c", "import sys; data = sys.stdin.read()"]
861-
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
865+
def test_wait_for_process_to_die_success_non_psutil(self) -> None:
866+
self._test_wait_for_process(_wait_for_process_to_die_non_psutil)
862867

863-
# wait_for_process_to_die() should time out while the process is running
864-
with self.assertRaises(TimeoutError):
865-
wait_for_process_to_die(p.pid, timeout=0.2)
866-
867-
p.stdin.close()
868+
def _test_wait_for_process(
869+
self, wait_fn: Callable[[int], None]
870+
) -> None:
871+
cmd = [sys.executable, "-c", ""]
872+
p = subprocess.Popen(cmd)
868873
p.wait()
869-
870-
# wait_for_process_to_die() should complete normally now
871-
wait_for_process_to_die(p.pid, timeout=10.0)
874+
wait_fn(p.pid)
872875

873876

874877
class HashTestCase(unittest.TestCase):

SCons/Util/__init__.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,14 +1297,28 @@ def print_time():
12971297
return print_time
12981298

12991299

1300-
def wait_for_process_to_die(pid: int, timeout: float = 60.0) -> None:
1300+
def wait_for_process_to_die(pid: int) -> None:
13011301
"""
13021302
Wait for the specified process to die.
13031303
1304-
A TimeoutError will be thrown if the timeout is hit before the process terminates.
1305-
Specifying a negative timeout will cause wait_for_process_to_die() to wait
1306-
indefinitely, and never throw TimeoutError.
1304+
TODO: Add timeout which raises exception
13071305
"""
1306+
# wait for the process to fully killed
1307+
try:
1308+
import psutil # pylint: disable=import-outside-toplevel
1309+
while True:
1310+
# TODO: this should use psutil.process_exists() or psutil.Process.wait()
1311+
# The psutil docs explicitly recommend against using process_iter()/pids()
1312+
# for checking the existence of a process.
1313+
if pid not in [proc.pid for proc in psutil.process_iter()]:
1314+
break
1315+
time.sleep(0.1)
1316+
except ImportError:
1317+
# if psutil is not installed we can do this the hard way
1318+
_wait_for_process_to_die_non_psutil(pid, timeout=-1.0)
1319+
1320+
1321+
def _wait_for_process_to_die_non_psutil(pid: int, timeout: float = 60.0) -> None:
13081322
start_time = time.time()
13091323
while True:
13101324
if not _is_process_alive(pid):

0 commit comments

Comments
 (0)