Skip to content

Instantly share code, notes, and snippets.

@robwiss
Created May 23, 2015 00:15
Show Gist options
  • Save robwiss/06ef1cc830f43a91a57b to your computer and use it in GitHub Desktop.
Save robwiss/06ef1cc830f43a91a57b to your computer and use it in GitHub Desktop.
code that will create race condition involving turtle mock::sequence
#define BOOST_TEST_DYN_LINK 1
#define BOOST_TEST_MODULE test_sequence
#define MOCK_THREAD_SAFE
#include <boost/test/unit_test.hpp>
#include <turtle/mock.hpp>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <queue>
#include <chrono>
// a dummy class with two methods
class A
{
public:
A() {}
virtual ~A() {}
virtual void a() = 0;
virtual void b() = 0;
};
MOCK_BASE_CLASS(AMock, A)
{
MOCK_METHOD(a, 0);
MOCK_METHOD(b, 0);
};
// a simple blocking queue
template <typename T>
class bqueue
{
public:
virtual ~bqueue()
{
close();
}
void close()
{
std::unique_lock<std::mutex> lk(m_);
closed_ = true;
cv_.notify_all();
cv_.wait(lk, [&] { return poppers == 0; });
}
void push(const T &data)
{
std::unique_lock<std::mutex> lk(m_);
if (closed_)
{
throw "pushed to a closed queue";
}
q_.push( data );
lk.unlock();
cv_.notify_one();
}
bool pop(T &data)
{
std::unique_lock<std::mutex> lk(m_);
++poppers;
cv_.wait(lk, [&] { return !q_.empty() || closed_; });
--poppers;
if ( closed_ )
{
cv_.notify_all();
return false;
}
data = std::move(q_.front());
q_.pop();
return true;
}
private:
bool closed_ = false;
std::mutex m_;
std::condition_variable cv_;
std::queue<T> q_;
int poppers = 0;
};
static bqueue<std::shared_ptr<A>> q;
// a thread that consumes from the queue and runs the A::a() method
static void thread_run()
{
for (;;)
{
std::shared_ptr<A> a;
if (!q.pop(a))
{
break;
}
// in reality this is a thread keeping track of timeouts so sleep to
// simulate a timeout occurring
std::this_thread::sleep_for(std::chrono::milliseconds(100));
a->a(); // run a() functionality
}
}
// initialization for the thread to run detached
static std::atomic_flag thread_init_lock = ATOMIC_FLAG_INIT;
static std::thread *t = nullptr;
static void init_thread()
{
if (!thread_init_lock.test_and_set())
{
t = new std::thread(thread_run);
t->detach();
}
}
// a test containing the use of a mock::sequence to track order of operations
BOOST_AUTO_TEST_CASE( sequence_with_threads )
{
mock::sequence seq;
std::mutex m;
std::condition_variable cv;
bool value = false;
auto a_mock = std::make_shared<AMock>();
// EXPECT a_mock->b() will be called and will wait for value to be set by
// the call to a_mock->a() that happens in the detached thread
MOCK_EXPECT( a_mock->b ).once().in(seq).calls(
[&]() {
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return value; } );
}
);
// EXPECT a_mock->a() will be called, thereby setting value to true
MOCK_EXPECT( a_mock->a ).once().in(seq).calls(
[&]() {
std::lock_guard<std::mutex> lk(m);
value = true;
cv.notify_one();
}
);
// create processing thread
init_thread();
// put a_mock on queue to be processed
q.push(a_mock);
// call a_mock->b() to wait for a_mock->a() to happen
a_mock->b();
// ensure the function ran
BOOST_CHECK( value == true );
}
@robwiss
Copy link
Author

robwiss commented May 23, 2015

Set TURTLE_INCLUDE as the path to turtle's include dir and compile with the thread sanitizer turned on to see the issue:

g++ --std=c++11 -I$TURTLE_INCLUDE test.cc -fPIE -lboost_unit_test_framework -pthread -pie -ltsan -fsanitize=thread -g -O2

I tested with gcc 4.8.2 and gcc 4.9.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment