webrtc/talk/base/signalthread_unittest.cc
fischman@webrtc.org e5063b1733 Thread: delete racy API (Release()) and fix racy code (started()).
- Thread::Release() wrote a local variable on the calling thread but read it on
  another thread, with no synchronization.  Happily it has no non-test callers
  so deleting it instead of trying to fix it (see bug for details).
- Thread::started_ similarly was racily being written to; replaced with a
  running_ Event, and hid the accessor except for tests & legacy callers,
  with a note about why it's a bad idea.

webrtc/base patched with:
git diff origin --relative=talk/base | patch -p1 -dwebrtc/base
followed by manual merge of 3 thunks that ran afoul of naming differences
between talk/base and webrtc/base.

BUG=3388
R=andrew@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/14589005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6236 4adac7df-926f-26a2-2b94-8c16560cd09d
2014-05-23 17:28:50 +00:00

216 lines
6.5 KiB
C++

/*
* libjingle
* Copyright 2004--2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/base/gunit.h"
#include "talk/base/signalthread.h"
#include "talk/base/thread.h"
using namespace talk_base;
class SignalThreadTest : public testing::Test, public sigslot::has_slots<> {
public:
class SlowSignalThread : public SignalThread {
public:
SlowSignalThread(SignalThreadTest* harness) : harness_(harness) {
}
virtual ~SlowSignalThread() {
EXPECT_EQ(harness_->main_thread_, Thread::Current());
++harness_->thread_deleted_;
}
const SignalThreadTest* harness() { return harness_; }
protected:
virtual void OnWorkStart() {
ASSERT_TRUE(harness_ != NULL);
++harness_->thread_started_;
EXPECT_EQ(harness_->main_thread_, Thread::Current());
EXPECT_FALSE(worker()->RunningForTest()); // not started yet
}
virtual void OnWorkStop() {
++harness_->thread_stopped_;
EXPECT_EQ(harness_->main_thread_, Thread::Current());
EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet
}
virtual void OnWorkDone() {
++harness_->thread_done_;
EXPECT_EQ(harness_->main_thread_, Thread::Current());
EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet
}
virtual void DoWork() {
EXPECT_NE(harness_->main_thread_, Thread::Current());
EXPECT_EQ(worker(), Thread::Current());
Thread::Current()->socketserver()->Wait(250, false);
}
private:
SignalThreadTest* harness_;
DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread);
};
void OnWorkComplete(talk_base::SignalThread* thread) {
SlowSignalThread* t = static_cast<SlowSignalThread*>(thread);
EXPECT_EQ(t->harness(), this);
EXPECT_EQ(main_thread_, Thread::Current());
++thread_completed_;
if (!called_release_) {
thread->Release();
}
}
virtual void SetUp() {
main_thread_ = Thread::Current();
thread_ = new SlowSignalThread(this);
thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete);
called_release_ = false;
thread_started_ = 0;
thread_done_ = 0;
thread_completed_ = 0;
thread_stopped_ = 0;
thread_deleted_ = 0;
}
virtual void TearDown() {
}
Thread* main_thread_;
SlowSignalThread* thread_;
bool called_release_;
int thread_started_;
int thread_done_;
int thread_completed_;
int thread_stopped_;
int thread_deleted_;
};
class OwnerThread : public Thread, public sigslot::has_slots<> {
public:
explicit OwnerThread(SignalThreadTest* harness)
: harness_(harness),
has_run_(false) {
}
virtual ~OwnerThread() {
Stop();
}
virtual void Run() {
SignalThreadTest::SlowSignalThread* signal_thread =
new SignalThreadTest::SlowSignalThread(harness_);
signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone);
signal_thread->Start();
Thread::Current()->socketserver()->Wait(100, false);
signal_thread->Release();
// Delete |signal_thread|.
signal_thread->Destroy(true);
has_run_ = true;
}
bool has_run() { return has_run_; }
void OnWorkDone(SignalThread* signal_thread) {
FAIL() << " This shouldn't get called.";
}
private:
SignalThreadTest* harness_;
bool has_run_;
DISALLOW_EVIL_CONSTRUCTORS(OwnerThread);
};
// Test for when the main thread goes away while the
// signal thread is still working. This may happen
// when shutting down the process.
TEST_F(SignalThreadTest, OwnerThreadGoesAway) {
{
scoped_ptr<OwnerThread> owner(new OwnerThread(this));
main_thread_ = owner.get();
owner->Start();
while (!owner->has_run()) {
Thread::Current()->socketserver()->Wait(10, false);
}
}
// At this point the main thread has gone away.
// Give the SignalThread a little time to do its callback,
// which will crash if the signal thread doesn't handle
// this situation well.
Thread::Current()->socketserver()->Wait(500, false);
}
#define EXPECT_STATE(started, done, completed, stopped, deleted) \
EXPECT_EQ(started, thread_started_); \
EXPECT_EQ(done, thread_done_); \
EXPECT_EQ(completed, thread_completed_); \
EXPECT_EQ(stopped, thread_stopped_); \
EXPECT_EQ(deleted, thread_deleted_);
TEST_F(SignalThreadTest, ThreadFinishes) {
thread_->Start();
EXPECT_STATE(1, 0, 0, 0, 0);
Thread::SleepMs(500);
EXPECT_STATE(1, 0, 0, 0, 0);
Thread::Current()->ProcessMessages(0);
EXPECT_STATE(1, 1, 1, 0, 1);
}
TEST_F(SignalThreadTest, ReleasedThreadFinishes) {
thread_->Start();
EXPECT_STATE(1, 0, 0, 0, 0);
thread_->Release();
called_release_ = true;
EXPECT_STATE(1, 0, 0, 0, 0);
Thread::SleepMs(500);
EXPECT_STATE(1, 0, 0, 0, 0);
Thread::Current()->ProcessMessages(0);
EXPECT_STATE(1, 1, 1, 0, 1);
}
TEST_F(SignalThreadTest, DestroyedThreadCleansUp) {
thread_->Start();
EXPECT_STATE(1, 0, 0, 0, 0);
thread_->Destroy(true);
EXPECT_STATE(1, 0, 0, 1, 1);
Thread::Current()->ProcessMessages(0);
EXPECT_STATE(1, 0, 0, 1, 1);
}
TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) {
thread_->Start();
EXPECT_STATE(1, 0, 0, 0, 0);
thread_->Destroy(false);
EXPECT_STATE(1, 0, 0, 1, 0);
Thread::SleepMs(500);
EXPECT_STATE(1, 0, 0, 1, 0);
Thread::Current()->ProcessMessages(0);
EXPECT_STATE(1, 1, 0, 1, 1);
}