Parcourir la source

[2202] Implement Thread wrapper

Michal 'vorner' Vaner il y a 12 ans
Parent
commit
3559ddcbe6
2 fichiers modifiés avec 144 ajouts et 2 suppressions
  1. 136 0
      src/lib/util/threads/thread.cc
  2. 8 2
      src/lib/util/threads/thread.h

+ 136 - 0
src/lib/util/threads/thread.cc

@@ -13,3 +13,139 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include "thread.h"
+#include "lock.h"
+
+#include <memory>
+#include <string>
+#include <cstring>
+#include <cerrno>
+
+#include <pthread.h>
+
+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<void ()>& 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*>(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<void ()> 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<void ()>& main) :
+    impl_(NULL)
+{
+    auto_ptr<Impl> 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<UncaughtException> 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);
+    }
+}
+
+}
+}
+}

+ 8 - 2
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<void()> body);
+    /// \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