8000 Merge branch 'main' into gh-25949 · smontanaro/cpython@e7ec54e · GitHub
[go: up one dir, main page]

Skip to content

Commit e7ec54e

Browse files
authored
Merge branch 'main' into pythongh-25949
2 parents cafe4f4 + a5ff80c commit e7ec54e

30 files changed

+1039
-179
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Lib/ast.py @isidentical
135135

136136
**/*idlelib* @terryjreedy
137137

138-
**/*typing* @gvanrossum @Fidget-Spinner @JelleZijlstra
138+
**/*typing* @gvanrossum @Fidget-Spinner @JelleZijlstra @AlexWaygood
139139

140140
**/*asyncore @giampaolo
141141
**/*asynchat @giampaolo

Doc/c-api/type.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,55 @@ Type Objects
5757
modification of the attributes or base classes of the type.
5858
5959
60+
.. c:function:: int PyType_AddWatcher(PyType_WatchCallback callback)
61+
62+
Register *callback* as a type watcher. Return a non-negative integer ID
63+
which must be passed to future calls to :c:func:`PyType_Watch`. In case of
64+
error (e.g. no more watcher IDs available), return ``-1`` and set an
65+
exception.
66+
67+
.. versionadded:: 3.12
68+
69+
70+
.. c:function:: int PyType_ClearWatcher(int watcher_id)
71+
72+
Clear watcher identified by *watcher_id* (previously returned from
73+
:c:func:`PyType_AddWatcher`). Return ``0`` on success, ``-1`` on error (e.g.
74+
if *watcher_id* was never registered.)
75+
76+
An extension should never call ``PyType_ClearWatcher`` with a *watcher_id*
77+
that was not returned to it by a previous call to
78+
:c:func:`PyType_AddWatcher`.
79+
80+
.. versionadded:: 3.12
81+
82+
83+
.. c:function:: int PyType_Watch(int watcher_id, PyObject *type)
84+
85+
Mark *type* as watched. The callback granted *watcher_id* by
86+
:c:func:`PyType_AddWatcher` will be called whenever
87+
:c:func:`PyType_Modified` reports a change to *type*. (The callback may be
88+
called only once for a series of consecutive modifications to *type*, if
89+
:c:func:`PyType_Lookup` is not called on *type* between the modifications;
90+
this is an implementation detail and subject to change.)
91+
92+
An extension should never call ``PyType_Watch`` with a *watcher_id* that was
93+
not returned to it by a previous call to :c:func:`PyType_AddWatcher`.
94+
95+
.. versionadded:: 3.12
96+
97+
98+
.. c:type:: int (*PyType_WatchCallback)(PyObject *type)
99+
100+
Type of a type-watcher callback function.
101+
102+
The callback must not modify *type* or cause :c:func:`PyType_Modified` to be
103+
called on *type* or any type in its MRO; violating this rule could cause
104+
infinite recursion.
105+
106+
.. versionadded:: 3.12
107+
108+
60109
.. c:function:: int PyType_HasFeature(PyTypeObject *o, int feature)
61110
62111
Return non-zero if the type object *o* sets the feature *feature*.

Doc/reference/compound_stmts.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ the case of :keyword:`except`, but in the case of exception groups we can have
343343
partial matches when the type matches some of the exceptions in the group.
344344
This means that multiple :keyword:`!except*` clauses can execute,
345345
each handling part of the exception group.
346-
Each clause executes once and handles an exception group
346+
Each clause executes at most once and handles an exception group
347347
of all matching exceptions. Each exception in the group is handled by at most
348348
one :keyword:`!except*` clause, the first that matches it. ::
349349

@@ -364,10 +364,22 @@ one :keyword:`!except*` clause, the first that matches it. ::
364364
| ValueError: 1
365365
+------------------------------------
366366

367+
367368
Any remaining exceptions that were not handled by any :keyword:`!except*`
368369
clause are re-raised at the end, combined into an exception group along with
369370
all exceptions that were raised from within :keyword:`!except*` clauses.
370371

372+
If the raised exception is not an exception group and its type matches
373+
one of the :keyword:`!except*` clauses, it is caught and wrapped by an
374+
exception group with an empty message string. ::
375+
376+
>>> try:
377+
... raise BlockingIOError
378+
... except* BlockingIOError as e:
379+
... print(repr(e))
380+
...
381+
ExceptionGroup('', (BlockingIOError()))
382+
371383
An :keyword:`!except*` clause must have a matching type,
372384
and this type cannot be a subclass of :exc:`BaseExceptionGroup`.
373385
It is not possible to mix :keyword:`except` and :keyword:`!except*`

Doc/whatsnew/3.12.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,12 @@ New Features
587587
:c:func:`PyDict_AddWatch` and related APIs to be called whenever a dictionary
588588
is modified. This is intended for use by optimizing interpreters, JIT
589589
compilers, or debuggers.
590+
(Contributed by Carl Meyer in :gh:`91052`.)
591+
592+
* Added :c:func:`PyType_AddWatcher` and :c:func:`PyType_Watch` API to register
593+
callbacks to receive notification on changes to a type.
594+
(Contributed by Carl Meyer in :gh:`91051`.)
595+
590596

591597
Porting to Python 3.12
592598
----------------------

Include/cpython/object.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ struct _typeobject {
224224

225225
destructor tp_finalize;
226226
vectorcallfunc tp_vectorcall;
227+
228+
/* bitset of which type-watchers care about this type */
229+
char tp_watched;
227230
};
228231

229232
/* This struct is used by the specializer
@@ -510,3 +513,11 @@ Py_DEPRECATED(3.11) typedef int UsingDeprecatedTrashcanMacro;
510513

511514
PyAPI_FUNC(int) _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
512515
PyAPI_FUNC(void) _PyObject_ClearManagedDict(PyObject *obj);
516+
517+
#define TYPE_MAX_WATCHERS 8
518+
519+
typedef int(*PyType_WatchCallback)(PyTypeObject *);
520+
PyAPI_FUNC(int) PyType_AddWatcher(PyType_WatchCallback callback);
521+
PyAPI_FUNC(int) PyType_ClearWatcher(int watcher_id);
522+
PyAPI_FUNC(int) PyType_Watch(int watcher_id, PyObject *type);
523+
PyAPI_FUNC(int) PyType_Unwatch(int watcher_id, PyObject *type);

Include/internal/pycore_interp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ struct _is {
166166
struct atexit_state atexit;
167167

168168
PyObject *audit_hooks;
169+
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
169170

170171
struct _Py_unicode_state unicode;
171172
struct _Py_float_state float_state;

Lib/multiprocessing/connection.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,6 @@ def arbitrary_address(family):
7373
if family == 'AF_INET':
7474
return ('localhost', 0)
7575
elif family == 'AF_UNIX':
76-
# Prefer abstract sockets if possible to avoid problems with the address
77-
# size. When coding portable applications, some implementations have
78-
# sun_path as short as 92 bytes in the sockaddr_un struct.
79-
if util.abstract_sockets_supported:
80-
return f"\0listener-{os.getpid()}-{next(_mmap_counter)}"
8176
return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
8277
elif family == 'AF_PIPE':
8378
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %

Lib/multiprocessing/popen_spawn_win32.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,20 @@ def __init__(self, process_obj):
5454
wfd = msvcrt.open_osfhandle(whandle, 0)
5555
cmd = spawn.get_command_line(parent_pid=os.getpid(),
5656
pipe_handle=rhandle)
57-
cmd = ' '.join('"%s"' % x for x in cmd)
5857

5958
python_exe = spawn.get_executable()
6059

6160
# bpo-35797: When running in a venv, we bypass the redirect
6261
# executor and launch our base Python.
6362
if WINENV and _path_eq(python_exe, sys.executable):
64-
python_exe = sys._base_executable
63+
cmd[0] = python_exe = sys._base_executable
6564
env = os.environ.copy()
6665
env["__PYVENV_LAUNCHER__"] = sys.executable
6766
else:
6867
env = None
6968

69+
cmd = ' '.join('"%s"' % x for x in cmd)
70+
7071
with open(wfd, 'wb', closefd=True) as to_child:
7172
# start process
7273
try:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import multiprocessing
2+
import random
3+
import sys
4+
import time
5+
6+
def fill_queue(queue, code):
7+
queue.put(code)
8+
9+
10+
def drain_queue(queue, code):
11+
if code != queue.get():
12+
sys.exit(1)
13+
14+
15+
def test_func():
16+
code = random.randrange(0, 1000)
17+
queue = multiprocessing.Queue()
18+
fill_pool = multiprocessing.Process(
19+
target=fill_queue,
20+
args=(queue, code)
21+
)
22+
drain_pool = multiprocessing.Process(
23+
target=drain_queue,
24+
args=(queue, code)
25+
)
26+
drain_pool.start()
27+
fill_pool.start()
28+
fill_pool.join()
29+
drain_pool.join()
30+
31+
32+
def main():
33+
test_pool = multiprocessing.Process(target=test_func)
34+
test_pool.start()
35+
test_pool.join()
36+
sys.exit(test_pool.exitcode)
37+
38+
39+
if __name__ == "__main__":
40+
main()

Lib/test/libregrtest/runtest_mp.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from test.libregrtest.setup import setup_tests
2323
from test.libregrtest.utils import format_duration, print_warning
2424

25+
if sys.platform == 'win32':
26+
import locale
27+
2528

2629
# Display the running tests if nothing happened last N seconds
2730
PROGRESS_UPDATE = 30.0 # seconds
@@ -267,11 +270,16 @@ def _run_process(self, test_name: str, tmp_dir: str, stdout_fh: TextIO) -> int:
267270
self.current_test_name = None
268271

269272
def _runtest(self, test_name: str) -> MultiprocessResult:
273+
if sys.platform == 'win32':
274+
# gh-95027: When stdout is not a TTY, Python uses the ANSI code
275+
# page for the sys.stdout encoding. If the main process runs in a
276+
# terminal, sys.stdout uses WindowsConsoleIO with UTF-8 encoding.
277+
encoding = locale.getencoding()
278+
else:
279+
encoding = sys.stdout.encoding
270280
# gh-94026: Write stdout+stderr to a tempfile as workaround for
271281
# non-blocking pipes on Emscripten with NodeJS.
272-
with tempfile.TemporaryFile(
273-
'w+', encoding=sys.stdout.encoding
274-
) as stdout_fh:
282+
with tempfile.TemporaryFile('w+', encoding=encoding) as stdout_fh:
275283
# gh-93353: Check for leaked temporary files in the parent process,
276284
# since the deletion of temporary files can happen late during
277285
# Python finalization: too late for libregrtest.

0 commit comments

Comments
 (0)
0