Skip to content

Commit 243805d

Browse files
jimmodpgeorge
authored andcommitted
py/scheduler: Fix race in checking scheduler pending state.
Because the atomic section starts after checking whether the scheduler state is pending, it's possible it can become a different state by the time the atomic section starts. This is especially likely on ports where MICROPY_BEGIN_ATOMIC_SECTION is implemented with a mutex (i.e. it might block), but the race exists regardless, i.e. if a context switch occurs between those two lines.
1 parent c2cfbcc commit 243805d

File tree

2 files changed

+29
-19
lines changed

2 files changed

+29
-19
lines changed

py/scheduler.c

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,27 @@ static inline bool mp_sched_empty(void) {
6060
void mp_handle_pending(bool raise_exc) {
6161
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
6262
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
63-
mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
64-
if (obj != MP_OBJ_NULL) {
65-
MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
66-
if (!mp_sched_num_pending()) {
67-
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
68-
}
69-
if (raise_exc) {
70-
MICROPY_END_ATOMIC_SECTION(atomic_state);
71-
nlr_raise(obj);
63+
// Re-check state is still pending now that we're in the atomic section.
64+
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
65+
mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
66+
if (obj != MP_OBJ_NULL) {
67+
MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
68+
if (!mp_sched_num_pending()) {
69+
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
70+
}
71+
if (raise_exc) {
72+
MICROPY_END_ATOMIC_SECTION(atomic_state);
73+
nlr_raise(obj);
74+
}
7275
}
76+
mp_handle_pending_tail(atomic_state);
77+
} else {
78+
MICROPY_END_ATOMIC_SECTION(atomic_state);
7379
}
74-
mp_handle_pending_tail(atomic_state);
7580
}
7681
}
7782

78-
// This function should only be called be mp_sched_handle_pending,
83+
// This function should only be called by mp_handle_pending,
7984
// or by the VM's inlined version of that function.
8085
void mp_handle_pending_tail(mp_uint_t atomic_state) {
8186
MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;

py/vm.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,18 +1366,23 @@ unwind_jump:;
13661366
#if MICROPY_ENABLE_SCHEDULER
13671367
// This is an inlined variant of mp_handle_pending
13681368
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
1369-
MARK_EXC_IP_SELECTIVE();
13701369
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
1371-
mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
1372-
if (obj != MP_OBJ_NULL) {
1373-
MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
1374-
if (!mp_sched_num_pending()) {
1375-
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
1370+
// Re-check state is still pending now that we're in the atomic section.
1371+
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
1372+
MARK_EXC_IP_SELECTIVE();
1373+
mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
1374+
if (obj != MP_OBJ_NULL) {
1375+
MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
1376+
if (!mp_sched_num_pending()) {
1377+
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
1378+
}
1379+
MICROPY_END_ATOMIC_SECTION(atomic_state);
1380+
RAISE(obj);
13761381
}
1382+
mp_handle_pending_tail(atomic_state);
1383+
} else {
13771384
MICROPY_END_ATOMIC_SECTION(atomic_state);
1378-
RAISE(obj);
13791385
}
1380-
mp_handle_pending_tail(atomic_state);
13811386
}
13821387
#else
13831388
// This is an inlined variant of mp_handle_pending

0 commit comments

Comments
 (0)