Use magic statics for thread-local globals

This commit is contained in:
Martin Hořeňovský
2025-12-01 22:23:51 +01:00
parent d427034253
commit e075e04cde
3 changed files with 77 additions and 50 deletions

View File

@@ -7,20 +7,27 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/internal/catch_message_info.hpp> #include <catch2/internal/catch_message_info.hpp>
#include <catch2/internal/catch_thread_local.hpp>
namespace Catch { namespace Catch {
namespace {
// Messages are owned by their individual threads, so the counter should
// be thread-local as well. Alternative consideration: atomic counter,
// so threads don't share IDs and things are easier to debug.
static unsigned int GetNextMessageID() {
static CATCH_INTERNAL_THREAD_LOCAL unsigned int counter = 0;
return ++counter;
}
}
MessageInfo::MessageInfo( StringRef _macroName, MessageInfo::MessageInfo( StringRef _macroName,
SourceLineInfo const& _lineInfo, SourceLineInfo const& _lineInfo,
ResultWas::OfType _type ) ResultWas::OfType _type )
: macroName( _macroName ), : macroName( _macroName ),
lineInfo( _lineInfo ), lineInfo( _lineInfo ),
type( _type ), type( _type ),
sequence( ++globalCount ) sequence( GetNextMessageID() )
{} {}
// Messages are owned by their individual threads, so the counter should be thread-local as well.
// Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
CATCH_INTERNAL_THREAD_LOCAL unsigned int MessageInfo::globalCount = 0;
} // end namespace Catch } // end namespace Catch

View File

@@ -12,7 +12,6 @@
#include <catch2/internal/catch_result_type.hpp> #include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_source_line_info.hpp> #include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_thread_local.hpp>
#include <string> #include <string>
@@ -38,8 +37,6 @@ namespace Catch {
bool operator < (MessageInfo const& other) const { bool operator < (MessageInfo const& other) const {
return sequence < other.sequence; return sequence < other.sequence;
} }
private:
static CATCH_INTERNAL_THREAD_LOCAL unsigned int globalCount;
}; };
} // end namespace Catch } // end namespace Catch

View File

@@ -174,27 +174,49 @@ namespace Catch {
// should also be thread local. For now we just use naked globals // should also be thread local. For now we just use naked globals
// below, in the future we will want to allocate piece of memory // below, in the future we will want to allocate piece of memory
// from heap, to avoid consuming too much thread-local storage. // from heap, to avoid consuming too much thread-local storage.
//
// Note that we also don't want the thread-local variables below
// be initialized for every thread, only for those that touch Catch2.
// To make this work with both GCC/Clang and MSVC, we have to make
// them thread-local magic statics. (Class-level statics have
// the desired semantics on GCC, but not on MSVC).
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false; static bool& g_lastAssertionPassed() {
static CATCH_INTERNAL_THREAD_LOCAL bool value = false;
return value;
}
// This is the source location for last encountered macro. It is // This is the source location for last encountered macro. It is
// used to provide the users with more precise location of error // used to provide the users with more precise location of error
// when an unexpected exception/fatal error happens. // when an unexpected exception/fatal error happens.
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1)); static SourceLineInfo& g_lastKnownLineInfo() {
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo value(
"DummyLocation", static_cast<size_t>( -1 ) );
return value;
}
// Should we clear message scopes before sending off the messages to // Should we clear message scopes before sending off the messages to
// reporter? Set in `assertionPassedFastPath` to avoid doing the full // reporter? Set in `assertionPassedFastPath` to avoid doing the full
// clear there for performance reasons. // clear there for performance reasons.
static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false; static bool& g_clearMessageScopes() {
static CATCH_INTERNAL_THREAD_LOCAL bool value = false;
return value;
}
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
// Actual messages to be provided to the reporter // Actual messages to be provided to the reporter
static CATCH_INTERNAL_THREAD_LOCAL std::vector<MessageInfo> g_messages; static std::vector<MessageInfo>& g_messages() {
static CATCH_INTERNAL_THREAD_LOCAL std::vector<MessageInfo> value;
return value;
}
// Owners for the UNSCOPED_X information macro // Owners for the UNSCOPED_X information macro
static CATCH_INTERNAL_THREAD_LOCAL std::vector<ScopedMessage> g_messageScopes; static std::vector<ScopedMessage>& g_messageScopes() {
static CATCH_INTERNAL_THREAD_LOCAL std::vector<ScopedMessage> value;
return value;
}
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
} // namespace Detail } // namespace Detail
@@ -320,15 +342,15 @@ namespace Catch {
void RunContext::assertionEnded(AssertionResult&& result) { void RunContext::assertionEnded(AssertionResult&& result) {
Detail::g_lastKnownLineInfo = result.m_info.lineInfo; Detail::g_lastKnownLineInfo() = result.m_info.lineInfo;
if (result.getResultType() == ResultWas::Ok) { if (result.getResultType() == ResultWas::Ok) {
m_atomicAssertionCount.passed++; m_atomicAssertionCount.passed++;
Detail::g_lastAssertionPassed = true; Detail::g_lastAssertionPassed() = true;
} else if (result.getResultType() == ResultWas::ExplicitSkip) { } else if (result.getResultType() == ResultWas::ExplicitSkip) {
m_atomicAssertionCount.skipped++; m_atomicAssertionCount.skipped++;
Detail::g_lastAssertionPassed = true; Detail::g_lastAssertionPassed() = true;
} else if (!result.succeeded()) { } else if (!result.succeeded()) {
Detail::g_lastAssertionPassed = false; Detail::g_lastAssertionPassed() = false;
if (result.isOk()) { if (result.isOk()) {
} }
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
@@ -337,12 +359,12 @@ namespace Catch {
m_atomicAssertionCount.failed++; m_atomicAssertionCount.failed++;
} }
else { else {
Detail::g_lastAssertionPassed = true; Detail::g_lastAssertionPassed() = true;
} }
if ( Detail::g_clearMessageScopes ) { if ( Detail::g_clearMessageScopes() ) {
Detail::g_messageScopes.clear(); Detail::g_messageScopes().clear();
Detail::g_clearMessageScopes = false; Detail::g_clearMessageScopes() = false;
} }
// From here, we are touching shared state and need mutex. // From here, we are touching shared state and need mutex.
@@ -350,11 +372,11 @@ namespace Catch {
{ {
auto _ = scopedDeactivate( *m_outputRedirect ); auto _ = scopedDeactivate( *m_outputRedirect );
updateTotalsFromAtomics(); updateTotalsFromAtomics();
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) ); m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages(), m_totals ) );
} }
if ( result.getResultType() != ResultWas::Warning ) { if ( result.getResultType() != ResultWas::Warning ) {
Detail::g_messageScopes.clear(); Detail::g_messageScopes().clear();
} }
// Reset working state. assertion info will be reset after // Reset working state. assertion info will be reset after
@@ -383,7 +405,7 @@ namespace Catch {
m_activeSections.push_back(&sectionTracker); m_activeSections.push_back(&sectionTracker);
SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) ); SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) );
Detail::g_lastKnownLineInfo = sectionLineInfo; Detail::g_lastKnownLineInfo() = sectionLineInfo;
{ {
auto _ = scopedDeactivate( *m_outputRedirect ); auto _ = scopedDeactivate( *m_outputRedirect );
@@ -402,7 +424,7 @@ namespace Catch {
m_trackerContext, m_trackerContext,
TestCaseTracking::NameAndLocationRef( TestCaseTracking::NameAndLocationRef(
generatorName, lineInfo ) ); generatorName, lineInfo ) );
Detail::g_lastKnownLineInfo = lineInfo; Detail::g_lastKnownLineInfo() = lineInfo;
return tracker; return tracker;
} }
@@ -581,15 +603,15 @@ namespace Catch {
} }
bool RunContext::lastAssertionPassed() { bool RunContext::lastAssertionPassed() {
return Detail::g_lastAssertionPassed; return Detail::g_lastAssertionPassed();
} }
void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
// We want to save the line info for better experience with unexpected assertions // We want to save the line info for better experience with unexpected assertions
Detail::g_lastKnownLineInfo = lineInfo; Detail::g_lastKnownLineInfo() = lineInfo;
++m_atomicAssertionCount.passed; ++m_atomicAssertionCount.passed;
Detail::g_lastAssertionPassed = true; Detail::g_lastAssertionPassed() = true;
Detail::g_clearMessageScopes = true; Detail::g_clearMessageScopes() = true;
} }
void RunContext::updateTotalsFromAtomics() { void RunContext::updateTotalsFromAtomics() {
@@ -613,7 +635,7 @@ namespace Catch {
Counts prevAssertions = m_totals.assertions; Counts prevAssertions = m_totals.assertions;
double duration = 0; double duration = 0;
m_shouldReportUnexpected = true; m_shouldReportUnexpected = true;
Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo; Detail::g_lastKnownLineInfo() = testCaseInfo.lineInfo;
Timer timer; Timer timer;
CATCH_TRY { CATCH_TRY {
@@ -643,10 +665,10 @@ namespace Catch {
m_testCaseTracker->close(); m_testCaseTracker->close();
handleUnfinishedSections(); handleUnfinishedSections();
Detail::g_messageScopes.clear(); Detail::g_messageScopes().clear();
// TBD: At this point, m_messages should be empty. Do we want to // TBD: At this point, m_messages should be empty. Do we want to
// assert that this is true, or keep the defensive clear call? // assert that this is true, or keep the defensive clear call?
Detail::g_messages.clear(); Detail::g_messages().clear();
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
m_reporter->sectionEnded(testCaseSectionStats); m_reporter->sectionEnded(testCaseSectionStats);
@@ -705,7 +727,7 @@ namespace Catch {
ITransientExpression const *expr, ITransientExpression const *expr,
bool negated ) { bool negated ) {
Detail::g_lastKnownLineInfo = info.lineInfo; Detail::g_lastKnownLineInfo() = info.lineInfo;
AssertionResultData data( resultType, LazyExpression( negated ) ); AssertionResultData data( resultType, LazyExpression( negated ) );
AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
@@ -720,7 +742,7 @@ namespace Catch {
std::string&& message, std::string&& message,
AssertionReaction& reaction AssertionReaction& reaction
) { ) {
Detail::g_lastKnownLineInfo = info.lineInfo; Detail::g_lastKnownLineInfo() = info.lineInfo;
AssertionResultData data( resultType, LazyExpression( false ) ); AssertionResultData data( resultType, LazyExpression( false ) );
data.message = CATCH_MOVE( message ); data.message = CATCH_MOVE( message );
@@ -751,7 +773,7 @@ namespace Catch {
std::string&& message, std::string&& message,
AssertionReaction& reaction AssertionReaction& reaction
) { ) {
Detail::g_lastKnownLineInfo = info.lineInfo; Detail::g_lastKnownLineInfo() = info.lineInfo;
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
data.message = CATCH_MOVE(message); data.message = CATCH_MOVE(message);
@@ -768,13 +790,13 @@ namespace Catch {
} }
AssertionInfo RunContext::makeDummyAssertionInfo() { AssertionInfo RunContext::makeDummyAssertionInfo() {
auto const& lastLineInfo = Detail::g_lastKnownLineInfo();
const bool testCaseJustStarted = const bool testCaseJustStarted =
Detail::g_lastKnownLineInfo == lastLineInfo == m_activeTestCase->getTestCaseInfo().lineInfo;
m_activeTestCase->getTestCaseInfo().lineInfo;
return AssertionInfo{ return AssertionInfo{
testCaseJustStarted ? "TEST_CASE"_sr : StringRef(), testCaseJustStarted ? "TEST_CASE"_sr : StringRef(),
Detail::g_lastKnownLineInfo, lastLineInfo,
testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr, testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr,
ResultDisposition::Normal ResultDisposition::Normal
}; };
@@ -784,7 +806,7 @@ namespace Catch {
AssertionInfo const& info AssertionInfo const& info
) { ) {
using namespace std::string_literals; using namespace std::string_literals;
Detail::g_lastKnownLineInfo = info.lineInfo; Detail::g_lastKnownLineInfo() = info.lineInfo;
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s;
@@ -814,7 +836,7 @@ namespace Catch {
} }
void IResultCapture::pushScopedMessage( MessageInfo&& message ) { void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE( message ) ); Detail::g_messages().push_back( CATCH_MOVE( message ) );
} }
void IResultCapture::popScopedMessage( unsigned int messageId ) { void IResultCapture::popScopedMessage( unsigned int messageId ) {
@@ -823,12 +845,13 @@ namespace Catch {
// messages than low single digits, so the optimization is tiny, // messages than low single digits, so the optimization is tiny,
// and we would have to hand-write the loop to avoid terrible // and we would have to hand-write the loop to avoid terrible
// codegen of reverse iterators in debug mode. // codegen of reverse iterators in debug mode.
Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(), auto& messages = Detail::g_messages();
Detail::g_messages.end(), messages.erase( std::find_if( messages.begin(),
[=]( MessageInfo const& msg ) { messages.end(),
return msg.sequence == [=]( MessageInfo const& msg ) {
messageId; return msg.sequence ==
} ) ); messageId;
} ) );
} }
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) { void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
@@ -836,11 +859,11 @@ namespace Catch {
// we have to get rid of them before adding new ones, or the // we have to get rid of them before adding new ones, or the
// delayed clear in assertion handling will erase the valid ones // delayed clear in assertion handling will erase the valid ones
// as well. // as well.
if ( Detail::g_clearMessageScopes ) { if ( Detail::g_clearMessageScopes() ) {
Detail::g_messageScopes.clear(); Detail::g_messageScopes().clear();
Detail::g_clearMessageScopes = false; Detail::g_clearMessageScopes() = false;
} }
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) ); Detail::g_messageScopes().emplace_back( CATCH_MOVE( builder ) );
} }
void seedRng(IConfig const& config) { void seedRng(IConfig const& config) {