From 1b274d9b7304f028a7200e3e60c5ffcbb9dc0ae2 Mon Sep 17 00:00:00 2001 From: Oliver Kowalke Date: Tue, 22 Sep 2015 19:10:09 +0200 Subject: [PATCH] support for fiber-specific storage --- include/boost/fiber/all.hpp | 1 + include/boost/fiber/context.hpp | 35 +++++ include/boost/fiber/detail/fss.hpp | 61 ++++++++ include/boost/fiber/fss.hpp | 108 +++++++++++++ src/context.cpp | 43 +++++ test/Jamfile.v2 | 15 ++ test/test_fss.cpp | 241 +++++++++++++++++++++++++++++ 7 files changed, 504 insertions(+) create mode 100644 include/boost/fiber/detail/fss.hpp create mode 100644 include/boost/fiber/fss.hpp create mode 100644 test/test_fss.cpp diff --git a/include/boost/fiber/all.hpp b/include/boost/fiber/all.hpp index 9383cb1b..e7a6ac65 100644 --- a/include/boost/fiber/all.hpp +++ b/include/boost/fiber/all.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/fiber/context.hpp b/include/boost/fiber/context.hpp index 84145a17..7cb5b553 100644 --- a/include/boost/fiber/context.hpp +++ b/include/boost/fiber/context.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ #include #include +#include #include #include #include @@ -118,6 +120,29 @@ private: flag_interruption_requested = 1 << 6 }; + struct BOOST_FIBERS_DECL fss_data { + void * vp; + detail::fss_cleanup_function::ptr_t cleanup_function; + + fss_data() : + vp( nullptr), + cleanup_function() { + } + + fss_data( void * vp_, + detail::fss_cleanup_function::ptr_t const& fn) : + vp( vp_), + cleanup_function( fn) { + BOOST_ASSERT( cleanup_function); + } + + void do_cleanup() { + ( * cleanup_function)( vp); + } + }; + + typedef std::map< uintptr_t, fss_data > fss_data_t; + static thread_local context * active_; #if ! defined(BOOST_FIBERS_NO_ATOMICS) @@ -144,6 +169,7 @@ public: intrusive::constant_time_size< false > > wait_queue_t; private: + fss_data_t fss_data_; wait_queue_t wait_queue_; detail::spinlock splk_; @@ -249,6 +275,7 @@ public: terminated_hook_(), wait_hook_(), tp_( (std::chrono::steady_clock::time_point::max)() ), + fss_data_(), wait_queue_(), splk_() { } @@ -301,6 +328,14 @@ public: return 0 != ( flags_ & flag_interruption_requested); } + void * get_fss_data( void const * vp) const; + + void set_fss_data( + void const * vp, + detail::fss_cleanup_function::ptr_t const& cleanup_fn, + void * data, + bool cleanup_existing); + void request_interruption( bool req) noexcept; bool ready_is_linked(); diff --git a/include/boost/fiber/detail/fss.hpp b/include/boost/fiber/detail/fss.hpp new file mode 100644 index 00000000..49b90f68 --- /dev/null +++ b/include/boost/fiber/detail/fss.hpp @@ -0,0 +1,61 @@ + +// 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 tss.hpp from boost.thread + +#ifndef BOOST_FIBERS_DETAIL_FSS_H +#define BOOST_FIBERS_DETAIL_FSS_H + +#include +#include + +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { +namespace detail { + +class fss_cleanup_function { +private: + std::atomic< std::size_t > use_count_; + +public: + typedef intrusive_ptr< fss_cleanup_function > ptr_t; + + fss_cleanup_function() noexcept : + use_count_( 0) { + } + + virtual ~fss_cleanup_function() { + } + + virtual void operator()( void * data) = 0; + + friend inline + void intrusive_ptr_add_ref( fss_cleanup_function * p) noexcept { + ++p->use_count_; + } + + friend inline + void intrusive_ptr_release( fss_cleanup_function * p) { + if ( --p->use_count_ == 0) { + delete p; + } + } +}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBERS_DETAIL_FSS_H diff --git a/include/boost/fiber/fss.hpp b/include/boost/fiber/fss.hpp new file mode 100644 index 00000000..5bbfa493 --- /dev/null +++ b/include/boost/fiber/fss.hpp @@ -0,0 +1,108 @@ + +// 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 tss.hpp from boost.thread + +#ifndef BOOST_FIBERS_FSS_H +#define BOOST_FIBERS_FSS_H + +#include + +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { + +template< typename T > +class fiber_specific_ptr { +private: + struct default_cleanup_function : public detail::fss_cleanup_function { + void operator()( void * data) { + delete static_cast< T * >( data); + } + }; + + struct custom_cleanup_function : public detail::fss_cleanup_function { + void (*fn)(T*); + + explicit custom_cleanup_function( void(*fn_)(T*) ): + fn( fn_) { + } + + void operator()( void* data) { + if ( fn) { + fn( static_cast< T * >( data) ); + } + } + }; + + detail::fss_cleanup_function::ptr_t cleanup_fn_; + +public: + typedef T element_type; + + fiber_specific_ptr() : + cleanup_fn_( new default_cleanup_function() ) { + } + + explicit fiber_specific_ptr( void(*fn)(T*) ) : + cleanup_fn_( new custom_cleanup_function( fn) ) { + } + + ~fiber_specific_ptr() { + context * f( context::active() ); + if ( nullptr != f) { + f->set_fss_data( + this, cleanup_fn_, nullptr, true); + } + } + + fiber_specific_ptr( fiber_specific_ptr const&) = delete; + fiber_specific_ptr & operator=( fiber_specific_ptr const&) = delete; + + T * get() const { + BOOST_ASSERT( context::active() ); + + void * vp( context::active()->get_fss_data( this) ); + return static_cast< T * >( vp); + } + + T * operator->() const { + return get(); + } + + T & operator*() const { + return * get(); + } + + T * release() { + T * tmp = get(); + context::active()->set_fss_data( + this, cleanup_fn_, nullptr, false); + return tmp; + } + + void reset( T * t) { + T * c = get(); + if ( c != t) { + context::active()->set_fss_data( + this, cleanup_fn_, t, true); + } + } +}; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBERS_FSS_H diff --git a/src/context.cpp b/src/context.cpp index d24ddee1..62bb42d7 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -63,6 +63,7 @@ context::context( main_context_t) : terminated_hook_(), wait_hook_(), tp_( (std::chrono::steady_clock::time_point::max)() ), + fss_data_(), wait_queue_(), splk_() { } @@ -85,6 +86,7 @@ context::context( dispatcher_context_t, boost::context::preallocated const& pall terminated_hook_(), wait_hook_(), tp_( (std::chrono::steady_clock::time_point::max)() ), + fss_data_(), wait_queue_(), splk_() { } @@ -141,6 +143,11 @@ context::release() noexcept { // notify scheduler scheduler_->set_ready( ctx); } + // release fiber-specific-data + for ( fss_data_t::value_type & data : fss_data_) { + data.second.do_cleanup(); + } + fss_data_.clear(); } void @@ -220,6 +227,42 @@ context::request_interruption( bool req) noexcept { } } +void * +context::get_fss_data( void const * vp) const { + uintptr_t key( reinterpret_cast< uintptr_t >( vp) ); + fss_data_t::const_iterator i( fss_data_.find( key) ); + return fss_data_.end() != i ? i->second.vp : nullptr; +} + +void +context::set_fss_data( void const * vp, + detail::fss_cleanup_function::ptr_t const& cleanup_fn, + void * data, + bool cleanup_existing) { + BOOST_ASSERT( cleanup_fn); + uintptr_t key( reinterpret_cast< uintptr_t >( vp) ); + fss_data_t::iterator i( fss_data_.find( key) ); + if ( fss_data_.end() != i) { + if( cleanup_existing) { + i->second.do_cleanup(); + } + if ( nullptr != data) { + fss_data_.insert( + i, + std::make_pair( + key, + fss_data( data, cleanup_fn) ) ); + } else { + fss_data_.erase( i); + } + } else { + fss_data_.insert( + std::make_pair( + key, + fss_data( data, cleanup_fn) ) ); + } +} + bool context::ready_is_linked() { return ready_hook_.is_linked(); diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 6d5e778c..2898eee8 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -146,3 +146,18 @@ run test_bounded_channel.cpp : cxx11_variadic_macros cxx11_variadic_templates cxx14_initialized_lambda_captures ] ; + +run test_fss.cpp : +: : +[ requires cxx11_constexpr + cxx11_decltype + cxx11_deleted_functions + cxx11_explicit_conversion_operators + cxx11_hdr_tuple cxx11_lambdas + cxx11_noexcept + cxx11_nullptr + cxx11_template_aliases + cxx11_rvalue_references + cxx11_variadic_macros + cxx11_variadic_templates + cxx14_initialized_lambda_captures ] ; diff --git a/test/test_fss.cpp b/test/test_fss.cpp new file mode 100644 index 00000000..de107ba9 --- /dev/null +++ b/test/test_fss.cpp @@ -0,0 +1,241 @@ +// Copyright (C) 2001-2003 +// William E. Kempf +// Copyright (C) 2007 Anthony Williams +// +// 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) + +#include + +#include + +#include + +boost::fibers::mutex check_mutex; +boost::fibers::mutex fss_mutex; +int fss_instances = 0; +int fss_total = 0; + +struct fss_value_t { + fss_value_t() { + std::unique_lock lock(fss_mutex); + ++fss_instances; + ++fss_total; + value = 0; + } + ~fss_value_t() { + std::unique_lock lock(fss_mutex); + --fss_instances; + } + int value; +}; + +boost::fibers::fiber_specific_ptr fss_value; + +void fss_fiber() { + fss_value.reset(new fss_value_t()); + for (int i=0; i<1000; ++i) { + int& n = fss_value->value; + if (n != i) { + std::unique_lock lock(check_mutex); + BOOST_CHECK_EQUAL(n, i); + } + ++n; + } +} + +void fss() { + fss_instances = 0; + fss_total = 0; + + boost::fibers::fiber f1( fss_fiber); + boost::fibers::fiber f2( fss_fiber); + boost::fibers::fiber f3( fss_fiber); + boost::fibers::fiber f4( fss_fiber); + boost::fibers::fiber f5( fss_fiber); + f1.join(); + f2.join(); + f3.join(); + f4.join(); + f5.join(); + + std::cout + << "fss_instances = " << fss_instances + << "; fss_total = " << fss_total + << "\n"; + std::cout.flush(); + + BOOST_CHECK_EQUAL(fss_instances, 0); + BOOST_CHECK_EQUAL(fss_total, 5); +} + +void test_fss() { + boost::fibers::fiber( fss).join(); +} + +bool fss_cleanup_called=false; + +struct Dummy { +}; + +void fss_custom_cleanup(Dummy* d) { + delete d; + fss_cleanup_called=true; +} + +boost::fibers::fiber_specific_ptr fss_with_cleanup(fss_custom_cleanup); + +void fss_fiber_with_custom_cleanup() { + fss_with_cleanup.reset(new Dummy); +} + +void fss_with_custom_cleanup() { + boost::fibers::fiber f(fss_fiber_with_custom_cleanup); + try { + f.join(); + } catch(...) { + f.interrupt(); + f.join(); + throw; + } + + BOOST_CHECK(fss_cleanup_called); +} + +void test_fss_with_custom_cleanup() { + boost::fibers::fiber( fss_with_custom_cleanup).join(); +} + + +Dummy* fss_object=new Dummy; + +void fss_fiber_with_custom_cleanup_and_release() { + fss_with_cleanup.reset(fss_object); + fss_with_cleanup.release(); +} + +void do_test_fss_does_no_cleanup_after_release() { + fss_cleanup_called=false; + boost::fibers::fiber f(fss_fiber_with_custom_cleanup_and_release); + try { + f.join(); + } catch(...) { + f.interrupt(); + f.join(); + throw; + } + + BOOST_CHECK(!fss_cleanup_called); + if(!fss_cleanup_called) { + delete fss_object; + } +} + +struct dummy_class_tracks_deletions { + static unsigned deletions; + + ~dummy_class_tracks_deletions() { + ++deletions; + } +}; + +unsigned dummy_class_tracks_deletions::deletions=0; + +boost::fibers::fiber_specific_ptr fss_with_null_cleanup(NULL); + +void fss_fiber_with_null_cleanup(dummy_class_tracks_deletions* delete_tracker) { + fss_with_null_cleanup.reset(delete_tracker); +} + +void do_test_fss_does_no_cleanup_with_null_cleanup_function() { + dummy_class_tracks_deletions* delete_tracker=new dummy_class_tracks_deletions; + boost::fibers::fiber f( + std::bind( fss_fiber_with_null_cleanup,delete_tracker) ); + try { + f.join(); + } catch(...) { + f.interrupt(); + f.join(); + throw; + } + + BOOST_CHECK(!dummy_class_tracks_deletions::deletions); + if(!dummy_class_tracks_deletions::deletions) { + delete delete_tracker; + } +} + +void test_fss_does_no_cleanup_after_release() { + boost::fibers::fiber( do_test_fss_does_no_cleanup_after_release).join(); +} + +void test_fss_does_no_cleanup_with_null_cleanup_function() { + boost::fibers::fiber( do_test_fss_does_no_cleanup_with_null_cleanup_function).join(); +} + + +void fiber_with_local_fss_ptr() { + { + boost::fibers::fiber_specific_ptr local_fss(fss_custom_cleanup); + + local_fss.reset(new Dummy); + } + BOOST_CHECK(fss_cleanup_called); + fss_cleanup_called=false; +} + +void fss_does_not_call_cleanup_after_ptr_destroyed() { + boost::fibers::fiber(fiber_with_local_fss_ptr).join(); + BOOST_CHECK(!fss_cleanup_called); +} + +void test_fss_does_not_call_cleanup_after_ptr_destroyed() { + boost::fibers::fiber( fss_does_not_call_cleanup_after_ptr_destroyed).join(); +} + + +void fss_cleanup_not_called_for_null_pointer() { + boost::fibers::fiber_specific_ptr local_fss(fss_custom_cleanup); + local_fss.reset(new Dummy); + fss_cleanup_called=false; + local_fss.reset(0); + BOOST_CHECK(fss_cleanup_called); + fss_cleanup_called=false; + local_fss.reset(new Dummy); + BOOST_CHECK(!fss_cleanup_called); +} + +void test_fss_cleanup_not_called_for_null_pointer() { + boost::fibers::fiber( fss_cleanup_not_called_for_null_pointer).join(); +} + + +void fss_at_the_same_adress() { + for(int i=0; i<2; i++) { + boost::fibers::fiber_specific_ptr local_fss(fss_custom_cleanup); + local_fss.reset(new Dummy); + fss_cleanup_called=false; + BOOST_CHECK(fss_cleanup_called); + fss_cleanup_called=false; + BOOST_CHECK(!fss_cleanup_called); + } +} + +void test_fss_at_the_same_adress() { + boost::fibers::fiber( fss_at_the_same_adress).join(); +} + + +boost::unit_test::test_suite* init_unit_test_suite(int, char*[]) { + boost::unit_test::test_suite* test = + BOOST_TEST_SUITE("Boost.Fiber: fss test suite"); + + test->add(BOOST_TEST_CASE(test_fss)); + test->add(BOOST_TEST_CASE(test_fss_with_custom_cleanup)); + test->add(BOOST_TEST_CASE(test_fss_does_no_cleanup_after_release)); + test->add(BOOST_TEST_CASE(test_fss_does_no_cleanup_with_null_cleanup_function)); + test->add(BOOST_TEST_CASE(test_fss_does_not_call_cleanup_after_ptr_destroyed)); + test->add(BOOST_TEST_CASE(test_fss_cleanup_not_called_for_null_pointer)); + + return test; +}