Skip to content

Commit 711a69e

Browse files
sawenzelclaude
andcommitted
Set the static TLS reserve for Geant4 inside the sim executables
This fixes o2-sim on the GRID, where Geant4 cannot be loaded since it uses the initial-exec TLS model, and adds a unit test. - A Geant4 loaded by dlopen needs a glibc static TLS reserve, which the Geant4 environment sets in GLIBC_TUNABLES. - In GRID jobs the variable arrives truncated as glibc.rtld.optional_static_tls, without its value, and is ignored. - o2-sim now sets the reserve at start, so the workers it launches have it. - The executables that load Geant4 check the reserve first in main and, if it is missing or too small, restart themselves once with it set, since glibc reads tunables only at program start. - Other tunables are kept, and a larger reserve set by the user is left as is. https://its.cern.ch/jira/browse/O2-7305 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent fb508a3 commit 711a69e

7 files changed

Lines changed: 188 additions & 0 deletions

File tree

‎Detectors/gconfig/CMakeLists.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ o2_target_root_dictionary(SimSetup
7474
include/SimSetup/MCReplayParam.h
7575
LINKDEF src/GConfLinkDef.h)
7676

77+
o2_add_test(StaticTLSReserve
78+
SOURCES test/testStaticTLSReserve.cxx
79+
COMPONENT_NAME SimSetup
80+
PUBLIC_LINK_LIBRARIES O2::SimSetup
81+
LABELS simsetup)
82+
7783
o2_add_test_root_macro(UserDecay.C
7884
PUBLIC_LINK_LIBRARIES O2::SimSetup
7985
LABELS simsetup)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#ifndef O2_SIMSETUP_STATICTLSRESERVE_H
13+
#define O2_SIMSETUP_STATICTLSRESERVE_H
14+
15+
#include <cerrno>
16+
#include <charconv>
17+
#include <climits>
18+
#include <cstdio>
19+
#include <cstdlib>
20+
#include <cstring>
21+
#include <string>
22+
#include <string_view>
23+
#include <unistd.h>
24+
25+
namespace o2
26+
{
27+
28+
/// Static TLS reserve in bytes for a Geant4 built with the initial-exec TLS model and loaded by dlopen
29+
constexpr unsigned long Geant4StaticTLSReserve = 32768;
30+
31+
/// The GLIBC_TUNABLES value with a static TLS reserve of at least `reserve` bytes, or an empty string if
32+
/// `tunables` already has one. As in glibc, the last well-formed reserve counts. Other tunables are kept.
33+
inline std::string withStaticTLSReserve(const char* tunables, unsigned long reserve = Geant4StaticTLSReserve)
34+
{
35+
constexpr std::string_view name = "glibc.rtld.optional_static_tls";
36+
std::string kept;
37+
bool haveReserve = false;
38+
unsigned long current = 0;
39+
std::string_view rest = tunables ? tunables : "";
40+
while (!rest.empty()) {
41+
const auto colon = rest.find(':');
42+
const std::string_view entry = rest.substr(0, colon);
43+
rest = colon == std::string_view::npos ? std::string_view{} : rest.substr(colon + 1);
44+
const auto equal = entry.find('=');
45+
if (entry.substr(0, equal) == name) {
46+
const std::string_view value = equal == std::string_view::npos ? std::string_view{} : entry.substr(equal + 1);
47+
unsigned long bytes = 0;
48+
const auto [end, ec] = std::from_chars(value.data(), value.data() + value.size(), bytes);
49+
if (!value.empty() && ec == std::errc() && end == value.data() + value.size()) {
50+
haveReserve = true;
51+
current = bytes;
52+
}
53+
continue; // our own entry replaces all of them
54+
}
55+
if (!entry.empty()) {
56+
kept += (kept.empty() ? "" : ":") + std::string(entry);
57+
}
58+
}
59+
if (haveReserve && current >= reserve) {
60+
return {};
61+
}
62+
return std::string(name) + "=" + std::to_string(reserve) + (kept.empty() ? "" : ":" + kept);
63+
}
64+
65+
/// Sets the static TLS reserve in the environment, for the programs started from here. Returns true if it changed.
66+
inline bool setStaticTLSReserve(unsigned long reserve = Geant4StaticTLSReserve)
67+
{
68+
const auto tunables = withStaticTLSReserve(getenv("GLIBC_TUNABLES"), reserve);
69+
if (tunables.empty()) {
70+
return false;
71+
}
72+
setenv("GLIBC_TUNABLES", tunables.c_str(), 1);
73+
return true;
74+
}
75+
76+
/// Restarts the program once with the static TLS reserve if it lacks one. glibc reads GLIBC_TUNABLES only at
77+
/// program start, so the reserve cannot be set later. Call this first in main, before any thread is started.
78+
inline void ensureStaticTLSReserve([[maybe_unused]] char* argv[],
79+
[[maybe_unused]] unsigned long reserve = Geant4StaticTLSReserve)
80+
{
81+
#ifdef __linux__
82+
constexpr const char* guard = "O2_STATIC_TLS_REEXEC";
83+
if (getenv(guard)) {
84+
unsetenv(guard); // restarted already; do not loop if glibc ignores the tunable
85+
return;
86+
}
87+
if (withStaticTLSReserve(getenv("GLIBC_TUNABLES"), reserve).empty()) {
88+
return;
89+
}
90+
// the real path keeps the process name; /proc/self/exe would rename it to "exe"
91+
char self[PATH_MAX];
92+
const auto length = readlink("/proc/self/exe", self, sizeof(self) - 1);
93+
if (length <= 0) {
94+
return;
95+
}
96+
self[length] = '\0';
97+
const char* base = strrchr(self, '/');
98+
if (strncmp(base ? base + 1 : self, "ld-", 3) == 0) {
99+
return; // started through the dynamic loader, which a restart would not reproduce
100+
}
101+
setStaticTLSReserve(reserve);
102+
setenv(guard, "1", 1);
103+
fprintf(stderr, "Restarting %s with GLIBC_TUNABLES=%s for the static TLS of Geant4\n", self, getenv("GLIBC_TUNABLES"));
104+
execv(self, argv);
105+
const int error = errno;
106+
unsetenv(guard);
107+
fprintf(stderr, "Restart of %s failed: %s\n", self, strerror(error));
108+
#endif
109+
}
110+
111+
} // namespace o2
112+
113+
#endif
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#define BOOST_TEST_MODULE Test SimSetup StaticTLSReserve
13+
#define BOOST_TEST_MAIN
14+
#define BOOST_TEST_DYN_LINK
15+
#include <boost/test/unit_test.hpp>
16+
#include "SimSetup/StaticTLSReserve.h"
17+
18+
using o2::withStaticTLSReserve;
19+
20+
BOOST_AUTO_TEST_CASE(missing_or_broken_reserve_is_added)
21+
{
22+
const std::string reserve = "glibc.rtld.optional_static_tls=32768";
23+
BOOST_CHECK_EQUAL(withStaticTLSReserve(nullptr, 32768), reserve);
24+
BOOST_CHECK_EQUAL(withStaticTLSReserve("", 32768), reserve);
25+
// the value as it arrives in GRID jobs
26+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls", 32768), reserve);
27+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=", 32768), reserve);
28+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=512", 32768), reserve);
29+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=-1", 32768), reserve);
30+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=32768x", 32768), reserve);
31+
}
32+
33+
BOOST_AUTO_TEST_CASE(sufficient_reserve_is_kept)
34+
{
35+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=32768", 32768), "");
36+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=1048576", 32768), "");
37+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.malloc.arena_max=4:glibc.rtld.optional_static_tls=65536", 32768), "");
38+
}
39+
40+
BOOST_AUTO_TEST_CASE(other_tunables_are_kept)
41+
{
42+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.malloc.arena_max=4", 32768),
43+
"glibc.rtld.optional_static_tls=32768:glibc.malloc.arena_max=4");
44+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls:glibc.malloc.arena_max=4", 32768),
45+
"glibc.rtld.optional_static_tls=32768:glibc.malloc.arena_max=4");
46+
// a longer name is a different tunable
47+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tlsx=1", 32768),
48+
"glibc.rtld.optional_static_tls=32768:glibc.rtld.optional_static_tlsx=1");
49+
}
50+
51+
BOOST_AUTO_TEST_CASE(last_reserve_counts)
52+
{
53+
// glibc applies the entries in order, so a later small reserve wins over an earlier large one
54+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=65536:glibc.rtld.optional_static_tls=512", 32768),
55+
"glibc.rtld.optional_static_tls=32768");
56+
BOOST_CHECK_EQUAL(withStaticTLSReserve("glibc.rtld.optional_static_tls=512:glibc.rtld.optional_static_tls=65536", 32768), "");
57+
}

‎run/O2SimDeviceRunner.cxx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "rapidjson/document.h"
4040
#include "rapidjson/stringbuffer.h"
4141
#include "rapidjson/filereadstream.h"
42+
#include <SimSetup/StaticTLSReserve.h>
4243
namespace bpo = boost::program_options;
4344

4445
std::vector<int> gChildProcesses; // global vector of child pids
@@ -267,6 +268,8 @@ bool waitForControlInput(int workerID)
267268

268269
int main(int argc, char* argv[])
269270
{
271+
// Geant4 is loaded by dlopen and may need a static TLS reserve, which glibc only takes at program start
272+
o2::ensureStaticTLSReserve(argv);
270273
struct sigaction act;
271274
memset(&act, 0, sizeof act);
272275
sigemptyset(&act.sa_mask);

‎run/o2sim.cxx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
#include <SimConfig/SimConfig.h>
1414
#include <TStopwatch.h>
1515
#include <fairlogger/Logger.h>
16+
#include <SimSetup/StaticTLSReserve.h>
1617

1718
int main(int argc, char* argv[])
1819
{
20+
// Geant4 is loaded by dlopen and may need a static TLS reserve, which glibc only takes at program start
21+
o2::ensureStaticTLSReserve(argv);
1922
TStopwatch timer;
2023
timer.Start();
2124
auto& conf = o2::conf::SimConfig::Instance();

‎run/o2sim_evalmat.cxx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
#include <SimConfig/MatMapParams.h>
1515
#include <TStopwatch.h>
1616
#include <fairlogger/Logger.h>
17+
#include <SimSetup/StaticTLSReserve.h>
1718

1819
int main(int argc, char* argv[])
1920
{
21+
// Geant4 is loaded by dlopen and may need a static TLS reserve, which glibc only takes at program start
22+
o2::ensureStaticTLSReserve(argv);
2023
TStopwatch timer;
2124
timer.Start();
2225

‎run/o2sim_parallel.cxx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "SimPublishChannelHelper.h"
4949
#include <CommonUtils/FileSystemUtils.h>
5050
#include <CCDB/BasicCCDBManager.h>
51+
#include <SimSetup/StaticTLSReserve.h>
5152

5253
std::string getServerLogName()
5354
{
@@ -435,6 +436,8 @@ std::vector<char*> checkArgs(int argc, char* argv[])
435436
// for parallel simulation
436437
int main(int argc, char* argv[])
437438
{
439+
// for the workers, which load Geant4 by dlopen; glibc takes the static TLS reserve only at program start
440+
o2::setStaticTLSReserve();
438441
LOG(info) << "This is o2-sim version " << o2::fullVersion() << " (" << o2::gitRevision() << ")";
439442
LOG(info) << o2::getBuildInfo();
440443

0 commit comments

Comments
 (0)