Add a macro to check if a task that shouldn't yield actually yields.

This commit is contained in:
Eric Frias 2014-08-02 19:43:28 -04:00
parent 8fa21821ae
commit 978de7885a
6 changed files with 98 additions and 2 deletions

View file

@ -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

View 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

View file

@ -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&);

View 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

View file

@ -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() );

View file

@ -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 )
{