Skip to content

Commit 66e367b

Browse files
authored
Merge branch 'main' into fix/gzip-seek-write-position
2 parents bc1bbbe + c1e20aa commit 66e367b

21 files changed

Lines changed: 315 additions & 110 deletions

File tree

‎Doc/library/mmap.rst‎

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ where :class:`bytearray` are expected; for example, you can use the :mod:`re`
1414
module to search through a memory-mapped file. You can also change a single
1515
byte by doing ``obj[index] = 97``, or change a subsequence by assigning to a
1616
slice: ``obj[i1:i2] = b'...'``. You can also read and write data starting at
17-
the current file position, and :meth:`seek` through the file to different positions.
17+
the current file position, and :meth:`~mmap.mmap.seek` through the file to
18+
different positions.
1819

1920
A memory-mapped file is created by the :class:`~mmap.mmap` constructor, which is
2021
different on Unix and on Windows. In either case you must provide a file
@@ -103,7 +104,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
103104
*prot*, if specified, gives the desired memory protection; the two most
104105
useful values are :const:`PROT_READ` and :const:`PROT_WRITE`, to specify
105106
that the pages may be read or written. *prot* defaults to
106-
:const:`PROT_READ \| PROT_WRITE`.
107+
:const:`PROT_READ` \| :const:`PROT_WRITE`.
107108

108109
*access* may be specified in lieu of *flags* and *prot* as an optional
109110
keyword parameter. It is an error to specify both *flags*, *prot* and
@@ -386,9 +387,55 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
386387
the mmap was created with :const:`ACCESS_READ`, then writing to it will
387388
raise a :exc:`TypeError` exception.
388389

390+
.. _access-constants:
391+
392+
ACCESS_* constants
393+
++++++++++++++++++
394+
395+
.. data:: ACCESS_DEFAULT
396+
ACCESS_READ
397+
ACCESS_WRITE
398+
ACCESS_COPY
399+
400+
These constants specify the access mode for a memory mapping.
401+
:const:`ACCESS_READ` provides read-only access, :const:`ACCESS_WRITE`
402+
provides write-through access, and :const:`ACCESS_COPY` provides
403+
copy-on-write access. :const:`ACCESS_DEFAULT` defers to the *prot*
404+
argument of :class:`mmap`.
405+
406+
.. _page-size-constants:
407+
408+
Page size constants
409+
+++++++++++++++++++
410+
411+
.. data:: PAGESIZE
412+
ALLOCATIONGRANULARITY
413+
414+
:const:`PAGESIZE` is the system memory page size in bytes.
415+
:const:`ALLOCATIONGRANULARITY` is the minimum alignment in bytes required
416+
for the *offset* argument of :class:`mmap`. On Unix,
417+
:const:`ALLOCATIONGRANULARITY` is equal to :const:`PAGESIZE`.
418+
419+
.. _prot-constants:
420+
421+
PROT_* constants
422+
++++++++++++++++
423+
424+
.. data:: PROT_READ
425+
PROT_WRITE
426+
PROT_EXEC
427+
428+
Memory protection flags accepted by the *prot* argument of :class:`mmap`.
429+
:const:`PROT_READ` allows pages to be read, :const:`PROT_WRITE` allows pages
430+
to be written, and :const:`PROT_EXEC` allows pages to be executed. Values
431+
can be combined using the bitwise OR operator. Not every option is present
432+
on every system.
433+
434+
.. availability:: Unix.
435+
389436
.. _madvise-constants:
390437

391-
MADV_* Constants
438+
MADV_* constants
392439
++++++++++++++++
393440

394441
.. data:: MADV_NORMAL
@@ -425,7 +472,7 @@ MADV_* Constants
425472

426473
.. _map-constants:
427474

428-
MAP_* Constants
475+
MAP_* constants
429476
+++++++++++++++
430477

431478
.. data:: MAP_SHARED
@@ -472,7 +519,7 @@ MAP_* Constants
472519

473520
.. _ms-constants:
474521

475-
MS_* Constants
522+
MS_* constants
476523
++++++++++++++
477524

478525
.. data:: MS_SYNC

‎Doc/library/profiling.sampling.rst‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,8 +1270,8 @@ The :option:`--compression` option controls data compression:
12701270

12711271
python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py
12721272

1273-
To analyze binary profiles, use the :ref:`replay-command` to convert them to
1274-
other formats like flame graphs or pstats output.
1273+
To analyze binary profiles, use the :ref:`replay command <replay-command>` to convert
1274+
them to other formats like flame graphs or pstats output.
12751275

12761276

12771277
Record and replay workflow

‎Doc/library/wave.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,15 @@ The :mod:`!wave` module defines the following function and exception:
7474

7575
Format code for IEEE floating-point audio.
7676

77+
.. versionadded:: 3.15
78+
7779

7880
.. data:: WAVE_FORMAT_EXTENSIBLE
7981

8082
Format code for WAVE extensible headers.
8183

84+
.. versionadded:: 3.12
85+
8286

8387
.. _wave-read-objects:
8488

@@ -281,6 +285,8 @@ Wave_write Objects
281285
When setting :data:`WAVE_FORMAT_IEEE_FLOAT`, the sample width must be
282286
4 or 8 bytes.
283287

288+
.. versionadded:: 3.15
289+
284290

285291
.. method:: getformat()
286292

@@ -300,6 +306,9 @@ Wave_write Objects
300306

301307
For ``format=WAVE_FORMAT_IEEE_FLOAT``, *sampwidth* must be 4 or 8.
302308

309+
.. versionchanged:: 3.15
310+
Added optional item *format* to the *tuple*.
311+
303312

304313
.. method:: getparams()
305314

‎Doc/tools/.nitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ Doc/library/email.charset.rst
88
Doc/library/email.parser.rst
99
Doc/library/logging.config.rst
1010
Doc/library/logging.handlers.rst
11-
Doc/library/mmap.rst
1211
Doc/library/multiprocessing.rst
1312
Doc/library/optparse.rst
1413
Doc/library/pickletools.rst

‎Lib/asyncio/unix_events.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,15 @@ def _do_waitpid(self, loop, expected_pid, callback, args):
968968
# This makes the reaping of the child and notification of the return code
969969
# atomic with respect to the event loop thread.
970970
try:
971-
os.waitid(os.P_PID, expected_pid, os.WEXITED | os.WNOWAIT)
971+
while True:
972+
res = os.waitid(os.P_PID, expected_pid, os.WEXITED | os.WNOWAIT)
973+
# On macOS, waitid() with WEXITED incorrectly returns stopped
974+
# child processes (si_code == CLD_STOPPED). Consume the stop
975+
# notification so we wait for actual process termination.
976+
if res.si_code == os.CLD_STOPPED:
977+
os.waitid(os.P_PID, expected_pid, os.WSTOPPED | os.WNOHANG)
978+
continue
979+
break
972980
except ChildProcessError:
973981
# The child process is already reaped
974982
pass

‎Lib/test/test_asyncio/test_subprocess.py‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import signal
44
import sys
55
import textwrap
6+
import threading
67
import unittest
78
import warnings
89
from unittest import mock
@@ -1162,6 +1163,63 @@ async def run():
11621163

11631164
self.loop.run_until_complete(run())
11641165

1166+
@unittest.skipUnless(hasattr(os, 'waitid'), 'needs os.waitid()')
1167+
@unittest.skipUnless(hasattr(signal, 'SIGSTOP'), 'needs SIGSTOP')
1168+
def test_stopped_child_does_not_block_event_loop(self):
1169+
# gh-158287: on macOS, waitid() with WEXITED incorrectly reports
1170+
# stopped children (si_code == CLD_STOPPED). The child watcher
1171+
# must not mistake this for process termination and attempt to reap
1172+
# the child with waitpid(), which blocks the event loop thread.
1173+
proc = None
1174+
timed_out = False
1175+
1176+
def watchdog():
1177+
nonlocal timed_out
1178+
timed_out = True
1179+
# If the event loop gets stuck in waitpid() because of this bug,
1180+
# killing the child process unblocks waitpid() so the test fails
1181+
# instead of hanging indefinitely.
1182+
if proc is not None:
1183+
if hasattr(signal, 'SIGCONT'):
1184+
try:
1185+
os.kill(proc.pid, signal.SIGCONT)
1186+
except ProcessLookupError:
1187+
pass
1188+
try:
1189+
os.kill(proc.pid, signal.SIGKILL)
1190+
except ProcessLookupError:
1191+
pass
1192+
1193+
timer = threading.Timer(support.SHORT_TIMEOUT, watchdog)
1194+
timer.start()
1195+
try:
1196+
async def run():
1197+
nonlocal proc
1198+
proc = await asyncio.create_subprocess_exec(*PROGRAM_BLOCKED)
1199+
try:
1200+
proc.send_signal(signal.SIGSTOP)
1201+
# Verify event loop remains responsive while child is stopped
1202+
for _ in range(3):
1203+
await asyncio.sleep(0.05)
1204+
self.assertFalse(timed_out, "event loop was blocked by stopped child")
1205+
self.assertIsNone(proc.returncode)
1206+
finally:
1207+
if hasattr(signal, 'SIGCONT'):
1208+
try:
1209+
proc.send_signal(signal.SIGCONT)
1210+
except ProcessLookupError:
1211+
pass
1212+
try:
1213+
proc.kill()
1214+
except ProcessLookupError:
1215+
pass
1216+
await proc.wait()
1217+
1218+
self.loop.run_until_complete(run())
1219+
finally:
1220+
timer.cancel()
1221+
timer.join()
1222+
11651223
@unittest.skipUnless(
11661224
unix_events.can_use_pidfd(),
11671225
"operating system does not support pidfds",

‎Lib/test/test_descr.py‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,10 +2231,22 @@ def some_number(self_, key):
22312231
def swallow(*args): pass
22322232
def format_impl(self, spec):
22332233
return "hello"
2234-
2235-
# It would be nice to have every special method tested here, but I'm
2236-
# only listing the ones I can remember outside of typeobject.c, since it
2237-
# does it right.
2234+
def do_eq(obj):
2235+
return obj == 1
2236+
def do_ne(obj):
2237+
return obj != 1
2238+
def do_lt(obj):
2239+
return obj < 1
2240+
def do_le(obj):
2241+
return obj <= 1
2242+
def do_gt(obj):
2243+
return obj > 1
2244+
def do_ge(obj):
2245+
return obj >= 1
2246+
2247+
# It would be nice to have every special method tested here.
2248+
# gh-131151: the rich comparison operators used to clear any exception
2249+
# raised by a descriptor's __get__.
22382250
specials = [
22392251
("__bytes__", bytes, hello, set(), {}),
22402252
("__reversed__", reversed, empty_seq, set(), {}),
@@ -2255,6 +2267,12 @@ def format_impl(self, spec):
22552267
("__ceil__", math.ceil, zero, set(), {}),
22562268
("__dir__", dir, empty_seq, set(), {}),
22572269
("__round__", round, zero, set(), {}),
2270+
("__eq__", do_eq, return_true, set(), {}),
2271+
("__ne__", do_ne, return_true, set(), {}),
2272+
("__lt__", do_lt, return_true, set(), {}),
2273+
("__le__", do_le, return_true, set(), {}),
2274+
("__gt__", do_gt, return_true, set(), {}),
2275+
("__ge__", do_ge, return_true, set(), {}),
22582276
]
22592277

22602278
class Checker(object):

‎Lib/test/test_profiling/test_sampling_profiler/test_children.py‎

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
SHORT_TIMEOUT,
1717
reap_children,
1818
requires_remote_subprocess_debugging,
19+
sleeping_retry,
1920
)
2021

2122
# Guard imports that require _remote_debugging module.
@@ -84,29 +85,13 @@ def reader():
8485
return result[0]
8586

8687

87-
def _wait_for_process_ready(proc, timeout):
88-
# Wait for a subprocess to be ready using polling instead of fixed sleep.
89-
# Returns True if process is ready, False if it exited or timeout.
90-
deadline = time.time() + timeout
91-
poll_interval = 0.01
92-
93-
while time.time() < deadline:
94-
if proc.poll() is not None:
95-
return False
96-
97-
try:
98-
if sys.platform == "linux":
99-
if os.path.exists(f"/proc/{proc.pid}/exe"):
100-
return True
101-
else:
102-
return True
103-
except OSError:
104-
pass
105-
106-
time.sleep(poll_interval)
107-
poll_interval = min(poll_interval * 2, 0.1)
108-
109-
return proc.poll() is None
88+
def _wait_for_process_exec(proc, name, timeout):
89+
# Wait until a child process calls exec() to replace the forked process
90+
# executable. Raise AssertionError on timeout.
91+
for _ in sleeping_retry(timeout, max_delay=0.1):
92+
exe = os.readlink(f"/proc/{proc.pid}/exe")
93+
if os.path.basename(exe) == name:
94+
break
11095

11196

11297
@unittest.skipIf(
@@ -634,17 +619,13 @@ def test_is_python_process_non_python_subprocess(self):
634619
"""Test that a non-Python subprocess is not detected as Python."""
635620
# Start a non-Python subprocess (sleep command)
636621
proc = subprocess.Popen(
637-
["sleep", "10"],
622+
["sleep", "60"],
638623
stdout=subprocess.DEVNULL,
639624
stderr=subprocess.DEVNULL,
640625
)
641626

642627
try:
643-
# Wait for process to be ready using polling
644-
self.assertTrue(
645-
_wait_for_process_ready(proc, SHORT_TIMEOUT),
646-
f"Process {proc.pid} should be ready within {SHORT_TIMEOUT}s",
647-
)
628+
_wait_for_process_exec(proc, "sleep", SHORT_TIMEOUT)
648629

649630
self.assertFalse(
650631
is_python_process(proc.pid),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug in :mod:`asyncio` where stopping a child process (such as with ``SIGSTOP``) on macOS caused the event loop to block indefinitely.

‎Modules/clinic/mmapmodule.c.h‎

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)