Skip to content

Commit bd7ca43

Browse files
hjelmnartpol84
authored andcommitted
opal/sync: fix race condition
This commit fixes a race condition discovered by @artpol84. The race happens when a signalling thread decrements the sync count to 0 then goes to sleep. If the waiting thread runs and detects the count == 0 before going to sleep on the condition variable it will destroy the condition variable while the signalling thread is potentially still processing the completion. The fix is to add a non-atomic member to the sync structure that indicates another process is handling completion. Since the member will only be set to false by the initiating thread and the completing thread the variable does not need to be protected. When destoying a condition variable the waiting thread needs to wait until the singalling thread is finished. Thanks to @artpol84 for tracking this down. Fixes open-mpi#1813 Signed-off-by: Nathan Hjelm <[email protected]>
1 parent 5795682 commit bd7ca43

File tree

1 file changed

+18
-3
lines changed

1 file changed

+18
-3
lines changed

opal/threads/wait_sync.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,37 @@ typedef struct ompi_wait_sync_t {
2424
pthread_mutex_t lock;
2525
struct ompi_wait_sync_t *next;
2626
struct ompi_wait_sync_t *prev;
27+
volatile bool signaling;
2728
} ompi_wait_sync_t;
2829

2930
#define REQUEST_PENDING (void*)0L
3031
#define REQUEST_COMPLETED (void*)1L
3132

3233
#define SYNC_WAIT(sync) (opal_using_threads() ? sync_wait_mt (sync) : sync_wait_st (sync))
3334

35+
/* The loop in release handles a race condition between the signaling
36+
* thread and the destruction of the condition variable. The signaling
37+
* member will be set to false after the final signaling thread has
38+
* finished opertating on the sync object. This is done to avoid
39+
* extra atomics in the singalling function and keep it as fast
40+
* as possible. Note that the race window is small so spinning here
41+
* is more optimal than sleeping since this macro is called in
42+
* the critical path. */
3443
#define WAIT_SYNC_RELEASE(sync) \
3544
if (opal_using_threads()) { \
36-
pthread_cond_destroy(&(sync)->condition); \
37-
pthread_mutex_destroy(&(sync)->lock); \
45+
while ((sync)->signaling) { \
46+
continue; \
47+
} \
48+
pthread_cond_destroy(&(sync)->condition); \
49+
pthread_mutex_destroy(&(sync)->lock); \
3850
}
3951

4052
#define WAIT_SYNC_SIGNAL(sync) \
4153
if (opal_using_threads()) { \
4254
pthread_mutex_lock(&(sync->lock)); \
4355
pthread_cond_signal(&sync->condition); \
4456
pthread_mutex_unlock(&(sync->lock)); \
57+
sync->signaling = false; \
4558
}
4659

4760
OPAL_DECLSPEC int sync_wait_mt(ompi_wait_sync_t *sync);
@@ -61,6 +74,7 @@ static inline int sync_wait_st (ompi_wait_sync_t *sync)
6174
(sync)->next = NULL; \
6275
(sync)->prev = NULL; \
6376
(sync)->status = 0; \
77+
(sync)->signaling = true; \
6478
if (opal_using_threads()) { \
6579
pthread_cond_init (&(sync)->condition, NULL); \
6680
pthread_mutex_init (&(sync)->lock, NULL); \
@@ -81,8 +95,9 @@ static inline void wait_sync_update(ompi_wait_sync_t *sync, int updates, int sta
8195
}
8296
} else {
8397
/* this is an error path so just use the atomic */
84-
opal_atomic_swap_32 (&sync->count, 0);
8598
sync->status = OPAL_ERROR;
99+
opal_atomic_wmb ();
100+
opal_atomic_swap_32 (&sync->count, 0);
86101
}
87102
WAIT_SYNC_SIGNAL(sync);
88103
}

0 commit comments

Comments
 (0)