Skip to content

Commit f80956a

Browse files
committed
Use magic statics for non-trivial thread-local globals
This avoids calling the global's constructor on threads that will never interact with them. Calling the constructor can have surprising overhead, as e.g. MSVC's Debug mode `std::vector` will allocate in the default constructor. Closes #3050
1 parent 32eac2d commit f80956a

File tree

3 files changed

+40
-25
lines changed

3 files changed

+40
-25
lines changed

src/catch2/internal/catch_message_info.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,24 @@
77
// SPDX-License-Identifier: BSL-1.0
88

99
#include <catch2/internal/catch_message_info.hpp>
10+
#include <catch2/internal/catch_thread_local.hpp>
1011

1112
namespace Catch {
1213

14+
namespace {
15+
// Messages are owned by their individual threads, so the counter should
16+
// be thread-local as well. Alternative consideration: atomic counter,
17+
// so threads don't share IDs and things are easier to debug.
18+
static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0;
19+
}
20+
1321
MessageInfo::MessageInfo( StringRef _macroName,
1422
SourceLineInfo const& _lineInfo,
1523
ResultWas::OfType _type )
1624
: macroName( _macroName ),
1725
lineInfo( _lineInfo ),
1826
type( _type ),
19-
sequence( ++globalCount )
27+
sequence( ++messageIDCounter )
2028
{}
2129

22-
// Messages are owned by their individual threads, so the counter should be thread-local as well.
23-
// Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
24-
CATCH_INTERNAL_THREAD_LOCAL unsigned int MessageInfo::globalCount = 0;
25-
2630
} // end namespace Catch

src/catch2/internal/catch_message_info.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#include <catch2/internal/catch_result_type.hpp>
1313
#include <catch2/internal/catch_source_line_info.hpp>
1414
#include <catch2/internal/catch_stringref.hpp>
15-
#include <catch2/internal/catch_thread_local.hpp>
1615

1716
#include <string>
1817

@@ -38,8 +37,6 @@ namespace Catch {
3837
bool operator < (MessageInfo const& other) const {
3938
return sequence < other.sequence;
4039
}
41-
private:
42-
static CATCH_INTERNAL_THREAD_LOCAL unsigned int globalCount;
4340
};
4441

4542
} // end namespace Catch

src/catch2/internal/catch_run_context.cpp

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,21 @@ namespace Catch {
174174
// should also be thread local. For now we just use naked globals
175175
// below, in the future we will want to allocate piece of memory
176176
// from heap, to avoid consuming too much thread-local storage.
177+
//
178+
// Note that we also don't want non-trivial the thread-local variables
179+
// below be initialized for every thread, only for those that touch
180+
// Catch2. To make this work with both GCC/Clang and MSVC, we have to
181+
// make them thread-local magic statics. (Class-level statics have the
182+
// desired semantics on GCC, but not on MSVC).
177183

178184
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
179185
static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false;
180186

181187
// This is the source location for last encountered macro. It is
182188
// used to provide the users with more precise location of error
183189
// when an unexpected exception/fatal error happens.
184-
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1));
190+
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
191+
g_lastKnownLineInfo( "DummyLocation", static_cast<size_t>( -1 ) );
185192

186193
// Should we clear message scopes before sending off the messages to
187194
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
@@ -191,10 +198,16 @@ namespace Catch {
191198
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
192199
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
193200
// Actual messages to be provided to the reporter
194-
static CATCH_INTERNAL_THREAD_LOCAL std::vector<MessageInfo> g_messages;
201+
static std::vector<MessageInfo>& g_messages() {
202+
static CATCH_INTERNAL_THREAD_LOCAL std::vector<MessageInfo> value;
203+
return value;
204+
}
195205

196206
// Owners for the UNSCOPED_X information macro
197-
static CATCH_INTERNAL_THREAD_LOCAL std::vector<ScopedMessage> g_messageScopes;
207+
static std::vector<ScopedMessage>& g_messageScopes() {
208+
static CATCH_INTERNAL_THREAD_LOCAL std::vector<ScopedMessage> value;
209+
return value;
210+
}
198211
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
199212

200213
} // namespace Detail
@@ -341,7 +354,7 @@ namespace Catch {
341354
}
342355

343356
if ( Detail::g_clearMessageScopes ) {
344-
Detail::g_messageScopes.clear();
357+
Detail::g_messageScopes().clear();
345358
Detail::g_clearMessageScopes = false;
346359
}
347360

@@ -350,11 +363,11 @@ namespace Catch {
350363
{
351364
auto _ = scopedDeactivate( *m_outputRedirect );
352365
updateTotalsFromAtomics();
353-
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) );
366+
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages(), m_totals ) );
354367
}
355368

356369
if ( result.getResultType() != ResultWas::Warning ) {
357-
Detail::g_messageScopes.clear();
370+
Detail::g_messageScopes().clear();
358371
}
359372

360373
// Reset working state. assertion info will be reset after
@@ -643,10 +656,10 @@ namespace Catch {
643656

644657
m_testCaseTracker->close();
645658
handleUnfinishedSections();
646-
Detail::g_messageScopes.clear();
659+
Detail::g_messageScopes().clear();
647660
// TBD: At this point, m_messages should be empty. Do we want to
648661
// assert that this is true, or keep the defensive clear call?
649-
Detail::g_messages.clear();
662+
Detail::g_messages().clear();
650663

651664
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
652665
m_reporter->sectionEnded(testCaseSectionStats);
@@ -814,7 +827,7 @@ namespace Catch {
814827
}
815828

816829
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
817-
Detail::g_messages.push_back( CATCH_MOVE( message ) );
830+
Detail::g_messages().push_back( CATCH_MOVE( message ) );
818831
}
819832

820833
void IResultCapture::popScopedMessage( unsigned int messageId ) {
@@ -823,12 +836,13 @@ namespace Catch {
823836
// messages than low single digits, so the optimization is tiny,
824837
// and we would have to hand-write the loop to avoid terrible
825838
// codegen of reverse iterators in debug mode.
826-
Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
827-
Detail::g_messages.end(),
828-
[=]( MessageInfo const& msg ) {
829-
return msg.sequence ==
830-
messageId;
831-
} ) );
839+
auto& messages = Detail::g_messages();
840+
messages.erase( std::find_if( messages.begin(),
841+
messages.end(),
842+
[=]( MessageInfo const& msg ) {
843+
return msg.sequence ==
844+
messageId;
845+
} ) );
832846
}
833847

834848
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
@@ -837,10 +851,10 @@ namespace Catch {
837851
// delayed clear in assertion handling will erase the valid ones
838852
// as well.
839853
if ( Detail::g_clearMessageScopes ) {
840-
Detail::g_messageScopes.clear();
854+
Detail::g_messageScopes().clear();
841855
Detail::g_clearMessageScopes = false;
842856
}
843-
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
857+
Detail::g_messageScopes().emplace_back( CATCH_MOVE( builder ) );
844858
}
845859

846860
void seedRng(IConfig const& config) {

0 commit comments

Comments
 (0)