diff options
author | Francis Dupont <fdupont@isc.org> | 2019-09-21 08:52:27 +0200 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2019-09-27 11:07:21 +0200 |
commit | 469f43a8285f84d014a1315a45232100f4343d1b (patch) | |
tree | fc22f937132d995deb63746c05774bc00df87ed1 /src/lib | |
parent | [918-check-c-11-thread-support-in-configure] Added ChangeLog entry for thread... (diff) | |
download | kea-469f43a8285f84d014a1315a45232100f4343d1b.tar.xz kea-469f43a8285f84d014a1315a45232100f4343d1b.zip |
[907-remove-kea-thread-library] Checkpoint (todo finish netconf)
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/testutils/threaded_test.cc | 14 | ||||
-rw-r--r-- | src/lib/testutils/threaded_test.h | 12 | ||||
-rw-r--r-- | src/lib/util/Makefile.am | 7 | ||||
-rw-r--r-- | src/lib/util/threads/Makefile.am | 16 | ||||
-rw-r--r-- | src/lib/util/threads/sync.cc | 252 | ||||
-rw-r--r-- | src/lib/util/threads/sync.h | 257 | ||||
-rw-r--r-- | src/lib/util/threads/tests/.gitignore | 1 | ||||
-rw-r--r-- | src/lib/util/threads/tests/Makefile.am | 38 | ||||
-rw-r--r-- | src/lib/util/threads/tests/condvar_unittest.cc | 170 | ||||
-rw-r--r-- | src/lib/util/threads/tests/lock_unittest.cc | 161 | ||||
-rw-r--r-- | src/lib/util/threads/tests/run_unittests.cc | 22 | ||||
-rw-r--r-- | src/lib/util/threads/tests/thread_unittest.cc | 262 | ||||
-rw-r--r-- | src/lib/util/threads/thread.cc | 194 | ||||
-rw-r--r-- | src/lib/util/threads/thread.h | 107 |
14 files changed, 13 insertions, 1500 deletions
diff --git a/src/lib/testutils/threaded_test.cc b/src/lib/testutils/threaded_test.cc index 2302771caa..1a0126f9c7 100644 --- a/src/lib/testutils/threaded_test.cc +++ b/src/lib/testutils/threaded_test.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -6,8 +6,6 @@ #include <testutils/threaded_test.h> -using namespace isc::util::thread; - namespace isc { namespace test { @@ -19,10 +17,10 @@ ThreadedTest::ThreadedTest() void ThreadedTest::doSignal(bool& flag) { { - Mutex::Locker lock(mutex_); + std::lock_guard<std::mutex> lock(mutex_); flag = true; } - condvar_.signal(); + condvar_.notify_one(); } void @@ -42,9 +40,9 @@ ThreadedTest::signalStopped() { void ThreadedTest::doWait(bool& flag) { - Mutex::Locker lock(mutex_); + std::unique_lock<std::mutex> lock(mutex_); while (!flag) { - condvar_.wait(mutex_); + condvar_.wait(lock); } } @@ -65,7 +63,7 @@ ThreadedTest::waitStopped() { bool ThreadedTest::isStopping() { - Mutex::Locker lock(mutex_); + std::lock_guard<std::mutex> lock(mutex_); return (stopping_); } diff --git a/src/lib/testutils/threaded_test.h b/src/lib/testutils/threaded_test.h index 537aec8efe..1ff4909c2e 100644 --- a/src/lib/testutils/threaded_test.h +++ b/src/lib/testutils/threaded_test.h @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,10 +7,10 @@ #ifndef THREADED_TEST_H #define THREADED_TEST_H -#include <util/threads/thread.h> -#include <util/threads/sync.h> #include <boost/shared_ptr.hpp> #include <gtest/gtest.h> +#include <thread> +#include <mutex> namespace isc { namespace test { @@ -62,13 +62,13 @@ protected: bool isStopping(); /// @brief Pointer to server thread. - boost::shared_ptr<util::thread::Thread> thread_; + boost::shared_ptr<std::thread> thread_; /// @brief Mutex used to synchronize threads. - util::thread::Mutex mutex_; + std::mutex mutex_; /// Condtional variable for thread waits. - util::thread::CondVar condvar_; + std::condition_variable condvar_; /// Flag indicating that the thread is ready. bool ready_; diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am index 0c78964aae..4280b608b3 100644 --- a/src/lib/util/Makefile.am +++ b/src/lib/util/Makefile.am @@ -1,6 +1,6 @@ AUTOMAKE_OPTIONS = subdir-objects -SUBDIRS = . io unittests tests python threads +SUBDIRS = . io unittests tests python AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) @@ -96,8 +96,3 @@ libkea_util_random_includedir = $(pkgincludedir)/util/random libkea_util_random_include_HEADERS = \ random/qid_gen.h \ random/random_number_generator.h - -libkea_util_threads_includedir = $(pkgincludedir)/util/threads -libkea_util_threads_include_HEADERS = \ - threads/sync.h \ - threads/thread.h diff --git a/src/lib/util/threads/Makefile.am b/src/lib/util/threads/Makefile.am deleted file mode 100644 index 337dc0a9d7..0000000000 --- a/src/lib/util/threads/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -SUBDIRS = . tests -AM_CXXFLAGS = $(KEA_CXXFLAGS) - -AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) - -lib_LTLIBRARIES = libkea-threads.la -libkea_threads_la_SOURCES = sync.h sync.cc -libkea_threads_la_SOURCES += thread.h thread.cc -libkea_threads_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la -libkea_threads_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la - -libkea_threads_la_LDFLAGS = -no-undefined -version-info 3:0:0 - - -CLEANFILES = *.gcno *.gcda diff --git a/src/lib/util/threads/sync.cc b/src/lib/util/threads/sync.cc deleted file mode 100644 index 06463f95e9..0000000000 --- a/src/lib/util/threads/sync.cc +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <util/threads/sync.h> - -#include <exceptions/exceptions.h> - -#include <cstring> -#include <memory> -#include <cerrno> -#include <cassert> - -#include <pthread.h> - -using std::unique_ptr; - -namespace isc { -namespace util { -namespace thread { - -class Mutex::Impl { -public: - Impl() -#ifdef ENABLE_DEBUG - : locked_count(0) -#endif // ENABLE_DEBUG - {} - - pthread_mutex_t mutex; -#ifdef ENABLE_DEBUG - size_t locked_count; -#endif // ENABLE_DEBUG -}; - -namespace { - -struct Deinitializer { - Deinitializer(pthread_mutexattr_t& attributes): - attributes_(attributes) - {} - ~Deinitializer() { - const int result = pthread_mutexattr_destroy(&attributes_); - // This should never happen. According to the man page, - // if there's error, it's our fault. - assert(result == 0); - } - pthread_mutexattr_t& attributes_; -}; - -} - -Mutex::Mutex() : - impl_(NULL) -{ - pthread_mutexattr_t attributes; - int result = pthread_mutexattr_init(&attributes); - switch (result) { - case 0: // All 0K - break; - case ENOMEM: - throw std::bad_alloc(); - default: - isc_throw(isc::InvalidOperation, std::strerror(result)); - } - Deinitializer deinitializer(attributes); - - // If debug mode is enabled in compilation, use the slower - // error-checking mutexes that detect deadlocks. Otherwise, use fast - // mutexes which don't. See the pthread_mutexattr_settype() POSIX - // documentation which describes these type attributes. -#ifdef ENABLE_DEBUG - result = pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_ERRORCHECK); -#else - result = pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_NORMAL); -#endif // ENABLE_DEBUG - if (result != 0) { - isc_throw(isc::InvalidOperation, std::strerror(result)); - } - - unique_ptr<Impl> impl(new Impl); - result = pthread_mutex_init(&impl->mutex, &attributes); - switch (result) { - case 0: // All 0K - impl_ = impl.release(); - break; - case ENOMEM: - case EAGAIN: - throw std::bad_alloc(); - default: - isc_throw(isc::InvalidOperation, std::strerror(result)); - } -} - -Mutex::~Mutex() { - if (impl_ != NULL) { - const int result = pthread_mutex_destroy(&impl_->mutex); - -#ifdef ENABLE_DEBUG - const bool locked = impl_->locked_count != 0; -#endif // ENABLE_DEBUG - - delete impl_; - // We don't want to throw from the destructor. Also, if this ever - // fails, something is really screwed up a lot. - assert(result == 0); - -#ifdef ENABLE_DEBUG - // We should not try to destroy a locked mutex, bad threaded monsters - // could get loose if we ever do and it is also forbidden by pthreads. - - // This should not be possible to happen, since the - // pthread_mutex_destroy should check for it already. But it seems - // there are systems that don't check it. - assert(!locked); -#endif // ENABLE_DEBUG - } -} - -#ifdef ENABLE_DEBUG - -void -Mutex::postLockAction() { - assert(impl_->locked_count == 0); - ++impl_->locked_count; -} - -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; -} - -bool -Mutex::locked() const { - return (impl_->locked_count != 0); -} - -#endif // ENABLE_DEBUG - -void -Mutex::lock() { - assert(impl_ != NULL); - const int result = pthread_mutex_lock(&impl_->mutex); - if (result != 0) { - isc_throw(isc::InvalidOperation, std::strerror(result)); - } -#ifdef ENABLE_DEBUG - postLockAction(); // Only in debug mode -#endif // ENABLE_DEBUG -} - -bool -Mutex::tryLock() { - assert(impl_ != NULL); - const int result = pthread_mutex_trylock(&impl_->mutex); - // In the case of pthread_mutex_trylock(), if it is called on a - // locked mutex from the same thread, some platforms (such as fedora - // and debian) return EBUSY whereas others (such as centos 5) return - // EDEADLK. We return false and don't pass the lock attempt in both - // cases. - if (result == EBUSY || result == EDEADLK) { - return (false); - } else if (result != 0) { - isc_throw(isc::InvalidOperation, std::strerror(result)); - } -#ifdef ENABLE_DEBUG - postLockAction(); // Only in debug mode -#endif // ENABLE_DEBUG - return (true); -} - -void -Mutex::unlock() { - assert(impl_ != NULL); -#ifdef ENABLE_DEBUG - preUnlockAction(false); // Only in debug mode. Ensure no throw. -#endif // ENABLE_DEBUG - const int result = pthread_mutex_unlock(&impl_->mutex); - assert(result == 0); // This should never be possible -} - -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) { -#ifdef ENABLE_DEBUG - mutex.preUnlockAction(true); // Only in debug mode - const int result = pthread_cond_wait(&impl_->cond_, &mutex.impl_->mutex); - mutex.postLockAction(); // Only in debug mode -#else - const int result = pthread_cond_wait(&impl_->cond_, &mutex.impl_->mutex); -#endif - // 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); -} - -} -} -} diff --git a/src/lib/util/threads/sync.h b/src/lib/util/threads/sync.h deleted file mode 100644 index 15e78fcb6e..0000000000 --- a/src/lib/util/threads/sync.h +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#ifndef KEA_THREAD_SYNC_H -#define KEA_THREAD_SYNC_H - -#include <exceptions/exceptions.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 dependent, 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-dependent 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 minimalist. 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 (unique_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 Exception thrown when the mutex is already locked and - /// a non-blocking locker is attempted around it. - struct AlreadyLocked : public isc::InvalidParameter { - AlreadyLocked(const char* file, size_t line, const char* what) : - isc::InvalidParameter(file, line, what) - {} - }; - - /// \brief Constructor. - /// - /// Locks the mutex. May block for extended period of time if - /// \c block is true. - /// - /// \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). - /// \throw AlreadyLocked if \c block is false and the mutex is - /// already locked. - Locker(Mutex& mutex, bool block = true) : - mutex_(mutex) - { - if (block) { - mutex.lock(); - } else { - if (!mutex.tryLock()) { - isc_throw(AlreadyLocked, "The mutex is already locked"); - } - } - } - - /// \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 needlessly - /// slow). - /// - /// \todo Disable in non-debug build - bool locked() const; - -private: - /// \brief Lock the mutex - /// - /// This method blocks until the mutex can be locked. - void lock(); - - /// \brief Try to lock the mutex - /// - /// This method doesn't block and returns immediately with a status - /// on whether the lock operation was successful. - /// - /// \return true if the lock was successful, false otherwise. - bool tryLock(); - - /// \brief Unlock the mutex - void unlock(); - -private: - friend class CondVar; - - // Commonly called after acquiring the lock, checking and updating - // internal state for debug. - // - // Note that this method is only available when the build is - // configured with debugging support. - 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). - // - // Note that this method is only available when the build is - // configured with debugging support. - void preUnlockAction(bool throw_ok); - - class Impl; - Impl* impl_; -}; - -/// \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 was 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. Also, Kea likely to not use threading model, so the usefulness -/// of this class is uncertain. -/// -/// \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: diff --git a/src/lib/util/threads/tests/.gitignore b/src/lib/util/threads/tests/.gitignore deleted file mode 100644 index d6d1ec87a1..0000000000 --- a/src/lib/util/threads/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/run_unittests diff --git a/src/lib/util/threads/tests/Makefile.am b/src/lib/util/threads/tests/Makefile.am deleted file mode 100644 index 81c5e5ef08..0000000000 --- a/src/lib/util/threads/tests/Makefile.am +++ /dev/null @@ -1,38 +0,0 @@ -SUBDIRS = . - -AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) -# XXX: we'll pollute the top builddir for creating a temporary test file -# # used to bind a UNIX domain socket so we can minimize the risk of exceeding -# # the limit of file name path size. -AM_CPPFLAGS += -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\" -AM_CXXFLAGS = $(KEA_CXXFLAGS) - -if USE_STATIC_LINK -AM_LDFLAGS = -static -endif - -CLEANFILES = *.gcno *.gcda - -TESTS_ENVIRONMENT = \ - $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) - -TESTS = -if HAVE_GTEST -TESTS += run_unittests -run_unittests_SOURCES = run_unittests.cc -run_unittests_SOURCES += thread_unittest.cc -run_unittests_SOURCES += lock_unittest.cc -run_unittests_SOURCES += condvar_unittest.cc - -run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) -run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) - -run_unittests_LDADD = $(top_builddir)/src/lib/util/threads/libkea-threads.la -run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la -run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la -run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la -run_unittests_LDADD += $(GTEST_LDADD) -endif - -noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/util/threads/tests/condvar_unittest.cc b/src/lib/util/threads/tests/condvar_unittest.cc deleted file mode 100644 index 856c9cda3f..0000000000 --- a/src/lib/util/threads/tests/condvar_unittest.cc +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <exceptions/exceptions.h> -#include <util/unittests/check_valgrind.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) { - if (!isc::util::unittests::runningOnValgrind()) { - 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); -} - -#ifdef HAS_UNDEFINED_PTHREAD_BEHAVIOR -TEST_F(CondVarTest, DISABLED_destroyWhileWait) { -#else -// This tests had to be disabled because it hangs on most of the OS used in lab -// TODO fix destroyWhileWait test -TEST_F(CondVarTest, DISABLED_destroyWhileWait) { -#endif - // We'll destroy a CondVar object while the thread is still waiting - // on it. This will trigger an assertion failure. - if (!isc::util::unittests::runningOnValgrind()) { - EXPECT_DEATH_IF_SUPPORTED({ - CondVar cond; - Mutex::Locker locker(mutex_); - Thread t(boost::bind(&signalAndWait, &cond, &mutex_)); - cond.wait(mutex_); - }, ""); - } -} - -#ifdef ENABLE_DEBUG - -TEST_F(CondVarTest, badWait) { - // In our implementation, wait() requires acquiring the lock beforehand. - EXPECT_THROW(condvar_.wait(mutex_), isc::InvalidOperation); -} - -#endif // ENABLE_DEBUG - -TEST_F(CondVarTest, emptySignal) { - // It's okay to call signal when no one waits. - EXPECT_NO_THROW(condvar_.signal()); -} - -} diff --git a/src/lib/util/threads/tests/lock_unittest.cc b/src/lib/util/threads/tests/lock_unittest.cc deleted file mode 100644 index 0ed9be0c13..0000000000 --- a/src/lib/util/threads/tests/lock_unittest.cc +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <gtest/gtest.h> - -#include <util/threads/sync.h> -#include <util/threads/thread.h> -#include <util/unittests/check_valgrind.h> - -#include <boost/bind.hpp> -#include <unistd.h> -#include <signal.h> - -using namespace isc::util::thread; - -namespace { - -#ifdef ENABLE_DEBUG - -// If we try to lock the debug mutex multiple times, it should -// throw. This test will complete properly only when pthread debugging -// facilities are enabled by configuring the code for debug build. -TEST(MutexTest, lockMultiple) { - Mutex mutex; - EXPECT_FALSE(mutex.locked()); // Debug-only build - - Mutex::Locker l1(mutex); - EXPECT_TRUE(mutex.locked()); // Debug-only build - - EXPECT_THROW({ - Mutex::Locker l2(mutex); // Attempt to lock again. - }, isc::InvalidOperation); - EXPECT_TRUE(mutex.locked()); // Debug-only build - - // block=true explicitly. - Mutex mutex2; - EXPECT_FALSE(mutex2.locked()); // Debug-only build - Mutex::Locker l12(mutex2, true); - EXPECT_TRUE(mutex2.locked()); // Debug-only build -} - -void -testThread(Mutex* mutex) -{ - // block=false (tryLock). This should not block indefinitely, but - // throw AlreadyLocked. If block were true, this would block - // indefinitely here. - EXPECT_THROW({ - Mutex::Locker l3(*mutex, false); - }, Mutex::Locker::AlreadyLocked); - - EXPECT_TRUE(mutex->locked()); // Debug-only build -} - -// Test the non-blocking variant using a second thread. -TEST(MutexTest, lockNonBlocking) { - // block=false (tryLock). - Mutex mutex; - Mutex::Locker l1(mutex, false); - EXPECT_TRUE(mutex.locked()); // Debug-only build - - // First, try another locker from the same thread. - EXPECT_THROW({ - Mutex::Locker l2(mutex, false); - }, Mutex::Locker::AlreadyLocked); - - EXPECT_TRUE(mutex.locked()); // Debug-only build - - // Now try another locker from a different thread. - Thread thread(boost::bind(&testThread, &mutex)); - thread.wait(); -} - -#endif // ENABLE_DEBUG - -// Destroying a locked mutex is a bad idea as well -#ifdef HAS_UNDEFINED_PTHREAD_BEHAVIOR -TEST(MutexTest, DISABLED_destroyLocked) { -#else -TEST(MutexTest, destroyLocked) { -#endif - if (!isc::util::unittests::runningOnValgrind()) { - EXPECT_DEATH_IF_SUPPORTED({ - Mutex* mutex = new Mutex; - new Mutex::Locker(*mutex); - delete mutex; - // This'll leak the locker, but inside the slave process, it should - // not be an issue. - }, ""); - } -} - -// In this test, we try to check if a mutex really locks. We could try that -// with a deadlock, but that's not practical (the test would not end). -// -// Instead, we try do to some operation on the same data from multiple threads -// that's likely to break if not locked. Also, the test must run for a while -// to have an opportunity to manifest. -// -// Currently we try incrementing a double variable. That one is large enough -// and complex enough so it should not be possible for the CPU to do it as an -// atomic operation, at least on common architectures. -const size_t iterations = 100000; - -void -performIncrement(volatile double* canary, volatile bool* ready_me, - volatile bool* ready_other, Mutex* mutex) -{ - // Loosely (busy) wait for the other thread so both will start - // approximately at the same time. - *ready_me = true; - while (!*ready_other) {} - - for (size_t i = 0; i < iterations; ++i) { - Mutex::Locker lock(*mutex); - *canary += 1; - } -} - -void -noHandler(int) {} - -TEST(MutexTest, swarm) { - if (!isc::util::unittests::runningOnValgrind()) { - // Create a timeout in case something got stuck here - struct sigaction ignored, original; - memset(&ignored, 0, sizeof(ignored)); - ignored.sa_handler = noHandler; - if (sigaction(SIGALRM, &ignored, &original)) { - FAIL() << "Couldn't set alarm"; - } - alarm(10); - // This type has a low chance of being atomic itself, further raising - // the chance of problems appearing. - double canary = 0; - Mutex mutex; - // Run two parallel threads - bool ready1 = false; - bool ready2 = false; - Thread t1(boost::bind(&performIncrement, &canary, &ready1, &ready2, - &mutex)); - Thread t2(boost::bind(&performIncrement, &canary, &ready2, &ready1, - &mutex)); - t1.wait(); - t2.wait(); - // Check it the sum is the expected value. - EXPECT_EQ(iterations * 2, canary) << "Threads are badly synchronized"; - // Cancel the alarm and return the original handler - alarm(0); - if (sigaction(SIGALRM, &original, NULL)) { - FAIL() << "Couldn't restore alarm"; - } - } -} - -} diff --git a/src/lib/util/threads/tests/run_unittests.cc b/src/lib/util/threads/tests/run_unittests.cc deleted file mode 100644 index d55ce68c6e..0000000000 --- a/src/lib/util/threads/tests/run_unittests.cc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <gtest/gtest.h> -#include <util/unittests/run_all.h> -#include <stdlib.h> - -// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writable -// directory. It will be used for creating a logger lockfile. - -int -main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - - setenv("KEA_LOCKFILE_DIR", TEST_DATA_TOPBUILDDIR, 1); - return (isc::util::unittests::run_all()); -} diff --git a/src/lib/util/threads/tests/thread_unittest.cc b/src/lib/util/threads/tests/thread_unittest.cc deleted file mode 100644 index bf95d3c008..0000000000 --- a/src/lib/util/threads/tests/thread_unittest.cc +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <util/process_spawn.h> -#include <util/threads/sync.h> -#include <util/threads/thread.h> -#include <util/unittests/check_valgrind.h> - -#include <boost/bind.hpp> -#include <boost/scoped_ptr.hpp> - -#include <gtest/gtest.h> - -#include <signal.h> -#include <unistd.h> - -// This file tests the Thread class. It's hard to test an actual thread is -// started, but we at least check the function is run and exceptions are -// propagated as they should. -// -// We run some tests multiple times to see if there happen to be a race -// condition (then it would have better chance showing up). -// -// The detached tests are not run as many times to prevent many threads being -// started in parallel (the other tests wait for the previous one to terminate -// before starting new one). - -using namespace isc::util; -using namespace isc::util::thread; - -namespace { -const size_t iterations = 200; -const size_t detached_iterations = 25; - -/// @brief Implements a thread which can be stopped. -/// -/// This class implements a worker thread which can be stopped by calling -/// StoppableThread::stop. The call to this function blocks until the thread -/// terminates. This class is useful for testing scenarios when the thread -/// needs to be run for a specific amount of time. -class StoppableThread { -public: - - /// @brief Constructor. - /// - /// It doesn't run the thread yet. It merely initializes required - /// class members. - StoppableThread() - : stopping_(false), mutex_(), thread_() { - } - - /// @brief Destructor. - /// - /// Detaches the thread. - ~StoppableThread() { - } - - /// @brief Starts the execution of the thread. - /// - /// This method will not start the thread if the thread has been stopped. - /// In order to start the thread again, @c StoppableThread::reset must be - /// called first. - void run() { - if (!amStopping()) { - thread_.reset(new Thread(boost::bind(&StoppableThread::loop, this))); - } - } - - /// @brief Stops the thread as soon as possible. - void stop() { - if (!amStopping()) { - setStopping(true); - if (thread_) { - thread_->wait(); - thread_.reset(); - } - } - } - - /// @brief Resets the thread state so as it can be ran again. - void reset() { - setStopping(false); - } - -private: - - /// @brief Checks if the thread is being stopped. - /// - /// @return true if the thread is being stopped. - bool amStopping() { - Mutex::Locker lock(mutex_); - return (stopping_); - } - - /// @brief Sets the stopping state. - /// - /// @param stopping New value for @c stopping_ state. - void setStopping(const bool stopping) { - Mutex::Locker lock(mutex_); - stopping_ = stopping; - } - - /// @brief Worker thread loop. - /// - /// It runs until the @c StoppableThread::stop is called. - void loop() { - for (;;) { - if (amStopping()) { - break; - } - usleep(100); - } - } - - /// @brief Flag indicating if the thread is being stopped. - bool stopping_; - /// @brief Mutex used for protecting @c stopping_ flag. - Mutex mutex_; - /// @brief Pointer to the thread instance. - boost::scoped_ptr<Thread> thread_; -}; - -/// @brief Static instance of the stoppable thread. -boost::scoped_ptr<StoppableThread> thread; - -/// @brief Test fixture class for testing @c Thread. -class ThreadTest : public ::testing::Test { -public: - - /// @brief Destructor. - /// - /// Stops the thread and resets the static pointer to - /// @c StoppableThread. - virtual ~ThreadTest() { - if (thread) { - thread->stop(); - } - thread.reset(); - } - - /// @brief No-op method. - static void doSomething(int*) { } - - /// @brief Marks specified boolean value as true to indicate that the - /// function has been run. - static void markRun(bool* mark) { - EXPECT_FALSE(*mark); - *mark = true; - } - - /// @brief Throws 42. - static void throwSomething() { - throw 42; // Throw something really unusual, to see everything is caught. - } - - /// @brief Throws standard exception. - static void throwException() { - throw std::exception(); - } - - /// @brief Returns signal mask set for a thread. - /// - /// @parm mask Pointer to signal mask set for the calling thread. - static void getSignalMask(sigset_t* mask) { - pthread_sigmask(SIG_SETMASK, 0, mask); - } -}; - - - -// We just test that we can forget about the thread and nothing -// bad will happen on our side. -TEST_F(ThreadTest, detached) { - if (!isc::util::unittests::runningOnValgrind()) { - int x; - for (size_t i = 0; i < detached_iterations; ++i) { - Thread thread(boost::bind(&ThreadTest::doSomething, &x)); - } - } -} - -// Wait for a thread to end first. The variable must be set at the time. -TEST_F(ThreadTest, wait) { - if (!isc::util::unittests::runningOnValgrind()) { - for (size_t i = 0; i < iterations; ++i) { - bool mark = false; - Thread thread(boost::bind(&ThreadTest::markRun, &mark)); - thread.wait(); - ASSERT_TRUE(mark) << "Not finished yet in " << i << "th iteration"; - // Can't wait second time - ASSERT_THROW(thread.wait(), isc::InvalidOperation); - } - } -} - -// Exception in the thread we forget about should not do anything to us -TEST_F(ThreadTest, detachedException) { - if (!isc::util::unittests::runningOnValgrind()) { - for (size_t i = 0; i < detached_iterations; ++i) { - Thread thread(&ThreadTest::throwSomething); - } - for (size_t i = 0; i < detached_iterations; ++i) { - Thread thread(&ThreadTest::throwException); - } - } -} - -// An uncaught exception in the thread should propagate through wait -TEST_F(ThreadTest, exception) { - if (!isc::util::unittests::runningOnValgrind()) { - for (size_t i = 0; i < iterations; ++i) { - Thread thread(throwSomething); - Thread thread2(throwException); - ASSERT_THROW(thread.wait(), Thread::UncaughtException); - ASSERT_THROW(thread2.wait(), Thread::UncaughtException); - } - } -} - -// Verify that all signals are blocked. -TEST_F(ThreadTest, sigmask) { - sigset_t mask; - sigemptyset(&mask); - Thread thread(boost::bind(&ThreadTest::getSignalMask, &mask)); - ASSERT_NO_THROW(thread.wait()); - EXPECT_EQ(1, sigismember(&mask, SIGHUP)); - EXPECT_EQ(1, sigismember(&mask, SIGINT)); - EXPECT_EQ(1, sigismember(&mask, SIGTERM)); -} - - -/// This test verifies using threads and spawning child processes -/// work together. -TEST_F(ThreadTest, spawnProcessWithThread) { - // Initialize and run the stoppable thread. Note that the 'thread' - // is a static variable, which will be 'cloned' into the child - // process. Its destructor must not be called when the child process - // terminates with EXIT_FAILURE status. - thread.reset(new StoppableThread()); - thread->run(); - - // Spawn the new process, using some non-existing executable. The - // current process will fork but the execvp should fail. - ProcessSpawn process_spawn("kea-dhcp4-a86570943h"); - pid_t pid = process_spawn.spawn(); - // Wait for the process to terminate. - while (process_spawn.isRunning(pid)) { - usleep(100); - } - // When the child process terminates it will call _exit() so - // nothing bad should happen from the child. - EXPECT_EQ(EXIT_FAILURE, process_spawn.getExitStatus(pid)); - // The thread is still there. - EXPECT_TRUE(thread); -} - -} diff --git a/src/lib/util/threads/thread.cc b/src/lib/util/threads/thread.cc deleted file mode 100644 index 54f67da6c8..0000000000 --- a/src/lib/util/threads/thread.cc +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <util/threads/thread.h> -#include <util/threads/sync.h> - -#include <memory> -#include <string> -#include <cstring> -#include <cerrno> - -#include <pthread.h> -#include <signal.h> - -#include <boost/noncopyable.hpp> -#include <boost/scoped_ptr.hpp> - -using std::string; -using std::exception; -using std::unique_ptr; -using boost::scoped_ptr; - -namespace isc { -namespace util { -namespace thread { - -namespace { - -// Signal blocker class. -class Blocker : boost::noncopyable { -public: - // Constructor blocks all signals - Blocker() { - sigset_t new_mask; - sigfillset(&new_mask); - pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask_); - } - - // Destructor restores the previous signal mask - ~Blocker() { - pthread_sigmask(SIG_SETMASK, &old_mask_, 0); - } - -private: - // The previous signal mask - sigset_t old_mask_; -}; - -} - -// The implementation of the Thread class. -// -// This internal state is not deleted until the thread terminates and is either -// waited for or detached. We could do this with shared_ptr (or, shared_ptr and -// weak_ptr), but we plan on compiling boost without thread support, so it -// might not be safe. Therefore we use an explicit mutex. It is being locked -// only 2-3 times in the lifetime of the thread, which should be negligible -// overhead anyway. -class Thread::Impl { -public: - Impl(const boost::function<void ()>& main) : - // Two things to happen before destruction - thread needs to terminate - // and the creating thread needs to release it. - waiting_(2), - main_(main), - exception_(false), - tid_(0) - {} - // Another of the waiting events is done. If there are no more, delete - // impl. - static void done(Impl* impl) { - bool should_delete(false); - { // We need to make sure the mutex is unlocked before it is deleted - Mutex::Locker locker(impl->mutex_); - if (--impl->waiting_ == 0) { - should_delete = true; - } - } - if (should_delete) { - delete impl; - } - } - // Run the thread. The type of parameter is because the pthread API. - static void* run(void* impl_raw) { - Impl* impl = static_cast<Impl*>(impl_raw); - try { - impl->main_(); - } catch (const exception& e) { - impl->exception_ = true; - impl->exception_text_ = e.what(); - } catch (...) { - impl->exception_ = true; - impl->exception_text_ = "Unknown exception"; - } - done(impl); - return (NULL); - } - // How many events are waiting? One is for the thread to finish, one - // for the destructor of Thread or wait. Once both happen, this is - // no longer needed. - size_t waiting_; - // The main function of the thread. - boost::function<void ()> main_; - // Was there an exception? - bool exception_; - string exception_text_; - // The mutex protects the waiting_ member, which ensures there are - // no race conditions and collisions when terminating. The other members - // should be safe, because: - // * tid_ is read only. - // * exception_ and exception_text_ is accessed outside of the thread - // only after join, by that time the thread must have terminated. - // * main_ is used in a read-only way here. If there are any shared - // resources used inside, it is up to the main_ itself to take care. - Mutex mutex_; - // Which thread are we talking about anyway? - pthread_t tid_; -}; - -Thread::Thread(const boost::function<void ()>& main) : - impl_(NULL) -{ - unique_ptr<Impl> impl(new Impl(main)); - Blocker blocker; - const int result = pthread_create(&impl->tid_, NULL, &Impl::run, - impl.get()); - // Any error here? - switch (result) { - case 0: // All 0K - impl_ = impl.release(); - break; - case EAGAIN: - throw std::bad_alloc(); - default: // Other errors. They should not happen. - isc_throw(isc::InvalidOperation, std::strerror(result)); - } -} - -Thread::~Thread() { - if (impl_ != NULL) { - // In case we didn't call wait yet - const int result = pthread_detach(impl_->tid_); - Impl::done(impl_); - impl_ = NULL; - // If the detach ever fails, something is screwed rather badly. - assert(result == 0); - } -} - -void -Thread::wait() { - if (impl_ == NULL) { - isc_throw(isc::InvalidOperation, - "Wait called and no thread to wait for"); - } - - const int result = pthread_join(impl_->tid_, NULL); - if (result != 0) { - isc_throw(isc::InvalidOperation, std::strerror(result)); - } - - // Was there an exception in the thread? - scoped_ptr<UncaughtException> ex; - // Something here could in theory throw. But we already terminated the thread, so - // we need to make sure we are in consistent state even in such situation (like - // releasing the mutex and impl_). - try { - if (impl_->exception_) { - ex.reset(new UncaughtException(__FILE__, __LINE__, - impl_->exception_text_.c_str())); - } - } catch (...) { - Impl::done(impl_); - impl_ = NULL; - // We have eaten the UncaughtException by now, but there's another - // exception instead, so we have at least something. - throw; - } - - Impl::done(impl_); - impl_ = NULL; - if (ex.get() != NULL) { - throw UncaughtException(*ex); - } -} - -} -} -} diff --git a/src/lib/util/threads/thread.h b/src/lib/util/threads/thread.h deleted file mode 100644 index b64621a094..0000000000 --- a/src/lib/util/threads/thread.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#ifndef KEA_THREAD_H -#define KEA_THREAD_H - -#include <exceptions/exceptions.h> - -#include <boost/noncopyable.hpp> -#include <boost/function.hpp> - -namespace isc { -namespace util { -/// \brief Wrappers for thread related functionality -/// -/// We provide our own wrappers, currently around pthreads. We tried using -/// the boost thread support, but it gave us some trouble, so we implemented -/// in-house ones. -namespace thread { - -/// \brief A separate thread. -/// -/// A thread of execution. When created, starts running in the background. -/// You can wait for it then or just forget it ever existed and leave it -/// live peacefully. -/// -/// The interface is minimalist for now. We may need to extend it later. -/// -/// \note While the objects of this class represent another thread, they -/// are not thread-safe. You're not supposed to call wait() on the same -/// object from multiple threads or so. They are reentrant (you can -/// wait for different threads from different threads). -class Thread : public boost::noncopyable { -public: - /// \brief There's an uncaught exception in a thread. - /// - /// When a thread terminates because it the main function of the thread - /// throws, this one is re-thrown out of wait() and contains the what - /// of the original exception. - class UncaughtException : public isc::Exception { - public: - UncaughtException(const char* file, size_t line, const char* what) : - Exception(file, line, what) - {} - }; - - /// \brief Create and start a thread. - /// - /// Create a new thread and run body inside it. - /// - /// If you need to pass parameters to body, or return some result, you - /// may just want to use boost::bind or alike to store them within the - /// body functor. - /// - /// \note The main functor will be copied internally. You need to consider - /// this when returning the result. - /// - /// The body should terminate by exiting the function. If it throws, it - /// is considered an error. You should generally catch any exceptions form - /// within there and handle them somehow. - /// - /// \param main The code to run inside the thread. - /// - /// \throw std::bad_alloc if allocation of the new thread or other - /// resources fails. - /// \throw isc::InvalidOperation for other errors (should not happen). - Thread(const boost::function<void()>& main); - - /// \brief Destructor. - /// - /// It is completely legitimate to destroy the thread without calling - /// wait() before. In such case, the thread will just live on until it - /// terminates. However, if the thread dies due to exception, for example, - /// it's up to you to detect that, no error is reported from this class. - /// - /// \throw isc::InvalidOperation in the rare case of OS reporting a - /// problem. This should not happen unless you messed up with the raw - /// thread by the low-level API. - ~Thread(); - - /// \brief Wait for the thread to terminate. - /// - /// Waits until the thread terminates. Must be called at most once. - /// - /// \throw isc::InvalidOperation if the OS API returns error. This usually - /// mean a programmer error (like two threads trying to wait on each - /// other). - /// \throw isc::InvalidOperation calling wait a second time. - /// \throw UncaughtException if the thread terminated by throwing an - /// exception instead of just returning from the function. - void wait(); -private: - class Impl; - Impl* impl_; -}; - -/// @brief Thread pointer type. -typedef boost::shared_ptr<Thread> ThreadPtr; - -} -} -} - -#endif |