Skip to content

Commit e882157

Browse files
authored
Merge branch 'main' into refactor/hashlib/module-methods-135532
2 parents 0abef75 + ef4fc86 commit e882157

33 files changed

+475
-102
lines changed

Doc/c-api/arg.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,12 @@ Building values
686686
``p`` (:class:`bool`) [int]
687687
Convert a C :c:expr:`int` to a Python :class:`bool` object.
688688
689+
Be aware that this format requires an ``int`` argument.
690+
Unlike most other contexts in C, variadic arguments are not coerced to
691+
a suitable type automatically.
692+
You can convert another type (for example, a pointer or a float) to a
693+
suitable ``int`` value using ``(x) ? 1 : 0`` or ``!!x``.
694+
689695
.. versionadded:: 3.14
690696
691697
``c`` (:class:`bytes` of length 1) [char]

Doc/library/argparse.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ See also :ref:`specifying-ambiguous-arguments`. The supported values are:
955955

956956
.. index:: single: + (plus); in argparse module
957957

958-
* ``'+'``. Just like ``'*'``, all command-line args present are gathered into a
958+
* ``'+'``. Just like ``'*'``, all command-line arguments present are gathered into a
959959
list. Additionally, an error message will be generated if there wasn't at
960960
least one command-line argument present. For example::
961961

Doc/library/shutil.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ Directory and files operations
4747
0, only the contents from the current file position to the end of the file will
4848
be copied.
4949

50+
:func:`copyfileobj` will *not* guarantee that the destination stream has
51+
been flushed on completion of the copy. If you want to read from the
52+
destination at the completion of the copy operation (for example, reading
53+
the contents of a temporary file that has been copied from a HTTP stream),
54+
you must ensure that you have called :func:`~io.IOBase.flush` or
55+
:func:`~io.IOBase.close` on the file-like object before attempting to read
56+
the destination file.
5057

5158
.. function:: copyfile(src, dst, *, follow_symlinks=True)
5259

Doc/whatsnew/3.14.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ As another example, generating HTML attributes from data:
278278
279279
attributes = {"src": "shrubbery.jpg", "alt": "looks nice"}
280280
template = t"<img {attributes}>"
281-
assert html(template) == '<img src="shrubbery.jpg" alt="looks nice" class="looks-nice">'
281+
assert html(template) == '<img src="shrubbery.jpg" alt="looks nice" />'
282282
283283
Compared to using an f-string, the ``html`` function has access to template attributes
284284
containing the original information: static strings, interpolations, and values

Include/internal/pycore_uop_ids.h

Lines changed: 47 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

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

InternalDocs/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ Program Execution
4242

4343
- [Exception Handling](exception_handling.md)
4444

45+
- [Quiescent-State Based Reclamation (QSBR)](qsbr.md)
4546

4647
Modules
4748
---
4849

49-
- [asyncio](asyncio.md)
50+
- [asyncio](asyncio.md)

InternalDocs/qsbr.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Quiescent-State Based Reclamation (QSBR)
2+
3+
## Introduction
4+
5+
When implementing lock-free data structures, a key challenge is determining
6+
when it is safe to free memory that has been logically removed from a
7+
structure. Freeing memory too early can lead to use-after-free bugs if another
8+
thread is still accessing it. Freeing it too late results in excessive memory
9+
consumption.
10+
11+
Safe memory reclamation (SMR) schemes address this by delaying the free
12+
operation until all concurrent read accesses are guaranteed to have completed.
13+
Quiescent-State Based Reclamation (QSBR) is a SMR scheme used in Python's
14+
free-threaded build to manage the lifecycle of shared memory.
15+
16+
QSBR requires threads to periodically report that they are in a quiescent
17+
state. A thread is in a quiescent state if it holds no references to shared
18+
objects that might be reclaimed. Think of it as a checkpoint where a thread
19+
signals, "I am not in the middle of any operation that relies on a shared
20+
resource." In Python, the eval_breaker provides a natural and convenient place
21+
for threads to report this state.
22+
23+
24+
## Use in Free-Threaded Python
25+
26+
While CPython's memory management is dominated by reference counting and a
27+
tracing garbage collector, these mechanisms are not suitable for all data
28+
structures. For example, the backing array of a list object is not individually
29+
reference-counted but may have a shorter lifetime than the `PyListObject` that
30+
contains it. We could delay reclamation until the next GC run, but we want
31+
reclamation to be prompt and to run the GC less frequently in the free-threaded
32+
build, as it requires pausing all threads.
33+
34+
Many operations in the free-threaded build are protected by locks. However, for
35+
performance-critical code, we want to allow reads to happen concurrently with
36+
updates. For instance, we want to avoid locking during most list read accesses.
37+
If a list is resized while another thread is reading it, QSBR provides the
38+
mechanism to determine when it is safe to free the list's old backing array.
39+
40+
Specific use cases for QSBR include:
41+
42+
* Dictionary keys (`PyDictKeysObject`) and list arrays (`_PyListArray`): When a
43+
dictionary or list that may be shared between threads is resized, we use QSBR
44+
to delay freeing the old keys or array until it's safe. For dicts and lists
45+
that are not shared, their storage can be freed immediately upon resize.
46+
47+
* Mimalloc `mi_page_t`: Non-locking dictionary and list accesses require
48+
cooperation from the memory allocator. If an object is freed and its memory is
49+
reused, we must ensure the new object's reference count field is at the same
50+
memory location. In practice, this means when a mimalloc page (`mi_page_t`)
51+
becomes empty, we don't immediately allow it to be reused for allocations of a
52+
different size class. QSBR is used to determine when it's safe to repurpose the
53+
page or return its memory to the OS.
54+
55+
56+
## Implementation Details
57+
58+
59+
### Core Implementation
60+
61+
The proposal to add QSBR to Python is contained in
62+
[Github issue 115103](https://github.com/python/cpython/issues/115103).
63+
Many details of that proposal have been copied here, so they can be kept
64+
up-to-date with the actual implementation.
65+
66+
Python's QSBR implementation is based on FreeBSD's "Global Unbounded
67+
Sequences." [^1][^2][^3]. It relies on a few key counters:
68+
69+
* Global Write Sequence (`wr_seq`): A per-interpreter counter, `wr_seq`, is started
70+
at 1 and incremented by 2 each time it is advanced. This ensures its value is
71+
always odd, which can be used to distinguish it from other state values. When
72+
an object needs to be reclaimed, `wr_seq` is advanced, and the object is tagged
73+
with this new sequence number.
74+
75+
* Per-Thread Read Sequence: Each thread has a local read sequence counter. When
76+
a thread reaches a quiescent state (e.g., at the eval_breaker), it copies the
77+
current global `wr_seq` to its local counter.
78+
79+
* Global Read Sequence (`rd_seq`): This per-interpreter value stores the minimum
80+
of all per-thread read sequence counters (excluding detached threads). It is
81+
updated by a "polling" operation.
82+
83+
To free an object, the following steps are taken:
84+
85+
1. Advance the global `wr_seq`.
86+
87+
2. Add the object's pointer to a deferred-free list, tagging it with the new
88+
`wr_seq` value as its qsbr_goal.
89+
90+
Periodically, a polling mechanism processes this deferred-free list:
91+
92+
1. The minimum read sequence value across all active threads is calculated and
93+
stored as the global `rd_seq`.
94+
95+
2. For each item on the deferred-free list, if its qsbr_goal is less than or
96+
equal to the new `rd_seq`, its memory is freed, and it is removed from the:
97+
list. Otherwise, it remains on the list for a future attempt.
98+
99+
100+
### Deferred Advance Optimization
101+
102+
To reduce memory contention from frequent updates to the global `wr_seq`, its
103+
advancement is sometimes deferred. Instead of incrementing `wr_seq` on every
104+
reclamation request, each thread tracks its number of deferrals locally. Once
105+
the deferral count reaches a limit (QSBR_DEFERRED_LIMIT, currently 10), the
106+
thread advances the global `wr_seq` and resets its local count.
107+
108+
When an object is added to the deferred-free list, its qsbr_goal is set to
109+
`wr_seq` + 2. By setting the goal to the next sequence value, we ensure it's safe
110+
to defer the global counter advancement. This optimization improves runtime
111+
speed but may increase peak memory usage by slightly delaying when memory can
112+
be reclaimed.
113+
114+
115+
## Limitations
116+
117+
Determining the `rd_seq` requires scanning over all thread states. This operation
118+
could become a bottleneck in applications with a very large number of threads
119+
(e.g., >1,000). Future work may address this with more advanced mechanisms,
120+
such as a tree-based structure or incremental scanning. For now, the
121+
implementation prioritizes simplicity, with plans for refinement if
122+
multi-threaded benchmarks reveal performance issues.
123+
124+
125+
## References
126+
127+
[^1]: https://youtu.be/ZXUIFj4nRjk?t=694
128+
[^2]: https://people.kernel.org/joelfernandes/gus-vs-rcu
129+
[^3]: http://bxr.su/FreeBSD/sys/kern/subr_smr.c#44

Lib/_pyrepl/base_eventqueue.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def push(self, char: int | bytes) -> None:
8787
if isinstance(k, dict):
8888
self.keymap = k
8989
else:
90-
self.insert(Event('key', k, self.flush_buf()))
90+
self.insert(Event('key', k, bytes(self.flush_buf())))
9191
self.keymap = self.compiled_keymap
9292

9393
elif self.buf and self.buf[0] == 27: # escape
@@ -96,7 +96,7 @@ def push(self, char: int | bytes) -> None:
9696
# the docstring in keymap.py
9797
trace('unrecognized escape sequence, propagating...')
9898
self.keymap = self.compiled_keymap
99-
self.insert(Event('key', '\033', bytearray(b'\033')))
99+
self.insert(Event('key', '\033', b'\033'))
100100
for _c in self.flush_buf()[1:]:
101101
self.push(_c)
102102

@@ -106,5 +106,5 @@ def push(self, char: int | bytes) -> None:
106106
except UnicodeError:
107107
return
108108
else:
109-
self.insert(Event('key', decoded, self.flush_buf()))
109+
self.insert(Event('key', decoded, bytes(self.flush_buf())))
110110
self.keymap = self.compiled_keymap

Lib/netrc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ def _security_check(self, fp, default_netrc, login):
162162
fowner = _getpwuid(prop.st_uid)
163163
user = _getpwuid(current_user_id)
164164
raise NetrcParseError(
165-
(f"~/.netrc file owner ({fowner}, {user}) does not match"
166-
" current user"))
165+
f"~/.netrc file owner ({fowner}) does not match"
166+
f" current user ({user})")
167167
if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
168168
raise NetrcParseError(
169169
"~/.netrc access too permissive: access"

0 commit comments

Comments
 (0)