Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/root-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,11 @@
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') && !matrix.platform == 'mac15' && !matrix.platform == 'mac26'}}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
OVERRIDES: ${{ join( matrix.overrides, ' ') }}
ROOT_OBJECT_AUTO_REGISTRATION: 0
run: |

Check failure on line 161 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
[ -d "${VIRTUAL_ENV_DIR}" ] && source ${VIRTUAL_ENV_DIR}/bin/activate
echo "Python is now $(which python3) $(python3 --version)"
src/.github/workflows/root-ci-config/build_root.py \
src/.github/workflows/root-ci-config/build_root.py \
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.

Suggested change
src/.github/workflows/root-ci-config/build_root.py \
src/.github/workflows/root-ci-config/build_root.py \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This commit will be removed before merging. It is here to demonstrate that all tests work today also when object auto registration is off.

--buildtype RelWithDebInfo \
--incremental $INCREMENTAL \
--base_ref ${{ github.base_ref }} \
Expand Down Expand Up @@ -286,8 +287,9 @@
env:
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
ROOT_OBJECT_AUTO_REGISTRATION: 0
shell: cmd
run: "C:\\setenv.bat ${{ matrix.target_arch }} &&

Check failure on line 292 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
python .github/workflows/root-ci-config/build_root.py
--buildtype ${{ matrix.config }}
--platform windows10
Expand Down Expand Up @@ -447,7 +449,7 @@
- self-hosted
- linux
- ${{ matrix.architecture == null && 'x64' || matrix.architecture }}
- ${{ matrix.extra-runs-on == null && 'cpu' || matrix.extra-runs-on }}

Check failure on line 452 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

property "extra-runs-on" is not defined in object type {architecture: string; image: string; is_special: bool; overrides: array<string>; platform_config: string; property: string}

Check failure on line 452 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

property "extra-runs-on" is not defined in object type {architecture: string; image: string; is_special: bool; overrides: array<string>; platform_config: string; property: string}

name: |
${{ matrix.image }} ${{ matrix.property }}
Expand Down Expand Up @@ -512,7 +514,8 @@
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
OVERRIDES: ${{ join( matrix.overrides, ' ') }}
ROOT_OBJECT_AUTO_REGISTRATION: 0
run: ".github/workflows/root-ci-config/build_root.py

Check failure on line 518 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
--buildtype RelWithDebInfo
--platform ${{ matrix.image }}
--platform_config ${{ matrix.platform_config }}
Expand Down
26 changes: 26 additions & 0 deletions README/ReleaseNotes/v640/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import unittest

import ROOT

from ROOT import TFile


Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -95,5 +95,5 @@ def test_detachhisto(self):
os.remove(filename)


if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions cmake/modules/RootTestDriver.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 <TROOT>: Object auto registration.*") # ROOT 7 demo mode in ROOT 6
string(REPLACE ";" "\n" _errvar0 "${_lines}")
if(_errvar0)
message(FATAL_ERROR "Unexpected error output")
Expand Down
5 changes: 5 additions & 0 deletions core/base/inc/TROOT.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion core/base/src/TDirectory.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */)
Expand Down
153 changes: 152 additions & 1 deletion core/base/src/TROOT.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ of a main program creating an interactive version is shown below:
#include <ROOT/RVersion.hxx>
#include "RConfigure.h"
#include "RConfigOptions.h"
#include <atomic>
#include <filesystem>
#include <string>
#include <map>
#include <sstream>
#include <set>
#include <cstdlib>
#ifdef WIN32
Expand Down Expand Up @@ -298,6 +300,75 @@ namespace {
static std::vector<ModuleHeaderInfo_t> 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;
Comment thread
pcanal marked this conversation as resolved.
}
}

Int_t TROOT::fgDirLevel = 0;
Expand Down Expand Up @@ -475,7 +546,6 @@ namespace Internal {
static Bool_t isImplicitMTEnabled = kFALSE;
return isImplicitMTEnabled;
}

} // end of Internal sub namespace
// back to ROOT namespace

Expand Down Expand Up @@ -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).
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.

Sorry to be picky here: does it make sense to have a table of the classes the instances of which are affected by the switch in this documentation?
That table can be also updated when new classes are added, such asTE*List.

Copy link
Copy Markdown
Member Author

@hageboeck hageboeck Apr 20, 2026

Choose a reason for hiding this comment

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

Yes, the table appeared now after the latest rebase. It's, however, not in the function you marked (which cannot be called outside of TROOT.cxx), it's in the function ROOT::Experimental::DisableObjectAutoRegistration() and its siblings.

Once merged, the docs should look roughly like this:
image

///
/// 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<TH1D> 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();
Expand Down
42 changes: 41 additions & 1 deletion core/base/test/TROOTTests.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <filesystem>
#include <sstream>
#include <string>
#include <iostream>
#include <thread>

TEST(TROOT, Version)
{
Expand Down Expand Up @@ -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();
}
}
2 changes: 1 addition & 1 deletion hist/hist/src/TEfficiency.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading