blob: afe36b708454993b7008ef5f6db3fc53967e29e1 [file]
/*
* Copyright 2026 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/scoped_operations_batcher.h"
#include <memory>
#include <utility>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "api/rtc_error.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
TEST(ScopedOperationsBatcherTest, ExecutesTasksOnTargetThread) {
auto target_thread = Thread::Create();
target_thread->Start();
bool task_executed = false;
bool target_checked = false;
{
ScopedOperationsBatcher batcher(target_thread.get());
batcher.Add([&] {
task_executed = true;
target_checked = target_thread->IsCurrent();
});
}
EXPECT_TRUE(task_executed);
EXPECT_TRUE(target_checked);
}
TEST(ScopedOperationsBatcherTest, ExecutesReturnedTasksOnCallingThread) {
// Use current thread as the calling (signaling) thread. This test runs
// without an explicit RunLoop, because the returned tasks are executed
// synchronously within the caller's context of `Run()` or
// `~ScopedOperationsBatcher()`.
auto signaling_thread = Thread::Current();
auto target_thread = Thread::Create();
target_thread->Start();
bool return_task_executed = false;
Thread* return_task_thread = nullptr;
bool task_executed = false;
Thread* task_thread = nullptr;
{
ScopedOperationsBatcher batcher(target_thread.get());
ScopedOperationsBatcher::BatchTaskWithFinalizer task =
[&]() -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
task_executed = true;
task_thread = Thread::Current();
return ScopedOperationsBatcher::FinalizerTask([&]() {
return_task_executed = true;
return_task_thread = Thread::Current();
});
};
batcher.AddWithFinalizer(std::move(task));
}
EXPECT_TRUE(task_executed);
EXPECT_EQ(task_thread, target_thread.get());
EXPECT_TRUE(return_task_executed);
EXPECT_EQ(return_task_thread, signaling_thread);
}
TEST(ScopedOperationsBatcherTest, YieldsToOtherTasks) {
auto target_thread = Thread::Create();
target_thread->Start();
std::vector<int> execution_order;
{
ScopedOperationsBatcher batcher(target_thread.get());
batcher.Add([&] { execution_order.push_back(1); });
batcher.Add([&] {
execution_order.push_back(2);
// Post a task that should interrupt the batch since we now yield to any
// pending task.
target_thread->PostTask([&] { execution_order.push_back(3); });
});
batcher.Add([&] { execution_order.push_back(4); });
batcher.Add([&] { execution_order.push_back(5); });
}
// Expect the task (3) to execute immediately after the task
// that posted it (2). The operations remaining in the batch (4, 5)
// should resume after the thread has processed the new task.
EXPECT_EQ(execution_order, std::vector<int>({1, 2, 3, 4, 5}));
}
TEST(ScopedOperationsBatcherTest, StopsExecutionOnError) {
auto target_thread = Thread::Create();
target_thread->Start();
bool task1_executed = false;
bool task2_executed = false;
bool task3_executed = false;
// There's no finalizer for task2.
bool finalizer1_executed = false;
bool finalizer3_executed = false;
RTCError error = RTCError::OK();
{
ScopedOperationsBatcher batcher(target_thread.get());
// Task 1.
batcher.AddWithFinalizer(
[&]() -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
task1_executed = true;
return ScopedOperationsBatcher::FinalizerTask(
[&] { finalizer1_executed = true; });
});
// Task 2.
batcher.AddWithFinalizer(
[&]() -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
task2_executed = true;
return RTCError(RTCErrorType::INVALID_STATE, "Failed");
});
// Task 3.
batcher.AddWithFinalizer(
[&]() -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
task3_executed = true;
return ScopedOperationsBatcher::FinalizerTask(
[&] { finalizer3_executed = true; });
});
error = batcher.Run();
}
EXPECT_FALSE(error.ok());
EXPECT_EQ(error.type(), RTCErrorType::INVALID_STATE);
EXPECT_STREQ(error.message(), "Failed");
EXPECT_TRUE(task1_executed);
EXPECT_TRUE(finalizer1_executed);
EXPECT_TRUE(task2_executed);
EXPECT_FALSE(task3_executed);
EXPECT_FALSE(finalizer3_executed);
}
} // namespace
} // namespace webrtc