Skip to content

Commit 6134cb0

Browse files
authored
Merge branch 'main' into patch-1
2 parents 64a80d9 + a8d74c0 commit 6134cb0

33 files changed

Lines changed: 342 additions & 49 deletions

.github/workflows/reusable-san.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ jobs:
8686
run: >-
8787
./python -m test
8888
${{ inputs.sanitizer == 'TSan' && '--tsan' || '' }}
89-
-j4 -W
89+
-j4 -W --timeout=900 --slowest
9090
- name: Parallel tests
9191
if: >-
9292
inputs.sanitizer == 'TSan'
9393
&& fromJSON(inputs.free-threading)
94-
run: ./python -m test --tsan-parallel --parallel-threads=4 -j4 -W
94+
run: ./python -m test --tsan-parallel --parallel-threads=4 -j4 -W --timeout=600 --slowest
9595
- name: Display logs
9696
if: always()
9797
run: find "${GITHUB_WORKSPACE}" -name 'san_log.*' | xargs head -n 1000

Doc/c-api/type.rst

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -563,10 +563,10 @@ but need extra remarks for use as slots:
563563
:c:member:`Slot ID <PySlot.sl_id>` for the name of the type,
564564
used to set :c:member:`PyTypeObject.tp_name`.
565565
566-
This slot (or :c:func:`PyType_Spec.name`) is required to create a type.
566+
This slot (or :c:member:`PyType_Spec.name`) is required to create a type.
567567
568568
This may not be used in :c:member:`PyType_Spec.slots`.
569-
Use :c:func:`PyType_Spec.name` instead.
569+
Use :c:member:`PyType_Spec.name` instead.
570570
571571
.. impl-detail::
572572
@@ -585,7 +585,7 @@ but need extra remarks for use as slots:
585585
The value must be positive.
586586
587587
This may not be used in :c:member:`PyType_Spec.slots`.
588-
Use :c:func:`PyType_Spec.basicsize` instead.
588+
Use :c:member:`PyType_Spec.basicsize` instead.
589589
590590
This slot may not be used with :c:func:`PyType_GetSlot`.
591591
Use :c:member:`PyTypeObject.tp_basicsize` instead if needed, but be aware
@@ -616,7 +616,7 @@ but need extra remarks for use as slots:
616616
:c:macro:`!Py_tp_extra_basicsize` is an error.
617617
618618
This may not be used in :c:member:`PyType_Spec.slots`.
619-
Use negative :c:func:`PyType_Spec.basicsize` instead.
619+
Use negative :c:member:`PyType_Spec.basicsize` instead.
620620
621621
This slot may not be used with :c:func:`PyType_GetSlot`.
622622
@@ -648,7 +648,7 @@ but need extra remarks for use as slots:
648648
- With the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.
649649
650650
This may not be used in :c:member:`PyType_Spec.slots`.
651-
Use :c:func:`PyType_Spec.itemsize` instead.
651+
Use :c:member:`PyType_Spec.itemsize` instead.
652652
653653
This slot may not be used with :c:func:`PyType_GetSlot`.
654654
@@ -663,13 +663,44 @@ but need extra remarks for use as slots:
663663
:c:func:`PyType_FromSpecWithBases` sets it automatically.
664664
665665
This may not be used in :c:member:`PyType_Spec.slots`.
666-
Use negative :c:func:`PyType_Spec.basicsize` instead.
666+
Use negative :c:member:`PyType_Spec.basicsize` instead.
667667
668668
This slot may not be used with :c:func:`PyType_GetSlot`.
669669
Use :c:func:`PyType_GetFlags` instead.
670670
671671
.. versionadded:: 3.15
672672
673+
.. c:macro:: Py_tp_bases
674+
675+
:c:member:`Slot ID <PySlot.sl_id>` for type flags, used to set
676+
:c:member:`PyTypeObject.tp_bases`.
677+
678+
The slot can be set to a tuple of type objects which the newly created
679+
type should inherit from, like the "positional arguments" of
680+
a Python :ref:`class definition <class>`.
681+
682+
Alternately, the slot can be set to a single type object to specify
683+
a single base.
684+
The effect is the same as specifying a one-element tuple.
685+
686+
.. versionchanged:: 3.15
687+
688+
Previously, :c:macro:`!Py_tp_bases` required a tuple of types.
689+
690+
.. c:macro:: Py_tp_base
691+
692+
Equivalent to :c:macro:`Py_tp_bases` (with ``s`` at the end).
693+
If both are specified, :c:macro:`!Py_tp_bases` takes priority and
694+
this slot is ignored.
695+
696+
.. versionchanged:: 3.15
697+
698+
Previously, :c:macro:`!Py_tp_base` required a single type, not a tuple.
699+
700+
.. soft-deprecated:: 3.15
701+
702+
When not targetting older Python versions, pefer :c:macro:`!Py_tp_bases`.
703+
673704
The following slots do not correspond to public fields in the
674705
underlying structures:
675706

Doc/c-api/typeobj.rst

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,12 +1936,12 @@ and :c:data:`PyType_Type` effectively act as defaults.)
19361936

19371937
.. c:member:: PyTypeObject* PyTypeObject.tp_base
19381938
1939-
.. corresponding-type-slot:: Py_tp_base
1940-
19411939
An optional pointer to a base type from which type properties are inherited. At
19421940
this level, only single inheritance is supported; multiple inheritance require
19431941
dynamically creating a type object by calling the metatype.
19441942

1943+
For the corresponding slot ID, see :c:macro:`Py_tp_base`.
1944+
19451945
.. note::
19461946

19471947
.. from Modules/xxmodule.c
@@ -2253,17 +2253,12 @@ and :c:data:`PyType_Type` effectively act as defaults.)
22532253

22542254
.. c:member:: PyObject* PyTypeObject.tp_bases
22552255
2256-
.. corresponding-type-slot:: Py_tp_bases
2257-
22582256
Tuple of base types.
22592257

22602258
This field should be set to ``NULL`` and treated as read-only.
22612259
Python will fill it in when the type is :c:func:`initialized <PyType_Ready>`.
22622260

2263-
For dynamically created classes, the :c:data:`Py_tp_bases`
2264-
:c:type:`slot <PyType_Slot>` can be used instead of the *bases* argument
2265-
of :c:func:`PyType_FromSpecWithBases`.
2266-
The argument form is preferred.
2261+
For the corresponding slot ID, see :c:macro:`Py_tp_bases`.
22672262

22682263
.. warning::
22692264

Doc/tools/removed-ids.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ reference/expressions.html: grammar-token-python-grammar-enclosure
2929
reference/expressions.html: grammar-token-python-grammar-list_display
3030
reference/expressions.html: grammar-token-python-grammar-parenth_form
3131
reference/expressions.html: grammar-token-python-grammar-set_display
32+
33+
# Moved to a different page
34+
c-api/typeobj.html: c.Py_tp_base
35+
c-api/typeobj.html: c.Py_tp_bases

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,6 +2476,12 @@ New features
24762476
* :c:func:`PyModule_FromDefAndSpec2`
24772477
* :c:func:`PyModule_ExecDef`
24782478

2479+
2480+
The slots :c:macro:`Py_tp_bases` and :c:macro:`Py_tp_base` are now
2481+
equivalent: they can be set either to a single type or a tuple of types.
2482+
The :c:macro:`Py_tp_bases` slot is preferred; the other is ignored if both
2483+
are specified.
2484+
24792485
(Contributed by Petr Viktorin in :gh:`149044`.)
24802486

24812487
* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and

Include/internal/pycore_interpframe.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,20 @@ _PyThreadState_GetFrame(PyThreadState *tstate)
287287
return _PyFrame_GetFirstComplete(tstate->current_frame);
288288
}
289289

290+
// Update last_profiled_frame for remote profiler frame caching.
291+
// Only update if we're removing the exact frame that was last profiled.
292+
// This avoids corrupting the cache when transient frames (called and returned
293+
// between profiler samples) update last_profiled_frame to addresses the
294+
// profiler never saw.
295+
#define _PyThreadState_UpdateLastProfiledFrame(tstate, frame, previous) \
296+
do { \
297+
PyThreadState *tstate_ = (tstate); \
298+
_PyInterpreterFrame *frame_ = (frame); \
299+
if (tstate_->last_profiled_frame == frame_) { \
300+
tstate_->last_profiled_frame = (previous); \
301+
} \
302+
} while (0)
303+
290304
/* For use by _PyFrame_GetFrameObject
291305
Do not call directly. */
292306
PyAPI_FUNC(PyFrameObject *)

Lib/test/test_capi/test_misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ def test_tp_bases_slot(self):
924924
def test_tp_bases_slot_none(self):
925925
self.assertRaisesRegex(
926926
TypeError,
927-
"metaclass conflict",
927+
"bases must be types",
928928
_testcapi.create_heapctype_with_none_bases_slot
929929
)
930930

Lib/test/test_capi/test_slots.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,38 @@ def test_repeat_error(self):
312312
_testlimitedcapi.module_from_slots("repeat_exec", FakeSpec())
313313
with self.assertRaisesRegex(SystemError, "multiple"):
314314
_testlimitedcapi.module_from_slots("repeat_gil", FakeSpec())
315+
316+
def test_bases_slots(self):
317+
create = _testlimitedcapi.type_from_base_slots
318+
319+
# Py_tp_bases overrides Py_tp_base
320+
cls = create(base=int, bases=float)
321+
self.assertEqual(cls.mro(), [cls, float, object])
322+
323+
# type is equivalent to one-element tuple
324+
cls = create(base=None, bases=int)
325+
self.assertEqual(cls.mro(), [cls, int, object])
326+
327+
cls = create(base=None, bases=(int,))
328+
self.assertEqual(cls.mro(), [cls, int, object])
329+
330+
cls = create(base=int)
331+
self.assertEqual(cls.mro(), [cls, int, object])
332+
333+
cls = create(base=(int,))
334+
self.assertEqual(cls.mro(), [cls, int, object])
335+
336+
# Tuple of bases works
337+
class Custom:
338+
pass
339+
cls = create(bases=int)
340+
sub = create(base=float, bases=(Custom, cls, int))
341+
self.assertEqual(sub.mro(), [sub, Custom, cls, int, object])
342+
343+
# Reasonable error message for non-types
344+
with self.assertRaisesRegex(TypeError,
345+
"bases must be types; got 'NoneType'"):
346+
create(base=None)
347+
with self.assertRaisesRegex(TypeError,
348+
"bases must be types; got 'str'"):
349+
create(bases="a string")

Lib/test/test_dtrace.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dis
22
import os.path
33
import re
4+
import signal
45
import subprocess
56
import sys
67
import sysconfig
@@ -50,6 +51,24 @@ def normalize_trace_output(output):
5051
)
5152

5253

54+
USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg"))
55+
56+
def create_process_group(*args, **kwargs):
57+
if USE_PROCESS_GROUP:
58+
kwargs['start_new_session'] = True
59+
return subprocess.Popen(*args, **kwargs)
60+
61+
def kill_process_group(proc):
62+
if USE_PROCESS_GROUP:
63+
try:
64+
os.killpg(proc.pid, signal.SIGKILL)
65+
except ProcessLookupError:
66+
pass
67+
else:
68+
proc.kill()
69+
proc.communicate() # Clean up
70+
71+
5372
class TraceBackend:
5473
EXTENSION = None
5574
COMMAND = None
@@ -205,15 +224,15 @@ def run_case(self, name, optimize_python=None):
205224
program = self.PROGRAMS[name].format(python=sys.executable)
206225

207226
try:
208-
proc = subprocess.Popen(
227+
proc = create_process_group(
209228
["bpftrace", "-e", program, "-c", " ".join(subcommand)],
210229
stdout=subprocess.PIPE,
211230
stderr=subprocess.PIPE,
212231
universal_newlines=True,
213232
)
214233
stdout, stderr = proc.communicate(timeout=60)
215234
except subprocess.TimeoutExpired:
216-
proc.kill()
235+
kill_process_group(proc)
217236
raise AssertionError("bpftrace timed out")
218237
except (FileNotFoundError, PermissionError) as e:
219238
raise unittest.SkipTest(f"bpftrace not available: {e}")
@@ -243,16 +262,15 @@ def assert_usable(self):
243262
# Check if bpftrace is available and can attach to USDT probes
244263
program = f'usdt:{sys.executable}:python:function__entry {{ printf("probe: success\\n"); exit(); }}'
245264
try:
246-
proc = subprocess.Popen(
265+
proc = create_process_group(
247266
["bpftrace", "-e", program, "-c", f"{sys.executable} -c pass"],
248267
stdout=subprocess.PIPE,
249268
stderr=subprocess.PIPE,
250269
universal_newlines=True,
251270
)
252271
stdout, stderr = proc.communicate(timeout=10)
253272
except subprocess.TimeoutExpired:
254-
proc.kill()
255-
proc.communicate() # Clean up
273+
kill_process_group(proc)
256274
raise unittest.SkipTest("bpftrace timed out during usability check")
257275
except OSError as e:
258276
raise unittest.SkipTest(f"bpftrace not available: {e}")

Lib/test/test_lazy_import/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,17 @@ def filter(*args):
19491949
def test_set_bad_filter(self):
19501950
self.assertRaises(ValueError, _testcapi.PyImport_SetLazyImportsFilter, 42)
19511951

1952+
def test_dunder_lazy_import_without_frame(self):
1953+
# gh-151510: __lazy_import__() called with no globals and no running
1954+
# Python frame must raise TypeError instead of crashing.
1955+
with self.assertRaisesRegex(
1956+
TypeError,
1957+
r"__lazy_import__\(\) missing globals when called without a frame",
1958+
):
1959+
_testcapi.lazy_import_without_frame(
1960+
"test.test_lazy_import.data.basic2"
1961+
)
1962+
19521963

19531964
if __name__ == '__main__':
19541965
unittest.main()

0 commit comments

Comments
 (0)