-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Make assertions thread-safe #2948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from all commits
83cbfb9
6829ced
d995e36
47458af
b97392b
6ac5e6a
a39c67f
29e64e4
f1cb615
1f5978c
684392a
47029d5
58137e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
add_executable(benchmarks catch_benchmarks.cpp) | ||
target_link_libraries(benchmarks PRIVATE Catch2WithMain) | ||
target_compile_features(benchmarks PUBLIC cxx_std_17) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#include <catch2/catch_test_macros.hpp> | ||
#include <catch2/benchmark/catch_benchmark.hpp> | ||
|
||
#include <mutex> | ||
|
||
std::recursive_mutex global_lock; | ||
|
||
int no_lock() { | ||
return 2; | ||
} | ||
|
||
int take_lock() { | ||
std::unique_lock<std::recursive_mutex> lock(global_lock); | ||
return 2; | ||
} | ||
|
||
TEST_CASE("std::recursive_mutex overhead benchmark", "[benchmark][mutex]") { | ||
BENCHMARK("no lock") { | ||
return no_lock(); | ||
}; | ||
|
||
BENCHMARK("with std::recursive_mutex") { | ||
return take_lock(); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,12 @@ | |
#include <catch2/internal/catch_context.hpp> | ||
#include <catch2/internal/catch_debugger.hpp> | ||
#include <catch2/internal/catch_test_failure_exception.hpp> | ||
#include <catch2/internal/catch_global_lock.hpp> | ||
#include <catch2/matchers/catch_matchers_string.hpp> | ||
|
||
namespace Catch { | ||
// The AssertionHandler API and handleExceptionMatchExpr are used by assertion macros. Everything here must be | ||
// locked as catch internals are not thread-safe. | ||
|
||
AssertionHandler::AssertionHandler | ||
( StringRef macroName, | ||
|
@@ -22,13 +25,23 @@ | |
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, | ||
m_resultCapture( getResultCapture() ) | ||
{ | ||
auto lock = take_global_lock(); | ||
m_resultCapture.notifyAssertionStarted( m_assertionInfo ); | ||
} | ||
|
||
AssertionHandler::~AssertionHandler() { | ||
auto lock = take_global_lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be moved inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handler is on stack, so multiple threads shouldn't be writing to it. |
||
if ( !m_completed ) { | ||
m_resultCapture.handleIncomplete( m_assertionInfo ); | ||
} | ||
} | ||
|
||
void AssertionHandler::handleExpr( ITransientExpression const& expr ) { | ||
auto lock = take_global_lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these should live in the capture, because it already handles things like assertion fast-path if no reporters have to be notified about a passing assertion. Speaking of, the current fast path for assertions is a simple counter increment without invoking the reporter. We should be able to have similar fast path (with an atomic counter) after this. |
||
m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); | ||
} | ||
void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string&& message) { | ||
auto lock = take_global_lock(); | ||
m_resultCapture.handleMessage( m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction ); | ||
} | ||
|
||
|
@@ -55,21 +68,26 @@ | |
} | ||
|
||
void AssertionHandler::handleUnexpectedInflightException() { | ||
auto lock = take_global_lock(); | ||
m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); | ||
} | ||
|
||
void AssertionHandler::handleExceptionThrownAsExpected() { | ||
auto lock = take_global_lock(); | ||
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); | ||
} | ||
void AssertionHandler::handleExceptionNotThrownAsExpected() { | ||
auto lock = take_global_lock(); | ||
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); | ||
} | ||
|
||
void AssertionHandler::handleUnexpectedExceptionNotThrown() { | ||
auto lock = take_global_lock(); | ||
m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); | ||
} | ||
|
||
void AssertionHandler::handleThrowingCallSkipped() { | ||
auto lock = take_global_lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codecov is complaining for this line, please check:
|
||
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
|
||
// Copyright Catch2 Authors | ||
// Distributed under the Boost Software License, Version 1.0. | ||
// (See accompanying file LICENSE.txt or copy at | ||
// https://www.boost.org/LICENSE_1_0.txt) | ||
|
||
// SPDX-License-Identifier: BSL-1.0 | ||
#include <catch2/internal/catch_global_lock.hpp> | ||
#include <catch2/internal/catch_compiler_capabilities.hpp> | ||
|
||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION | ||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS | ||
|
||
namespace Catch { | ||
|
||
std::recursive_mutex& get_global_lock() { | ||
static std::recursive_mutex global_lock; | ||
return global_lock; | ||
} | ||
|
||
} // namespace Catch | ||
|
||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
// Copyright Catch2 Authors | ||
// Distributed under the Boost Software License, Version 1.0. | ||
// (See accompanying file LICENSE.txt or copy at | ||
// https://www.boost.org/LICENSE_1_0.txt) | ||
|
||
// SPDX-License-Identifier: BSL-1.0 | ||
|
||
#ifndef CATCH_GLOBAL_LOCK_HPP_INCLUDED | ||
#define CATCH_GLOBAL_LOCK_HPP_INCLUDED | ||
|
||
#include <mutex> | ||
|
||
namespace Catch { | ||
|
||
std::recursive_mutex& get_global_lock(); | ||
|
||
inline auto take_global_lock() { | ||
return std::unique_lock<std::recursive_mutex>(get_global_lock()); | ||
} | ||
|
||
} // namespace Catch | ||
|
||
#endif // CATCH_GLOBAL_LOCK_HPP_INCLUDED |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
#include <catch2/internal/catch_console_width.hpp> | ||
#include <catch2/reporters/catch_reporter_helpers.hpp> | ||
#include <catch2/internal/catch_move_and_forward.hpp> | ||
#include <catch2/internal/catch_global_lock.hpp> | ||
#include <catch2/catch_get_random_seed.hpp> | ||
|
||
#include <cstdio> | ||
|
@@ -462,7 +463,10 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { | |
StreamingReporterBase::sectionEnded(_sectionStats); | ||
} | ||
|
||
// Catch benchmark macros call these functions. Since catch internals are not thread-safe locking is needed. | ||
|
||
void ConsoleReporter::benchmarkPreparing( StringRef name ) { | ||
auto lock = take_global_lock(); | ||
lazyPrintWithoutClosingBenchmarkTable(); | ||
|
||
auto nameCol = TextFlow::Column( static_cast<std::string>( name ) ) | ||
|
@@ -480,6 +484,7 @@ void ConsoleReporter::benchmarkPreparing( StringRef name ) { | |
} | ||
|
||
void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { | ||
auto lock = take_global_lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Locking in ConsoleReporter is suspect, because nothing enforces that the users use it. It should be done further up the callstack, e.g. in run_context. |
||
(*m_tablePrinter) << info.samples << ColumnBreak() | ||
<< info.iterations << ColumnBreak(); | ||
if ( !m_config->benchmarkNoAnalysis() ) { | ||
|
@@ -489,6 +494,7 @@ void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { | |
( *m_tablePrinter ) << OutputFlush{}; | ||
} | ||
void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { | ||
auto lock = take_global_lock(); | ||
if (m_config->benchmarkNoAnalysis()) | ||
{ | ||
(*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); | ||
|
@@ -506,6 +512,7 @@ void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { | |
} | ||
|
||
void ConsoleReporter::benchmarkFailed( StringRef error ) { | ||
auto lock = take_global_lock(); | ||
auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); | ||
(*m_tablePrinter) | ||
<< "Benchmark failed (" << error << ')' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain the rationale behind choosing std::recursive_mutex over other types of mutexes.