Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ jobs:
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
with:
allowed-failures: >-
build-ios,
build-windows-msi,
build-ubuntu-ssltests-awslc,
build-ubuntu-ssltests-openssl,
Expand Down
4 changes: 4 additions & 0 deletions Doc/c-api/gcsupport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ The :c:member:`~PyTypeObject.tp_traverse` handler must have the following type:
object argument. If *visit* returns a non-zero value that value should be
returned immediately.

The traversal function must not have any side effects. Implementations
may not modify the reference counts of any Python objects nor create or
destroy any Python objects.

To simplify writing :c:member:`~PyTypeObject.tp_traverse` handlers, a :c:func:`Py_VISIT` macro is
provided. In order to use this macro, the :c:member:`~PyTypeObject.tp_traverse` implementation
must name its arguments exactly *visit* and *arg*:
Expand Down
5 changes: 5 additions & 0 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,11 @@ and :c:data:`PyType_Type` effectively act as defaults.)
but the instance has no strong reference to the elements inside it, as they
are allowed to be removed even if the instance is still alive).

.. warning::
The traversal function must not have any side effects. It must not
modify the reference counts of any Python objects nor create or destroy
any Python objects.

Note that :c:func:`Py_VISIT` requires the *visit* and *arg* parameters to
:c:func:`!local_traverse` to have these specific names; don't name them just
anything.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,12 @@ Build changes
modules that are missing or packaged separately.
(Contributed by Stan Ulbrych and Petr Viktorin in :gh:`139707`.)

* Annotating anonymous mmap usage is now supported if Linux kernel supports
:manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
Annotations are visible in ``/proc/<pid>/maps`` if the kernel supports the feature
and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode <debug-build>`.
(Contributed by Donghee Na in :gh:`141770`)


Porting to Python 3.15
======================
Expand Down
45 changes: 45 additions & 0 deletions Include/internal/pycore_mmap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef Py_INTERNAL_MMAP_H
#define Py_INTERNAL_MMAP_H

#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_pystate.h"

#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
# include <linux/prctl.h>
# include <sys/prctl.h>
#endif

#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
static inline void
_PyAnnotateMemoryMap(void *addr, size_t size, const char *name)
{
#ifndef Py_DEBUG
if (!_Py_GetConfig()->dev_mode) {
return;
}
#endif
assert(strlen(name) < 80);
int old_errno = errno;
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
/* Ignore errno from prctl */
/* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */
errno = old_errno;
}
#else
static inline void
_PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name))
{
}
#endif

#ifdef __cplusplus
}
#endif
#endif // !Py_INTERNAL_MMAP_H
7 changes: 0 additions & 7 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,6 @@ PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)

#define PyStackRef_IsDeferred(ref) (((ref).bits & Py_TAG_BITS) == Py_TAG_DEFERRED)

static inline PyObject *
PyStackRef_NotDeferred_AsPyObject(_PyStackRef stackref)
{
assert(!PyStackRef_IsDeferred(stackref));
return (PyObject *)stackref.bits;
}

static inline PyObject *
PyStackRef_AsPyObjectSteal(_PyStackRef stackref)
{
Expand Down
2 changes: 2 additions & 0 deletions InternalDocs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Program Execution

- [The Bytecode Interpreter](interpreter.md)

- [Stack references (_PyStackRef)](stackrefs.md)

- [The JIT](jit.md)

- [Garbage Collector Design](garbage_collector.md)
Expand Down
80 changes: 80 additions & 0 deletions InternalDocs/stackrefs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Stack references (`_PyStackRef`)

Stack references are the interpreter's tagged representation of values on the evaluation stack.
They carry metadata to track ownership and support optimizations such as tagged small ints.

## Shape and tagging

- A `_PyStackRef` is a tagged pointer-sized value (see `Include/internal/pycore_stackref.h`).
- Tag bits distinguish three cases:
- `Py_TAG_REFCNT` unset - reference count lives on the pointed-to object.
- `Py_TAG_REFCNT` set - ownership is "borrowed" (no refcount to drop on close) or the object is immortal.
- `Py_INT_TAG` set - tagged small integer stored directly in the stackref (no heap allocation).
- Special constants: `PyStackRef_NULL`, `PyStackRef_ERROR`, and embedded `None`/`True`/`False`.

In GIL builds, most objects carry their refcount; tagged borrowed refs skip decref on close. In free
threading builds, the tag is also used to mark deferred refcounted objects so the GC can see them and
to avoid refcount contention on commonly shared objects.

## Converting to and from PyObject*

Three conversions control ownership:

- `PyStackRef_FromPyObjectNew(obj)` - create a new reference (INCREF if mortal).
- `PyStackRef_FromPyObjectSteal(obj)` - take over ownership without changing the count unless the
object is immortal.
- `PyStackRef_FromPyObjectBorrow(obj)` - create a borrowed stackref (never decref on close).

The `obj` argument must not be `NULL`.

Going back to `PyObject*` mirrors this:

- `PyStackRef_AsPyObjectBorrow(ref)` - borrow the underlying pointer
- `PyStackRef_AsPyObjectSteal(ref)` - transfer ownership from the stackref; if ref is borrowed or
deferred, this creates a new owning `PyObject*` reference.
- `PyStackRef_AsPyObjectNew(ref)` - create a new owning reference

Only `PyStackRef_AsPyObjectBorrow` allows ref to be `PyStackRef_NULL`.

## Operations on stackrefs

The interpreter treats `_PyStackRef` as the unit of stack storage. Ownership must be managed with
the stackref primitives:

- `PyStackRef_DUP` - like `Py_NewRef` for stackrefs; preserves the original.
- `PyStackRef_Borrow` - create a borrowed stackref from another stackref.
- `PyStackRef_CLOSE` / `PyStackRef_XCLOSE` - like `Py_DECREF`; invalidates the stackref.
- `PyStackRef_CLEAR` - like `Py_CLEAR`; closes and sets the stackref to `PyStackRef_NULL`
- `PyStackRef_MakeHeapSafe` - converts borrowed reference to owning reference

Borrow tracking (for debug builds with `Py_STACKREF_DEBUG`) records who you borrowed from and reports
double-close, leaked borrows, or use-after-close via fatal errors.

## Borrow-friendly opcodes

The interpreter can push borrowed references directly. For example, `LOAD_FAST_BORROW` loads a local
variable as a borrowed `_PyStackRef`, avoiding both INCREF and DECREF for the temporary lifetime on
the evaluation stack.

## Tagged integers on the stack

Small ints can be stored inline with `Py_INT_TAG`, so no heap object is involved. Helpers like
`PyStackRef_TagInt`, `PyStackRef_UntagInt`, and `PyStackRef_IncrementTaggedIntNoOverflow` operate on
these values. Type checks use `PyStackRef_IsTaggedInt` and `PyStackRef_LongCheck`.

## Free threading considerations

With `Py_GIL_DISABLED`, `Py_TAG_DEFERRED` is an alias for `Py_TAG_REFCNT`.
Objects that support deferred reference counting can be pushed to the evaluation
stack and stored in local variables without directly incrementing the reference
count because they are only freed during cyclic garbage collection. This avoids
reference count contention on commonly shared objects such as methods and types. The GC
scans each thread's locals and evaluation stack to keep objects that use
deferred reference counting alive.

## Debugging support

`Py_STACKREF_DEBUG` builds replace the inline tags with table-backed IDs so the runtime can track
creation sites, borrows, closes, and leaks. Enabling `Py_STACKREF_CLOSE_DEBUG` additionally records
double closes. The tables live on `PyInterpreterState` and are initialized in `pystate.c`; helper
routines reside in `Python/stackrefs.c`.
1 change: 0 additions & 1 deletion Lib/_py_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,6 @@ def warn_explicit(message, category, filename, lineno,
else:
text = message
message = category(message)
modules = None
key = (text, category, lineno)
with _wm._lock:
if registry is None:
Expand Down
2 changes: 1 addition & 1 deletion Lib/_threading_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def thread_deleted(_, idt=idt):
# as soon as the OS-level thread ends instead.
local = wrlocal()
if local is not None:
dct = local.dicts.pop(idt)
local.dicts.pop(idt)
wrlocal = ref(self, local_deleted)
wrthread = ref(thread, thread_deleted)
thread.__dict__[key] = wrlocal
Expand Down
4 changes: 2 additions & 2 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ async def sock_sendfile(self, sock, file, offset=0, count=None,
try:
return await self._sock_sendfile_native(sock, file,
offset, count)
except exceptions.SendfileNotAvailableError as exc:
except exceptions.SendfileNotAvailableError:
if not fallback:
raise
return await self._sock_sendfile_fallback(sock, file,
Expand Down Expand Up @@ -1270,7 +1270,7 @@ async def sendfile(self, transport, file, offset=0, count=None,
try:
return await self._sendfile_native(transport, file,
offset, count)
except exceptions.SendfileNotAvailableError as exc:
except exceptions.SendfileNotAvailableError:
if not fallback:
raise

Expand Down
2 changes: 1 addition & 1 deletion Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ async def sock_accept(self, sock):
async def _sock_sendfile_native(self, sock, file, offset, count):
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
except (AttributeError, io.UnsupportedOperation):
raise exceptions.SendfileNotAvailableError("not a regular file")
try:
fsize = os.fstat(fileno).st_size
Expand Down
2 changes: 1 addition & 1 deletion Lib/asyncio/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def _get_awaited_by_tasks(pid: int) -> list:
e = e.__context__
print(f"Error retrieving tasks: {e}")
sys.exit(1)
except PermissionError as e:
except PermissionError:
exit_with_permission_help_text()


Expand Down
2 changes: 1 addition & 1 deletion Lib/asyncio/unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
"os.sendfile() is not available")
try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err:
except (AttributeError, io.UnsupportedOperation):
raise exceptions.SendfileNotAvailableError("not a regular file")
try:
fsize = os.fstat(fileno).st_size
Expand Down
4 changes: 2 additions & 2 deletions Lib/codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ def _maybe_compile(compiler, source, filename, symbol, flags):
try:
compiler(source + "\n", filename, symbol, flags=flags)
return None
except _IncompleteInputError as e:
except _IncompleteInputError:
return None
except SyntaxError as e:
except SyntaxError:
pass
# fallthrough

Expand Down
2 changes: 1 addition & 1 deletion Lib/compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
cfile = importlib.util.cache_from_source(fullname)
opt_cfiles[opt_level] = cfile

head, tail = name[:-3], name[-3:]
tail = name[-3:]
if tail == '.py':
if not force:
try:
Expand Down
6 changes: 3 additions & 3 deletions Lib/concurrent/interpreters/_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def put(self, obj, block=True, timeout=None, *,
while True:
try:
_queues.put(self._id, obj, unboundop)
except QueueFull as exc:
except QueueFull:
if timeout is not None and time.time() >= end:
raise # re-raise
time.sleep(_delay)
Expand Down Expand Up @@ -258,7 +258,7 @@ def get(self, block=True, timeout=None, *,
while True:
try:
obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
except QueueEmpty:
if timeout is not None and time.time() >= end:
raise # re-raise
time.sleep(_delay)
Expand All @@ -277,7 +277,7 @@ def get_nowait(self):
"""
try:
obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
except QueueEmpty:
raise # re-raise
if unboundop is not None:
assert obj is None, repr(obj)
Expand Down
1 change: 0 additions & 1 deletion Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,6 @@ def print_instruction_line(self, instr, mark_as_current):
fields.append(instr.opname.ljust(_OPNAME_WIDTH))
# Column: Opcode argument
if instr.arg is not None:
arg = repr(instr.arg)
# If opname is longer than _OPNAME_WIDTH, we allow it to overflow into
# the space reserved for oparg. This results in fewer misaligned opargs
# in the disassembly output.
Expand Down
2 changes: 1 addition & 1 deletion Lib/encodings/uu_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def uu_decode(input, errors='strict'):
break
try:
data = binascii.a2b_uu(s)
except binascii.Error as v:
except binascii.Error:
# Workaround for broken uuencoders by /Fredrik Lundh
nbytes = (((s[0]-32) & 63) * 4 + 5) // 3
data = binascii.a2b_uu(s[:nbytes])
Expand Down
8 changes: 4 additions & 4 deletions Lib/ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ def makeport(self):
port = sock.getsockname()[1] # Get proper port
host = self.sock.getsockname()[0] # Get proper host
if self.af == socket.AF_INET:
resp = self.sendport(host, port)
self.sendport(host, port)
else:
resp = self.sendeprt(host, port)
self.sendeprt(host, port)
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(self.timeout)
return sock
Expand Down Expand Up @@ -455,7 +455,7 @@ def retrlines(self, cmd, callback = None):
"""
if callback is None:
callback = print_line
resp = self.sendcmd('TYPE A')
self.sendcmd('TYPE A')
with self.transfercmd(cmd) as conn, \
conn.makefile('r', encoding=self.encoding) as fp:
while 1:
Expand Down Expand Up @@ -951,7 +951,7 @@ def test():
elif file[:2] == '-d':
cmd = 'CWD'
if file[2:]: cmd = cmd + ' ' + file[2:]
resp = ftp.sendcmd(cmd)
ftp.sendcmd(cmd)
elif file == '-p':
ftp.set_pasv(not ftp.passiveserver)
else:
Expand Down
2 changes: 1 addition & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ def wrapper(*args, **kwds):
# still adjusting the links.
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
oldresult = root[RESULT] # noqa: F841
root[KEY] = root[RESULT] = None

# Now update the cache dictionary.
Expand Down
1 change: 0 additions & 1 deletion Lib/idlelib/pyshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,6 @@ def restart_subprocess(self, with_cwd=False, filename=''):
self.rpcclt.close()
self.terminate_subprocess()
console = self.tkconsole
was_executing = console.executing
console.executing = False
self.spawn_subprocess()
try:
Expand Down
4 changes: 2 additions & 2 deletions Lib/idlelib/textview.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ def __init__(self, parent, title, contents, modal=True, wrap=WORD,
self.title(title)
self.viewframe = ViewFrame(self, contents, wrap=wrap)
self.protocol("WM_DELETE_WINDOW", self.ok)
self.button_ok = button_ok = Button(self, text='Close',
command=self.ok, takefocus=False)
self.button_ok = Button(self, text='Close',
command=self.ok, takefocus=False)
self.viewframe.pack(side='top', expand=True, fill='both')

self.is_modal = modal
Expand Down
Loading
Loading