{"id":30178356,"url":"https://github.com/sysgrok/async-io-mini","last_synced_at":"2025-08-12T05:20:37.776Z","repository":{"id":239981079,"uuid":"801150208","full_name":"sysgrok/async-io-mini","owner":"sysgrok","description":null,"archived":false,"fork":false,"pushed_at":"2025-01-15T19:20:21.000Z","size":63,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-31T19:39:46.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sysgrok.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-05-15T17:31:37.000Z","updated_at":"2025-07-31T11:40:27.000Z","dependencies_parsed_at":"2024-05-16T06:21:50.834Z","dependency_job_id":"7aeff064-2097-443a-887f-d3fe5ab75577","html_url":"https://github.com/sysgrok/async-io-mini","commit_stats":{"total_commits":30,"total_committers":2,"mean_commits":15.0,"dds":"0.033333333333333326","last_synced_commit":"4f2a3ebd7165412dc3575c7c9f39734ff6138c47"},"previous_names":["ivmarkov/async-io-mini","sysgrok/async-io-mini"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/sysgrok/async-io-mini","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sysgrok%2Fasync-io-mini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sysgrok%2Fasync-io-mini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sysgrok%2Fasync-io-mini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sysgrok%2Fasync-io-mini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sysgrok","download_url":"https://codeload.github.com/sysgrok/async-io-mini/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sysgrok%2Fasync-io-mini/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270005591,"owners_count":24510939,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-12T05:20:36.393Z","updated_at":"2025-08-12T05:20:37.760Z","avatar_url":"https://github.com/sysgrok.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# async-io-mini\n\n[![CI](https://github.com/ivmarkov/async-io-mini/actions/workflows/ci.yml/badge.svg)](https://github.com/ivmarkov/async-io-mini/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](https://github.com/ivmarkov/async-io-mini)\n[![Cargo](https://img.shields.io/crates/v/async-io-mini.svg)](https://crates.io/crates/async-io-mini)\n[![Documentation](https://docs.rs/async-io/badge.svg)](https://docs.rs/async-io-mini)\n\nAsync I/O and timers for MCUs.\n\nThis crate is a fork of the splendid [`async-io`](https://github.com/smol-rs/async-io) crate targetting MCUs and ESP-IDF in particular.\n\n## How to use?\n\n`async-io-mini` is an API-compatible replacement for the `Async` and `Timer` types from `async-io`.\n\nSo either:\n* Just replace all `use async_io` occurances in your crate with `use async_io_mini`\n* Or - in your `Cargo.toml` - replace:\n  * `async-io = \"...\"`\n  * with `async-io = { package = \"async-io-mini\", ... }`\n\nAdditionally, you need to provide an `embassy-time-driver` implementation. This is either done by the HAL of your MCU, or `embassy-time` provides you with a `std`-specific implementation. If you are not using `embassy-executor`, you will also need to select one of the `embassy-time/generic-queue-*` features.\n\n## Justification\n\nWhile `async-io` supports a ton of operating systems - _including ESP-IDF for the Espressif MCU chips_ - it does have a non-trivial memory consumption in the hidden thread named `async-io`.  Since its hidden `Reactor` object is initialized lazily, it so happens that it is first allocated on-stack, and then it is moved into the static context. This requires the `async-io` thread (as well as _any_ thread from where you are polling sockets) to have at least 8K stack, which - by MCU standards! - is relatively large if you are memory-constrained.\n\nIn contrast, `async-io-mini`:\n- Needs \u003c 3K of stack with ESP-IDF (and that's only because ESP-IDF interrupts are executed on the stack of the interrupted thread, i.e. we need to leave some room for these);\n- It's reactor is allocated to the `static` context eagerly as its constructor function is `const` (hence no stack blowups);\n- The reactor has a smaller memory footprint too (~ 500 bytes), as it is hard-coded to the `select` syscall and does not support timers. MCUs (with lwIP) usually have max file and socket handles in the lower tens (~ 20 in ESP-IDF) so all structures can be limited to that size;\n- No heap allocations - initially and during polling.\n\nFurther, `async-io` has a non-trivial set of dependencies (again - for MCUs; for regular OSes it is a dwarf by any meaningful measurement!): `rustix`, `polling`, `async-lock`, `event`, `tracing`, `parking-lot` and more. Nothing wrong with with that per-se, but that's a large implementation surface that e.g. recently is triggering a possible miscompilation on Espressif xtensa targets (NOT that this is a justification not to root-cause and fix the problem!).\n\n`async-io-mini` only has the following non-optional dependencies:\n- `libc` (which indirectly comes with Rust STD anyway);\n- `heapless` (for `heapless::Vec` and nothing else);\n- `log` (might become optional);\n- `enumset` (not crucial, might remove).\n\n## Enhancements\n\nThe `Timer` type of `async_io_mini` is based on the `embassy-time` crate, and as such should offer a higher resolution on embedded operating systems like the ESP-IDF than what can be normally achieved by implementing timers using the `timeout` parameter of the `select` syscall (as `async-io` does).\n\nThe reason for this is that on the ESP-IDF, the `timeout` parameter of `select` provides a resolution of 10 milliseconds (one FreeRTOS sys-tick), while\n`embassy-time` is implemented using the [ESP-IDF Timer service](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/esp_timer.html), which provides resolutions down to 20-30 microseconds.\n\nWith that said, for greenfield code that does not need to be compatible with `async-io`, use the native `embassy_time::Timer` and `embassy_time::Ticker` rather than `async_io_mini::Timer`, because the latter has a larger memory footprint (40 bytes on 32bit archs) compared to the `embassy-time` types (8 and 16 bytes each).\n\n## Limitations\n\n### No equivalent of `async_io::block_on`\n\nImplementing socket polling as a shared task between the hidden `async-io-mini` thread and the thread calling `async_io_mini::block_on` is not trivial and probably not worth it on MCUs. Just use `futures_lite::block_on` or the `block_on` equivalent for your OS (i.e. `esp_idf_svc::hal::task::block_on` for the ESP-IDF).\n\n## Implementation\n\n### Async\n\nThe first time `Async` is used, a thread named `async-io-mini` will be spawned.\nThe purpose of this thread is to wait for I/O events reported by the operating system, and then\nwake appropriate futures blocked on I/O when they can be resumed.\n\nTo wait for the next I/O event, the \"async-io-mini\" thread uses the [select](https://en.wikipedia.org/wiki/Select_(Unix)) syscall, and **is thus only useful for MCUs (might just be the ESP-IDF) where the number of file or socket handles is very small anyway**.\n\n### Timer\n\nAs per above, the `Timer` type is a wrapper around the functionality provided by the `embassy-time` crate.\n\n## Examples\n\nConnect to `example.com:80`, or time out after 10 seconds.\n\n```rust\nuse async_io_mini::{Async, Timer};\nuse futures_lite::{future::FutureExt, io};\n\nuse std::net::{TcpStream, ToSocketAddrs};\nuse std::time::Duration;\n\nlet addr = \"example.com:80\".to_socket_addrs()?.next().unwrap();\n\nlet stream = Async::\u003cTcpStream\u003e::connect(addr).or(async {\n    Timer::after(Duration::from_secs(10)).await;\n    Err(io::ErrorKind::TimedOut.into())\n})\n.await?;\n```\n\n## License\n\nLicensed under either of\n\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n#### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsysgrok%2Fasync-io-mini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsysgrok%2Fasync-io-mini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsysgrok%2Fasync-io-mini/lists"}