From 1c63a02e231f1c845cf9d726b3fb3f6c531b8758 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 14 Feb 2010 23:39:15 +0100 Subject: [PATCH] augment and round up the C++ thread wrapper --- src/backend/thread-wrapper.hpp | 99 ++++++++++++--- src/lib/result.hpp | 164 +++++++++++++++++++++++++ tests/lib/thread-wrapper-join-test.cpp | 16 ++- 3 files changed, 260 insertions(+), 19 deletions(-) create mode 100644 src/lib/result.hpp diff --git a/src/backend/thread-wrapper.hpp b/src/backend/thread-wrapper.hpp index 5c501675f..7185f18eb 100644 --- a/src/backend/thread-wrapper.hpp +++ b/src/backend/thread-wrapper.hpp @@ -29,6 +29,7 @@ #include "lib/error.hpp" #include "include/logging.h" #include "lib/bool-checkable.hpp" +#include "lib/result.hpp" extern "C" { #include "backend/threads.h" @@ -44,6 +45,9 @@ namespace backend { using std::tr1::bind; using std::tr1::function; using lib::Literal; + namespace error = lumiera::error; + using error::LUMIERA_ERROR_STATE; + using error::LUMIERA_ERROR_EXTERNAL; typedef struct nobug_flag* NoBugFlag; @@ -57,10 +61,41 @@ namespace backend { * - allows to bind to various kinds of functions including member functions * The new thread starts immediately within the ctor; after returning, the new * thread has already copied the arguments and indeed actively started to run. - * + * + * \par Joining, cancellation and memory management + * In the basic version (class Thread), the created thread is completely detached + * and not further controllable. There is no way to find out its execution state, + * wait on termination or even cancel it. Client code needs to implement such + * facilities explicitly, if needed. Care has to be taken with memory management, + * as there are no guarantees beyond the existence of the arguments bound into + * the operation functor. If the operation in the started thread needs additional + * storage, it has to manage it actively. + * + * There is an extended version (class ThreadJoinable) to allow at least to wait + * on the started thread's termination (joining). Building on this it is possible + * to create a self-contained "thread in an object"; the dtor of such an class + * must join to prevent pulling away member variables the thread function will + * continue to use. + * + * \par failures in the thread function + * The operation started in the new thread is protected by a top-level catch block. + * Error states or caught exceptions can be propagated through the lumiera_error + * state flag, when using the \c join() facility. By invoking \join().maybeThrow() + * on a join-able thread, exceptions can be propagated. + * @note any errorstate or caught exception detected on termination of a standard + * async Thread is considered a violation of policy and will result in emergency + * shutdown of the whole application. + * + * \par synchronisation barriers + * Lumiera threads provide a low-level synchronisation mechanism, which is used + * to secure the hand-over of additional arguments to the thread function. It + * can be used by client code, but care has to be taken to avoid getting out + * of sync. When invoking the #sync and #syncPoint functions, the caller will + * block until the counterpart has also invoked the corresponding function. + * If this doesn't happen, you'll block forever. */ class Thread - : boost::noncopyable //////TODO: do we want Thread instances to be copyable? + : boost::noncopyable { protected: @@ -82,9 +117,25 @@ namespace backend { lumiera_thread_sync (); // sync point: arguments handed over - _doIt_(); // execute the actual operation in the new thread + try + { + _doIt_(); // execute the actual operation in the new thread + } + + catch (std::exception& failure) + { + if (!lumiera_error_peek()) + LUMIERA_ERROR_SET (sync, STATE + ,failure.what()); + } + catch (...) + { + LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL + , "Thread terminated abnormally"); + } } + public: ThreadStartContext (LumieraThread& handle ,Operation const& operation_to_execute @@ -95,7 +146,6 @@ namespace backend { : operation_(operation_to_execute) { REQUIRE (!lumiera_error(), "Error pending at thread start") ; - TODO("the threadclass needs to become a parameter"); handle = lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags , &run // invoking the run helper and.. @@ -104,7 +154,8 @@ namespace backend { , logging_flag ); if (!handle) - lumiera::throwOnError(); + throw error::State ("Failed to start a new Thread for \"+purpose+\"" + , lumiera_error()); // make sure the new thread had the opportunity to take the Operation // prior to leaving and thereby possibly destroying this local context @@ -136,18 +187,21 @@ namespace backend { } + /** @note by design there is no possibility to find out + * just based on the thread handle, if the thread is alive. + * We define our own accounting here based on the internals + * of the thread wrapper. This will break down, if you mix + * uses of the C++ wrapper with the raw C functions. */ bool isValid() const { - return thread_ - && true ////////////TODO: how to determine that the thread is still running? - ; + return thread_; } /** Synchronisation barrier. In the function executing in this thread - * needs to be a corresponding lumiera_thread_sync() call. Blocking - * until both the caller and the thread have reached the barrier. + * needs to be a corresponding Thread::sync() call. Blocking until + * both the caller and the thread have reached the barrier. */ void sync () @@ -156,6 +210,16 @@ namespace backend { if (!lumiera_thread_sync_other (thread_)) lumiera::throwOnError(); } + + /** counterpart of the synchronisation barrier, to be called from + * within the thread to be synchronised. Will block until both + * this thread and the outward partner reached the barrier. + */ + static void + syncPoint () + { + lumiera_thread_sync (); + } }; @@ -181,19 +245,24 @@ namespace backend { /** put the caller into a blocking wait until this thread has terminated. - * @throws error::Logic if this thread has already terminated + * @return token signalling either success or failure. + * The caller can find out by invoking \c isValid() + * or \c maybeThrow() on this result token */ - void join() + lib::Result + join () { if (!isValid()) - throw lumiera::error::Logic ("joining on an already terminated thread"); + throw error::Logic ("joining on an already terminated thread"); lumiera_err errorInOtherThread = lumiera_thread_join (thread_); thread_ = 0; - + if (errorInOtherThread) - throw lumiera::error::State ("Thread terminated with error:", errorInOtherThread); + return error::State ("Thread terminated with error", errorInOtherThread); + else + return true; } }; diff --git a/src/lib/result.hpp b/src/lib/result.hpp new file mode 100644 index 000000000..8cc77beb9 --- /dev/null +++ b/src/lib/result.hpp @@ -0,0 +1,164 @@ +/* + RESULT.hpp - intermediary token representing the result of an operation + + Copyright (C) Lumiera.org + 2010, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file result.hpp + ** Intermediary value object to represent the result of an operation. + ** This operation might have produced a value result or failed with an exception. + ** Typically, the Result token used \em inline -- immediately either invoking one + ** of the member function or employing the built-in result type conversion. It + ** will be copyable iff the result value is copyable. There is an implicit + ** valid or failure state, which can be tested. Any attempt to get the value + ** of an invalid result token will cause in an exception to be thrown. + ** + ** @todo WIP and rather brainstorming as of 2/10 + ** + ** @see backend::ThreadJob usage example + */ + + + +#ifndef LIB_RESULT_H +#define LIB_RESULT_H + +//#include "pre.hpp" +#include "lib/error.hpp" +#include "lib/wrapper.hpp" +#include "lib/util.hpp" + +#include + + + +namespace lib { + + using util::isnil; + using std::string; + namespace error = lumiera::error; + + + + /** + * Result value and status of some operation. + * It can be created for passing a result produced + * by the operation, or the failure to do so. The value + * can be retrieved by implicit or explicit conversion. + * @throws on any attempt to access the value in case of failure + * @warning this class has a lot of implicit conversions; + * care should be taken when defining functions + * to take Result instances as parameter.... + */ + template + class Result + { + string failureLog_; + wrapper::ItemWrapper value_; + + public: + /** mark an invalid/failed result */ + Result () + : failureLog_("no result") + { } + + /** failed result, with reason given.*/ + Result (lumiera::Error const& reason) + : failureLog_(reason.what()) + { } + + /** standard case: valid result */ + Result (RES const& value) + : failureLog_("") + , value_(value) + { } + + + + bool + isValid() const + { + return value_.isValid(); + } + + void + maybeThrow() const + { + if (!isValid()) + throw error::State (failureLog_, lumiera_error_peek()); + } + + operator RES() const + { + maybeThrow(); + return *value_; + } + + template + TY + get() const + { + maybeThrow(); + return static_cast (*value_); + } + }; + + + /** + * Specialisation for signalling success or failure, + * without returning any value result. + */ + template<> + class Result + { + string failureLog_; + + public: + /** mark either failure (default) or success */ + Result (bool success =false) + : failureLog_(success? "": "operation failed") + { } + + /** failed result, with reason given.*/ + Result (lumiera::Error const& reason) + : failureLog_(reason.what()) + { } + + + + bool + isValid() const + { + return isnil (failureLog_); + } + + void + maybeThrow() const + { + if (!isValid()) + throw error::State (failureLog_, lumiera_error_peek()); + } + }; + + + + +} // namespace lib +#endif diff --git a/tests/lib/thread-wrapper-join-test.cpp b/tests/lib/thread-wrapper-join-test.cpp index 53836d5bf..0c207f930 100644 --- a/tests/lib/thread-wrapper-join-test.cpp +++ b/tests/lib/thread-wrapper-join-test.cpp @@ -114,11 +114,19 @@ namespace test { void getError() { - ThreadJoinable newThread("test Thread joining-2" - , bind (&ThreadWrapperJoin_test::theAction, this, DESTRUCTION_CODE) - ); + ThreadJoinable thread1("test Thread joining-3" + , bind (&ThreadWrapperJoin_test::theAction, this, DESTRUCTION_CODE) + ); - VERIFY_ERROR(SPECIAL, newThread.join() ); + VERIFY_ERROR(SPECIAL, thread1.join().maybeThrow() ); + + + + ThreadJoinable thread2("test Thread joining-4" + , bind (&ThreadWrapperJoin_test::theAction, this, DESTRUCTION_CODE) + ); + + ASSERT (!thread2.join().isValid() ); // can check success without throwing } };