mirror of
https://github.com/FairRootGroup/FairMQ.git
synced 2026-06-15 08:17:05 +00:00
test: pre-fill libstdc++ ctype caches before threads exist
- std::ctype<char> caches narrow()/widen() results per character in plain char arrays of the global classic-locale facet, written without synchronization from header-inlined code (locale_facets.h); two threads exercising an uncached character concurrently (e.g. compiling a std::regex in Channel::Validate) constitute a true data race that ThreadSanitizer rightfully reports - the stores are real and unsynchronized, so a tsan-instrumented libstdc++ cannot help here; instead fill the caches before any thread is spawned, which turns every later access into a pure read - warm the lazily-installed num_put/num_get caches used by stream insertion/extraction as well, via a small format/parse round-trip - wire the warm-up into the gtest runner main() and, via a static initializer, into the test device runner
This commit is contained in:
committed by
Dennis Klein
parent
fc69b5e7ae
commit
2bd9a072a9
@@ -32,6 +32,7 @@ endif()
|
|||||||
add_testhelper(runTestDevice
|
add_testhelper(runTestDevice
|
||||||
SOURCES
|
SOURCES
|
||||||
helper/runTestDevice.cxx
|
helper/runTestDevice.cxx
|
||||||
|
helper/LocaleWarmup.h
|
||||||
helper/devices/TestPairLeft.h
|
helper/devices/TestPairLeft.h
|
||||||
helper/devices/TestPairRight.h
|
helper/devices/TestPairRight.h
|
||||||
helper/devices/TestPollIn.h
|
helper/devices/TestPollIn.h
|
||||||
|
|||||||
63
test/helper/LocaleWarmup.h
Normal file
63
test/helper/LocaleWarmup.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* Copyright (C) 2026 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
|
||||||
|
* *
|
||||||
|
* This software is distributed under the terms of the *
|
||||||
|
* GNU Lesser General Public Licence (LGPL) version 3, *
|
||||||
|
* copied verbatim in the file "LICENSE" *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#ifndef FAIR_MQ_TEST_LOCALEWARMUP_H
|
||||||
|
#define FAIR_MQ_TEST_LOCALEWARMUP_H
|
||||||
|
|
||||||
|
#include <locale>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace fair::mq::test {
|
||||||
|
|
||||||
|
/// libstdc++'s std::ctype<char> caches narrow()/widen() results per character
|
||||||
|
/// in plain char arrays of the global classic-locale facet -- by design with
|
||||||
|
/// unsynchronized writes, emitted from header-inlined code (locale_facets.h).
|
||||||
|
/// Whenever two threads exercise an uncached character concurrently (e.g. by
|
||||||
|
/// compiling a std::regex), ThreadSanitizer rightfully reports the write as a
|
||||||
|
/// data race. The stores are real and unsynchronized, so instrumenting
|
||||||
|
/// libstdc++ cannot help; instead, fill the caches before any thread exists,
|
||||||
|
/// which turns every later access into a pure read.
|
||||||
|
inline void WarmUpLocaleCaches()
|
||||||
|
{
|
||||||
|
auto const& ct = std::use_facet<std::ctype<char>>(std::locale::classic());
|
||||||
|
for (int c = 1; c < 256; ++c) {
|
||||||
|
auto const ch = static_cast<char>(c);
|
||||||
|
ct.narrow(ch, '\0');
|
||||||
|
ct.widen(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the range overload runs _M_narrow_init(), which sets the
|
||||||
|
// all-cached flag (_M_narrow_ok); the single-char loop above fills the
|
||||||
|
// cache without it. (widen() needs no equivalent: the single-char
|
||||||
|
// overload already runs _M_widen_init().)
|
||||||
|
char from[256];
|
||||||
|
char to[256];
|
||||||
|
for (int c = 0; c < 256; ++c) {
|
||||||
|
from[c] = static_cast<char>(c);
|
||||||
|
}
|
||||||
|
ct.narrow(from, from + 256, '?', to);
|
||||||
|
|
||||||
|
// num_put/num_get caches used by stream insertion/extraction
|
||||||
|
std::ostringstream os;
|
||||||
|
os << 4711 << ' ' << 3.14;
|
||||||
|
std::istringstream is("42 3.14");
|
||||||
|
int i = 0;
|
||||||
|
double d = 0.;
|
||||||
|
is >> i >> d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For binaries that do not own main(): define a namespace-scope instance to
|
||||||
|
/// run the warm-up during static initialization, before main().
|
||||||
|
struct LocaleWarmup
|
||||||
|
{
|
||||||
|
LocaleWarmup() { WarmUpLocaleCaches(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fair::mq::test
|
||||||
|
|
||||||
|
#endif /* FAIR_MQ_TEST_LOCALEWARMUP_H */
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
* copied verbatim in the file "LICENSE" *
|
* copied verbatim in the file "LICENSE" *
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "LocaleWarmup.h"
|
||||||
#include "devices/TestPairLeft.h"
|
#include "devices/TestPairLeft.h"
|
||||||
#include "devices/TestPairRight.h"
|
#include "devices/TestPairRight.h"
|
||||||
#include "devices/TestPollIn.h"
|
#include "devices/TestPollIn.h"
|
||||||
@@ -30,6 +31,10 @@
|
|||||||
|
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
[[maybe_unused]] fair::mq::test::LocaleWarmup const gLocaleWarmup{};
|
||||||
|
}
|
||||||
|
|
||||||
auto addCustomOptions(bpo::options_description& options) -> void
|
auto addCustomOptions(bpo::options_description& options) -> void
|
||||||
{
|
{
|
||||||
options.add_options()
|
options.add_options()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <TestEnvironment.h>
|
#include <TestEnvironment.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <helper/LocaleWarmup.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ string runTestDevice = "@RUN_TEST_DEVICE@";
|
|||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
fair::mq::test::WarmUpLocaleCaches();
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||||||
setenv("FAIRMQ_PATH", FAIRMQ_TEST_ENVIRONMENT, 0);
|
setenv("FAIRMQ_PATH", FAIRMQ_TEST_ENVIRONMENT, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user