From c53ce2a5815a8a42d8d399f0117dae868c8f00e4 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 28 Jul 2026 15:26:13 +0800 Subject: [PATCH] fix: prevent video callback crash during transport teardown --- submodules/minirtc | 2 +- tests/video_callback_lifetime_test.cpp | 120 +++++++++++++++++++++++++ xmake/targets.lua | 5 ++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/video_callback_lifetime_test.cpp diff --git a/submodules/minirtc b/submodules/minirtc index c1dbc3b..00aad6a 160000 --- a/submodules/minirtc +++ b/submodules/minirtc @@ -1 +1 @@ -Subproject commit c1dbc3ba8347320e0d9c7df359207c25adadde27 +Subproject commit 00aad6aa372f19011bb906da2e0bcea96ea35b8b diff --git a/tests/video_callback_lifetime_test.cpp b/tests/video_callback_lifetime_test.cpp new file mode 100644 index 0000000..457942b --- /dev/null +++ b/tests/video_callback_lifetime_test.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +namespace { + +std::filesystem::path FindRepoRoot() { + std::filesystem::path current = std::filesystem::current_path(); + while (!current.empty()) { + if (std::filesystem::exists(current / "xmake.lua") && + std::filesystem::exists( + current / "submodules/minirtc/src/transport/ice_transport_controller.cpp")) { + return current; + } + current = current.parent_path(); + } + return {}; +} + +std::string ReadFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + if (!file) { + return {}; + } + + std::ostringstream stream; + stream << file.rdbuf(); + return stream.str(); +} + +std::string ExtractSection(const std::string& source, const std::string& begin, + const std::string& end) { + const size_t begin_pos = source.find(begin); + if (begin_pos == std::string::npos) { + return {}; + } + const size_t end_pos = source.find(end, begin_pos + begin.size()); + if (end_pos == std::string::npos) { + return {}; + } + return source.substr(begin_pos, end_pos - begin_pos); +} + +bool ExpectContains(const char* name, const std::string& value, + const std::string& expected) { + if (value.find(expected) != std::string::npos) { + return true; + } + + std::cerr << name << " missing expected text: " << expected << "\n"; + return false; +} + +bool ExpectNotContains(const char* name, const std::string& value, + const std::string& unexpected) { + if (value.find(unexpected) == std::string::npos) { + return true; + } + + std::cerr << name << " contains unsafe text: " << unexpected << "\n"; + return false; +} + +} // namespace + +int main() { + const std::filesystem::path repo_root = FindRepoRoot(); + if (repo_root.empty()) { + std::cerr << "failed to locate repository root\n"; + return 1; + } + + const std::string controller = ReadFile( + repo_root / + "submodules/minirtc/src/transport/ice_transport_controller.cpp"); + const std::string destroy = ExtractSection( + controller, "void IceTransportController::Destroy()", + "uint32_t IceTransportController::AddVideoSendChannel"); + const std::string send_video = ExtractSection( + controller, "int IceTransportController::SendVideo(", + "void IceTransportController::MaybeDegradeResolutionOnEncodeTime"); + const std::string on_video_encoded = ExtractSection( + controller, "int IceTransportController::OnVideoEncoded(", + "int IceTransportController::SendAudio("); + + bool ok = true; + ok &= ExpectContains("Destroy", destroy, + "std::unique_lock lock(stream_senders_mutex_);"); + ok &= ExpectContains("Destroy", destroy, + "senders.swap(stream_senders_);"); + ok &= ExpectContains("Destroy", destroy, + "codecs.push_back(std::move(context->codec));"); + ok &= ExpectContains("Destroy", destroy, "senders.clear();"); + ok &= ExpectContains("Destroy", destroy, "codecs.clear();"); + ok &= ExpectNotContains("Destroy", destroy, + "std::shared_lock lock(stream_senders_mutex_);"); + + ok &= ExpectContains("SendVideo", send_video, + "std::weak_ptr weak_self"); + ok &= ExpectContains("SendVideo", send_video, + "std::weak_ptr weak_context"); + ok &= ExpectContains( + "SendVideo", send_video, + "[weak_self, weak_context, channel_name, queue_delay_ms"); + ok &= ExpectContains("SendVideo", send_video, + "return self->OnVideoEncoded("); + ok &= ExpectNotContains("SendVideo", send_video, + "[this, channel_name, context"); + + ok &= ExpectContains("OnVideoEncoded", on_video_encoded, + "if (!is_running_.load())"); + ok &= ExpectContains("OnVideoEncoded", on_video_encoded, + "std::shared_lock lock(stream_senders_mutex_);"); + ok &= ExpectContains("OnVideoEncoded", on_video_encoded, + "it->second != context"); + + return ok ? 0 : 1; +} diff --git a/xmake/targets.lua b/xmake/targets.lua index 9d4b225..250614d 100644 --- a/xmake/targets.lua +++ b/xmake/targets.lua @@ -73,6 +73,11 @@ function setup_targets() set_default(false) add_files("tests/connection_status_protocol_test.cpp") + target("video_callback_lifetime_test") + set_kind("binary") + set_default(false) + add_files("tests/video_callback_lifetime_test.cpp") + target("windows_manifest_resource_test") set_kind("binary") set_default(false)