When locking a mutex, ensure the task has a context before attempting to lock.
This commit is contained in:
parent
d188b138d6
commit
976bbce668
2 changed files with 73 additions and 23 deletions
|
|
@ -23,19 +23,22 @@ namespace fc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param next - is set to the next context to get the lock.
|
* @param last_context - is set to the next context to get the lock (the next-to-last element of the list)
|
||||||
* @return the last context (the one with the lock)
|
* @return the last context (the one with the lock)
|
||||||
*/
|
*/
|
||||||
static fc::context* get_tail( fc::context* h, fc::context*& next ) {
|
static fc::context* get_tail( fc::context* list_head, fc::context*& context_to_unblock ) {
|
||||||
next = 0;
|
context_to_unblock = 0;
|
||||||
fc::context* n = h;
|
fc::context* list_context_iter = list_head;
|
||||||
if( !n ) return n;
|
if( !list_context_iter )
|
||||||
while( n->next_blocked_mutex ) {
|
return list_context_iter;
|
||||||
next = n;
|
while( list_context_iter->next_blocked_mutex )
|
||||||
n=n->next_blocked_mutex;
|
{
|
||||||
|
context_to_unblock = list_context_iter;
|
||||||
|
list_context_iter = list_context_iter->next_blocked_mutex;
|
||||||
}
|
}
|
||||||
return n;
|
return list_context_iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fc::context* remove( fc::context* head, fc::context* target ) {
|
static fc::context* remove( fc::context* head, fc::context* target ) {
|
||||||
fc::context* c = head;
|
fc::context* c = head;
|
||||||
fc::context* p = 0;
|
fc::context* p = 0;
|
||||||
|
|
@ -114,17 +117,21 @@ namespace fc {
|
||||||
}
|
}
|
||||||
|
|
||||||
void mutex::lock() {
|
void mutex::lock() {
|
||||||
fc::context* n = 0;
|
fc::context* current_context = fc::thread::current().my->current;
|
||||||
fc::context* cc = fc::thread::current().my->current;
|
if( !current_context )
|
||||||
|
current_context = fc::thread::current().my->current = new fc::context( &fc::thread::current() );
|
||||||
|
|
||||||
{
|
{
|
||||||
fc::unique_lock<fc::spin_yield_lock> lock(m_blist_lock);
|
fc::unique_lock<fc::spin_yield_lock> lock(m_blist_lock);
|
||||||
if( !m_blist ) {
|
if( !m_blist ) {
|
||||||
m_blist = cc;
|
m_blist = current_context;
|
||||||
|
assert(!current_context->next_blocked_mutex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow recusive locks
|
// allow recusive locks
|
||||||
if ( get_tail( m_blist, n ) == cc ) {
|
fc::context* dummy_context_to_unblock = 0;
|
||||||
|
if ( get_tail( m_blist, dummy_context_to_unblock ) == current_context ) {
|
||||||
assert(false);
|
assert(false);
|
||||||
// EMF: I think recursive locks are currently broken -- we need to
|
// EMF: I think recursive locks are currently broken -- we need to
|
||||||
// keep track of how many times this mutex has been locked by the
|
// keep track of how many times this mutex has been locked by the
|
||||||
|
|
@ -132,9 +139,10 @@ namespace fc {
|
||||||
// the next context only if the count drops to zero
|
// the next context only if the count drops to zero
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cc->next_blocked_mutex = m_blist;
|
current_context->next_blocked_mutex = m_blist;
|
||||||
m_blist = cc;
|
m_blist = current_context;
|
||||||
|
|
||||||
|
#if 0
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
auto i = m_blist;
|
auto i = m_blist;
|
||||||
while( i ) {
|
while( i ) {
|
||||||
|
|
@ -142,25 +150,26 @@ namespace fc {
|
||||||
++cnt;
|
++cnt;
|
||||||
}
|
}
|
||||||
//wlog( "wait queue len %1%", cnt );
|
//wlog( "wait queue len %1%", cnt );
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fc::thread::current().yield(false);
|
fc::thread::current().yield(false);
|
||||||
BOOST_ASSERT( cc->next_blocked_mutex == 0 );
|
BOOST_ASSERT( current_context->next_blocked_mutex == 0 );
|
||||||
} catch ( ... ) {
|
} catch ( ... ) {
|
||||||
wlog( "lock threw" );
|
wlog( "lock threw" );
|
||||||
cleanup( *this, m_blist_lock, m_blist, cc);
|
cleanup( *this, m_blist_lock, m_blist, current_context);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mutex::unlock() {
|
void mutex::unlock() {
|
||||||
fc::context* next = 0;
|
fc::context* context_to_unblock = 0;
|
||||||
{ fc::unique_lock<fc::spin_yield_lock> lock(m_blist_lock);
|
{ fc::unique_lock<fc::spin_yield_lock> lock(m_blist_lock);
|
||||||
get_tail(m_blist, next);
|
get_tail(m_blist, context_to_unblock);
|
||||||
if( next ) {
|
if( context_to_unblock ) {
|
||||||
next->next_blocked_mutex = 0;
|
context_to_unblock->next_blocked_mutex = 0;
|
||||||
next->ctx_thread->my->unblock( next );
|
context_to_unblock->ctx_thread->my->unblock( context_to_unblock );
|
||||||
} else {
|
} else {
|
||||||
m_blist = 0;
|
m_blist = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,54 @@ BOOST_AUTO_TEST_CASE( leave_mutex_locked )
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
fc::mutex test_mutex;
|
fc::mutex test_mutex;
|
||||||
fc::future<void> test_task = fc::async([&](){ fc::scoped_lock<fc::mutex> test_lock(test_mutex); for (int i = 0; i < 10; ++i) fc::usleep(fc::seconds(1));});
|
fc::future<void> test_task = fc::async([&](){
|
||||||
|
fc::scoped_lock<fc::mutex> test_lock(test_mutex);
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
fc::usleep(fc::seconds(1));
|
||||||
|
}, "test_task");
|
||||||
fc::usleep(fc::seconds(3));
|
fc::usleep(fc::seconds(3));
|
||||||
test_task.cancel_and_wait();
|
test_task.cancel_and_wait();
|
||||||
}
|
}
|
||||||
BOOST_TEST_PASSPOINT();
|
BOOST_TEST_PASSPOINT();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( cancel_task_blocked_on_mutex)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
fc::mutex test_mutex;
|
||||||
|
fc::future<void> test_task;
|
||||||
|
{
|
||||||
|
fc::scoped_lock<fc::mutex> test_lock(test_mutex);
|
||||||
|
test_task = fc::async([&test_mutex](){
|
||||||
|
BOOST_TEST_MESSAGE("--- In test_task, locking mutex");
|
||||||
|
fc::scoped_lock<fc::mutex> async_task_test_lock(test_mutex);
|
||||||
|
BOOST_TEST_MESSAGE("--- In test_task, mutex locked, commencing sleep");
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
fc::usleep(fc::seconds(1));
|
||||||
|
BOOST_TEST_MESSAGE("--- In test_task, sleeps done, exiting");
|
||||||
|
}, "test_task");
|
||||||
|
fc::usleep(fc::seconds(3));
|
||||||
|
test_task.cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
test_task.wait(fc::seconds(1));
|
||||||
|
BOOST_ERROR("test should have been canceled, not exited cleanly");
|
||||||
|
}
|
||||||
|
catch (const fc::canceled_exception&)
|
||||||
|
{
|
||||||
|
BOOST_TEST_PASSPOINT();
|
||||||
|
}
|
||||||
|
catch (const fc::timeout_exception&)
|
||||||
|
{
|
||||||
|
BOOST_ERROR("unable to cancel task blocked on mutex");
|
||||||
|
}
|
||||||
|
BOOST_TEST_MESSAGE("Unlocking mutex locked from the main task so the test task will have the opportunity to lock it and be canceled");
|
||||||
|
}
|
||||||
|
test_task.cancel_and_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE( test_non_preemptable_assertion )
|
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
|
return; // this isn't a real test, because the thing it tries to test works by asserting, not by throwing
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue