mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 13:20:44 +01:00

Bug: webrtc:11943 Change-Id: I064a5252ed08e7d06695d03364326fa7f4c562a7 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/331301 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Per Kjellander <perkj@webrtc.org> Cr-Commit-Position: refs/heads/main@{#41385}
853 lines
27 KiB
C++
853 lines
27 KiB
C++
/*
|
|
* Copyright 2004 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 "rtc_base/thread.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "api/field_trials_view.h"
|
|
#include "api/task_queue/task_queue_factory.h"
|
|
#include "api/task_queue/task_queue_test.h"
|
|
#include "api/units/time_delta.h"
|
|
#include "rtc_base/async_udp_socket.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/event.h"
|
|
#include "rtc_base/fake_clock.h"
|
|
#include "rtc_base/gunit.h"
|
|
#include "rtc_base/internal/default_socket_server.h"
|
|
#include "rtc_base/network/received_packet.h"
|
|
#include "rtc_base/null_socket_server.h"
|
|
#include "rtc_base/physical_socket_server.h"
|
|
#include "rtc_base/ref_counted_object.h"
|
|
#include "rtc_base/socket_address.h"
|
|
#include "rtc_base/synchronization/mutex.h"
|
|
#include "rtc_base/third_party/sigslot/sigslot.h"
|
|
#include "test/gmock.h"
|
|
#include "test/testsupport/rtc_expect_death.h"
|
|
|
|
#if defined(WEBRTC_WIN)
|
|
#include <comdef.h> // NOLINT
|
|
|
|
#endif
|
|
|
|
namespace rtc {
|
|
namespace {
|
|
|
|
using ::testing::ElementsAre;
|
|
using ::webrtc::TimeDelta;
|
|
|
|
// Generates a sequence of numbers (collaboratively).
|
|
class TestGenerator {
|
|
public:
|
|
TestGenerator() : last(0), count(0) {}
|
|
|
|
int Next(int prev) {
|
|
int result = prev + last;
|
|
last = result;
|
|
count += 1;
|
|
return result;
|
|
}
|
|
|
|
int last;
|
|
int count;
|
|
};
|
|
|
|
// Receives messages and sends on a socket.
|
|
class MessageClient : public TestGenerator {
|
|
public:
|
|
MessageClient(Thread* pth, Socket* socket) : socket_(socket) {}
|
|
|
|
~MessageClient() { delete socket_; }
|
|
|
|
void OnValue(int value) {
|
|
int result = Next(value);
|
|
EXPECT_GE(socket_->Send(&result, sizeof(result)), 0);
|
|
}
|
|
|
|
private:
|
|
Socket* socket_;
|
|
};
|
|
|
|
// Receives on a socket and sends by posting messages.
|
|
class SocketClient : public TestGenerator, public sigslot::has_slots<> {
|
|
public:
|
|
SocketClient(Socket* socket,
|
|
const SocketAddress& addr,
|
|
Thread* post_thread,
|
|
MessageClient* phandler)
|
|
: socket_(AsyncUDPSocket::Create(socket, addr)),
|
|
post_thread_(post_thread),
|
|
post_handler_(phandler) {
|
|
socket_->RegisterReceivedPacketCallback(
|
|
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
|
|
OnPacket(socket, packet);
|
|
});
|
|
}
|
|
|
|
~SocketClient() override { delete socket_; }
|
|
|
|
SocketAddress address() const { return socket_->GetLocalAddress(); }
|
|
|
|
void OnPacket(AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
|
|
EXPECT_EQ(packet.payload().size(), sizeof(uint32_t));
|
|
uint32_t prev =
|
|
reinterpret_cast<const uint32_t*>(packet.payload().data())[0];
|
|
uint32_t result = Next(prev);
|
|
|
|
post_thread_->PostDelayedTask([post_handler_ = post_handler_,
|
|
result] { post_handler_->OnValue(result); },
|
|
TimeDelta::Millis(200));
|
|
}
|
|
|
|
private:
|
|
AsyncUDPSocket* socket_;
|
|
Thread* post_thread_;
|
|
MessageClient* post_handler_;
|
|
};
|
|
|
|
class CustomThread : public rtc::Thread {
|
|
public:
|
|
CustomThread()
|
|
: Thread(std::unique_ptr<SocketServer>(new rtc::NullSocketServer())) {}
|
|
~CustomThread() override { Stop(); }
|
|
bool Start() { return false; }
|
|
|
|
bool WrapCurrent() { return Thread::WrapCurrent(); }
|
|
void UnwrapCurrent() { Thread::UnwrapCurrent(); }
|
|
};
|
|
|
|
// A thread that does nothing when it runs and signals an event
|
|
// when it is destroyed.
|
|
class SignalWhenDestroyedThread : public Thread {
|
|
public:
|
|
SignalWhenDestroyedThread(Event* event)
|
|
: Thread(std::unique_ptr<SocketServer>(new NullSocketServer())),
|
|
event_(event) {}
|
|
|
|
~SignalWhenDestroyedThread() override {
|
|
Stop();
|
|
event_->Set();
|
|
}
|
|
|
|
void Run() override {
|
|
// Do nothing.
|
|
}
|
|
|
|
private:
|
|
Event* event_;
|
|
};
|
|
|
|
// See: https://code.google.com/p/webrtc/issues/detail?id=2409
|
|
TEST(ThreadTest, DISABLED_Main) {
|
|
rtc::AutoThread main_thread;
|
|
const SocketAddress addr("127.0.0.1", 0);
|
|
|
|
// Create the messaging client on its own thread.
|
|
auto th1 = Thread::CreateWithSocketServer();
|
|
Socket* socket = th1->socketserver()->CreateSocket(addr.family(), SOCK_DGRAM);
|
|
MessageClient msg_client(th1.get(), socket);
|
|
|
|
// Create the socket client on its own thread.
|
|
auto th2 = Thread::CreateWithSocketServer();
|
|
Socket* asocket =
|
|
th2->socketserver()->CreateSocket(addr.family(), SOCK_DGRAM);
|
|
SocketClient sock_client(asocket, addr, th1.get(), &msg_client);
|
|
|
|
socket->Connect(sock_client.address());
|
|
|
|
th1->Start();
|
|
th2->Start();
|
|
|
|
// Get the messages started.
|
|
th1->PostDelayedTask([&msg_client] { msg_client.OnValue(1); },
|
|
TimeDelta::Millis(100));
|
|
|
|
// Give the clients a little while to run.
|
|
// Messages will be processed at 100, 300, 500, 700, 900.
|
|
Thread* th_main = Thread::Current();
|
|
th_main->ProcessMessages(1000);
|
|
|
|
// Stop the sending client. Give the receiver a bit longer to run, in case
|
|
// it is running on a machine that is under load (e.g. the build machine).
|
|
th1->Stop();
|
|
th_main->ProcessMessages(200);
|
|
th2->Stop();
|
|
|
|
// Make sure the results were correct
|
|
EXPECT_EQ(5, msg_client.count);
|
|
EXPECT_EQ(34, msg_client.last);
|
|
EXPECT_EQ(5, sock_client.count);
|
|
EXPECT_EQ(55, sock_client.last);
|
|
}
|
|
|
|
TEST(ThreadTest, CountBlockingCalls) {
|
|
rtc::AutoThread current;
|
|
|
|
// When the test runs, this will print out:
|
|
// (thread_unittest.cc:262): Blocking TestBody: total=2 (actual=1, could=1)
|
|
RTC_LOG_THREAD_BLOCK_COUNT();
|
|
#if RTC_DCHECK_IS_ON
|
|
rtc::Thread::ScopedCountBlockingCalls blocked_calls(
|
|
[&](uint32_t actual_block, uint32_t could_block) {
|
|
EXPECT_EQ(1u, actual_block);
|
|
EXPECT_EQ(1u, could_block);
|
|
});
|
|
|
|
EXPECT_EQ(0u, blocked_calls.GetBlockingCallCount());
|
|
EXPECT_EQ(0u, blocked_calls.GetCouldBeBlockingCallCount());
|
|
EXPECT_EQ(0u, blocked_calls.GetTotalBlockedCallCount());
|
|
|
|
// Test invoking on the current thread. This should not count as an 'actual'
|
|
// invoke, but should still count as an invoke that could block since we
|
|
// that the call to `BlockingCall` serves a purpose in some configurations
|
|
// (and should not be used a general way to call methods on the same thread).
|
|
current.BlockingCall([]() {});
|
|
EXPECT_EQ(0u, blocked_calls.GetBlockingCallCount());
|
|
EXPECT_EQ(1u, blocked_calls.GetCouldBeBlockingCallCount());
|
|
EXPECT_EQ(1u, blocked_calls.GetTotalBlockedCallCount());
|
|
|
|
// Create a new thread to invoke on.
|
|
auto thread = Thread::CreateWithSocketServer();
|
|
thread->Start();
|
|
EXPECT_EQ(42, thread->BlockingCall([]() { return 42; }));
|
|
EXPECT_EQ(1u, blocked_calls.GetBlockingCallCount());
|
|
EXPECT_EQ(1u, blocked_calls.GetCouldBeBlockingCallCount());
|
|
EXPECT_EQ(2u, blocked_calls.GetTotalBlockedCallCount());
|
|
thread->Stop();
|
|
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2);
|
|
#else
|
|
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(0);
|
|
RTC_LOG(LS_INFO) << "Test not active in this config";
|
|
#endif
|
|
}
|
|
|
|
#if RTC_DCHECK_IS_ON
|
|
TEST(ThreadTest, CountBlockingCallsOneCallback) {
|
|
rtc::AutoThread current;
|
|
bool was_called_back = false;
|
|
{
|
|
rtc::Thread::ScopedCountBlockingCalls blocked_calls(
|
|
[&](uint32_t actual_block, uint32_t could_block) {
|
|
was_called_back = true;
|
|
});
|
|
current.BlockingCall([]() {});
|
|
}
|
|
EXPECT_TRUE(was_called_back);
|
|
}
|
|
|
|
TEST(ThreadTest, CountBlockingCallsSkipCallback) {
|
|
rtc::AutoThread current;
|
|
bool was_called_back = false;
|
|
{
|
|
rtc::Thread::ScopedCountBlockingCalls blocked_calls(
|
|
[&](uint32_t actual_block, uint32_t could_block) {
|
|
was_called_back = true;
|
|
});
|
|
// Changed `blocked_calls` to not issue the callback if there are 1 or
|
|
// fewer blocking calls (i.e. we set the minimum required number to 2).
|
|
blocked_calls.set_minimum_call_count_for_callback(2);
|
|
current.BlockingCall([]() {});
|
|
}
|
|
// We should not have gotten a call back.
|
|
EXPECT_FALSE(was_called_back);
|
|
}
|
|
#endif
|
|
|
|
// Test that setting thread names doesn't cause a malfunction.
|
|
// There's no easy way to verify the name was set properly at this time.
|
|
TEST(ThreadTest, Names) {
|
|
// Default name
|
|
auto thread = Thread::CreateWithSocketServer();
|
|
EXPECT_TRUE(thread->Start());
|
|
thread->Stop();
|
|
// Name with no object parameter
|
|
thread = Thread::CreateWithSocketServer();
|
|
EXPECT_TRUE(thread->SetName("No object", nullptr));
|
|
EXPECT_TRUE(thread->Start());
|
|
thread->Stop();
|
|
// Really long name
|
|
thread = Thread::CreateWithSocketServer();
|
|
EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this));
|
|
EXPECT_TRUE(thread->Start());
|
|
thread->Stop();
|
|
}
|
|
|
|
TEST(ThreadTest, Wrap) {
|
|
Thread* current_thread = Thread::Current();
|
|
ThreadManager::Instance()->SetCurrentThread(nullptr);
|
|
|
|
{
|
|
CustomThread cthread;
|
|
EXPECT_TRUE(cthread.WrapCurrent());
|
|
EXPECT_EQ(&cthread, Thread::Current());
|
|
EXPECT_TRUE(cthread.RunningForTest());
|
|
EXPECT_FALSE(cthread.IsOwned());
|
|
cthread.UnwrapCurrent();
|
|
EXPECT_FALSE(cthread.RunningForTest());
|
|
}
|
|
ThreadManager::Instance()->SetCurrentThread(current_thread);
|
|
}
|
|
|
|
#if (!defined(NDEBUG) || RTC_DCHECK_IS_ON)
|
|
TEST(ThreadTest, InvokeToThreadAllowedReturnsTrueWithoutPolicies) {
|
|
rtc::AutoThread main_thread;
|
|
// Create and start the thread.
|
|
auto thread1 = Thread::CreateWithSocketServer();
|
|
auto thread2 = Thread::CreateWithSocketServer();
|
|
|
|
thread1->PostTask(
|
|
[&]() { EXPECT_TRUE(thread1->IsInvokeToThreadAllowed(thread2.get())); });
|
|
main_thread.ProcessMessages(100);
|
|
}
|
|
|
|
TEST(ThreadTest, InvokeAllowedWhenThreadsAdded) {
|
|
rtc::AutoThread main_thread;
|
|
// Create and start the thread.
|
|
auto thread1 = Thread::CreateWithSocketServer();
|
|
auto thread2 = Thread::CreateWithSocketServer();
|
|
auto thread3 = Thread::CreateWithSocketServer();
|
|
auto thread4 = Thread::CreateWithSocketServer();
|
|
|
|
thread1->AllowInvokesToThread(thread2.get());
|
|
thread1->AllowInvokesToThread(thread3.get());
|
|
|
|
thread1->PostTask([&]() {
|
|
EXPECT_TRUE(thread1->IsInvokeToThreadAllowed(thread2.get()));
|
|
EXPECT_TRUE(thread1->IsInvokeToThreadAllowed(thread3.get()));
|
|
EXPECT_FALSE(thread1->IsInvokeToThreadAllowed(thread4.get()));
|
|
});
|
|
main_thread.ProcessMessages(100);
|
|
}
|
|
|
|
TEST(ThreadTest, InvokesDisallowedWhenDisallowAllInvokes) {
|
|
rtc::AutoThread main_thread;
|
|
// Create and start the thread.
|
|
auto thread1 = Thread::CreateWithSocketServer();
|
|
auto thread2 = Thread::CreateWithSocketServer();
|
|
|
|
thread1->DisallowAllInvokes();
|
|
|
|
thread1->PostTask(
|
|
[&]() { EXPECT_FALSE(thread1->IsInvokeToThreadAllowed(thread2.get())); });
|
|
main_thread.ProcessMessages(100);
|
|
}
|
|
#endif // (!defined(NDEBUG) || RTC_DCHECK_IS_ON)
|
|
|
|
TEST(ThreadTest, InvokesAllowedByDefault) {
|
|
rtc::AutoThread main_thread;
|
|
// Create and start the thread.
|
|
auto thread1 = Thread::CreateWithSocketServer();
|
|
auto thread2 = Thread::CreateWithSocketServer();
|
|
|
|
thread1->PostTask(
|
|
[&]() { EXPECT_TRUE(thread1->IsInvokeToThreadAllowed(thread2.get())); });
|
|
main_thread.ProcessMessages(100);
|
|
}
|
|
|
|
TEST(ThreadTest, BlockingCall) {
|
|
// Create and start the thread.
|
|
auto thread = Thread::CreateWithSocketServer();
|
|
thread->Start();
|
|
// Try calling functors.
|
|
EXPECT_EQ(42, thread->BlockingCall([] { return 42; }));
|
|
bool called = false;
|
|
thread->BlockingCall([&] { called = true; });
|
|
EXPECT_TRUE(called);
|
|
|
|
// Try calling bare functions.
|
|
struct LocalFuncs {
|
|
static int Func1() { return 999; }
|
|
static void Func2() {}
|
|
};
|
|
EXPECT_EQ(999, thread->BlockingCall(&LocalFuncs::Func1));
|
|
thread->BlockingCall(&LocalFuncs::Func2);
|
|
}
|
|
|
|
// Verifies that two threads calling Invoke on each other at the same time does
|
|
// not deadlock but crash.
|
|
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
|
TEST(ThreadTest, TwoThreadsInvokeDeathTest) {
|
|
GTEST_FLAG_SET(death_test_style, "threadsafe");
|
|
AutoThread thread;
|
|
Thread* main_thread = Thread::Current();
|
|
auto other_thread = Thread::CreateWithSocketServer();
|
|
other_thread->Start();
|
|
other_thread->BlockingCall([main_thread] {
|
|
RTC_EXPECT_DEATH(main_thread->BlockingCall([] {}), "loop");
|
|
});
|
|
}
|
|
|
|
TEST(ThreadTest, ThreeThreadsInvokeDeathTest) {
|
|
GTEST_FLAG_SET(death_test_style, "threadsafe");
|
|
AutoThread thread;
|
|
Thread* first = Thread::Current();
|
|
|
|
auto second = Thread::Create();
|
|
second->Start();
|
|
auto third = Thread::Create();
|
|
third->Start();
|
|
|
|
second->BlockingCall([&] {
|
|
third->BlockingCall(
|
|
[&] { RTC_EXPECT_DEATH(first->BlockingCall([] {}), "loop"); });
|
|
});
|
|
}
|
|
|
|
#endif
|
|
|
|
// Verifies that if thread A invokes a call on thread B and thread C is trying
|
|
// to invoke A at the same time, thread A does not handle C's invoke while
|
|
// invoking B.
|
|
TEST(ThreadTest, ThreeThreadsBlockingCall) {
|
|
AutoThread thread;
|
|
Thread* thread_a = Thread::Current();
|
|
auto thread_b = Thread::CreateWithSocketServer();
|
|
auto thread_c = Thread::CreateWithSocketServer();
|
|
thread_b->Start();
|
|
thread_c->Start();
|
|
|
|
class LockedBool {
|
|
public:
|
|
explicit LockedBool(bool value) : value_(value) {}
|
|
|
|
void Set(bool value) {
|
|
webrtc::MutexLock lock(&mutex_);
|
|
value_ = value;
|
|
}
|
|
|
|
bool Get() {
|
|
webrtc::MutexLock lock(&mutex_);
|
|
return value_;
|
|
}
|
|
|
|
private:
|
|
webrtc::Mutex mutex_;
|
|
bool value_ RTC_GUARDED_BY(mutex_);
|
|
};
|
|
|
|
struct LocalFuncs {
|
|
static void Set(LockedBool* out) { out->Set(true); }
|
|
static void InvokeSet(Thread* thread, LockedBool* out) {
|
|
thread->BlockingCall([out] { Set(out); });
|
|
}
|
|
|
|
// Set `out` true and call InvokeSet on `thread`.
|
|
static void SetAndInvokeSet(LockedBool* out,
|
|
Thread* thread,
|
|
LockedBool* out_inner) {
|
|
out->Set(true);
|
|
InvokeSet(thread, out_inner);
|
|
}
|
|
|
|
// Asynchronously invoke SetAndInvokeSet on `thread1` and wait until
|
|
// `thread1` starts the call.
|
|
static void AsyncInvokeSetAndWait(Thread* thread1,
|
|
Thread* thread2,
|
|
LockedBool* out) {
|
|
LockedBool async_invoked(false);
|
|
|
|
thread1->PostTask([&async_invoked, thread2, out] {
|
|
SetAndInvokeSet(&async_invoked, thread2, out);
|
|
});
|
|
|
|
EXPECT_TRUE_WAIT(async_invoked.Get(), 2000);
|
|
}
|
|
};
|
|
|
|
LockedBool thread_a_called(false);
|
|
|
|
// Start the sequence A --(invoke)--> B --(async invoke)--> C --(invoke)--> A.
|
|
// Thread B returns when C receives the call and C should be blocked until A
|
|
// starts to process messages.
|
|
Thread* thread_c_ptr = thread_c.get();
|
|
thread_b->BlockingCall([thread_c_ptr, thread_a, &thread_a_called] {
|
|
LocalFuncs::AsyncInvokeSetAndWait(thread_c_ptr, thread_a, &thread_a_called);
|
|
});
|
|
EXPECT_FALSE(thread_a_called.Get());
|
|
|
|
EXPECT_TRUE_WAIT(thread_a_called.Get(), 2000);
|
|
}
|
|
|
|
static void DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(
|
|
FakeClock& clock,
|
|
Thread& q) {
|
|
std::vector<int> run_order;
|
|
|
|
Event done;
|
|
int64_t now = TimeMillis();
|
|
q.PostDelayedTask([&] { run_order.push_back(3); }, TimeDelta::Millis(3));
|
|
q.PostDelayedTask([&] { run_order.push_back(0); }, TimeDelta::Millis(1));
|
|
q.PostDelayedTask([&] { run_order.push_back(1); }, TimeDelta::Millis(2));
|
|
q.PostDelayedTask([&] { run_order.push_back(4); }, TimeDelta::Millis(3));
|
|
q.PostDelayedTask([&] { run_order.push_back(2); }, TimeDelta::Millis(2));
|
|
q.PostDelayedTask([&] { done.Set(); }, TimeDelta::Millis(4));
|
|
// Validate time was frozen while tasks were posted.
|
|
RTC_DCHECK_EQ(TimeMillis(), now);
|
|
|
|
// Change time to make all tasks ready to run and wait for them.
|
|
clock.AdvanceTime(TimeDelta::Millis(4));
|
|
ASSERT_TRUE(done.Wait(TimeDelta::Seconds(1)));
|
|
|
|
EXPECT_THAT(run_order, ElementsAre(0, 1, 2, 3, 4));
|
|
}
|
|
|
|
TEST(ThreadTest, DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) {
|
|
ScopedBaseFakeClock clock;
|
|
Thread q(CreateDefaultSocketServer(), true);
|
|
q.Start();
|
|
DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(clock, q);
|
|
|
|
NullSocketServer nullss;
|
|
Thread q_nullss(&nullss, true);
|
|
q_nullss.Start();
|
|
DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(clock, q_nullss);
|
|
}
|
|
|
|
// Ensure that ProcessAllMessageQueues does its essential function; process
|
|
// all messages (both delayed and non delayed) up until the current time, on
|
|
// all registered message queues.
|
|
TEST(ThreadManager, ProcessAllMessageQueues) {
|
|
rtc::AutoThread main_thread;
|
|
Event entered_process_all_message_queues(true, false);
|
|
auto a = Thread::CreateWithSocketServer();
|
|
auto b = Thread::CreateWithSocketServer();
|
|
a->Start();
|
|
b->Start();
|
|
|
|
std::atomic<int> messages_processed(0);
|
|
auto incrementer = [&messages_processed,
|
|
&entered_process_all_message_queues] {
|
|
// Wait for event as a means to ensure Increment doesn't occur outside
|
|
// of ProcessAllMessageQueues. The event is set by a message posted to
|
|
// the main thread, which is guaranteed to be handled inside
|
|
// ProcessAllMessageQueues.
|
|
entered_process_all_message_queues.Wait(Event::kForever);
|
|
messages_processed.fetch_add(1);
|
|
};
|
|
auto event_signaler = [&entered_process_all_message_queues] {
|
|
entered_process_all_message_queues.Set();
|
|
};
|
|
|
|
// Post messages (both delayed and non delayed) to both threads.
|
|
a->PostTask(incrementer);
|
|
b->PostTask(incrementer);
|
|
a->PostDelayedTask(incrementer, TimeDelta::Zero());
|
|
b->PostDelayedTask(incrementer, TimeDelta::Zero());
|
|
main_thread.PostTask(event_signaler);
|
|
|
|
ThreadManager::ProcessAllMessageQueuesForTesting();
|
|
EXPECT_EQ(4, messages_processed.load(std::memory_order_acquire));
|
|
}
|
|
|
|
// Test that ProcessAllMessageQueues doesn't hang if a thread is quitting.
|
|
TEST(ThreadManager, ProcessAllMessageQueuesWithQuittingThread) {
|
|
auto t = Thread::CreateWithSocketServer();
|
|
t->Start();
|
|
t->Quit();
|
|
ThreadManager::ProcessAllMessageQueuesForTesting();
|
|
}
|
|
|
|
void WaitAndSetEvent(Event* wait_event, Event* set_event) {
|
|
wait_event->Wait(Event::kForever);
|
|
set_event->Set();
|
|
}
|
|
|
|
// A functor that keeps track of the number of copies and moves.
|
|
class LifeCycleFunctor {
|
|
public:
|
|
struct Stats {
|
|
size_t copy_count = 0;
|
|
size_t move_count = 0;
|
|
};
|
|
|
|
LifeCycleFunctor(Stats* stats, Event* event) : stats_(stats), event_(event) {}
|
|
LifeCycleFunctor(const LifeCycleFunctor& other) { *this = other; }
|
|
LifeCycleFunctor(LifeCycleFunctor&& other) { *this = std::move(other); }
|
|
|
|
LifeCycleFunctor& operator=(const LifeCycleFunctor& other) {
|
|
stats_ = other.stats_;
|
|
event_ = other.event_;
|
|
++stats_->copy_count;
|
|
return *this;
|
|
}
|
|
|
|
LifeCycleFunctor& operator=(LifeCycleFunctor&& other) {
|
|
stats_ = other.stats_;
|
|
event_ = other.event_;
|
|
++stats_->move_count;
|
|
return *this;
|
|
}
|
|
|
|
void operator()() { event_->Set(); }
|
|
|
|
private:
|
|
Stats* stats_;
|
|
Event* event_;
|
|
};
|
|
|
|
// A functor that verifies the thread it was destroyed on.
|
|
class DestructionFunctor {
|
|
public:
|
|
DestructionFunctor(Thread* thread, bool* thread_was_current, Event* event)
|
|
: thread_(thread),
|
|
thread_was_current_(thread_was_current),
|
|
event_(event) {}
|
|
~DestructionFunctor() {
|
|
// Only signal the event if this was the functor that was invoked to avoid
|
|
// the event being signaled due to the destruction of temporary/moved
|
|
// versions of this object.
|
|
if (was_invoked_) {
|
|
*thread_was_current_ = thread_->IsCurrent();
|
|
event_->Set();
|
|
}
|
|
}
|
|
|
|
void operator()() { was_invoked_ = true; }
|
|
|
|
private:
|
|
Thread* thread_;
|
|
bool* thread_was_current_;
|
|
Event* event_;
|
|
bool was_invoked_ = false;
|
|
};
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithLambda) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event event;
|
|
background_thread->PostTask([&event] { event.Set(); });
|
|
event.Wait(Event::kForever);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithCopiedFunctor) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
LifeCycleFunctor::Stats stats;
|
|
Event event;
|
|
LifeCycleFunctor functor(&stats, &event);
|
|
background_thread->PostTask(functor);
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_EQ(1u, stats.copy_count);
|
|
EXPECT_EQ(0u, stats.move_count);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithMovedFunctor) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
LifeCycleFunctor::Stats stats;
|
|
Event event;
|
|
LifeCycleFunctor functor(&stats, &event);
|
|
background_thread->PostTask(std::move(functor));
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_EQ(0u, stats.copy_count);
|
|
EXPECT_EQ(1u, stats.move_count);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithReferencedFunctorShouldCopy) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
LifeCycleFunctor::Stats stats;
|
|
Event event;
|
|
LifeCycleFunctor functor(&stats, &event);
|
|
LifeCycleFunctor& functor_ref = functor;
|
|
background_thread->PostTask(functor_ref);
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_EQ(1u, stats.copy_count);
|
|
EXPECT_EQ(0u, stats.move_count);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithCopiedFunctorDestroyedOnTargetThread) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event event;
|
|
bool was_invoked_on_background_thread = false;
|
|
DestructionFunctor functor(background_thread.get(),
|
|
&was_invoked_on_background_thread, &event);
|
|
background_thread->PostTask(functor);
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_TRUE(was_invoked_on_background_thread);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesWithMovedFunctorDestroyedOnTargetThread) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event event;
|
|
bool was_invoked_on_background_thread = false;
|
|
DestructionFunctor functor(background_thread.get(),
|
|
&was_invoked_on_background_thread, &event);
|
|
background_thread->PostTask(std::move(functor));
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_TRUE(was_invoked_on_background_thread);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest,
|
|
InvokesWithReferencedFunctorShouldCopyAndDestroyedOnTargetThread) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event event;
|
|
bool was_invoked_on_background_thread = false;
|
|
DestructionFunctor functor(background_thread.get(),
|
|
&was_invoked_on_background_thread, &event);
|
|
DestructionFunctor& functor_ref = functor;
|
|
background_thread->PostTask(functor_ref);
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_TRUE(was_invoked_on_background_thread);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesOnBackgroundThread) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event event;
|
|
bool was_invoked_on_background_thread = false;
|
|
Thread* background_thread_ptr = background_thread.get();
|
|
background_thread->PostTask(
|
|
[background_thread_ptr, &was_invoked_on_background_thread, &event] {
|
|
was_invoked_on_background_thread = background_thread_ptr->IsCurrent();
|
|
event.Set();
|
|
});
|
|
event.Wait(Event::kForever);
|
|
|
|
EXPECT_TRUE(was_invoked_on_background_thread);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesAsynchronously) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
// The first event ensures that SendSingleMessage() is not blocking this
|
|
// thread. The second event ensures that the message is processed.
|
|
Event event_set_by_test_thread;
|
|
Event event_set_by_background_thread;
|
|
background_thread->PostTask([&event_set_by_test_thread,
|
|
&event_set_by_background_thread] {
|
|
WaitAndSetEvent(&event_set_by_test_thread, &event_set_by_background_thread);
|
|
});
|
|
event_set_by_test_thread.Set();
|
|
event_set_by_background_thread.Wait(Event::kForever);
|
|
}
|
|
|
|
TEST(ThreadPostTaskTest, InvokesInPostedOrder) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event first;
|
|
Event second;
|
|
Event third;
|
|
Event fourth;
|
|
|
|
background_thread->PostTask(
|
|
[&first, &second] { WaitAndSetEvent(&first, &second); });
|
|
background_thread->PostTask(
|
|
[&second, &third] { WaitAndSetEvent(&second, &third); });
|
|
background_thread->PostTask(
|
|
[&third, &fourth] { WaitAndSetEvent(&third, &fourth); });
|
|
|
|
// All tasks have been posted before the first one is unblocked.
|
|
first.Set();
|
|
// Only if the chain is invoked in posted order will the last event be set.
|
|
fourth.Wait(Event::kForever);
|
|
}
|
|
|
|
TEST(ThreadPostDelayedTaskTest, InvokesAsynchronously) {
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
// The first event ensures that SendSingleMessage() is not blocking this
|
|
// thread. The second event ensures that the message is processed.
|
|
Event event_set_by_test_thread;
|
|
Event event_set_by_background_thread;
|
|
background_thread->PostDelayedTask(
|
|
[&event_set_by_test_thread, &event_set_by_background_thread] {
|
|
WaitAndSetEvent(&event_set_by_test_thread,
|
|
&event_set_by_background_thread);
|
|
},
|
|
TimeDelta::Millis(10));
|
|
event_set_by_test_thread.Set();
|
|
event_set_by_background_thread.Wait(Event::kForever);
|
|
}
|
|
|
|
TEST(ThreadPostDelayedTaskTest, InvokesInDelayOrder) {
|
|
ScopedFakeClock clock;
|
|
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
|
|
background_thread->Start();
|
|
|
|
Event first;
|
|
Event second;
|
|
Event third;
|
|
Event fourth;
|
|
|
|
background_thread->PostDelayedTask(
|
|
[&third, &fourth] { WaitAndSetEvent(&third, &fourth); },
|
|
TimeDelta::Millis(11));
|
|
background_thread->PostDelayedTask(
|
|
[&first, &second] { WaitAndSetEvent(&first, &second); },
|
|
TimeDelta::Millis(9));
|
|
background_thread->PostDelayedTask(
|
|
[&second, &third] { WaitAndSetEvent(&second, &third); },
|
|
TimeDelta::Millis(10));
|
|
|
|
// All tasks have been posted before the first one is unblocked.
|
|
first.Set();
|
|
// Only if the chain is invoked in delay order will the last event be set.
|
|
clock.AdvanceTime(TimeDelta::Millis(11));
|
|
EXPECT_TRUE(fourth.Wait(TimeDelta::Zero()));
|
|
}
|
|
|
|
TEST(ThreadPostDelayedTaskTest, IsCurrentTaskQueue) {
|
|
auto current_tq = webrtc::TaskQueueBase::Current();
|
|
{
|
|
std::unique_ptr<rtc::Thread> thread(rtc::Thread::Create());
|
|
thread->WrapCurrent();
|
|
EXPECT_EQ(webrtc::TaskQueueBase::Current(),
|
|
static_cast<webrtc::TaskQueueBase*>(thread.get()));
|
|
thread->UnwrapCurrent();
|
|
}
|
|
EXPECT_EQ(webrtc::TaskQueueBase::Current(), current_tq);
|
|
}
|
|
|
|
class ThreadFactory : public webrtc::TaskQueueFactory {
|
|
public:
|
|
std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
|
|
CreateTaskQueue(absl::string_view /* name */,
|
|
Priority /*priority*/) const override {
|
|
std::unique_ptr<Thread> thread = Thread::Create();
|
|
thread->Start();
|
|
return std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>(
|
|
thread.release());
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<webrtc::TaskQueueFactory> CreateDefaultThreadFactory(
|
|
const webrtc::FieldTrialsView*) {
|
|
return std::make_unique<ThreadFactory>();
|
|
}
|
|
|
|
using ::webrtc::TaskQueueTest;
|
|
|
|
INSTANTIATE_TEST_SUITE_P(RtcThread,
|
|
TaskQueueTest,
|
|
::testing::Values(CreateDefaultThreadFactory));
|
|
|
|
} // namespace
|
|
} // namespace rtc
|