Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ categories = ["asynchronous", "network-programming", "MCU", "embedded"]
exclude = ["/.*"]

[features]
default = ["futures-io", "futures-lite"]
default = ["futures-io", "futures-lite", "embassy-time"]
embassy-time = ["embassy-time-driver", "embassy-time-queue-driver", "dep:embassy-time"]

[dependencies]
libc = "0.2"
Expand All @@ -21,6 +22,8 @@ heapless = "0.8"
log = { version = "0.4", default-features = false }
futures-io = { version = "0.3", default-features = false, optional = true, features = ["std"] }
futures-lite = { version = "1", default-features = false, optional = true }
embassy-time-driver = { version = "0.1", optional = true }
embassy-time-queue-driver = { version = "0.1", optional = true }
embassy-time = { version = "0.3", optional = true }

[dev-dependencies]
Expand All @@ -32,4 +35,8 @@ env_logger = "0.10"

[[test]]
name = "async"
required-features = ["futures-io", "futures-lite"]
required-features = ["futures-io", "futures-lite", "embassy-time"]

[[test]]
name = "timer"
required-features = ["futures-lite", "embassy-time"]
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
[![Cargo](https://img.shields.io/crates/v/async-io-mini.svg)](https://crates.io/crates/async-io-mini)
[![Documentation](https://docs.rs/async-io/badge.svg)](https://docs.rs/async-io-mini)

Async I/O. **EXPERIMENTAL!!**
Async I/O and timers. **Experimental**

This crate is an **experimental** fork of the splendid [`async-io`](https://github.com/smol-rs/async-io) crate targetting MCUs and ESP-IDF in particular.
This crate is an experimental fork of the splendid [`async-io`](https://github.com/smol-rs/async-io) crate targetting MCUs and ESP-IDF in particular.

## How to use?

`async-io-mini` is a drop-in, API-compatible replacement for the `Async` type from `async-io` (but does NOT have an equivalent of `Timer` - see why [below](#limitations)).
`async-io-mini` is a drop-in, API-compatible replacement for the `Async` and `Timer` types from `async-io`.

So either:
* Just replace all `use async_io` occurances in your crate with `use async_io_mini`
Expand All @@ -37,40 +37,53 @@ Further, `async-io` has a non-trivial set of dependencies (again - for MCUs; for
- `log` (might become optional);
- `enumset` (not crucial, might remove).

## Limitations
## Enhancements

The `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).

### No timers
The reason for this is that on the ESP-IDF, the `timeout` parameter of `select` provides a resolution of 10ms (one FreeRTOS sys-tick), while
`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 up to 1 microsecond.

`async-io-mini` does NOT have an equivalent of `async_io::Timer`. On ESP-IDF at least, timers based on OS systicks are often not very useful, as the OS systick is low-res (10ms).
With 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).

Workaround: use the `Timer` struct from the [`embassy-time`](https://crates.io/crates/embassy-time) crate, which provides a very similar API and is highly optimized for embedded environments. On the ESP-IDF, the `embassy-time-driver` implementation is backed by the ESP-IDF Timer service, which runs off from a high priority thread by default and thus has good res.
## Limitations

### No equivalent of `async_io::block_on`

Implementing 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).

## Implementation

### Async

The first time `Async` is used, a thread named `async-io-mini` will be spawned.
The purpose of this thread is to wait for I/O events reported by the operating system, and then
wake appropriate futures blocked on I/O when they can be resumed.

To 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**.

### Timer

As per above, the `Timer` type is a wrapper around the functionality provided by the `embassy-time` crate.

## Examples

Connect to `example.com:80`.
Connect to `example.com:80`, or time out after 10 seconds.

```rust
use async_io_mini::Async;
use async_io_mini::{Async, Timer};
use futures_lite::{future::FutureExt, io};

use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;

let addr = "example.com:80".to_socket_addrs()?.next().unwrap();

let stream = Async::<TcpStream>::connect(addr).await?;
let stream = Async::<TcpStream>::connect(addr).or(async {
Timer::after(Duration::from_secs(10)).await;
Err(io::ErrorKind::TimedOut.into())
})
.await?;
```

## License
Expand Down
Loading
Loading