Documentation
¶
Overview ¶
Package gc ports cpython/Python/gc.c. v0.3 shipped Track / Untrack / RegisterFinalizer / Finalize against a package-level state variable; v0.10 grows that into a full GCState (see state.go) and adds the cycle collector. The public functions in this file keep their v0.3 signatures so the rest of the runtime compiles unchanged.
CPython: Python/gc.c file overview
Index ¶
- Constants
- Variables
- func Attach(qsbr *QSBRThreadState)
- func Collect(gen int) int
- func Detach(qsbr *QSBRThreadState)
- func Disable()
- func Enable()
- func Finalize(o objects.Object)
- func Freeze()
- func Garbage() []objects.Object
- func GetCount() (gen0, gen1, gen2 int)
- func GetDebug() int
- func GetFreezeCount() int
- func GetObjects(gen int) []objects.Object
- func GetReferents(args ...objects.Object) []objects.Object
- func GetReferrers(args ...objects.Object) []objects.Object
- func GetThreshold() (gen0, gen1, gen2 int)
- func GoalReached(qsbr *QSBRThreadState, goal uint64) bool
- func IsEnabled() bool
- func IsFinalized(o objects.Object) bool
- func IsTracked(o objects.Object) bool
- func Poll(qsbr *QSBRThreadState, goal uint64) bool
- func QSBRLEQ(a, b uint64) bool
- func QSBRLT(a, b uint64) bool
- func RegisterFinalizer(o objects.Object, fn Finalizer)
- func RegisterWeakProxy(p *objects.WeakProxy)
- func RegisterWeakref(w *objects.Weakref)
- func SetCallbacks(cbs *objects.List)
- func SetDebug(flags int)
- func SetGarbage(g *objects.List)
- func SetThreshold(gen0, gen1, gen2 int)
- func Track(o objects.Object)
- func Unfreeze()
- func Untrack(o objects.Object)
- type Finalizer
- type GenStats
- type IndexHeap
- type IndexPool
- type QSBRPad
- type QSBRShared
- func (s *QSBRShared) Advance() uint64
- func (s *QSBRShared) AfterFork(survivor *QSBRThreadState)
- func (s *QSBRShared) Fini()
- func (s *QSBRShared) Init()
- func (s *QSBRShared) QuiescentState(qsbr *QSBRThreadState)
- func (s *QSBRShared) Register(tstate any, index int) *QSBRThreadState
- func (s *QSBRShared) Reserve() int
- func (s *QSBRShared) SharedCurrent() uint64
- func (s *QSBRShared) SharedNext() uint64
- func (s *QSBRShared) Unregister(qsbr *QSBRThreadState)
- type QSBRThreadState
- type UniqueIDPool
Constants ¶
const ( QSBROffline uint64 = 0 QSBRInitial uint64 = 1 QSBRIncr uint64 = 2 )
QSBR sequence-number constants. The write sequence stays odd (incremented by 2) so the offline sentinel 0 is unambiguous.
CPython: Include/internal/pycore_qsbr.h:27-29 QSBR_OFFLINE / QSBR_INITIAL / QSBR_INCR
const ( DebugStats = 1 << 0 DebugCollectable = 1 << 1 DebugUncollectable = 1 << 2 DebugSaveAll = 1 << 5 DebugLeak = DebugCollectable | DebugUncollectable | DebugSaveAll )
Debug flag bits, surfaced as module-level constants on the gc module.
CPython: Include/internal/pycore_gc.h _PyGC_DEBUG_*
const InvalidUniqueID int64 = 0
InvalidUniqueID is the sentinel returned when AssignUniqueID fails or when an object has not been assigned an id.
CPython: Include/internal/pycore_uniqueid.h:28 _Py_INVALID_UNIQUE_ID
const NumGenerations = 3
NumGenerations is the number of CPython generations.
CPython: Include/internal/pycore_interp_structs.h:200 NUM_GENERATIONS
Variables ¶
var ErrIndexPoolNoMemory = errors.New("index pool: out of memory")
ErrIndexPoolNoMemory is returned when AllocIndex cannot grow the freelist to cover the next outstanding index. CPython raises MemoryError here; gopy returns the error so the caller can decide.
CPython: Python/index_pool.c:167 PyErr_NoMemory
Functions ¶
func Attach ¶
func Attach(qsbr *QSBRThreadState)
Attach marks qsbr as online by stamping the current write sequence into qsbr.Seq. Caller must not have an outstanding attach.
CPython: Python/qsbr.c:173-180 _Py_qsbr_attach
func Collect ¶
Collect runs a collection on generations 0..gen and returns the number of objects reclaimed. The argument is clamped into the [0, NumGenerations) range so callers can pass gc.collect()'s optional generation through unchecked. When the collector is disabled (gc.disable()) Collect returns 0 without touching state.
Weakref callbacks queued by handleWeakrefs run after the collector lock has been released. CPython does the same so callbacks can safely take the GIL, allocate, or trigger another collection.
CPython: Python/gc.c:1430 gc_collect_main
func Detach ¶
func Detach(qsbr *QSBRThreadState)
Detach marks qsbr as offline so the next scan ignores it. Caller must not have outstanding pointer accesses to shared data.
CPython: Python/qsbr.c:182-188 _Py_qsbr_detach
func Disable ¶
func Disable()
Disable suppresses automatic collection.
CPython: Python/gc.c:1654 PyGC_Disable
func Enable ¶
func Enable()
Enable turns automatic collection back on.
CPython: Python/gc.c:1645 PyGC_Enable
func Finalize ¶
Finalize runs the finalizer for o exactly once and clears it. Safe to call on objects that never registered one. Mirrors PyObject_CallFinalizerFromDealloc.
CPython: Objects/object.c:L497 PyObject_CallFinalizerFromDealloc
func Freeze ¶
func Freeze()
Freeze moves every tracked object out of the regular generations into the permanent list. Frozen objects are skipped by Collect.
CPython: Python/gc.c:1715 _PyGC_Freeze
func Garbage ¶
Garbage returns a snapshot of the gc.garbage list. The list grows when the collector runs with DEBUG_SAVEALL set. The same list object is stamped onto the gc module dict, so user code can also read or clear it through gc.garbage directly.
CPython: Modules/gcmodule.c gc.garbage attribute
func GetCount ¶
func GetCount() (gen0, gen1, gen2 int)
GetCount returns the (gen0, gen1, gen2) live counts.
CPython: Modules/gcmodule.c gc_get_count_impl
func GetDebug ¶
func GetDebug() int
GetDebug reports the current debug bitmask.
CPython: Modules/gcmodule.c:131 gc_get_debug_impl
func GetFreezeCount ¶
func GetFreezeCount() int
GetFreezeCount reports how many objects are currently frozen.
CPython: Python/gc.c:1740 _PyGC_GetFreezeCount
func GetObjects ¶
GetObjects returns every tracked object. When gen is in [0, NumGenerations) only that generation is reported; pass -1 for "all generations". Mirrors the optional generation argument on gc.get_objects.
CPython: Modules/gcmodule.c gc_get_objects_impl
func GetReferents ¶
GetReferents returns every direct target of any object in args. Untracked objects can still be passed in; we walk their tp_traverse regardless.
CPython: Modules/gcmodule.c gc_get_referents
func GetReferrers ¶
GetReferrers returns every tracked object whose tp_traverse visits at least one of the args.
CPython: Modules/gcmodule.c gc_get_referrers
func GetThreshold ¶
func GetThreshold() (gen0, gen1, gen2 int)
GetThreshold returns the (gen0, gen1, gen2) thresholds.
CPython: Modules/gcmodule.c gc_get_threshold_impl
func GoalReached ¶
func GoalReached(qsbr *QSBRThreadState, goal uint64) bool
GoalReached reports whether every reader has published a sequence at or after goal. Cheaper than Poll because it skips the per-thread scan.
CPython: Include/internal/pycore_qsbr.h:113-118 _Py_qbsr_goal_reached
func IsEnabled ¶
func IsEnabled() bool
IsEnabled reports whether automatic collection is on.
CPython: Python/gc.c:1663 PyGC_IsEnabled
func IsFinalized ¶
IsFinalized reports whether the GC has already run o's finalizer. Mirrors PyObject_GC_IsFinalized: truthy means finalizeGarbage has touched o on a prior cycle.
CPython: Include/cpython/objimpl.h PyObject_GC_IsFinalized
func IsTracked ¶
IsTracked reports whether o is currently tracked.
CPython: Include/internal/pycore_object.h:268 _PyObject_GC_IS_TRACKED
func Poll ¶
func Poll(qsbr *QSBRThreadState, goal uint64) bool
Poll reports whether goal has been reached. Triggers a scan when the cached rdSeq is too old.
CPython: Python/qsbr.c:159-171 _Py_qsbr_poll
func QSBRLEQ ¶
QSBRLEQ reports a <= b under wrap-around-safe comparison.
CPython: Include/internal/pycore_qsbr.h:35 QSBR_LEQ
func QSBRLT ¶
QSBRLT reports a < b under wrap-around-safe comparison.
CPython: Include/internal/pycore_qsbr.h:34 QSBR_LT
func RegisterFinalizer ¶
RegisterFinalizer associates fn with o. The runtime calls Finalize to invoke it. Mirrors PyObject_GC_RegisterFinalizer in spirit; in CPython the per-object slot is tp_finalize, but gopy keeps the mapping out-of-band so Header stays small.
CPython: Objects/object.c:L489 PyObject_CallFinalizer (caller side)
func RegisterWeakProxy ¶
RegisterWeakProxy records p against its referent. The collector clears the proxy and queues its callback exactly the way it does for ref-style weakrefs.
CPython: Objects/weakrefobject.c:925 PyWeakref_NewProxy registers via the same tp_weaklistoffset slot used by PyWeakref_NewRef.
func RegisterWeakref ¶
RegisterWeakref records w against its referent so a future collection can clear it. Caller-side: objects.NewWeakref builds the weakref; gopy code that wants the GC to clear the weakref when the referent dies must call this.
CPython: Objects/weakrefobject.c:271 PyWeakref_NewRef registers via the referent's tp_weaklistoffset slot.
func SetCallbacks ¶
SetCallbacks installs the gc.callbacks list. The module-level setup stamps a list on the module dict and remembers it here so the collector can iterate it. Passing nil clears the binding.
CPython: Modules/gcmodule.c module init publishes gcstate->callbacks and the collector iterates it through invoke_gc_callback.
func SetDebug ¶
func SetDebug(flags int)
SetDebug installs the debug bitmask used by the collector for the DEBUG_STATS / DEBUG_COLLECTABLE / DEBUG_UNCOLLECTABLE / DEBUG_SAVEALL hooks. Out-of-range bits are accepted unchanged to match CPython.
CPython: Modules/gcmodule.c:116 gc_set_debug_impl
func SetGarbage ¶
SetGarbage installs the gc.garbage list. The module init stamps the list onto the module dict and registers it here so the collector can append uncollectable cycles when DEBUG_SAVEALL is set. Passing nil clears the binding.
CPython: Python/gc.c:180 _PyGC_Init publishes gcstate->garbage; the collector appends through delete_garbage / handle_legacy_finalizers.
func SetThreshold ¶
func SetThreshold(gen0, gen1, gen2 int)
SetThreshold installs new generation thresholds. CPython treats negative or zero on gen0 as "disable automatic collection"; we mirror that by zeroing the count fields in the same way.
CPython: Modules/gcmodule.c gc_set_threshold
func Track ¶
Track adds o to the youngest generation. CPython appends to the generation0 list head; we follow exactly that pattern.
CPython: Include/internal/pycore_object.h:225 _PyObject_GC_TRACK
func Unfreeze ¶
func Unfreeze()
Unfreeze moves every permanent object back to gen 0.
CPython: Python/gc.c:1727 _PyGC_Unfreeze
func Untrack ¶
Untrack removes o from whichever generation list it currently sits on. No-op if the object was never tracked. CPython internally keeps the FINALIZED bit while clearing COLLECTING; gopy lets the gcHead vanish since the per-object map drops it too.
CPython: Include/internal/pycore_object.h:248 _PyObject_GC_UNTRACK
Types ¶
type Finalizer ¶
Finalizer is the Go equivalent of tp_finalize. The runtime invokes it once, immediately before reclaiming the object.
CPython: Include/cpython/object.h:237 tp_finalize
type GenStats ¶
type GenStats struct {
// contains filtered or unexported fields
}
GenStats mirrors CPython's struct gc_generation_stats: per-generation counters that get_stats reports back to user code.
CPython: Include/internal/pycore_interp_structs.h gc_generation_stats
type IndexHeap ¶
type IndexHeap struct {
// contains filtered or unexported fields
}
IndexHeap is a min-heap of int32. Used as the freelist of released indices so AllocIndex always returns the smallest available value.
CPython: Include/internal/pycore_interp_structs.h:725-734 _PyIndexHeap
type IndexPool ¶
type IndexPool struct {
FreeIndices IndexHeap
NextIndex int32
TLBCGeneration uint32
// contains filtered or unexported fields
}
IndexPool is the unbounded pool of indices. Indices are dispensed starting from 0; freed indices are pushed onto FreeIndices and reused on the next allocation.
CPython: Include/internal/pycore_interp_structs.h:738-750 _PyIndexPool
func (*IndexPool) AllocIndex ¶
AllocIndex returns the smallest available index. Callers receive 0 on the first call, 1 on the second, and so on; freed indices are reused before NextIndex grows.
CPython: Python/index_pool.c:151-180 _PyIndexPool_AllocIndex
type QSBRPad ¶
type QSBRPad struct {
QSBR QSBRThreadState
// contains filtered or unexported fields
}
QSBRPad pads QSBRThreadState out to a 64-byte cache line so two threads do not false-share their sequence stores.
CPython: Include/internal/pycore_qsbr.h:73-76 _qsbr_pad
type QSBRShared ¶
type QSBRShared struct {
// contains filtered or unexported fields
}
QSBRShared is the per-interpreter QSBR state. wrSeq advances on every Advance; rdSeq is the minimum observed sequence across all attached threads, computed lazily by qsbrPollScan.
CPython: Include/internal/pycore_qsbr.h:79-94 _qsbr_shared
func (*QSBRShared) Advance ¶
func (s *QSBRShared) Advance() uint64
Advance bumps the write sequence by QSBRIncr and returns the new value. Callers use the return value as a goal for Poll.
CPython: Python/qsbr.c:113-120 _Py_qsbr_advance
func (*QSBRShared) AfterFork ¶
func (s *QSBRShared) AfterFork(survivor *QSBRThreadState)
AfterFork rebuilds the freelist so only the surviving thread's slot stays allocated. Other allocated slots come from threads that did not fork, so they are returned to the freelist.
CPython: Python/qsbr.c:273-290 _Py_qsbr_after_fork
func (*QSBRShared) Fini ¶
func (s *QSBRShared) Fini()
Fini drops the array. Called from the interpreter teardown.
CPython: Python/qsbr.c:262-271 _Py_qsbr_fini
func (*QSBRShared) Init ¶
func (s *QSBRShared) Init()
Init seeds wrSeq and rdSeq to QSBRInitial. The runtime-init macro in CPython does this statically; gopy needs an explicit call so a freshly-constructed QSBRShared does not look like every reader is permanently offline (Seq == 0 == QSBROffline).
CPython: Include/internal/pycore_runtime_init.h:143-144 .wr_seq / .rd_seq = QSBR_INITIAL
func (*QSBRShared) QuiescentState ¶
func (s *QSBRShared) QuiescentState(qsbr *QSBRThreadState)
QuiescentState publishes the latest write sequence as the thread's observed read sequence. Called at points where the thread holds no shared pointers needing protection.
CPython: Include/internal/pycore_qsbr.h:104-109 _Py_qsbr_quiescent_state
func (*QSBRShared) Register ¶
func (s *QSBRShared) Register(tstate any, index int) *QSBRThreadState
Register binds tstate to the QSBR slot at index. tstate is stored as any so package gc stays free of a state import.
CPython: Python/qsbr.c:219-232 _Py_qsbr_register
func (*QSBRShared) Reserve ¶
func (s *QSBRShared) Reserve() int
Reserve allocates a QSBR slot and returns its index. Returns -1 if the array could not be grown.
gopy NOTE: upstream brackets growThreadArray with StopTheWorld / StartTheWorld; gopy has no stop-the-world hook yet, so the lock on s.mu is the only serialization. The freelist invariant still holds: the array slice is rebuilt before any reader sees the new indices.
CPython: Python/qsbr.c:190-217 _Py_qsbr_reserve
func (*QSBRShared) SharedCurrent ¶
func (s *QSBRShared) SharedCurrent() uint64
SharedCurrent returns the latest write sequence.
CPython: Include/internal/pycore_qsbr.h:96-100 _Py_qsbr_shared_current
func (*QSBRShared) SharedNext ¶
func (s *QSBRShared) SharedNext() uint64
SharedNext returns the next sequence value (current + increment) without bumping the writer.
CPython: Python/qsbr.c:122-126 _Py_qsbr_shared_next
func (*QSBRShared) Unregister ¶
func (s *QSBRShared) Unregister(qsbr *QSBRThreadState)
Unregister releases qsbr back to the freelist. Caller must have already detached qsbr (Seq == QSBROffline).
CPython: Python/qsbr.c:234-260 _Py_qsbr_unregister
type QSBRThreadState ¶
type QSBRThreadState struct {
Seq atomic.Uint64
TState any
DeferredCount int
DeferredMemory uint64
DeferredPageMemory uint64
ShouldProcess bool
Allocated bool
// contains filtered or unexported fields
}
QSBRThreadState is the per-thread QSBR slot. seq is the last sequence the thread observed (or QSBROffline when detached).
CPython: Include/internal/pycore_qsbr.h:41-70 _qsbr_thread_state
type UniqueIDPool ¶
type UniqueIDPool struct {
// contains filtered or unexported fields
}
UniqueIDPool is the per-interpreter pool of unique ids. The table is grown lazily; freelistHead chains the free slots through their next field as 1-based indices (0 == end of list).
CPython: Include/internal/pycore_interp_structs.h:760-771 _Py_unique_id_pool
func (*UniqueIDPool) AssignUniqueID ¶
func (p *UniqueIDPool) AssignUniqueID(obj any) int64
AssignUniqueID pulls the head off the freelist, stores obj in the resulting slot, and returns the 1-based id. Returns InvalidUniqueID if the resize fails.
CPython: Python/uniqueid.c:79-102 _PyObject_AssignUniqueID
func (*UniqueIDPool) Finalize ¶
func (p *UniqueIDPool) Finalize()
Finalize releases the pool's storage. Allocated entries have their obj cleared so the interpreter teardown does not leave dangling references in the pool table.
CPython: Python/uniqueid.c:203-230 _PyObject_FinalizeUniqueIdPool
func (*UniqueIDPool) Lookup ¶
func (p *UniqueIDPool) Lookup(id int64) any
Lookup returns the object currently associated with id, or nil when the slot is free or out of range. No CPython analog, but useful for the refcount-merge port that lands later.
func (*UniqueIDPool) ReleaseUniqueID ¶
func (p *UniqueIDPool) ReleaseUniqueID(id int64)
ReleaseUniqueID returns id to the freelist. The caller is responsible for ensuring nothing still holds id.
CPython: Python/uniqueid.c:104-117 _PyObject_ReleaseUniqueID
func (*UniqueIDPool) Size ¶
func (p *UniqueIDPool) Size() int
Size returns the current table size. Used by the per-thread refcount layer to right-size its values array.
CPython: read of _Py_atomic_load_ssize(&pool->size)