From 15238f0d6ccf3d24986dff6f01ae030b4206e2e0 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 3 Feb 2026 11:39:07 +0100 Subject: [PATCH 1/4] [PyROOT] Release GIL before TH1.Fit runs. TH1.Fit might call into Python functions, which would require the GIL to be held. If TH1.Fit holds it, though, the process deadlocks. Here, the GIL is released before TH1.Fit, which runs fully in C++ anyway. Fix #21080. (cherry picked from commit e8a739aae5ad5153172707ca131d4dbea11b9d37) --- .../pyroot/pythonizations/python/ROOT/_pythonization/_th1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_th1.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_th1.py index 0e9a819102c6d..9efd66e13b890 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_th1.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_th1.py @@ -265,3 +265,4 @@ def pythonize_th1(klass): _add_indexing_features(klass) inject_clone_releasing_ownership(klass) + klass.Fit.__release_gil__ = True From 4275350693121d184a1f9d8b83bf40d436138379 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 3 Feb 2026 12:53:49 +0100 Subject: [PATCH 2/4] [PyROOT] Rename TH1's test file to collect future tests in one file. This saves startup times and collects TH1 tests in one place. (cherry picked from commit 9372b504ed1506d97c51e753eced16773e1ab5c1) --- bindings/pyroot/pythonizations/test/CMakeLists.txt | 2 +- .../pyroot/pythonizations/test/{th1_operators.py => th1.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename bindings/pyroot/pythonizations/test/{th1_operators.py => th1.py} (100%) diff --git a/bindings/pyroot/pythonizations/test/CMakeLists.txt b/bindings/pyroot/pythonizations/test/CMakeLists.txt index ad4dd92ca3794..c37c47a3ebd89 100644 --- a/bindings/pyroot/pythonizations/test/CMakeLists.txt +++ b/bindings/pyroot/pythonizations/test/CMakeLists.txt @@ -59,7 +59,7 @@ ROOT_ADD_PYUNITTEST(pyroot_pyz_ttree_branch ttree_branch.py PYTHON_DEPS numpy) ROOT_ADD_PYUNITTEST(pyroot_pyz_tcolor tcolor.py) # TH1 and subclasses pythonizations -ROOT_ADD_PYUNITTEST(pyroot_pyz_th1_operators th1_operators.py) +ROOT_ADD_PYUNITTEST(pyroot_pyz_th1 th1.py) ROOT_ADD_PYUNITTEST(pyroot_pyz_th1_fillN th1_fillN.py PYTHON_DEPS numpy) ROOT_ADD_PYUNITTEST(pyroot_pyz_th2 th2.py) ROOT_ADD_PYUNITTEST(pyroot_pyz_th3 th3.py) diff --git a/bindings/pyroot/pythonizations/test/th1_operators.py b/bindings/pyroot/pythonizations/test/th1.py similarity index 100% rename from bindings/pyroot/pythonizations/test/th1_operators.py rename to bindings/pyroot/pythonizations/test/th1.py From d06af450df05a84b20d08728a69d3a0141505178 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 3 Feb 2026 13:50:42 +0100 Subject: [PATCH 3/4] [PyROOT] Add test for TH1.Fit with IMT. The timeout of the test is reduced because the deadlock would otherwise show only after 1500s. (cherry picked from commit b14edf99144894b9f55ae6d8fc7cd6fa6da55158) --- .../pyroot/pythonizations/test/CMakeLists.txt | 2 + bindings/pyroot/pythonizations/test/th1.py | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/pythonizations/test/CMakeLists.txt b/bindings/pyroot/pythonizations/test/CMakeLists.txt index c37c47a3ebd89..9be01d99e8909 100644 --- a/bindings/pyroot/pythonizations/test/CMakeLists.txt +++ b/bindings/pyroot/pythonizations/test/CMakeLists.txt @@ -60,6 +60,8 @@ ROOT_ADD_PYUNITTEST(pyroot_pyz_tcolor tcolor.py) # TH1 and subclasses pythonizations ROOT_ADD_PYUNITTEST(pyroot_pyz_th1 th1.py) +# The above tests a deadlock. It should complete in about 1s. If we don't reduce the timeout, we need to wait 1500 s. +set_tests_properties(pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-th1 PROPERTIES TIMEOUT 30) ROOT_ADD_PYUNITTEST(pyroot_pyz_th1_fillN th1_fillN.py PYTHON_DEPS numpy) ROOT_ADD_PYUNITTEST(pyroot_pyz_th2 th2.py) ROOT_ADD_PYUNITTEST(pyroot_pyz_th3 th3.py) diff --git a/bindings/pyroot/pythonizations/test/th1.py b/bindings/pyroot/pythonizations/test/th1.py index 881f89e8baa19..5dd7f32e90367 100644 --- a/bindings/pyroot/pythonizations/test/th1.py +++ b/bindings/pyroot/pythonizations/test/th1.py @@ -15,7 +15,7 @@ def test_imul(self): h = ROOT.TH1F("testHist", "", nbins, -4, 4) h.FillRandom("gaus") - initial_bins = [ h.GetBinContent(i) for i in range(nbins) ] + initial_bins = [h.GetBinContent(i) for i in range(nbins)] c = 2 # Multiply in place @@ -26,5 +26,45 @@ def test_imul(self): self.assertEqual(h.GetBinContent(i), initial_bins[i] * c) -if __name__ == '__main__': +class TH1IMT(unittest.TestCase): + """ + Test a deadlock when IMT is used in conjunction with a fit function in Python. + Since TH1.Fit held the GIL, the fit function could never be evaluated + """ + + @classmethod + def setUpClass(cls): + ROOT.ROOT.EnableImplicitMT(4) + + @classmethod + def tearDownClass(cls): + ROOT.ROOT.DisableImplicitMT() + + def test_fit_python_function(self): + xmin = 0 + xmax = 1 + + h1 = ROOT.TH1F("h1", "", 20, xmin, xmax) + h1.FillRandom("gaus", 1000) + + def func(x, pars): + return pars[0] + pars[1] * x[0] + + my_func = ROOT.TF1("f1", func, xmin, xmax, npar=2, ndim=1) + + my_func.SetParNames( + "A", + "B", + ) + my_func.SetParameter(0, 1) + my_func.SetParameter(1, -1) + + r = h1.Fit(my_func, "SE0Q", "", xmin, xmax) + + self.assertFalse(r.IsEmpty()) + self.assertTrue(r.IsValid()) + self.assertGreater(r.Parameter(0), 0) + + +if __name__ == "__main__": unittest.main() From 787d682337faaf617d1e7bebb15eeee396c9b40c Mon Sep 17 00:00:00 2001 From: siliataider Date: Thu, 5 Feb 2026 13:46:00 +0100 Subject: [PATCH 4/4] [DF][Python] Deserialize Dask results in the main thread (cherry picked from commit d48c865f4aeb186a2a24c161806af238d62ae50c) --- bindings/distrdf/python/DistRDF/Backends/Dask/Backend.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bindings/distrdf/python/DistRDF/Backends/Dask/Backend.py b/bindings/distrdf/python/DistRDF/Backends/Dask/Backend.py index 70ed6e8b7a8c7..a4e32951a7381 100644 --- a/bindings/distrdf/python/DistRDF/Backends/Dask/Backend.py +++ b/bindings/distrdf/python/DistRDF/Backends/Dask/Backend.py @@ -319,10 +319,11 @@ def _process_partial_results(self, cumulative_plots: Dict[int, Any] = {} # Collect all futures in batches that had arrived since the last iteration - for batch in as_completed(future_tasks, with_results=True).batches(): - for future, result in batch: - merged_results = reducer(merged_results, result) if merged_results else result - + for batch in as_completed(future_tasks, with_results=False).batches(): + for future in batch: + result = future.result() + merged_results = reducer(merged_results, result) if merged_results else result + mergeables = merged_results.mergeables for pad_num, (drawable_id, (callbacks_list, index, operation_name)) in enumerate(drawables_info_dict.items(), start=1):