New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
py/scheduler: Fix race with scheduler and pending exceptions. #8845
Conversation
I have updated this to only fix the race. Will leave the scheduler-on-main-thread for another PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic looks sound to me.
One thing that jumped out at me is that there is a potential difference of behavior with regard to pending exceptions when the scheduler is locked (this difference also existed before these changes so probably deserves a separate issue/PR if it is something to be concerned about). If the scheduler is disabled at compile time, then pending exceptions can be raised at times when the scheduler would be locked (in interrupt handlers, finalizers, etc). But if the scheduler is enabled then pending exceptions won't be raised during this time. With this PR, the condition also depends on threads being enabled or not but both behaviors still are possible.
That's a good point... We should definitely re-visit that as part of a discussion of moving the scheduler to the main thread only (I'd also like to consider per-thread scheduling... e.g. the ability to "schedule on same thread" etc). |
py/vm.c
Outdated
mp_handle_pending_tail(atomic_state); | ||
} else { | ||
MICROPY_END_ATOMIC_SECTION(atomic_state); | ||
MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to be careful here to make an atomic read-then-clear of mp_pending_exception
? At the moment it is:
mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception);
MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL;
What happens if an IRQ runs between those two statements and changes mp_pending_exception
? Eg if it's a ctrl-C (that overrides an existing non-ctrl-C pending exception) then that ctrl-C will be lost.
Also, the existing code checked that obj != MP_OBJ_NULL
after obtaining the atomic lock. Shouldn't the new code also do this?
Same comments apply to the new mp_handle_pending()
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated. I don't think the original code handled this case when the scheduler was disabled, but it's now protected for both configurations. Also added back the double check for pending exception.
The optimisation that allows a single check in the VM for either a pending exception or non-empty scheduler queue doesn't work when threading is enabled, as one thread can clear the sched_state if it has no pending exception, meaning the thread with the pending exception will never see it. This removes that optimisation for threaded builds. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Code size diff is now -24 bytes. Thinking about this some more, I'm not entirely sure I understand what the benefit for inlining this in the VM is... surely the only thing that needs to be done in the VM is the fast path to check sched_state/pending_exc, the actual processing can be offloaded by a call to mp_handle_pending -- I will send another PR as a comparison. (Edit: see #8869) |
Closed in favour of #8869. |
…ries-2024-01-26 frozen/: update frozen libraries
Alternative to #8838 that keeps per-thread pending exceptions. cc @dlech
The optimisation that allows a single check in the VM for either a pending exception or non-empty scheduler queue doesn't work when threading is enabled, as one thread can clear the sched_state if it has no pending exception, meaning the thread with the pending exception will never see it.
This removes that optimisation for threaded builds,
and also makes it so the scheduler only runs on the main thread (matching CPython behaviour). See #8838 (comment) for more background.This retains the single-check optimisation for the non-threading build, so performance is unaffected (actually marginally better). I will check on other ports.
The code size diff for PYBV11 is -56 bytes.