diff --git a/include/boost/fiber/context.hpp b/include/boost/fiber/context.hpp index 2dc50d4f..615cfd93 100644 --- a/include/boost/fiber/context.hpp +++ b/include/boost/fiber/context.hpp @@ -233,6 +233,8 @@ public: void release() noexcept; + void join() noexcept; + bool is_main_context() const noexcept { return 0 != ( flags_ & flag_main_context); } @@ -269,7 +271,7 @@ public: inline static intrusive_ptr< context > make_dispatcher_context( scheduler * sched) { fixedsize_stack salloc; // use default satck-size - boost::context::stack_context sctx( salloc.allocate() ); + boost::context::stack_context sctx = salloc.allocate(); #if defined(BOOST_NO_CXX14_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) // reserve space for control structure std::size_t size = sctx.size - sizeof( context); @@ -297,7 +299,7 @@ static intrusive_ptr< context > make_dispatcher_context( scheduler * sched) { template< typename StackAlloc, typename Fn, typename ... Args > static intrusive_ptr< context > make_worker_context( StackAlloc salloc, Fn && fn, Args && ... args) { - boost::context::stack_context sctx( salloc.allocate() ); + boost::context::stack_context sctx = salloc.allocate(); #if defined(BOOST_NO_CXX14_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) // reserve space for control structure std::size_t size = sctx.size - sizeof( context); diff --git a/include/boost/fiber/exceptions.hpp b/include/boost/fiber/exceptions.hpp new file mode 100644 index 00000000..b02d68ae --- /dev/null +++ b/include/boost/fiber/exceptions.hpp @@ -0,0 +1,262 @@ + +// Copyright Oliver Kowalke 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// based on boost.thread + +#ifndef BOOST_fiber_EXCEPTIONS_H +#define BOOST_fiber_EXCEPTIONS_H + +#include +#include +#include + +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { + +class fiber_exception : public std::system_error { +public: + fiber_exception() : + std::system_error( 0, std::system_category() ) { + } + + fiber_exception( int sys_error_code) : + std::system_error( sys_error_code, std::system_category() ) { + } + + fiber_exception( int ev, const char * what_arg) : + std::system_error( std::error_code( ev, std::system_category() ), what_arg) { + } + + fiber_exception( int ev, const std::string & what_arg) : + std::system_error( std::error_code( ev, std::system_category() ), what_arg) { + } + + virtual ~fiber_exception() throw() { + } +}; + +class condition_error : public fiber_exception { +public: + condition_error() : + fiber_exception( 0, "Condition error") { + } + + condition_error( int ev) : + fiber_exception( ev, "Condition error") { + } + + condition_error( int ev, const char * what_arg) : + fiber_exception( ev, what_arg) { + } + + condition_error( int ev, const std::string & what_arg) : + fiber_exception( ev, what_arg) { + } +}; + +class lock_error : public fiber_exception { +public: + lock_error() : + fiber_exception( 0, "boost::lock_error") { + } + + lock_error( int ev) : + fiber_exception( ev, "boost::lock_error") { + } + + lock_error( int ev, const char * what_arg) : + fiber_exception( ev, what_arg) { + } + + lock_error( int ev, const std::string & what_arg) : + fiber_exception( ev, what_arg) { + } +}; + +class fiber_resource_error : public fiber_exception { +public: + fiber_resource_error() : + fiber_exception( + static_cast< int >( std::errc::resource_unavailable_try_again), + "boost::fiber_resource_error") { + } + + fiber_resource_error( int ev) : + fiber_exception( ev, "boost::fiber_resource_error") { + } + + fiber_resource_error( int ev, const char * what_arg) : + fiber_exception( ev, what_arg) { + } + + fiber_resource_error( int ev, const std::string & what_arg) : + fiber_exception( ev, what_arg) { + } +}; + +class invalid_argument : public fiber_exception { +public: + invalid_argument() : + fiber_exception( + static_cast< int >( std::errc::invalid_argument), + "boost::invalid_argument") { + } + + invalid_argument( int ev) : + fiber_exception( ev, "boost::invalid_argument") { + } + + invalid_argument( int ev, const char * what_arg) : + fiber_exception( ev, what_arg) { + } + + invalid_argument( int ev, const std::string & what_arg) : + fiber_exception( ev, what_arg) { + } +}; + +class logic_error : public fiber_exception { +public: + logic_error() : + fiber_exception( 0, "boost::logic_error") { + } + + logic_error( const char * what_arg) : + fiber_exception( 0, what_arg) { + } + + logic_error( int ev) : + fiber_exception( ev, "boost::logic_error") { + } + + logic_error( int ev, const char * what_arg) : + fiber_exception( ev, what_arg) { + } + + logic_error( int ev, const std::string & what_arg) : + fiber_exception( ev, what_arg) { + } +}; + +class fiber_interrupted : public fiber_exception { +public: + fiber_interrupted() : + fiber_exception( + static_cast< int >( std::errc::interrupted), + "boost::fiber_interrupted") { + } +}; + +enum class future_errc { + unknown = 0, + broken_promise, + future_already_retrieved, + promise_already_satisfied, + no_state +}; + +BOOST_FIBERS_DECL +std::error_category const& future_category() noexcept; + +}} + +namespace std { + +template<> +struct is_error_code_enum< boost::fibers::future_errc > : public true_type { +}; + +inline +std::error_code make_error_code( boost::fibers::future_errc e) noexcept { + return std::error_code( static_cast< int >( e), boost::fibers::future_category() ); +} + +inline +std::error_condition make_error_condition( boost::fibers::future_errc e) noexcept { + return std::error_condition( static_cast< int >( e), boost::fibers::future_category() ); +} + +} + +namespace boost { +namespace fibers { + +class future_error : public std::logic_error { +private: + std::error_code ec_; + +public: + future_error( std::error_code ec) : + logic_error( ec.message() ), + ec_( ec) { + } + + std::error_code const& code() const noexcept { + return ec_; + } + + const char* what() const throw() { + return code().message().c_str(); + } +}; + +class future_uninitialized : public future_error { +public: + future_uninitialized() : + future_error( std::make_error_code( future_errc::no_state) ) { + } +}; + +class future_already_retrieved : public future_error { +public: + future_already_retrieved() : + future_error( std::make_error_code( future_errc::future_already_retrieved) ) { + } +}; + +class broken_promise : public future_error { +public: + broken_promise() : + future_error( std::make_error_code( future_errc::broken_promise) ) { + } +}; + +class promise_already_satisfied : public future_error { +public: + promise_already_satisfied() : + future_error( std::make_error_code( future_errc::promise_already_satisfied) ) { + } +}; + +class promise_uninitialized : public future_error { +public: + promise_uninitialized() : + future_error( std::make_error_code( future_errc::no_state) ) { + } +}; + +class packaged_task_uninitialized : public future_error { +public: + packaged_task_uninitialized() : + future_error( std::make_error_code( future_errc::no_state) ) { + } +}; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_fiber_EXCEPTIONS_H diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 87b1ecc8..2cd8d87f 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -92,13 +92,15 @@ public: impl_.swap( other.impl_); } + id get_id() const noexcept { + return impl_ ? impl_->get_id() : id(); + } + bool joinable() const noexcept { return nullptr != impl_; } - id get_id() const noexcept { - return impl_ ? impl_->get_id() : id(); - } + void join(); }; inline diff --git a/include/boost/fiber/scheduler.hpp b/include/boost/fiber/scheduler.hpp index 3faaf2ae..0dbf3cea 100644 --- a/include/boost/fiber/scheduler.hpp +++ b/include/boost/fiber/scheduler.hpp @@ -34,7 +34,7 @@ private: intrusive::constant_time_size< false > > terminated_queue_t; context * main_ctx_; - intrusive_ptr< context > dispatching_ctx_; + intrusive_ptr< context > dispatcher_ctx_; ready_queue_t ready_queue_; terminated_queue_t terminated_queue_; bool shutdown_; @@ -55,10 +55,12 @@ public: void set_main_context( context *) noexcept; - void set_dispatching_context( intrusive_ptr< context >) noexcept; + void set_dispatcher_context( intrusive_ptr< context >) noexcept; void dispatch(); + void set_ready( context *) noexcept; + void set_terminated( context *) noexcept; void re_schedule( context *) noexcept; diff --git a/src/context.cpp b/src/context.cpp index f25e9e62..59e32b5f 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -20,12 +20,10 @@ static context * make_main_context() { static thread_local context main_ctx( main_context); // scheduler for this thread static thread_local scheduler sched; - // attach scheduler to main fiber context - main_ctx.set_scheduler( & sched); // attach main fiber context to scheduler sched.set_main_context( & main_ctx); - // create and attach dispatching fiber context to scheduler - sched.set_dispatching_context( + // create and attach dispatcher fiber context to scheduler + sched.set_dispatcher_context( make_dispatcher_context( & sched) ); return & main_ctx; } @@ -120,11 +118,25 @@ context::release() noexcept { wait_queue_t::iterator e = wait_queue_.end(); for ( wait_queue_t::iterator i = wait_queue_.begin(); i != e;) { context * f = & ( * i); + // remove fiber from wait-queue i = wait_queue_.erase( i); - //FIXME: signal that f might resume + // notify scheduler + scheduler_->set_ready( f); } } +void +context::join() noexcept { + // get active context + context * active_ctx = context::active(); + // push active context to wait-queue, member + // of the context which has to be joined by + // the active context + wait_queue_.push_back( * active_ctx); + // suspend active context + scheduler_->re_schedule( active_ctx); +} + bool context::wait_is_linked() { return wait_hook_.is_linked(); diff --git a/src/fiber.cpp b/src/fiber.cpp index 5c0fafc5..6b5c599e 100644 --- a/src/fiber.cpp +++ b/src/fiber.cpp @@ -10,6 +10,9 @@ #include +#include "boost/fiber/exceptions.hpp" +#include "boost/fiber/scheduler.hpp" + #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX #endif @@ -19,7 +22,27 @@ namespace fibers { void fiber::start_() { - //FIXME: spawn new fiber-context + context::active()->get_scheduler()->set_ready( impl_.get() ); +} + +void +fiber::join() { + if ( context::active()->get_id() == get_id() ) { + throw fiber_resource_error( static_cast< int >( std::errc::resource_deadlock_would_occur), + "boost fiber: trying to join itself"); + } + + if ( ! joinable() ) { + throw fiber_resource_error( static_cast< int >( std::errc::invalid_argument), + "boost fiber: fiber not joinable"); + } + + ptr_t tmp; + tmp.swap( impl_); + + tmp->join(); + + // FIXME: call interruption_point() } }} diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 8518c10f..c9158716 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -21,18 +21,12 @@ void scheduler::resume_( context * actx, context * ctx) { BOOST_ASSERT( nullptr != actx); BOOST_ASSERT( nullptr != ctx); + BOOST_ASSERT( actx->get_scheduler() == ctx->get_scheduler() ); // fiber next-to-run is same as current active-fiber // this might happen in context of this_fiber::yield() if ( actx == ctx) { return; } - // pass new fiber the scheduler of the current active fiber - // this might be necessary if the new fiber was migrated - // from another thread - // FIXME: mabye better don in the sched-algorithm (knows - // if fiber was migrated) - // -> performance issue? - ctx->set_scheduler( actx->get_scheduler() ); // assign new fiber to active-fiber context::active( ctx); // resume active-fiber == ctx @@ -42,7 +36,7 @@ scheduler::resume_( context * actx, context * ctx) { context * scheduler::get_next_() noexcept { BOOST_ASSERT( ! ready_queue_.empty() ); - context * ctx( & ready_queue_.front() ); + context * ctx = & ready_queue_.front(); ready_queue_.pop_front(); return ctx; } @@ -52,7 +46,7 @@ scheduler::release_terminated_() { terminated_queue_t::iterator e( terminated_queue_.end() ); for ( terminated_queue_t::iterator i( terminated_queue_.begin() ); i != e;) { - context * ctx( & ( * i) ); + context * ctx = & ( * i); i = terminated_queue_.erase( i); intrusive_ptr_release( ctx); } @@ -60,7 +54,7 @@ scheduler::release_terminated_() { scheduler::scheduler() noexcept : main_ctx_( nullptr), - dispatching_ctx_(), + dispatcher_ctx_(), ready_queue_(), terminated_queue_(), shutdown_( false) { @@ -68,15 +62,15 @@ scheduler::scheduler() noexcept : scheduler::~scheduler() noexcept { BOOST_ASSERT( nullptr != main_ctx_); - BOOST_ASSERT( nullptr != dispatching_ctx_.get() ); + BOOST_ASSERT( nullptr != dispatcher_ctx_.get() ); BOOST_ASSERT( context::active() == main_ctx_); - // signal dispatching context termination + // signal dispatcher context termination shutdown_ = true; // resume pending fibers resume_( main_ctx_, get_next_() ); - // deallocate dispatching-context - dispatching_ctx_.reset(); + // deallocate dispatcher-context + dispatcher_ctx_.reset(); // set main-context to nullptr main_ctx_ = nullptr; } @@ -85,41 +79,62 @@ void scheduler::set_main_context( context * main_ctx) noexcept { BOOST_ASSERT( nullptr != main_ctx); main_ctx_ = main_ctx; + main_ctx_->set_scheduler( this); } void -scheduler::set_dispatching_context( intrusive_ptr< context > dispatching_ctx) noexcept { - BOOST_ASSERT( dispatching_ctx); - dispatching_ctx_.swap( dispatching_ctx); - // add dispatching context to ready-queue +scheduler::set_dispatcher_context( intrusive_ptr< context > dispatcher_ctx) noexcept { + BOOST_ASSERT( dispatcher_ctx); + dispatcher_ctx_.swap( dispatcher_ctx); + // add dispatcher context to ready-queue // so it is the first element in the ready-queue // if the main context tries to suspend the first time - // the dispatching context is resumed and + // the dispatcher context is resumed and // scheduler::dispatch() is executed - ready_queue_.push_back( * dispatching_ctx_.get() ); + dispatcher_ctx_->set_scheduler( this); + ready_queue_.push_back( * dispatcher_ctx_.get() ); } void scheduler::dispatch() { while ( ! shutdown_) { release_terminated_(); + auto ctx = get_next_(); + // push dispatcher context to ready-queue + // so that ready-queue never becomes empty + auto active_ctx = context::active(); + ready_queue_.push_back( * active_ctx); + resume_( active_ctx, ctx); } release_terminated_(); - resume_( dispatching_ctx_.get(), main_ctx_); + resume_( dispatcher_ctx_.get(), main_ctx_); +} + +void +scheduler::set_ready( context * ctx) noexcept { + BOOST_ASSERT( nullptr != ctx); + BOOST_ASSERT( ! ctx->ready_is_linked() ); + BOOST_ASSERT( ! ctx->terminated_is_linked() ); + BOOST_ASSERT( ! ctx->wait_is_linked() ); + // set the scheduler for new fiber context + ctx->set_scheduler( this); + // push new fiber context to redy-queue + ready_queue_.push_back( * ctx); } void scheduler::set_terminated( context * ctx) noexcept { BOOST_ASSERT( nullptr != ctx); BOOST_ASSERT( ! ctx->ready_is_linked() ); + BOOST_ASSERT( ! ctx->terminated_is_linked() ); BOOST_ASSERT( ! ctx->wait_is_linked() ); terminated_queue_.push_back( * ctx); } void -scheduler::re_schedule( context * actx) noexcept { - BOOST_ASSERT( nullptr != actx); - resume_( actx, get_next_() ); +scheduler::re_schedule( context * active_ctx) noexcept { + BOOST_ASSERT( nullptr != active_ctx); + resume_( active_ctx, get_next_() ); } }} diff --git a/test/test_fiber.cpp b/test/test_fiber.cpp index a546c641..0fbf928f 100644 --- a/test/test_fiber.cpp +++ b/test/test_fiber.cpp @@ -12,17 +12,31 @@ #include +int value1 = 0; + +void fn1() { + value1 = 1; +} + void test_scheduler_dtor() { boost::fibers::context * ctx( boost::fibers::context::active() ); (void)ctx; } +void test_join() { + value1 = 0; + boost::fibers::fiber f( fn1); + f.join(); + BOOST_CHECK_EQUAL( value1, 1); +} + boost::unit_test::test_suite * init_unit_test_suite( int, char* []) { boost::unit_test::test_suite * test = BOOST_TEST_SUITE("Boost.Fiber: fiber test suite"); test->add( BOOST_TEST_CASE( & test_scheduler_dtor) ); + test->add( BOOST_TEST_CASE( & test_join) ); return test; }