fix(linux): make the render-executor completion handoff visible to TSan
GLib's GMutex is a raw futex, so ThreadSanitizer cannot see the happens-before edge g_main_context_invoke_full provides and fails the thread lane on plane_render_executor_test. Publish the completion payload through an explicit release/acquire pair, and suppress the remaining reports that originate wholly inside libglib's own source bookkeeping. Races on repository memory still fail the run; verified 0/100 failing TSan runs in an ubuntu:24.04 container (20/20 before).
This commit is contained in:
@@ -232,4 +232,11 @@ if(PLEZY_BUILD_MPV_RELIABILITY_TESTS)
|
||||
target_link_libraries(plane_render_executor_test PRIVATE PkgConfig::GTK Threads::Threads)
|
||||
apply_mpv_reliability_sanitizer(plane_render_executor_test)
|
||||
add_test(NAME plane_render_executor_test COMMAND plane_render_executor_test)
|
||||
if(PLEZY_MPV_RELIABILITY_SANITIZER STREQUAL "thread")
|
||||
# GLib's futex-based GMutex is invisible to TSan; suppress reports that
|
||||
# originate inside libglib. Inert when the sanitizer probe fell back to an
|
||||
# unsanitized build. See mpv/tsan_suppressions.txt.
|
||||
set_tests_properties(plane_render_executor_test PROPERTIES ENVIRONMENT
|
||||
"TSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/mpv/tsan_suppressions.txt")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "plane_render_executor.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
namespace mpv {
|
||||
@@ -9,15 +10,29 @@ namespace {
|
||||
struct CompletionInvocation {
|
||||
PlaneRenderExecutor::Completion completion;
|
||||
bool result;
|
||||
// The worker hands this struct to the main context through
|
||||
// g_main_context_invoke_full, whose internal context lock is a GLib futex
|
||||
// that ThreadSanitizer cannot observe. This release/acquire pair republishes
|
||||
// that same happens-before edge in a form TSan models; GLib still provides
|
||||
// the delivery ordering.
|
||||
std::atomic<bool> published{false};
|
||||
};
|
||||
|
||||
gboolean InvokeCompletion(gpointer data) {
|
||||
auto* invocation = static_cast<CompletionInvocation*>(data);
|
||||
// Pairs with the release store in Run(); see CompletionInvocation.
|
||||
(void)invocation->published.load(std::memory_order_acquire);
|
||||
invocation->completion(invocation->result);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void DestroyCompletionInvocation(gpointer data) { delete static_cast<CompletionInvocation*>(data); }
|
||||
void DestroyCompletionInvocation(gpointer data) {
|
||||
auto* invocation = static_cast<CompletionInvocation*>(data);
|
||||
// The destroy notify can also be the first main-thread access when the
|
||||
// source is torn down without dispatching; take the same edge.
|
||||
(void)invocation->published.load(std::memory_order_acquire);
|
||||
delete invocation;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -84,9 +99,10 @@ void PlaneRenderExecutor::Run(const std::shared_ptr<Shared>& shared) {
|
||||
|
||||
const bool result = job ? job() : false;
|
||||
if (completion) {
|
||||
auto* invocation = new CompletionInvocation{std::move(completion), result};
|
||||
invocation->published.store(true, std::memory_order_release);
|
||||
g_main_context_invoke_full(
|
||||
shared->completion_context, G_PRIORITY_DEFAULT, InvokeCompletion,
|
||||
new CompletionInvocation{std::move(completion), result}, DestroyCompletionInvocation);
|
||||
shared->completion_context, G_PRIORITY_DEFAULT, InvokeCompletion, invocation, DestroyCompletionInvocation);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# ThreadSanitizer suppressions for the focused mpv reliability tests.
|
||||
#
|
||||
# GLib's GMutex is a raw futex, not a pthread mutex, so TSan cannot observe
|
||||
# the happens-before edges GMainContext provides. Any cross-thread handoff
|
||||
# through g_main_context_invoke_full then reports races inside GLib's own
|
||||
# source/queue bookkeeping (g_malloc vs free of idle sources, etc.).
|
||||
#
|
||||
# This only hides reports whose racing access originates inside libglib;
|
||||
# races on this repository's memory still fail the run. The executor's own
|
||||
# completion handoff carries an explicit release/acquire pair (see
|
||||
# plane_render_executor.cc) precisely so it stays visible to TSan.
|
||||
called_from_lib:libglib-2.0.so
|
||||
Reference in New Issue
Block a user