Skip to content

extmod/uasyncio: Add ThreadSafeEvent. #6886

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

Merged
merged 3 commits into from
Feb 16, 2021

Conversation

jimmo
Copy link
Member

@jimmo jimmo commented Feb 12, 2021

This is a MicroPython-extension that allows for events to be set from IRQ (hard or soft) or scheduler context.

Works on the bare-metal ports (which use extmod/uselect). To use this on Unix requires PR #6885

@dpgeorge dpgeorge added the extmod Relates to extmod/ directory in source label Feb 13, 2021
@dpgeorge
Copy link
Member

This new class is self-clearing, which is different to how Event behaves (the latter has a clear() method). IMO this class should match Event. But then there's also the question of whether this ThreadSafeEvent should allow multiple tasks to wait on it (like Event). That of course adds code, and is not strictly needed (one could use a standard Event combined with this one to multiplex). If it's not going to match Event then maybe rename it to something like ThreadSafeFlag?


# MicroPython-extension: Event class that can be set from IRQs or scheduler context.
# Implementation is a stream that asyncio will poll until a flag is set.
class ThreadSafeEvent(io.IOBase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... this now puts a requirement on all ports to have uio.IOBase... is it worth guarding this code in hasattr(uio, 'IOBase')?

@@ -29,3 +33,24 @@ async def wait(self):
core.cur_task.data = self.waiting
yield
return True


_MP_STREAM_POLL = const(3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to eliminate the need for a from micropython import const I'd say just remove this line and hard-code the value with a comment below

self._flag = 0

def ioctl(self, req, flags):
if req == _MP_STREAM_POLL:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be hard-coded as: if req == 3: # MP_STREAM_POLL

@jimmo jimmo force-pushed the asyncio-polling-event branch from 60d5f7c to 965cbff Compare February 15, 2021 06:29
@jimmo
Copy link
Member Author

jimmo commented Feb 15, 2021

OK that all sounds very reasonable -- I've update it to ThreadSafeFlag and added docs and a basic test. I've kept it self-clearing because it's hard to avoid races otherwise.

The test is a bit annoying because it can't run on Unix (see #6885), but I've run it on pyboard and on a unix build with extmod/uselect.

@jimmo jimmo force-pushed the asyncio-polling-event branch 2 times, most recently from 5f4bd8b to e2eefd4 Compare February 16, 2021 04:39
@jimmo
Copy link
Member Author

jimmo commented Feb 16, 2021

Updated to use import uio instead of import io.

async def wait(self):
yield core._io_queue.queue_read(self)
self._flag = 0
except ImportError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2x blank lines needed here for black formatting

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@jimmo jimmo force-pushed the asyncio-polling-event branch from e2eefd4 to 6d5f4c6 Compare February 16, 2021 04:59
.. method:: ThreadSafeFlag.wait()

Wait for the flag to be set. If the flag is already set then it returns
immediately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that doesn't match the implementation (returning immediately if set)... maybe the implementation should be changed so it does return immediately if self._flag is set... that would make things more responsive at the risk of "starving" other things (but since Event.wait already behaves this way we know it's probably ok to return immediately)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree. I've updated the code to return immediately and added that to the test.

This is a MicroPython-extension that allows for code running in IRQ
(hard or soft) or scheduler context to sequence asyncio code.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
@jimmo jimmo force-pushed the asyncio-polling-event branch from 6d5f4c6 to 51e0a39 Compare February 16, 2021 05:41
yield
task 3
wait task
task 3 done
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when I run this test on a PYBD-SF6 I get:

set event
yield
task 3
task 3 done
wait task

which makes more sense to me than this .exp file, that task 3 starts and is immediately finished

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
@jimmo jimmo force-pushed the asyncio-polling-event branch from 51e0a39 to 83d2305 Compare February 16, 2021 06:08
@dpgeorge dpgeorge merged commit 83d2305 into micropython:master Feb 16, 2021
@dpgeorge
Copy link
Member

Thanks!

@peterhinch
Copy link
Contributor

Very nice. I'll do some testing later.

@dpgeorge
Copy link
Member

You can make an async-pin like this:

class AsyncPin:
    def __init__(self, pin, trigger):
        self.pin = pin
        self.flag = ThreadSafeFlag()
        self.pin.irq(lambda pin: self.flag.set(), trigger, hard=True)

    def wait_edge(self):
        return self.flag.wait()

Then call await async_pin.wait_edge().

@tve
Copy link
Contributor

tve commented Feb 16, 2021

How does this fit with uevent plans?

@dpgeorge
Copy link
Member

How does this fit with uevent plans?

I still have uevent on the to-do list, it's just been blocked by cmake and other things (which are being cleared now). I hope that we can still offer the same ThreadSafeFlag class and API after the move to uevent, and it'll become efficient (rather than need polling).

Wind-stormger pushed a commit to BPI-STEAM/micropython that referenced this pull request Sep 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extmod Relates to extmod/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants