Fix a data race in the thread unit tests.
The flag used in thread_unittest.cc:FunctorB is subject to a (mostly harmless) data race. In a tsan build, reproduce using out/Release/rtc_unittests --gtest_filter=AsyncInvokeTest.FireAndForget There are additional tsan warnings, not all deterministic, when running all the rtc_unittets: Some data races related to destructors, and a locking-order-inversion warning. Hence applying this patch does not make the unit tests tsan-clean. I should also add that this is my very first cl, so I'm not at all familiar with the process. Review URL: https://codereview.webrtc.org/1439613004 Cr-Commit-Position: refs/heads/master@{#10645}
This commit is contained in:
@ -137,16 +137,48 @@ class SignalWhenDestroyedThread : public Thread {
|
|||||||
Event* event_;
|
Event* event_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A bool wrapped in a mutex, to avoid data races. Using a volatile
|
||||||
|
// bool should be sufficient for correct code ("eventual consistency"
|
||||||
|
// between caches is sufficient), but we can't tell the compiler about
|
||||||
|
// that, and then tsan complains about a data race.
|
||||||
|
|
||||||
|
// See also discussion at
|
||||||
|
// http://stackoverflow.com/questions/7223164/is-mutex-needed-to-synchronize-a-simple-flag-between-pthreads
|
||||||
|
|
||||||
|
// Using std::atomic<bool> or std::atomic_flag in C++11 is probably
|
||||||
|
// the right thing to do, but those features are not yet allowed. Or
|
||||||
|
// rtc::AtomicInt, if/when that is added. Since the use isn't
|
||||||
|
// performance critical, use a plain critical section for the time
|
||||||
|
// being.
|
||||||
|
|
||||||
|
class AtomicBool {
|
||||||
|
public:
|
||||||
|
explicit AtomicBool(bool value = false) : flag_(value) {}
|
||||||
|
AtomicBool& operator=(bool value) {
|
||||||
|
CritScope scoped_lock(&cs_);
|
||||||
|
flag_ = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
bool get() const {
|
||||||
|
CritScope scoped_lock(&cs_);
|
||||||
|
return flag_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable CriticalSection cs_;
|
||||||
|
bool flag_;
|
||||||
|
};
|
||||||
|
|
||||||
// Function objects to test Thread::Invoke.
|
// Function objects to test Thread::Invoke.
|
||||||
struct FunctorA {
|
struct FunctorA {
|
||||||
int operator()() { return 42; }
|
int operator()() { return 42; }
|
||||||
};
|
};
|
||||||
class FunctorB {
|
class FunctorB {
|
||||||
public:
|
public:
|
||||||
explicit FunctorB(bool* flag) : flag_(flag) {}
|
explicit FunctorB(AtomicBool* flag) : flag_(flag) {}
|
||||||
void operator()() { if (flag_) *flag_ = true; }
|
void operator()() { if (flag_) *flag_ = true; }
|
||||||
private:
|
private:
|
||||||
bool* flag_;
|
AtomicBool* flag_;
|
||||||
};
|
};
|
||||||
struct FunctorC {
|
struct FunctorC {
|
||||||
int operator()() {
|
int operator()() {
|
||||||
@ -266,10 +298,10 @@ TEST(ThreadTest, Invoke) {
|
|||||||
thread.Start();
|
thread.Start();
|
||||||
// Try calling functors.
|
// Try calling functors.
|
||||||
EXPECT_EQ(42, thread.Invoke<int>(FunctorA()));
|
EXPECT_EQ(42, thread.Invoke<int>(FunctorA()));
|
||||||
bool called = false;
|
AtomicBool called;
|
||||||
FunctorB f2(&called);
|
FunctorB f2(&called);
|
||||||
thread.Invoke<void>(f2);
|
thread.Invoke<void>(f2);
|
||||||
EXPECT_TRUE(called);
|
EXPECT_TRUE(called.get());
|
||||||
// Try calling bare functions.
|
// Try calling bare functions.
|
||||||
struct LocalFuncs {
|
struct LocalFuncs {
|
||||||
static int Func1() { return 999; }
|
static int Func1() { return 999; }
|
||||||
@ -408,9 +440,9 @@ TEST_F(AsyncInvokeTest, FireAndForget) {
|
|||||||
Thread thread;
|
Thread thread;
|
||||||
thread.Start();
|
thread.Start();
|
||||||
// Try calling functor.
|
// Try calling functor.
|
||||||
bool called = false;
|
AtomicBool called;
|
||||||
invoker.AsyncInvoke<void>(&thread, FunctorB(&called));
|
invoker.AsyncInvoke<void>(&thread, FunctorB(&called));
|
||||||
EXPECT_TRUE_WAIT(called, kWaitTimeout);
|
EXPECT_TRUE_WAIT(called.get(), kWaitTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(AsyncInvokeTest, WithCallback) {
|
TEST_F(AsyncInvokeTest, WithCallback) {
|
||||||
@ -478,26 +510,26 @@ TEST_F(AsyncInvokeTest, KillInvokerBeforeExecute) {
|
|||||||
|
|
||||||
TEST_F(AsyncInvokeTest, Flush) {
|
TEST_F(AsyncInvokeTest, Flush) {
|
||||||
AsyncInvoker invoker;
|
AsyncInvoker invoker;
|
||||||
bool flag1 = false;
|
AtomicBool flag1;
|
||||||
bool flag2 = false;
|
AtomicBool flag2;
|
||||||
// Queue two async calls to the current thread.
|
// Queue two async calls to the current thread.
|
||||||
invoker.AsyncInvoke<void>(Thread::Current(),
|
invoker.AsyncInvoke<void>(Thread::Current(),
|
||||||
FunctorB(&flag1));
|
FunctorB(&flag1));
|
||||||
invoker.AsyncInvoke<void>(Thread::Current(),
|
invoker.AsyncInvoke<void>(Thread::Current(),
|
||||||
FunctorB(&flag2));
|
FunctorB(&flag2));
|
||||||
// Because we haven't pumped messages, these should not have run yet.
|
// Because we haven't pumped messages, these should not have run yet.
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
// Force them to run now.
|
// Force them to run now.
|
||||||
invoker.Flush(Thread::Current());
|
invoker.Flush(Thread::Current());
|
||||||
EXPECT_TRUE(flag1);
|
EXPECT_TRUE(flag1.get());
|
||||||
EXPECT_TRUE(flag2);
|
EXPECT_TRUE(flag2.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(AsyncInvokeTest, FlushWithIds) {
|
TEST_F(AsyncInvokeTest, FlushWithIds) {
|
||||||
AsyncInvoker invoker;
|
AsyncInvoker invoker;
|
||||||
bool flag1 = false;
|
AtomicBool flag1;
|
||||||
bool flag2 = false;
|
AtomicBool flag2;
|
||||||
// Queue two async calls to the current thread, one with a message id.
|
// Queue two async calls to the current thread, one with a message id.
|
||||||
invoker.AsyncInvoke<void>(Thread::Current(),
|
invoker.AsyncInvoke<void>(Thread::Current(),
|
||||||
FunctorB(&flag1),
|
FunctorB(&flag1),
|
||||||
@ -505,17 +537,17 @@ TEST_F(AsyncInvokeTest, FlushWithIds) {
|
|||||||
invoker.AsyncInvoke<void>(Thread::Current(),
|
invoker.AsyncInvoke<void>(Thread::Current(),
|
||||||
FunctorB(&flag2));
|
FunctorB(&flag2));
|
||||||
// Because we haven't pumped messages, these should not have run yet.
|
// Because we haven't pumped messages, these should not have run yet.
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
// Execute pending calls with id == 5.
|
// Execute pending calls with id == 5.
|
||||||
invoker.Flush(Thread::Current(), 5);
|
invoker.Flush(Thread::Current(), 5);
|
||||||
EXPECT_TRUE(flag1);
|
EXPECT_TRUE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
flag1 = false;
|
flag1 = false;
|
||||||
// Execute all pending calls. The id == 5 call should not execute again.
|
// Execute all pending calls. The id == 5 call should not execute again.
|
||||||
invoker.Flush(Thread::Current());
|
invoker.Flush(Thread::Current());
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_TRUE(flag2);
|
EXPECT_TRUE(flag2.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
class GuardedAsyncInvokeTest : public testing::Test {
|
class GuardedAsyncInvokeTest : public testing::Test {
|
||||||
@ -564,11 +596,11 @@ TEST_F(GuardedAsyncInvokeTest, KillThreadFireAndForget) {
|
|||||||
// Kill |thread|.
|
// Kill |thread|.
|
||||||
thread = nullptr;
|
thread = nullptr;
|
||||||
// Try calling functor.
|
// Try calling functor.
|
||||||
bool called = false;
|
AtomicBool called;
|
||||||
EXPECT_FALSE(invoker->AsyncInvoke<void>(FunctorB(&called)));
|
EXPECT_FALSE(invoker->AsyncInvoke<void>(FunctorB(&called)));
|
||||||
// With thread gone, nothing should happen.
|
// With thread gone, nothing should happen.
|
||||||
WAIT(called, kWaitTimeout);
|
WAIT(called.get(), kWaitTimeout);
|
||||||
EXPECT_FALSE(called);
|
EXPECT_FALSE(called.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that we can call AsyncInvoke with callback after the thread died.
|
// Test that we can call AsyncInvoke with callback after the thread died.
|
||||||
@ -595,9 +627,9 @@ TEST_F(GuardedAsyncInvokeTest, KillThreadWithCallback) {
|
|||||||
TEST_F(GuardedAsyncInvokeTest, FireAndForget) {
|
TEST_F(GuardedAsyncInvokeTest, FireAndForget) {
|
||||||
GuardedAsyncInvoker invoker;
|
GuardedAsyncInvoker invoker;
|
||||||
// Try calling functor.
|
// Try calling functor.
|
||||||
bool called = false;
|
AtomicBool called;
|
||||||
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&called)));
|
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&called)));
|
||||||
EXPECT_TRUE_WAIT(called, kWaitTimeout);
|
EXPECT_TRUE_WAIT(called.get(), kWaitTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GuardedAsyncInvokeTest, WithCallback) {
|
TEST_F(GuardedAsyncInvokeTest, WithCallback) {
|
||||||
@ -660,39 +692,39 @@ TEST_F(GuardedAsyncInvokeTest, KillInvokerBeforeExecute) {
|
|||||||
|
|
||||||
TEST_F(GuardedAsyncInvokeTest, Flush) {
|
TEST_F(GuardedAsyncInvokeTest, Flush) {
|
||||||
GuardedAsyncInvoker invoker;
|
GuardedAsyncInvoker invoker;
|
||||||
bool flag1 = false;
|
AtomicBool flag1;
|
||||||
bool flag2 = false;
|
AtomicBool flag2;
|
||||||
// Queue two async calls to the current thread.
|
// Queue two async calls to the current thread.
|
||||||
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag1)));
|
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag1)));
|
||||||
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag2)));
|
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag2)));
|
||||||
// Because we haven't pumped messages, these should not have run yet.
|
// Because we haven't pumped messages, these should not have run yet.
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
// Force them to run now.
|
// Force them to run now.
|
||||||
EXPECT_TRUE(invoker.Flush());
|
EXPECT_TRUE(invoker.Flush());
|
||||||
EXPECT_TRUE(flag1);
|
EXPECT_TRUE(flag1.get());
|
||||||
EXPECT_TRUE(flag2);
|
EXPECT_TRUE(flag2.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GuardedAsyncInvokeTest, FlushWithIds) {
|
TEST_F(GuardedAsyncInvokeTest, FlushWithIds) {
|
||||||
GuardedAsyncInvoker invoker;
|
GuardedAsyncInvoker invoker;
|
||||||
bool flag1 = false;
|
AtomicBool flag1;
|
||||||
bool flag2 = false;
|
AtomicBool flag2;
|
||||||
// Queue two async calls to the current thread, one with a message id.
|
// Queue two async calls to the current thread, one with a message id.
|
||||||
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag1), 5));
|
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag1), 5));
|
||||||
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag2)));
|
EXPECT_TRUE(invoker.AsyncInvoke<void>(FunctorB(&flag2)));
|
||||||
// Because we haven't pumped messages, these should not have run yet.
|
// Because we haven't pumped messages, these should not have run yet.
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
// Execute pending calls with id == 5.
|
// Execute pending calls with id == 5.
|
||||||
EXPECT_TRUE(invoker.Flush(5));
|
EXPECT_TRUE(invoker.Flush(5));
|
||||||
EXPECT_TRUE(flag1);
|
EXPECT_TRUE(flag1.get());
|
||||||
EXPECT_FALSE(flag2);
|
EXPECT_FALSE(flag2.get());
|
||||||
flag1 = false;
|
flag1 = false;
|
||||||
// Execute all pending calls. The id == 5 call should not execute again.
|
// Execute all pending calls. The id == 5 call should not execute again.
|
||||||
EXPECT_TRUE(invoker.Flush());
|
EXPECT_TRUE(invoker.Flush());
|
||||||
EXPECT_FALSE(flag1);
|
EXPECT_FALSE(flag1.get());
|
||||||
EXPECT_TRUE(flag2);
|
EXPECT_TRUE(flag2.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(WEBRTC_WIN)
|
#if defined(WEBRTC_WIN)
|
||||||
|
Reference in New Issue
Block a user