Skip to content

Async functions should not call open() #704

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
cclauss opened this issue Jul 23, 2023 · 2 comments
Closed

Async functions should not call open() #704

cclauss opened this issue Jul 23, 2023 · 2 comments

Comments

@cclauss
Copy link
Contributor

cclauss commented Jul 23, 2023

% ruff --select=ASYNC .

micropython/bluetooth/aioble/examples/l2cap_file_client.py:90:14: ASYNC101 Async functions should not call open, time.sleep, or subprocess methods
micropython/bluetooth/aioble/examples/l2cap_file_server.py:85:22: ASYNC101 Async functions should not call open, time.sleep, or subprocess methods
Found 2 errors.

I have used https://pypi.org/project/aiofiles elsewhere to fix this but I am unsure of its compatibility with micropython.

% ruff rule ASYNC101

open-sleep-or-subprocess-in-async-function (ASYNC101)

Derived from the flake8-async linter.

What it does

Checks that async functions do not contain calls to open, time.sleep,
or subprocess methods.

Why is this bad?

Blocking an async function via a blocking call will block the entire
event loop, preventing it from executing other tasks while waiting for the
call to complete, negating the benefits of asynchronous programming.

Instead of making a blocking call, use an equivalent asynchronous library
or function.

Example

async def foo():
    time.sleep(1000)

Use instead:

async def foo():
    await asyncio.sleep(1000)

@jimmo

@jimmo
Copy link
Member

jimmo commented Jul 25, 2023

Thanks @cclauss -- The advice is of course correct, and for time.sleep vs await asyncio.sleep then definitely, but for file IO we don't really have an alternative on MicroPython.

Our VFS layer just has no concept of asyncio-style concurrency, and to make this work it would need to go all the way down to the filesystem driver (which for both FAT and LittleFS is third-party code). You could get some way there using a thread pool (like aiofiles does) but threads are not universally enabled on MicroPython.

In most cases where the device is using a filesystem on internal flash (or memory-mapped external flash like ESP32), opening-for-reading and reading of flash-backed files is just a memory-mapped operation. There is no "waiting" so there isn't anything to be gained anyway.

For writing on the other hand, the erase and write block times are significant, however in the case where the filesystem is backed by the same flash memory that the MicroPython firmware itself is running out of, the CPU is paused during these operations anyway (and this applies to both cores on e.g. rp2040).

So I think for now the # noqa: ASYNC101 is a reasonable thing to do, but yes this is in general a bit of an FAQ and we should write more about it in the documentation. One of the common ways this issue shows up in is when using the logging module in an asyncio application.

@cclauss
Copy link
Contributor Author

cclauss commented Jul 25, 2023

Awesome write up. Quite helpful. For micropython, it is too bad that ruff puts open(), sleep(), and subprocess into a single rule but the targeted noqa directive will cover this nicely. Thanks for your guidance on this issue. Closing.

@cclauss cclauss closed this as completed Jul 25, 2023
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

No branches or pull requests

2 participants