Skip to content

gh-75537: Add gc.ensure_disabled() context manager - #154999

Closed
SakshamKapoor2911 wants to merge 5 commits into
python:mainfrom
SakshamKapoor2911:fix/pr75537-gc-ensure-disabled
Closed

gh-75537: Add gc.ensure_disabled() context manager#154999
SakshamKapoor2911 wants to merge 5 commits into
python:mainfrom
SakshamKapoor2911:fix/pr75537-gc-ensure-disabled

Conversation

@SakshamKapoor2911

@SakshamKapoor2911 SakshamKapoor2911 commented Jul 31, 2026

Copy link
Copy Markdown

Fixes: #75537

Summary

Add gc.ensure_disabled() — a context manager that temporarily disables the cyclic garbage collector and reliably restores it to its previous state on exit.

with gc.ensure_disabled():
    ...  # GC is disabled during this block

Background

This feature was originally implemented by @pablogsal in 2018 (PR #4224), merged by @rhettinger, and subsequently reverted (PR #5495/#5496) due to edge cases around GIL release, debug build assertions, and threading concerns during the 3.7 beta window.

The modern CPython C API (PyGC_Disable() returns the old state) resolves the key state-tracking issue. The implementation:

  • Uses PyGC_Disable() (returns old state) for atomic state save/restore
  • Runs entirely under the GIL — no threading races
  • Handles nesting: each level saves and restores its own state
  • Properly handles the "already disabled" case: GC stays disabled

Tests

Four new tests in Lib/test/test_gc.py (GCTogglingTests):
test_ensure_disabled, test_ensure_disabled_nesting,
test_ensure_disabled_already_disabled, test_ensure_disabled_exception

Existing test suite: 59 tests pass, 0 regressions.

Add a context manager to the gc module that temporarily disables the
cyclic garbage collector and reliably restores it to its previous
state on exit.  Uses PyGC_Disable() which returns the old state,
making nesting and restoration atomic under the GIL.

The feature was originally merged in 2018 (PR python#4224) but reverted
due to edge cases around GIL release, debug build crashes, and
unclear nesting semantics.  The modern C API (PyGC_Disable returning
old state) resolves the state-tracking issues that caused the revert.
@python-cla-bot

python-cla-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app

bedevere-app Bot commented Jul 31, 2026

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Comment thread Modules/gcmodule.c Outdated
@read-the-docs-community

read-the-docs-community Bot commented Jul 31, 2026

Copy link
Copy Markdown

@SakshamKapoor2911
SakshamKapoor2911 force-pushed the fix/pr75537-gc-ensure-disabled branch 2 times, most recently from 3dc9842 to 060cb64 Compare July 31, 2026 16:17
@SakshamKapoor2911
SakshamKapoor2911 force-pushed the fix/pr75537-gc-ensure-disabled branch from 060cb64 to 53e93bc Compare July 31, 2026 16:28

@maurycy maurycy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, there much, much more subtlety around pausing (defering):

#153946 (comment)
#153946 (comment)
#154188 (comment)
#154188 (comment)

Perhaps the right course of action is reusing internals that'd be added by #154188.

Comment thread Modules/gcmodule.c
Comment thread Modules/gcmodule.c


PyDoc_STRVAR(gc_ensure_disabled__doc__,
"ensure_disabled() -> context manager\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gc.ensure_disabled() without with would be irreversible?

@SakshamKapoor2911 SakshamKapoor2911 Jul 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, and consistent with all Python context managers. threading.Lock().acquire() without with also leaves the lock held indefinitely. This is a known limitation: exit only fires via the with statement. Adding a tp_dealloc fallback to re-enable GC on garbage collection would cause surprising behavior (the GC silently re-enabling at an unpredictable time). The with statement is the only supported usage.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maurycy Do you think this is a reasonable tradeoff, or would you suggest I made modifications to prevent this?

Comment thread Modules/gcmodule.c
@SakshamKapoor2911

SakshamKapoor2911 commented Jul 31, 2026

Copy link
Copy Markdown
Author

Thanks for the pointer to #154188 and #153946. I think these are complementary layers of the same capability:

  • gh-153946: Add API to defer automatic garbage collection #154188 adds a private _PyGC_PauseAutomaticCollection for CPython internals — gc.isenabled() stays True, only automatic collection is deferred. Right for core code building large object graphs.

  • This PR adds a public gc.ensure_disabled() for users. gc.isenabled() returns False inside the block, telling user code the GC is off. Right for user code that needs to know the GC is paused.

They differ by design: the private API preserves gc.isenabled() because internals shouldn't change user-visible state. The public API should change gc.isenabled() because users need to see the effect.

The 2018 merged implementation (PR #4224) also used disable/enable, and the revert was for unrelated issues (GIL release, debug crash) that PyGC_Disable() returning old state now resolves.

@SakshamKapoor2911
SakshamKapoor2911 force-pushed the fix/pr75537-gc-ensure-disabled branch from 53e93bc to 5a55da3 Compare July 31, 2026 17:10
@Aniketsy

Copy link
Copy Markdown
Contributor

@SakshamKapoor2911 just small suggestion please avoid force push as this forces members to review PR from beginning instead of new changes ,

@picnixz

picnixz commented Aug 1, 2026

Copy link
Copy Markdown
Member

This PR is premature. The discussion did not advance since 3y ago and I believe a newer discussion is necessary first.

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why we could not have a pure Python function for that, left to users themselves. What is wrong with manually disabling/enabling the GC first?

@bedevere-app

bedevere-app Bot commented Aug 1, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add context manager to temporarily disable GC

5 participants