-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
core,rp2,esp8266,windows, unix: Add new cross-port functions for event waiting and handling. #13096
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
core,rp2,esp8266,windows, unix: Add new cross-port functions for event waiting and handling. #13096
Conversation
1ca5e66
to
b750adc
Compare
Code size report:
|
f1aab4a
to
7793250
Compare
Hmm, so the challenge from changing macros to functions here is ports with threading enabled. A lot of the ports with threading call This creates a layering violation with Options would seem to be:
A version of the second option seems better from the point of view of having headers that "include what they use" and layer nicely, but the first option will be quick and easy... |
Oh, I thought of a third option: Convert (Unlike the "nowait" version, these generally get called in places where I don't expect the overhead of one more function call will be very noticeable.) |
2674222
to
881214b
Compare
py/runtime.h
Outdated
@@ -26,6 +26,7 @@ | |||
#ifndef MICROPY_INCLUDED_PY_RUNTIME_H | |||
#define MICROPY_INCLUDED_PY_RUNTIME_H | |||
|
|||
#include "py/mphal.h" |
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.
This breaks the example/natmod builds... I think just remove it from here. This header should only be included from .c files.
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.
This is tricky. Without a definition of MICROPY_INTERNAL_WFE
, including runtime.h
will fail (the difference between macros and inline functions is they force you to structure your headers with a more layered "include what you use"!)
Conceptually these "event" functions are a bit different, in as much as runtime.h is "pure Python runtime", mphal.h is "hardware abstractions", and then event functions are a bridge between the two.
I've updated this PR to put them into a new py/event.h
header, which solves these kind of conflicts (and allows removing all the messing about with esp8266 headers, too.)
Let me know what you think.
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.
I'm pretty sure I tried just removing this include and the build worked.... but maybe I didn't test enough.
How about putting these inline functions in py/mphal.h
? As you say, they are half hardware abstraction functions.
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.
How about putting these inline functions in
py/mphal.h
? As you say, they are half hardware abstraction functions.
Hmm, maybe that's a bad idea, because then py/mphal.h
will need to include py/runtime.h
, which is no good.
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.
Yeah, that's it. My very first version of this added the inlines tomphal.h
and ran into that problem.
I think conceptually:
mphal.h
is "lower" and relates to the hardware.runtime.h
is "higher" and relates to the pure Python layer.event.h
is "in between" the two, as it needs to refer to both.
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.
Made the functions non-inline, declared them in py/runtime.h
, and defined them in py/scheduler.c
.
I've tested this on a Pico and Pico W and they both now pass all the asyncio tests (again). Also networking and BLE works on Pico W. |
// next interrupt or event. | ||
// | ||
// Note: on "tickless" ports this can suspend execution for a long time, | ||
// don't call unless you know an interrupt is coming to continue execution. |
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.
Maybe add "and may also return early".
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.
Reworded both these comments to explicitly explain that it's the tick macro that can cause frequent wakeups.
I think esp8266 needs to be updated to use the new scheme, because it does things differently to the other ports:
In particular the |
py/runtime.h
Outdated
// For ports still using the old macros | ||
MICROPY_EVENT_POLL_HOOK_FAST | ||
#else | ||
mp_handle_pending(true); |
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.
This is not general enough. Some ports like esp8266 must do work in addition to mp_handle_pending()
. So we need a way to hook into this function with a macro.
I guess for now we could use MICROPY_EVENT_POLL_HOOK_FAST
for this, but eventually we will need a way to hook.
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.
I had anticipated there would be some port-specific tweaks needed here, but I figured it was best to deal with them case-by-case as we move the ports over.
esp8266 port should still work the same with this PR, as it still defines MICROPY_EVENT_POLL_HOOK
and the implementation of the mp_event_wait_*
functions use that macro if it's defined.
I think esp8266 can probably be ported by defining #define MICROPY_INTERNAL_WFE ets_event_poll()
, as this is the only way to "wait for event" on the esp8266 non-OS SDK. I'd suggest doing this in a follow-up PR, though, the esp8266 changes here were just juggling headers so it would compile.
In the specific case of MICROPY_EVENT_POLL_HOOK_FAST
and mp_event_handle_no_wait()
, esp8266 port doesn't define this hook at the moment so I think it will keep working much the same?
Fully agree that some other ports might need an additional macro added here though, when we move these over.
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.
In the specific case of MICROPY_EVENT_POLL_HOOK_FAST and mp_event_handle_no_wait(), esp8266 port doesn't define this hook at the moment so I think it will keep working much the same?
Ah, I'm with you now that the i2c scan specifically failed over to the "slow" hook if the fast hook wasn't defined.
OK, will check all the ports for this behaviour.
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 esp8266 with a new hook MICROPY_INTERNAL_EVENT_HOOK
and verified i2c.scan() completes. Do you know if any other ports relied on that fallback logic in i2c? It looks like probably not.
Compared to before, there is a small behaviour change - esp8266 port will call ets_event_poll()
anywhere else that mp_event_handle_nowait()
is called. I think this is only modselect.c though, and it seems like a good idea to be processing wifi events in that instance.
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.
Ah, I'm with you now that the i2c scan specifically failed over to the "slow" hook if the fast hook wasn't defined.
Indeed. The existing behaviour is quite subtle, actually too subtle. I'm pretty sure only esp8266 relied on it.
Compared to before, there is a small behaviour change - esp8266 port will call
ets_event_poll()
anywhere else thatmp_event_handle_nowait()
is called. I think this is only modselect.c though, and it seems like a good idea to be processing wifi events in that instance.
That sounds like a good improvement.
0e72fd8
to
da98c6f
Compare
da98c6f
to
deb930b
Compare
} while (0); | ||
#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) msec_sleep(MAX(1.0, (double)(TIMEOUT_MS))) | ||
#else | ||
#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) /* No-op */ |
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.
@stinos Do these changes look alright to you? See py/event.h
for context. I haven't had a chance to test the Windows port with this PR, yet.
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.
Not completely sure what MICROPY_INTERNAL_WFE is supposed to do, but scheduler.c has MICROPY_INTERNAL_WFE(-1)
so that does not seem to work as intended?
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.
MICROPY_INTERNAL_WFE is "wait for event". But it can be conservative and return immediately, but in that case it'll use more power/CPU.
If the argument is -1 it means "wait indefinitely for the next event". Here that would just sleep 1ms.
When MICROPY_ENABLE_SCHEDULER is disabled, I don't think this macro is ever used, so doesn't need to do anything in that case.
deb930b
to
c05313b
Compare
Yeah, I think the order of |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #13096 +/- ##
==========================================
- Coverage 98.36% 98.36% -0.01%
==========================================
Files 159 159
Lines 20978 20989 +11
==========================================
+ Hits 20636 20646 +10
- Misses 342 343 +1 ☔ View full report in Codecov by Sentry. |
c05313b
to
adf61cd
Compare
The coverage CI check is complaining that |
@dpgeorge Coverage is still failing. I think because the only code which calls It looks like It seems like it might be reasonable to refactor this to call (The other option might be to implement |
OK, that's a bit tricky. How about this then: --- a/ports/unix/coverage.c
+++ b/ports/unix/coverage.c
@@ -578,9 +578,9 @@ STATIC mp_obj_t extra_coverage(void) {
mp_sched_unlock();
mp_printf(&mp_plat_print, "unlocked\n");
- // drain pending callbacks
+ // drain pending callbacks (and test mp_event_wait_ms())
while (mp_sched_num_pending()) {
- mp_handle_pending(true);
+ mp_event_wait_ms(1);
}
// setting the keyboard interrupt and raising it during mp_handle_pending |
49b4c9f
to
ee53c81
Compare
fc19e73
to
a023d2d
Compare
a023d2d
to
8d29027
Compare
These are intended to replace MICROPY_EVENT_POLL_HOOK and MICROPY_EVENT_POLL_HOOK_FAST, which are insufficient for tickless ports. This implementation is along the lines suggested here: micropython#12925 (comment) Currently any usage of these functions expands to use the existing hook macros, but this can be switched over port by port. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
See previous commit for details of these functions. As of this commit, these still call the old hook macros on all ports. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
This commit changes all uses in the rp2 port, and drivers that are optionally supported by that port. The old MICROPY_EVENT_POLL_HOOK and MICROPY_EVENT_POLL_HOOK_FAST macros are no longer used for rp2 builds and are removed (C user code will need to be changed to suit). Also take the opportunity to change some timeouts that used 64-bit arithmetic to 32-bit, to hopefully claw back a little code size. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
Previously this was not set, so potential for race conditions in interrupt handlers this didn't issue SEV. (Which is currently all of them, as far as I can see.) Eventually we might be able to augment the interrupt handlers that wake the main thread to call SEV, and leave the others as-is to suspend the CPU slightly faster, but this will solve the issue for now. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
This should be the equivalent of the previous event poll hook macro. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
This is necessary to avoid watchdog timeout in long i2c.scan(), as previously machine_i2c.c would call MICROPY_EVENT_POLL_HOOK if MICROPY_EVENT_POLL_HOOK_FAST was not available. Compared to previous implementation, this implementation removes the ets_event_poll() function and calls the SDK function ets_loop_iter() from MICROPY_INTERNAL_EVENT_HOOK instead. This allows using the port-agnostic functions in more places. There is a small behaviour change, which is that the event loop gets iterated in a few more places (i.e. anywhere that mp_event_handle_nowait() is called). However, this looks like maybe only modselect.c - and is probably good to process Wi-Fi events in that polling loop. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
8d29027
to
b30e80d
Compare
I've changed it so all three of these event functions are normal functions (not inline). This saves code size, and makes the abstraction a bit stronger. These functions really don't need to be inline. The original macros were because that was a bit of a hack that just allowed a port to insert arbitrary code for the polling. But now that it has been formalised in this PR these event functions are better not inlined. If they are inline then the more they are used the more code size will grow, and we want to be able to use them on all ports, even ones that are space constrained. I checked all uses of these event functions and none of them are critical that they warrant (or need, eg to be in IRAM) an inline function. Better to spend the bytes elsewhere to improve performance. It also means we don't need a new |
Necessary to get coverage of the new event functions. Deletes the case that called usleep(delay) for mp_hal_delay_ms(), it seems like this wouldn't have ever happened anyhow (MICROPY_EVENT_POOL_HOOK is always defined for the unix port). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
b30e80d
to
2c828a8
Compare
This PR fixes #12925, which is a regression on rp2 introduced when rp2 became "tickless" in #12837.
It implements one of the suggestions from the linked Issue: to add new functions for "wait for event" and "handle events". The new functions are port-agnostic:
mp_event_handle_nowait()
- Handle any pending MicroPython events without waiting for an interrupt or event.mp_event_wait_indefinite()
- Handle any pending MicroPython events and then suspend execution until the next interrupt or event. This function is deliberately named to attract attention when developers use it, as on tickless ports it will cause issues if the developer expects it to return without a corresponding interrupt having occurred.mp_event_wait_ms(mp_uint_t timeout_ms)
- Handle any pending MicroPython events and then suspend execution until the next interrupt or event, or until timeout_ms milliseconds have elapsed.Each port only needs to provide a
MICROPY_INTERNAL_WFE(OPTIONAL_TIMEOUT)
macro to implement the "wait for event" logic. More port-specific macros may be needed when adding other ports.These functions are intended to eventually replace
MICROPY_EVENT_POLL_HOOK
andMICROPY_EVENT_POLL_HOOK_FAST
, although on all ports except rp2 they are currently implemented using these macros.This PR switches over
extmod
,rp2
port and the built-in drivers supported byrp2
to use the new functions, fixing the issue of indefinite sleep under some conditions.It also switches over
esp8266
, to avoid problems wherei2c.scan()
doesn't callets_event_poll()
and can trigger a WDT.Finally it switches over Windows, as this was necessary to get the same sleep behaviour with the new functions, and unix so that the coverage tests pass on the new functions.
rp2
port also enables SEVONPEND to ensureSEV
bit is set on each interrupt, removing potential race conditions between the interrupt and the main thread checking a hardware condition. It may be possible to make this more fine-grained in future by manually callingSEV
from individual ISRs, but this approach ensures the firmware can't become stuck if aSEV
is missing.This work was funded through GitHub Sponsors.