Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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