Add a macro to check if a task that shouldn't yield actually yields.
This commit is contained in:
parent
8fa21821ae
commit
978de7885a
6 changed files with 98 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
35
include/fc/thread/non_preemptable_scope_check.hpp
Normal file
35
include/fc/thread/non_preemptable_scope_check.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
#include <fc/time.hpp>
|
||||
#include <fc/thread/spin_yield_lock.hpp>
|
||||
|
||||
// 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
|
||||
|
||||
|
|
@ -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&);
|
||||
|
|
|
|||
20
src/thread/non_preemptable_scope_check.cpp
Normal file
20
src/thread/non_preemptable_scope_check.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef NDEBUG
|
||||
#include <fc/thread/non_preemptable_scope_check.hpp>
|
||||
#include <fc/thread/thread.hpp>
|
||||
#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
|
||||
|
|
@ -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<int> 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() );
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,38 @@
|
|||
#include <fc/thread/thread.hpp>
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <fc/exception/exception.hpp>
|
||||
#include <fc/thread/non_preemptable_scope_check.hpp>
|
||||
|
||||
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 )
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue