From b4252fa9ea36489f55b9c42a3bc14cc344bac9fc Mon Sep 17 00:00:00 2001 Message-Id: From: Tim Blechmann Date: Mon, 8 Jul 2013 10:55:51 +0200 Subject: [PATCH] thread: semaphore implementation linux: posix semaphores osx/ios: dispatch semaphores win32: windows semaphores --- boost/thread/apple/semaphore_dispatch.hpp | 105 +++++++++++++++++ boost/thread/detail/semaphore_emulation.hpp | 91 +++++++++++++++ boost/thread/pthread/semaphore_posix.hpp | 170 ++++++++++++++++++++++++++++ boost/thread/semaphore.hpp | 69 +++++++++++ boost/thread/win32/semaphore_win32.hpp | 38 +++++++ libs/thread/doc/semaphore.qbk | 161 ++++++++++++++++++++++++++ libs/thread/doc/thread.qbk | 1 + libs/thread/src/win32/thread.cpp | 63 +++++++++++ libs/thread/test/Jamfile.v2 | 1 - libs/thread/test/test_semaphore.cpp | 124 ++++++++++++++++++++ 10 files changed, 822 insertions(+), 1 deletion(-) create mode 100644 boost/thread/apple/semaphore_dispatch.hpp create mode 100644 boost/thread/detail/semaphore_emulation.hpp create mode 100644 boost/thread/pthread/semaphore_posix.hpp create mode 100644 boost/thread/semaphore.hpp create mode 100644 boost/thread/win32/semaphore_win32.hpp create mode 100644 libs/thread/doc/semaphore.qbk create mode 100644 libs/thread/test/test_semaphore.cpp diff --git a/boost/thread/apple/semaphore_dispatch.hpp b/boost/thread/apple/semaphore_dispatch.hpp new file mode 100644 index 0000000..2bbce52 --- /dev/null +++ b/boost/thread/apple/semaphore_dispatch.hpp @@ -0,0 +1,105 @@ +// semaphore.hpp, osx/ios dispatch semaphores +// +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_THREAD_APPLE_SEMAPHORE_DISPATCH_HPP +#define BOOST_THREAD_APPLE_SEMAPHORE_DISPATCH_HPP + +#include + +#include + +#ifdef BOOST_THREAD_USES_CHRONO +#include +#include +#endif + +namespace boost { + +class semaphore +{ + BOOST_DELETED_FUNCTION(semaphore(semaphore const&)) + BOOST_DELETED_FUNCTION(semaphore& operator=(semaphore const&)) + +public: + semaphore(int i=0) + { + BOOST_ASSERT_MSG(i >= 0, "boost::semaphore constructor called with negative count"); + sem = dispatch_semaphore_create(i); + if (sem == NULL) + boost::throw_exception(thread_resource_error(system::errc::not_enough_memory, "boost::semaphore constructor failed in dispatch_semaphore_create")); + } + + ~semaphore() + { + dispatch_release(sem); + } + + void post() + { + dispatch_semaphore_signal(sem); + } + + void wait() + { + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + } + + bool try_wait(void) + { + const long status = dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW); + return status == 0; + } + +#ifdef BOOST_THREAD_USES_CHRONO + template + bool try_wait_for(const chrono::duration & rel_time) + { + return try_wait_until(chrono::steady_clock::now() + rel_time); + } + + template + bool try_wait_until(const chrono::time_point & timeout ) + { + using namespace chrono; + system_clock::time_point s_now = system_clock::now(); + typename Clock::time_point c_now = Clock::now(); + return try_wait_until(s_now + ceil(timeout - c_now)); + } + + template + bool try_wait_until(const chrono::time_point& t) + { + using namespace chrono; + typedef time_point nano_sys_tmpt; + return try_wait_until(nano_sys_tmpt(ceil(t.time_since_epoch()))); + } + + bool try_wait_until(const chrono::time_point& tp) + { + chrono::nanoseconds d = tp.time_since_epoch(); + timespec ts = boost::detail::to_timespec(d); + return do_wait_lock_until(dispatch_walltime(&ts, 0)); + } + +private: + bool do_wait_lock_until(const dispatch_time_t timeout) + { + const long status = dispatch_semaphore_wait(sem, timeout); + return status == 0; + } + +#endif // BOOST_THREAD_USES_CHRONO + + +private: + dispatch_semaphore_t sem; +}; + +} + +#endif // BOOST_THREAD_APPLE_SEMAPHORE_DISPATCH_HPP diff --git a/boost/thread/detail/semaphore_emulation.hpp b/boost/thread/detail/semaphore_emulation.hpp new file mode 100644 index 0000000..56ab97a --- /dev/null +++ b/boost/thread/detail/semaphore_emulation.hpp @@ -0,0 +1,91 @@ +// semaphore.hpp, mutex/condition_varibale emulation +// +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_THREAD_DETAIL_SEMAPHORE_EMULATION_HPP +#define BOOST_THREAD_DETAIL_SEMAPHORE_EMULATION_HPP + +#include +#include + +#include +#include + +#ifdef BOOST_THREAD_USES_CHRONO +#include +#include +#endif + +namespace boost { + +class semaphore +{ + BOOST_DELETED_FUNCTION(semaphore(semaphore const&)) + BOOST_DELETED_FUNCTION(semaphore& operator=(semaphore const&)) + +public: + semaphore(int i=0): + m_count(i) + { + BOOST_ASSERT_MSG(i >= 0, "boost::semaphore constructor called with negative count"); + } + + void post(void) + { + mutex::scoped_lock lock(m_mutex); + ++m_count; + m_cond.notify_one(); + } + + void wait(void) + { + mutex::scoped_lock lock(m_mutex); + m_cond.wait(lock, boost::bind(&semaphore::check_wakeup_condition, this)); + + --m_count; + } + + bool try_wait(void) + { + mutex::scoped_lock lock(m_mutex); + if (!check_wakeup_condition()) + return false; + + --m_count; + return true; + } + +#ifdef BOOST_THREAD_USES_CHRONO + template + bool try_wait_for(const chrono::duration & rel_time) + { + mutex::scoped_lock lock(m_mutex); + return m_cond.wait_for(lock, rel_time, boost::bind(&semaphore::check_wakeup_condition, this)); + } + + template + bool try_wait_until(const chrono::time_point & timeout ) + { + mutex::scoped_lock lock(m_mutex); + return m_cond.wait_until(lock, timeout, boost::bind(&semaphore::check_wakeup_condition, this)); + } +#endif // BOOST_THREAD_USES_CHRONO + +private: + bool check_wakeup_condition() + { + return m_count > 0; + } + + unsigned int m_count; + boost::mutex m_mutex; + boost::condition_variable m_cond; +}; + +} + +#endif // BOOST_THREAD_DETAIL_SEMAPHORE_EMULATION_HPP diff --git a/boost/thread/pthread/semaphore_posix.hpp b/boost/thread/pthread/semaphore_posix.hpp new file mode 100644 index 0000000..ca91297 --- /dev/null +++ b/boost/thread/pthread/semaphore_posix.hpp @@ -0,0 +1,170 @@ +// semaphore.hpp, posix implementation +// +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + + +#ifndef BOOST_THREAD_PTHREAD_SEMAPHORE_POSIX_HPP +#define BOOST_THREAD_PTHREAD_SEMAPHORE_POSIX_HPP + +#include + +#include +#include + +#ifdef BOOST_THREAD_USES_CHRONO +#include +#include +#endif + +namespace boost { + +class semaphore +{ + BOOST_DELETED_FUNCTION(semaphore(semaphore const&)) + BOOST_DELETED_FUNCTION(semaphore& operator=(semaphore const&)) + +public: + semaphore(int i=0) + { + BOOST_ASSERT_MSG(i >= 0, "boost::semaphore constructor called with negative count"); + + const int status = sem_init(&sem, 0, i); + if (status) + boost::throw_exception(thread_resource_error(status, "boost::semaphore constructor failed in sem_init")); + } + + ~semaphore() + { + const int status = sem_destroy(&sem); + (void)status; + BOOST_ASSERT(!status); + } + + void post() + { + const int status = sem_post(&sem); + + switch (status) + { + case EOVERFLOW: + boost::throw_exception(thread_resource_error(status, "boost::semaphore post failed: Maximum allowable value would be exceeded")); + break; + + case EINVAL: + BOOST_ASSERT(false); + + default: + break; + } + } + + void wait(void) + { + for (;;) + { + const int status = sem_wait(&sem); + if (status == 0) + return; + + switch (errno) + { + case EINTR: // interrupted by a signal handler + continue; + + case EINVAL: + default: + // we should not reach here + BOOST_ASSERT(false); + return; + } + } + } + + bool try_wait(void) + { + const int status = sem_trywait(&sem); + if (status == 0) + return true; + + switch (errno) + { + case EINVAL: + BOOST_ASSERT(false); + + case EAGAIN: + return false; + + default: + return false; + } + } + +#ifdef BOOST_THREAD_USES_CHRONO + template + bool try_wait_for(const chrono::duration & rel_time) + { + return try_wait_until(chrono::steady_clock::now() + rel_time); + } + + template + bool try_wait_until(const chrono::time_point & timeout ) + { + using namespace chrono; + system_clock::time_point s_now = system_clock::now(); + typename Clock::time_point c_now = Clock::now(); + return try_wait_until(s_now + ceil(timeout - c_now)); + } + + template + bool try_wait_until(const chrono::time_point& t) + { + using namespace chrono; + typedef time_point nano_sys_tmpt; + return try_wait_until(nano_sys_tmpt(ceil(t.time_since_epoch()))); + } + + bool try_wait_until(const chrono::time_point& tp) + { + chrono::nanoseconds d = tp.time_since_epoch(); + timespec ts = boost::detail::to_timespec(d); + return do_wait_lock_until(ts); + } + +private: + bool do_wait_lock_until(struct timespec const & timeout) + { + for (;;) { + const int status = sem_timedwait(&sem, &timeout); + if (status == 0) + return true; + + switch (errno) + { + case ETIMEDOUT: + return false; + + case EINTR: // interrupted by a signal handler + continue; + + case EINVAL: + case EAGAIN: + default: + BOOST_ASSERT(false); + return false; + } + } + } + +#endif // BOOST_THREAD_USES_CHRONO + +private: + sem_t sem; +}; + +} + +#endif /* BOOST_THREAD_PTHREAD_SEMAPHORE_POSIX_HPP */ diff --git a/boost/thread/semaphore.hpp b/boost/thread/semaphore.hpp new file mode 100644 index 0000000..0953df2 --- /dev/null +++ b/boost/thread/semaphore.hpp @@ -0,0 +1,69 @@ +// semaphore.hpp +// +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_THREAD_SEMAPHORE_HPP +#define BOOST_THREAD_SEMAPHORE_HPP + +#include + + +// configuration helpers + +#ifdef BOOST_HAS_UNISTD_H +#include + +#if (_POSIX_SEMAPHORES - 0) >= 200112L +#define BOOST_THREAD_POSIX_SEMAPHORES +#endif + +#endif // BOOST_HAS_UNISTD_H + +#if defined(__APPLE__) +#include + +// OSX +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_6 +#define BOOST_THREAD_DISPATCH_SEMAPHORES +#endif + +#endif + +// iOS +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + +// untested! +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 +#define BOOST_THREAD_DISPATCH_SEMAPHORES +#endif + +#endif + + +#endif // __APPLE__ + + +// implementation + +#if defined(BOOST_THREAD_PLATFORM_WIN32) +#include + +#elif defined(BOOST_THREAD_POSIX_SEMAPHORES) +#include + +#elif defined(BOOST_THREAD_DISPATCH_SEMAPHORES) +#include + +#else +#include + +#endif + + +#endif // BOOST_THREAD_SEMAPHORE_HPP diff --git a/boost/thread/win32/semaphore_win32.hpp b/boost/thread/win32/semaphore_win32.hpp new file mode 100644 index 0000000..a6d8cf7 --- /dev/null +++ b/boost/thread/win32/semaphore_win32.hpp @@ -0,0 +1,38 @@ +// semaphore.hpp, win32 semaphores +// +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_THREAD_WIN32_SEMAPHORE_WIN32_HPP +#define BOOST_THREAD_WIN32_SEMAPHORE_WIN32_HPP + +#include + +namespace boost { + +class semaphore +{ + BOOST_DELETED_FUNCTION(semaphore(semaphore const&)) + BOOST_DELETED_FUNCTION(semaphore& operator=(semaphore const&)) + +public: + semaphore(int i=0); + + ~semaphore(); + + void post(); + + bool wait(); + + bool try_wait(void); + +private: + detail::win32::HANDLE sem_; +}; + +} + +#endif // BOOST_THREAD_WIN32_SEMAPHORE_WIN32_HPP diff --git a/libs/thread/doc/semaphore.qbk b/libs/thread/doc/semaphore.qbk new file mode 100644 index 0000000..b1f90f8 --- /dev/null +++ b/libs/thread/doc/semaphore.qbk @@ -0,0 +1,161 @@ +[/ + (C) Copyright 2007-8 Anthony Williams. + (C) Copyright 2013 Tim Blechmann. + (C) Copyright 2005-2012 Ion Gaztanaga + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt). +] + +[section:semaphore semaphores] + +[section:semaphores_whats_a_semaphores What's A Semaphore?] + +A semaphore is a synchronization mechanism between processes based in an internal +count that offers two basic operations: + +* [*Wait]: Tests the value of the semaphore count, and waits if the value is less than or + equal than 0. Otherwise, decrements the semaphore count. + +* [*Post]: Increments the semaphore count. If any process is blocked, one of those processes + is awoken. + +If the initial semaphore count is initialized to 1, a [*Wait] operation is equivalent to a +mutex locking and [*Post] is equivalent to a mutex unlocking. This type of semaphore is known +as a [*binary semaphore]. + +Although semaphores can be used like mutexes, they have a unique feature: unlike mutexes, +a [*Post] operation need not be executed by the same thread/process that executed the +[*Wait] operation. + +[endsect] + +[section:semaphore_class Class `semaphore`] + + #include + + namespace boost + { + class semaphore + { + public: + semaphore(int initial_count = 0); + + semaphore(semaphore const&) = delete; + semaphore& operator=(semaphore const&) = delete; + + void post(); + void wait(); + bool try_wait(); + + #ifdef BOOST_THREAD_USES_CHRONO + template + bool try_wait_for(const chrono::duration & duration); + + template + bool try_wait_until(const chrono::time_point & timeout); + #endif + }; + } + + +[section:constructor `semaphore()`] + +[variablelist + +[[Effects:] [Constructs an object of class `semaphore`. The semaphore is initialized to `initial_count`, +which is expected to be non-negative.]] + +[[Throws:] [__thread_resource_error__ if an error occurs.]] + +] + +[endsect] + + +[section:destructor `~semaphore()`] + +[variablelist + +[[Precondition:] [No thread is waiting on `*this`]] + +[[Effects:] [Destroys the object.]] + +[[Throws:] [Nothing.]] + +] + +[endsect] + + +[section:post `post()`] + +[variablelist + +[[Effects:] [Increments the semaphore count. If a thread is waiting for `*this`, it will be unblocked.]] + +[[Throws:] [__thread_resource_error__ if an error (e.g. counter overflow) occurs.]] + +] + +[endsect] + + +[section:wait `wait()`] + +[variablelist + +[[Effects:] [If the semaphore count is positive, it atomically decrements it and returns. Otherwise blocks +the current thread, until it can successfully decrement a positive semaphore count.]] + +[[Throws:] [__thread_resource_error__ if an error occurs.]] + +] + +[endsect] + + +[section:try_wait `try_wait()`] + +[variablelist + +[[Effects:] [If the semaphore count is positive, it atomically decrements it and returns `true`. Otherwise `false`.]] + +[[Throws:] [__thread_resource_error__ if an error occurs.]] + +] + +[endsect] + + +[section:try_wait `template bool try_wait_for(const chrono::duration & duration)`] + +[variablelist + +[[Effects:] [If the semaphore count is positive, it atomically decrements it and returns `true`. Otherwise it waits for the semaphore +for `duration`.]] + +[[Throws:] [__thread_resource_error__ if an error occurs.]] + +] + +[endsect] + + +[section:try_wait `template bool try_wait_until(const chrono::time_point & timeout)`] + +[variablelist + +[[Effects:] [If the semaphore count is positive, it atomically decrements it and returns `true`. Otherwise it waits for the semaphore +until `timeout`.]] + +[[Throws:] [__thread_resource_error__ if an error occurs.]] + +] + + +[endsect] + +[endsect] + +[endsect] diff --git a/libs/thread/doc/thread.qbk b/libs/thread/doc/thread.qbk index 7acc256..32231af 100644 --- a/libs/thread/doc/thread.qbk +++ b/libs/thread/doc/thread.qbk @@ -241,6 +241,7 @@ [include barrier.qbk] [/include latch.qbk] [include futures.qbk] +[include semaphore.qbk] [/include async_executors.qbk] [endsect] diff --git a/libs/thread/src/win32/thread.cpp b/libs/thread/src/win32/thread.cpp index 28dd8d4..4b1e61e 100644 --- a/libs/thread/src/win32/thread.cpp +++ b/libs/thread/src/win32/thread.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #if defined BOOST_THREAD_USES_DATETIME @@ -747,5 +748,67 @@ namespace boost current_thread_data->notify_all_at_thread_exit(&cond, lk.release()); } } + + semaphore::semaphore(int i) + { + BOOST_ASSERT_MSG(i >= 0, "boost::semaphore constructor called with negative count"); + + sem_ = CreateSemaphore(NULL, i, (std::numeric_limits::max)(), NULL); + if (!sem_) + boost::throw_exception(thread_resource_error(GetLastError(), "boost::semaphore constructor failed in CreateSemaphore")); + } + + semaphore::~semaphore() + { + CloseHandle(sem_); + } + + void semaphore::post() + { + const BOOL status = ReleaseSemaphore(sem_, 1, NULL); + if (status == 0) + boost::throw_exception(thread_resource_error(GetLastError(), "boost::semaphore::post failed in ReleaseSemaphore")); + } + + bool semaphore::wait() + { + const DWORD status = WaitForSingleObject(sem_, INFINITE); + + switch (status) + { + case WAIT_OBJECT_0: + return true; + + case WAIT_FAILED: + boost::throw_exception(thread_resource_error(GetLastError(), "boost::semaphore::wait failed in ReleaseSemaphore")); + + default: + BOOST_ASSERT(false); + return false; + } + } + + bool semaphore::try_wait() + { + const DWORD status = WaitForSingleObject(sem_, 0L); + + switch (status) + { + case WAIT_OBJECT_0: + return true; + + case WAIT_TIMEOUT: + return false; + + case WAIT_FAILED: + boost::throw_exception(thread_resource_error(GetLastError(), "boost::semaphore::wait failed in ReleaseSemaphore")); + + default: + BOOST_ASSERT(false); + return false; + } + return true; + } + } diff --git a/libs/thread/test/Jamfile.v2 b/libs/thread/test/Jamfile.v2 index 2081b95..13d275f 100644 --- a/libs/thread/test/Jamfile.v2 +++ b/libs/thread/test/Jamfile.v2 @@ -639,7 +639,6 @@ rule thread-compile ( sources : reqs * : name ) test-suite ts_sync_bounded_queue : [ thread-run2-noit ./sync/mutual_exclusion/sync_bounded_queue/single_thread_pass.cpp : sync_bounded_queue__single_thread_p ] - [ thread-run2-noit ./sync/mutual_exclusion/sync_bounded_queue/multi_thread_pass.cpp : sync_bounded_queue__multi_thread_p ] ; #explicit ts_this_thread ; diff --git a/libs/thread/test/test_semaphore.cpp b/libs/thread/test/test_semaphore.cpp new file mode 100644 index 0000000..fcea549 --- /dev/null +++ b/libs/thread/test/test_semaphore.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2013 Tim Blechmann +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#include +#include + +#include + +static void test_semaphore_post_wait() +{ + boost::semaphore sem(0); + + sem.post(); + sem.wait(); +} + + +static void test_semaphore_try_wait() +{ + boost::semaphore sem(0); + + BOOST_REQUIRE(!sem.try_wait()); + sem.post(); + BOOST_REQUIRE(sem.try_wait()); +} + + +struct semaphore_wait_and_post_test +{ + void run() + { + boost::thread post_thread(boost::bind(&semaphore_wait_and_post_test::wait_and_post, this)); + sem_.wait(); + } + + void wait_and_post() + { + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + sem_.post(); + } + + static void run_test() + { + semaphore_wait_and_post_test test; + test.run(); + } + + boost::semaphore sem_; + boost::thread thread_; +}; + +static void test_semaphore_wait_for() +{ + using namespace boost; + + semaphore sem(0); + + BOOST_AUTO(start, chrono::system_clock::now()); + + BOOST_REQUIRE(!sem.try_wait_for(chrono::milliseconds(500))); + + BOOST_AUTO(end, chrono::system_clock::now()); + BOOST_AUTO(wait_time, end - start); + + // guessing! + BOOST_REQUIRE( wait_time > chrono::milliseconds(450) ); + BOOST_REQUIRE( wait_time < chrono::milliseconds(1000) ); + + sem.post(); + + BOOST_REQUIRE(sem.try_wait_for(chrono::milliseconds(500))); +} + +static void test_semaphore_wait_until() +{ + using namespace boost; + + semaphore sem(0); + { + BOOST_AUTO(now, chrono::system_clock::now()); + BOOST_AUTO(timeout, now + chrono::milliseconds(500)); + + BOOST_REQUIRE(!sem.try_wait_until(timeout)); + + BOOST_AUTO(end, chrono::system_clock::now()); + BOOST_AUTO(timeout_delta, end - timeout); + + // guessing! + BOOST_REQUIRE( timeout_delta > chrono::milliseconds(-400) ); + BOOST_REQUIRE( timeout_delta < chrono::milliseconds(400) ); + } + + sem.post(); + + { + BOOST_AUTO(start, chrono::system_clock::now()); + BOOST_AUTO(timeout, start + chrono::milliseconds(500)); + + BOOST_REQUIRE(sem.try_wait_until(timeout)); + + BOOST_AUTO(end, chrono::system_clock::now()); + + // guessing! + BOOST_REQUIRE( (end - start) < chrono::milliseconds(100) ); + } +} + + +boost::unit_test::test_suite* init_unit_test_suite(int, char*[]) +{ + boost::unit_test::test_suite* test = BOOST_TEST_SUITE("Boost.Threads: semaphore test suite"); + + test->add(BOOST_TEST_CASE(test_semaphore_post_wait)); + test->add(BOOST_TEST_CASE(test_semaphore_try_wait)); + test->add(BOOST_TEST_CASE(semaphore_wait_and_post_test::run_test)); + + test->add(BOOST_TEST_CASE(test_semaphore_wait_for)); + test->add(BOOST_TEST_CASE(test_semaphore_wait_until)); + return test; +} -- 1.8.1.5