diff --git a/test/test_read_write_mutex.cpp b/test/test_read_write_mutex.cpp new file mode 100644 index 00000000..c8dc3adb --- /dev/null +++ b/test/test_read_write_mutex.cpp @@ -0,0 +1,336 @@ +// Copyright (C) 2001-2003 +// William E. Kempf +// +// Permission to use, copy, modify, distribute and sell this software +// and its documentation for any purpose is hereby granted without fee, +// provided that the above copyright notice appear in all copies and +// that both that copyright notice and this permission notice appear +// in supporting documentation. William E. Kempf makes no representations +// about the suitability of this software for any purpose. +// It is provided "as is" without express or implied warranty. + +#include +#include +#include + +#include + +#include + +namespace { + +int shared_val = 0; + +boost::xtime xsecs(int secs) +{ + boost::xtime ret; + BOOST_TEST(boost::TIME_UTC == boost::xtime_get(&ret, boost::TIME_UTC)); + ret.sec += secs; + return ret; +} + +template +class thread_adapter +{ +public: + thread_adapter(void (*func)(void*,RW &), void* param1,RW ¶m2) + : _func(func), _param1(param1) ,_param2(param2){ } + void operator()() const { _func(_param1, _param2); } + +private: + void (*_func)(void*, RW &); + void* _param1; + RW& _param2; +}; + +template +struct data +{ + data(int id, RW &m, int secs=0) + : m_id(id), m_value(-1), m_secs(secs), m_read_write_mutex(m) + { + } + int m_id; + int m_value; + int m_secs; + + RW& m_read_write_mutex; // Reader/Writer mutex +}; + +// plain_writer excercises the "infinite" lock for each +// read_write_mutex type. + +template +void plain_writer(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + // std::cout << "-->W" << pdata->m_id << "\n"; + + typename RW::scoped_read_write_lock l(rw,boost::EXCL_LOCK); + + boost::thread::sleep(xsecs(3)); + shared_val += 10; + + pdata->m_value = shared_val; +} + +template +void plain_reader(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + typename RW::scoped_read_write_lock l(rw,boost::SHARED_LOCK); + + pdata->m_value = shared_val; +} + +template +void try_writer(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + // std::cout << "-->W" << pdata->m_id << "\n"; + + typename RW::scoped_try_read_write_lock l(rw,boost::NO_LOCK); + + if(l.try_wrlock()) + { + + boost::thread::sleep(xsecs(3)); + shared_val += 10; + + pdata->m_value = shared_val; + } + +} + +template +void try_reader(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + typename RW::scoped_try_read_write_lock l(rw); + + if(l.try_rdlock()) + { + pdata->m_value = shared_val; + } +} + +template +void timed_writer(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + + boost::xtime xt; + xt = xsecs(pdata->m_secs); + typename RW::scoped_timed_read_write_lock l(rw,boost::NO_LOCK); + + if(l.timed_wrlock(xt)) + { + boost::thread::sleep(xsecs(3)); + shared_val += 10; + + pdata->m_value = shared_val; + } +} + +template +void timed_reader(void *arg,RW &rw) +{ + data *pdata = (data *) arg; + boost::xtime xt; + xt = xsecs(pdata->m_secs); + + typename RW::scoped_timed_read_write_lock l(rw,boost::NO_LOCK); + + if(l.timed_rdlock(xt)) + { + pdata->m_value = shared_val; + } +} + +template +void dump_times(const char *prefix,data *pdata) +{ + std::cout << " " << prefix << pdata->m_id << + " In:" << pdata->m_start.LowPart << + " Holding:" << pdata->m_holding.LowPart << + " Out: " << pdata->m_end.LowPart << std::endl; +} + +template +void test_plain_read_write_mutex(RW &read_write_mutex) +{ + shared_val = 0; + data r1(1,read_write_mutex); + data r2(2,read_write_mutex); + data w1(1,read_write_mutex); + data w2(2,read_write_mutex); + + // Writer one launches, holds the lock for 3 seconds. + boost::thread tw1(thread_adapter(plain_writer,&w1,read_write_mutex)); + + // Writer two launches, tries to grab the lock, "clearly" + // after Writer one will already be holding it. + boost::thread::sleep(xsecs(1)); + boost::thread tw2(thread_adapter(plain_writer,&w2,read_write_mutex)); + + // Reader one launches, "clearly" after writer two, and "clearly" + // while writer 1 still holds the lock + boost::thread::sleep(xsecs(1)); + boost::thread tr1(thread_adapter(plain_reader,&r1,read_write_mutex)); + boost::thread tr2(thread_adapter(plain_reader,&r2,read_write_mutex)); + + tr2.join(); + tr1.join(); + tw2.join(); + tw1.join(); + + if(read_write_mutex.policy() == boost::sp_writer_priority) + { + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(w2.m_value == 20); + BOOST_TEST(r1.m_value == 20); // Readers get in after 2nd writer + BOOST_TEST(r2.m_value == 20); + } + else if(read_write_mutex.policy() == boost::sp_reader_priority) + { + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(w2.m_value == 20); + BOOST_TEST(r1.m_value == 10); // Readers get in before 2nd writer + BOOST_TEST(r2.m_value == 10); + } + else if(read_write_mutex.policy() == boost::sp_alternating_many_reads) + { + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(w2.m_value == 20); + BOOST_TEST(r1.m_value == 10); // Readers get in before 2nd writer + BOOST_TEST(r2.m_value == 10); + } + else if(read_write_mutex.policy() == boost::sp_alternating_single_reads) + { + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(w2.m_value == 20); + + // One Reader gets in before 2nd writer, but we can't tell + // which reader will "win", so just check their sum. + BOOST_TEST((r1.m_value + r2.m_value == 30)); + } +} + +template +void test_try_read_write_mutex(RW &read_write_mutex) +{ + data r1(1,read_write_mutex); + data w1(2,read_write_mutex); + data w2(3,read_write_mutex); + + // We start with some specialized tests for "try" behavior + + shared_val = 0; + + // Writer one launches, holds the lock for 3 seconds. + + boost::thread tw1(thread_adapter(try_writer,&w1,read_write_mutex)); + + // Reader one launches, "clearly" after writer #1 holds the lock + // and before it releases the lock. + boost::thread::sleep(xsecs(1)); + boost::thread tr1(thread_adapter(try_reader,&r1,read_write_mutex)); + + // Writer two launches in the same timeframe. + boost::thread tw2(thread_adapter(try_writer,&w2,read_write_mutex)); + + tw2.join(); + tr1.join(); + tw1.join(); + + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(r1.m_value == -1); // Try would return w/o waiting + BOOST_TEST(w2.m_value == -1); // Try would return w/o waiting + + // We finish by repeating the plain tests with the try lock + // This is important to verify that try locks are proper read_write_mutexes as + // well. + test_plain_read_write_mutex(read_write_mutex); + +} + +template +void test_timed_read_write_mutex(RW &read_write_mutex) +{ + data r1(1,read_write_mutex,1); + data r2(2,read_write_mutex,3); + data w1(3,read_write_mutex,3); + data w2(4,read_write_mutex,1); + + // We begin with some specialized tests for "timed" behavior + + shared_val = 0; + + // Writer one will hold the lock for 3 seconds. + boost::thread tw1(thread_adapter(timed_writer,&w1,read_write_mutex)); + + boost::thread::sleep(xsecs(1)); + // Writer two will "clearly" try for the lock after the readers + // have tried for it. Writer will wait up 1 second for the lock. + // This write will fail. + boost::thread tw2(thread_adapter(timed_writer,&w2,read_write_mutex)); + + // Readers one and two will "clearly" try for the lock after writer + // one already holds it. 1st reader will wait 1 second, and will fail + // to get the lock. 2nd reader will wait 3 seconds, and will get + // the lock. + + boost::thread tr1(thread_adapter(timed_reader,&r1,read_write_mutex)); + boost::thread tr2(thread_adapter(timed_reader,&r2,read_write_mutex)); + + + tw1.join(); + tr1.join(); + tr2.join(); + tw2.join(); + + + BOOST_TEST(w1.m_value == 10); + BOOST_TEST(r1.m_value == -1); + BOOST_TEST(r2.m_value == 10); + BOOST_TEST(w2.m_value == -1); + + // We follow by repeating the try tests with the timed lock. + // This is important to verify that timed locks are proper try locks as + // well + test_try_read_write_mutex(read_write_mutex); +} + +} // namespace + +void test_read_write_mutex() +{ + int i; + for(i = (int) boost::sp_writer_priority; + i <= (int) boost::sp_alternating_single_reads; + i++) + { + boost::read_write_mutex plain_rw((boost::read_write_scheduling_policy) i); + boost::try_read_write_mutex try_rw((boost::read_write_scheduling_policy) i); + boost::timed_read_write_mutex timed_rw((boost::read_write_scheduling_policy) i); + + std::cout << "plain test, sp=" << i << "\n"; + test_plain_read_write_mutex(plain_rw); + + std::cout << "try test, sp=" << i << "\n"; + test_try_read_write_mutex(try_rw); + + std::cout << "timed test, sp=" << i << "\n"; + test_timed_read_write_mutex(timed_rw); + } +} + +boost::unit_test_framework::test_suite* init_unit_test_suite(int, char*[]) +{ + boost::unit_test_framework::test_suite* test = + BOOST_TEST_SUITE("Boost.Threads: read_write_mutex test suite"); + + test->add(BOOST_TEST_CASE(&test_read_write_mutex)); + + return test; +}