-
-
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
Changes from all commits
615ba17
ba300c5
6f20280
d66c80c
5a0e434
9e0f241
7f65ccc
2ae6a02
a7028d2
ff96b87
b7fa6f2
6ba3c1a
85bbbd9
221d5ef
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,6 @@ | ||
add_executable(Benchmarks catch_benchmarks.cpp) | ||
target_link_libraries(Benchmarks PRIVATE Catch2WithMain) | ||
target_compile_features(Benchmarks PUBLIC cxx_std_14) | ||
|
||
list(APPEND CATCH_TEST_TARGETS Benchmarks) | ||
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) |
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 @@ namespace Catch { | |
: 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(); | ||
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 @@ namespace Catch { | |
} | ||
|
||
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(); | ||
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 |
---|---|---|
@@ -0,0 +1,37 @@ | ||
|
||
// 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/catch_test_macros.hpp> | ||
#include <catch2/internal/catch_textflow.hpp> | ||
#include <catch2/benchmark/catch_benchmark.hpp> | ||
|
||
#include <thread> | ||
#include <atomic> | ||
|
||
TEST_CASE( "ThreadAssertionTest", | ||
"[Multithreading]" ) { | ||
SECTION( "Basic" ) { | ||
std::atomic_bool should_stop{false}; | ||
std::thread a([&should_stop] () { | ||
while (!should_stop) { | ||
FAIL_CHECK(false); | ||
CHECK(true); | ||
} | ||
}); | ||
std::thread b([&should_stop] () { | ||
while (!should_stop) { | ||
FAIL_CHECK(false); | ||
CHECK(true); | ||
} | ||
}); | ||
std::this_thread::sleep_for( std::chrono::milliseconds( 1'000 ) ); | ||
should_stop = true; | ||
a.join(); | ||
b.join(); | ||
} | ||
} |
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.
Can this be moved inside the
if
or ism_completed
written to from other threads?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.
The handler is on stack, so multiple threads shouldn't be writing to it.