Skip to content

Commit f4344ae

Browse files
committed
Merge #222: test, ci: Fix threadsanitizer errors in mptest
73d22ba test: Fix tsan race in thread busy test (Ryan Ofsky) b74e1bb ci: Use tsan-instrumented cap'n proto in sanitizers job (Ryan Ofsky) c332774 test: Fix failing exception check in new thread busy test (Ryan Ofsky) ca3c05d test: Use KJ_LOG instead of std::cout for logging (Ryan Ofsky) 7eb1da1 ci: Use tsan-instrumented libcxx in sanitizers job (Ryan Ofsky) Pull request description: This PR fixes threadsanitizer errors that were found in bitcoin CI and reported by maflcko in bitcoin/bitcoin#33518 (comment). It also extends the libmultiprocess sanitize CI job to report the same errors. Before this PR, libmultiprocess was built with threadsanitzer in CI, but cap'n proto and libc++ dependencies were built without it, so races in these libraries could be undetected. Building libc++ with threadsanitzer triggered threadsanitzer errors about accessing `std::cout` from multiple threads. Those errors are suppressed in bitcoin CI, but there is not really a reason to suppress there here, so they are just fixed by replacing `std::cout` with `KJ_LOG` in the test. This change also cleans up `mptest` output, which is nice. Full output can be seen by running `mptest --verbose`. Building cap'nproto with threadsanitzer triggered threadsanitzer errors in the new "thread busy" test added in #214. These were just caused by accessing some test variables from two different threads and are fixed in the last commit here. ACKs for top commit: Eunovo: Tested ACK 73d22ba Sjors: ACK 73d22ba Tree-SHA512: b8389030897fa5c176e84fbe91d86689189d04c3facd6453e08c544d5fb7ca9e40111ee62e7d0eb14b25b73986a80a283b580553945a5a3b4ad446ade3c434e3
2 parents ec86e43 + 73d22ba commit f4344ae

File tree

5 files changed

+50
-16
lines changed

5 files changed

+50
-16
lines changed

ci/configs/sanitize.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
CI_DESC="CI job running ThreadSanitizer"
22
CI_DIR=build-sanitize
3+
NIX_ARGS=(--arg enableLibcxx true --argstr libcxxSanitizers "Thread" --argstr capnprotoSanitizers "thread")
34
export CXX=clang++
45
export CXXFLAGS="-ggdb -Werror -Wall -Wextra -Wpedantic -Wthread-safety -Wno-unused-parameter -fsanitize=thread"
56
CMAKE_ARGS=()

include/mp/proxy-io.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class LoggingErrorHandler : public kj::TaskSet::ErrorHandler
9898
EventLoop& m_loop;
9999
};
100100

101+
//! Log flags. Update stringify function if changed!
101102
enum class Log {
102103
Trace = 0,
103104
Debug,
@@ -107,6 +108,8 @@ enum class Log {
107108
Raise,
108109
};
109110

111+
kj::StringPtr KJ_STRINGIFY(Log flags);
112+
110113
struct LogMessage {
111114

112115
//! Message to be logged

shell.nix

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
, enableLibcxx ? false # Whether to use libc++ toolchain and libraries instead of libstdc++
44
, minimal ? false # Whether to create minimal shell without extra tools (faster when cross compiling)
55
, capnprotoVersion ? null
6+
, capnprotoSanitizers ? null # Optional sanitizers to build cap'n proto with
67
, cmakeVersion ? null
8+
, libcxxSanitizers ? null # Optional LLVM_USE_SANITIZER value to use for libc++, see https://llvm.org/docs/CMake.html
79
}:
810

911
let
1012
lib = pkgs.lib;
11-
llvm = crossPkgs.llvmPackages_20;
13+
llvmBase = crossPkgs.llvmPackages_21;
14+
llvm = llvmBase // lib.optionalAttrs (libcxxSanitizers != null) {
15+
libcxx = llvmBase.libcxx.override {
16+
devExtraCmakeFlags = [ "-DLLVM_USE_SANITIZER=${libcxxSanitizers}" ];
17+
};
18+
};
1219
capnprotoHashes = {
1320
"0.7.0" = "sha256-Y/7dUOQPDHjniuKNRw3j8dG1NI9f/aRWpf8V0WzV9k8=";
1421
"0.7.1" = "sha256-3cBpVmpvCXyqPUXDp12vCFCk32ZXWpkdOliNH37UwWE=";
@@ -35,7 +42,17 @@ let
3542
} // (lib.optionalAttrs (lib.versionOlder capnprotoVersion "0.10") {
3643
env = { }; # Drop -std=c++20 flag forced by nixpkgs
3744
}));
38-
capnproto = capnprotoBase.override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; });
45+
capnproto = (capnprotoBase.overrideAttrs (old: lib.optionalAttrs (capnprotoSanitizers != null) {
46+
env = (old.env or { }) // {
47+
CXXFLAGS =
48+
lib.concatStringsSep " " [
49+
(old.env.CXXFLAGS or "")
50+
"-fsanitize=${capnprotoSanitizers}"
51+
"-fno-omit-frame-pointer"
52+
"-g"
53+
];
54+
};
55+
})).override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; });
3956
clang = if enableLibcxx then llvm.libcxxClang else llvm.clang;
4057
clang-tools = llvm.clang-tools.override { inherit enableLibcxx; };
4158
cmakeHashes = {

src/mp/proxy.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <kj/debug.h>
2525
#include <kj/function.h>
2626
#include <kj/memory.h>
27+
#include <kj/string.h>
2728
#include <map>
2829
#include <memory>
2930
#include <optional>
@@ -430,4 +431,16 @@ std::string LongThreadName(const char* exe_name)
430431
return g_thread_context.thread_name.empty() ? ThreadName(exe_name) : g_thread_context.thread_name;
431432
}
432433

434+
kj::StringPtr KJ_STRINGIFY(Log v)
435+
{
436+
switch (v) {
437+
case Log::Trace: return "Trace";
438+
case Log::Debug: return "Debug";
439+
case Log::Info: return "Info";
440+
case Log::Warning: return "Warning";
441+
case Log::Error: return "Error";
442+
case Log::Raise: return "Raise";
443+
}
444+
return "<Log?>";
445+
}
433446
} // namespace mp

test/mp/test/test.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
#include <capnp/rpc.h>
1111
#include <condition_variable>
1212
#include <cstring>
13-
#include <exception>
1413
#include <functional>
1514
#include <future>
16-
#include <iostream>
1715
#include <kj/async.h>
1816
#include <kj/async-io.h>
1917
#include <kj/common.h>
@@ -24,14 +22,15 @@
2422
#include <kj/test.h>
2523
#include <memory>
2624
#include <mp/proxy.h>
27-
#include "mp/proxy.capnp.h"
25+
#include <mp/proxy.capnp.h>
2826
#include <mp/proxy-io.h>
29-
#include "mp/util.h"
27+
#include <mp/util.h>
3028
#include <optional>
3129
#include <set>
3230
#include <stdexcept>
3331
#include <string>
3432
#include <string_view>
33+
#include <system_error>
3534
#include <thread>
3635
#include <utility>
3736
#include <vector>
@@ -67,9 +66,10 @@ class TestSetup
6766

6867
TestSetup(bool client_owns_connection = true)
6968
: thread{[&] {
70-
EventLoop loop("mptest", [](mp::LogMessage log_data) {
71-
std::cout << "LOG" << (int)log_data.level << ": " << log_data.message << "\n";
72-
if (log_data.level == mp::Log::Raise) throw std::runtime_error(log_data.message);
69+
EventLoop loop("mptest", [](mp::LogMessage log) {
70+
// Info logs are not printed by default, but will be shown with `mptest --verbose`
71+
KJ_LOG(INFO, log.level, log.message);
72+
if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
7373
});
7474
auto pipe = loop.m_io_context.provider->newTwoWayPipe();
7575

@@ -321,21 +321,21 @@ KJ_TEST("Make simultaneous IPC callbacks with same request_thread and callback_t
321321
setup.server->m_impl->m_fn = [&] {};
322322
foo->callFnAsync();
323323
ThreadContext& tc{g_thread_context};
324-
std::optional<Thread::Client> callback_thread, request_thread;
325-
{
324+
Thread::Client *callback_thread, *request_thread;
325+
foo->m_context.loop->sync([&] {
326326
Lock lock(tc.waiter->m_mutex);
327-
callback_thread = tc.callback_threads.at(foo->m_context.connection)->m_client;
328-
request_thread = tc.request_threads.at(foo->m_context.connection)->m_client;
329-
}
327+
callback_thread = &tc.callback_threads.at(foo->m_context.connection)->m_client;
328+
request_thread = &tc.request_threads.at(foo->m_context.connection)->m_client;
329+
});
330330

331331
setup.server->m_impl->m_fn = [&] {
332332
try
333333
{
334334
signal.get_future().get();
335335
}
336-
catch(const std::exception& e)
336+
catch (const std::future_error& e)
337337
{
338-
KJ_EXPECT(e.what() == std::string("Future already retrieved"));
338+
KJ_EXPECT(e.code() == std::make_error_code(std::future_errc::future_already_retrieved));
339339
}
340340
};
341341

0 commit comments

Comments
 (0)