Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9c4c427
TRegion: Very basic TRegion type
xFrednet Apr 8, 2026
78e5fe4
Copy and pasta the needed bits
xFrednet Apr 8, 2026
8f11ad1
TracingRegions a working prototype
xFrednet Apr 15, 2026
cf0cacb
TracingRegions C-interface for closing
xFrednet Apr 16, 2026
f112d8b
Weakrefs again
xFrednet Apr 16, 2026
247f0cb
Cowns are working?
xFrednet Apr 16, 2026
6271692
IDK
xFrednet Apr 21, 2026
bf4b7b0
Memory fun
xFrednet Apr 28, 2026
31566c6
Pyrona: Log what objects have incoming refs
xFrednet Jul 15, 2026
4168771
TRegion: Mermaid plan
xFrednet Jul 15, 2026
545bc5d
TRegion: Add mermaid output
xFrednet Jul 15, 2026
8e4a045
TRegions: Disable LRU cache for `sqlite3`
xFrednet Aug 18, 2026
6200c35
TRegions: Only open on attribute access
xFrednet Aug 18, 2026
10af569
TRegions: Keep the bridge object in the GC list of the owning region
xFrednet Aug 18, 2026
e06a499
F: Bugfix and doc updates
xFrednet Aug 18, 2026
b0bf8fe
A compiling version
xFrednet Aug 19, 2026
ebb9505
Seemingly a working rewrite
xFrednet Aug 19, 2026
16cf7c1
TRegions
xFrednet Aug 19, 2026
c9500ec
Again nice error reporting and mermaid
xFrednet Aug 19, 2026
e282562
Immutability: Fix bug in shallow immutability check
xFrednet Aug 19, 2026
56948e9
TRegions: Fixes and niceties
xFrednet Aug 19, 2026
821b1d5
TRegions: A lot of bug fixes
xFrednet Aug 20, 2026
451149c
TRegions: Delete close region content
xFrednet Aug 20, 2026
d5a5b6b
TRegions: Delection tests
xFrednet Aug 22, 2026
dff0a7d
TRegions: hierachy cycle detection for better messages
xFrednet Aug 26, 2026
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
29 changes: 29 additions & 0 deletions Include/internal/pycore_cown.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef Py_INTERNAL_COWN_H
#define Py_INTERNAL_COWN_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "Py_BUILD_CORE must be defined to include this header"
#endif

#include "object.h"
#include "exports.h"

typedef struct _PyCownObject _PyCownObject;
#define _PyCownObject_CAST(op) _Py_CAST(_PyCownObject*, op)

PyAPI_DATA(PyTypeObject) _PyCown_Type;

typedef uint64_t _PyCown_ipid_t;
typedef uint64_t _PyCown_thread_id_t;

PyAPI_FUNC(_PyCown_ipid_t) _PyCown_ThisInterpreterId(void);
PyAPI_FUNC(_PyCown_thread_id_t) _PyCown_ThisThreadId(void);


#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_COWN_H */
19 changes: 19 additions & 0 deletions Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,25 @@ extern PyObject *_PyGC_GetObjects(PyInterpreterState *interp, int generation);
extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs);

// Functions to clear types free lists
/* Disposal of a list of objects that are known to be unreachable. Used by the
* collector itself and by anything else that owns a set of objects it has
* established to be garbage, such as a closed tracing region.
*
* `_PyGC_FinalizeGarbage()` runs the finalizer of every object in `collectable`,
* before anything is cleared, so that a `__del__` still sees its object intact.
*
* `_PyGC_DeleteGarbage()` then breaks the references between them, deallocating
* every object whose reference count reaches zero. Objects that a finalizer kept
* alive are moved to `old` instead.
*
* Neither may be called with an exception set. Only available in the default
* build; the free-threaded collector has its own implementation.
*/
#ifndef Py_GIL_DISABLED
extern void _PyGC_FinalizeGarbage(PyGC_Head *collectable);
extern void _PyGC_DeleteGarbage(PyGC_Head *collectable, PyGC_Head *old);
#endif

extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp);
extern void _Py_ScheduleGC(PyThreadState *tstate);
extern void _Py_RunGC(PyThreadState *tstate);
Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_immutability.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ extern "C" {
# error "Py_BUILD_CORE must be defined to include this header"
#endif

PyAPI_DATA(PyTypeObject) _PyTracingRegion_Type;
PyAPI_FUNC(int) _PyTracingRegion_Close(PyObject* region);
PyAPI_FUNC(int) _PyTracingRegion_IsClosed(PyObject* region);

struct _Py_immutability_state {
int late_init_done;
struct _Py_hashtable_t *shallow_immutable_types;
Expand Down
2 changes: 2 additions & 0 deletions Lib/immutable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
FREEZABLE_PROXY = _c.FREEZABLE_PROXY
InterpreterLocal = _c.InterpreterLocal
SharedField = _c.SharedField
TracingRegion = _c.TracingRegion
Cown = _c.Cown

# FIXME(immutable): For the longest time we used the name `isfrozen`
# without the underscore. This keeps the function name for now, but
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_freeze/test_implicit.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import unittest
from immutable import freeze, is_frozen

Expand Down Expand Up @@ -139,6 +140,26 @@ def test_deeply_nested_no_stack_overflow(self):
obj = (obj,)
self.assertTrue(is_frozen(obj))

def test_abandoned_walk_keeps_references(self):
"""An aborted walk must not drop references it never took.

The walk pushes objects onto a worklist without increfing them, so
anything still on the worklist when a mutable object aborts the walk
used to be decrefed when the worklist was released. That freed the
object while its real owners were still pointing at it, which showed
up much later as a negative refcount.
"""
# Built at runtime so it is neither interned nor immortal, which makes
# its reference count fully accounted for by this test.
item = "".join(["abandoned", "-", "worklist", "-", "entry"])
# Tuples are traversed back to front, so `item` reaches the worklist
# before the dict aborts the walk.
obj = ({"mutable": 1}, item)

before = sys.getrefcount(item)
self.assertFalse(is_frozen(obj))
self.assertEqual(sys.getrefcount(item), before)


if __name__ == '__main__':
unittest.main()
Loading
Loading