From: Michal 'vorner' Vaner Date: Mon, 3 Sep 2012 12:13:14 +0000 (+0200) Subject: [2202] Implement Thread wrapper X-Git-Tag: trac2402_base~79^2~25 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3559ddcbe67e91941139c8c74ed77bb9be501dc4;p=thirdparty%2Fkea.git [2202] Implement Thread wrapper --- diff --git a/src/lib/util/threads/thread.cc b/src/lib/util/threads/thread.cc index 052566f6df..1393c9a777 100644 --- a/src/lib/util/threads/thread.cc +++ b/src/lib/util/threads/thread.cc @@ -13,3 +13,139 @@ // PERFORMANCE OF THIS SOFTWARE. #include "thread.h" +#include "lock.h" + +#include +#include +#include +#include + +#include + +using namespace std; + +namespace isc { +namespace util { +namespace thread { + +// 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& main) : + waiting_(2), + main_(main), + exception_(false) + {} + // 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 = reinterpret_cast(impl_raw); + try { + impl->main_(); + } + catch (const exception& e) { + Mutex::Locker locker(impl->mutex); + impl->exception_ = true; + impl->exception_text_ = e.what(); + } + catch (...) { + Mutex::Locker locker(impl->mutex); + impl->exception_ = true; + } + 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 main_; + // Was there an exception? + bool exception_; + string exception_text_; + Mutex mutex; + // Which thread are we talking about anyway? + pthread_t tid; +}; + +Thread::Thread(const boost::function& main) : + impl_(NULL) +{ + auto_ptr impl(new Impl(main)); + 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, strerror(result)); + } +} + +Thread::~ Thread() { + if (impl_ != NULL) { + // In case we didn't call wait yet + int result = pthread_detach(impl_->tid); + Impl::done(impl_); + impl_ = NULL; + if (result != 0) { + // Yes, really throwing from destructor. But this would + // mean someone really messed up the internal state, so + // we need to do something about it, even if it causes + // application to terminate. + isc_throw(isc::InvalidOperation, strerror(result)); + } + } +} + +void +Thread::wait() { + if (impl_ == NULL) { + isc_throw(isc::InvalidOperation, "Wait called and no thread to wait for"); + } + + int result = pthread_join(impl_->tid, NULL); + if (result != 0) { + isc_throw(isc::InvalidOperation, strerror(result)); + } + + // Was there an exception in the thread? + auto_ptr ex; + if (impl_->exception_) { + ex.reset(new UncaughtException(__FILE__, __LINE__, + impl_->exception_text_.c_str())); + } + 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 index 16aa7a59f0..0ddae72a66 100644 --- a/src/lib/util/threads/thread.h +++ b/src/lib/util/threads/thread.h @@ -31,6 +31,11 @@ namespace thread { /// live peacefully. /// /// The interface is minimalistic 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. @@ -56,11 +61,12 @@ public: /// is considered an error. You should generally catch any exceptions form /// within there and handle them somehow. /// - /// \param body The code to run inside the thread. + /// \param main The code to run inside the thread. /// /// \throw std::bad_alloc if allocation of the new thread or other resources /// fails. - Thread(boost::function body); + /// \throw isc::InvalidOperation for other errors (should not happen). + Thread(const boost::function& main); /// \brief Destructor. /// /// It is completely legitimate to destroy the thread without calling