Skip to content

[Python] Delete TBrowser instances at exit#22200

Open
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:issue-21912
Open

[Python] Delete TBrowser instances at exit#22200
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:issue-21912

Conversation

@guitargeek
Copy link
Copy Markdown
Contributor

Closes #21912, which reported that a Python session that constructs a TRootBrowser segfaults at exit inside TROOT::EndOfProcessCleanups().

To fix this, register an atexit hook that calls
gROOT->GetListOfBrowsers()->Delete() while the Python interpreter is still alive. This is the same workaround suggested in the issue, just made automatic. Honors PyConfig.ShutDown, and short-circuits if _finalSetup() never ran (no ROOT C++ runtime means nothing to clean up, and we do not want to drag cppyy in).

Why we cannot just call EndOfProcessCleanups() from atexit? That was the previous behaviour, removed in commit e9d2803 ("[python] Avoid interfering with gc at exit time"). Doing this would re-introduce test failures that the removal commit as fixed:

  pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-tdirectory-attrsyntax
  pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-tdirectoryfile-attrsyntax-get

EndOfProcessCleanups() tears down C++ objects that the TestCase classes still hold via CPyCppyy proxies (TFile / TDirectory). When CPython's gc walks those classes, it deallocates the proxies and reads memory ROOT just freed. Limiting the exit-time work to TROOT::fBrowsers leaves such objects untouched, so those tests stay green.

Closes root-project#21912, which reported that a Python session that constructs a
TRootBrowser segfaults at exit inside `TROOT::EndOfProcessCleanups()`.

To fix this, register an atexit hook that calls
`gROOT->GetListOfBrowsers()->Delete()` while the Python interpreter is
still alive. This is the same workaround suggested in the issue, just
made automatic. Honors `PyConfig.ShutDown`, and short-circuits if
`_finalSetup()` never ran (no ROOT C++ runtime means nothing to clean
up, and we do not want to drag cppyy in).

Why we cannot just call `EndOfProcessCleanups()` from atexit? That was
the previous behaviour, removed in commit e9d2803 ("[python] Avoid
interfering with gc at exit time"). Doing this would re-introduce test
failures that the removal commit as fixed:

```
  pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-tdirectory-attrsyntax
  pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-tdirectoryfile-attrsyntax-get
```

`EndOfProcessCleanups()` tears down C++ objects that the TestCase
classes still hold via CPyCppyy proxies (TFile / TDirectory). When
CPython's gc walks those classes, it deallocates the proxies and reads
memory ROOT just freed. Limiting the exit-time work to
`TROOT::fBrowsers` leaves such objects untouched, so those tests stay
green.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

Test Results

    22 files      22 suites   3d 17h 2m 25s ⏱️
 3 847 tests  3 846 ✅ 0 💤 1 ❌
76 894 runs  76 893 ✅ 0 💤 1 ❌

For more details on these failures, see this check.

Results for commit ece41ea.

♻️ This comment has been updated with latest results.

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.

Crash in TROOT::EndOfProcessCleanups if created a classic TRootBrowser in python session through c++

1 participant