Skip to content

extmod: implement "uevent" module for event-based polling and use with uasyncio (RFC, WIP) #6110

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

Closed
wants to merge 9 commits into from

Conversation

dpgeorge
Copy link
Member

@dpgeorge dpgeorge commented Jun 4, 2020

This is an alternative to #6106 to allow arbitrary events to hook into uasyncio, not just streams that are readable/writable.

The idea here is to provide a new MicroPython-specific module called uevent which is similar in spirit to select and selectors but optimised for the needs of an async scheduler. It's completely independent to uasyncio but used by it to wait for IO events.

The use of uevent goes like this:

import uevent
poller = uevent.poll()
entry = poller.register(object, READ) # register object for READ events
poller.register(entry, WRITE) # also enable WRITE events on this object
entry.data = 'my data' # store arbitrary data into the entry

# wait for any events then iterate through the available ones
for entry in poller.wait_ms(timeout):
    print(entry.data)

It looks very similar to select.poll but has some important differences which make it better suited to handling events:

  • registering an object updates (with logical or) the event flags if the object is already registered, it doesn't overwrite them
  • once an event fires that event flag is cleared (but other event flags, if any, remain set for the next wait)
  • each registered object is associated with additional user state for efficiency (to make things O(1))

The important thin with uevent is that on bare-metal it's implemented fully as O(1) for all operations (registering, modifying, waiting, getting next available event). It does this by providing registered objects with a callback to call when they have an event of interest.

The PR here includes working code for stm32 (sockets and native pin events), unix (file descriptors only) and zephyr (native pin events).

The code is still very much WIP and proof-of-concept, but the highlights are:

  • extmod/modlwip.c has very minimal changes to make it event driven, ie provide callbacks into uevent when sockets become readable/writable (so no more busy loop polling!)
  • the code for zephyr to wait is literally just a k_sem_take(&sem, timeout), with the semaphore set asynchronously by a pin interrupt
  • the code for stm32 to wait is a "sleepy loop" which checks for any pending events (a simple check of the pointer of a linked list != NULL) or if the timeout has passed, and then does MICROPY_EVENT_POLL_HOOK (which could be just a WFI and it'd still work)
  • on unix it still uses poll

The code to implement an async pin object with this PR is:

class AsyncPin:
    def __init__(self, pin, trigger):
        self.pin = pin
        self.pin.set_event(trigger)

    async def wait_edge(self):
        yield asyncio.core._io_queue._enqueue(self.pin, 0)
        return self.pin.get_event()

async def main():
    usr = AsyncPin(Pin("USR"), Pin.IRQ_FALLING)
    await usr.wait_edge()

asyncio.run(main())

Note that one can't directly await on a raw machine.Pin object, instead such objects are registered with the poller and the poller wakes when the raw pin object has some kind of event. It's then up to the AsyncPin wrapper to manage that event (in the case above it just retrieves it with pin.get_event() which returns the number of edges since the last call).

The code here doesn't yet implement a way to call Event.set() from a soft-scheduled callback, like #6106 does, but I still think that's a useful feature to have more control over waking asyncio from an IRQ.

In summary: the PR here reimplements the select module as uevent and makes it event/callback based (on bare-metal) rather than busy-loop polling. And then builds on this to add events to machine.Pin objects so they can be registered with uevent and hence integrated with uasyncio.

@tve
Copy link
Contributor

tve commented Jun 4, 2020

Sounds exciting :-).

@tve
Copy link
Contributor

tve commented Jun 8, 2020

This looks promising! I started reviewing yesterday but then had to take a break and now I see that you have a new PR, I'll switch over to that...

@dpgeorge
Copy link
Member Author

dpgeorge commented Jun 8, 2020

I started reviewing yesterday but then had to take a break and now I see that you have a new PR, I'll switch over to that...

It'd be great if you could review this one first because it contains all the details to actually make an event-based wait work, ie no busy polling. The PR here shows more of the long-term goal and, while it works, is not in a state to be merged. It needs some cleaning and broken into logical pieces and #6125 is the start of that process.

@dpgeorge
Copy link
Member Author

Closing this for now, #6125 is still open and links back here for reference.

@dpgeorge dpgeorge closed this Nov 17, 2021
@dpgeorge dpgeorge deleted the extmod-poll-event-based branch November 17, 2021 03:25
tannewt added a commit to tannewt/circuitpython that referenced this pull request Mar 8, 2022
…l-math-funcs

Add disclaimer about availability of math functions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants