diff --git a/CMakeLists.txt b/CMakeLists.txt index 773adae..2f7e9c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ set( fc_sources src/thread/spin_lock.cpp src/thread/spin_yield_lock.cpp src/thread/mutex.cpp + src/thread/non_preemptable_scope_check.cpp src/asio.cpp src/string.cpp src/shared_ptr.cpp diff --git a/include/fc/thread/non_preemptable_scope_check.hpp b/include/fc/thread/non_preemptable_scope_check.hpp new file mode 100644 index 0000000..e52a1b9 --- /dev/null +++ b/include/fc/thread/non_preemptable_scope_check.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +// This file defines a macro: +// ASSERT_TASK_NOT_PREEMPTED() +// This macro is used to declare that the current scope is not expected to yield. +// If the task does yield, it will generate an assertion. +// +// Use this when you you're coding somethign that must not yield, and you think you +// have done it (just by virtue of the fact that you don't think you've called any +// functions that could yield). This will help detect when your assumptions are +// wrong and you accidentally call something that yields. +// +// This has no cost in release mode, and is extremely cheap in debug mode. + +#define FC_NON_PREEMPTABLE_SCOPE_COMBINE_HELPER(x,y) x ## y +#define FC_NON_PREEMPTABLE_SCOPE_COMBINE(x,y) FC_NON_PREEMPTABLE_SCOPE_COMBINE_HELPER(x,y) + +#ifdef NDEBUG +# define ASSERT_TASK_NOT_PREEMPTED() do {} while (0) +#else +# define ASSERT_TASK_NOT_PREEMPTED() fc::non_preemptable_scope_check FC_NON_PREEMPTABLE_SCOPE_COMBINE(scope_checker_, __LINE__) + +namespace fc +{ + class non_preemptable_scope_check + { + public: + non_preemptable_scope_check(); + ~non_preemptable_scope_check(); + }; +} // namespace fc +#endif + diff --git a/include/fc/thread/thread.hpp b/include/fc/thread/thread.hpp index e6f01c8..1b99b80 100644 --- a/include/fc/thread/thread.hpp +++ b/include/fc/thread/thread.hpp @@ -119,6 +119,9 @@ namespace fc { friend class promise_base; friend class thread_d; friend class mutex; +#ifndef NDEBUG + friend class non_preemptable_scope_check; +#endif friend void yield(); friend void usleep(const microseconds&); friend void sleep_until(const time_point&); diff --git a/src/thread/non_preemptable_scope_check.cpp b/src/thread/non_preemptable_scope_check.cpp new file mode 100644 index 0000000..574bdd4 --- /dev/null +++ b/src/thread/non_preemptable_scope_check.cpp @@ -0,0 +1,20 @@ +#ifndef NDEBUG +#include +#include +#include "thread_d.hpp" + + +namespace fc +{ + non_preemptable_scope_check::non_preemptable_scope_check() + { + ++thread::current().my->non_preemptable_scope_count; + } + + non_preemptable_scope_check::~non_preemptable_scope_check() + { + assert(thread::current().my->non_preemptable_scope_count > 0); + --thread::current().my->non_preemptable_scope_count; + } +} // fc +#endif \ No newline at end of file diff --git a/src/thread/thread_d.hpp b/src/thread/thread_d.hpp index f533dd7..b40c096 100644 --- a/src/thread/thread_d.hpp +++ b/src/thread/thread_d.hpp @@ -27,6 +27,9 @@ namespace fc { ready_head(0), ready_tail(0), blocked(0) +#ifndef NDEBUG + ,non_preemptable_scope_count(0) +#endif { static boost::atomic cnt(0); name = fc::string("th_") + char('a'+cnt++); @@ -84,8 +87,9 @@ namespace fc { fc::context* ready_tail; fc::context* blocked; - - +#ifndef NDEBUG + unsigned non_preemptable_scope_count; +#endif #if 0 void debug( const fc::string& s ) { @@ -284,6 +288,7 @@ namespace fc { * have it wait for something to do. */ bool start_next_fiber( bool reschedule = false ) { + assert(non_preemptable_scope_count == 0); check_for_timeouts(); if( !current ) current = new fc::context( &fc::thread::current() ); diff --git a/tests/task_cancel.cpp b/tests/task_cancel.cpp index c0c76f6..07c107e 100644 --- a/tests/task_cancel.cpp +++ b/tests/task_cancel.cpp @@ -4,6 +4,38 @@ #include #include #include +#include + +BOOST_AUTO_TEST_CASE( test_non_preemptable_assertion ) +{ + return; // this isn't a real test, because the thing it tries to test works by asserting, not by throwing + fc::usleep(fc::milliseconds(10)); // this should not assert + { + ASSERT_TASK_NOT_PREEMPTED(); + fc::usleep(fc::seconds(1)); // this should assert + } + + { + ASSERT_TASK_NOT_PREEMPTED(); + { + ASSERT_TASK_NOT_PREEMPTED(); + fc::usleep(fc::seconds(1)); // this should assert + } + } + + { + ASSERT_TASK_NOT_PREEMPTED(); + { + ASSERT_TASK_NOT_PREEMPTED(); + int i = 4; + i += 2; + } + fc::usleep(fc::seconds(1)); // this should assert + } + + fc::usleep(fc::seconds(1)); // this should not assert + BOOST_TEST_PASSPOINT(); +} BOOST_AUTO_TEST_CASE( cancel_an_active_task ) {