Skip to content

Commit 01ed966

Browse files
committed
Merge remote-tracking branch 'upstream/main' into gh-144278
2 parents 29502a7 + 9b154ab commit 01ed966

28 files changed

Lines changed: 597 additions & 199 deletions

Doc/library/subprocess.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,14 +803,29 @@ Instances of the :class:`Popen` class have the following methods:
803803

804804
.. note::
805805

806-
When the ``timeout`` parameter is not ``None``, then (on POSIX) the
807-
function is implemented using a busy loop (non-blocking call and short
808-
sleeps). Use the :mod:`asyncio` module for an asynchronous wait: see
806+
When ``timeout`` is not ``None`` and the platform supports it, an
807+
efficient event-driven mechanism is used to wait for process termination:
808+
809+
- Linux >= 5.3 uses :func:`os.pidfd_open` + :func:`select.poll`
810+
- macOS and other BSD variants use :func:`select.kqueue` +
811+
``KQ_FILTER_PROC`` + ``KQ_NOTE_EXIT``
812+
- Windows uses ``WaitForSingleObject``
813+
814+
If none of these mechanisms are available, the function falls back to a
815+
busy loop (non-blocking call and short sleeps).
816+
817+
.. note::
818+
819+
Use the :mod:`asyncio` module for an asynchronous wait: see
809820
:class:`asyncio.create_subprocess_exec`.
810821

811822
.. versionchanged:: 3.3
812823
*timeout* was added.
813824

825+
.. versionchanged:: 3.15
826+
if *timeout* is not ``None``, use efficient event-driven implementation
827+
on Linux >= 5.3 and macOS / BSD.
828+
814829
.. method:: Popen.communicate(input=None, timeout=None)
815830

816831
Interact with process: Send data to stdin. Read data from stdout and stderr,

Doc/whatsnew/3.15.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,20 @@ ssl
743743

744744
(Contributed by Ron Frederick in :gh:`138252`.)
745745

746+
subprocess
747+
----------
748+
749+
* :meth:`subprocess.Popen.wait`: when ``timeout`` is not ``None`` and the
750+
platform supports it, an efficient event-driven mechanism is used to wait for
751+
process termination:
752+
753+
- Linux >= 5.3 uses :func:`os.pidfd_open` + :func:`select.poll`.
754+
- macOS and other BSD variants use :func:`select.kqueue` + ``KQ_FILTER_PROC`` + ``KQ_NOTE_EXIT``.
755+
- Windows keeps using ``WaitForSingleObject`` (unchanged).
756+
757+
If none of these mechanisms are available, the function falls back to the
758+
traditional busy loop (non-blocking call and short sleeps).
759+
(Contributed by Giampaolo Rodola in :gh:`83069`).
746760

747761
symtable
748762
--------

Include/internal/pycore_code.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -292,17 +292,7 @@ extern int _PyCode_SafeAddr2Line(PyCodeObject *co, int addr);
292292
extern void _PyCode_Clear_Executors(PyCodeObject *code);
293293

294294

295-
#ifdef Py_GIL_DISABLED
296-
// gh-115999 tracks progress on addressing this.
297-
#define ENABLE_SPECIALIZATION 0
298-
// Use this to enable specialization families once they are thread-safe. All
299-
// uses will be replaced with ENABLE_SPECIALIZATION once all families are
300-
// thread-safe.
301-
#define ENABLE_SPECIALIZATION_FT 1
302-
#else
303295
#define ENABLE_SPECIALIZATION 1
304-
#define ENABLE_SPECIALIZATION_FT ENABLE_SPECIALIZATION
305-
#endif
306296

307297
/* Specialization functions, these are exported only for other re-generated
308298
* interpreters to call */

Lib/subprocess.py

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,60 @@ def _use_posix_spawn():
748748
return False
749749

750750

751+
def _can_use_pidfd_open():
752+
# Availability: Linux >= 5.3
753+
if not hasattr(os, "pidfd_open"):
754+
return False
755+
try:
756+
pidfd = os.pidfd_open(os.getpid(), 0)
757+
except OSError as err:
758+
if err.errno in {errno.EMFILE, errno.ENFILE}:
759+
# transitory 'too many open files'
760+
return True
761+
# likely blocked by security policy like SECCOMP (EPERM,
762+
# EACCES, ENOSYS)
763+
return False
764+
else:
765+
os.close(pidfd)
766+
return True
767+
768+
769+
def _can_use_kqueue():
770+
# Availability: macOS, BSD
771+
names = (
772+
"kqueue",
773+
"KQ_EV_ADD",
774+
"KQ_EV_ONESHOT",
775+
"KQ_FILTER_PROC",
776+
"KQ_NOTE_EXIT",
777+
)
778+
if not all(hasattr(select, x) for x in names):
779+
return False
780+
kq = None
781+
try:
782+
kq = select.kqueue()
783+
kev = select.kevent(
784+
os.getpid(),
785+
filter=select.KQ_FILTER_PROC,
786+
flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
787+
fflags=select.KQ_NOTE_EXIT,
788+
)
789+
kq.control([kev], 1, 0)
790+
return True
791+
except OSError as err:
792+
if err.errno in {errno.EMFILE, errno.ENFILE}:
793+
# transitory 'too many open files'
794+
return True
795+
return False
796+
finally:
797+
if kq is not None:
798+
kq.close()
799+
800+
801+
_CAN_USE_PIDFD_OPEN = not _mswindows and _can_use_pidfd_open()
802+
_CAN_USE_KQUEUE = not _mswindows and _can_use_kqueue()
803+
804+
751805
# These are primarily fail-safe knobs for negatives. A True value does not
752806
# guarantee the given libc/syscall API will be used.
753807
_USE_POSIX_SPAWN = _use_posix_spawn()
@@ -2046,14 +2100,100 @@ def _try_wait(self, wait_flags):
20462100
sts = 0
20472101
return (pid, sts)
20482102

2103+
def _wait_pidfd(self, timeout):
2104+
"""Wait for PID to terminate using pidfd_open() + poll().
2105+
Linux >= 5.3 only.
2106+
"""
2107+
if not _CAN_USE_PIDFD_OPEN:
2108+
return False
2109+
try:
2110+
pidfd = os.pidfd_open(self.pid, 0)
2111+
except OSError:
2112+
# May be:
2113+
# - ESRCH: no such process
2114+
# - EMFILE, ENFILE: too many open files (usually 1024)
2115+
# - ENODEV: anonymous inode filesystem not supported
2116+
# - EPERM, EACCES, ENOSYS: undocumented; may happen if
2117+
# blocked by security policy like SECCOMP
2118+
return False
2119+
2120+
try:
2121+
poller = select.poll()
2122+
poller.register(pidfd, select.POLLIN)
2123+
events = poller.poll(timeout * 1000)
2124+
if not events:
2125+
raise TimeoutExpired(self.args, timeout)
2126+
return True
2127+
finally:
2128+
os.close(pidfd)
2129+
2130+
def _wait_kqueue(self, timeout):
2131+
"""Wait for PID to terminate using kqueue(). macOS and BSD only."""
2132+
if not _CAN_USE_KQUEUE:
2133+
return False
2134+
try:
2135+
kq = select.kqueue()
2136+
except OSError:
2137+
# likely EMFILE / ENFILE (too many open files)
2138+
return False
2139+
2140+
try:
2141+
kev = select.kevent(
2142+
self.pid,
2143+
filter=select.KQ_FILTER_PROC,
2144+
flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
2145+
fflags=select.KQ_NOTE_EXIT,
2146+
)
2147+
try:
2148+
events = kq.control([kev], 1, timeout) # wait
2149+
except OSError:
2150+
return False
2151+
else:
2152+
if not events:
2153+
raise TimeoutExpired(self.args, timeout)
2154+
return True
2155+
finally:
2156+
kq.close()
20492157

20502158
def _wait(self, timeout):
2051-
"""Internal implementation of wait() on POSIX."""
2159+
"""Internal implementation of wait() on POSIX.
2160+
2161+
Uses efficient pidfd_open() + poll() on Linux or kqueue()
2162+
on macOS/BSD when available. Falls back to polling
2163+
waitpid(WNOHANG) otherwise.
2164+
"""
20522165
if self.returncode is not None:
20532166
return self.returncode
20542167

20552168
if timeout is not None:
2056-
endtime = _time() + timeout
2169+
if timeout < 0:
2170+
raise TimeoutExpired(self.args, timeout)
2171+
started = _time()
2172+
endtime = started + timeout
2173+
2174+
# Try efficient wait first.
2175+
if self._wait_pidfd(timeout) or self._wait_kqueue(timeout):
2176+
# Process is gone. At this point os.waitpid(pid, 0)
2177+
# will return immediately, but in very rare races
2178+
# the PID may have been reused.
2179+
# os.waitpid(pid, WNOHANG) ensures we attempt a
2180+
# non-blocking reap without blocking indefinitely.
2181+
with self._waitpid_lock:
2182+
if self.returncode is not None:
2183+
return self.returncode # Another thread waited.
2184+
(pid, sts) = self._try_wait(os.WNOHANG)
2185+
assert pid == self.pid or pid == 0
2186+
if pid == self.pid:
2187+
self._handle_exitstatus(sts)
2188+
return self.returncode
2189+
# os.waitpid(pid, WNOHANG) returned 0 instead
2190+
# of our PID, meaning PID has not yet exited,
2191+
# even though poll() / kqueue() said so. Very
2192+
# rare and mostly theoretical. Fallback to busy
2193+
# polling.
2194+
elapsed = _time() - started
2195+
endtime -= elapsed
2196+
20572197
# Enter a busy loop if we have a timeout. This busy loop was
20582198
# cribbed from Lib/threading.py in Thread.wait() at r71065.
20592199
delay = 0.0005 # 500 us -> initial delay of 1 ms
@@ -2085,6 +2225,7 @@ def _wait(self, timeout):
20852225
# http://bugs.python.org/issue14396.
20862226
if pid == self.pid:
20872227
self._handle_exitstatus(sts)
2228+
20882229
return self.returncode
20892230

20902231

Lib/test/support/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,11 +1459,6 @@ def requires_specialization(test):
14591459
_opcode.ENABLE_SPECIALIZATION, "requires specialization")(test)
14601460

14611461

1462-
def requires_specialization_ft(test):
1463-
return unittest.skipUnless(
1464-
_opcode.ENABLE_SPECIALIZATION_FT, "requires specialization")(test)
1465-
1466-
14671462
def reset_code(f: types.FunctionType) -> types.FunctionType:
14681463
"""Clear all specializations, local instrumentation, and JIT code for the given function."""
14691464
f.__code__ = f.__code__.replace()

Lib/test/test_capi/test_set.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ def test_add(self):
166166
# CRASHES: add(instance, NULL)
167167
# CRASHES: add(NULL, NULL)
168168

169+
def test_add_frozenset(self):
170+
add = _testlimitedcapi.set_add
171+
frozen_set = frozenset()
172+
# test adding an element to a non-uniquely referenced frozenset throws an exception
173+
self.assertRaises(SystemError, add, frozen_set, 1)
174+
169175
def test_discard(self):
170176
discard = _testlimitedcapi.set_discard
171177
for cls in (set, set_subclass):

Lib/test/test_monitoring.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import unittest
1313

1414
import test.support
15-
from test.support import import_helper, requires_specialization_ft, script_helper
15+
from test.support import import_helper, requires_specialization, script_helper
1616

1717
_testcapi = import_helper.import_module("_testcapi")
1818
_testinternalcapi = import_helper.import_module("_testinternalcapi")
@@ -1047,7 +1047,7 @@ def func():
10471047
)
10481048
self.assertEqual(events[0], ("throw", IndexError))
10491049

1050-
@requires_specialization_ft
1050+
@requires_specialization
10511051
def test_no_unwind_for_shim_frame(self):
10521052
class ValueErrorRaiser:
10531053
def __init__(self):

0 commit comments

Comments
 (0)