From 0bb5456ba15ea0d244af600c2e280488427ee49f Mon Sep 17 00:00:00 2001 From: Vincenzo Eduardo Padulano Date: Mon, 25 May 2026 07:23:56 +0200 Subject: [PATCH] [python] Ensure GIL is held during CPython API call ROOT's CPython extension defines a custom error handler that tries to use Python standard warnings when possible. In such cases, the CPython API is called and thus the GIL must be held. If the calling Python function had previously released the GIL it will result in trouble. This is the case for the TH1.Fit method which has been changed to always releasing the GIL, under the assumption that everything happening during the call happens in C++, but that's not true. This commit proposes to restore the GIL for the call to the CPython API to raise the warning. (cherry picked from commit 56ce5ffb6ab87af88ad58ed4c8a5eb127e27f1f8) --- .../pythonizations/src/RPyROOTApplication.cxx | 2 ++ bindings/pyroot/pythonizations/test/th1.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx b/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx index 2808b2856f47d..67f11364399e3 100644 --- a/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx +++ b/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx @@ -122,7 +122,9 @@ static void ErrMsgHandler(int level, Bool_t abort, const char *location, const c // the GIL. if (!gGlobalMutex) { // Either printout or raise exception, depending on user settings + auto state = PyGILState_Ensure(); PyErr_WarnExplicit(NULL, (char *)msg, (char *)location, 0, (char *)"ROOT", NULL); + PyGILState_Release(state); } else { ::DefaultErrorHandler(level, abort, location, msg); } diff --git a/bindings/pyroot/pythonizations/test/th1.py b/bindings/pyroot/pythonizations/test/th1.py index 5dd7f32e90367..6d7332e5c0d0b 100644 --- a/bindings/pyroot/pythonizations/test/th1.py +++ b/bindings/pyroot/pythonizations/test/th1.py @@ -66,5 +66,19 @@ def func(x, pars): self.assertGreater(r.Parameter(0), 0) +class TH1FitWarning(unittest.TestCase): + def test_fit_with_warning(self): + """ + Regression test for https://github.com/root-project/root/issues/22396 + """ + import ROOT + + h = ROOT.TH1D("test", "test", 100, 0, 1) + # Trying to fit a empty histogram will result in a warning on the C++ side which is re-raised as a Python + # warning by the ROOT CPython extension + with self.assertWarns(RuntimeWarning, msg="Fit data is empty"): + h.Fit("gaus", "S") + + if __name__ == "__main__": unittest.main()