gc

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: May 8, 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 added in v0.12.0

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 added in v0.12.0

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 added in v0.10.0

func Disable()

Disable suppresses automatic collection.

CPython: Python/gc.c:1654 PyGC_Disable

func Enable added in v0.10.0

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 added in v0.10.0

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 added in v0.10.0

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 added in v0.10.0

func GetCount() (gen0, gen1, gen2 int)

GetCount returns the (gen0, gen1, gen2) live counts.

CPython: Modules/gcmodule.c gc_get_count_impl

func GetDebug added in v0.10.1

func GetDebug() int

GetDebug reports the current debug bitmask.

CPython: Modules/gcmodule.c:131 gc_get_debug_impl

func GetFreezeCount added in v0.10.0

func GetFreezeCount() int

GetFreezeCount reports how many objects are currently frozen.

CPython: Python/gc.c:1740 _PyGC_GetFreezeCount

func GetObjects added in v0.10.0

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 added in v0.10.0

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 added in v0.10.0

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 added in v0.10.0

func GetThreshold() (gen0, gen1, gen2 int)

GetThreshold returns the (gen0, gen1, gen2) thresholds.

CPython: Modules/gcmodule.c gc_get_threshold_impl

func GoalReached added in v0.12.0

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 added in v0.10.0

func IsEnabled() bool

IsEnabled reports whether automatic collection is on.

CPython: Python/gc.c:1663 PyGC_IsEnabled

func IsFinalized added in v0.10.1

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.10.1

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 added in v0.10.0

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 added in v0.10.1

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 added in v0.10.1

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 added in v0.10.1

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 added in v0.10.0

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 added in v0.10.0

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 added in v0.10.1

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 added in v0.10.1

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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 added in v0.12.0

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