gc

package
v0.12.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

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

View Source
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

View Source
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_*

View Source
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

View Source
const NumGenerations = 3

NumGenerations is the number of CPython generations.

CPython: Include/internal/pycore_interp_structs.h:200 NUM_GENERATIONS

Variables

View Source
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

func Collect(gen int) int

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

func Finalize(o objects.Object)

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

func Garbage() []objects.Object

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

func GetObjects(gen int) []objects.Object

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

func GetReferents(args ...objects.Object) []objects.Object

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

func GetReferrers(args ...objects.Object) []objects.Object

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

func IsFinalized(o objects.Object) bool

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

func IsTracked(o objects.Object) bool

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

func QSBRLEQ(a, b uint64) bool

QSBRLEQ reports a <= b under wrap-around-safe comparison.

CPython: Include/internal/pycore_qsbr.h:35 QSBR_LEQ

func QSBRLT

func QSBRLT(a, b uint64) bool

QSBRLT reports a < b under wrap-around-safe comparison.

CPython: Include/internal/pycore_qsbr.h:34 QSBR_LT

func RegisterFinalizer

func RegisterFinalizer(o objects.Object, fn Finalizer)

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

func RegisterWeakProxy(p *objects.WeakProxy)

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

func RegisterWeakref(w *objects.Weakref)

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

func SetCallbacks(cbs *objects.List)

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

func SetGarbage(g *objects.List)

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

func Track(o objects.Object)

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

func Untrack(o objects.Object)

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

type Finalizer func(o objects.Object)

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

func GetStats

func GetStats() []GenStats

GetStats snapshots the per-generation (collections, collected, uncollectable) counters. The returned slice has length NumGenerations.

CPython: Modules/gcmodule.c:365 gc_get_stats_impl

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

func (p *IndexPool) AllocIndex() (int32, error)

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

func (*IndexPool) Fini

func (p *IndexPool) Fini()

Fini releases the pool's storage. The PyMutex itself does not need teardown.

CPython: Python/index_pool.c:191-195 _PyIndexPool_Fini

func (*IndexPool) FreeIndex

func (p *IndexPool) FreeIndex(index int32)

FreeIndex returns index to the pool. Cannot fail because AllocIndex already grew the freelist to fit every outstanding index.

CPython: Python/index_pool.c:182-189 _PyIndexPool_FreeIndex

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 {
	WrSeq atomic.Uint64
	RdSeq atomic.Uint64
	// 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

	Shared *QSBRShared

	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)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL