mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 05:27:55 +00:00
[master] Merge branch 'trac2332'
commit. commit.
This commit is contained in:
commit
5080ddf460
@ -26,7 +26,7 @@
|
|||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
|
|
||||||
#include <util/buffer.h>
|
#include <util/buffer.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <dns/edns.h>
|
#include <dns/edns.h>
|
||||||
#include <dns/exceptions.h>
|
#include <dns/exceptions.h>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
#include <bench/benchmark_util.h>
|
#include <bench/benchmark_util.h>
|
||||||
|
|
||||||
#include <util/buffer.h>
|
#include <util/buffer.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <dns/message.h>
|
#include <dns/message.h>
|
||||||
#include <dns/name.h>
|
#include <dns/name.h>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
#include <config/ccsession.h>
|
#include <config/ccsession.h>
|
||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
#include <dns/rrclass.h>
|
#include <dns/rrclass.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
#include <util/buffer.h>
|
#include <util/buffer.h>
|
||||||
#include <util/io/socketsession.h>
|
#include <util/io/socketsession.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <dns/message.h>
|
#include <dns/message.h>
|
||||||
#include <dns/messagerenderer.h>
|
#include <dns/messagerenderer.h>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <auth/datasrc_config.h>
|
#include <auth/datasrc_config.h>
|
||||||
|
|
||||||
#include <util/unittests/mock_socketsession.h>
|
#include <util/unittests/mock_socketsession.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
#include <dns/tests/unittest_util.h>
|
#include <dns/tests/unittest_util.h>
|
||||||
#include <testutils/dnsmessage_test.h>
|
#include <testutils/dnsmessage_test.h>
|
||||||
#include <testutils/srv_test.h>
|
#include <testutils/srv_test.h>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#include "datasrc_util.h"
|
#include "datasrc_util.h"
|
||||||
|
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <auth/auth_srv.h>
|
#include <auth/auth_srv.h>
|
||||||
#include <auth/auth_config.h>
|
#include <auth/auth_config.h>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#include <config/tests/fake_session.h>
|
#include <config/tests/fake_session.h>
|
||||||
#include <config/ccsession.h>
|
#include <config/ccsession.h>
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
|||||||
AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
|
AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
|
||||||
|
|
||||||
lib_LTLIBRARIES = libb10-threads.la
|
lib_LTLIBRARIES = libb10-threads.la
|
||||||
libb10_threads_la_SOURCES = lock.h lock.cc
|
libb10_threads_la_SOURCES = sync.h sync.cc
|
||||||
libb10_threads_la_SOURCES += thread.h thread.cc
|
libb10_threads_la_SOURCES += thread.h thread.cc
|
||||||
libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||||
|
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef B10_THREAD_LOCK_H
|
|
||||||
#define B10_THREAD_LOCK_H
|
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
|
||||||
|
|
||||||
#include <cstdlib> // for NULL.
|
|
||||||
|
|
||||||
namespace isc {
|
|
||||||
namespace util {
|
|
||||||
namespace thread {
|
|
||||||
|
|
||||||
/// \brief Mutex with very simple interface
|
|
||||||
///
|
|
||||||
/// Since mutexes are very system dependant, we create our own wrapper around
|
|
||||||
/// whatever is available on the system and hide it.
|
|
||||||
///
|
|
||||||
/// To use this mutex, create it and then lock and unlock it by creating the
|
|
||||||
/// Mutex::Locker object.
|
|
||||||
///
|
|
||||||
/// Also, as mutex is a low-level system object, an error might happen at any
|
|
||||||
/// operation with it. We convert many errors to the isc::InvalidOperation,
|
|
||||||
/// since the errors usually happen only when used in a wrong way. Any methods
|
|
||||||
/// or constructors in this class can throw. Allocation errors are converted
|
|
||||||
/// to std::bad_alloc (for example when OS-dependant limit of mutexes is
|
|
||||||
/// exceeded). Some errors which usually mean a programmer error abort the
|
|
||||||
/// program, since there could be no safe way to recover from them.
|
|
||||||
///
|
|
||||||
/// The current interface is somewhat minimalistic. If we ever need more, we
|
|
||||||
/// can add it later.
|
|
||||||
class Mutex : public boost::noncopyable {
|
|
||||||
public:
|
|
||||||
/// \brief Constructor.
|
|
||||||
///
|
|
||||||
/// Creates a mutex. It is a non-recursive mutex (can be locked just once,
|
|
||||||
/// if the same threads tries to lock it again, Bad Things Happen).
|
|
||||||
///
|
|
||||||
/// Depending on compilation parameters and OS, the mutex may or may not
|
|
||||||
/// do some error and sanity checking. However, such checking is meant
|
|
||||||
/// only to aid development, not rely on it as a feature.
|
|
||||||
///
|
|
||||||
/// \throw std::bad_alloc In case allocation of something (memory, the
|
|
||||||
/// OS mutex) fails.
|
|
||||||
/// \throw isc::InvalidOperation Other unspecified errors around the mutex.
|
|
||||||
/// This should be rare.
|
|
||||||
Mutex();
|
|
||||||
|
|
||||||
/// \brief Destructor.
|
|
||||||
///
|
|
||||||
/// Destroys the mutex. It is not allowed to destroy a mutex which is
|
|
||||||
/// currently locked. This means a Locker created with this Mutex must
|
|
||||||
/// never live longer than the Mutex itself.
|
|
||||||
~Mutex();
|
|
||||||
|
|
||||||
/// \brief This holds a lock on a Mutex.
|
|
||||||
///
|
|
||||||
/// To lock a mutex, create a locker. It'll get unlocked when the locker
|
|
||||||
/// is destroyed.
|
|
||||||
///
|
|
||||||
/// If you create the locker on the stack or using some other "garbage
|
|
||||||
/// collecting" mechanism (auto_ptr, for example), it ensures exception
|
|
||||||
/// safety with regards to the mutex - it'll get released on the exit
|
|
||||||
/// of function no matter by what means.
|
|
||||||
class Locker : public boost::noncopyable {
|
|
||||||
public:
|
|
||||||
/// \brief Constructor.
|
|
||||||
///
|
|
||||||
/// Locks the mutex. May block for extended period of time.
|
|
||||||
///
|
|
||||||
/// \throw isc::InvalidOperation when OS reports error. This usually
|
|
||||||
/// means an attempt to use the mutex in a wrong way (locking
|
|
||||||
/// a mutex second time from the same thread, for example).
|
|
||||||
Locker(Mutex& mutex) :
|
|
||||||
mutex_(NULL)
|
|
||||||
{
|
|
||||||
// Set the mutex_ after we acquire the lock. This is because of
|
|
||||||
// exception safety. If lock() throws, it didn't work, so we must
|
|
||||||
// not unlock when we are destroyed. In such case, mutex_ is
|
|
||||||
// NULL and checked in the destructor.
|
|
||||||
mutex.lock();
|
|
||||||
mutex_ = &mutex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \brief Destructor.
|
|
||||||
///
|
|
||||||
/// Unlocks the mutex.
|
|
||||||
~Locker() {
|
|
||||||
if (mutex_ != NULL) {
|
|
||||||
mutex_->unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
Mutex* mutex_;
|
|
||||||
};
|
|
||||||
/// \brief If the mutex is currently locked
|
|
||||||
///
|
|
||||||
/// This is debug aiding method only. And it might be unavailable in
|
|
||||||
/// non-debug build (because keeping the state might be needlesly
|
|
||||||
/// slow).
|
|
||||||
///
|
|
||||||
/// \todo Disable in non-debug build
|
|
||||||
bool locked() const;
|
|
||||||
private:
|
|
||||||
class Impl;
|
|
||||||
Impl* impl_;
|
|
||||||
void lock();
|
|
||||||
void unlock();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -12,7 +12,7 @@
|
|||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include "lock.h"
|
#include "sync.h"
|
||||||
|
|
||||||
#include <exceptions/exceptions.h>
|
#include <exceptions/exceptions.h>
|
||||||
|
|
||||||
@ -109,6 +109,15 @@ Mutex::~Mutex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Mutex::postLockAction() {
|
||||||
|
// This assertion would fail only in non-debugging mode, in which case
|
||||||
|
// this method wouldn't be called either, so we simply assert the
|
||||||
|
// condition.
|
||||||
|
assert(impl_->locked_count == 0);
|
||||||
|
++impl_->locked_count;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Mutex::lock() {
|
Mutex::lock() {
|
||||||
assert(impl_ != NULL);
|
assert(impl_ != NULL);
|
||||||
@ -116,13 +125,26 @@ Mutex::lock() {
|
|||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
isc_throw(isc::InvalidOperation, std::strerror(result));
|
isc_throw(isc::InvalidOperation, std::strerror(result));
|
||||||
}
|
}
|
||||||
++impl_->locked_count; // Only in debug mode
|
postLockAction(); // Only in debug mode
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Mutex::preUnlockAction(bool throw_ok) {
|
||||||
|
if (impl_->locked_count == 0) {
|
||||||
|
if (throw_ok) {
|
||||||
|
isc_throw(isc::InvalidOperation,
|
||||||
|
"Unlock attempt for unlocked mutex");
|
||||||
|
} else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--impl_->locked_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Mutex::unlock() {
|
Mutex::unlock() {
|
||||||
assert(impl_ != NULL);
|
assert(impl_ != NULL);
|
||||||
--impl_->locked_count; // Only in debug mode
|
preUnlockAction(false); // Only in debug mode. Ensure no throw.
|
||||||
const int result = pthread_mutex_unlock(&impl_->mutex);
|
const int result = pthread_mutex_unlock(&impl_->mutex);
|
||||||
assert(result == 0); // This should never be possible
|
assert(result == 0); // This should never be possible
|
||||||
}
|
}
|
||||||
@ -133,6 +155,59 @@ Mutex::locked() const {
|
|||||||
return (impl_->locked_count != 0);
|
return (impl_->locked_count != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CondVar::Impl {
|
||||||
|
public:
|
||||||
|
Impl() {
|
||||||
|
const int result = pthread_cond_init(&cond_, NULL);
|
||||||
|
if (result != 0) {
|
||||||
|
isc_throw(isc::Unexpected, "pthread_cond_init failed: "
|
||||||
|
<< std::strerror(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~Impl() {
|
||||||
|
const int result = pthread_cond_destroy(&cond_);
|
||||||
|
|
||||||
|
// This can happen if we try to destroy cond_ while some other thread
|
||||||
|
// is waiting on it. assert() may be too strong for such a case,
|
||||||
|
// but we cannot safely destroy cond_ anyway. In order to avoid
|
||||||
|
// throwing from a destructor we simply let the process die.
|
||||||
|
assert(result == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For convenience allow the main class to access this directly.
|
||||||
|
pthread_cond_t cond_;
|
||||||
|
};
|
||||||
|
|
||||||
|
CondVar::CondVar() : impl_(new Impl)
|
||||||
|
{}
|
||||||
|
|
||||||
|
CondVar::~CondVar() {
|
||||||
|
delete impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CondVar::wait(Mutex& mutex) {
|
||||||
|
mutex.preUnlockAction(true); // Only in debug mode
|
||||||
|
const int result = pthread_cond_wait(&impl_->cond_, &mutex.impl_->mutex);
|
||||||
|
mutex.postLockAction(); // Only in debug mode
|
||||||
|
|
||||||
|
// pthread_cond_wait should normally succeed unless mutex is completely
|
||||||
|
// broken.
|
||||||
|
if (result != 0) {
|
||||||
|
isc_throw(isc::BadValue, "pthread_cond_wait failed unexpectedly: " <<
|
||||||
|
std::strerror(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CondVar::signal() {
|
||||||
|
const int result = pthread_cond_signal(&impl_->cond_);
|
||||||
|
|
||||||
|
// pthread_cond_signal() can only fail when if cond_ is invalid. It
|
||||||
|
//should be impossible as long as this is a valid CondVar object.
|
||||||
|
assert(result == 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
223
src/lib/util/threads/sync.h
Normal file
223
src/lib/util/threads/sync.h
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef B10_THREAD_SYNC_H
|
||||||
|
#define B10_THREAD_SYNC_H
|
||||||
|
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
#include <cstdlib> // for NULL.
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace util {
|
||||||
|
namespace thread {
|
||||||
|
class CondVar;
|
||||||
|
|
||||||
|
/// \brief Mutex with very simple interface
|
||||||
|
///
|
||||||
|
/// Since mutexes are very system dependant, we create our own wrapper around
|
||||||
|
/// whatever is available on the system and hide it.
|
||||||
|
///
|
||||||
|
/// To use this mutex, create it and then lock and unlock it by creating the
|
||||||
|
/// Mutex::Locker object.
|
||||||
|
///
|
||||||
|
/// Also, as mutex is a low-level system object, an error might happen at any
|
||||||
|
/// operation with it. We convert many errors to the isc::InvalidOperation,
|
||||||
|
/// since the errors usually happen only when used in a wrong way. Any methods
|
||||||
|
/// or constructors in this class can throw. Allocation errors are converted
|
||||||
|
/// to std::bad_alloc (for example when OS-dependant limit of mutexes is
|
||||||
|
/// exceeded). Some errors which usually mean a programmer error abort the
|
||||||
|
/// program, since there could be no safe way to recover from them.
|
||||||
|
///
|
||||||
|
/// The current interface is somewhat minimalistic. If we ever need more, we
|
||||||
|
/// can add it later.
|
||||||
|
class Mutex : boost::noncopyable {
|
||||||
|
public:
|
||||||
|
/// \brief Constructor.
|
||||||
|
///
|
||||||
|
/// Creates a mutex. It is a non-recursive mutex (can be locked just once,
|
||||||
|
/// if the same threads tries to lock it again, Bad Things Happen).
|
||||||
|
///
|
||||||
|
/// Depending on compilation parameters and OS, the mutex may or may not
|
||||||
|
/// do some error and sanity checking. However, such checking is meant
|
||||||
|
/// only to aid development, not rely on it as a feature.
|
||||||
|
///
|
||||||
|
/// \throw std::bad_alloc In case allocation of something (memory, the
|
||||||
|
/// OS mutex) fails.
|
||||||
|
/// \throw isc::InvalidOperation Other unspecified errors around the mutex.
|
||||||
|
/// This should be rare.
|
||||||
|
Mutex();
|
||||||
|
|
||||||
|
/// \brief Destructor.
|
||||||
|
///
|
||||||
|
/// Destroys the mutex. It is not allowed to destroy a mutex which is
|
||||||
|
/// currently locked. This means a Locker created with this Mutex must
|
||||||
|
/// never live longer than the Mutex itself.
|
||||||
|
~Mutex();
|
||||||
|
|
||||||
|
/// \brief This holds a lock on a Mutex.
|
||||||
|
///
|
||||||
|
/// To lock a mutex, create a locker. It'll get unlocked when the locker
|
||||||
|
/// is destroyed.
|
||||||
|
///
|
||||||
|
/// If you create the locker on the stack or using some other "garbage
|
||||||
|
/// collecting" mechanism (auto_ptr, for example), it ensures exception
|
||||||
|
/// safety with regards to the mutex - it'll get released on the exit
|
||||||
|
/// of function no matter by what means.
|
||||||
|
class Locker : boost::noncopyable {
|
||||||
|
public:
|
||||||
|
/// \brief Constructor.
|
||||||
|
///
|
||||||
|
/// Locks the mutex. May block for extended period of time.
|
||||||
|
///
|
||||||
|
/// \throw isc::InvalidOperation when OS reports error. This usually
|
||||||
|
/// means an attempt to use the mutex in a wrong way (locking
|
||||||
|
/// a mutex second time from the same thread, for example).
|
||||||
|
Locker(Mutex& mutex) :
|
||||||
|
mutex_(mutex)
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Destructor.
|
||||||
|
///
|
||||||
|
/// Unlocks the mutex.
|
||||||
|
~Locker() {
|
||||||
|
mutex_.unlock();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Mutex& mutex_;
|
||||||
|
};
|
||||||
|
/// \brief If the mutex is currently locked
|
||||||
|
///
|
||||||
|
/// This is debug aiding method only. And it might be unavailable in
|
||||||
|
/// non-debug build (because keeping the state might be needlesly
|
||||||
|
/// slow).
|
||||||
|
///
|
||||||
|
/// \todo Disable in non-debug build
|
||||||
|
bool locked() const;
|
||||||
|
private:
|
||||||
|
friend class CondVar;
|
||||||
|
|
||||||
|
// Commonly called after acquiring the lock, checking and updating
|
||||||
|
// internal state for debug.
|
||||||
|
void postLockAction();
|
||||||
|
|
||||||
|
// Commonly called before releasing the lock, checking and updating
|
||||||
|
// internal state for debug.
|
||||||
|
//
|
||||||
|
// If throw_ok is true, it throws \c isc::InvalidOperation when the check
|
||||||
|
// fails; otherwise it aborts the process. This parameter must be set
|
||||||
|
// to false if the call to this shouldn't result in an exception (e.g.
|
||||||
|
// when called from a destructor).
|
||||||
|
void preUnlockAction(bool throw_ok);
|
||||||
|
|
||||||
|
class Impl;
|
||||||
|
Impl* impl_;
|
||||||
|
void lock();
|
||||||
|
void unlock();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Encapsulation for a condition variable.
|
||||||
|
///
|
||||||
|
/// This class provides a simple encapsulation of condition variable for
|
||||||
|
/// inter-thread synchronization. It has similar but simplified interface as
|
||||||
|
/// that for \c pthread_cond_ variants.
|
||||||
|
///
|
||||||
|
/// It uses the \c Mutex class object for the mutex used with the condition
|
||||||
|
/// variable. Since for normal applications the internal \c Mutex::Locker
|
||||||
|
/// class is the only available interface to acquire a lock, sample code
|
||||||
|
/// for waiting on a condition variable would look like this:
|
||||||
|
/// \code
|
||||||
|
/// CondVar cond;
|
||||||
|
/// Mutex mutex;
|
||||||
|
/// {
|
||||||
|
/// Mutex::Locker locker(mutex);
|
||||||
|
/// while (some_condition) {
|
||||||
|
/// cond.wait(mutex);
|
||||||
|
/// }
|
||||||
|
/// // do something under the protection of locker
|
||||||
|
/// } // lock is released here
|
||||||
|
/// \endcode
|
||||||
|
/// Note that \c mutex passed to the \c wait() method must be the same one
|
||||||
|
/// used to construct the \c locker.
|
||||||
|
///
|
||||||
|
/// Right now there is no equivalent to pthread_cond_broadcast() or
|
||||||
|
/// pthread_cond_timedwait() in this class, because this class is meant
|
||||||
|
/// for internal development of BIND 10 and we don't need these at the
|
||||||
|
/// moment. If and when we need these interfaces they can be added at that
|
||||||
|
/// point.
|
||||||
|
///
|
||||||
|
/// \note This class is defined as a friend class of \c Mutex and directly
|
||||||
|
/// refers to and modifies private internals of the \c Mutex class. It breaks
|
||||||
|
/// the assumption that the lock is only acquired or released via the
|
||||||
|
/// \c Locker class and breaks other integrity assumption on \c Mutex,
|
||||||
|
/// thereby making it more fragile, but we couldn't find other way to
|
||||||
|
/// implement a safe and still simple realization of condition variables.
|
||||||
|
/// So, this is a kind of compromise. If this class is needed to be
|
||||||
|
/// extended, first consider a way to use public interfaces of \c Mutex;
|
||||||
|
/// do not easily rely on the fact that this class is a friend of it.
|
||||||
|
class CondVar : boost::noncopyable {
|
||||||
|
public:
|
||||||
|
/// \brief Constructor.
|
||||||
|
///
|
||||||
|
/// \throw std::bad_alloc memory allocation failure
|
||||||
|
/// \throw isc::Unexpected other unexpected shortage of system resource
|
||||||
|
CondVar();
|
||||||
|
|
||||||
|
/// \brief Destructor.
|
||||||
|
///
|
||||||
|
/// An object of this class must not be destroyed while some thread
|
||||||
|
/// is waiting on it. If this condition isn't met the destructor will
|
||||||
|
/// terminate the program.
|
||||||
|
~CondVar();
|
||||||
|
|
||||||
|
/// \brief Wait on the condition variable.
|
||||||
|
///
|
||||||
|
/// This method works like \c pthread_cond_wait(). For mutex it takes
|
||||||
|
/// an \c Mutex class object. A lock for the mutex must have been
|
||||||
|
/// acquired. If this condition isn't met, it can throw an exception
|
||||||
|
/// (in the debug mode build) or result in undefined behavior.
|
||||||
|
///
|
||||||
|
/// The lock will be automatically released within this method, and
|
||||||
|
/// will be re-acquired on the exit of this method.
|
||||||
|
///
|
||||||
|
/// \throw isc::InvalidOperation mutex isn't locked
|
||||||
|
/// \throw isc::BadValue mutex is not a valid \c Mutex object
|
||||||
|
///
|
||||||
|
/// \param mutex A \c Mutex object to be released on wait().
|
||||||
|
void wait(Mutex& mutex);
|
||||||
|
|
||||||
|
/// \brief Unblock a thread waiting for the condition variable.
|
||||||
|
///
|
||||||
|
/// This method wakes one of other threads (if any) waiting on this object
|
||||||
|
/// via the \c wait() call.
|
||||||
|
///
|
||||||
|
/// This method never throws; if some unexpected low level error happens
|
||||||
|
/// it terminates the program.
|
||||||
|
void signal();
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
Impl* impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace thread
|
||||||
|
} // namespace util
|
||||||
|
} // namespace isc
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Local Variables:
|
||||||
|
// mode: c++
|
||||||
|
// End:
|
@ -23,6 +23,7 @@ TESTS += run_unittests
|
|||||||
run_unittests_SOURCES = run_unittests.cc
|
run_unittests_SOURCES = run_unittests.cc
|
||||||
run_unittests_SOURCES += thread_unittest.cc
|
run_unittests_SOURCES += thread_unittest.cc
|
||||||
run_unittests_SOURCES += lock_unittest.cc
|
run_unittests_SOURCES += lock_unittest.cc
|
||||||
|
run_unittests_SOURCES += condvar_unittest.cc
|
||||||
|
|
||||||
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||||
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PTHREAD_LDFLAGS)
|
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PTHREAD_LDFLAGS)
|
||||||
|
161
src/lib/util/threads/tests/condvar_unittest.cc
Normal file
161
src/lib/util/threads/tests/condvar_unittest.cc
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
#include <exceptions/exceptions.h>
|
||||||
|
|
||||||
|
#include <util/threads/sync.h>
|
||||||
|
#include <util/threads/thread.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
#include <boost/scoped_ptr.hpp>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
using namespace isc::util::thread;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Used as a signal handler below.
|
||||||
|
volatile bool do_exit; // use for emergency escape
|
||||||
|
void
|
||||||
|
alarmHandler(int) {
|
||||||
|
do_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CondVarTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
// We use a signal in case some of the thread synchronization tests
|
||||||
|
// unexpectedly cause a deadlock.
|
||||||
|
void SetUp() {
|
||||||
|
do_exit = false;
|
||||||
|
|
||||||
|
std::memset(&handler_, 0, sizeof(handler_));
|
||||||
|
handler_.sa_handler = alarmHandler;
|
||||||
|
if (sigaction(SIGALRM, &handler_, &original_) != 0) {
|
||||||
|
FAIL() << "Couldn't set alarm";
|
||||||
|
}
|
||||||
|
alarm(10); // 10sec duration: arbitrary choice
|
||||||
|
}
|
||||||
|
void TearDown() {
|
||||||
|
// Cancel the alarm and return the original handler
|
||||||
|
alarm(0);
|
||||||
|
if (sigaction(SIGALRM, &original_, NULL)) {
|
||||||
|
FAIL() << "Couldn't restore alarm";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CondVar condvar_;
|
||||||
|
Mutex mutex_;
|
||||||
|
private:
|
||||||
|
struct sigaction handler_, original_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(CondVarTest0, create) {
|
||||||
|
// Just construct and destruct it. Nothing unusual should happen.
|
||||||
|
EXPECT_NO_THROW(CondVar condvar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running on a separate thread, just updating the argument and waking up
|
||||||
|
// the other thread via the condition variable passed.
|
||||||
|
void
|
||||||
|
ringSignal(CondVar* condvar, Mutex* mutex, int* arg) {
|
||||||
|
assert(*arg == 0);
|
||||||
|
Mutex::Locker locker(*mutex);
|
||||||
|
++*arg;
|
||||||
|
condvar->signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple wait-signal operation on a condition variable.
|
||||||
|
TEST_F(CondVarTest, waitAndSignal) {
|
||||||
|
Mutex::Locker locker(mutex_);
|
||||||
|
int shared_var = 0; // let the other thread increment this
|
||||||
|
Thread t(boost::bind(&ringSignal, &condvar_, &mutex_, &shared_var));
|
||||||
|
condvar_.wait(mutex_);
|
||||||
|
t.wait();
|
||||||
|
EXPECT_EQ(1, shared_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread's main code for the next test
|
||||||
|
void
|
||||||
|
signalAndWait(CondVar* condvar1, CondVar* condvar2, Mutex* mutex, int* arg) {
|
||||||
|
Mutex::Locker locker(*mutex);
|
||||||
|
++*arg;
|
||||||
|
condvar2->signal(); // let the main thread know this one is ready
|
||||||
|
condvar1->wait(*mutex);
|
||||||
|
++*arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to the previous test, but having two threads wait for a condvar.
|
||||||
|
TEST_F(CondVarTest, multiWaits) {
|
||||||
|
boost::scoped_ptr<Mutex::Locker> locker(new Mutex::Locker(mutex_));
|
||||||
|
CondVar condvar2; // separate cond var for initial synchronization
|
||||||
|
int shared_var = 0; // let the other thread increment this
|
||||||
|
Thread t1(boost::bind(&signalAndWait, &condvar_, &condvar2, &mutex_,
|
||||||
|
&shared_var));
|
||||||
|
Thread t2(boost::bind(&signalAndWait, &condvar_, &condvar2, &mutex_,
|
||||||
|
&shared_var));
|
||||||
|
|
||||||
|
// Wait until both threads are waiting on condvar_.
|
||||||
|
while (shared_var < 2 && !do_exit) {
|
||||||
|
condvar2.wait(mutex_);
|
||||||
|
}
|
||||||
|
// Check we exited from the loop successfully.
|
||||||
|
ASSERT_FALSE(do_exit);
|
||||||
|
ASSERT_EQ(2, shared_var);
|
||||||
|
|
||||||
|
// release the lock, wake up both threads, wait for them to die, and
|
||||||
|
// confirm they successfully woke up.
|
||||||
|
locker.reset();
|
||||||
|
condvar_.signal();
|
||||||
|
condvar_.signal();
|
||||||
|
t1.wait();
|
||||||
|
t2.wait();
|
||||||
|
EXPECT_EQ(4, shared_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to the previous version of the same function, but just do
|
||||||
|
// condvar operations. It will never wake up.
|
||||||
|
void
|
||||||
|
signalAndWait(CondVar* condvar, Mutex* mutex) {
|
||||||
|
Mutex::Locker locker(*mutex);
|
||||||
|
condvar->signal();
|
||||||
|
condvar->wait(*mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CondVarTest, destroyWhileWait) {
|
||||||
|
// We'll destroy a CondVar object while the thread is still waiting
|
||||||
|
// on it. This will trigger an assertion failure.
|
||||||
|
EXPECT_DEATH_IF_SUPPORTED({
|
||||||
|
CondVar cond;
|
||||||
|
Mutex::Locker locker(mutex_);
|
||||||
|
Thread t(boost::bind(&signalAndWait, &cond, &mutex_));
|
||||||
|
cond.wait(mutex_);
|
||||||
|
}, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CondVarTest, badWait) {
|
||||||
|
// In our implementation, wait() requires acquiring the lock beforehand.
|
||||||
|
EXPECT_THROW(condvar_.wait(mutex_), isc::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CondVarTest, emptySignal) {
|
||||||
|
// It's okay to call signal when no one waits.
|
||||||
|
EXPECT_NO_THROW(condvar_.signal());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include <util/threads/lock.h>
|
#include <util/threads/sync.h>
|
||||||
#include <util/threads/thread.h>
|
#include <util/threads/thread.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
@ -80,13 +80,13 @@ performIncrement(volatile double* canary, volatile bool* ready_me,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
no_handler(int) {}
|
noHandler(int) {}
|
||||||
|
|
||||||
TEST(MutexTest, swarm) {
|
TEST(MutexTest, swarm) {
|
||||||
// Create a timeout in case something got stuck here
|
// Create a timeout in case something got stuck here
|
||||||
struct sigaction ignored, original;
|
struct sigaction ignored, original;
|
||||||
memset(&ignored, 0, sizeof ignored);
|
memset(&ignored, 0, sizeof(ignored));
|
||||||
ignored.sa_handler = no_handler;
|
ignored.sa_handler = noHandler;
|
||||||
if (sigaction(SIGALRM, &ignored, &original)) {
|
if (sigaction(SIGALRM, &ignored, &original)) {
|
||||||
FAIL() << "Couldn't set alarm";
|
FAIL() << "Couldn't set alarm";
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "lock.h"
|
#include "sync.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user