Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/linux-taskrunner-use-after-free
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patch type="fixed" "Fix use-after-free crash in TaskRunnerLinux::EnqueueTask when the runner is destroyed before the main loop dispatches a queued task"
8 changes: 4 additions & 4 deletions linux/livekit_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class VisualizerSink : public libwebrtc::AudioTrackSink {
&flutter::StandardMethodCodec::GetInstance())),
media_track_(media_track), is_centered_(is_centered),
bar_count_(bar_count) {
task_runner_ = std::make_unique<livekit_client_plugin::TaskRunnerLinux>();
task_runner_ = std::make_shared<livekit_client_plugin::TaskRunnerLinux>();
auto handler = std::make_unique<
flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
[&](const flutter::EncodableValue *arguments,
Expand Down Expand Up @@ -151,7 +151,7 @@ class VisualizerSink : public libwebrtc::AudioTrackSink {

private:
std::unique_ptr<AudioVisualizer> audio_visualizer_;
std::unique_ptr<livekit_client_plugin::TaskRunnerLinux> task_runner_;
std::shared_ptr<livekit_client_plugin::TaskRunnerLinux> task_runner_;
std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> channel_;
std::shared_ptr<flutter::EventSink<flutter::EncodableValue>> sink_;
std::list<flutter::EncodableValue> event_queue_;
Expand All @@ -172,7 +172,7 @@ class AudioRendererSink : public libwebrtc::AudioTrackSink {
messenger, event_channel_name,
&flutter::StandardMethodCodec::GetInstance())),
media_track_(media_track), format_(format) {
task_runner_ = std::make_unique<livekit_client_plugin::TaskRunnerLinux>();
task_runner_ = std::make_shared<livekit_client_plugin::TaskRunnerLinux>();
auto handler = std::make_unique<
flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
[&](const flutter::EncodableValue *arguments,
Expand Down Expand Up @@ -250,7 +250,7 @@ class AudioRendererSink : public libwebrtc::AudioTrackSink {
}
}

std::unique_ptr<livekit_client_plugin::TaskRunnerLinux> task_runner_;
std::shared_ptr<livekit_client_plugin::TaskRunnerLinux> task_runner_;
std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> channel_;
std::shared_ptr<flutter::EventSink<flutter::EncodableValue>> sink_;
bool on_listen_called_ = false;
Expand Down
33 changes: 23 additions & 10 deletions linux/task_runner_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,32 @@ void TaskRunnerLinux::EnqueueTask(TaskClosure task) {

GMainContext* context = g_main_context_default();
if (context) {
g_main_context_invoke(
context,
[](gpointer user_data) -> gboolean {
TaskRunnerLinux* runner = static_cast<TaskRunnerLinux*>(user_data);
std::lock_guard<std::mutex> lock(runner->tasks_mutex_);
while (!runner->tasks_.empty()) {
TaskClosure task = std::move(runner->tasks_.front());
runner->tasks_.pop();
task();
// A weak_ptr (not `this`) is passed through so that if the runner is
// destroyed before the main loop dispatches this callback — e.g. the
// owning sink is torn down while an audio frame's task is still queued —
// the callback safely no-ops instead of locking a mutex inside freed
// memory. g_main_context_invoke_full's notify always runs exactly once,
// whether the callback fired inline or via the idle source, so the
// heap-allocated weak_ptr is never leaked.
auto* weak_self = new std::weak_ptr<TaskRunnerLinux>(weak_from_this());
g_main_context_invoke_full(
context, G_PRIORITY_DEFAULT,
[](gpointer user_data) -> gboolean {
auto* weak_self = static_cast<std::weak_ptr<TaskRunnerLinux>*>(user_data);
if (auto runner = weak_self->lock()) {
std::lock_guard<std::mutex> lock(runner->tasks_mutex_);
while (!runner->tasks_.empty()) {
TaskClosure task = std::move(runner->tasks_.front());
runner->tasks_.pop();
task();
}
}
return G_SOURCE_REMOVE;
},
this);
weak_self,
[](gpointer user_data) {
delete static_cast<std::weak_ptr<TaskRunnerLinux>*>(user_data);
});
}
}

Expand Down
8 changes: 7 additions & 1 deletion linux/task_runner_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@

namespace livekit_client_plugin {

class TaskRunnerLinux {
// Owned via std::shared_ptr by its creator (never std::unique_ptr): EnqueueTask
// schedules a GLib idle callback that runs asynchronously on the main loop, and
// enable_shared_from_this lets that callback hold a weak reference so it can
// detect the runner having been destroyed in the meantime (e.g. stopAudioRenderer
// tearing down the sink while a callback is still queued) instead of dereferencing
// freed memory.
class TaskRunnerLinux : public std::enable_shared_from_this<TaskRunnerLinux> {
public:
TaskRunnerLinux() = default;
~TaskRunnerLinux() = default;
Expand Down
Loading