score: Fix race condition on SMP

We must ensure that the Thread_Control::Wait information update is
visible to the target thread before we update its wait flags, otherwise
we may return out of date events or a wrong status.

Close #2471.
This commit is contained in:
Sebastian Huber
2015-11-10 17:23:12 +01:00
parent 0c9bf40b89
commit 72857659c1
3 changed files with 68 additions and 42 deletions

View File

@@ -100,6 +100,12 @@ void _Event_Seize(
_Thread_Set_state( executing, block_state ); _Thread_Set_state( executing, block_state );
/*
* See _Event_Surrender() and _Thread_Timeout(), corresponding atomic
* variable is Thread_Control::Wait::flags.
*/
_Atomic_Fence( ATOMIC_ORDER_ACQUIRE );
success = _Thread_Wait_flags_try_change( success = _Thread_Wait_flags_try_change(
executing, executing,
intend_to_block, intend_to_block,

View File

@@ -34,6 +34,20 @@ static void _Event_Satisfy(
*(rtems_event_set *) the_thread->Wait.return_argument = seized_events; *(rtems_event_set *) the_thread->Wait.return_argument = seized_events;
} }
static bool _Event_Is_blocking_on_event(
const Thread_Control *the_thread,
Thread_Wait_flags wait_class
)
{
Thread_Wait_flags wait_flags;
Thread_Wait_flags wait_mask;
wait_flags = _Thread_Wait_flags_get( the_thread );
wait_mask = THREAD_WAIT_CLASS_MASK | THREAD_WAIT_STATE_READY_AGAIN;
return ( wait_flags & wait_mask ) == wait_class;
}
static bool _Event_Is_satisfied( static bool _Event_Is_satisfied(
const Thread_Control *the_thread, const Thread_Control *the_thread,
rtems_event_set pending_events, rtems_event_set pending_events,
@@ -59,46 +73,43 @@ void _Event_Surrender(
ISR_lock_Context *lock_context ISR_lock_Context *lock_context
) )
{ {
rtems_event_set pending_events; rtems_event_set pending_events;
rtems_event_set seized_events; rtems_event_set seized_events;
Thread_Wait_flags wait_flags; bool unblock;
bool unblock;
_Thread_Lock_acquire_default_critical( the_thread, lock_context ); _Thread_Lock_acquire_default_critical( the_thread, lock_context );
_Event_sets_Post( event_in, &event->pending_events ); _Event_sets_Post( event_in, &event->pending_events );
pending_events = event->pending_events; pending_events = event->pending_events;
wait_flags = _Thread_Wait_flags_get( the_thread );
if ( if (
( wait_flags & THREAD_WAIT_CLASS_MASK ) == wait_class _Event_Is_blocking_on_event( the_thread, wait_class )
&& _Event_Is_satisfied( the_thread, pending_events, &seized_events ) && _Event_Is_satisfied( the_thread, pending_events, &seized_events )
) { ) {
Thread_Wait_flags intend_to_block; Thread_Wait_flags ready_again;
Thread_Wait_flags blocked; bool success;
bool success;
intend_to_block = wait_class | THREAD_WAIT_STATE_INTEND_TO_BLOCK; _Event_Satisfy( the_thread, event, pending_events, seized_events );
blocked = wait_class | THREAD_WAIT_STATE_BLOCKED;
/* See _Event_Seize() */
_Atomic_Fence( ATOMIC_ORDER_RELEASE );
ready_again = wait_class | THREAD_WAIT_STATE_READY_AGAIN;
success = _Thread_Wait_flags_try_change_critical( success = _Thread_Wait_flags_try_change_critical(
the_thread, the_thread,
intend_to_block, wait_class | THREAD_WAIT_STATE_INTEND_TO_BLOCK,
wait_class | THREAD_WAIT_STATE_READY_AGAIN ready_again
); );
if ( success ) { if ( success ) {
_Event_Satisfy( the_thread, event, pending_events, seized_events );
unblock = false; unblock = false;
} else if ( _Thread_Wait_flags_get( the_thread ) == blocked ) {
_Event_Satisfy( the_thread, event, pending_events, seized_events );
_Thread_Wait_flags_set(
the_thread,
wait_class | THREAD_WAIT_STATE_READY_AGAIN
);
unblock = true;
} else { } else {
unblock = false; _Assert(
_Thread_Wait_flags_get( the_thread )
== wait_class | THREAD_WAIT_STATE_BLOCKED
);
_Thread_Wait_flags_set( the_thread, ready_again );
unblock = true;
} }
} else { } else {
unblock = false; unblock = false;

View File

@@ -39,35 +39,44 @@ void _Thread_Timeout( Objects_Id id, void *arg )
ISR_lock_Control *thread_lock; ISR_lock_Control *thread_lock;
ISR_lock_Context lock_context; ISR_lock_Context lock_context;
Thread_Wait_flags wait_flags; Thread_Wait_flags wait_flags;
Thread_Wait_flags wait_class;
Thread_Wait_flags intend_to_block;
Thread_Wait_flags blocked;
bool success;
bool unblock; bool unblock;
the_thread = arg; the_thread = arg;
thread_lock = _Thread_Lock_acquire( the_thread, &lock_context ); thread_lock = _Thread_Lock_acquire( the_thread, &lock_context );
wait_flags = _Thread_Wait_flags_get( the_thread ); wait_flags = _Thread_Wait_flags_get( the_thread );
wait_class = wait_flags & THREAD_WAIT_CLASS_MASK;
intend_to_block = wait_class | THREAD_WAIT_STATE_INTEND_TO_BLOCK;
blocked = wait_class | THREAD_WAIT_STATE_BLOCKED;
success = _Thread_Wait_flags_try_change_critical(
the_thread,
intend_to_block,
wait_class | THREAD_WAIT_STATE_READY_AGAIN
);
if ( success ) { if ( ( wait_flags & THREAD_WAIT_STATE_READY_AGAIN ) == 0 ) {
Thread_Wait_flags wait_class;
Thread_Wait_flags ready_again;
bool success;
_Thread_Do_timeout( the_thread ); _Thread_Do_timeout( the_thread );
unblock = false;
} else if ( _Thread_Wait_flags_get( the_thread ) == blocked ) { /*
_Thread_Wait_flags_set( * This fence is only necessary for the events, see _Event_Seize(). The
* thread queues use the thread lock for synchronization.
*/
_Atomic_Fence( ATOMIC_ORDER_RELEASE );
wait_class = wait_flags & THREAD_WAIT_CLASS_MASK;
ready_again = wait_class | THREAD_WAIT_STATE_READY_AGAIN;
success = _Thread_Wait_flags_try_change_critical(
the_thread, the_thread,
wait_class | THREAD_WAIT_STATE_READY_AGAIN wait_class | THREAD_WAIT_STATE_INTEND_TO_BLOCK,
ready_again
); );
_Thread_Do_timeout( the_thread );
unblock = true; if ( success ) {
unblock = false;
} else {
_Assert(
_Thread_Wait_flags_get( the_thread )
== wait_class | THREAD_WAIT_STATE_BLOCKED
);
_Thread_Wait_flags_set( the_thread, ready_again );
unblock = true;
}
} else { } else {
unblock = false; unblock = false;
} }