From 75c93da1004468fe3e388839b9f066582a667c46 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 23 Sep 2025 16:13:43 +0200 Subject: [PATCH 01/12] [NFC] Clarify the role of TH1::DirectoryAutoAdd. The ROOT 7 ownership settings don't affect TH1::AddDirectoryStatus, so objects read from a TFile associate themselves with that file. In light of adding the ROOT 7 mode "auto registration off", the doxygen was improved a bit. --- core/base/src/TDirectory.cxx | 2 +- hist/hist/src/TGraph2D.cxx | 8 +++----- hist/hist/src/TH1.cxx | 29 +++++++++++++++++++---------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/base/src/TDirectory.cxx b/core/base/src/TDirectory.cxx index f2d0f9a632d2b..136a7cb5b7ff2 100644 --- a/core/base/src/TDirectory.cxx +++ b/core/base/src/TDirectory.cxx @@ -364,7 +364,7 @@ static TBuffer* R__CreateBuffer() /// /// If autoadd is true and if the object class has a /// DirectoryAutoAdd function, it will be called at the end of the -/// function with the parameter gDirector. This usually means that +/// function with the parameter gDirectory. This usually means that /// the object will be appended to the current ROOT directory. TObject *TDirectory::CloneObject(const TObject *obj, Bool_t autoadd /* = kTRUE */) diff --git a/hist/hist/src/TGraph2D.cxx b/hist/hist/src/TGraph2D.cxx index 6fd4198beb2e4..7c1feec03bb47 100644 --- a/hist/hist/src/TGraph2D.cxx +++ b/hist/hist/src/TGraph2D.cxx @@ -707,13 +707,11 @@ void TGraph2D::Clear(Option_t * /*option = "" */) } } - //////////////////////////////////////////////////////////////////////////////// -/// Perform the automatic addition of the graph to the given directory +/// Registration of the graph to the given directory. /// -/// Note this function is called in place when the semantic requires -/// this object to be added to a directory (I.e. when being read from -/// a TKey or being Cloned) +/// This callback is used to register a TGraph2D to the current directory when a TKey +/// is read or an object is being cloned using TDirectory::CloneObject(). void TGraph2D::DirectoryAutoAdd(TDirectory *dir) { diff --git a/hist/hist/src/TH1.cxx b/hist/hist/src/TH1.cxx index 977c718c550fa..4a067e9d18e00 100644 --- a/hist/hist/src/TH1.cxx +++ b/hist/hist/src/TH1.cxx @@ -75,6 +75,8 @@ @} */ +// clang-format off + /** \class TH1 \ingroup Histograms TH1 is the base class of all histogram classes in %ROOT. @@ -113,22 +115,28 @@ ROOT supports the following histogram types: - TH1S : histograms with one short per channel. Maximum bin content = 32767 - TH1I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") - TH1L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "**") - - TH1F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = +/-16777216 (\ref floatmax "***") - - TH1D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = +/-9007199254740992 (\ref doublemax "****") + - TH1F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = ++/-16777216 (\ref floatmax "***") + - TH1D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = ++/-9007199254740992 (\ref doublemax "****") - 2-D histograms: - TH2C : histograms with one byte per channel. Maximum bin content = 127 - TH2S : histograms with one short per channel. Maximum bin content = 32767 - TH2I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") - TH2L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "**") - - TH2F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = +/-16777216 (\ref floatmax "***") - - TH2D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = +/-9007199254740992 (\ref doublemax "****") + - TH2F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = ++/-16777216 (\ref floatmax "***") + - TH2D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = ++/-9007199254740992 (\ref doublemax "****") - 3-D histograms: - TH3C : histograms with one byte per channel. Maximum bin content = 127 - TH3S : histograms with one short per channel. Maximum bin content = 32767 - TH3I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") - TH3L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "**") - - TH3F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = +/-16777216 (\ref floatmax "***") - - TH3D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = +/-9007199254740992 (\ref doublemax "****") + - TH3F : histograms with one float per channel. Maximum precision 7 digits, maximum integer bin content = ++/-16777216 (\ref floatmax "***") + - TH3D : histograms with one double per channel. Maximum precision 14 digits, maximum integer bin content = ++/-9007199254740992 (\ref doublemax "****") - Profile histograms: See classes TProfile, TProfile2D and TProfile3D. Profile histograms are used to display the mean value of Y and its standard deviation for each bin in X. Profile histograms are in many cases an elegant @@ -587,6 +595,8 @@ When using the options 2 or 3 above, the labels are automatically histogram, call TH1::ResetStats. See TH1::GetStats. */ +// clang-format on + TF1 *gF1=nullptr; //left for back compatibility (use TVirtualFitter::GetUserFunc instead) Int_t TH1::fgBufferSize = 1000; @@ -2806,11 +2816,10 @@ TObject* TH1::Clone(const char* newname) const } //////////////////////////////////////////////////////////////////////////////// -/// Perform the automatic addition of the histogram to the given directory +/// Callback to perform the automatic addition of the histogram to the given directory. /// -/// Note this function is called in place when the semantic requires -/// this object to be added to a directory (I.e. when being read from -/// a TKey or being Cloned) +/// This callback is used to register a histogram to the current directory when a TKey +/// is read or an object is being cloned using TDirectory::CloneObject(). void TH1::DirectoryAutoAdd(TDirectory *dir) { From cea7afbb4c74484d681c9a8b57f2638898e42b69 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 9 Sep 2025 10:52:41 +0200 Subject: [PATCH 02/12] [PyROOT] Make a TFile context manager test/tutorial ready for ROOT 7. - Don't rely on implicit registration to directories. - Make the test insensitive to any defaults by always adding the histogram to the file. - Make ruff happier. --- .../test/tfile_context_manager.py | 4 +-- tutorials/io/tfile_context_manager.py | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/bindings/pyroot/pythonizations/test/tfile_context_manager.py b/bindings/pyroot/pythonizations/test/tfile_context_manager.py index 7ab1f1e1b17e8..57a3fba2796c3 100644 --- a/bindings/pyroot/pythonizations/test/tfile_context_manager.py +++ b/bindings/pyroot/pythonizations/test/tfile_context_manager.py @@ -2,7 +2,6 @@ import unittest import ROOT - from ROOT import TFile @@ -64,6 +63,7 @@ def test_filewrite(self): histoname = "myhisto_3" with TFile(filename, "recreate") as outfile: hout = ROOT.TH1F(histoname, histoname, self.NBINS, self.XMIN, self.XMAX) + hout.SetDirectory(outfile) outfile.Write() self.check_file_data(outfile, filename, histoname) @@ -95,5 +95,5 @@ def test_detachhisto(self): os.remove(filename) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tutorials/io/tfile_context_manager.py b/tutorials/io/tfile_context_manager.py index b2b95fe625355..4d93748cf2fe5 100644 --- a/tutorials/io/tfile_context_manager.py +++ b/tutorials/io/tfile_context_manager.py @@ -14,10 +14,13 @@ import ROOT from ROOT import TFile, gROOT -# By default, objects of some ROOT types such as `TH1` and its derived types +# By default, in ROOT 6, objects of some ROOT types such as `TH1` and its derived types # are automatically attached to a ROOT.TDirectory when they are created. -# Specifically, at any given point of a ROOT application, the ROOT.gDirectory -# object tells which is the current directory where objects will be attached to. +# Objects are attached to the current directory denoted by ROOT.gDirectory. +# For ROOT 7, the default will be *not* to attach objects to ROOT.gDirectory, except for +# cases where the documentation states it explicitly, such as TTree. +# In ROOT 6, this mode can be tested by setting ROOT.Experimental.DisableObjectAutoRegistration(). + # The next line will print 'PyROOT' as the name of the current directory. # That is the global directory created when using ROOT from Python, which is # the ROOT.gROOT object. @@ -25,7 +28,11 @@ # We can check to which directory a newly created histogram is attached. histo_1 = ROOT.TH1F("histo_1", "histo_1", 10, 0, 10) -print("Histogram '{}' is attached to: '{}'.\n".format(histo_1.GetName(), histo_1.GetDirectory().GetName())) +print( + "Histogram '{}' is attached to: '{}'.\n".format( + histo_1.GetName(), "Nothing" if not histo_1.GetDirectory() else histo_1.GetDirectory().GetName() + ) +) # For quick saving and forgetting of objects into ROOT files, it is possible to # open a TFile as a Python context manager. In the context, objects can be @@ -37,8 +44,19 @@ histo_2 = ROOT.TH1F("histo_2", "histo_2", 10, 0, 10) # Inside the context, the current directory is the open file print("Current directory: '{}'.\n".format(ROOT.gDirectory.GetName())) - # And the created histogram is automatically attached to the file - print("Histogram '{}' is attached to: '{}'.\n".format(histo_2.GetName(), histo_2.GetDirectory().GetName())) + # In ROOT 6, the created histogram is automatically attached to the file + print( + "Histogram '{}' is attached to: '{}'.\n".format( + histo_2.GetName(), histo_2.GetDirectory().GetName() if histo_2.GetDirectory() else "Nothing" + ) + ) + # When auto registration is off, this can be done explicitly: + histo_2.SetDirectory(f) + print( + "After setting the directory, histogram '{}' is attached to: '{}'.\n".format( + histo_2.GetName(), histo_2.GetDirectory().GetName() + ) + ) # Before exiting the context, objects can be written to the file f.WriteObject(histo_2, "my_histogram") From a26f549e8fbc781f57833c4256c97c3ca4686cd9 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 10 Mar 2026 11:41:04 +0100 Subject: [PATCH 03/12] [roottest] Whitelist ROOT 7 preview messages in roottest checks. - When the object auto registration is switched to ROOT 7 mode, the corresponding notification needs to be masked from output diffs and/or the RootTestDriver checks. - Ruff formatting --- cmake/modules/RootTestDriver.cmake | 1 + roottest/scripts/custom_diff.py | 1 + 2 files changed, 2 insertions(+) diff --git a/cmake/modules/RootTestDriver.cmake b/cmake/modules/RootTestDriver.cmake index 4579acca8b1e5..8a09431c0c183 100644 --- a/cmake/modules/RootTestDriver.cmake +++ b/cmake/modules/RootTestDriver.cmake @@ -195,6 +195,7 @@ if(CMD) string(STRIP "${_errvar0}" _errvar0) string(REPLACE "\n" ";" _lines "${_errvar0}") list(FILTER _lines EXCLUDE REGEX "^Info in <.+::ACLiC>: creating shared library.+") + list(FILTER _lines EXCLUDE REGEX "^Info in : Object auto registration.*") # ROOT 7 demo mode in ROOT 6 string(REPLACE ";" "\n" _errvar0 "${_lines}") if(_errvar0) message(FATAL_ERROR "Unexpected error output") diff --git a/roottest/scripts/custom_diff.py b/roottest/scripts/custom_diff.py index 10f5e1e9c97bf..112c10228ffb6 100755 --- a/roottest/scripts/custom_diff.py +++ b/roottest/scripts/custom_diff.py @@ -32,6 +32,7 @@ def __init__(self): r"^Info in <\w+::ACLiC>: creating shared library", # Compiled macros r"^In file included from input_line", # Wrapper input line r"^[:space:]*$", # Lines which are empty apart from spaces + r"^Info in : Object auto registration", # ROOT 7 mode ] ] From 6c0cb641d29f2f0df5560379593ec0b724495a21 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Thu, 20 Mar 2025 15:17:54 +0100 Subject: [PATCH 04/12] [core] Add ROOT::Experimental::DisableObjectAutoRegistration() & co. - Add Enable/DisableObjectAutoRegistration() in ROOT::Experimental. - Add a function to test if registration is on or off. - Allow for setting defaults using - The environment variable ROOT_OBJECT_AUTO_REGISTRATION - The rootrc variable Root.ObjectAutoRegistration --- core/base/inc/TROOT.h | 5 ++ core/base/src/TROOT.cxx | 153 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/core/base/inc/TROOT.h b/core/base/inc/TROOT.h index e85e43eb8ff48..4db7be27a742a 100644 --- a/core/base/inc/TROOT.h +++ b/core/base/inc/TROOT.h @@ -97,6 +97,11 @@ namespace ROOT { void DisableImplicitMT(); Bool_t IsImplicitMTEnabled(); UInt_t GetThreadPoolSize(); + namespace Experimental { + void EnableObjectAutoRegistration(); + void DisableObjectAutoRegistration(); + bool ObjectAutoRegistrationEnabled(); + } // namespace Experimental } namespace ROOT::Deprecated::Internal { diff --git a/core/base/src/TROOT.cxx b/core/base/src/TROOT.cxx index 4caca7d16cc0f..85edbf60201a8 100644 --- a/core/base/src/TROOT.cxx +++ b/core/base/src/TROOT.cxx @@ -71,9 +71,11 @@ of a main program creating an interactive version is shown below: #include #include "RConfigure.h" #include "RConfigOptions.h" +#include #include #include #include +#include #include #include #ifdef WIN32 @@ -298,6 +300,75 @@ namespace { static std::vector moduleHeaderInfoBuffer; return moduleHeaderInfoBuffer; } + + enum class AutoReg : unsigned char { + kNotInitialised = 0, + kOn, + kOff, + }; + + //////////////////////////////////////////////////////////////////////////////// + /// \brief Test if various objects (such as TH1-derived classes) should automatically register + /// themselves (ROOT 6 mode) or not (ROOT 7 mode). + /// A default can be set in a .rootrc using e.g. "Root.ObjectAutoRegistration: 1" or setting + /// the environment variable "ROOT_OBJECT_AUTO_REGISTRATION=0". + AutoReg &ObjectAutoRegistrationEnabledImpl() + { + static constexpr auto rcName = "Root.ObjectAutoRegistration"; // Update the docs if this is changed + static constexpr auto envName = "ROOT_OBJECT_AUTO_REGISTRATION"; // Update the docs if this is changed + thread_local static AutoReg tlsState = AutoReg::kNotInitialised; + + static const AutoReg defaultState = []() { + AutoReg autoReg = AutoReg::kOn; // ROOT 6 default + std::stringstream infoMessage; + + if (gEnv) { + const auto desiredValue = gEnv->GetValue(rcName, -1); + if (desiredValue == 0) { + autoReg = AutoReg::kOff; + infoMessage << "disabled in " << gEnv->GetRcName(); + } else if (desiredValue == 1) { + autoReg = AutoReg::kOn; + infoMessage << "enabled in " << gEnv->GetRcName(); + } else if (desiredValue != -1) { + Error("TROOT", "%s should be 0 or 1", rcName); + } + } + + if (const auto env = gSystem->Getenv(envName); env) { + int desiredValue = -1; + try { + desiredValue = std::stoi(env); + } catch (std::invalid_argument &e) { + Error("TROOT", "%s should be 0 or 1", envName); + } + if (desiredValue == 0) { + autoReg = AutoReg::kOff; + infoMessage << (infoMessage.str().empty() ? "" : " and ") << "disabled using the environment variable " + << envName; + } else if (desiredValue == 1) { + autoReg = AutoReg::kOn; + infoMessage << (infoMessage.str().empty() ? "" : " and ") << "enabled using the environment variable " + << envName; + } else { + Error("TROOT", "%s should be 0 or 1", envName); + } + } + + if (!infoMessage.str().empty()) { + Info("TROOT", "Object auto registration %s\n", infoMessage.str().c_str()); + } + + return autoReg; + }(); + + if (tlsState == AutoReg::kNotInitialised) { + assert(defaultState != AutoReg::kNotInitialised); + tlsState = defaultState; + } + + return tlsState; + } } Int_t TROOT::fgDirLevel = 0; @@ -475,7 +546,6 @@ namespace Internal { static Bool_t isImplicitMTEnabled = kFALSE; return isImplicitMTEnabled; } - } // end of Internal sub namespace // back to ROOT namespace @@ -621,6 +691,87 @@ namespace Internal { return 0; #endif } + + /// Namespace for ROOT features in testing. + /// The API might change without notice until moved out of this namespace. + namespace Experimental { + //////////////////////////////////////////////////////////////////////////////// + /// \brief Enable automatic registration of objects for the current thread (ROOT 6 default). + /// + /// In ROOT 6 mode, ROOT will implicitly assign ownership of histograms or TTrees + /// to the current \ref gDirectory, for example to the last TFile that was opened. + /// \code{.cpp} + /// TFile file(...); + /// TTree* tree = new TTree(...); + /// TH1D* histo = new TH1D(...); + /// file.Write(); // Both tree and histogram are in the file now + /// \endcode + /// + /// On the path to ROOT 7, the auto registration of most objects will be phased out, so + /// they are fully owned by the user. To write these to files, the user needs to do + /// one of the following: + /// - Manage the object, and write it explicitly: + /// \code{.cpp} + /// TFile file(...); + /// std::unique_ptr histo{new TH1D(...)}; + /// file.WriteObject(histo.get(), "HistogramName"); + /// // histo remains valid even if the file is closed + /// \endcode + /// - Explicitly transfer ownership to the file using `SetDirectory()`: + /// \code{.cpp} + /// TFile file(...); + /// TH1x* histogram = new TH1x(...); + /// histogram->SetDirectory(&file); + /// \endcode + /// + /// ### Objects covered by this mode + /// + /// | | Honours `DisableObjectAutoRegistration()`? | Could this be disabled previously? | + /// | --------------------- | ------------------------------------------ | ---------------------------------- | + /// | TH1 and derived | Yes | TH1::AddDirectoryStatus() | + /// | TGraph2D | Yes | TH1::AddDirectoryStatus() | + /// | RooPlot | Yes | RooPlot::addDirectoryStatus() | + /// | TEfficiency | Yes | No | + /// | TProfile2D | Yes | TH1::AddDirectoryStatus() | + /// | TEntryList | No, but planned for 6.42 | No | + /// | TEventList | No, but planned for 6.42 | No | + /// | TFunction | No, but work in progress | No | + /// + /// ## Setting defaults + /// + /// A default can be set in a .rootrc using e.g. `Root.ObjectAutoRegistration: 1` or setting + /// the environment variable `ROOT_OBJECT_AUTO_REGISTRATION=0`. Note that this default affects + /// all the threads that get started. + /// When a default is set using one of these methods, ROOT will notify with an Info message. + /// + /// ## Difference to TH1::AddDirectoryStatus() + /// + /// For classes deriving from TH1, both ObjectAutoRegistrationEnabled() and TH1::AddDirectoryStatus() + /// need to be true for auto-registration to take effect. The former should be preferred over the latter, however, + /// because it is thread local and extends to more objects such as TGraph2D, TEfficiency, RooPlot. + void EnableObjectAutoRegistration() + { + ObjectAutoRegistrationEnabledImpl() = AutoReg::kOn; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \brief Disable automatic registration of objects for the current thread (ROOT 7 default). + /// \copydetails ROOT::Experimental::EnableObjectAutoRegistration() + void DisableObjectAutoRegistration() + { + ObjectAutoRegistrationEnabledImpl() = AutoReg::kOff; + } + + //////////////////////////////////////////////////////////////////////////////// + /// Test whether objects in this thread auto-register themselves, e.g. to the current ROOT directory. + /// \copydetails ROOT::Experimental::EnableObjectAutoRegistration() + bool ObjectAutoRegistrationEnabled() + { + const auto state = ObjectAutoRegistrationEnabledImpl(); + assert(state != AutoReg::kNotInitialised); + return state == AutoReg::kOn; + } + } // namespace Experimental } // end of ROOT namespace TROOT *ROOT::Internal::gROOTLocal = ROOT::GetROOT(); From c48e4a3eacb7ec9003bbe832ad179a6bfd1293f0 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Thu, 26 Mar 2026 16:54:02 +0100 Subject: [PATCH 05/12] [core] Add a test for thread-local object auto registration state. --- core/base/test/TROOTTests.cxx | 42 ++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/core/base/test/TROOTTests.cxx b/core/base/test/TROOTTests.cxx index 0e3c097ef20c0..a1ccc0ad87fb9 100644 --- a/core/base/test/TROOTTests.cxx +++ b/core/base/test/TROOTTests.cxx @@ -5,7 +5,7 @@ #include #include #include -#include +#include TEST(TROOT, Version) { @@ -51,3 +51,43 @@ TEST(TROOT, GetIncludeDir) EXPECT_EQ(includeDir, includeDirRef); } + +TEST(TROOT, AutoRegistrationTLS) +{ + using namespace ROOT::Experimental; + using namespace std::chrono_literals; + + const auto initialState = ObjectAutoRegistrationEnabled(); + auto enabler = [&]() { + const auto innerState = ObjectAutoRegistrationEnabled(); + EXPECT_EQ(innerState, initialState); + EnableObjectAutoRegistration(); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), true); + std::this_thread::sleep_for(10ms); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), true); + }; + auto disabler = [&]() { + const auto innerState = ObjectAutoRegistrationEnabled(); + EXPECT_EQ(innerState, initialState); + DisableObjectAutoRegistration(); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), false); + std::this_thread::sleep_for(10ms); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), false); + }; + + std::thread t1{enabler}; + std::thread t2{disabler}; + + DisableObjectAutoRegistration(); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), false); + + std::thread t3{enabler}; + std::thread t4{disabler}; + + EnableObjectAutoRegistration(); + EXPECT_EQ(ObjectAutoRegistrationEnabled(), true); + + for (auto thread : {&t1, &t2, &t3, &t4}) { + thread->join(); + } +} From 8d4d34a89ec443b7e2de1293402750537295f519 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Fri, 20 Mar 2026 11:49:20 +0100 Subject: [PATCH 06/12] [hist] Propagate object registration settings to TH1/TEfficiency/TGraph2D. In locations where TH1::AddDirectoryStatus is queried, also query ObjectAutoRegistrationEnabled(). This extends the ROOT 7 testing mode to TH1 and derived classes, TGraph2D and TEfficiency. --- hist/hist/src/TEfficiency.cxx | 2 +- hist/hist/src/TGraph2D.cxx | 7 +++---- hist/hist/src/TH1.cxx | 30 ++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/hist/hist/src/TEfficiency.cxx b/hist/hist/src/TEfficiency.cxx index 7a02d1fed6413..18ceb70536091 100644 --- a/hist/hist/src/TEfficiency.cxx +++ b/hist/hist/src/TEfficiency.cxx @@ -1515,7 +1515,7 @@ void TEfficiency::Build(const char* name,const char* title) SetTitle(title); SetStatisticOption(kDefStatOpt); - if (TH1::AddDirectoryStatus()) + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && TH1::AddDirectoryStatus()) SetDirectory(gDirectory); SetBit(kPosteriorMode,false); diff --git a/hist/hist/src/TGraph2D.cxx b/hist/hist/src/TGraph2D.cxx index 7c1feec03bb47..6ab1ab073c1df 100644 --- a/hist/hist/src/TGraph2D.cxx +++ b/hist/hist/src/TGraph2D.cxx @@ -531,7 +531,7 @@ TGraph2D::TGraph2D(const TGraph2D &g) (*this) = g; // append TGraph2D to gdirectory - if (TH1::AddDirectoryStatus()) { + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && TH1::AddDirectoryStatus()) { fDirectory = gDirectory; if (fDirectory) { // append without replacing existing objects @@ -627,7 +627,7 @@ void TGraph2D::Build(Int_t n) fPainter = nullptr; fUserHisto = kFALSE; - if (TH1::AddDirectoryStatus()) { + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && TH1::AddDirectoryStatus()) { fDirectory = gDirectory; if (fDirectory) { fDirectory->Append(this, kTRUE); @@ -715,8 +715,7 @@ void TGraph2D::Clear(Option_t * /*option = "" */) void TGraph2D::DirectoryAutoAdd(TDirectory *dir) { - Bool_t addStatus = TH1::AddDirectoryStatus(); - if (addStatus) { + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && TH1::AddDirectoryStatus()) { SetDirectory(dir); if (dir) { ResetBit(kCanDelete); diff --git a/hist/hist/src/TH1.cxx b/hist/hist/src/TH1.cxx index 4a067e9d18e00..b51d1037bb651 100644 --- a/hist/hist/src/TH1.cxx +++ b/hist/hist/src/TH1.cxx @@ -171,7 +171,7 @@ Histograms may also be created by: - making a projection from a 2-D or 3-D histogram, see below - reading a histogram from a file - When a histogram is created, a reference to it is automatically added + When a histogram is created in ROOT 6, a reference to it is automatically added to the list of in-memory objects for the current file or directory. Then the pointer to this histogram in the current directory can be found by its name, doing: @@ -181,14 +181,18 @@ Histograms may also be created by: This default behaviour can be changed by: ~~~ {.cpp} - h->SetDirectory(nullptr); // for the current histogram h - TH1::AddDirectory(kFALSE); // sets a global switch disabling the referencing + h->SetDirectory(nullptr); // for one histogram h + TH1::AddDirectory(kFALSE); // deprecated, see below ~~~ When the histogram is deleted, the reference to it is removed from the list of objects in memory. When a file is closed, all histograms in memory associated with this file are automatically deleted. +In ROOT 7, this auto registration will be phased out. This mode can be tested in ROOT 6 using +ROOT::Experimental::DisableObjectAutoRegistration(). To opt in to the ROOT-6-style registration +in ROOT 7, use ROOT::Experimental::EnableObjectAutoRegistration(). + \anchor labelling-axis ### Labelling axes @@ -752,7 +756,10 @@ TH1::TH1(const char *name,const char *title,Int_t nbins,const Double_t *xbins) } //////////////////////////////////////////////////////////////////////////////// -/// Static function: cannot be inlined on Windows/NT. +/// Check whether TH1-derived classes should register themselves to the current gDirectory. +/// \note Even if this returns true, the state of +/// ROOT::Experimental::ObjectAutoRegistrationEnabled() might prevent the registration of +/// histograms, since it has higher precedence. Bool_t TH1::AddDirectoryStatus() { @@ -800,7 +807,7 @@ void TH1::Build() UseCurrentStyle(); - if (TH1::AddDirectoryStatus()) { + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && TH1::AddDirectoryStatus()) { fDirectory = gDirectory; if (fDirectory) { fFunctions->UseRWLock(); @@ -1285,16 +1292,19 @@ Bool_t TH1::Add(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2) } //////////////////////////////////////////////////////////////////////////////// -/// Sets the flag controlling the automatic add of histograms in memory +/// Sets the flag controlling the automatic add of histograms in memory. /// /// By default (fAddDirectory = kTRUE), histograms are automatically added -/// to the list of objects in memory. +/// to the current directory (gDirectory). /// Note that one histogram can be removed from its support directory /// by calling h->SetDirectory(nullptr) or h->SetDirectory(dir) to add it /// to the list of objects in the directory dir. /// -/// NOTE that this is a static function. To call it, use; -/// TH1::AddDirectory +/// This is a static function. To call it, use `TH1::AddDirectory` +/// +/// \deprecated Use ROOT::Experimental::ObjectAutoRegistrationEnabled(). It can be +/// set using an entry in rootrc or an environment variable, is initialised in a +/// thread-safe manner and covers more cases. void TH1::AddDirectory(Bool_t add) { @@ -2760,7 +2770,7 @@ void TH1::Copy(TObject &obj) const // will be added to gDirectory independently of the fDirectory stored. // and if the AddDirectoryStatus() is false it will not be added to // any directory (fDirectory = nullptr) - if (fgAddDirectory && gDirectory) { + if (ROOT::Experimental::ObjectAutoRegistrationEnabled() && AddDirectoryStatus() && gDirectory) { gDirectory->Append(&obj); ((TH1&)obj).fFunctions->UseRWLock(); ((TH1&)obj).fDirectory = gDirectory; From 2a0441d4df5aacb39a639494f59c09ac26a2457c Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Fri, 5 Sep 2025 17:43:00 +0200 Subject: [PATCH 07/12] [RF] Propagate implicit object registration settings to RooPlot. --- roofit/roofitcore/src/RooPlot.cxx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/roofit/roofitcore/src/RooPlot.cxx b/roofit/roofitcore/src/RooPlot.cxx index 84db698f46985..9d0cf19e84f1a 100644 --- a/roofit/roofitcore/src/RooPlot.cxx +++ b/roofit/roofitcore/src/RooPlot.cxx @@ -56,6 +56,7 @@ object onto a one-dimensional plot. #include "TH1D.h" #include "TBrowser.h" #include "TVirtualPad.h" +#include "TROOT.h" #include "TAttLine.h" #include "TAttFill.h" @@ -83,9 +84,9 @@ bool RooPlot::setAddDirectoryStatus(bool flag) { bool ret = flag ; _addDirStatus RooPlot::RooPlot() { - if (gDirectory && addDirectoryStatus()) { - SetDirectory(gDirectory); - } + if (gDirectory && ROOT::Experimental::ObjectAutoRegistrationEnabled() && addDirectoryStatus()) { + SetDirectory(gDirectory); + } } @@ -242,8 +243,8 @@ void RooPlot::initialize() { SetName(histName()) ; - if (gDirectory && addDirectoryStatus()) { - SetDirectory(gDirectory); + if (gDirectory && ROOT::Experimental::ObjectAutoRegistrationEnabled() && addDirectoryStatus()) { + SetDirectory(gDirectory); } // We do not have useful stats of our own From 444af54d57257139540b663b5128f3f5a1151c73 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Fri, 13 Feb 2026 15:45:38 +0100 Subject: [PATCH 08/12] [hist] Add a test for explicit registration of TH1 to TDirectory. --- hist/hist/test/test_TH1.cxx | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/hist/hist/test/test_TH1.cxx b/hist/hist/test/test_TH1.cxx index b1fba0682f706..7a441c51fbb32 100644 --- a/hist/hist/test/test_TH1.cxx +++ b/hist/hist/test/test_TH1.cxx @@ -170,8 +170,54 @@ TEST(TH1, Normalize) EXPECT_FLOAT_EQ(v2.GetMaximum(), 7.9999990); } +TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOff) +{ + const bool oldSetting = ROOT::Experimental::ObjectAutoRegistrationEnabled(); + ROOT::Experimental::DisableObjectAutoRegistration(); + + TH1D histo1("histo1", "Test Histogram", 10, 0, 10); + auto histo2 = std::make_unique("histo2", "Test Histogram", 10, 0, 10); + TH1D *histo3 = new TH1D("histo3", "Test Histogram", 10, 0, 10); + + { + TDirectory dir("dir", "Test Directory"); + histo3->SetDirectory(&dir); + + dir.cd(); + + TH1D histo4("histo4", "Test Histogram", 10, 0, 10); + auto histo5 = std::make_unique("histo5", "Test Histogram", 10, 0, 10); + + EXPECT_EQ(dir.GetList()->GetSize(), 1); + EXPECT_EQ(dir.Get("histo3"), histo3); + + EXPECT_EQ(histo1.GetDirectory(), nullptr); + EXPECT_EQ(histo2->GetDirectory(), nullptr); + EXPECT_EQ(histo3->GetDirectory(), &dir); + EXPECT_EQ(histo4.GetDirectory(), nullptr); + EXPECT_EQ(histo5->GetDirectory(), nullptr); + + histo5.reset(); + + EXPECT_EQ(dir.GetList()->GetSize(), 1); + EXPECT_EQ(dir.Get("histo3"), histo3); + } + + EXPECT_STREQ(histo1.GetName(), "histo1"); + EXPECT_STREQ(histo2->GetName(), "histo2"); + + EXPECT_EQ(histo1.GetDirectory(), nullptr); + EXPECT_EQ(histo2->GetDirectory(), nullptr); + + if (oldSetting) + ROOT::Experimental::EnableObjectAutoRegistration(); +} + TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOn) { + const bool ownershipDisabledBefore = !ROOT::Experimental::ObjectAutoRegistrationEnabled(); + ROOT::Experimental::EnableObjectAutoRegistration(); + TH1D histo1("histo1", "Test Histogram", 10, 0, 10); auto histo2 = std::make_unique("histo2", "Test Histogram", 10, 0, 10); TH1D *histo3 = new TH1D("histo3", "Test Histogram", 10, 0, 10); @@ -213,6 +259,9 @@ TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOn) EXPECT_EQ(histo1.GetDirectory(), gROOT); EXPECT_EQ(histo2->GetDirectory(), gROOT); + + if (ownershipDisabledBefore) + ROOT::Experimental::DisableObjectAutoRegistration(); } TEST(TAxis, BinComputation_FPAccuracy) From 15a1f44ce29ffe808a2baadc376f240ad256cf4c Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Mon, 27 Oct 2025 17:35:12 +0100 Subject: [PATCH 09/12] [RFile] Prepare an RFile test for both states of object auto registration. --- io/io/test/rfile.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/io/io/test/rfile.cxx b/io/io/test/rfile.cxx index 16f38fdbab3a8..e08d7b2c14751 100644 --- a/io/io/test/rfile.cxx +++ b/io/io/test/rfile.cxx @@ -132,9 +132,10 @@ TEST(RFile, CheckNoAutoRegistrationWrite) EXPECT_EQ(gDirectory, gROOT); auto hist = std::make_unique("hist", "", 100, -10, 10); file->Put("hist", *hist); - EXPECT_EQ(hist->GetDirectory(), gROOT); + TDirectory const *expectedDir = ROOT::Experimental::ObjectAutoRegistrationEnabled() ? gROOT : nullptr; + EXPECT_EQ(hist->GetDirectory(), expectedDir); file->Close(); - EXPECT_EQ(hist->GetDirectory(), gROOT); + EXPECT_EQ(hist->GetDirectory(), expectedDir); hist.reset(); // no double free should happen when ROOT exits } From af7263ffde16d835d4ea7a6a8953b4ff30ac9b11 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Mon, 20 Apr 2026 11:58:30 +0200 Subject: [PATCH 10/12] Add release notes for the auto registration off mode. --- README/ReleaseNotes/v640/index.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README/ReleaseNotes/v640/index.md b/README/ReleaseNotes/v640/index.md index 7519cbbe32350..a5073993058a4 100644 --- a/README/ReleaseNotes/v640/index.md +++ b/README/ReleaseNotes/v640/index.md @@ -501,6 +501,32 @@ sum(1 for x in collection if x == obj) # value comparison (if defined for the ## Experimental features +### Opting out of object auto registration +In preparation for ROOT 7, ROOT 6.40 introduces an experimental mode for opting out of the auto-registration of objects. +In ROOT 7, this will be the default, and an opt-in will be required to make these objects auto-register themselves. +The table below shows which objects currently honour this mode, and which objects are planned to be added on the path to ROOT 7. + +The planned ROOT 7 behaviour can be enabled in one of three ways: +1. `ROOT::Experimental::DisableObjectAutoRegistration()`: This disables auto registration *for the current thread*. +2. Setting the environment variable `ROOT_OBJECT_AUTO_REGISTRATION=0`: This sets the default for every thread that starts. +3. In `.rootrc`, set the entry `Root.ObjectAutoRegistration: 0`: This sets the default for every thread that starts. + +**Note that method 1 affects only the current thread**, whereas methods 2 and 3 set the default for every thread that is started in this ROOT session. +Using `ROOT::Experimental::EnableObjectAutoRegistration()`, the auto-registration can be enabled for a single thread without affecting the rest of the session. + +Consult the doxygen documentation of these functions in the [ROOT::Experimental namespace](https://root.cern.ch/doc/v640/namespaceROOT_1_1Experimental.html) for details. + +| | Honours `DisableObjectAutoRegistration()`? | Could this be disabled previously? | +| --------------------- | ------------------------------------------ | ---------------------------------- | +| TH1 and derived | Yes | TH1::AddDirectoryStatus() | +| TGraph2D | Yes | TH1::AddDirectoryStatus() | +| RooPlot | Yes | RooPlot::addDirectoryStatus() | +| TEfficiency | Yes | No | +| TProfile2D | Yes | TH1::AddDirectoryStatus() | +| TEntryList | No, but planned for 6.42 | No | +| TEventList | No, but planned for 6.42 | No | +| TFunction | No, but work in progress | No | + ## Versions of built-in packages The version of the following packages has been updated: From 1c1f71da86f261339090920d932bc83116d0f222 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 21 Apr 2026 10:31:26 +0200 Subject: [PATCH 11/12] [CI] Handle all linux environment settings in one step. --- .github/workflows/root-ci.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/root-ci.yml b/.github/workflows/root-ci.yml index fda8d764cae10..5a7b680289567 100644 --- a/.github/workflows/root-ci.yml +++ b/.github/workflows/root-ci.yml @@ -468,17 +468,13 @@ jobs: POST_INSTALL_DIR: /github/home/ROOT-CI/PostInstall steps: - - name: Configure ccache + - name: Set up environment run: | ccache -o max_size=${{ matrix.is_special && '5G' || '1.5G' }} - ccache -p || true ccache -s || true - - - name: Set up Python Virtual Env - # if the `if` expr is false, `if` still has exit code 0. - # if the `if` block is entered, the block's exit code becomes the exit - # code of the `if`. - run: 'if [ -d /py-venv/ROOT-CI/bin/ ]; then . /py-venv/ROOT-CI/bin/activate && echo PATH=$PATH >> $GITHUB_ENV; fi' + if [ -d /py-venv/ROOT-CI/bin/ ]; then + . /py-venv/ROOT-CI/bin/activate && echo PATH=$PATH >> $GITHUB_ENV; + fi - name: Checkout uses: actions/checkout@v6 @@ -499,7 +495,8 @@ jobs: - name: Print debug info run: 'printf "%s@%s\\n" "$(whoami)" "$(hostname)"; - ls -la + ls -la; + echo "Path is: $PATH" ' - uses: root-project/gcc-problem-matcher-improved@main From b8bdaba235229ce28e4523e2018bd5293fee728c Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 21 Apr 2026 11:31:04 +0200 Subject: [PATCH 12/12] [CI] Disable object auto registration in the clang ninja job. --- .github/workflows/root-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/root-ci.yml b/.github/workflows/root-ci.yml index 5a7b680289567..6d8e2644cb988 100644 --- a/.github/workflows/root-ci.yml +++ b/.github/workflows/root-ci.yml @@ -413,7 +413,7 @@ jobs: - image: alma10 platform_config: alma10-clang_ninja is_special: true - property: "clang Ninja builtin" + property: "clang Ninja builtins auto-registration off" overrides: ["CMAKE_CXX_STANDARD=20"] # Fedora Rawhide with Python debug build - image: rawhide @@ -475,6 +475,10 @@ jobs: if [ -d /py-venv/ROOT-CI/bin/ ]; then . /py-venv/ROOT-CI/bin/activate && echo PATH=$PATH >> $GITHUB_ENV; fi + if ${{ contains(matrix.property, 'auto-registration off') }}; then + echo "Disabling ROOT's object auto registration for this job" + echo ROOT_OBJECT_AUTO_REGISTRATION=0 >> $GITHUB_ENV; + fi - name: Checkout uses: actions/checkout@v6