Skip to content

Commit 6ffffa6

Browse files
Merge remote-tracking branch 'upstream/main' into pr/135818
2 parents bba9435 + f783cc3 commit 6ffffa6

27 files changed

+910
-124
lines changed

.github/workflows/tail-call.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,23 @@ jobs:
8181

8282
- name: Native Windows MSVC (release)
8383
if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
84-
shell: cmd
84+
shell: pwsh
8585
run: |
8686
choco install visualstudio2026buildtools --no-progress -y --force --params "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --locale en-US --passive"
8787
$env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\bin;$env:PATH"
88-
./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }} "/p:PlatformToolset=v145"
88+
$env:PlatformToolset = "v145"
89+
./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }}
8990
./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
9091
9192
# No tests (yet):
9293
- name: Emulated Windows Clang (release)
9394
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'
94-
shell: cmd
95+
shell: pwsh
9596
run: |
9697
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
97-
set PlatformToolset=clangcl
98-
set LLVMToolsVersion=${{ matrix.llvm }}.1.0
99-
set LLVMInstallDir=C:\Program Files\LLVM
98+
$env:PlatformToolset = "clangcl"
99+
$env:LLVMToolsVersion = "${{ matrix.llvm }}.1.0"
100+
$env:LLVMInstallDir = "C:\Program Files\LLVM"
100101
./PCbuild/build.bat --tail-call-interp -p ${{ matrix.architecture }}
101102
102103
- name: Native macOS (release)

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: v0.13.2
3+
rev: v0.14.10
44
hooks:
55
- id: ruff-check
66
name: Run Ruff (lint) on Apple/
@@ -52,7 +52,7 @@ repos:
5252
files: ^Tools/wasm/
5353

5454
- repo: https://github.com/psf/black-pre-commit-mirror
55-
rev: 25.9.0
55+
rev: 25.12.0
5656
hooks:
5757
- id: black
5858
name: Run Black on Tools/jit/
@@ -83,24 +83,24 @@ repos:
8383
files: '^\.github/CODEOWNERS|\.(gram)$'
8484

8585
- repo: https://github.com/python-jsonschema/check-jsonschema
86-
rev: 0.34.0
86+
rev: 0.36.0
8787
hooks:
8888
- id: check-dependabot
8989
- id: check-github-workflows
9090
- id: check-readthedocs
9191

9292
- repo: https://github.com/rhysd/actionlint
93-
rev: v1.7.7
93+
rev: v1.7.9
9494
hooks:
9595
- id: actionlint
9696

9797
- repo: https://github.com/woodruffw/zizmor-pre-commit
98-
rev: v1.14.1
98+
rev: v1.19.0
9999
hooks:
100100
- id: zizmor
101101

102102
- repo: https://github.com/sphinx-contrib/sphinx-lint
103-
rev: v1.0.0
103+
rev: v1.0.2
104104
hooks:
105105
- id: sphinx-lint
106106
args: [--enable=default-role]

Doc/deprecations/pending-removal-in-3.15.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ Pending removal in Python 3.15
3333

3434
* ``load_module()`` method: use ``exec_module()`` instead.
3535

36-
* :class:`locale`:
37-
38-
* The :func:`~locale.getdefaultlocale` function
39-
has been deprecated since Python 3.11.
40-
Its removal was originally planned for Python 3.13 (:gh:`90817`),
41-
but has been postponed to Python 3.15.
42-
Use :func:`~locale.getlocale`, :func:`~locale.setlocale`,
43-
and :func:`~locale.getencoding` instead.
44-
(Contributed by Hugo van Kemenade in :gh:`111187`.)
45-
4636
* :mod:`pathlib`:
4737

4838
* :meth:`!.PurePath.is_reserved`

Doc/library/locale.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,6 @@ The :mod:`locale` module defines the following exception and functions:
370370
determined.
371371
The "C" locale is represented as ``(None, None)``.
372372

373-
.. deprecated-removed:: 3.11 3.15
374-
375373

376374
.. function:: getlocale(category=LC_CTYPE)
377375

Doc/library/profiling.sampling.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ The default configuration works well for most use cases:
342342
- Disabled
343343
* - Default for ``--subprocesses``
344344
- Disabled
345+
* - Default for ``--blocking``
346+
- Disabled (non-blocking sampling)
345347

346348

347349
Sampling interval and duration
@@ -392,6 +394,50 @@ This option is particularly useful when investigating concurrency issues or
392394
when work is distributed across a thread pool.
393395

394396

397+
.. _blocking-mode:
398+
399+
Blocking mode
400+
-------------
401+
402+
By default, Tachyon reads the target process's memory without stopping it.
403+
This non-blocking approach is ideal for most profiling scenarios because it
404+
imposes virtually zero overhead on the target application: the profiled
405+
program runs at full speed and is unaware it is being observed.
406+
407+
However, non-blocking sampling can occasionally produce incomplete or
408+
inconsistent stack traces in applications with many generators or coroutines
409+
that rapidly switch between yield points, or in programs with very fast-changing
410+
call stacks where functions enter and exit between the start and end of a single
411+
stack read, resulting in reconstructed stacks that mix frames from different
412+
execution states or that never actually existed.
413+
414+
For these cases, the :option:`--blocking` option stops the target process during
415+
each sample::
416+
417+
python -m profiling.sampling run --blocking script.py
418+
python -m profiling.sampling attach --blocking 12345
419+
420+
When blocking mode is enabled, the profiler suspends the target process,
421+
reads its stack, then resumes it. This guarantees that each captured stack
422+
represents a real, consistent snapshot of what the process was doing at that
423+
instant. The trade-off is that the target process runs slower because it is
424+
repeatedly paused.
425+
426+
.. warning::
427+
428+
Do not use very high sample rates (low ``--interval`` values) with blocking
429+
mode. Suspending and resuming a process takes time, and if the sampling
430+
interval is too short, the target will spend more time stopped than running.
431+
For blocking mode, intervals of 1000 microseconds (1 millisecond) or higher
432+
are recommended. The default 100 microsecond interval may cause noticeable
433+
slowdown in the target application.
434+
435+
Use blocking mode only when you observe inconsistent stacks in your profiles,
436+
particularly with generator-heavy or coroutine-heavy code. For most
437+
applications, the default non-blocking mode provides accurate results with
438+
zero impact on the target process.
439+
440+
395441
Special frames
396442
--------------
397443

@@ -1383,6 +1429,13 @@ Sampling options
13831429
Also profile subprocesses. Each subprocess gets its own profiler
13841430
instance and output file. Incompatible with ``--live``.
13851431

1432+
.. option:: --blocking
1433+
1434+
Pause the target process during each sample. This ensures consistent
1435+
stack traces at the cost of slowing down the target. Use with longer
1436+
intervals (1000 µs or higher) to minimize impact. See :ref:`blocking-mode`
1437+
for details.
1438+
13861439

13871440
Mode options
13881441
------------

Doc/tools/check-warnings.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,11 @@ def main(argv: list[str] | None = None) -> int:
311311
if not Path("Doc").exists() or not Path("Doc").is_dir():
312312
raise RuntimeError(wrong_directory_msg)
313313

314-
with Path("Doc/sphinx-warnings.txt").open(encoding="UTF-8") as f:
315-
warnings = f.read().splitlines()
314+
warnings = (
315+
Path("Doc/sphinx-warnings.txt")
316+
.read_text(encoding="UTF-8")
317+
.splitlines()
318+
)
316319

317320
cwd = str(Path.cwd()) + os.path.sep
318321
files_with_nits = {

Doc/whatsnew/3.15.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ locale
562562
but included in the language code.
563563
(Contributed by Serhiy Storchaka in :gh:`137729`.)
564564

565+
* Undeprecate the :func:`locale.getdefaultlocale` function.
566+
(Contributed by Victor Stinner in :gh:`130796`.)
567+
565568

566569
math
567570
----

Lib/locale.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -559,12 +559,6 @@ def getdefaultlocale(envvars=('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE')):
559559
560560
"""
561561

562-
import warnings
563-
warnings._deprecated(
564-
"locale.getdefaultlocale",
565-
"{name!r} is deprecated and slated for removal in Python {remove}. "
566-
"Use setlocale(), getencoding() and getlocale() instead.",
567-
remove=(3, 15))
568562
return _getdefaultlocale(envvars)
569563

570564

Lib/profiling/sampling/cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,13 @@ def _add_sampling_options(parser):
347347
action="store_true",
348348
help="Also profile subprocesses. Each subprocess gets its own profiler and output file.",
349349
)
350+
sampling_group.add_argument(
351+
"--blocking",
352+
action="store_true",
353+
help="Stop all threads in target process before sampling to get consistent snapshots. "
354+
"Uses thread_suspend on macOS and ptrace on Linux. Adds overhead but ensures memory "
355+
"reads are from a frozen state.",
356+
)
350357

351358

352359
def _add_mode_options(parser):
@@ -585,6 +592,15 @@ def _validate_args(args, parser):
585592
if getattr(args, 'command', None) == "replay":
586593
return
587594

595+
# Warn about blocking mode with aggressive sampling intervals
596+
if args.blocking and args.interval < 100:
597+
print(
598+
f"Warning: --blocking with a {args.interval} µs interval will stop all threads "
599+
f"{1_000_000 // args.interval} times per second. "
600+
"Consider using --interval 1000 or higher to reduce overhead.",
601+
file=sys.stderr
602+
)
603+
588604
# Check if live mode is available
589605
if hasattr(args, 'live') and args.live and LiveStatsCollector is None:
590606
parser.error(
@@ -861,6 +877,7 @@ def _handle_attach(args):
861877
native=args.native,
862878
gc=args.gc,
863879
opcodes=args.opcodes,
880+
blocking=args.blocking,
864881
)
865882
_handle_output(collector, args, args.pid, mode)
866883

@@ -939,6 +956,7 @@ def _handle_run(args):
939956
native=args.native,
940957
gc=args.gc,
941958
opcodes=args.opcodes,
959+
blocking=args.blocking,
942960
)
943961
_handle_output(collector, args, process.pid, mode)
944962
finally:
@@ -984,6 +1002,7 @@ def _handle_live_attach(args, pid):
9841002
native=args.native,
9851003
gc=args.gc,
9861004
opcodes=args.opcodes,
1005+
blocking=args.blocking,
9871006
)
9881007

9891008

@@ -1031,6 +1050,7 @@ def _handle_live_run(args):
10311050
native=args.native,
10321051
gc=args.gc,
10331052
opcodes=args.opcodes,
1053+
blocking=args.blocking,
10341054
)
10351055
finally:
10361056
# Clean up the subprocess

0 commit comments

Comments
 (0)