← index #5795PR #6056
Likely Duplicate · high · value 0.531
QUERY · ISSUE

extmod/uasyncio Event.set() not safe in ISR

openby kevinkk525opened 2020-03-25updated 2020-04-07
extmod

If you call Event.set() in an ISR, you could end up losing all tasks that were awaiting the event (if the ISR is executed while uasyncio is sorting tasks on the queue e.g.).

I made a testscript a while ago to demonstrate this: https://gist.github.com/kevinkk525/5b89395604da3df3be7a015abcba04fa

CANDIDATE · PULL REQUEST

uasyncio: make uasyncio.Event() safe to call from an interrupt (RFC, WIP)

closedby dpgeorgeopened 2020-05-20updated 2021-11-17
extmod

The set of commits in this PR add an interface between soft interrupt handlers (ie micropython.schedule calls) and the uasyncio module. For example, one can use it to build an async-pin which can be awaited on for an edge or level change.

There's a lot to discuss here, and at the moment it's more of a proof-of-concept to show what's necessary to make it work.

The approach taken here has two parts to it:

  1. add a way for the asyncio scheduler (namely select.poll) to be woken asynchronously/externally
  2. make uasyncio.Event() "thread safe" so it can be set from a soft interrupt handler, ie from something scheduled by micropython.schedule

The solution for the first part was chosen so that it will work on the unix port, and it's also the same way that CPython works: create a special socket.socketpair() (like a unix file pipe), register that with select.poll and then write to it to signal that the asyncio scheduler should wake up. This is thread-safe and race-free (the signal can come in just before poll is called and it'll still work).

The second part (making Event.set() thread safe) is done for efficiency: even if there are hundreds of external events (eg pin irqs, UARTs, ble events, etc) they do not put a burden on poll because only the socketpair (and IO streams) are registered with poll.

The new things in this PR are:

  • addition of micropython.schedule_lock() and micropython.schedule_unlock()
  • addition of usocket.socketpair() including a bare-metal version for ports that need it
  • improve MICROPY_EVENT_POLL_HOOK on stm32 to be atomic wrt waiting for events
  • make uasyncio.Event() thread/IRQ safe

The way to use this feature is:

  1. create an uasyncio.Event() corresponding to the external event
  2. tasks wait on this event
  3. IRQ triggers soft callback via micropython.schedule()
  4. that callback will set the Event -- internally the event will schedule all waiting tasks (immediately in the soft callback) and then notify the asyncio poll via the socketpair that it should wake up and run the next task

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied