diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bee2562..7b9439b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,24 @@ jobs: toolchain: ${{ matrix.rust }} override: true + - name: Cache cargo registry + uses: actions/cache@v2 + with: + path: ~/.cargo/registry + key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }} + + - name: Cache cargo index + uses: actions/cache@v2 + with: + path: ~/.cargo/git + key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-index-${{ hashFiles('**/Cargo.toml') }} + + - name: Cache cargo build + uses: actions/cache@v2 + with: + path: target + key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }} + - name: check uses: actions-rs/cargo@v1 with: @@ -40,6 +58,15 @@ jobs: with: command: check args: --features unstable --all --bins --examples --tests + + - name: check wasm + uses: actions-rs/cargo@v1 + with: + command: check + target: wasm32-unknown-unknown + override: true + args: --features unstable --all --bins --tests + - name: check bench uses: actions-rs/cargo@v1 if: matrix.rust == 'nightly' @@ -65,11 +92,67 @@ jobs: command: test args: --all --features "unstable attributes" - - name: documentation test + build__with_no_std: + name: Build with no-std + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - name: setup + run: | + rustup default nightly + rustup target add thumbv7m-none-eabi + + - name: check no_std uses: actions-rs/cargo@v1 with: - command: test - args: --doc --features "unstable attributes" + command: check + args: --no-default-features --features alloc --target thumbv7m-none-eabi -Z avoid-dev-deps + + check_tokio_02_feature: + name: Check tokio02 feature + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check tokio02 + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --features tokio02 + + cross: + name: Cross compile + runs-on: ubuntu-latest + strategy: + matrix: + target: + - i686-unknown-linux-gnu + - powerpc-unknown-linux-gnu + - powerpc64-unknown-linux-gnu + - mips-unknown-linux-gnu + - arm-linux-androideabi + + steps: + - uses: actions/checkout@master + + - name: Install nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + + - name: Install cross + run: cargo install cross + + - name: check + run: cross check --all --target ${{ matrix.target }} + + - name: check unstable + run: cross check --all --features unstable --target ${{ matrix.target }} + + - name: test + run: cross test --all --features unstable --target ${{ matrix.target }} check_fmt_and_docs: name: Checking fmt and docs @@ -98,15 +181,3 @@ jobs: - name: Docs run: cargo doc --features docs - - # clippy_check: - # name: Clippy check - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v1 - # - name: Install rust - # run: rustup update beta && rustup default beta - # - name: Install clippy - # run: rustup component add clippy - # - name: clippy - # run: cargo clippy --all --features unstable diff --git a/CHANGELOG.md b/CHANGELOG.md index 831c45c..d6bb2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,101 @@ and this project adheres to [Semantic Versioning](https://book.async.rs/overview ## [Unreleased] +# [1.6.1] - 2020-06-11 + +## Added + +- Added `tokio02` feature flag, to allow compatability usage with tokio@0.2 ([#804](https://github.com/async-rs/async-std/pull/804)). + +## Changed + +- Removed unstable `stdio` lock methods, due to their unsoundness ([#807](https://github.com/async-rs/async-std/pull/807)). + +## Fixed + +- Fixed wrong slice index for file reading ([#802](https://github.com/async-rs/async-std/pull/802)). +- Fixed recursive calls to `block_on` ([#799](https://github.com/async-rs/async-std/pull/799)) and ([#809](https://github.com/async-rs/async-std/pull/809)). +- Remove `default` feature requirement for the `unstable` feature ([#806](https://github.com/async-rs/async-std/pull/806)). + +# [1.6.0] - 2020-05-22 + +See `1.6.0-beta.1` and `1.6.0-beta.2`. + +# [1.6.0-beta.2] - 2020-05-19 + +## Added + +- Added an environment variable to configure the thread pool size of the runtime. ([#774](https://github.com/async-rs/async-std/pull/774)) +- Implement `Clone` for `UnixStream` ([#772](https://github.com/async-rs/async-std/pull/772)) + +## Changed + +- For `wasm`, switched underlying `Timer` implementation to [`futures-timer`](https://github.com/async-rs/futures-timer). ([#776](https://github.com/async-rs/async-std/pull/776)) + +## Fixed + +- Use `smol::block_on` to handle drop of `File`, avoiding nested executor panic. ([#768](https://github.com/async-rs/async-std/pull/768)) + +# [1.6.0-beta.1] - 2020-05-07 + +## Added + +- Added `task::spawn_local`. ([#757](https://github.com/async-rs/async-std/pull/757)) +- Added out of the box support for `wasm`. ([#757](https://github.com/async-rs/async-std/pull/757)) +- Added `JoinHandle::cancel` ([#757](https://github.com/async-rs/async-std/pull/757)) +- Added `sync::Condvar` ([#369](https://github.com/async-rs/async-std/pull/369)) +- Added `sync::Sender::try_send` and `sync::Receiver::try_recv` ([#585](https://github.com/async-rs/async-std/pull/585)) +- Added `no_std` support for `task`, `future` and `stream` ([#680](https://github.com/async-rs/async-std/pull/680)) + +## Changed + +- Switched underlying runtime to [`smol`](https://github.com/stjepang/smol/). ([#757](https://github.com/async-rs/async-std/pull/757)) +- Switched implementation of `sync::Barrier` to use `sync::Condvar` like `std` does. ([#581](https://github.com/async-rs/async-std/pull/581)) + +## Fixed + +- Allow compilation on 32 bit targets, by using `AtomicUsize` for `TaskId`. ([#756](https://github.com/async-rs/async-std/pull/756)) + +# [1.5.0] - 2020-02-03 + +[API Documentation](https://docs.rs/async-std/1.5.0/async-std) + +This patch includes various quality of life improvements to async-std. +Including improved performance, stability, and the addition of various +`Clone` impls that replace the use of `Arc` in many cases. + +## Added + +- Added links to various ecosystem projects from the README ([#660](https://github.com/async-rs/async-std/pull/660)) +- Added an example on `FromStream` for `Result` ([#643](https://github.com/async-rs/async-std/pull/643)) +- Added `stream::pending` as "unstable" ([#615](https://github.com/async-rs/async-std/pull/615)) +- Added an example of `stream::timeout` to document the error flow ([#675](https://github.com/async-rs/async-std/pull/675)) +- Implement `Clone` for `DirEntry` ([#682](https://github.com/async-rs/async-std/pull/682)) +- Implement `Clone` for `TcpStream` ([#689](https://github.com/async-rs/async-std/pull/689)) + +## Changed + +- Removed internal comment on `stream::Interval` ([#645](https://github.com/async-rs/async-std/pull/645)) +- The "unstable" feature can now be used without requiring the "default" feature ([#647](https://github.com/async-rs/async-std/pull/647)) +- Removed unnecessary trait bound on `stream::FlatMap` ([#651](https://github.com/async-rs/async-std/pull/651)) +- Updated the "broadcaster" dependency used by "unstable" to `1.0.0` ([#681](https://github.com/async-rs/async-std/pull/681)) +- Updated `async-task` to 1.2.1 ([#676](https://github.com/async-rs/async-std/pull/676)) +- `task::block_on` now parks after a single poll, improving performance in many cases ([#684](https://github.com/async-rs/async-std/pull/684)) +- Improved reading flow of the "client" part of the async-std tutorial ([#550](https://github.com/async-rs/async-std/pull/550)) +- Use `take_while` instead of `scan` in `impl` of `Product`, `Sum` and `FromStream` ([#667](https://github.com/async-rs/async-std/pull/667)) +- `TcpStream::connect` no longer uses a thread from the threadpool, improving performance ([#687](https://github.com/async-rs/async-std/pull/687)) + +## Fixed + +- Fixed crate documentation typo ([#655](https://github.com/async-rs/async-std/pull/655)) +- Fixed documentation for `UdpSocket::recv` ([#648](https://github.com/async-rs/async-std/pull/648)) +- Fixed documentation for `UdpSocket::send` ([#671](https://github.com/async-rs/async-std/pull/671)) +- Fixed typo in stream documentation ([#650](https://github.com/async-rs/async-std/pull/650)) +- Fixed typo on `sync::JoinHandle` documentation ([#659](https://github.com/async-rs/async-std/pull/659)) +- Removed use of `std::error::Error::description` which failed CI ([#661](https://github.com/async-rs/async-std/pull/662)) +- Removed the use of rustfmt's unstable `format_code_in_doc_comments` option which failed CI ([#685](https://github.com/async-rs/async-std/pull/685)) +- Fixed a code typo in the `task::sleep` example ([#688](https://github.com/async-rs/async-std/pull/688)) + # [1.4.0] - 2019-12-20 [API Documentation](https://docs.rs/async-std/1.4.0/async-std) @@ -637,7 +732,12 @@ task::blocking(async { - Initial beta release -[Unreleased]: https://github.com/async-rs/async-std/compare/v1.3.0...HEAD +[Unreleased]: https://github.com/async-rs/async-std/compare/v1.6.1...HEAD +[1.6.1]: https://github.com/async-rs/async-std/compare/v1.6.0...v1.6.1 +[1.6.0]: https://github.com/async-rs/async-std/compare/v1.5.0...v1.6.0 +[1.6.0-beta.2]: https://github.com/async-rs/async-std/compare/v1.6.0-beta.1...v1.6.0-beta.2 +[1.6.0-beta.1]: https://github.com/async-rs/async-std/compare/v1.5.0...v1.6.0-beta.1 +[1.5.0]: https://github.com/async-rs/async-std/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/async-rs/async-std/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/async-rs/async-std/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/async-rs/async-std/compare/v1.1.0...v1.2.0 diff --git a/Cargo.toml b/Cargo.toml index 63d599a..bf86da8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "async-std" -version = "1.4.0" +version = "1.6.1" authors = [ "Stjepan Glavina ", "Yoshua Wuyts ", + "Friedel Ziegelmayer ", "Contributors to async-std", ] edition = "2018" @@ -24,57 +25,72 @@ rustdoc-args = ["--cfg", "feature=\"docs\""] default = [ "std", "async-task", - "crossbeam-channel", - "crossbeam-deque", - "futures-timer", "kv-log-macro", "log", - "mio", - "mio-uds", "num_cpus", "pin-project-lite", + "smol", ] docs = ["attributes", "unstable", "default"] -unstable = ["std", "broadcaster", "futures-timer"] +unstable = [ + "std", + "futures-timer", +] attributes = ["async-attributes"] std = [ + "alloc", "crossbeam-utils", - "futures-core", + "futures-core/std", "futures-io", "memchr", "once_cell", - "pin-project-lite", "pin-utils", "slab", + "wasm-bindgen-futures", + "futures-channel", ] +alloc = [ + "futures-core/alloc", + "pin-project-lite", +] +tokio02 = ["smol/tokio02"] [dependencies] async-attributes = { version = "1.1.1", optional = true } -async-task = { version = "1.0.0", optional = true } -broadcaster = { version = "0.2.6", optional = true, default-features = false, features = ["default-channels"] } -crossbeam-channel = { version = "0.4.0", optional = true } -crossbeam-deque = { version = "0.7.2", optional = true } -crossbeam-utils = { version = "0.7.0", optional = true } -futures-core = { version = "0.3.1", optional = true } -futures-io = { version = "0.3.1", optional = true } -futures-timer = { version = "2.0.2", optional = true } +async-task = { version = "3.0.0", optional = true } +crossbeam-utils = { version = "0.7.2", optional = true } +futures-core = { version = "0.3.4", optional = true, default-features = false } +futures-io = { version = "0.3.4", optional = true } kv-log-macro = { version = "1.0.4", optional = true } log = { version = "0.4.8", features = ["kv_unstable"], optional = true } -memchr = { version = "2.2.1", optional = true } -mio = { version = "0.6.19", optional = true } -mio-uds = { version = "0.6.7", optional = true } -num_cpus = { version = "1.11.1", optional = true } -once_cell = { version = "1.2.0", optional = true } -pin-project-lite = { version = "0.1.1", optional = true } +memchr = { version = "2.3.3", optional = true } +num_cpus = { version = "1.12.0", optional = true } +once_cell = { version = "1.3.1", optional = true } +pin-project-lite = { version = "0.1.4", optional = true } pin-utils = { version = "0.1.0-alpha.4", optional = true } slab = { version = "0.4.2", optional = true } +futures-timer = { version = "3.0.2", optional = true } + +# Devdepencency, but they are not allowed to be optional :/ +surf = { version = "1.0.3", optional = true } + +[target.'cfg(not(target_os = "unknown"))'.dependencies] +smol = { version = "0.1.11", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +futures-timer = { version = "3.0.2", optional = true, features = ["wasm-bindgen"] } +wasm-bindgen-futures = { version = "0.4.10", optional = true } +futures-channel = { version = "0.3.4", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3.10" [dev-dependencies] femme = "1.3.0" -rand = "0.7.2" -surf = "1.0.3" +rand = "0.7.3" tempdir = "0.3.7" -futures = "0.3.1" +futures = "0.3.4" +rand_xorshift = "0.2.0" [[test]] name = "stream" @@ -83,3 +99,8 @@ required-features = ["unstable"] [[example]] name = "tcp-ipv4-and-6-echo" required-features = ["unstable"] + +[[example]] +name = "surf-web" +required-features = ["surf"] + diff --git a/README.md b/README.md index 8cd3ac5..0e1b593 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@
+ + + CI Status + diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index d679825..8b49ce7 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -19,8 +19,9 @@ - [Clean Shutdown](./tutorial/clean_shutdown.md) - [Handling Disconnection](./tutorial/handling_disconnection.md) - [Implementing a Client](./tutorial/implementing_a_client.md) -- [TODO: Async Patterns](./patterns.md) +- [Async Patterns](./patterns.md) - [TODO: Collected Small Patterns](./patterns/small-patterns.md) + - [Production-Ready Accept Loop](./patterns/accept-loop.md) - [Security practices](./security/index.md) - [Security Disclosures and Policy](./security/policy.md) - [Glossary](./glossary.md) diff --git a/docs/src/patterns/accept-loop.md b/docs/src/patterns/accept-loop.md new file mode 100644 index 0000000..4508baf --- /dev/null +++ b/docs/src/patterns/accept-loop.md @@ -0,0 +1,266 @@ +# Production-Ready Accept Loop + +A production-ready accept loop needs the following things: +1. Handling errors +2. Limiting the number of simultanteous connections to avoid deny-of-service + (DoS) attacks + + +## Handling errors + +There are two kinds of errors in an accept loop: +1. Per-connection errors. The system uses them to notify that there was a + connection in the queue and it's dropped by the peer. Subsequent connections + can be already queued so next connection must be accepted immediately. +2. Resource shortages. When these are encountered it doesn't make sense to + accept the next socket immediately. But the listener stays active, so you server + should try to accept socket later. + +Here is the example of a per-connection error (printed in normal and debug mode): +``` +Error: Connection reset by peer (os error 104) +Error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" } +``` + +And the following is the most common example of a resource shortage error: +``` +Error: Too many open files (os error 24) +Error: Os { code: 24, kind: Other, message: "Too many open files" } +``` + +### Testing Application + +To test your application for these errors try the following (this works +on unixes only). + +Lower limits and start the application: +``` +$ ulimit -n 100 +$ cargo run --example your_app + Compiling your_app v0.1.0 (/work) + Finished dev [unoptimized + debuginfo] target(s) in 5.47s + Running `target/debug/examples/your_app` +Server is listening on: http://127.0.0.1:1234 +``` +Then in another console run the [`wrk`] benchmark tool: +``` +$ wrk -c 1000 http://127.0.0.1:1234 +Running 10s test @ http://localhost:8080/ + 2 threads and 1000 connections +$ telnet localhost 1234 +Trying ::1... +Connected to localhost. +``` + +Important is to check the following things: + +1. The application doesn't crash on error (but may log errors, see below) +2. It's possible to connect to the application again once load is stopped + (few seconds after `wrk`). This is what `telnet` does in example above, + make sure it prints `Connected to `. +3. The `Too many open files` error is logged in the appropriate log. This + requires to set "maximum number of simultaneous connections" parameter (see + below) of your application to a value greater then `100` for this example. +4. Check CPU usage of the app while doing a test. It should not occupy 100% + of a single CPU core (it's unlikely that you can exhaust CPU by 1000 + connections in Rust, so this means error handling is not right). + +#### Testing non-HTTP applications + +If it's possible, use the appropriate benchmark tool and set the appropriate +number of connections. For example `redis-benchmark` has a `-c` parameter for +that, if you implement redis protocol. + +Alternatively, can still use `wrk`, just make sure that connection is not +immediately closed. If it is, put a temporary timeout before handing +the connection to the protocol handler, like this: + +```rust,edition2018 +# extern crate async_std; +# use std::time::Duration; +# use async_std::{ +# net::{TcpListener, ToSocketAddrs}, +# prelude::*, +# }; +# +# type Result = std::result::Result>; +# +#async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { +# let listener = TcpListener::bind(addr).await?; +# let mut incoming = listener.incoming(); +while let Some(stream) = incoming.next().await { + task::spawn(async { + task::sleep(Duration::from_secs(10)).await; // 1 + connection_loop(stream).await; + }); +} +# Ok(()) +# } +``` + +1. Make sure the sleep coroutine is inside the spawned task, not in the loop. + +[`wrk`]: https://github.com/wg/wrk + + +### Handling Errors Manually + +Here is how basic accept loop could look like: + +```rust,edition2018 +# extern crate async_std; +# use std::time::Duration; +# use async_std::{ +# net::{TcpListener, ToSocketAddrs}, +# prelude::*, +# }; +# +# type Result = std::result::Result>; +# +async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { + let listener = TcpListener::bind(addr).await?; + let mut incoming = listener.incoming(); + while let Some(result) = incoming.next().await { + let stream = match stream { + Err(ref e) if is_connection_error(e) => continue, // 1 + Err(e) => { + eprintln!("Error: {}. Pausing for 500ms."); // 3 + task::sleep(Duration::from_millis(500)).await; // 2 + continue; + } + Ok(s) => s, + }; + // body + } + Ok(()) +} +``` + +1. Ignore per-connection errors. +2. Sleep and continue on resource shortage. +3. It's important to log the message, because these errors commonly mean the + misconfiguration of the system and are helpful for operations people running + the application. + +Be sure to [test your application](#testing-application). + + +### External Crates + +The crate [`async-listen`] has a helper to achieve this task: +```rust,edition2018 +# extern crate async_std; +# extern crate async_listen; +# use std::time::Duration; +# use async_std::{ +# net::{TcpListener, ToSocketAddrs}, +# prelude::*, +# }; +# +# type Result = std::result::Result>; +# +use async_listen::{ListenExt, error_hint}; + +async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { + + let listener = TcpListener::bind(addr).await?; + let mut incoming = listener + .incoming() + .log_warnings(log_accept_error) // 1 + .handle_errors(Duration::from_millis(500)); + while let Some(socket) = incoming.next().await { // 2 + // body + } + Ok(()) +} + +fn log_accept_error(e: &io::Error) { + eprintln!("Error: {}. Listener paused for 0.5s. {}", e, error_hint(e)) // 3 +} +``` + +1. Logs resource shortages (`async-listen` calls them warnings). If you use + `log` crate or any other in your app this should go to the log. +2. Stream yields sockets without `Result` wrapper after `handle_errors` because + all errors are already handled. +3. Together with the error we print a hint, which explains some errors for end + users. For example, it recommends increasing open file limit and gives + a link. + +[`async-listen`]: https://crates.io/crates/async-listen/ + +Be sure to [test your application](#testing-application). + + +## Connections Limit + +Even if you've applied everything described in +[Handling Errors](#handling-errors) section, there is still a problem. + +Let's imagine you have a server that needs to open a file to process +client request. At some point, you might encounter the following situation: + +1. There are as many client connection as max file descriptors allowed for + the application. +2. Listener gets `Too many open files` error so it sleeps. +3. Some client sends a request via the previously open connection. +4. Opening a file to serve request fails, because of the same + `Too many open files` error, until some other client drops a connection. + +There are many more possible situations, this is just a small illustation that +limiting number of connections is very useful. Generally, it's one of the ways +to control resources used by a server and avoiding some kinds of deny of +service (DoS) attacks. + +### `async-listen` crate + +Limiting maximum number of simultaneous connections with [`async-listen`] +looks like the following: + +```rust,edition2018 +# extern crate async_std; +# extern crate async_listen; +# use std::time::Duration; +# use async_std::{ +# net::{TcpListener, TcpStream, ToSocketAddrs}, +# prelude::*, +# }; +# +# type Result = std::result::Result>; +# +use async_listen::{ListenExt, Token, error_hint}; + +async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { + + let listener = TcpListener::bind(addr).await?; + let mut incoming = listener + .incoming() + .log_warnings(log_accept_error) + .handle_errors(Duration::from_millis(500)) // 1 + .backpressure(100); + while let Some((token, socket)) = incoming.next().await { // 2 + task::spawn(async move { + connection_loop(&token, stream).await; // 3 + }); + } + Ok(()) +} +async fn connection_loop(_token: &Token, stream: TcpStream) { // 4 + // ... +} +# fn log_accept_error(e: &io::Error) { +# eprintln!("Error: {}. Listener paused for 0.5s. {}", e, error_hint(e)); +# } +``` + +1. We need to handle errors first, because [`backpressure`] helper expects + stream of `TcpStream` rather than `Result`. +2. The token yielded by a new stream is what is counted by backpressure helper. + I.e. if you drop a token, new connection can be established. +3. We give the connection loop a reference to token to bind token's lifetime to + the lifetime of the connection. +4. The token itsellf in the function can be ignored, hence `_token` + +[`backpressure`]: https://docs.rs/async-listen/0.1.2/async_listen/trait.ListenExt.html#method.backpressure + +Be sure to [test this behavior](#testing-application). diff --git a/docs/src/tutorial/implementing_a_client.md b/docs/src/tutorial/implementing_a_client.md index fd72855..ba9d6f3 100644 --- a/docs/src/tutorial/implementing_a_client.md +++ b/docs/src/tutorial/implementing_a_client.md @@ -1,18 +1,16 @@ ## Implementing a client -Let's now implement the client for the chat. -Because the protocol is line-based, the implementation is pretty straightforward: +Since the protocol is line-based, implementing a client for the chat is straightforward: * Lines read from stdin should be sent over the socket. * Lines read from the socket should be echoed to stdout. -Unlike the server, the client needs only limited concurrency, as it interacts with only a single user. -For this reason, async doesn't bring a lot of performance benefits in this case. +Although async does not significantly affect client performance (as unlike the server, the client interacts solely with one user and only needs limited concurrency), async is still useful for managing concurrency! + +The client has to read from stdin and the socket *simultaneously*. +Programming this with threads is cumbersome, especially when implementing a clean shutdown. +With async, the `select!` macro is all that is needed. -However, async is still useful for managing concurrency! -Specifically, the client should *simultaneously* read from stdin and from the socket. -Programming this with threads is cumbersome, especially when implementing clean shutdown. -With async, we can just use the `select!` macro. ```rust,edition2018 # extern crate async_std; diff --git a/docs/src/tutorial/receiving_messages.md b/docs/src/tutorial/receiving_messages.md index 4f70529..f62b65d 100644 --- a/docs/src/tutorial/receiving_messages.md +++ b/docs/src/tutorial/receiving_messages.md @@ -111,7 +111,7 @@ We can "fix" it by waiting for the task to be joined, like this: # # async move |stream| { let handle = task::spawn(connection_loop(stream)); -handle.await +handle.await? # }; ``` diff --git a/examples/tcp-echo.rs b/examples/tcp-echo.rs index 7c50be0..c04f077 100644 --- a/examples/tcp-echo.rs +++ b/examples/tcp-echo.rs @@ -14,8 +14,9 @@ use async_std::task; async fn process(stream: TcpStream) -> io::Result<()> { println!("Accepted from: {}", stream.peer_addr()?); - let (reader, writer) = &mut (&stream, &stream); - io::copy(reader, writer).await?; + let mut reader = stream.clone(); + let mut writer = stream; + io::copy(&mut reader, &mut writer).await?; Ok(()) } diff --git a/examples/tcp-ipv4-and-6-echo.rs b/examples/tcp-ipv4-and-6-echo.rs index aef5e15..a00e1f3 100644 --- a/examples/tcp-ipv4-and-6-echo.rs +++ b/examples/tcp-ipv4-and-6-echo.rs @@ -15,8 +15,9 @@ use async_std::task; async fn process(stream: TcpStream) -> io::Result<()> { println!("Accepted from: {}", stream.peer_addr()?); - let (reader, writer) = &mut (&stream, &stream); - io::copy(reader, writer).await?; + let mut reader = stream.clone(); + let mut writer = stream; + io::copy(&mut reader, &mut writer).await?; Ok(()) } diff --git a/rustfmt.toml b/rustfmt.toml index c6d404e..1082fd8 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1 @@ version = "Two" -format_code_in_doc_comments = true diff --git a/src/fs/dir_entry.rs b/src/fs/dir_entry.rs index 527fab4..e1622ee 100644 --- a/src/fs/dir_entry.rs +++ b/src/fs/dir_entry.rs @@ -158,6 +158,12 @@ impl fmt::Debug for DirEntry { } } +impl Clone for DirEntry { + fn clone(&self) -> Self { + DirEntry(self.0.clone()) + } +} + cfg_unix! { use crate::os::unix::fs::DirEntryExt; diff --git a/src/fs/file.rs b/src/fs/file.rs index 7fe99ee..2ff5643 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -12,7 +12,7 @@ use crate::future; use crate::io::{self, Read, Seek, SeekFrom, Write}; use crate::path::Path; use crate::prelude::*; -use crate::task::{self, spawn_blocking, Context, Poll, Waker}; +use crate::task::{spawn_blocking, Context, Poll, Waker}; use crate::utils::Context as _; /// An open file on the filesystem. @@ -315,7 +315,7 @@ impl Drop for File { // non-blocking fashion, but our only other option here is losing data remaining in the // write cache. Good task schedulers should be resilient to occasional blocking hiccups in // file destructors so we don't expect this to be a common problem in practice. - let _ = task::block_on(self.flush()); + let _ = smol::block_on(self.flush()); } } @@ -673,7 +673,7 @@ impl LockGuard { if available > 0 || self.cache.is_empty() { // Copy data from the cache into the buffer. let n = cmp::min(available, buf.len()); - buf[..n].copy_from_slice(&self.cache[start..n]); + buf[..n].copy_from_slice(&self.cache[start..(start + n)]); // Move the read cursor forward. self.mode = Mode::Reading(start + n); @@ -867,3 +867,15 @@ impl LockGuard { Poll::Ready(Ok(())) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn async_file_drop() { + crate::task::block_on(async move { + File::open(file!()).await.unwrap(); + }); + } +} diff --git a/src/future/future/delay.rs b/src/future/future/delay.rs index 641084f..b6c30bc 100644 --- a/src/future/future/delay.rs +++ b/src/future/future/delay.rs @@ -2,10 +2,10 @@ use std::future::Future; use std::pin::Pin; use std::time::Duration; -use futures_timer::Delay; use pin_project_lite::pin_project; use crate::task::{Context, Poll}; +use crate::utils::Timer; pin_project! { #[doc(hidden)] @@ -14,13 +14,13 @@ pin_project! { #[pin] future: F, #[pin] - delay: Delay, + delay: Timer, } } impl DelayFuture { pub fn new(future: F, dur: Duration) -> DelayFuture { - let delay = Delay::new(dur); + let delay = Timer::after(dur); DelayFuture { future, delay } } diff --git a/src/future/future/mod.rs b/src/future/future/mod.rs index d610096..24f3fb5 100644 --- a/src/future/future/mod.rs +++ b/src/future/future/mod.rs @@ -21,8 +21,8 @@ cfg_unstable_default! { } extension_trait! { - use std::pin::Pin; - use std::ops::{Deref, DerefMut}; + use core::pin::Pin; + use core::ops::{Deref, DerefMut}; use crate::task::{Context, Poll}; @@ -136,7 +136,7 @@ extension_trait! { [`Future`]: ../future/trait.Future.html "#] - pub trait FutureExt: std::future::Future { + pub trait FutureExt: core::future::Future { /// Returns a Future that delays execution for a specified time. /// /// # Examples diff --git a/src/future/mod.rs b/src/future/mod.rs index 9936276..db0607a 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -46,21 +46,26 @@ //! [`Future::race`]: trait.Future.html#method.race //! [`Future::try_race`]: trait.Future.html#method.try_race -pub use future::Future; -pub use pending::pending; -pub use poll_fn::poll_fn; -pub use ready::ready; +cfg_alloc! { + pub use future::Future; + pub(crate) mod future; +} -pub(crate) mod future; -mod pending; -mod poll_fn; -mod ready; +cfg_std! { + pub use pending::pending; + pub use poll_fn::poll_fn; + pub use ready::ready; -cfg_default! { - pub use timeout::{timeout, TimeoutError}; - mod timeout; + mod pending; + mod poll_fn; + mod ready; } +#[cfg(any(feature = "unstable", feature = "default"))] +pub use timeout::{timeout, TimeoutError}; +#[cfg(any(feature = "unstable", feature = "default"))] +mod timeout; + cfg_unstable! { pub use into_future::IntoFuture; pub(crate) use maybe_done::MaybeDone; diff --git a/src/future/timeout.rs b/src/future/timeout.rs index 05aaa45..4a9d93c 100644 --- a/src/future/timeout.rs +++ b/src/future/timeout.rs @@ -1,13 +1,13 @@ use std::error::Error; use std::fmt; +use std::future::Future; use std::pin::Pin; use std::time::Duration; -use std::future::Future; -use futures_timer::Delay; use pin_project_lite::pin_project; use crate::task::{Context, Poll}; +use crate::utils::Timer; /// Awaits a future or times out after a duration of time. /// @@ -33,11 +33,7 @@ pub async fn timeout(dur: Duration, f: F) -> Result where F: Future, { - let f = TimeoutFuture { - future: f, - delay: Delay::new(dur), - }; - f.await + TimeoutFuture::new(f, dur).await } pin_project! { @@ -46,14 +42,17 @@ pin_project! { #[pin] future: F, #[pin] - delay: Delay, + delay: Timer, } } impl TimeoutFuture { #[allow(dead_code)] pub(super) fn new(future: F, dur: Duration) -> TimeoutFuture { - TimeoutFuture { future: future, delay: Delay::new(dur) } + TimeoutFuture { + future, + delay: Timer::after(dur), + } } } diff --git a/src/io/buf_read/mod.rs b/src/io/buf_read/mod.rs index d919a78..7a0ecc6 100644 --- a/src/io/buf_read/mod.rs +++ b/src/io/buf_read/mod.rs @@ -29,7 +29,7 @@ extension_trait! { ``` # #[allow(unused_imports)] - use async_std::prelude::*; + use async_std::io::prelude::*; ``` [`std::io::BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html diff --git a/src/io/mod.rs b/src/io/mod.rs index 51c473d..a673636 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -275,7 +275,7 @@ cfg_std! { #[doc(inline)] pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; - pub use buf_read::{BufRead, Lines}; + pub use buf_read::{BufRead, Lines, Split}; pub use buf_reader::BufReader; pub use buf_writer::{BufWriter, IntoInnerError}; pub use copy::copy; @@ -307,22 +307,24 @@ cfg_std! { cfg_default! { // For use in the print macros. #[doc(hidden)] + #[cfg(not(target_os = "unknown"))] pub use stdio::{_eprint, _print}; + #[cfg(not(target_os = "unknown"))] pub use stderr::{stderr, Stderr}; + #[cfg(not(target_os = "unknown"))] pub use stdin::{stdin, Stdin}; + #[cfg(not(target_os = "unknown"))] pub use stdout::{stdout, Stdout}; pub use timeout::timeout; mod timeout; + #[cfg(not(target_os = "unknown"))] mod stderr; + #[cfg(not(target_os = "unknown"))] mod stdin; + #[cfg(not(target_os = "unknown"))] mod stdio; + #[cfg(not(target_os = "unknown"))] mod stdout; } - -cfg_unstable_default! { - pub use stderr::StderrLock; - pub use stdin::StdinLock; - pub use stdout::StdoutLock; -} diff --git a/src/io/read/mod.rs b/src/io/read/mod.rs index 8aade18..388237c 100644 --- a/src/io/read/mod.rs +++ b/src/io/read/mod.rs @@ -17,9 +17,9 @@ use std::mem; use crate::io::IoSliceMut; -pub use take::Take; pub use bytes::Bytes; pub use chain::Chain; +pub use take::Take; extension_trait! { use std::pin::Pin; @@ -477,13 +477,13 @@ unsafe fn initialize(_reader: &R, buf: &mut [u8]) { std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) } -#[cfg(test)] +#[cfg(all(test, not(target_os = "unknown")))] mod tests { use crate::io; use crate::prelude::*; #[test] - fn test_read_by_ref() -> io::Result<()> { + fn test_read_by_ref() { crate::task::block_on(async { let mut f = io::Cursor::new(vec![0u8, 1, 2, 3, 4, 5, 6, 7, 8]); let mut buffer = Vec::new(); @@ -493,14 +493,13 @@ mod tests { let reference = f.by_ref(); // read at most 5 bytes - assert_eq!(reference.take(5).read_to_end(&mut buffer).await?, 5); + assert_eq!(reference.take(5).read_to_end(&mut buffer).await.unwrap(), 5); assert_eq!(&buffer, &[0, 1, 2, 3, 4]) } // drop our &mut reference so we can use f again // original file still usable, read the rest - assert_eq!(f.read_to_end(&mut other_buffer).await?, 4); + assert_eq!(f.read_to_end(&mut other_buffer).await.unwrap(), 4); assert_eq!(&other_buffer, &[5, 6, 7, 8]); - Ok(()) - }) + }); } } diff --git a/src/io/read/take.rs b/src/io/read/take.rs index 09b02c2..ba9a9e3 100644 --- a/src/io/read/take.rs +++ b/src/io/read/take.rs @@ -218,7 +218,7 @@ impl BufRead for Take { } } -#[cfg(test)] +#[cfg(all(test, not(target_os = "unknown")))] mod tests { use crate::io; use crate::prelude::*; diff --git a/src/io/stderr.rs b/src/io/stderr.rs index 5ff8a02..5067ed4 100644 --- a/src/io/stderr.rs +++ b/src/io/stderr.rs @@ -5,11 +5,6 @@ use std::future::Future; use crate::io::{self, Write}; use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; -cfg_unstable! { - use once_cell::sync::Lazy; - use std::io::Write as _; -} - /// Constructs a new handle to the standard error of the current process. /// /// This function is an async version of [`std::io::stderr`]. @@ -58,22 +53,6 @@ pub fn stderr() -> Stderr { #[derive(Debug)] pub struct Stderr(Mutex); -/// A locked reference to the Stderr handle. -/// -/// This handle implements the [`Write`] traits, and is constructed via the [`Stderr::lock`] -/// method. -/// -/// [`Write`]: trait.Read.html -/// [`Stderr::lock`]: struct.Stderr.html#method.lock -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[derive(Debug)] -pub struct StderrLock<'a>(std::io::StderrLock<'a>); - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -unsafe impl Send for StderrLock<'_> {} - /// The state of the asynchronous stderr. /// /// The stderr can be either idle or busy performing an asynchronous operation. @@ -108,35 +87,6 @@ enum Operation { Flush(io::Result<()>), } -impl Stderr { - /// Locks this handle to the standard error stream, returning a writable guard. - /// - /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Write trait for writing data. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::io; - /// use async_std::prelude::*; - /// - /// let stderr = io::stderr(); - /// let mut handle = stderr.lock().await; - /// - /// handle.write_all(b"hello world").await?; - /// # - /// # Ok(()) }) } - /// ``` - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[cfg(any(feature = "unstable", feature = "docs"))] - pub async fn lock(&self) -> StderrLock<'static> { - static STDERR: Lazy = Lazy::new(std::io::stderr); - - spawn_blocking(move || StderrLock(STDERR.lock())).await - } -} - impl Write for Stderr { fn poll_write( mut self: Pin<&mut Self>, @@ -239,23 +189,3 @@ cfg_windows! { } } } - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -impl io::Write for StderrLock<'_> { - fn poll_write( - mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Poll::Ready(self.0.write(buf)) - } - - fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(self.0.flush()) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.poll_flush(cx) - } -} diff --git a/src/io/stdin.rs b/src/io/stdin.rs index 369ccae..fc280f8 100644 --- a/src/io/stdin.rs +++ b/src/io/stdin.rs @@ -7,11 +7,6 @@ use crate::io::{self, Read}; use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; use crate::utils::Context as _; -cfg_unstable! { - use once_cell::sync::Lazy; - use std::io::Read as _; -} - /// Constructs a new handle to the standard input of the current process. /// /// This function is an async version of [`std::io::stdin`]. @@ -61,21 +56,6 @@ pub fn stdin() -> Stdin { #[derive(Debug)] pub struct Stdin(Mutex); -/// A locked reference to the Stdin handle. -/// -/// This handle implements the [`Read`] traits, and is constructed via the [`Stdin::lock`] method. -/// -/// [`Read`]: trait.Read.html -/// [`Stdin::lock`]: struct.Stdin.html#method.lock -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(feature = "unstable")] -#[derive(Debug)] -pub struct StdinLock<'a>(std::io::StdinLock<'a>); - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -unsafe impl Send for StdinLock<'_> {} - /// The state of the asynchronous stdin. /// /// The stdin can be either idle or busy performing an asynchronous operation. @@ -165,35 +145,6 @@ impl Stdin { .await .context(|| String::from("could not read line on stdin")) } - - /// Locks this handle to the standard input stream, returning a readable guard. - /// - /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Read trait for accessing the underlying data. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::io; - /// use async_std::prelude::*; - /// - /// let mut buffer = String::new(); - /// - /// let stdin = io::stdin(); - /// let mut handle = stdin.lock().await; - /// - /// handle.read_to_string(&mut buffer).await?; - /// # - /// # Ok(()) }) } - /// ``` - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[cfg(any(feature = "unstable", feature = "docs"))] - pub async fn lock(&self) -> StdinLock<'static> { - static STDIN: Lazy = Lazy::new(std::io::stdin); - - spawn_blocking(move || StdinLock(STDIN.lock())).await - } } impl Read for Stdin { @@ -265,15 +216,3 @@ cfg_windows! { } } } - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -impl Read for StdinLock<'_> { - fn poll_read( - mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Poll::Ready(self.0.read(buf)) - } -} diff --git a/src/io/stdout.rs b/src/io/stdout.rs index 1711c09..b3dfe64 100644 --- a/src/io/stdout.rs +++ b/src/io/stdout.rs @@ -5,11 +5,6 @@ use std::future::Future; use crate::io::{self, Write}; use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; -cfg_unstable! { - use once_cell::sync::Lazy; - use std::io::Write as _; -} - /// Constructs a new handle to the standard output of the current process. /// /// This function is an async version of [`std::io::stdout`]. @@ -58,22 +53,6 @@ pub fn stdout() -> Stdout { #[derive(Debug)] pub struct Stdout(Mutex); -/// A locked reference to the Stderr handle. -/// -/// This handle implements the [`Write`] traits, and is constructed via the [`Stdout::lock`] -/// method. -/// -/// [`Write`]: trait.Read.html -/// [`Stdout::lock`]: struct.Stdout.html#method.lock -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[derive(Debug)] -pub struct StdoutLock<'a>(std::io::StdoutLock<'a>); - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -unsafe impl Send for StdoutLock<'_> {} - /// The state of the asynchronous stdout. /// /// The stdout can be either idle or busy performing an asynchronous operation. @@ -108,35 +87,6 @@ enum Operation { Flush(io::Result<()>), } -impl Stdout { - /// Locks this handle to the standard error stream, returning a writable guard. - /// - /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Write trait for writing data. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::io; - /// use async_std::prelude::*; - /// - /// let stdout = io::stdout(); - /// let mut handle = stdout.lock().await; - /// - /// handle.write_all(b"hello world").await?; - /// # - /// # Ok(()) }) } - /// ``` - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[cfg(any(feature = "unstable", feature = "docs"))] - pub async fn lock(&self) -> StdoutLock<'static> { - static STDOUT: Lazy = Lazy::new(std::io::stdout); - - spawn_blocking(move || StdoutLock(STDOUT.lock())).await - } -} - impl Write for Stdout { fn poll_write( mut self: Pin<&mut Self>, @@ -239,23 +189,3 @@ cfg_windows! { } } } - -#[cfg(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -impl Write for StdoutLock<'_> { - fn poll_write( - mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Poll::Ready(self.0.write(buf)) - } - - fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(self.0.flush()) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.poll_flush(cx) - } -} diff --git a/src/io/timeout.rs b/src/io/timeout.rs index 6e22dbf..ce33fea 100644 --- a/src/io/timeout.rs +++ b/src/io/timeout.rs @@ -1,12 +1,12 @@ +use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; -use std::future::Future; -use futures_timer::Delay; use pin_project_lite::pin_project; use crate::io; +use crate::utils::Timer; /// Awaits an I/O future or times out after a duration of time. /// @@ -37,7 +37,7 @@ where F: Future>, { Timeout { - timeout: Delay::new(dur), + timeout: Timer::after(dur), future: f, } .await @@ -53,7 +53,7 @@ pin_project! { #[pin] future: F, #[pin] - timeout: Delay, + timeout: Timer, } } diff --git a/src/lib.rs b/src/lib.rs index c9d1277..6f76265 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,8 @@ //! //! Call an async function from the main function: //! -//! ``` +#![cfg_attr(feature = "attributes", doc = "```")] +#![cfg_attr(not(feature = "attributes"), doc = "```ignore")] //! async fn say_hello() { //! println!("Hello, world!"); //! } @@ -151,7 +152,8 @@ //! //! Await two futures concurrently, and return a tuple of their output: //! -//! ``` +#![cfg_attr(feature = "attributes", doc = "```")] +#![cfg_attr(not(feature = "attributes"), doc = "```ignore")] //! use async_std::prelude::*; //! //! #[async_std::main] @@ -164,7 +166,8 @@ //! //! Create a UDP server that echoes back each received message to the sender: //! -//! ```no_run +#![cfg_attr(feature = "attributes", doc = "```no_run")] +#![cfg_attr(not(feature = "attributes"), doc = "```ignore")] //! use async_std::net::UdpSocket; //! //! #[async_std::main] @@ -194,7 +197,7 @@ //! //! ```toml //! [dependencies.async-std] -//! version = "1.0.0" +//! version = "1.6.1" //! features = ["unstable"] //! ``` //! @@ -207,20 +210,55 @@ //! //! ```toml //! [dependencies.async-std] -//! version = "1.0.0" +//! version = "1.6.1" //! features = ["attributes"] //! ``` //! +//! Compatibility with the `tokio` runtime is possible using the `tokio02` +//! Cargo feature: +//! +//! ```toml +//! [dependencies.async-std] +//! version = "1.6.1" +//! features = ["tokio02"] +//! ``` +//! //! Additionally it's possible to only use the core traits and combinators by //! only enabling the `std` Cargo feature: //! //! ```toml //! [dependencies.async-std] -//! version = "1.0.0" +//! version = "1.6.1" //! default-features = false //! features = ["std"] //! ``` +//! +//! And to use async-std on `no_std` targets that only support `alloc` only +//! enable the `alloc` Cargo feature: +//! +//! ```toml +//! [dependencies.async-std] +//! version = "1.6.1" +//! default-features = false +//! features = ["alloc"] +//! ``` +//! +//! # Runtime configuration +//! +//! Several environment variables are available to tune the async-std +//! runtime: +//! +//! * `ASYNC_STD_THREAD_COUNT`: The number of threads that the +//! async-std runtime will start. By default, this is one per logical +//! cpu as reported by the [num_cpus](num_cpus) crate, which may be +//! different than the number of physical cpus. Async-std _will panic_ +//! if this is set to any value other than a positive integer. +//! * `ASYNC_STD_THREAD_NAME`: The name that async-std's runtime +//! threads report to the operating system. The default value is +//! `"async-std/runtime"`. +//! +#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "docs", feature(doc_cfg))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] #![allow(clippy::mutex_atomic, clippy::module_inception)] @@ -229,6 +267,8 @@ #![doc(html_logo_url = "https://async.rs/images/logo--hero.svg")] #![recursion_limit = "2048"] +extern crate alloc; + #[macro_use] mod utils; @@ -240,24 +280,31 @@ pub use async_attributes::{main, test}; #[cfg(feature = "std")] mod macros; -cfg_std! { +cfg_alloc! { + pub mod task; pub mod future; + pub mod stream; +} + +cfg_std! { pub mod io; pub mod os; pub mod prelude; - pub mod stream; pub mod sync; - pub mod task; } cfg_default! { + #[cfg(not(target_os = "unknown"))] pub mod fs; pub mod path; pub mod net; + #[cfg(not(target_os = "unknown"))] + pub(crate) mod rt; } cfg_unstable! { pub mod pin; + #[cfg(not(target_os = "unknown"))] pub mod process; mod unit; diff --git a/src/net/driver/mod.rs b/src/net/driver/mod.rs deleted file mode 100644 index 7f33e85..0000000 --- a/src/net/driver/mod.rs +++ /dev/null @@ -1,315 +0,0 @@ -use std::fmt; -use std::sync::{Arc, Mutex}; - -use mio::{self, Evented}; -use once_cell::sync::Lazy; -use slab::Slab; - -use crate::io; -use crate::task::{Context, Poll, Waker}; -use crate::utils::abort_on_panic; - -/// Data associated with a registered I/O handle. -#[derive(Debug)] -struct Entry { - /// A unique identifier. - token: mio::Token, - - /// Tasks that are blocked on reading from this I/O handle. - readers: Mutex>, - - /// Thasks that are blocked on writing to this I/O handle. - writers: Mutex>, -} - -/// The state of a networking driver. -struct Reactor { - /// A mio instance that polls for new events. - poller: mio::Poll, - - /// A collection of registered I/O handles. - entries: Mutex>>, - - /// Dummy I/O handle that is only used to wake up the polling thread. - notify_reg: (mio::Registration, mio::SetReadiness), - - /// An identifier for the notification handle. - notify_token: mio::Token, -} - -impl Reactor { - /// Creates a new reactor for polling I/O events. - fn new() -> io::Result { - let poller = mio::Poll::new()?; - let notify_reg = mio::Registration::new2(); - - let mut reactor = Reactor { - poller, - entries: Mutex::new(Slab::new()), - notify_reg, - notify_token: mio::Token(0), - }; - - // Register a dummy I/O handle for waking up the polling thread. - let entry = reactor.register(&reactor.notify_reg.0)?; - reactor.notify_token = entry.token; - - Ok(reactor) - } - - /// Registers an I/O event source and returns its associated entry. - fn register(&self, source: &dyn Evented) -> io::Result> { - let mut entries = self.entries.lock().unwrap(); - - // Reserve a vacant spot in the slab and use its key as the token value. - let vacant = entries.vacant_entry(); - let token = mio::Token(vacant.key()); - - // Allocate an entry and insert it into the slab. - let entry = Arc::new(Entry { - token, - readers: Mutex::new(Vec::new()), - writers: Mutex::new(Vec::new()), - }); - vacant.insert(entry.clone()); - - // Register the I/O event source in the poller. - let interest = mio::Ready::all(); - let opts = mio::PollOpt::edge(); - self.poller.register(source, token, interest, opts)?; - - Ok(entry) - } - - /// Deregisters an I/O event source associated with an entry. - fn deregister(&self, source: &dyn Evented, entry: &Entry) -> io::Result<()> { - // Deregister the I/O object from the mio instance. - self.poller.deregister(source)?; - - // Remove the entry associated with the I/O object. - self.entries.lock().unwrap().remove(entry.token.0); - - Ok(()) - } - - // fn notify(&self) { - // self.notify_reg - // .1 - // .set_readiness(mio::Ready::readable()) - // .unwrap(); - // } -} - -/// The state of the global networking driver. -static REACTOR: Lazy = Lazy::new(|| { - // Spawn a thread that waits on the poller for new events and wakes up tasks blocked on I/O - // handles. - std::thread::Builder::new() - .name("async-std/net".to_string()) - .spawn(move || { - // If the driver thread panics, there's not much we can do. It is not a - // recoverable error and there is no place to propagate it into so we just abort. - abort_on_panic(|| { - main_loop().expect("async networking thread has panicked"); - }) - }) - .expect("cannot start a thread driving blocking tasks"); - - Reactor::new().expect("cannot initialize reactor") -}); - -/// Waits on the poller for new events and wakes up tasks blocked on I/O handles. -fn main_loop() -> io::Result<()> { - let reactor = &REACTOR; - let mut events = mio::Events::with_capacity(1000); - - loop { - // Block on the poller until at least one new event comes in. - reactor.poller.poll(&mut events, None)?; - - // Lock the entire entry table while we're processing new events. - let entries = reactor.entries.lock().unwrap(); - - for event in events.iter() { - let token = event.token(); - - if token == reactor.notify_token { - // If this is the notification token, we just need the notification state. - reactor.notify_reg.1.set_readiness(mio::Ready::empty())?; - } else { - // Otherwise, look for the entry associated with this token. - if let Some(entry) = entries.get(token.0) { - // Set the readiness flags from this I/O event. - let readiness = event.readiness(); - - // Wake up reader tasks blocked on this I/O handle. - if !(readiness & reader_interests()).is_empty() { - for w in entry.readers.lock().unwrap().drain(..) { - w.wake(); - } - } - - // Wake up writer tasks blocked on this I/O handle. - if !(readiness & writer_interests()).is_empty() { - for w in entry.writers.lock().unwrap().drain(..) { - w.wake(); - } - } - } - } - } - } -} - -/// An I/O handle powered by the networking driver. -/// -/// This handle wraps an I/O event source and exposes a "futurized" interface on top of it, -/// implementing traits `AsyncRead` and `AsyncWrite`. -pub struct Watcher { - /// Data associated with the I/O handle. - entry: Arc, - - /// The I/O event source. - source: Option, -} - -impl Watcher { - /// Creates a new I/O handle. - /// - /// The provided I/O event source will be kept registered inside the reactor's poller for the - /// lifetime of the returned I/O handle. - pub fn new(source: T) -> Watcher { - Watcher { - entry: REACTOR - .register(&source) - .expect("cannot register an I/O event source"), - source: Some(source), - } - } - - /// Returns a reference to the inner I/O event source. - pub fn get_ref(&self) -> &T { - self.source.as_ref().unwrap() - } - - /// Polls the inner I/O source for a non-blocking read operation. - /// - /// If the operation returns an error of the `io::ErrorKind::WouldBlock` kind, the current task - /// will be registered for wakeup when the I/O source becomes readable. - pub fn poll_read_with<'a, F, R>(&'a self, cx: &mut Context<'_>, mut f: F) -> Poll> - where - F: FnMut(&'a T) -> io::Result, - { - // If the operation isn't blocked, return its result. - match f(self.source.as_ref().unwrap()) { - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - res => return Poll::Ready(res), - } - - // Lock the waker list. - let mut list = self.entry.readers.lock().unwrap(); - - // Try running the operation again. - match f(self.source.as_ref().unwrap()) { - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - res => return Poll::Ready(res), - } - - // Register the task if it isn't registered already. - if list.iter().all(|w| !w.will_wake(cx.waker())) { - list.push(cx.waker().clone()); - } - - Poll::Pending - } - - /// Polls the inner I/O source for a non-blocking write operation. - /// - /// If the operation returns an error of the `io::ErrorKind::WouldBlock` kind, the current task - /// will be registered for wakeup when the I/O source becomes writable. - pub fn poll_write_with<'a, F, R>( - &'a self, - cx: &mut Context<'_>, - mut f: F, - ) -> Poll> - where - F: FnMut(&'a T) -> io::Result, - { - // If the operation isn't blocked, return its result. - match f(self.source.as_ref().unwrap()) { - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - res => return Poll::Ready(res), - } - - // Lock the waker list. - let mut list = self.entry.writers.lock().unwrap(); - - // Try running the operation again. - match f(self.source.as_ref().unwrap()) { - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - res => return Poll::Ready(res), - } - - // Register the task if it isn't registered already. - if list.iter().all(|w| !w.will_wake(cx.waker())) { - list.push(cx.waker().clone()); - } - - Poll::Pending - } - - /// Deregisters and returns the inner I/O source. - /// - /// This method is typically used to convert `Watcher`s to raw file descriptors/handles. - #[allow(dead_code)] - pub fn into_inner(mut self) -> T { - let source = self.source.take().unwrap(); - REACTOR - .deregister(&source, &self.entry) - .expect("cannot deregister I/O event source"); - source - } -} - -impl Drop for Watcher { - fn drop(&mut self) { - if let Some(ref source) = self.source { - REACTOR - .deregister(source, &self.entry) - .expect("cannot deregister I/O event source"); - } - } -} - -impl fmt::Debug for Watcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Watcher") - .field("entry", &self.entry) - .field("source", &self.source) - .finish() - } -} - -/// Returns a mask containing flags that interest tasks reading from I/O handles. -#[inline] -fn reader_interests() -> mio::Ready { - mio::Ready::all() - mio::Ready::writable() -} - -/// Returns a mask containing flags that interest tasks writing into I/O handles. -#[inline] -fn writer_interests() -> mio::Ready { - mio::Ready::writable() | hup() -} - -/// Returns a flag containing the hangup status. -#[inline] -fn hup() -> mio::Ready { - #[cfg(unix)] - let ready = mio::unix::UnixReady::hup().into(); - - #[cfg(not(unix))] - let ready = mio::Ready::empty(); - - ready -} diff --git a/src/net/mod.rs b/src/net/mod.rs index 29e4309..1814073 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -61,11 +61,16 @@ pub use std::net::Shutdown; pub use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; pub use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; +#[cfg(not(target_os = "unknown"))] pub use addr::ToSocketAddrs; +#[cfg(not(target_os = "unknown"))] pub use tcp::{Incoming, TcpListener, TcpStream}; +#[cfg(not(target_os = "unknown"))] pub use udp::UdpSocket; +#[cfg(not(target_os = "unknown"))] mod addr; -pub(crate) mod driver; +#[cfg(not(target_os = "unknown"))] mod tcp; +#[cfg(not(target_os = "unknown"))] mod udp; diff --git a/src/net/tcp/listener.rs b/src/net/tcp/listener.rs index fe06a96..72c5d3a 100644 --- a/src/net/tcp/listener.rs +++ b/src/net/tcp/listener.rs @@ -2,11 +2,12 @@ use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; -use crate::future; +use smol::Async; + use crate::io; -use crate::net::driver::Watcher; use crate::net::{TcpStream, ToSocketAddrs}; use crate::stream::Stream; +use crate::sync::Arc; use crate::task::{Context, Poll}; /// A TCP socket server, listening for connections. @@ -48,7 +49,7 @@ use crate::task::{Context, Poll}; /// ``` #[derive(Debug)] pub struct TcpListener { - watcher: Watcher, + watcher: Async, } impl TcpListener { @@ -75,16 +76,12 @@ impl TcpListener { /// [`local_addr`]: #method.local_addr pub async fn bind(addrs: A) -> io::Result { let mut last_err = None; - let addrs = addrs - .to_socket_addrs() - .await?; + let addrs = addrs.to_socket_addrs().await?; for addr in addrs { - match mio::net::TcpListener::bind(&addr) { - Ok(mio_listener) => { - return Ok(TcpListener { - watcher: Watcher::new(mio_listener), - }); + match Async::::bind(&addr) { + Ok(listener) => { + return Ok(TcpListener { watcher: listener }); } Err(err) => last_err = Some(err), } @@ -115,13 +112,9 @@ impl TcpListener { /// # Ok(()) }) } /// ``` pub async fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { - let (io, addr) = - future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.accept_std())) - .await?; - - let mio_stream = mio::net::TcpStream::from_stream(io)?; + let (stream, addr) = self.watcher.accept().await?; let stream = TcpStream { - watcher: Watcher::new(mio_stream), + watcher: Arc::new(stream), }; Ok((stream, addr)) } @@ -207,9 +200,8 @@ impl<'a> Stream for Incoming<'a> { impl From for TcpListener { /// Converts a `std::net::TcpListener` into its asynchronous equivalent. fn from(listener: std::net::TcpListener) -> TcpListener { - let mio_listener = mio::net::TcpListener::from_std(listener).unwrap(); TcpListener { - watcher: Watcher::new(mio_listener), + watcher: Async::new(listener).expect("TcpListener is known to be good"), } } } @@ -231,29 +223,31 @@ cfg_unix! { impl IntoRawFd for TcpListener { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + self.watcher.into_raw_fd() } } } cfg_windows! { - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - // - // impl AsRawSocket for TcpListener { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for TcpListener { - // unsafe fn from_raw_socket(handle: RawSocket) -> TcpListener { - // net::TcpListener::from_raw_socket(handle).try_into().unwrap() - // } - // } - // - // impl IntoRawSocket for TcpListener { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } + use crate::os::windows::io::{ + AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket, + }; + + impl AsRawSocket for TcpListener { + fn as_raw_socket(&self) -> RawSocket { + self.watcher.as_raw_socket() + } + } + + impl FromRawSocket for TcpListener { + unsafe fn from_raw_socket(handle: RawSocket) -> TcpListener { + std::net::TcpListener::from_raw_socket(handle).into() + } + } + + impl IntoRawSocket for TcpListener { + fn into_raw_socket(self) -> RawSocket { + self.watcher.into_raw_socket() + } + } } diff --git a/src/net/tcp/stream.rs b/src/net/tcp/stream.rs index ae8ca7d..b854143 100644 --- a/src/net/tcp/stream.rs +++ b/src/net/tcp/stream.rs @@ -1,13 +1,13 @@ -use std::io::{IoSlice, IoSliceMut, Read as _, Write as _}; +use std::io::{IoSlice, IoSliceMut}; use std::net::SocketAddr; use std::pin::Pin; -use crate::future; +use smol::Async; + use crate::io::{self, Read, Write}; -use crate::net::driver::Watcher; use crate::net::ToSocketAddrs; -use crate::task::{spawn_blocking, Context, Poll}; -use crate::utils::Context as _; +use crate::sync::Arc; +use crate::task::{Context, Poll}; /// A TCP stream between a local and a remote socket. /// @@ -45,9 +45,9 @@ use crate::utils::Context as _; /// # /// # Ok(()) }) } /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TcpStream { - pub(super) watcher: Watcher, + pub(super) watcher: Arc>, } impl TcpStream { @@ -72,25 +72,19 @@ impl TcpStream { /// ``` pub async fn connect(addrs: A) -> io::Result { let mut last_err = None; - let addrs = addrs - .to_socket_addrs() - .await?; + let addrs = addrs.to_socket_addrs().await?; for addr in addrs { - let res = spawn_blocking(move || { - let std_stream = std::net::TcpStream::connect(addr) - .context(|| format!("could not connect to {}", addr))?; - let mio_stream = mio::net::TcpStream::from_stream(std_stream) - .context(|| format!("could not open async connection to {}", addr))?; - Ok(TcpStream { - watcher: Watcher::new(mio_stream), - }) - }) - .await; - - match res { - Ok(stream) => return Ok(stream), - Err(err) => last_err = Some(err), + match Async::::connect(&addr).await { + Ok(stream) => { + return Ok(TcpStream { + watcher: Arc::new(stream), + }); + } + Err(e) => { + last_err = Some(e); + continue; + } } } @@ -208,7 +202,7 @@ impl TcpStream { /// # Ok(()) }) } /// ``` pub async fn peek(&self, buf: &mut [u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.peek(buf))).await + self.watcher.peek(buf).await } /// Gets the value of the `TCP_NODELAY` option on this socket. @@ -311,7 +305,7 @@ impl Read for &TcpStream { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - self.watcher.poll_read_with(cx, |mut inner| inner.read(buf)) + Pin::new(&mut &*self.watcher).poll_read(cx, buf) } } @@ -347,26 +341,23 @@ impl Write for &TcpStream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - self.watcher - .poll_write_with(cx, |mut inner| inner.write(buf)) + Pin::new(&mut &*self.watcher).poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.watcher.poll_write_with(cx, |mut inner| inner.flush()) + Pin::new(&mut &*self.watcher).poll_flush(cx) } - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - self.shutdown(std::net::Shutdown::Write)?; - Poll::Ready(Ok(())) + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut &*self.watcher).poll_close(cx) } } impl From for TcpStream { /// Converts a `std::net::TcpStream` into its asynchronous equivalent. fn from(stream: std::net::TcpStream) -> TcpStream { - let mio_stream = mio::net::TcpStream::from_stream(stream).unwrap(); TcpStream { - watcher: Watcher::new(mio_stream), + watcher: Arc::new(Async::new(stream).expect("TcpStream is known to be good")), } } } @@ -388,29 +379,37 @@ cfg_unix! { impl IntoRawFd for TcpStream { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + // TODO(stjepang): This does not mean `RawFd` is now the sole owner of the file + // descriptor because it's possible that there are other clones of this `TcpStream` + // using it at the same time. We should probably document that behavior. + self.as_raw_fd() } } } cfg_windows! { - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - // - // impl AsRawSocket for TcpStream { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for TcpStream { - // unsafe fn from_raw_socket(handle: RawSocket) -> TcpStream { - // net::TcpStream::from_raw_socket(handle).try_into().unwrap() - // } - // } - // - // impl IntoRawSocket for TcpListener { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } + use crate::os::windows::io::{ + RawSocket, AsRawSocket, FromRawSocket, IntoRawSocket + }; + + impl AsRawSocket for TcpStream { + fn as_raw_socket(&self) -> RawSocket { + self.watcher.get_ref().as_raw_socket() + } + } + + impl FromRawSocket for TcpStream { + unsafe fn from_raw_socket(handle: RawSocket) -> TcpStream { + std::net::TcpStream::from_raw_socket(handle).into() + } + } + + impl IntoRawSocket for TcpStream { + fn into_raw_socket(self) -> RawSocket { + // TODO(stjepang): This does not mean `RawFd` is now the sole owner of the file + // descriptor because it's possible that there are other clones of this `TcpStream` + // using it at the same time. We should probably document that behavior. + self.as_raw_socket() + } + } } diff --git a/src/net/udp/mod.rs b/src/net/udp/mod.rs index 1b6d0d7..30cceb7 100644 --- a/src/net/udp/mod.rs +++ b/src/net/udp/mod.rs @@ -2,8 +2,8 @@ use std::io; use std::net::SocketAddr; use std::net::{Ipv4Addr, Ipv6Addr}; -use crate::future; -use crate::net::driver::Watcher; +use smol::Async; + use crate::net::ToSocketAddrs; use crate::utils::Context as _; @@ -45,7 +45,7 @@ use crate::utils::Context as _; /// ``` #[derive(Debug)] pub struct UdpSocket { - watcher: Watcher, + watcher: Async, } impl UdpSocket { @@ -69,16 +69,12 @@ impl UdpSocket { /// ``` pub async fn bind(addrs: A) -> io::Result { let mut last_err = None; - let addrs = addrs - .to_socket_addrs() - .await?; + let addrs = addrs.to_socket_addrs().await?; for addr in addrs { - match mio::net::UdpSocket::bind(&addr) { - Ok(mio_socket) => { - return Ok(UdpSocket { - watcher: Watcher::new(mio_socket), - }); + match Async::::bind(&addr) { + Ok(socket) => { + return Ok(UdpSocket { watcher: socket }); } Err(err) => last_err = Some(err), } @@ -102,7 +98,7 @@ impl UdpSocket { /// ```no_run /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { /// # - /// use async_std::net::UdpSocket; + /// use async_std::net::UdpSocket; /// /// let socket = UdpSocket::bind("127.0.0.1:0").await?; /// let addr = socket.local_addr()?; @@ -153,12 +149,10 @@ impl UdpSocket { } }; - future::poll_fn(|cx| { - self.watcher - .poll_write_with(cx, |inner| inner.send_to(buf, &addr)) - }) - .await - .context(|| format!("could not send packet to {}", addr)) + self.watcher + .send_to(buf, addr) + .await + .context(|| format!("could not send packet to {}", addr)) } /// Receives data from the socket. @@ -181,22 +175,7 @@ impl UdpSocket { /// # Ok(()) }) } /// ``` pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { - future::poll_fn(|cx| { - self.watcher - .poll_read_with(cx, |inner| inner.recv_from(buf)) - }) - .await - .context(|| { - use std::fmt::Write; - - let mut error = String::from("could not receive data on "); - if let Ok(addr) = self.local_addr() { - let _ = write!(&mut error, "{}", addr); - } else { - error.push_str("socket"); - } - error - }) + self.watcher.recv_from(buf).await } /// Connects the UDP socket to a remote address. @@ -244,9 +223,12 @@ impl UdpSocket { })) } - /// Sends data on the socket to the given address. + /// Sends data on the socket to the remote address to which it is connected. /// - /// On success, returns the number of bytes written. + /// The [`connect`] method will connect this socket to a remote address. + /// This method will fail if the socket is not connected. + /// + /// [`connect`]: #method.connect /// /// # Examples /// @@ -255,35 +237,16 @@ impl UdpSocket { /// # /// use async_std::net::UdpSocket; /// - /// const THE_MERCHANT_OF_VENICE: &[u8] = b" - /// If you prick us, do we not bleed? - /// If you tickle us, do we not laugh? - /// If you poison us, do we not die? - /// And if you wrong us, shall we not revenge? - /// "; - /// - /// let socket = UdpSocket::bind("127.0.0.1:0").await?; + /// let socket = UdpSocket::bind("127.0.0.1:34254").await?; + /// socket.connect("127.0.0.1:8080").await?; + /// let bytes = socket.send(b"Hi there!").await?; /// - /// let addr = "127.0.0.1:7878"; - /// let sent = socket.send_to(THE_MERCHANT_OF_VENICE, &addr).await?; - /// println!("Sent {} bytes to {}", sent, addr); + /// println!("Sent {} bytes", bytes); /// # /// # Ok(()) }) } /// ``` pub async fn send(&self, buf: &[u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_write_with(cx, |inner| inner.send(buf))) - .await - .context(|| { - use std::fmt::Write; - - let mut error = String::from("could not send data on "); - if let Ok(addr) = self.local_addr() { - let _ = write!(&mut error, "{}", addr); - } else { - error.push_str("socket"); - } - error - }) + self.watcher.send(buf).await } /// Receives data from the socket. @@ -307,19 +270,7 @@ impl UdpSocket { /// # Ok(()) }) } /// ``` pub async fn recv(&self, buf: &mut [u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.recv(buf))) - .await - .context(|| { - use std::fmt::Write; - - let mut error = String::from("could not receive data on "); - if let Ok(addr) = self.local_addr() { - let _ = write!(&mut error, "{}", addr); - } else { - error.push_str("socket"); - } - error - }) + self.watcher.recv(buf).await } /// Gets the value of the `SO_BROADCAST` option for this socket. @@ -502,9 +453,8 @@ impl UdpSocket { impl From for UdpSocket { /// Converts a `std::net::UdpSocket` into its asynchronous equivalent. fn from(socket: std::net::UdpSocket) -> UdpSocket { - let mio_socket = mio::net::UdpSocket::from_socket(socket).unwrap(); UdpSocket { - watcher: Watcher::new(mio_socket), + watcher: Async::new(socket).expect("UdpSocket is known to be good"), } } } @@ -526,29 +476,31 @@ cfg_unix! { impl IntoRawFd for UdpSocket { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + self.watcher.into_raw_fd() } } } cfg_windows! { - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - // - // impl AsRawSocket for UdpSocket { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for UdpSocket { - // unsafe fn from_raw_socket(handle: RawSocket) -> UdpSocket { - // net::UdpSocket::from_raw_socket(handle).into() - // } - // } - // - // impl IntoRawSocket for UdpSocket { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } + use crate::os::windows::io::{ + RawSocket, AsRawSocket, IntoRawSocket, FromRawSocket + }; + + impl AsRawSocket for UdpSocket { + fn as_raw_socket(&self) -> RawSocket { + self.watcher.get_ref().as_raw_socket() + } + } + + impl FromRawSocket for UdpSocket { + unsafe fn from_raw_socket(handle: RawSocket) -> UdpSocket { + std::net::UdpSocket::from_raw_socket(handle).into() + } + } + + impl IntoRawSocket for UdpSocket { + fn into_raw_socket(self) -> RawSocket { + self.watcher.into_raw_socket() + } + } } diff --git a/src/option/from_stream.rs b/src/option/from_stream.rs index dc6d841..de929ca 100644 --- a/src/option/from_stream.rs +++ b/src/option/from_stream.rs @@ -2,6 +2,7 @@ use std::pin::Pin; use crate::prelude::*; use crate::stream::{FromStream, IntoStream}; +use std::convert::identity; impl FromStream> for Option where @@ -24,18 +25,15 @@ where .take_while(|elem| { elem.is_some() || { found_none = true; + // Stop processing the stream on `None` false } }) - .map(Option::unwrap) + .filter_map(identity) .collect() .await; - if found_none { - None - } else { - Some(out) - } + if found_none { None } else { Some(out) } }) } } diff --git a/src/option/product.rs b/src/option/product.rs index ff61eaa..b446c1f 100644 --- a/src/option/product.rs +++ b/src/option/product.rs @@ -2,6 +2,7 @@ use std::pin::Pin; use crate::prelude::*; use crate::stream::{Product, Stream}; +use std::convert::identity; impl Product> for Option where @@ -48,18 +49,15 @@ where .take_while(|elem| { elem.is_some() || { found_none = true; + // Stop processing the stream on `None` false } }) - .map(Option::unwrap), + .filter_map(identity), ) .await; - if found_none { - None - } else { - Some(out) - } + if found_none { None } else { Some(out) } }) } } diff --git a/src/option/sum.rs b/src/option/sum.rs index cd92f78..de404f4 100644 --- a/src/option/sum.rs +++ b/src/option/sum.rs @@ -2,6 +2,7 @@ use std::pin::Pin; use crate::prelude::*; use crate::stream::{Stream, Sum}; +use std::convert::identity; impl Sum> for Option where @@ -43,18 +44,15 @@ where .take_while(|elem| { elem.is_some() || { found_none = true; + // Stop processing the stream on `None` false } }) - .map(Option::unwrap), + .filter_map(identity), ) .await; - if found_none { - None - } else { - Some(out) - } + if found_none { None } else { Some(out) } }) } } diff --git a/src/os/unix/net/datagram.rs b/src/os/unix/net/datagram.rs index fc426b7..6a30b02 100644 --- a/src/os/unix/net/datagram.rs +++ b/src/os/unix/net/datagram.rs @@ -2,16 +2,14 @@ use std::fmt; use std::net::Shutdown; +use std::os::unix::net::UnixDatagram as StdUnixDatagram; -use mio_uds; +use smol::Async; use super::SocketAddr; -use crate::future; use crate::io; -use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::Path; -use crate::task::spawn_blocking; /// A Unix datagram socket. /// @@ -42,13 +40,13 @@ use crate::task::spawn_blocking; /// # Ok(()) }) } /// ``` pub struct UnixDatagram { - watcher: Watcher, + watcher: Async, } impl UnixDatagram { - fn new(socket: mio_uds::UnixDatagram) -> UnixDatagram { + fn new(socket: StdUnixDatagram) -> UnixDatagram { UnixDatagram { - watcher: Watcher::new(socket), + watcher: Async::new(socket).expect("UnixDatagram is known to be good"), } } @@ -67,8 +65,8 @@ impl UnixDatagram { /// ``` pub async fn bind>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let socket = spawn_blocking(move || mio_uds::UnixDatagram::bind(path)).await?; - Ok(UnixDatagram::new(socket)) + let socket = Async::::bind(path)?; + Ok(UnixDatagram { watcher: socket }) } /// Creates a Unix datagram which is not bound to any address. @@ -85,7 +83,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub fn unbound() -> io::Result { - let socket = mio_uds::UnixDatagram::unbound()?; + let socket = StdUnixDatagram::unbound()?; Ok(UnixDatagram::new(socket)) } @@ -105,7 +103,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub fn pair() -> io::Result<(UnixDatagram, UnixDatagram)> { - let (a, b) = mio_uds::UnixDatagram::pair()?; + let (a, b) = StdUnixDatagram::pair()?; let a = UnixDatagram::new(a); let b = UnixDatagram::new(b); Ok((a, b)) @@ -197,11 +195,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { - future::poll_fn(|cx| { - self.watcher - .poll_read_with(cx, |inner| inner.recv_from(buf)) - }) - .await + self.watcher.recv_from(buf).await } /// Receives data from the socket. @@ -222,7 +216,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub async fn recv(&self, buf: &mut [u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.recv(buf))).await + self.watcher.recv(buf).await } /// Sends data on the socket to the specified address. @@ -242,11 +236,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub async fn send_to>(&self, buf: &[u8], path: P) -> io::Result { - future::poll_fn(|cx| { - self.watcher - .poll_write_with(cx, |inner| inner.send_to(buf, path.as_ref())) - }) - .await + self.watcher.send_to(buf, path.as_ref()).await } /// Sends data on the socket to the socket's peer. @@ -267,7 +257,7 @@ impl UnixDatagram { /// # Ok(()) }) } /// ``` pub async fn send(&self, buf: &[u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_write_with(cx, |inner| inner.send(buf))).await + self.watcher.send(buf).await } /// Shut down the read, write, or both halves of this connection. @@ -312,31 +302,31 @@ impl fmt::Debug for UnixDatagram { } } -impl From for UnixDatagram { +impl From for UnixDatagram { /// Converts a `std::os::unix::net::UnixDatagram` into its asynchronous equivalent. - fn from(datagram: std::os::unix::net::UnixDatagram) -> UnixDatagram { - let mio_datagram = mio_uds::UnixDatagram::from_datagram(datagram).unwrap(); + fn from(datagram: StdUnixDatagram) -> UnixDatagram { UnixDatagram { - watcher: Watcher::new(mio_datagram), + watcher: Async::new(datagram).expect("UnixDatagram is known to be good"), } } } impl AsRawFd for UnixDatagram { fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() + self.watcher.as_raw_fd() } } impl FromRawFd for UnixDatagram { unsafe fn from_raw_fd(fd: RawFd) -> UnixDatagram { - let datagram = std::os::unix::net::UnixDatagram::from_raw_fd(fd); - datagram.into() + let raw = StdUnixDatagram::from_raw_fd(fd); + let datagram = Async::::new(raw).expect("invalid file descriptor"); + UnixDatagram { watcher: datagram } } } impl IntoRawFd for UnixDatagram { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + self.watcher.into_raw_fd() } } diff --git a/src/os/unix/net/listener.rs b/src/os/unix/net/listener.rs index 675ef48..ac03307 100644 --- a/src/os/unix/net/listener.rs +++ b/src/os/unix/net/listener.rs @@ -1,20 +1,20 @@ //! Unix-specific networking extensions. use std::fmt; -use std::pin::Pin; use std::future::Future; +use std::os::unix::net::UnixListener as StdUnixListener; +use std::pin::Pin; -use mio_uds; +use smol::Async; use super::SocketAddr; use super::UnixStream; -use crate::future; use crate::io; -use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::Path; use crate::stream::Stream; -use crate::task::{spawn_blocking, Context, Poll}; +use crate::sync::Arc; +use crate::task::{Context, Poll}; /// A Unix domain socket server, listening for connections. /// @@ -50,7 +50,7 @@ use crate::task::{spawn_blocking, Context, Poll}; /// # Ok(()) }) } /// ``` pub struct UnixListener { - watcher: Watcher, + watcher: Async, } impl UnixListener { @@ -69,11 +69,9 @@ impl UnixListener { /// ``` pub async fn bind>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let listener = spawn_blocking(move || mio_uds::UnixListener::bind(path)).await?; + let listener = Async::::bind(path)?; - Ok(UnixListener { - watcher: Watcher::new(listener), - }) + Ok(UnixListener { watcher: listener }) } /// Accepts a new incoming connection to this listener. @@ -93,29 +91,9 @@ impl UnixListener { /// # Ok(()) }) } /// ``` pub async fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { - future::poll_fn(|cx| { - let res = futures_core::ready!(self.watcher.poll_read_with(cx, |inner| { - match inner.accept_std() { - // Converting to `WouldBlock` so that the watcher will - // add the waker of this task to a list of readers. - Ok(None) => Err(io::ErrorKind::WouldBlock.into()), - res => res, - } - })); - - match res? { - Some((io, addr)) => { - let mio_stream = mio_uds::UnixStream::from_stream(io)?; - let stream = UnixStream { - watcher: Watcher::new(mio_stream), - }; - Poll::Ready(Ok((stream, addr))) - } - // This should never happen since `None` is converted to `WouldBlock` - None => unreachable!(), - } - }) - .await + let (stream, addr) = self.watcher.accept().await?; + + Ok((UnixStream { watcher: Arc::new(stream) }, addr)) } /// Returns a stream of incoming connections. @@ -206,19 +184,18 @@ impl Stream for Incoming<'_> { } } -impl From for UnixListener { +impl From for UnixListener { /// Converts a `std::os::unix::net::UnixListener` into its asynchronous equivalent. - fn from(listener: std::os::unix::net::UnixListener) -> UnixListener { - let mio_listener = mio_uds::UnixListener::from_listener(listener).unwrap(); + fn from(listener: StdUnixListener) -> UnixListener { UnixListener { - watcher: Watcher::new(mio_listener), + watcher: Async::new(listener).expect("UnixListener is known to be good"), } } } impl AsRawFd for UnixListener { fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() + self.watcher.as_raw_fd() } } @@ -231,6 +208,6 @@ impl FromRawFd for UnixListener { impl IntoRawFd for UnixListener { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + self.watcher.into_raw_fd() } } diff --git a/src/os/unix/net/stream.rs b/src/os/unix/net/stream.rs index 647edc9..b1ba5bc 100644 --- a/src/os/unix/net/stream.rs +++ b/src/os/unix/net/stream.rs @@ -1,18 +1,18 @@ //! Unix-specific networking extensions. use std::fmt; -use std::io::{Read as _, Write as _}; use std::net::Shutdown; +use std::os::unix::net::UnixStream as StdUnixStream; use std::pin::Pin; -use mio_uds; +use smol::Async; use super::SocketAddr; use crate::io::{self, Read, Write}; -use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::Path; -use crate::task::{spawn_blocking, Context, Poll}; +use crate::sync::Arc; +use crate::task::{Context, Poll}; /// A Unix stream socket. /// @@ -37,8 +37,9 @@ use crate::task::{spawn_blocking, Context, Poll}; /// # /// # Ok(()) }) } /// ``` +#[derive(Clone)] pub struct UnixStream { - pub(super) watcher: Watcher, + pub(super) watcher: Arc>, } impl UnixStream { @@ -57,15 +58,9 @@ impl UnixStream { /// ``` pub async fn connect>(path: P) -> io::Result { let path = path.as_ref().to_owned(); + let stream = Arc::new(Async::::connect(path).await?); - spawn_blocking(move || { - let std_stream = std::os::unix::net::UnixStream::connect(path)?; - let mio_stream = mio_uds::UnixStream::from_stream(std_stream)?; - Ok(UnixStream { - watcher: Watcher::new(mio_stream), - }) - }) - .await + Ok(UnixStream { watcher: stream }) } /// Creates an unnamed pair of connected sockets. @@ -84,12 +79,12 @@ impl UnixStream { /// # Ok(()) }) } /// ``` pub fn pair() -> io::Result<(UnixStream, UnixStream)> { - let (a, b) = mio_uds::UnixStream::pair()?; + let (a, b) = Async::::pair()?; let a = UnixStream { - watcher: Watcher::new(a), + watcher: Arc::new(a), }; let b = UnixStream { - watcher: Watcher::new(b), + watcher: Arc::new(b), }; Ok((a, b)) } @@ -169,7 +164,7 @@ impl Read for &UnixStream { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - self.watcher.poll_read_with(cx, |mut inner| inner.read(buf)) + Pin::new(&mut &*self.watcher).poll_read(cx, buf) } } @@ -197,16 +192,15 @@ impl Write for &UnixStream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - self.watcher - .poll_write_with(cx, |mut inner| inner.write(buf)) + Pin::new(&mut &*self.watcher).poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.watcher.poll_write_with(cx, |mut inner| inner.flush()) + Pin::new(&mut &*self.watcher).poll_flush(cx) } - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut &*self.watcher).poll_close(cx) } } @@ -227,19 +221,17 @@ impl fmt::Debug for UnixStream { } } -impl From for UnixStream { +impl From for UnixStream { /// Converts a `std::os::unix::net::UnixStream` into its asynchronous equivalent. - fn from(stream: std::os::unix::net::UnixStream) -> UnixStream { - let mio_stream = mio_uds::UnixStream::from_stream(stream).unwrap(); - UnixStream { - watcher: Watcher::new(mio_stream), - } + fn from(stream: StdUnixStream) -> UnixStream { + let stream = Async::new(stream).expect("UnixStream is known to be good"); + UnixStream { watcher: Arc::new(stream) } } } impl AsRawFd for UnixStream { fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() + self.watcher.as_raw_fd() } } @@ -252,6 +244,6 @@ impl FromRawFd for UnixStream { impl IntoRawFd for UnixStream { fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() + self.as_raw_fd() } } diff --git a/src/os/windows/io.rs b/src/os/windows/io.rs index e83d557..30d37a0 100644 --- a/src/os/windows/io.rs +++ b/src/os/windows/io.rs @@ -2,7 +2,8 @@ cfg_not_docs! { pub use std::os::windows::io::{ - AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle, RawSocket, + AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle, + AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket, }; } @@ -45,4 +46,33 @@ cfg_docs! { /// it once it's no longer needed. fn into_raw_handle(self) -> RawHandle; } + + /// Creates I/O objects from raw sockets. + pub trait FromRawSocket { + /// Creates a new I/O object from the given raw socket. + /// + /// This function will consume ownership of the socket provided and it will be closed when the returned object goes out of scope. + /// + /// This function is also unsafe as the primitives currently returned have the contract that they are the sole owner of the + /// file descriptor they are wrapping. Usage of this function could accidentally allow violating this contract which can cause + /// memory unsafety in code that relies on it being true. + unsafe fn from_raw_socket(sock: RawSocket) -> Self; + } + + /// Extracts raw sockets. + pub trait AsRawSocket { + /// Extracts the underlying raw socket from this object. + fn as_raw_socket(&self) -> RawSocket; + } + + /// A trait to express the ability to consume an object and acquire ownership of + /// its raw `SOCKET`. + pub trait IntoRawSocket { + /// Consumes this object, returning the raw underlying socket. + /// + /// This function **transfers ownership** of the underlying socket to the + /// caller. Callers are then the unique owners of the socket and must close + /// it once it's no longer needed. + fn into_raw_socket(self) -> RawSocket; + } } diff --git a/src/path/path.rs b/src/path/path.rs index dfe9426..185bfaf 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -4,9 +4,9 @@ use std::ffi::{OsStr, OsString}; use std::rc::Rc; use std::sync::Arc; -use crate::fs; -use crate::io; use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; +#[cfg(not(target_os = "unknown"))] +use crate::{fs, io}; /// A slice of a path. /// @@ -584,6 +584,7 @@ impl Path { /// # /// # Ok(()) }) } /// ``` + #[cfg(not(target_os = "unknown"))] pub async fn metadata(&self) -> io::Result { fs::metadata(self).await } @@ -607,6 +608,7 @@ impl Path { /// # /// # Ok(()) }) } /// ``` + #[cfg(not(target_os = "unknown"))] pub async fn symlink_metadata(&self) -> io::Result { fs::symlink_metadata(self).await } @@ -632,6 +634,7 @@ impl Path { /// # /// # Ok(()) }) } /// ``` + #[cfg(not(target_os = "unknown"))] pub async fn canonicalize(&self) -> io::Result { fs::canonicalize(self).await } @@ -654,6 +657,7 @@ impl Path { /// # /// # Ok(()) }) } /// ``` + #[cfg(not(target_os = "unknown"))] pub async fn read_link(&self) -> io::Result { fs::read_link(self).await } @@ -688,6 +692,7 @@ impl Path { /// # /// # Ok(()) }) } /// ``` + #[cfg(not(target_os = "unknown"))] pub async fn read_dir(&self) -> io::Result { fs::read_dir(self).await } @@ -717,6 +722,7 @@ impl Path { /// check errors, call [fs::metadata]. /// /// [fs::metadata]: ../fs/fn.metadata.html + #[cfg(not(target_os = "unknown"))] pub async fn exists(&self) -> bool { fs::metadata(self).await.is_ok() } @@ -749,6 +755,7 @@ impl Path { /// /// [fs::metadata]: ../fs/fn.metadata.html /// [fs::Metadata::is_file]: ../fs/struct.Metadata.html#method.is_file + #[cfg(not(target_os = "unknown"))] pub async fn is_file(&self) -> bool { fs::metadata(self) .await @@ -785,6 +792,7 @@ impl Path { /// /// [fs::metadata]: ../fs/fn.metadata.html /// [fs::Metadata::is_dir]: ../fs/struct.Metadata.html#method.is_dir + #[cfg(not(target_os = "unknown"))] pub async fn is_dir(&self) -> bool { fs::metadata(self) .await diff --git a/src/result/from_stream.rs b/src/result/from_stream.rs index a8490d6..8a8e0ea 100644 --- a/src/result/from_stream.rs +++ b/src/result/from_stream.rs @@ -10,6 +10,23 @@ where /// Takes each element in the stream: if it is an `Err`, no further /// elements are taken, and the `Err` is returned. Should no `Err` /// occur, a container with the values of each `Result` is returned. + /// + /// # Examples + /// + /// ``` + /// # fn main() { async_std::task::block_on(async { + /// # + /// use async_std::prelude::*; + /// use async_std::stream; + /// + /// let v = stream::from_iter(vec![1, 2]); + /// let res: Result, &'static str> = v.map(|x: u32| + /// x.checked_add(1).ok_or("Overflow!") + /// ).collect().await; + /// assert_eq!(res, Ok(vec![2, 3])); + /// # + /// # }) } + /// ``` #[inline] fn from_stream<'a, S: IntoStream> + 'a>( stream: S, @@ -17,26 +34,34 @@ where let stream = stream.into_stream(); Box::pin(async move { - // Using `scan` here because it is able to stop the stream early + // Using `take_while` here because it is able to stop the stream early // if a failure occurs + let mut is_error = false; let mut found_error = None; let out: V = stream - .scan((), |_, elem| { - match elem { - Ok(elem) => Some(elem), - Err(err) => { - found_error = Some(err); - // Stop processing the stream on error - None - } + .take_while(|elem| { + // Stop processing the stream on `Err` + !is_error + && (elem.is_ok() || { + is_error = true; + // Capture first `Err` + true + }) + }) + .filter_map(|elem| match elem { + Ok(value) => Some(value), + Err(err) => { + found_error = Some(err); + None } }) .collect() .await; - match found_error { - Some(err) => Err(err), - None => Ok(out), + if is_error { + Err(found_error.unwrap()) + } else { + Ok(out) } }) } diff --git a/src/result/product.rs b/src/result/product.rs index ec9d94a..45782ff 100644 --- a/src/result/product.rs +++ b/src/result/product.rs @@ -1,7 +1,7 @@ use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Stream, Product}; +use crate::stream::{Product, Stream}; impl Product> for Result where @@ -36,26 +36,39 @@ where ``` "#] fn product<'a, S>(stream: S) -> Pin> + 'a>> - where S: Stream> + 'a + where + S: Stream> + 'a, { Box::pin(async move { - // Using `scan` here because it is able to stop the stream early + // Using `take_while` here because it is able to stop the stream early // if a failure occurs + let mut is_error = false; let mut found_error = None; - let out = >::product(stream - .scan((), |_, elem| { - match elem { - Ok(elem) => Some(elem), + let out = >::product( + stream + .take_while(|elem| { + // Stop processing the stream on `Err` + !is_error + && (elem.is_ok() || { + is_error = true; + // Capture first `Err` + true + }) + }) + .filter_map(|elem| match elem { + Ok(value) => Some(value), Err(err) => { found_error = Some(err); - // Stop processing the stream on error None } - } - })).await; - match found_error { - Some(err) => Err(err), - None => Ok(out) + }), + ) + .await; + + if is_error { + Err(found_error.unwrap()) + } else { + Ok(out) } }) } diff --git a/src/result/sum.rs b/src/result/sum.rs index ccc4240..b6d84a0 100644 --- a/src/result/sum.rs +++ b/src/result/sum.rs @@ -36,26 +36,39 @@ where ``` "#] fn sum<'a, S>(stream: S) -> Pin> + 'a>> - where S: Stream> + 'a + where + S: Stream> + 'a, { Box::pin(async move { - // Using `scan` here because it is able to stop the stream early + // Using `take_while` here because it is able to stop the stream early // if a failure occurs + let mut is_error = false; let mut found_error = None; - let out = >::sum(stream - .scan((), |_, elem| { - match elem { - Ok(elem) => Some(elem), + let out = >::sum( + stream + .take_while(|elem| { + // Stop processing the stream on `Err` + !is_error + && (elem.is_ok() || { + is_error = true; + // Capture first `Err` + true + }) + }) + .filter_map(|elem| match elem { + Ok(value) => Some(value), Err(err) => { found_error = Some(err); - // Stop processing the stream on error None } - } - })).await; - match found_error { - Some(err) => Err(err), - None => Ok(out) + }), + ) + .await; + + if is_error { + Err(found_error.unwrap()) + } else { + Ok(out) } }) } diff --git a/src/rt/mod.rs b/src/rt/mod.rs new file mode 100644 index 0000000..d8550aa --- /dev/null +++ b/src/rt/mod.rs @@ -0,0 +1,34 @@ +//! The runtime. + +use std::env; +use std::thread; + +use once_cell::sync::Lazy; + +use crate::future; + +/// Dummy runtime struct. +pub struct Runtime {} + +/// The global runtime. +pub static RUNTIME: Lazy = Lazy::new(|| { + // Create an executor thread pool. + + let thread_count = env::var("ASYNC_STD_THREAD_COUNT") + .map(|env| { + env.parse() + .expect("ASYNC_STD_THREAD_COUNT must be a number") + }) + .unwrap_or_else(|_| num_cpus::get()) + .max(1); + + let thread_name = env::var("ASYNC_STD_THREAD_NAME").unwrap_or("async-std/runtime".to_string()); + + for _ in 0..thread_count { + thread::Builder::new() + .name(thread_name.clone()) + .spawn(|| crate::task::block_on(future::pending::<()>())) + .expect("cannot start a runtime thread"); + } + Runtime {} +}); diff --git a/src/stream/double_ended_stream/next_back.rs b/src/stream/double_ended_stream/next_back.rs index aa642d0..9fb52b7 100644 --- a/src/stream/double_ended_stream/next_back.rs +++ b/src/stream/double_ended_stream/next_back.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::future::Future; +use core::pin::Pin; +use core::future::Future; use crate::stream::DoubleEndedStream; use crate::task::{Context, Poll}; diff --git a/src/stream/double_ended_stream/nth_back.rs b/src/stream/double_ended_stream/nth_back.rs index e32a28f..2701e9b 100644 --- a/src/stream/double_ended_stream/nth_back.rs +++ b/src/stream/double_ended_stream/nth_back.rs @@ -1,6 +1,6 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; use crate::stream::DoubleEndedStream; diff --git a/src/stream/double_ended_stream/rfind.rs b/src/stream/double_ended_stream/rfind.rs index 9472693..3666551 100644 --- a/src/stream/double_ended_stream/rfind.rs +++ b/src/stream/double_ended_stream/rfind.rs @@ -1,6 +1,6 @@ -use std::task::{Context, Poll}; -use std::future::Future; -use std::pin::Pin; +use core::task::{Context, Poll}; +use core::future::Future; +use core::pin::Pin; use crate::stream::DoubleEndedStream; diff --git a/src/stream/double_ended_stream/rfold.rs b/src/stream/double_ended_stream/rfold.rs index 9002f8d..9bc1878 100644 --- a/src/stream/double_ended_stream/rfold.rs +++ b/src/stream/double_ended_stream/rfold.rs @@ -1,6 +1,6 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; use pin_project_lite::pin_project; diff --git a/src/stream/double_ended_stream/try_rfold.rs b/src/stream/double_ended_stream/try_rfold.rs index 9e6295a..d67b6ec 100644 --- a/src/stream/double_ended_stream/try_rfold.rs +++ b/src/stream/double_ended_stream/try_rfold.rs @@ -1,5 +1,5 @@ use crate::future::Future; -use std::pin::Pin; +use core::pin::Pin; use crate::task::{Context, Poll}; use pin_project_lite::pin_project; diff --git a/src/stream/empty.rs b/src/stream/empty.rs index 4909070..a11337a 100644 --- a/src/stream/empty.rs +++ b/src/stream/empty.rs @@ -1,5 +1,5 @@ -use std::marker::PhantomData; -use std::pin::Pin; +use core::marker::PhantomData; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/extend.rs b/src/stream/extend.rs index c48fe1e..702cbca 100644 --- a/src/stream/extend.rs +++ b/src/stream/extend.rs @@ -1,6 +1,6 @@ -use std::pin::Pin; +use core::pin::Pin; +use core::future::Future; -use crate::prelude::*; use crate::stream::IntoStream; /// Extends a collection with the contents of a stream. diff --git a/src/stream/from_fn.rs b/src/stream/from_fn.rs index 8067176..d373645 100644 --- a/src/stream/from_fn.rs +++ b/src/stream/from_fn.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/from_iter.rs b/src/stream/from_iter.rs index 705d150..ea2ed8e 100644 --- a/src/stream/from_iter.rs +++ b/src/stream/from_iter.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/from_stream.rs b/src/stream/from_stream.rs index 67b9b3d..12d89e8 100644 --- a/src/stream/from_stream.rs +++ b/src/stream/from_stream.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use crate::stream::IntoStream; diff --git a/src/stream/interval.rs b/src/stream/interval.rs index f8a6ef6..4e5c92b 100644 --- a/src/stream/interval.rs +++ b/src/stream/interval.rs @@ -1,10 +1,10 @@ +use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; +use std::time::Duration; -use futures_timer::Delay; - -use crate::prelude::*; +use crate::stream::Stream; +use crate::utils::Timer; /// Creates a new stream that yields at a set interval. /// @@ -45,7 +45,7 @@ use crate::prelude::*; #[cfg_attr(feature = "docs", doc(cfg(unstable)))] pub fn interval(dur: Duration) -> Interval { Interval { - delay: Delay::new(dur), + delay: Timer::after(dur), interval: dur, } } @@ -60,7 +60,7 @@ pub fn interval(dur: Duration) -> Interval { #[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[derive(Debug)] pub struct Interval { - delay: Delay, + delay: Timer, interval: Duration, } @@ -71,125 +71,8 @@ impl Stream for Interval { if Pin::new(&mut self.delay).poll(cx).is_pending() { return Poll::Pending; } - let when = Instant::now(); - let next = next_interval(when, Instant::now(), self.interval); - self.delay.reset(next); + let interval = self.interval; + let _ = std::mem::replace(&mut self.delay, Timer::after(interval)); Poll::Ready(Some(())) } } - -/// Converts Duration object to raw nanoseconds if possible -/// -/// This is useful to divide intervals. -/// -/// While technically for large duration it's impossible to represent any -/// duration as nanoseconds, the largest duration we can represent is about -/// 427_000 years. Large enough for any interval we would use or calculate in -/// async-std. -fn duration_to_nanos(dur: Duration) -> Option { - dur.as_secs() - .checked_mul(1_000_000_000) - .and_then(|v| v.checked_add(u64::from(dur.subsec_nanos()))) -} - -fn next_interval(prev: Instant, now: Instant, interval: Duration) -> Instant { - let new = prev + interval; - if new > now { - return new; - } - - let spent_ns = duration_to_nanos(now.duration_since(prev)).expect("interval should be expired"); - let interval_ns = - duration_to_nanos(interval).expect("interval is less that 427 thousand years"); - let mult = spent_ns / interval_ns + 1; - assert!( - mult < (1 << 32), - "can't skip more than 4 billion intervals of {:?} \ - (trying to skip {})", - interval, - mult - ); - prev + interval * (mult as u32) -} - -#[cfg(test)] -mod test { - use super::next_interval; - use std::cmp::Ordering; - use std::time::{Duration, Instant}; - - struct Timeline(Instant); - - impl Timeline { - fn new() -> Timeline { - Timeline(Instant::now()) - } - fn at(&self, millis: u64) -> Instant { - self.0 + Duration::from_millis(millis) - } - fn at_ns(&self, sec: u64, nanos: u32) -> Instant { - self.0 + Duration::new(sec, nanos) - } - } - - fn dur(millis: u64) -> Duration { - Duration::from_millis(millis) - } - - // The math around Instant/Duration isn't 100% precise due to rounding - // errors, see #249 for more info - fn almost_eq(a: Instant, b: Instant) -> bool { - match a.cmp(&b) { - Ordering::Equal => true, - Ordering::Greater => a - b < Duration::from_millis(1), - Ordering::Less => b - a < Duration::from_millis(1), - } - } - - #[test] - fn norm_next() { - let tm = Timeline::new(); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(2), dur(10)), - tm.at(11) - )); - assert!(almost_eq( - next_interval(tm.at(7777), tm.at(7788), dur(100)), - tm.at(7877) - )); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(1000), dur(2100)), - tm.at(2101) - )); - } - - #[test] - fn fast_forward() { - let tm = Timeline::new(); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(1000), dur(10)), - tm.at(1001) - )); - assert!(almost_eq( - next_interval(tm.at(7777), tm.at(8888), dur(100)), - tm.at(8977) - )); - assert!(almost_eq( - next_interval(tm.at(1), tm.at(10000), dur(2100)), - tm.at(10501) - )); - } - - /// TODO: this test actually should be successful, but since we can't - /// multiply Duration on anything larger than u32 easily we decided - /// to allow it to fail for now - #[test] - #[should_panic(expected = "can't skip more than 4 billion intervals")] - fn large_skip() { - let tm = Timeline::new(); - assert_eq!( - next_interval(tm.at_ns(0, 1), tm.at_ns(25, 0), Duration::new(0, 2)), - tm.at_ns(25, 1) - ); - } -} diff --git a/src/stream/mod.rs b/src/stream/mod.rs index d8b96ec..0bfd4e8 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -325,6 +325,7 @@ cfg_unstable! { mod fused_stream; mod interval; mod into_stream; + mod pending; mod product; mod successors; mod sum; @@ -336,6 +337,7 @@ cfg_unstable! { pub use fused_stream::FusedStream; pub use interval::{interval, Interval}; pub use into_stream::IntoStream; + pub use pending::{pending, Pending}; pub use product::Product; pub use stream::Merge; pub use successors::{successors, Successors}; diff --git a/src/stream/once.rs b/src/stream/once.rs index 939722d..b86f181 100644 --- a/src/stream/once.rs +++ b/src/stream/once.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/pending.rs b/src/stream/pending.rs new file mode 100644 index 0000000..edb6be4 --- /dev/null +++ b/src/stream/pending.rs @@ -0,0 +1,68 @@ +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use crate::stream::{DoubleEndedStream, ExactSizeStream, FusedStream, Stream}; + +/// A stream that never returns any items. +/// +/// This stream is created by the [`pending`] function. See its +/// documentation for more. +/// +/// [`pending`]: fn.pending.html +#[derive(Debug)] +pub struct Pending { + _marker: PhantomData, +} + +/// Creates a stream that never returns any items. +/// +/// The returned stream will always return `Pending` when polled. +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use std::time::Duration; +/// +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let dur = Duration::from_millis(100); +/// let mut s = stream::pending::<()>().timeout(dur); +/// +/// let item = s.next().await; +/// +/// assert!(item.is_some()); +/// assert!(item.unwrap().is_err()); +/// +/// # +/// # }) +/// ``` +pub fn pending() -> Pending { + Pending { + _marker: PhantomData, + } +} + +impl Stream for Pending { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Pending + } +} + +impl DoubleEndedStream for Pending { + fn poll_next_back(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Pending + } +} + +impl FusedStream for Pending {} + +impl ExactSizeStream for Pending { + fn len(&self) -> usize { + 0 + } +} diff --git a/src/stream/product.rs b/src/stream/product.rs index 2f5bf4c..15497e8 100644 --- a/src/stream/product.rs +++ b/src/stream/product.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::future::Future; +use core::pin::Pin; +use core::future::Future; use crate::stream::Stream; diff --git a/src/stream/repeat.rs b/src/stream/repeat.rs index f3dfdbd..e73a2f8 100644 --- a/src/stream/repeat.rs +++ b/src/stream/repeat.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/repeat_with.rs b/src/stream/repeat_with.rs index e183a77..39984a3 100644 --- a/src/stream/repeat_with.rs +++ b/src/stream/repeat_with.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/all.rs b/src/stream/stream/all.rs index d2ac5ca..06f4d7f 100644 --- a/src/stream/stream/all.rs +++ b/src/stream/stream/all.rs @@ -1,6 +1,6 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/any.rs b/src/stream/stream/any.rs index 34168e6..15154c5 100644 --- a/src/stream/stream/any.rs +++ b/src/stream/stream/any.rs @@ -1,6 +1,6 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/chain.rs b/src/stream/stream/chain.rs index 909fc19..c034a53 100644 --- a/src/stream/stream/chain.rs +++ b/src/stream/stream/chain.rs @@ -1,9 +1,10 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; use super::fuse::Fuse; -use crate::prelude::*; +use crate::stream::stream::StreamExt; +use crate::stream::Stream; use crate::task::{Context, Poll}; pin_project! { diff --git a/src/stream/stream/cloned.rs b/src/stream/stream/cloned.rs index 4c77c5c..eac992d 100644 --- a/src/stream/stream/cloned.rs +++ b/src/stream/stream/cloned.rs @@ -1,7 +1,7 @@ use crate::stream::Stream; use crate::task::{Context, Poll}; use pin_project_lite::pin_project; -use std::pin::Pin; +use core::pin::Pin; pin_project! { /// A stream that clones the elements of an underlying stream. diff --git a/src/stream/stream/cmp.rs b/src/stream/stream/cmp.rs index 2be0c1a..9d2b0ec 100644 --- a/src/stream/stream/cmp.rs +++ b/src/stream/stream/cmp.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::fuse::Fuse; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/copied.rs b/src/stream/stream/copied.rs index 651c31b..19296f5 100644 --- a/src/stream/stream/copied.rs +++ b/src/stream/stream/copied.rs @@ -1,7 +1,7 @@ use crate::stream::Stream; use crate::task::{Context, Poll}; use pin_project_lite::pin_project; -use std::pin::Pin; +use core::pin::Pin; pin_project! { /// A stream that copies the elements of an underlying stream. diff --git a/src/stream/stream/count.rs b/src/stream/stream/count.rs index 1ca8aef..63e0449 100644 --- a/src/stream/stream/count.rs +++ b/src/stream/stream/count.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/cycle.rs b/src/stream/stream/cycle.rs index 5f8eaa2..ef46d1a 100644 --- a/src/stream/stream/cycle.rs +++ b/src/stream/stream/cycle.rs @@ -1,5 +1,5 @@ -use std::mem::ManuallyDrop; -use std::pin::Pin; +use core::mem::ManuallyDrop; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/delay.rs b/src/stream/stream/delay.rs index 5768792..0ba42b0 100644 --- a/src/stream/stream/delay.rs +++ b/src/stream/stream/delay.rs @@ -1,11 +1,12 @@ -use std::future::Future; -use std::pin::Pin; -use std::time::Duration; +use core::future::Future; +use core::pin::Pin; +use core::time::Duration; use pin_project_lite::pin_project; use crate::stream::Stream; use crate::task::{Context, Poll}; +use crate::utils::Timer; pin_project! { #[doc(hidden)] @@ -14,7 +15,7 @@ pin_project! { #[pin] stream: S, #[pin] - delay: futures_timer::Delay, + delay: Timer, delay_done: bool, } } @@ -23,7 +24,7 @@ impl Delay { pub(super) fn new(stream: S, dur: Duration) -> Self { Delay { stream, - delay: futures_timer::Delay::new(dur), + delay: Timer::after(dur), delay_done: false, } } diff --git a/src/stream/stream/enumerate.rs b/src/stream/stream/enumerate.rs index c4a37d6..093fefb 100644 --- a/src/stream/stream/enumerate.rs +++ b/src/stream/stream/enumerate.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/eq.rs b/src/stream/stream/eq.rs index 58ccc90..3d8307b 100644 --- a/src/stream/stream/eq.rs +++ b/src/stream/stream/eq.rs @@ -1,10 +1,10 @@ -use std::pin::Pin; -use std::future::Future; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::fuse::Fuse; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/filter.rs b/src/stream/stream/filter.rs index 00344b0..2dc7dd4 100644 --- a/src/stream/stream/filter.rs +++ b/src/stream/stream/filter.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/filter_map.rs b/src/stream/stream/filter_map.rs index 3cd1e47..e43e8f0 100644 --- a/src/stream/stream/filter_map.rs +++ b/src/stream/stream/filter_map.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::pin::Pin; +use core::task::{Context, Poll}; use pin_project_lite::pin_project; diff --git a/src/stream/stream/find.rs b/src/stream/stream/find.rs index 4a0749b..8652ac0 100644 --- a/src/stream/stream/find.rs +++ b/src/stream/stream/find.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/find_map.rs b/src/stream/stream/find_map.rs index c794943..f7e3c1e 100644 --- a/src/stream/stream/find_map.rs +++ b/src/stream/stream/find_map.rs @@ -1,6 +1,6 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; use crate::stream::Stream; diff --git a/src/stream/stream/flat_map.rs b/src/stream/stream/flat_map.rs index 97f5737..d0cc73d 100644 --- a/src/stream/stream/flat_map.rs +++ b/src/stream/stream/flat_map.rs @@ -1,9 +1,9 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; -use crate::prelude::*; use crate::stream::stream::map::Map; +use crate::stream::stream::StreamExt; use crate::stream::{IntoStream, Stream}; use crate::task::{Context, Poll}; @@ -41,7 +41,6 @@ where impl Stream for FlatMap where S: Stream, - S::Item: IntoStream, U: Stream, F: FnMut(S::Item) -> U, { diff --git a/src/stream/stream/flatten.rs b/src/stream/stream/flatten.rs index 5f8d000..7a767e1 100644 --- a/src/stream/stream/flatten.rs +++ b/src/stream/stream/flatten.rs @@ -1,5 +1,5 @@ -use std::fmt; -use std::pin::Pin; +use core::fmt; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/fold.rs b/src/stream/stream/fold.rs index a346eb6..3938a37 100644 --- a/src/stream/stream/fold.rs +++ b/src/stream/stream/fold.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/for_each.rs b/src/stream/stream/for_each.rs index dce5cda..dbada10 100644 --- a/src/stream/stream/for_each.rs +++ b/src/stream/stream/for_each.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::future::Future; +use core::pin::Pin; +use core::future::Future; use pin_project_lite::pin_project; diff --git a/src/stream/stream/fuse.rs b/src/stream/stream/fuse.rs index c7449c2..f3a0596 100644 --- a/src/stream/stream/fuse.rs +++ b/src/stream/stream/fuse.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/ge.rs b/src/stream/stream/ge.rs index 67b20be..1e1b70d 100644 --- a/src/stream/stream/ge.rs +++ b/src/stream/stream/ge.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::partial_cmp::PartialCmpFuture; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/gt.rs b/src/stream/stream/gt.rs index 1c12189..d58e7e9 100644 --- a/src/stream/stream/gt.rs +++ b/src/stream/stream/gt.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::partial_cmp::PartialCmpFuture; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/inspect.rs b/src/stream/stream/inspect.rs index bb39662..481810d 100644 --- a/src/stream/stream/inspect.rs +++ b/src/stream/stream/inspect.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/last.rs b/src/stream/stream/last.rs index d037efc..ebf1a48 100644 --- a/src/stream/stream/last.rs +++ b/src/stream/stream/last.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/le.rs b/src/stream/stream/le.rs index 7b86161..169f9ec 100644 --- a/src/stream/stream/le.rs +++ b/src/stream/stream/le.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::partial_cmp::PartialCmpFuture; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/lt.rs b/src/stream/stream/lt.rs index 100a003..1851b8e 100644 --- a/src/stream/stream/lt.rs +++ b/src/stream/stream/lt.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::partial_cmp::PartialCmpFuture; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/map.rs b/src/stream/stream/map.rs index 8e074a7..0eab3ce 100644 --- a/src/stream/stream/map.rs +++ b/src/stream/stream/map.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/max.rs b/src/stream/stream/max.rs index d8ff119..03fe635 100644 --- a/src/stream/stream/max.rs +++ b/src/stream/stream/max.rs @@ -1,7 +1,6 @@ -use std::cmp::{Ord, Ordering}; -use std::marker::PhantomData; -use std::pin::Pin; -use std::future::Future; +use core::cmp::{Ord, Ordering}; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; @@ -11,29 +10,23 @@ use crate::task::{Context, Poll}; pin_project! { #[doc(hidden)] #[allow(missing_debug_implementations)] - pub struct MaxFuture { + pub struct MaxFuture { #[pin] stream: S, - _compare: PhantomData, max: Option, } } -impl MaxFuture { +impl MaxFuture { pub(super) fn new(stream: S) -> Self { - Self { - stream, - _compare: PhantomData, - max: None, - } + Self { stream, max: None } } } -impl Future for MaxFuture +impl Future for MaxFuture where S: Stream, S::Item: Ord, - F: FnMut(&S::Item, &S::Item) -> Ordering, { type Output = Option; diff --git a/src/stream/stream/max_by.rs b/src/stream/stream/max_by.rs index 36b876b..8f98645 100644 --- a/src/stream/stream/max_by.rs +++ b/src/stream/stream/max_by.rs @@ -1,6 +1,6 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::pin::Pin; +use core::future::Future; use pin_project_lite::pin_project; diff --git a/src/stream/stream/max_by_key.rs b/src/stream/stream/max_by_key.rs index e421f94..8fa91ab 100644 --- a/src/stream/stream/max_by_key.rs +++ b/src/stream/stream/max_by_key.rs @@ -1,6 +1,6 @@ -use std::cmp::Ordering; -use std::future::Future; -use std::pin::Pin; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/merge.rs b/src/stream/stream/merge.rs index 84ac432..2320972 100644 --- a/src/stream/stream/merge.rs +++ b/src/stream/stream/merge.rs @@ -1,10 +1,11 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::pin::Pin; +use core::task::{Context, Poll}; use pin_project_lite::pin_project; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Fuse; +use crate::stream::Stream; use crate::utils; pin_project! { diff --git a/src/stream/stream/min.rs b/src/stream/stream/min.rs index 4ce52be..8430b94 100644 --- a/src/stream/stream/min.rs +++ b/src/stream/stream/min.rs @@ -1,7 +1,6 @@ -use std::cmp::{Ord, Ordering}; -use std::marker::PhantomData; -use std::pin::Pin; -use std::future::Future; +use core::cmp::{Ord, Ordering}; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; @@ -11,29 +10,23 @@ use crate::task::{Context, Poll}; pin_project! { #[doc(hidden)] #[allow(missing_debug_implementations)] - pub struct MinFuture { + pub struct MinFuture { #[pin] stream: S, - _compare: PhantomData, min: Option, } } -impl MinFuture { +impl MinFuture { pub(super) fn new(stream: S) -> Self { - Self { - stream, - _compare: PhantomData, - min: None, - } + Self { stream, min: None } } } -impl Future for MinFuture +impl Future for MinFuture where S: Stream, S::Item: Ord, - F: FnMut(&S::Item, &S::Item) -> Ordering, { type Output = Option; diff --git a/src/stream/stream/min_by.rs b/src/stream/stream/min_by.rs index e35719e..fe1d40e 100644 --- a/src/stream/stream/min_by.rs +++ b/src/stream/stream/min_by.rs @@ -1,6 +1,6 @@ -use std::cmp::Ordering; -use std::pin::Pin; -use std::future::Future; +use core::cmp::Ordering; +use core::pin::Pin; +use core::future::Future; use pin_project_lite::pin_project; diff --git a/src/stream/stream/min_by_key.rs b/src/stream/stream/min_by_key.rs index 07c3642..549b798 100644 --- a/src/stream/stream/min_by_key.rs +++ b/src/stream/stream/min_by_key.rs @@ -1,6 +1,6 @@ -use std::cmp::Ordering; -use std::future::Future; -use std::pin::Pin; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/mod.rs b/src/stream/stream/mod.rs index b98950b..4be0eb5 100644 --- a/src/stream/stream/mod.rs +++ b/src/stream/stream/mod.rs @@ -110,12 +110,12 @@ pub use take::Take; pub use take_while::TakeWhile; pub use zip::Zip; -use std::cmp::Ordering; +use core::cmp::Ordering; cfg_unstable! { - use std::future::Future; - use std::pin::Pin; - use std::time::Duration; + use core::future::Future; + use core::pin::Pin; + use core::time::Duration; use crate::stream::into_stream::IntoStream; use crate::stream::{FromStream, Product, Sum}; @@ -791,18 +791,22 @@ extension_trait! { # async_std::task::block_on(async { use async_std::prelude::*; - use async_std::stream::IntoStream; use async_std::stream; - let inner1 = stream::from_iter(vec![1,2,3]); - let inner2 = stream::from_iter(vec![4,5,6]); + let words = stream::from_iter(&["alpha", "beta", "gamma"]); - let s = stream::from_iter(vec![inner1, inner2]); + let merged: String = words + .flat_map(|s| stream::from_iter(s.chars())) + .collect().await; + assert_eq!(merged, "alphabetagamma"); - let v :Vec<_> = s.flat_map(|s| s.into_stream()).collect().await; - - assert_eq!(v, vec![1,2,3,4,5,6]); + let d3 = stream::from_iter(&[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); + let d1: Vec<_> = d3 + .flat_map(|item| stream::from_iter(item)) + .flat_map(|item| stream::from_iter(item)) + .collect().await; + assert_eq!(d1, [&1, &2, &3, &4, &5, &6, &7, &8]); # }); ``` "#] @@ -1007,7 +1011,7 @@ extension_trait! { # Examples - ```ignore + ``` # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; @@ -1024,12 +1028,12 @@ extension_trait! { # }) } ``` "#] - fn max( + fn max( self, - ) -> impl Future> [MaxFuture] + ) -> impl Future> [MaxFuture] where Self: Sized, - F: FnMut(&Self::Item, &Self::Item) -> Ordering, + Self::Item: Ord, { MaxFuture::new(self) } @@ -1040,7 +1044,7 @@ extension_trait! { # Examples - ```ignore + ``` # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; @@ -1057,12 +1061,12 @@ extension_trait! { # }) } ``` "#] - fn min( + fn min( self, - ) -> impl Future> [MinFuture] + ) -> impl Future> [MinFuture] where Self: Sized, - F: FnMut(&Self::Item, &Self::Item) -> Ordering, + Self::Item: Ord, { MinFuture::new(self) } @@ -1641,6 +1645,13 @@ extension_trait! { while let Some(v) = s.next().await { assert_eq!(v, Ok(1)); } + + // when timeout + let mut s = stream::pending::<()>().timeout(Duration::from_millis(10)); + match s.next().await { + Some(item) => assert!(item.is_err()), + None => panic!() + }; # # Ok(()) }) } ``` diff --git a/src/stream/stream/ne.rs b/src/stream/stream/ne.rs index ec11d1f..c51ab31 100644 --- a/src/stream/stream/ne.rs +++ b/src/stream/stream/ne.rs @@ -1,10 +1,10 @@ -use std::pin::Pin; -use std::future::Future; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::fuse::Fuse; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/next.rs b/src/stream/stream/next.rs index 23abb0b..7bd2083 100644 --- a/src/stream/stream/next.rs +++ b/src/stream/stream/next.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::future::Future; +use core::pin::Pin; +use core::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/nth.rs b/src/stream/stream/nth.rs index 267bd40..8cdabb6 100644 --- a/src/stream/stream/nth.rs +++ b/src/stream/stream/nth.rs @@ -1,6 +1,6 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::future::Future; use crate::stream::Stream; diff --git a/src/stream/stream/partial_cmp.rs b/src/stream/stream/partial_cmp.rs index 85587c9..928a03b 100644 --- a/src/stream/stream/partial_cmp.rs +++ b/src/stream/stream/partial_cmp.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; -use std::future::Future; -use std::pin::Pin; +use core::cmp::Ordering; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; use super::fuse::Fuse; -use crate::prelude::*; +use crate::stream::stream::StreamExt; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/partition.rs b/src/stream/stream/partition.rs index aaf58ac..69268ec 100644 --- a/src/stream/stream/partition.rs +++ b/src/stream/stream/partition.rs @@ -1,7 +1,7 @@ use pin_project_lite::pin_project; -use std::default::Default; -use std::future::Future; -use std::pin::Pin; +use core::default::Default; +use core::future::Future; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/position.rs b/src/stream/stream/position.rs index df60eaa..2811b6d 100644 --- a/src/stream/stream/position.rs +++ b/src/stream/stream/position.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/scan.rs b/src/stream/stream/scan.rs index 385edf8..d72b0dc 100644 --- a/src/stream/stream/scan.rs +++ b/src/stream/stream/scan.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/skip.rs b/src/stream/stream/skip.rs index bcff50d..52b137d 100644 --- a/src/stream/stream/skip.rs +++ b/src/stream/stream/skip.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::pin::Pin; +use core::task::{Context, Poll}; use pin_project_lite::pin_project; diff --git a/src/stream/stream/skip_while.rs b/src/stream/stream/skip_while.rs index 2334713..d139de4 100644 --- a/src/stream/stream/skip_while.rs +++ b/src/stream/stream/skip_while.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/step_by.rs b/src/stream/stream/step_by.rs index 2149cda..3dd3d62 100644 --- a/src/stream/stream/step_by.rs +++ b/src/stream/stream/step_by.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/take.rs b/src/stream/stream/take.rs index 8c85227..ffc3e99 100644 --- a/src/stream/stream/take.rs +++ b/src/stream/stream/take.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/take_while.rs b/src/stream/stream/take_while.rs index 2ba8490..60eb8c5 100644 --- a/src/stream/stream/take_while.rs +++ b/src/stream/stream/take_while.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/throttle.rs b/src/stream/stream/throttle.rs index ce8c13b..2f9333a 100644 --- a/src/stream/stream/throttle.rs +++ b/src/stream/stream/throttle.rs @@ -1,12 +1,12 @@ use std::future::Future; use std::pin::Pin; -use std::time::{Duration, Instant}; +use std::time::Duration; -use futures_timer::Delay; use pin_project_lite::pin_project; use crate::stream::Stream; use crate::task::{Context, Poll}; +use crate::utils::Timer; pin_project! { /// A stream that only yields one element once every `duration`. @@ -25,7 +25,7 @@ pin_project! { #[pin] blocked: bool, #[pin] - delay: Delay, + delay: Timer, } } @@ -35,7 +35,7 @@ impl Throttle { stream, duration, blocked: false, - delay: Delay::new(Duration::default()), + delay: Timer::after(Duration::default()), } } } @@ -59,7 +59,7 @@ impl Stream for Throttle { Poll::Ready(None) => Poll::Ready(None), Poll::Ready(Some(v)) => { *this.blocked = true; - this.delay.reset(Instant::now() + *this.duration); + let _ = std::mem::replace(&mut *this.delay, Timer::after(*this.duration)); Poll::Ready(Some(v)) } } diff --git a/src/stream/stream/timeout.rs b/src/stream/stream/timeout.rs index f580360..28e52ae 100644 --- a/src/stream/stream/timeout.rs +++ b/src/stream/stream/timeout.rs @@ -1,14 +1,14 @@ use std::error::Error; use std::fmt; +use std::future::Future; use std::pin::Pin; use std::time::Duration; -use std::future::Future; -use futures_timer::Delay; use pin_project_lite::pin_project; use crate::stream::Stream; use crate::task::{Context, Poll}; +use crate::utils::Timer; pin_project! { /// A stream with timeout time set @@ -17,13 +17,13 @@ pin_project! { #[pin] stream: S, #[pin] - delay: Delay, + delay: Timer, } } impl Timeout { pub(crate) fn new(stream: S, dur: Duration) -> Self { - let delay = Delay::new(dur); + let delay = Timer::after(dur); Self { stream, delay } } diff --git a/src/stream/stream/try_fold.rs b/src/stream/stream/try_fold.rs index 3b92d95..73312ff 100644 --- a/src/stream/stream/try_fold.rs +++ b/src/stream/stream/try_fold.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use crate::future::Future; use crate::stream::Stream; diff --git a/src/stream/stream/try_for_each.rs b/src/stream/stream/try_for_each.rs index 86f1674..917bfd1 100644 --- a/src/stream/stream/try_for_each.rs +++ b/src/stream/stream/try_for_each.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/unzip.rs b/src/stream/stream/unzip.rs index cb57578..7771509 100644 --- a/src/stream/stream/unzip.rs +++ b/src/stream/stream/unzip.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/stream/zip.rs b/src/stream/stream/zip.rs index 597691b..83d613c 100644 --- a/src/stream/stream/zip.rs +++ b/src/stream/stream/zip.rs @@ -1,5 +1,5 @@ -use std::fmt; -use std::pin::Pin; +use core::fmt; +use core::pin::Pin; use pin_project_lite::pin_project; diff --git a/src/stream/successors.rs b/src/stream/successors.rs index 4421564..a9ce40f 100644 --- a/src/stream/successors.rs +++ b/src/stream/successors.rs @@ -1,5 +1,5 @@ -use std::mem; -use std::pin::Pin; +use core::mem; +use core::pin::Pin; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/sum.rs b/src/stream/sum.rs index 9607baf..3b3144e 100644 --- a/src/stream/sum.rs +++ b/src/stream/sum.rs @@ -1,5 +1,5 @@ -use std::future::Future; -use std::pin::Pin; +use core::future::Future; +use core::pin::Pin; use crate::stream::Stream; diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs index 2822d54..f492ebe 100644 --- a/src/sync/barrier.rs +++ b/src/sync/barrier.rs @@ -1,6 +1,4 @@ -use broadcaster::BroadcastChannel; - -use crate::sync::Mutex; +use crate::sync::{Condvar,Mutex}; /// A barrier enables multiple tasks to synchronize the beginning /// of some computation. @@ -36,14 +34,13 @@ use crate::sync::Mutex; #[derive(Debug)] pub struct Barrier { state: Mutex, - wait: BroadcastChannel<(usize, usize)>, - n: usize, + cvar: Condvar, + num_tasks: usize, } // The inner state of a double barrier #[derive(Debug)] struct BarrierState { - waker: BroadcastChannel<(usize, usize)>, count: usize, generation_id: usize, } @@ -81,25 +78,14 @@ impl Barrier { /// /// let barrier = Barrier::new(10); /// ``` - pub fn new(mut n: usize) -> Barrier { - let waker = BroadcastChannel::new(); - let wait = waker.clone(); - - if n == 0 { - // if n is 0, it's not clear what behavior the user wants. - // in std::sync::Barrier, an n of 0 exhibits the same behavior as n == 1, where every - // .wait() immediately unblocks, so we adopt that here as well. - n = 1; - } - + pub fn new(n: usize) -> Barrier { Barrier { state: Mutex::new(BarrierState { - waker, count: 0, generation_id: 1, }), - n, - wait, + cvar: Condvar::new(), + num_tasks: n, } } @@ -143,35 +129,20 @@ impl Barrier { /// # }); /// ``` pub async fn wait(&self) -> BarrierWaitResult { - let mut lock = self.state.lock().await; - let local_gen = lock.generation_id; - - lock.count += 1; + let mut state = self.state.lock().await; + let local_gen = state.generation_id; + state.count += 1; - if lock.count < self.n { - let mut wait = self.wait.clone(); - - let mut generation_id = lock.generation_id; - let mut count = lock.count; - - drop(lock); - - while local_gen == generation_id && count < self.n { - let (g, c) = wait.recv().await.expect("sender has not been closed"); - generation_id = g; - count = c; + if state.count < self.num_tasks { + while local_gen == state.generation_id && state.count < self.num_tasks { + state = self.cvar.wait(state).await; } BarrierWaitResult(false) } else { - lock.count = 0; - lock.generation_id = lock.generation_id.wrapping_add(1); - - lock.waker - .send(&(lock.generation_id, lock.count)) - .await - .expect("there should be at least one receiver"); - + state.count = 0; + state.generation_id = state.generation_id.wrapping_add(1); + self.cvar.notify_all(); BarrierWaitResult(true) } } @@ -202,7 +173,7 @@ impl BarrierWaitResult { } } -#[cfg(test)] +#[cfg(all(test, not(target_os = "unknown")))] mod test { use futures::channel::mpsc::unbounded; use futures::sink::SinkExt; diff --git a/src/sync/channel.rs b/src/sync/channel.rs index 2647f65..3207846 100644 --- a/src/sync/channel.rs +++ b/src/sync/channel.rs @@ -1,5 +1,6 @@ use std::cell::UnsafeCell; -use std::fmt; +use std::error::Error; +use std::fmt::{self, Debug, Display}; use std::future::Future; use std::isize; use std::marker::PhantomData; @@ -31,6 +32,7 @@ use crate::sync::WakerSet; /// # Examples /// /// ``` +/// # fn main() -> Result<(), async_std::sync::RecvError> { /// # async_std::task::block_on(async { /// # /// use std::time::Duration; @@ -41,7 +43,7 @@ use crate::sync::WakerSet; /// let (s, r) = channel(1); /// /// // This call returns immediately because there is enough space in the channel. -/// s.send(1).await; +/// s.send(1usize).await; /// /// task::spawn(async move { /// // This call will have to wait because the channel is full. @@ -50,10 +52,11 @@ use crate::sync::WakerSet; /// }); /// /// task::sleep(Duration::from_secs(1)).await; -/// assert_eq!(r.recv().await, Some(1)); -/// assert_eq!(r.recv().await, Some(2)); +/// assert_eq!(r.recv().await?, 1); +/// assert_eq!(r.recv().await?, 2); +/// # Ok(()) /// # -/// # }) +/// # }) } /// ``` #[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] @@ -112,6 +115,7 @@ impl Sender { /// # Examples /// /// ``` + /// # fn main() -> Result<(), async_std::sync::RecvError> { /// # async_std::task::block_on(async { /// # /// use async_std::sync::channel; @@ -124,11 +128,12 @@ impl Sender { /// s.send(2).await; /// }); /// - /// assert_eq!(r.recv().await, Some(1)); - /// assert_eq!(r.recv().await, Some(2)); - /// assert_eq!(r.recv().await, None); + /// assert_eq!(r.recv().await?, 1); + /// assert_eq!(r.recv().await?, 2); + /// assert!(r.recv().await.is_err()); /// # - /// # }) + /// # Ok(()) + /// # }) } /// ``` pub async fn send(&self, msg: T) { struct SendFuture<'a, T> { @@ -192,6 +197,27 @@ impl Sender { .await } + /// Attempts to send a message into the channel. + /// + /// If the channel is full, this method will return an error. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// assert!(s.try_send(1).is_ok()); + /// assert!(s.try_send(2).is_err()); + /// # + /// # }) + /// ``` + pub fn try_send(&self, msg: T) -> Result<(), TrySendError> { + self.channel.try_send(msg) + } + /// Returns the channel capacity. /// /// # Examples @@ -313,6 +339,7 @@ impl fmt::Debug for Sender { /// # Examples /// /// ``` +/// # fn main() -> Result<(), async_std::sync::RecvError> { /// # async_std::task::block_on(async { /// # /// use std::time::Duration; @@ -323,15 +350,16 @@ impl fmt::Debug for Sender { /// let (s, r) = channel(100); /// /// task::spawn(async move { -/// s.send(1).await; +/// s.send(1usize).await; /// task::sleep(Duration::from_secs(1)).await; /// s.send(2).await; /// }); /// -/// assert_eq!(r.recv().await, Some(1)); // Received immediately. -/// assert_eq!(r.recv().await, Some(2)); // Received after 1 second. +/// assert_eq!(r.recv().await?, 1); // Received immediately. +/// assert_eq!(r.recv().await?, 2); // Received after 1 second. /// # -/// # }) +/// # Ok(()) +/// # }) } /// ``` #[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] @@ -346,12 +374,14 @@ pub struct Receiver { impl Receiver { /// Receives a message from the channel. /// - /// If the channel is empty and still has senders, this method will wait until a message is - /// sent into the channel or until all senders get dropped. + /// If the channel is empty and still has senders, this method + /// will wait until a message is sent into it. Once all senders + /// have been dropped it will return `None`. /// /// # Examples /// /// ``` + /// # fn main() -> Result<(), async_std::sync::RecvError> { /// # async_std::task::block_on(async { /// # /// use async_std::sync::channel; @@ -360,24 +390,26 @@ impl Receiver { /// let (s, r) = channel(1); /// /// task::spawn(async move { - /// s.send(1).await; + /// s.send(1usize).await; /// s.send(2).await; + /// // Then we drop the sender /// }); /// - /// assert_eq!(r.recv().await, Some(1)); - /// assert_eq!(r.recv().await, Some(2)); - /// assert_eq!(r.recv().await, None); + /// assert_eq!(r.recv().await?, 1); + /// assert_eq!(r.recv().await?, 2); + /// assert!(r.recv().await.is_err()); /// # - /// # }) + /// # Ok(()) + /// # }) } /// ``` - pub async fn recv(&self) -> Option { + pub async fn recv(&self) -> Result { struct RecvFuture<'a, T> { channel: &'a Channel, opt_key: Option, } impl Future for RecvFuture<'_, T> { - type Output = Option; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { poll_recv( @@ -405,6 +437,30 @@ impl Receiver { .await } + /// Attempts to receive a message from the channel. + /// + /// If the channel is empty, this method will return an error. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// + /// s.send(1u8).await; + /// + /// assert!(r.try_recv().is_ok()); + /// assert!(r.try_recv().is_err()); + /// # + /// # }) + /// ``` + pub fn try_recv(&self) -> Result { + self.channel.try_recv() + } + /// Returns the channel capacity. /// /// # Examples @@ -519,12 +575,13 @@ impl Stream for Receiver { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = &mut *self; - poll_recv( + let res = futures_core::ready!(poll_recv( &this.channel, &this.channel.stream_wakers, &mut this.opt_key, cx, - ) + )); + Poll::Ready(res.ok()) } } @@ -543,7 +600,7 @@ fn poll_recv( wakers: &WakerSet, opt_key: &mut Option, cx: &mut Context<'_>, -) -> Poll> { +) -> Poll> { loop { // If the current task is in the set, remove it. if let Some(key) = opt_key.take() { @@ -552,8 +609,8 @@ fn poll_recv( // Try receiving a message. match channel.try_recv() { - Ok(msg) => return Poll::Ready(Some(msg)), - Err(TryRecvError::Disconnected) => return Poll::Ready(None), + Ok(msg) => return Poll::Ready(Ok(msg)), + Err(TryRecvError::Disconnected) => return Poll::Ready(Err(RecvError {})), Err(TryRecvError::Empty) => { // Insert this receive operation. *opt_key = Some(wakers.insert(cx)); @@ -932,8 +989,11 @@ impl Drop for Channel { } } -/// An error returned from the `try_send()` method. -enum TrySendError { +/// An error returned from the `try_send` method. +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(PartialEq, Eq)] +pub enum TrySendError { /// The channel is full but not disconnected. Full(T), @@ -941,11 +1001,59 @@ enum TrySendError { Disconnected(T), } -/// An error returned from the `try_recv()` method. -enum TryRecvError { +impl Error for TrySendError {} + +impl Debug for TrySendError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Full(_) => Debug::fmt("Full", f), + Self::Disconnected(_) => Debug::fmt("Disconnected", f), + } + } +} + +impl Display for TrySendError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Full(_) => Display::fmt("The channel is full.", f), + Self::Disconnected(_) => Display::fmt("The channel is full and disconnected.", f), + } + } +} + +/// An error returned from the `try_recv` method. +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug, PartialEq, Eq)] +pub enum TryRecvError { /// The channel is empty but not disconnected. Empty, /// The channel is empty and disconnected. Disconnected, } + +impl Error for TryRecvError {} + +impl Display for TryRecvError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Empty => Display::fmt("The channel is empty.", f), + Self::Disconnected => Display::fmt("The channel is empty and disconnected.", f), + } + } +} + +/// An error returned from the `recv` method. +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug, PartialEq, Eq)] +pub struct RecvError; + +impl Error for RecvError {} + +impl Display for RecvError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt("The channel is empty.", f) + } +} diff --git a/src/sync/condvar.rs b/src/sync/condvar.rs new file mode 100644 index 0000000..67507f3 --- /dev/null +++ b/src/sync/condvar.rs @@ -0,0 +1,417 @@ +use std::fmt; +use std::pin::Pin; +use std::time::Duration; + +use super::mutex::{guard_lock, MutexGuard}; +use crate::future::{timeout, Future}; +use crate::sync::WakerSet; +use crate::task::{Context, Poll}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct WaitTimeoutResult(bool); + +/// A type indicating whether a timed wait on a condition variable returned due to a time out or +/// not +impl WaitTimeoutResult { + /// Returns `true` if the wait was known to have timed out. + pub fn timed_out(self) -> bool { + self.0 + } +} + +/// A Condition Variable +/// +/// This type is an async version of [`std::sync::Mutex`]. +/// +/// [`std::sync::Condvar`]: https://doc.rust-lang.org/std/sync/struct.Condvar.html +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use std::sync::Arc; +/// +/// use async_std::sync::{Mutex, Condvar}; +/// use async_std::task; +/// +/// let pair = Arc::new((Mutex::new(false), Condvar::new())); +/// let pair2 = pair.clone(); +/// +/// // Inside of our lock, spawn a new thread, and then wait for it to start. +/// task::spawn(async move { +/// let (lock, cvar) = &*pair2; +/// let mut started = lock.lock().await; +/// *started = true; +/// // We notify the condvar that the value has changed. +/// cvar.notify_one(); +/// }); +/// +/// // Wait for the thread to start up. +/// let (lock, cvar) = &*pair; +/// let mut started = lock.lock().await; +/// while !*started { +/// started = cvar.wait(started).await; +/// } +/// +/// # }) +/// ``` +pub struct Condvar { + wakers: WakerSet, +} + +unsafe impl Send for Condvar {} +unsafe impl Sync for Condvar {} + +impl Default for Condvar { + fn default() -> Self { + Condvar::new() + } +} + +impl Condvar { + /// Creates a new condition variable + /// + /// # Examples + /// + /// ``` + /// use async_std::sync::Condvar; + /// + /// let cvar = Condvar::new(); + /// ``` + pub fn new() -> Self { + Condvar { + wakers: WakerSet::new(), + } + } + + /// Blocks the current task until this condition variable receives a notification. + /// + /// Unlike the std equivalent, this does not check that a single mutex is used at runtime. + /// However, as a best practice avoid using with multiple mutexes. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// use std::sync::Arc; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_one(); + /// }); + /// + /// // Wait for the thread to start up. + /// let (lock, cvar) = &*pair; + /// let mut started = lock.lock().await; + /// while !*started { + /// started = cvar.wait(started).await; + /// } + /// # }) + /// ``` + #[allow(clippy::needless_lifetimes)] + pub async fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { + let mutex = guard_lock(&guard); + + self.await_notify(guard).await; + + mutex.lock().await + } + + fn await_notify<'a, T>(&self, guard: MutexGuard<'a, T>) -> AwaitNotify<'_, 'a, T> { + AwaitNotify { + cond: self, + guard: Some(guard), + key: None, + } + } + + /// Blocks the current taks until this condition variable receives a notification and the + /// required condition is met. Spurious wakeups are ignored and this function will only + /// return once the condition has been met. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use std::sync::Arc; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_one(); + /// }); + /// + /// // Wait for the thread to start up. + /// let (lock, cvar) = &*pair; + /// // As long as the value inside the `Mutex` is `false`, we wait. + /// let _guard = cvar.wait_until(lock.lock().await, |started| { *started }).await; + /// # + /// # }) + /// ``` + #[allow(clippy::needless_lifetimes)] + pub async fn wait_until<'a, T, F>( + &self, + mut guard: MutexGuard<'a, T>, + mut condition: F, + ) -> MutexGuard<'a, T> + where + F: FnMut(&mut T) -> bool, + { + while !condition(&mut *guard) { + guard = self.wait(guard).await; + } + guard + } + + /// Waits on this condition variable for a notification, timing out after a specified duration. + /// + /// For these reasons `Condvar::wait_timeout_until` is recommended in most cases. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use std::sync::Arc; + /// use std::time::Duration; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_one(); + /// }); + /// + /// // wait for the thread to start up + /// let (lock, cvar) = &*pair; + /// let mut started = lock.lock().await; + /// loop { + /// let result = cvar.wait_timeout(started, Duration::from_millis(10)).await; + /// started = result.0; + /// if *started == true { + /// // We received the notification and the value has been updated, we can leave. + /// break + /// } + /// } + /// # + /// # }) + /// ``` + #[allow(clippy::needless_lifetimes)] + pub async fn wait_timeout<'a, T>( + &self, + guard: MutexGuard<'a, T>, + dur: Duration, + ) -> (MutexGuard<'a, T>, WaitTimeoutResult) { + let mutex = guard_lock(&guard); + match timeout(dur, self.wait(guard)).await { + Ok(guard) => (guard, WaitTimeoutResult(false)), + Err(_) => (mutex.lock().await, WaitTimeoutResult(true)), + } + } + + /// Waits on this condition variable for a notification, timing out after a specified duration. + /// Spurious wakes will not cause this function to return. + /// + /// # Examples + /// ``` + /// # async_std::task::block_on(async { + /// use std::sync::Arc; + /// use std::time::Duration; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_one(); + /// }); + /// + /// // wait for the thread to start up + /// let (lock, cvar) = &*pair; + /// let result = cvar.wait_timeout_until( + /// lock.lock().await, + /// Duration::from_millis(100), + /// |&mut started| started, + /// ).await; + /// if result.1.timed_out() { + /// // timed-out without the condition ever evaluating to true. + /// } + /// // access the locked mutex via result.0 + /// # }); + /// ``` + #[allow(clippy::needless_lifetimes)] + pub async fn wait_timeout_until<'a, T, F>( + &self, + guard: MutexGuard<'a, T>, + dur: Duration, + condition: F, + ) -> (MutexGuard<'a, T>, WaitTimeoutResult) + where + F: FnMut(&mut T) -> bool, + { + let mutex = guard_lock(&guard); + match timeout(dur, self.wait_until(guard, condition)).await { + Ok(guard) => (guard, WaitTimeoutResult(false)), + Err(_) => (mutex.lock().await, WaitTimeoutResult(true)), + } + } + + /// Wakes up one blocked task on this condvar. + /// + /// # Examples + /// + /// ``` + /// # fn main() { async_std::task::block_on(async { + /// use std::sync::Arc; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_one(); + /// }); + /// + /// // Wait for the thread to start up. + /// let (lock, cvar) = &*pair; + /// let mut started = lock.lock().await; + /// while !*started { + /// started = cvar.wait(started).await; + /// } + /// # }) } + /// ``` + pub fn notify_one(&self) { + self.wakers.notify_one(); + } + + /// Wakes up all blocked tasks on this condvar. + /// + /// # Examples + /// ``` + /// # fn main() { async_std::task::block_on(async { + /// # + /// use std::sync::Arc; + /// + /// use async_std::sync::{Mutex, Condvar}; + /// use async_std::task; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = pair.clone(); + /// + /// task::spawn(async move { + /// let (lock, cvar) = &*pair2; + /// let mut started = lock.lock().await; + /// *started = true; + /// // We notify the condvar that the value has changed. + /// cvar.notify_all(); + /// }); + /// + /// // Wait for the thread to start up. + /// let (lock, cvar) = &*pair; + /// let mut started = lock.lock().await; + /// // As long as the value inside the `Mutex` is `false`, we wait. + /// while !*started { + /// started = cvar.wait(started).await; + /// } + /// # + /// # }) } + /// ``` + pub fn notify_all(&self) { + self.wakers.notify_all(); + } +} + +impl fmt::Debug for Condvar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("Condvar { .. }") + } +} + +/// A future that waits for another task to notify the condition variable. +/// +/// This is an internal future that `wait` and `wait_until` await on. +struct AwaitNotify<'a, 'b, T> { + /// The condition variable that we are waiting on + cond: &'a Condvar, + /// The lock used with `cond`. + /// This will be released the first time the future is polled, + /// after registering the context to be notified. + guard: Option>, + /// A key into the conditions variable's `WakerSet`. + /// This is set to the index of the `Waker` for the context each time + /// the future is polled and not completed. + key: Option, +} + +impl<'a, 'b, T> Future for AwaitNotify<'a, 'b, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.guard.take() { + Some(_) => { + self.key = Some(self.cond.wakers.insert(cx)); + // the guard is dropped when we return, which frees the lock + Poll::Pending + } + None => { + if let Some(key) = self.key { + if self.cond.wakers.remove_if_notified(key, cx) { + self.key = None; + Poll::Ready(()) + } else { + Poll::Pending + } + } else { + // This should only happen if it is polled twice after receiving a notification + Poll::Ready(()) + } + } + } + } +} + +impl<'a, 'b, T> Drop for AwaitNotify<'a, 'b, T> { + fn drop(&mut self) { + if let Some(key) = self.key { + self.cond.wakers.cancel(key); + } + } +} diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 088c520..bccc6ec 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -184,9 +184,11 @@ mod rwlock; cfg_unstable! { pub use barrier::{Barrier, BarrierWaitResult}; - pub use channel::{channel, Sender, Receiver}; + pub use channel::{channel, Sender, Receiver, RecvError, TryRecvError, TrySendError}; + pub use condvar::Condvar; mod barrier; + mod condvar; mod channel; } diff --git a/src/sync/mutex.rs b/src/sync/mutex.rs index c62b561..ae953fd 100644 --- a/src/sync/mutex.rs +++ b/src/sync/mutex.rs @@ -287,3 +287,8 @@ impl DerefMut for MutexGuard<'_, T> { unsafe { &mut *self.0.value.get() } } } + +#[cfg(feature = "unstable")] +pub fn guard_lock<'a, T>(guard: &MutexGuard<'a, T>) -> &'a Mutex { + guard.0 +} diff --git a/src/sync/waker_set.rs b/src/sync/waker_set.rs index 7e897af..881304b 100644 --- a/src/sync/waker_set.rs +++ b/src/sync/waker_set.rs @@ -80,6 +80,28 @@ impl WakerSet { } } + /// If the waker for this key is still waiting for a notification, then update + /// the waker for the entry, and return false. If the waker has been notified, + /// treat the entry as completed and return true. + #[cfg(feature = "unstable")] + pub fn remove_if_notified(&self, key: usize, cx: &Context<'_>) -> bool { + let mut inner = self.lock(); + + match &mut inner.entries[key] { + None => { + inner.entries.remove(key); + true + } + Some(w) => { + // We were never woken, so update instead + if !w.will_wake(cx.waker()) { + *w = cx.waker().clone(); + } + false + } + } + } + /// Removes the waker of a cancelled operation. /// /// Returns `true` if another blocked operation from the set was notified. diff --git a/src/task/block_on.rs b/src/task/block_on.rs index 80259c5..fa66f91 100644 --- a/src/task/block_on.rs +++ b/src/task/block_on.rs @@ -1,15 +1,6 @@ -use std::cell::Cell; use std::future::Future; -use std::mem::{self, ManuallyDrop}; -use std::sync::Arc; -use std::task::{RawWaker, RawWakerVTable}; -use std::thread; -use crossbeam_utils::sync::Parker; -use kv_log_macro::trace; -use log::log_enabled; - -use crate::task::{Context, Poll, Task, Waker}; +use crate::task::Builder; /// Spawns a task and blocks the current thread on its result. /// @@ -34,113 +25,20 @@ use crate::task::{Context, Poll, Task, Waker}; /// }) /// } /// ``` +#[cfg(not(target_os = "unknown"))] pub fn block_on(future: F) -> T where F: Future, { - // Create a new task handle. - let task = Task::new(None); - - // Log this `block_on` operation. - if log_enabled!(log::Level::Trace) { - trace!("block_on", { - task_id: task.id().0, - parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0), - }); - } - - let future = async move { - // Drop task-locals on exit. - defer! { - Task::get_current(|t| unsafe { t.drop_locals() }); - } - - // Log completion on exit. - defer! { - if log_enabled!(log::Level::Trace) { - Task::get_current(|t| { - trace!("completed", { - task_id: t.id().0, - }); - }); - } - } - - future.await - }; - - // Run the future as a task. - unsafe { Task::set_current(&task, || run(future)) } + Builder::new().blocking(future) } -/// Blocks the current thread on a future's result. -fn run(future: F) -> T +/// Spawns a task and waits for it to finish. +#[cfg(target_os = "unknown")] +pub fn block_on(future: F) where - F: Future, + F: Future + 'static, + T: 'static, { - thread_local! { - // May hold a pre-allocated parker that can be reused for efficiency. - // - // Note that each invocation of `block` needs its own parker. In particular, if `block` - // recursively calls itself, we must make sure that each recursive call uses a distinct - // parker instance. - static CACHE: Cell>> = Cell::new(None); - } - - // Virtual table for wakers based on `Arc`. - static VTABLE: RawWakerVTable = { - unsafe fn clone_raw(ptr: *const ()) -> RawWaker { - let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Parker)); - #[allow(clippy::redundant_clone)] - mem::forget(arc.clone()); - RawWaker::new(ptr, &VTABLE) - } - - unsafe fn wake_raw(ptr: *const ()) { - let arc = Arc::from_raw(ptr as *const Parker); - arc.unparker().unpark(); - } - - unsafe fn wake_by_ref_raw(ptr: *const ()) { - let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Parker)); - arc.unparker().unpark(); - } - - unsafe fn drop_raw(ptr: *const ()) { - drop(Arc::from_raw(ptr as *const Parker)) - } - - RawWakerVTable::new(clone_raw, wake_raw, wake_by_ref_raw, drop_raw) - }; - - // Pin the future on the stack. - pin_utils::pin_mut!(future); - - CACHE.with(|cache| { - // Reuse a cached parker or create a new one for this invocation of `block`. - let arc_parker: Arc = cache.take().unwrap_or_else(|| Arc::new(Parker::new())); - let ptr = (&*arc_parker as *const Parker) as *const (); - - // Create a waker and task context. - let waker = unsafe { ManuallyDrop::new(Waker::from_raw(RawWaker::new(ptr, &VTABLE))) }; - let cx = &mut Context::from_waker(&waker); - - let mut step = 0; - loop { - if let Poll::Ready(t) = future.as_mut().poll(cx) { - // Save the parker for the next invocation of `block`. - cache.set(Some(arc_parker)); - return t; - } - - // Yield a few times or park the current thread. - if step < 3 { - thread::yield_now(); - step += 1; - } else { - arc_parker.park(); - step = 0; - } - } - }) + Builder::new().local(future).unwrap(); } diff --git a/src/task/builder.rs b/src/task/builder.rs index afd4c2c..0024f8a 100644 --- a/src/task/builder.rs +++ b/src/task/builder.rs @@ -1,11 +1,13 @@ -use kv_log_macro::trace; -use log::log_enabled; +use std::cell::Cell; use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use pin_project_lite::pin_project; use crate::io; -use crate::task::executor; -use crate::task::{JoinHandle, Task}; -use crate::utils::abort_on_panic; +use crate::task::{JoinHandle, Task, TaskLocalsWrapper}; /// Task builder that configures the settings of a new task. #[derive(Debug, Default)] @@ -27,58 +29,174 @@ impl Builder { self } + fn build(self, future: F) -> SupportTaskLocals + where + F: Future, + { + let name = self.name.map(Arc::new); + + // Create a new task handle. + let task = Task::new(name); + + #[cfg(not(target_os = "unknown"))] + once_cell::sync::Lazy::force(&crate::rt::RUNTIME); + + let tag = TaskLocalsWrapper::new(task.clone()); + + SupportTaskLocals { tag, future } + } + /// Spawns a task with the configured settings. + #[cfg(not(target_os = "unknown"))] pub fn spawn(self, future: F) -> io::Result> where F: Future + Send + 'static, T: Send + 'static, { - // Create a new task handle. - let task = Task::new(self.name); - - // Log this `spawn` operation. - if log_enabled!(log::Level::Trace) { - trace!("spawn", { - task_id: task.id().0, - parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0), - }); + let wrapped = self.build(future); + + kv_log_macro::trace!("spawn", { + task_id: wrapped.tag.id().0, + parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0), + }); + + let task = wrapped.tag.task().clone(); + let smol_task = smol::Task::spawn(wrapped).into(); + + Ok(JoinHandle::new(smol_task, task)) + } + + /// Spawns a task locally with the configured settings. + #[cfg(all(not(target_os = "unknown"), feature = "unstable"))] + pub fn local(self, future: F) -> io::Result> + where + F: Future + 'static, + T: 'static, + { + let wrapped = self.build(future); + + kv_log_macro::trace!("spawn_local", { + task_id: wrapped.tag.id().0, + parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0), + }); + + let task = wrapped.tag.task().clone(); + let smol_task = smol::Task::local(wrapped).into(); + + Ok(JoinHandle::new(smol_task, task)) + } + + /// Spawns a task locally with the configured settings. + #[cfg(all(target_arch = "wasm32", feature = "unstable"))] + pub fn local(self, future: F) -> io::Result> + where + F: Future + 'static, + T: 'static, + { + use futures_channel::oneshot::channel; + let (sender, receiver) = channel(); + + let wrapped = self.build(async move { + let res = future.await; + let _ = sender.send(res); + }); + kv_log_macro::trace!("spawn_local", { + task_id: wrapped.tag.id().0, + parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0), + }); + + let task = wrapped.tag.task().clone(); + wasm_bindgen_futures::spawn_local(wrapped); + + Ok(JoinHandle::new(receiver, task)) + } + + /// Spawns a task locally with the configured settings. + #[cfg(all(target_arch = "wasm32", not(feature = "unstable")))] + pub(crate) fn local(self, future: F) -> io::Result> + where + F: Future + 'static, + T: 'static, + { + use futures_channel::oneshot::channel; + let (sender, receiver) = channel(); + + let wrapped = self.build(async move { + let res = future.await; + let _ = sender.send(res); + }); + + kv_log_macro::trace!("spawn_local", { + task_id: wrapped.tag.id().0, + parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0), + }); + + let task = wrapped.tag.task().clone(); + wasm_bindgen_futures::spawn_local(wrapped); + + Ok(JoinHandle::new(receiver, task)) + } + + /// Spawns a task with the configured settings, blocking on its execution. + #[cfg(not(target_os = "unknown"))] + pub fn blocking(self, future: F) -> T + where + F: Future, + { + let wrapped = self.build(future); + + // Log this `block_on` operation. + kv_log_macro::trace!("block_on", { + task_id: wrapped.tag.id().0, + parent_task_id: TaskLocalsWrapper::get_current(|t| t.id().0).unwrap_or(0), + }); + + thread_local! { + /// Tracks the number of nested block_on calls. + static NUM_NESTED_BLOCKING: Cell = Cell::new(0); } - let future = async move { - // Drop task-locals on exit. - defer! { - Task::get_current(|t| unsafe { t.drop_locals() }); - } + // Run the future as a task. + NUM_NESTED_BLOCKING.with(|num_nested_blocking| { + let count = num_nested_blocking.get(); + let should_run = count == 0; + // increase the count + num_nested_blocking.replace(count + 1); - // Log completion on exit. - defer! { - if log_enabled!(log::Level::Trace) { - Task::get_current(|t| { - trace!("completed", { - task_id: t.id().0, - }); - }); - } + unsafe { + TaskLocalsWrapper::set_current(&wrapped.tag, || { + let res = if should_run { + // The first call should use run. + smol::run(wrapped) + } else { + smol::block_on(wrapped) + }; + num_nested_blocking.replace(num_nested_blocking.get() - 1); + res + }) } + }) + } +} - future.await - }; - - let schedule = move |t| executor::schedule(Runnable(t)); - let (task, handle) = async_task::spawn(future, schedule, task); - task.schedule(); - Ok(JoinHandle::new(handle)) +pin_project! { + /// Wrapper to add support for task locals. + struct SupportTaskLocals { + tag: TaskLocalsWrapper, + #[pin] + future: F, } } -/// A runnable task. -pub(crate) struct Runnable(async_task::Task); +impl Future for SupportTaskLocals { + type Output = F::Output; -impl Runnable { - /// Runs the task by polling its future once. - pub fn run(self) { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { unsafe { - Task::set_current(self.0.tag(), || abort_on_panic(|| self.0.run())); + TaskLocalsWrapper::set_current(&self.tag, || { + let this = self.project(); + this.future.poll(cx) + }) } } } diff --git a/src/task/current.rs b/src/task/current.rs index 0dc3699..e4624e1 100644 --- a/src/task/current.rs +++ b/src/task/current.rs @@ -1,4 +1,4 @@ -use crate::task::Task; +use crate::task::{Task, TaskLocalsWrapper}; /// Returns a handle to the current task. /// @@ -23,6 +23,6 @@ use crate::task::Task; /// # }) /// ``` pub fn current() -> Task { - Task::get_current(|t| t.clone()) + TaskLocalsWrapper::get_current(|t| t.task().clone()) .expect("`task::current()` called outside the context of a task") } diff --git a/src/task/executor/mod.rs b/src/task/executor/mod.rs deleted file mode 100644 index 2a6a696..0000000 --- a/src/task/executor/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Task executor. -//! -//! API bindings between `crate::task` and this module are very simple: -//! -//! * The only export is the `schedule` function. -//! * The only import is the `crate::task::Runnable` type. - -pub(crate) use pool::schedule; - -use sleepers::Sleepers; - -mod pool; -mod sleepers; diff --git a/src/task/executor/pool.rs b/src/task/executor/pool.rs deleted file mode 100644 index 5249b3d..0000000 --- a/src/task/executor/pool.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::cell::Cell; -use std::iter; -use std::thread; -use std::time::Duration; - -use crossbeam_deque::{Injector, Stealer, Worker}; -use once_cell::sync::Lazy; -use once_cell::unsync::OnceCell; - -use crate::task::executor::Sleepers; -use crate::task::Runnable; -use crate::utils::{abort_on_panic, random}; - -/// The state of an executor. -struct Pool { - /// The global queue of tasks. - injector: Injector, - - /// Handles to local queues for stealing work from worker threads. - stealers: Vec>, - - /// Used for putting idle workers to sleep and notifying them when new tasks come in. - sleepers: Sleepers, -} - -/// Global executor that runs spawned tasks. -static POOL: Lazy = Lazy::new(|| { - let num_threads = num_cpus::get().max(1); - let mut stealers = Vec::new(); - - // Spawn worker threads. - for _ in 0..num_threads { - let worker = Worker::new_fifo(); - stealers.push(worker.stealer()); - - let proc = Processor { - worker, - slot: Cell::new(None), - slot_runs: Cell::new(0), - }; - - thread::Builder::new() - .name("async-std/executor".to_string()) - .spawn(|| { - let _ = PROCESSOR.with(|p| p.set(proc)); - abort_on_panic(main_loop); - }) - .expect("cannot start a thread driving tasks"); - } - - Pool { - injector: Injector::new(), - stealers, - sleepers: Sleepers::new(), - } -}); - -/// The state of a worker thread. -struct Processor { - /// The local task queue. - worker: Worker, - - /// Contains the next task to run as an optimization that skips queues. - slot: Cell>, - - /// How many times in a row tasks have been taked from the slot rather than the queue. - slot_runs: Cell, -} - -thread_local! { - /// Worker thread state. - static PROCESSOR: OnceCell = OnceCell::new(); -} - -/// Schedules a new runnable task for execution. -pub(crate) fn schedule(task: Runnable) { - PROCESSOR.with(|proc| { - // If the current thread is a worker thread, store it into its task slot or push it into - // its local task queue. Otherwise, push it into the global task queue. - match proc.get() { - Some(proc) => { - // Replace the task in the slot. - if let Some(task) = proc.slot.replace(Some(task)) { - // If the slot already contained a task, push it into the local task queue. - proc.worker.push(task); - POOL.sleepers.notify_one(); - } - } - None => { - POOL.injector.push(task); - POOL.sleepers.notify_one(); - } - } - }) -} - -/// Main loop running a worker thread. -fn main_loop() { - /// Number of yields when no runnable task is found. - const YIELDS: u32 = 3; - /// Number of short sleeps when no runnable task in found. - const SLEEPS: u32 = 1; - - // The number of times the thread didn't find work in a row. - let mut fails = 0; - - loop { - // Try to find a runnable task. - match find_runnable() { - Some(task) => { - fails = 0; - - // Run the found task. - task.run(); - } - None => { - fails += 1; - - // Yield the current thread or put it to sleep. - if fails <= YIELDS { - thread::yield_now(); - } else if fails <= YIELDS + SLEEPS { - thread::sleep(Duration::from_micros(10)); - } else { - POOL.sleepers.wait(); - fails = 0; - } - } - } - } -} - -/// Find the next runnable task. -fn find_runnable() -> Option { - /// Maximum number of times the slot can be used in a row. - const SLOT_LIMIT: u32 = 16; - - PROCESSOR.with(|proc| { - let proc = proc.get().unwrap(); - - // Try taking a task from the slot. - let runs = proc.slot_runs.get(); - if runs < SLOT_LIMIT { - if let Some(task) = proc.slot.take() { - proc.slot_runs.set(runs + 1); - return Some(task); - } - } - proc.slot_runs.set(0); - - // Pop a task from the local queue, if not empty. - proc.worker.pop().or_else(|| { - // Otherwise, we need to look for a task elsewhere. - iter::repeat_with(|| { - // Try stealing a batch of tasks from the global queue. - POOL.injector - .steal_batch_and_pop(&proc.worker) - // Or try stealing a batch of tasks from one of the other threads. - .or_else(|| { - // First, pick a random starting point in the list of local queues. - let len = POOL.stealers.len(); - let start = random(len as u32) as usize; - - // Try stealing a batch of tasks from each local queue starting from the - // chosen point. - let (l, r) = POOL.stealers.split_at(start); - let stealers = r.iter().chain(l.iter()); - stealers - .map(|s| s.steal_batch_and_pop(&proc.worker)) - .collect() - }) - }) - // Loop while no task was stolen and any steal operation needs to be retried. - .find(|s| !s.is_retry()) - // Extract the stolen task, if there is one. - .and_then(|s| s.success()) - }) - }) -} diff --git a/src/task/executor/sleepers.rs b/src/task/executor/sleepers.rs deleted file mode 100644 index 4e70129..0000000 --- a/src/task/executor/sleepers.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Condvar, Mutex}; - -/// The place where worker threads go to sleep. -/// -/// Similar to how thread parking works, if a notification comes up while no threads are sleeping, -/// the next thread that attempts to go to sleep will pick up the notification immediately. -pub struct Sleepers { - /// How many threads are currently a sleep. - sleep: Mutex, - - /// A condvar for notifying sleeping threads. - wake: Condvar, - - /// Set to `true` if a notification came up while nobody was sleeping. - notified: AtomicBool, -} - -impl Sleepers { - /// Creates a new `Sleepers`. - pub fn new() -> Sleepers { - Sleepers { - sleep: Mutex::new(0), - wake: Condvar::new(), - notified: AtomicBool::new(false), - } - } - - /// Puts the current thread to sleep. - pub fn wait(&self) { - let mut sleep = self.sleep.lock().unwrap(); - - if !self.notified.swap(false, Ordering::SeqCst) { - *sleep += 1; - let _ = self.wake.wait(sleep).unwrap(); - } - } - - /// Notifies one thread. - pub fn notify_one(&self) { - if !self.notified.load(Ordering::SeqCst) { - let mut sleep = self.sleep.lock().unwrap(); - - if *sleep > 0 { - *sleep -= 1; - self.wake.notify_one(); - } else { - self.notified.store(true, Ordering::SeqCst); - } - } - } -} diff --git a/src/task/join_handle.rs b/src/task/join_handle.rs index 9fefff2..110b827 100644 --- a/src/task/join_handle.rs +++ b/src/task/join_handle.rs @@ -12,15 +12,23 @@ use crate::task::{Context, Poll, Task}; /// /// [spawned]: fn.spawn.html #[derive(Debug)] -pub struct JoinHandle(async_task::JoinHandle); +pub struct JoinHandle { + handle: Option>, + task: Task, +} -unsafe impl Send for JoinHandle {} -unsafe impl Sync for JoinHandle {} +#[cfg(not(target_os = "unknown"))] +type InnerHandle = async_task::JoinHandle; +#[cfg(target_arch = "wasm32")] +type InnerHandle = futures_channel::oneshot::Receiver; impl JoinHandle { /// Creates a new `JoinHandle`. - pub(crate) fn new(inner: async_task::JoinHandle) -> JoinHandle { - JoinHandle(inner) + pub(crate) fn new(inner: InnerHandle, task: Task) -> JoinHandle { + JoinHandle { + handle: Some(inner), + task, + } } /// Returns a handle to the underlying task. @@ -39,7 +47,23 @@ impl JoinHandle { /// # /// # }) pub fn task(&self) -> &Task { - self.0.tag() + &self.task + } + + /// Cancel this task. + #[cfg(not(target_os = "unknown"))] + pub async fn cancel(mut self) -> Option { + let handle = self.handle.take().unwrap(); + handle.cancel(); + handle.await + } + + /// Cancel this task. + #[cfg(target_arch = "wasm32")] + pub async fn cancel(mut self) -> Option { + let mut handle = self.handle.take().unwrap(); + handle.close(); + handle.await.ok() } } @@ -47,10 +71,11 @@ impl Future for JoinHandle { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match Pin::new(&mut self.0).poll(cx) { + match Pin::new(&mut self.handle.as_mut().unwrap()).poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(None) => panic!("cannot await the result of a panicked task"), - Poll::Ready(Some(val)) => Poll::Ready(val), + Poll::Ready(output) => { + Poll::Ready(output.expect("cannot await the result of a panicked task")) + } } } } diff --git a/src/task/mod.rs b/src/task/mod.rs index f738fca..ca0b92a 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -117,13 +117,16 @@ //! [`task_local!`]: ../macro.task_local.html //! [`with`]: struct.LocalKey.html#method.with -cfg_std! { +cfg_alloc! { #[doc(inline)] - pub use std::task::{Context, Poll, Waker}; - + pub use core::task::{Context, Poll, Waker}; pub use ready::ready; - pub use yield_now::yield_now; + mod ready; +} + +cfg_std! { + pub use yield_now::yield_now; mod yield_now; } @@ -135,26 +138,39 @@ cfg_default! { pub use task_id::TaskId; pub use join_handle::JoinHandle; pub use sleep::sleep; + #[cfg(not(target_os = "unknown"))] pub use spawn::spawn; pub use task_local::{AccessError, LocalKey}; - use builder::Runnable; - use task_local::LocalsMap; + pub(crate) use task_local::LocalsMap; + pub(crate) use task_locals_wrapper::TaskLocalsWrapper; mod block_on; mod builder; mod current; - mod executor; mod join_handle; mod sleep; + #[cfg(not(target_os = "unknown"))] mod spawn; + #[cfg(not(target_os = "unknown"))] mod spawn_blocking; mod task; mod task_id; mod task_local; + mod task_locals_wrapper; + #[cfg(not(target_os = "unknown"))] #[cfg(any(feature = "unstable", test))] pub use spawn_blocking::spawn_blocking; + #[cfg(not(target_os = "unknown"))] #[cfg(not(any(feature = "unstable", test)))] pub(crate) use spawn_blocking::spawn_blocking; } + +cfg_unstable! { + #[cfg(feature = "default")] + pub use spawn_local::spawn_local; + + #[cfg(feature = "default")] + mod spawn_local; +} diff --git a/src/task/spawn_blocking.rs b/src/task/spawn_blocking.rs index 578afa4..e9ed0c5 100644 --- a/src/task/spawn_blocking.rs +++ b/src/task/spawn_blocking.rs @@ -1,12 +1,4 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::thread; -use std::time::Duration; - -use crossbeam_channel::{unbounded, Receiver, Sender}; -use once_cell::sync::Lazy; - use crate::task::{JoinHandle, Task}; -use crate::utils::abort_on_panic; /// Spawns a blocking task. /// @@ -31,7 +23,8 @@ use crate::utils::abort_on_panic; /// /// task::spawn_blocking(|| { /// println!("long-running task here"); -/// }).await; +/// }) +/// .await; /// # /// # }) /// ``` @@ -42,80 +35,8 @@ where F: FnOnce() -> T + Send + 'static, T: Send + 'static, { - let schedule = |task| POOL.sender.send(task).unwrap(); - let (task, handle) = async_task::spawn(async { f() }, schedule, Task::new(None)); - task.schedule(); - JoinHandle::new(handle) -} - -type Runnable = async_task::Task; - -/// The number of sleeping worker threads. -static SLEEPING: AtomicUsize = AtomicUsize::new(0); - -struct Pool { - sender: Sender, - receiver: Receiver, -} - -static POOL: Lazy = Lazy::new(|| { - // Start a single worker thread waiting for the first task. - start_thread(); - - let (sender, receiver) = unbounded(); - Pool { sender, receiver } -}); - -fn start_thread() { - SLEEPING.fetch_add(1, Ordering::SeqCst); - let timeout = Duration::from_secs(1); - - thread::Builder::new() - .name("async-std/blocking".to_string()) - .spawn(move || { - loop { - let mut task = match POOL.receiver.recv_timeout(timeout) { - Ok(task) => task, - Err(_) => { - // Check whether this is the last sleeping thread. - if SLEEPING.fetch_sub(1, Ordering::SeqCst) == 1 { - // If so, then restart the thread to make sure there is always at least - // one sleeping thread. - if SLEEPING.compare_and_swap(0, 1, Ordering::SeqCst) == 0 { - continue; - } - } - - // Stop the thread. - return; - } - }; - - // If there are no sleeping threads, then start one to make sure there is always at - // least one sleeping thread. - if SLEEPING.fetch_sub(1, Ordering::SeqCst) == 1 { - start_thread(); - } - - loop { - // Run the task. - abort_on_panic(|| task.run()); - - // Try taking another task if there are any available. - task = match POOL.receiver.try_recv() { - Ok(task) => task, - Err(_) => break, - }; - } - - // If there is at least one sleeping thread, stop this thread instead of putting it - // to sleep. - if SLEEPING.load(Ordering::SeqCst) > 0 { - return; - } + once_cell::sync::Lazy::force(&crate::rt::RUNTIME); - SLEEPING.fetch_add(1, Ordering::SeqCst); - } - }) - .expect("cannot start a blocking thread"); + let handle = smol::Task::blocking(async move { f() }).into(); + JoinHandle::new(handle, Task::new(None)) } diff --git a/src/task/spawn_local.rs b/src/task/spawn_local.rs new file mode 100644 index 0000000..04da020 --- /dev/null +++ b/src/task/spawn_local.rs @@ -0,0 +1,28 @@ +use std::future::Future; + +use crate::task::{Builder, JoinHandle}; + +/// Spawns a task onto the thread-local executor. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// let handle = task::spawn_local(async { +/// 1 + 2 +/// }); +/// +/// assert_eq!(handle.await, 3); +/// # +/// # }) +/// ``` +pub fn spawn_local(future: F) -> JoinHandle +where + F: Future + 'static, + T: 'static, +{ + Builder::new().local(future).expect("cannot spawn task") +} diff --git a/src/task/task.rs b/src/task/task.rs index bcec2e0..eba99c7 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -1,74 +1,32 @@ -use std::cell::Cell; use std::fmt; -use std::mem::ManuallyDrop; -use std::ptr; -use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::Arc; -use crate::task::{LocalsMap, TaskId}; -use crate::utils::abort_on_panic; +use crate::task::TaskId; -thread_local! { - /// A pointer to the currently running task. - static CURRENT: Cell<*const Task> = Cell::new(ptr::null_mut()); -} - -/// The inner representation of a task handle. -struct Inner { +/// A handle to a task. +#[derive(Clone)] +pub struct Task { /// The task ID. id: TaskId, /// The optional task name. - name: Option>, - - /// The map holding task-local values. - locals: LocalsMap, -} - -impl Inner { - #[inline] - fn new(name: Option) -> Inner { - Inner { - id: TaskId::generate(), - name: name.map(String::into_boxed_str), - locals: LocalsMap::new(), - } - } + name: Option>, } -/// A handle to a task. -pub struct Task { - /// The inner representation. - /// - /// This pointer is lazily initialized on first use. In most cases, the inner representation is - /// never touched and therefore we don't allocate it unless it's really needed. - inner: AtomicPtr, -} - -unsafe impl Send for Task {} -unsafe impl Sync for Task {} - impl Task { /// Creates a new task handle. - /// - /// If the task is unnamed, the inner representation of the task will be lazily allocated on - /// demand. #[inline] - pub(crate) fn new(name: Option) -> Task { - let inner = match name { - None => AtomicPtr::default(), - Some(name) => { - let raw = Arc::into_raw(Arc::new(Inner::new(Some(name)))); - AtomicPtr::new(raw as *mut Inner) - } - }; - Task { inner } + pub(crate) fn new(name: Option>) -> Task { + Task { + id: TaskId::generate(), + name, + } } /// Gets the task's unique identifier. #[inline] pub fn id(&self) -> TaskId { - self.inner().id + self.id } /// Returns the name of this task. @@ -77,93 +35,7 @@ impl Task { /// /// [`Builder::name`]: struct.Builder.html#method.name pub fn name(&self) -> Option<&str> { - self.inner().name.as_ref().map(|s| &**s) - } - - /// Returns the map holding task-local values. - pub(crate) fn locals(&self) -> &LocalsMap { - &self.inner().locals - } - - /// Drops all task-local values. - /// - /// This method is only safe to call at the end of the task. - #[inline] - pub(crate) unsafe fn drop_locals(&self) { - let raw = self.inner.load(Ordering::Acquire); - if let Some(inner) = raw.as_mut() { - // Abort the process if dropping task-locals panics. - abort_on_panic(|| { - inner.locals.clear(); - }); - } - } - - /// Returns the inner representation, initializing it on first use. - fn inner(&self) -> &Inner { - loop { - let raw = self.inner.load(Ordering::Acquire); - if !raw.is_null() { - return unsafe { &*raw }; - } - - let new = Arc::into_raw(Arc::new(Inner::new(None))) as *mut Inner; - if self.inner.compare_and_swap(raw, new, Ordering::AcqRel) != raw { - unsafe { - drop(Arc::from_raw(new)); - } - } - } - } - - /// Set a reference to the current task. - pub(crate) unsafe fn set_current(task: *const Task, f: F) -> R - where - F: FnOnce() -> R, - { - CURRENT.with(|current| { - let old_task = current.replace(task); - defer! { - current.set(old_task); - } - f() - }) - } - - /// Gets a reference to the current task. - pub(crate) fn get_current(f: F) -> Option - where - F: FnOnce(&Task) -> R, - { - let res = CURRENT.try_with(|current| unsafe { current.get().as_ref().map(f) }); - match res { - Ok(Some(val)) => Some(val), - Ok(None) | Err(_) => None, - } - } -} - -impl Drop for Task { - fn drop(&mut self) { - // Deallocate the inner representation if it was initialized. - let raw = *self.inner.get_mut(); - if !raw.is_null() { - unsafe { - drop(Arc::from_raw(raw)); - } - } - } -} - -impl Clone for Task { - fn clone(&self) -> Task { - // We need to make sure the inner representation is initialized now so that this instance - // and the clone have raw pointers that point to the same `Arc`. - let arc = unsafe { ManuallyDrop::new(Arc::from_raw(self.inner())) }; - let raw = Arc::into_raw(Arc::clone(&arc)); - Task { - inner: AtomicPtr::new(raw as *mut Inner), - } + self.name.as_ref().map(|s| s.as_str()) } } diff --git a/src/task/task_id.rs b/src/task/task_id.rs index 67eee15..92c607c 100644 --- a/src/task/task_id.rs +++ b/src/task/task_id.rs @@ -1,5 +1,5 @@ use std::fmt; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::{AtomicUsize, Ordering}; /// A unique identifier for a task. /// @@ -13,15 +13,16 @@ use std::sync::atomic::{AtomicU64, Ordering}; /// }) /// ``` #[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)] -pub struct TaskId(pub(crate) u64); +pub struct TaskId(pub(crate) usize); impl TaskId { /// Generates a new `TaskId`. pub(crate) fn generate() -> TaskId { - static COUNTER: AtomicU64 = AtomicU64::new(1); + // TODO: find a good version to emulate u64 atomics on 32 bit systems. + static COUNTER: AtomicUsize = AtomicUsize::new(1); let id = COUNTER.fetch_add(1, Ordering::Relaxed); - if id > u64::max_value() / 2 { + if id > usize::max_value() / 2 { std::process::abort(); } TaskId(id) diff --git a/src/task/task_local.rs b/src/task/task_local.rs index 72e53d7..4e2ba83 100644 --- a/src/task/task_local.rs +++ b/src/task/task_local.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::fmt; use std::sync::atomic::{AtomicU32, Ordering}; -use crate::task::Task; +use crate::task::TaskLocalsWrapper; /// The key for accessing a task-local value. /// @@ -98,7 +98,7 @@ impl LocalKey { where F: FnOnce(&T) -> R, { - Task::get_current(|task| unsafe { + TaskLocalsWrapper::get_current(|task| unsafe { // Prepare the numeric key, initialization function, and the map of task-locals. let key = self.key(); let init = || Box::new((self.__init)()) as Box; diff --git a/src/task/task_locals_wrapper.rs b/src/task/task_locals_wrapper.rs new file mode 100644 index 0000000..2a7ddb7 --- /dev/null +++ b/src/task/task_locals_wrapper.rs @@ -0,0 +1,84 @@ +use std::cell::Cell; +use std::ptr; + +use crate::task::{LocalsMap, Task, TaskId}; +use crate::utils::abort_on_panic; + +thread_local! { + /// A pointer to the currently running task. + static CURRENT: Cell<*const TaskLocalsWrapper> = Cell::new(ptr::null_mut()); +} + +/// A wrapper to store task local data. +pub(crate) struct TaskLocalsWrapper { + /// The actual task details. + task: Task, + + /// The map holding task-local values. + locals: LocalsMap, +} + +impl TaskLocalsWrapper { + /// Creates a new task handle. + /// + /// If the task is unnamed, the inner representation of the task will be lazily allocated on + /// demand. + #[inline] + pub(crate) fn new(task: Task) -> Self { + Self { + task, + locals: LocalsMap::new(), + } + } + + /// Gets the task's unique identifier. + #[inline] + pub fn id(&self) -> TaskId { + self.task.id() + } + + /// Returns a reference to the inner `Task`. + pub(crate) fn task(&self) -> &Task { + &self.task + } + + /// Returns the map holding task-local values. + pub(crate) fn locals(&self) -> &LocalsMap { + &self.locals + } + + /// Set a reference to the current task. + pub(crate) unsafe fn set_current(task: *const TaskLocalsWrapper, f: F) -> R + where + F: FnOnce() -> R, + { + CURRENT.with(|current| { + let old_task = current.replace(task); + defer! { + current.set(old_task); + } + f() + }) + } + + /// Gets a reference to the current task. + pub(crate) fn get_current(f: F) -> Option + where + F: FnOnce(&TaskLocalsWrapper) -> R, + { + let res = CURRENT.try_with(|current| unsafe { current.get().as_ref().map(f) }); + match res { + Ok(Some(val)) => Some(val), + Ok(None) | Err(_) => None, + } + } +} + +impl Drop for TaskLocalsWrapper { + fn drop(&mut self) { + // Abort the process if dropping task-locals panics. + abort_on_panic(|| { + unsafe { self.locals.clear() }; + }); + } +} diff --git a/src/task/yield_now.rs b/src/task/yield_now.rs index 4030696..2b1fd0b 100644 --- a/src/task/yield_now.rs +++ b/src/task/yield_now.rs @@ -1,5 +1,5 @@ -use std::pin::Pin; use std::future::Future; +use std::pin::Pin; use crate::task::{Context, Poll}; diff --git a/src/unit/from_stream.rs b/src/unit/from_stream.rs index da216e2..7b78438 100644 --- a/src/unit/from_stream.rs +++ b/src/unit/from_stream.rs @@ -8,6 +8,6 @@ impl FromStream<()> for () { fn from_stream<'a, S: IntoStream + 'a>( stream: S, ) -> Pin + 'a>> { - Box::pin(stream.into_stream().for_each(|_| ())) + Box::pin(stream.into_stream().for_each(drop)) } } diff --git a/src/utils.rs b/src/utils.rs index ef50ed0..e064570 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +use alloc::string::String; + /// Calls a function and aborts if it panics. /// /// This is useful in unsafe code where we can't recover from panics. @@ -19,7 +21,7 @@ pub fn abort_on_panic(f: impl FnOnce() -> T) -> T { } /// Generates a random number in `0..n`. -#[cfg(any(feature = "unstable", feature = "default"))] +#[cfg(feature = "unstable")] pub fn random(n: u32) -> u32 { use std::cell::Cell; use std::num::Wrapping; @@ -57,6 +59,43 @@ pub(crate) trait Context { fn context(self, message: impl Fn() -> String) -> Self; } +#[cfg(all(not(target_os = "unknown"), feature = "default"))] +mod timer { + pub type Timer = smol::Timer; +} + +#[cfg(any( + all(target_arch = "wasm32", feature = "default"), + all(feature = "unstable", not(feature = "default")) +))] +mod timer { + use std::pin::Pin; + use std::task::Poll; + + #[derive(Debug)] + pub(crate) struct Timer(futures_timer::Delay); + + impl Timer { + pub(crate) fn after(dur: std::time::Duration) -> Self { + Timer(futures_timer::Delay::new(dur)) + } + } + + impl std::future::Future for Timer { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + match Pin::new(&mut self.0).poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(_) => Poll::Ready(()), + } + } + } +} + +#[cfg(any(feature = "unstable", feature = "default"))] +pub(crate) use timer::*; + /// Defers evaluation of a block of code until the end of the scope. #[cfg(feature = "default")] #[doc(hidden)] @@ -104,6 +143,7 @@ macro_rules! cfg_unstable_default { /// Declares Unix-specific items. #[doc(hidden)] +#[allow(unused_macros)] macro_rules! cfg_unix { ($($item:item)*) => { $( @@ -116,6 +156,7 @@ macro_rules! cfg_unix { /// Declares Windows-specific items. #[doc(hidden)] +#[allow(unused_macros)] macro_rules! cfg_windows { ($($item:item)*) => { $( @@ -128,6 +169,7 @@ macro_rules! cfg_windows { /// Declares items when the "docs" feature is enabled. #[doc(hidden)] +#[allow(unused_macros)] macro_rules! cfg_docs { ($($item:item)*) => { $( @@ -139,6 +181,7 @@ macro_rules! cfg_docs { /// Declares items when the "docs" feature is disabled. #[doc(hidden)] +#[allow(unused_macros)] macro_rules! cfg_not_docs { ($($item:item)*) => { $( @@ -160,6 +203,18 @@ macro_rules! cfg_std { } } +/// Declares no-std items. +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! cfg_alloc { + ($($item:item)*) => { + $( + #[cfg(feature = "alloc")] + $item + )* + } +} + /// Declares default items. #[allow(unused_macros)] #[doc(hidden)] @@ -180,6 +235,7 @@ macro_rules! cfg_default { /// /// Inside invocations of this macro, we write a definitions that looks similar to the final /// rendered docs, and the macro then generates all the boilerplate for us. +#[allow(unused_macros)] #[doc(hidden)] macro_rules! extension_trait { ( @@ -204,14 +260,14 @@ macro_rules! extension_trait { #[allow(dead_code)] mod owned { #[doc(hidden)] - pub struct ImplFuture(std::marker::PhantomData); + pub struct ImplFuture(core::marker::PhantomData); } // A fake `impl Future` type that borrows its environment. #[allow(dead_code)] mod borrowed { #[doc(hidden)] - pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>); + pub struct ImplFuture<'a, T>(core::marker::PhantomData<&'a T>); } // Render a fake trait combining the bodies of the base trait and the extension trait. @@ -238,11 +294,6 @@ macro_rules! extension_trait { $(#[cfg(feature = "docs")] $imp)* }; - // Optimization: expand `$head` eagerly before starting a new method definition. - (@ext ($($head:tt)*) #[doc = $d:literal] $($tail:tt)*) => { - $($head)* extension_trait!(@ext (#[doc = $d]) $($tail)*); - }; - // Parse the return type in an extension method. (@doc ($($head:tt)*) -> impl Future $(+ $lt:lifetime)? [$f:ty] $($tail:tt)*) => { extension_trait!(@doc ($($head)* -> owned::ImplFuture<$out>) $($tail)*); diff --git a/tests/addr.rs b/tests/addr.rs index aada557..fcd5aa1 100644 --- a/tests/addr.rs +++ b/tests/addr.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "unknown"))] + use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use async_std::net::ToSocketAddrs; diff --git a/tests/block_on.rs b/tests/block_on.rs index c422d06..4c26480 100644 --- a/tests/block_on.rs +++ b/tests/block_on.rs @@ -1,16 +1,63 @@ -use async_std::task; +#![cfg(not(target_os = "unknown"))] + +use async_std::{future::ready, task::block_on}; #[test] fn smoke() { - let res = task::block_on(async { 1 + 2 }); + let res = block_on(async { 1 + 2 }); assert_eq!(res, 3); } #[test] #[should_panic = "boom"] fn panic() { - task::block_on(async { + block_on(async { // This panic should get propagated into the parent thread. panic!("boom"); }); } + +#[cfg(feature = "unstable")] +#[test] +fn nested_block_on_local() { + use async_std::task::spawn_local; + + let x = block_on(async { + let a = block_on(async { block_on(async { ready(3).await }) }); + let b = spawn_local(async { block_on(async { ready(2).await }) }).await; + let c = block_on(async { block_on(async { ready(1).await }) }); + a + b + c + }); + + assert_eq!(x, 3 + 2 + 1); + + let y = block_on(async { + let a = block_on(async { block_on(async { ready(3).await }) }); + let b = spawn_local(async { block_on(async { ready(2).await }) }).await; + let c = block_on(async { block_on(async { ready(1).await }) }); + a + b + c + }); + + assert_eq!(y, 3 + 2 + 1); +} + +#[test] +fn nested_block_on() { + let x = block_on(async { + let a = block_on(async { block_on(async { ready(3).await }) }); + let b = block_on(async { block_on(async { ready(2).await }) }); + let c = block_on(async { block_on(async { ready(1).await }) }); + a + b + c + }); + + assert_eq!(x, 3 + 2 + 1); + + let y = block_on(async { + let a = block_on(async { block_on(async { ready(3).await }) }); + let b = block_on(async { block_on(async { ready(2).await }) }); + let c = block_on(async { block_on(async { ready(1).await }) }); + a + b + c + }); + + assert_eq!(y, 3 + 2 + 1); +} diff --git a/tests/buf_writer.rs b/tests/buf_writer.rs index 5df90e0..442cf8a 100644 --- a/tests/buf_writer.rs +++ b/tests/buf_writer.rs @@ -2,15 +2,19 @@ use async_std::io::{self, BufWriter, SeekFrom}; use async_std::prelude::*; use async_std::task; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_buffered_writer() { #![allow(clippy::cognitive_complexity)] task::block_on(async { - let inner = Vec::new(); - let mut writer = BufWriter::with_capacity(2, inner); + let inner: Vec = Vec::new(); + let mut writer = BufWriter::>::with_capacity(2, inner); writer.write(&[0, 1]).await.unwrap(); - assert_eq!(writer.buffer(), []); + assert!(writer.buffer().is_empty()); assert_eq!(*writer.get_ref(), [0, 1]); writer.write(&[2]).await.unwrap(); @@ -22,7 +26,7 @@ fn test_buffered_writer() { assert_eq!(*writer.get_ref(), [0, 1]); writer.flush().await.unwrap(); - assert_eq!(writer.buffer(), []); + assert!(writer.buffer().is_empty()); assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); writer.write(&[4]).await.unwrap(); @@ -35,31 +39,33 @@ fn test_buffered_writer() { assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5]); writer.write(&[7, 8]).await.unwrap(); - assert_eq!(writer.buffer(), []); + assert!(writer.buffer().is_empty()); assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); writer.write(&[9, 10, 11]).await.unwrap(); - assert_eq!(writer.buffer(), []); + assert!(writer.buffer().is_empty()); assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); writer.flush().await.unwrap(); - assert_eq!(writer.buffer(), []); + assert!(writer.buffer().is_empty()); assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); }) } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_buffered_writer_inner_into_inner_flushes() { task::block_on(async { - let mut w = BufWriter::with_capacity(3, Vec::new()); + let mut w = BufWriter::with_capacity(3, Vec::::new()); w.write(&[0, 1]).await.unwrap(); - assert_eq!(*w.get_ref(), []); + assert!(w.get_ref().is_empty()); let w = w.into_inner().await.unwrap(); assert_eq!(w, [0, 1]); }) } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_buffered_writer_seek() { task::block_on(async { let mut w = BufWriter::with_capacity(3, io::Cursor::new(Vec::new())); diff --git a/tests/channel.rs b/tests/channel.rs index 34bd888..a218ea2 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -6,25 +6,34 @@ use std::time::Duration; use async_std::sync::channel; use async_std::task; -use rand::{thread_rng, Rng}; +use rand::{Rng, SeedableRng}; + +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); fn ms(ms: u64) -> Duration { Duration::from_millis(ms) } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn smoke() { task::block_on(async { let (s, r) = channel(1); s.send(7).await; - assert_eq!(r.recv().await, Some(7)); + assert_eq!(r.recv().await.unwrap(), 7); s.send(8).await; - assert_eq!(r.recv().await, Some(8)); + assert_eq!(r.recv().await.unwrap(), 8); drop(s); - assert_eq!(r.recv().await, None); + assert!(r.recv().await.is_err()); }); task::block_on(async { @@ -35,6 +44,7 @@ fn smoke() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn capacity() { for i in 1..10 { let (s, r) = channel::<()>(i); @@ -44,6 +54,7 @@ fn capacity() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn len_empty_full() { #![allow(clippy::cognitive_complexity)] task::block_on(async { @@ -74,7 +85,7 @@ fn len_empty_full() { assert_eq!(r.is_empty(), false); assert_eq!(r.is_full(), true); - r.recv().await; + let _ = r.recv().await; assert_eq!(s.len(), 1); assert_eq!(s.is_empty(), false); @@ -86,17 +97,18 @@ fn len_empty_full() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn recv() { task::block_on(async { let (s, r) = channel(100); - task::spawn(async move { - assert_eq!(r.recv().await, Some(7)); + spawn(async move { + assert_eq!(r.recv().await.unwrap(), 7); task::sleep(ms(1000)).await; - assert_eq!(r.recv().await, Some(8)); + assert_eq!(r.recv().await.unwrap(), 8); task::sleep(ms(1000)).await; - assert_eq!(r.recv().await, Some(9)); - assert_eq!(r.recv().await, None); + assert_eq!(r.recv().await.unwrap(), 9); + assert!(r.recv().await.is_err()); }); task::sleep(ms(1500)).await; @@ -107,11 +119,12 @@ fn recv() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn send() { task::block_on(async { let (s, r) = channel(1); - task::spawn(async move { + spawn(async move { s.send(7).await; task::sleep(ms(1000)).await; s.send(8).await; @@ -122,13 +135,14 @@ fn send() { }); task::sleep(ms(1500)).await; - assert_eq!(r.recv().await, Some(7)); - assert_eq!(r.recv().await, Some(8)); - assert_eq!(r.recv().await, Some(9)); + assert_eq!(r.recv().await.unwrap(), 7); + assert_eq!(r.recv().await.unwrap(), 8); + assert_eq!(r.recv().await.unwrap(), 9); }) } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn recv_after_disconnect() { task::block_on(async { let (s, r) = channel(100); @@ -139,14 +153,15 @@ fn recv_after_disconnect() { drop(s); - assert_eq!(r.recv().await, Some(1)); - assert_eq!(r.recv().await, Some(2)); - assert_eq!(r.recv().await, Some(3)); - assert_eq!(r.recv().await, None); + assert_eq!(r.recv().await.unwrap(), 1); + assert_eq!(r.recv().await.unwrap(), 2); + assert_eq!(r.recv().await.unwrap(), 3); + assert!(r.recv().await.is_err()); }) } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn len() { const COUNT: usize = 25_000; const CAP: usize = 1000; @@ -164,7 +179,7 @@ fn len() { } for i in 0..50 { - r.recv().await; + let _ = r.recv().await; assert_eq!(r.len(), 50 - i - 1); } } @@ -184,11 +199,11 @@ fn len() { assert_eq!(s.len(), 0); assert_eq!(r.len(), 0); - let child = task::spawn({ + let child = spawn({ let r = r.clone(); async move { for i in 0..COUNT { - assert_eq!(r.recv().await, Some(i)); + assert_eq!(r.recv().await.unwrap(), i); let len = r.len(); assert!(len <= CAP); } @@ -209,12 +224,13 @@ fn len() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn disconnect_wakes_receiver() { task::block_on(async { let (s, r) = channel::<()>(1); - let child = task::spawn(async move { - assert_eq!(r.recv().await, None); + let child = spawn(async move { + assert!(r.recv().await.is_err()); }); task::sleep(ms(1000)).await; @@ -225,17 +241,18 @@ fn disconnect_wakes_receiver() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn spsc() { const COUNT: usize = 100_000; task::block_on(async { let (s, r) = channel(3); - let child = task::spawn(async move { + let child = spawn(async move { for i in 0..COUNT { - assert_eq!(r.recv().await, Some(i)); + assert_eq!(r.recv().await.unwrap(), i); } - assert_eq!(r.recv().await, None); + assert!(r.recv().await.is_err()); }); for i in 0..COUNT { @@ -248,6 +265,7 @@ fn spsc() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn mpmc() { const COUNT: usize = 25_000; const TASKS: usize = 4; @@ -262,7 +280,7 @@ fn mpmc() { for _ in 0..TASKS { let r = r.clone(); let v = v.clone(); - tasks.push(task::spawn(async move { + tasks.push(spawn(async move { for _ in 0..COUNT { let n = r.recv().await.unwrap(); v[n].fetch_add(1, Ordering::SeqCst); @@ -272,7 +290,7 @@ fn mpmc() { for _ in 0..TASKS { let s = s.clone(); - tasks.push(task::spawn(async move { + tasks.push(spawn(async move { for i in 0..COUNT { s.send(i).await; } @@ -290,6 +308,7 @@ fn mpmc() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn oneshot() { const COUNT: usize = 10_000; @@ -297,8 +316,8 @@ fn oneshot() { for _ in 0..COUNT { let (s, r) = channel(1); - let c1 = task::spawn(async move { r.recv().await.unwrap() }); - let c2 = task::spawn(async move { s.send(0).await }); + let c1 = spawn(async move { r.recv().await.unwrap() }); + let c2 = spawn(async move { s.send(0).await }); c1.await; c2.await; @@ -307,6 +326,7 @@ fn oneshot() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn drops() { const RUNS: usize = 100; @@ -321,17 +341,16 @@ fn drops() { } } - let mut rng = thread_rng(); - for _ in 0..RUNS { - task::block_on(async { + let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0); + task::block_on(async move { let steps = rng.gen_range(0, 10_000); let additional = rng.gen_range(0, 50); DROPS.store(0, Ordering::SeqCst); let (s, r) = channel::(50); - let child = task::spawn({ + let child = spawn({ let r = r.clone(); async move { for _ in 0..steps { diff --git a/tests/condvar.rs b/tests/condvar.rs new file mode 100644 index 0000000..b5ec12a --- /dev/null +++ b/tests/condvar.rs @@ -0,0 +1,103 @@ +#![cfg(feature = "unstable")] +use std::sync::Arc; +use std::time::Duration; + +use async_std::sync::{Condvar, Mutex}; +use async_std::task::{self, JoinHandle}; + +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn wait_timeout_with_lock() { + task::block_on(async { + let pair = Arc::new((Mutex::new(false), Condvar::new())); + let pair2 = pair.clone(); + + spawn(async move { + let (m, c) = &*pair2; + let _g = m.lock().await; + task::sleep(Duration::from_millis(200)).await; + c.notify_one(); + }); + + let (m, c) = &*pair; + let (_, wait_result) = c + .wait_timeout(m.lock().await, Duration::from_millis(50)) + .await; + assert!(wait_result.timed_out()); + }) +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn wait_timeout_without_lock() { + task::block_on(async { + let m = Mutex::new(false); + let c = Condvar::new(); + + let (_, wait_result) = c + .wait_timeout(m.lock().await, Duration::from_millis(10)) + .await; + assert!(wait_result.timed_out()); + }) +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn wait_timeout_until_timed_out() { + task::block_on(async { + let m = Mutex::new(false); + let c = Condvar::new(); + + let (_, wait_result) = c + .wait_timeout_until(m.lock().await, Duration::from_millis(100), |&mut started| { + started + }) + .await; + assert!(wait_result.timed_out()); + }) +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn notify_all() { + task::block_on(async { + let mut tasks: Vec> = Vec::new(); + let pair = Arc::new((Mutex::new(0u32), Condvar::new())); + + for _ in 0..10 { + let pair = pair.clone(); + tasks.push(spawn(async move { + let (m, c) = &*pair; + let mut count = m.lock().await; + while *count == 0 { + count = c.wait(count).await; + } + *count += 1; + })); + } + + // Give some time for tasks to start up + task::sleep(Duration::from_millis(50)).await; + + let (m, c) = &*pair; + { + let mut count = m.lock().await; + *count += 1; + c.notify_all(); + } + + for t in tasks { + t.await; + } + let count = m.lock().await; + assert_eq!(11, *count); + }) +} diff --git a/tests/io_timeout.rs b/tests/io_timeout.rs index 85a17ab..3711506 100644 --- a/tests/io_timeout.rs +++ b/tests/io_timeout.rs @@ -5,6 +5,14 @@ use async_std::task; #[test] #[should_panic(expected = "timed out")] +#[cfg(not(any( + target_os = "unknown", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "x86", +)))] // stdin tests fail when running through cross fn io_timeout_timedout() { task::block_on(async { io::timeout(Duration::from_secs(1), async { diff --git a/tests/mutex.rs b/tests/mutex.rs index fd1c07b..76f42e2 100644 --- a/tests/mutex.rs +++ b/tests/mutex.rs @@ -5,7 +5,16 @@ use async_std::sync::Mutex; use async_std::task; use futures::channel::mpsc; +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn smoke() { task::block_on(async { let m = Mutex::new(()); @@ -15,18 +24,21 @@ fn smoke() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn try_lock() { let m = Mutex::new(()); *m.try_lock().unwrap() = (); } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn into_inner() { let m = Mutex::new(10); assert_eq!(m.into_inner(), 10); } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn get_mut() { let mut m = Mutex::new(10); *m.get_mut() = 20; @@ -34,6 +46,7 @@ fn get_mut() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn contention() { task::block_on(async { let (tx, mut rx) = mpsc::unbounded(); @@ -42,22 +55,29 @@ fn contention() { let mutex = Arc::new(Mutex::new(0)); let num_tasks = 10000; + let mut handles = Vec::new(); for _ in 0..num_tasks { let tx = tx.clone(); let mutex = mutex.clone(); - task::spawn(async move { + handles.push(spawn(async move { let mut lock = mutex.lock().await; *lock += 1; tx.unbounded_send(()).unwrap(); drop(lock); - }); + })); } for _ in 0..num_tasks { rx.next().await.unwrap(); } + for handle in handles.into_iter() { + handle.await; + } + + dbg!("wait"); + let lock = mutex.lock().await; assert_eq!(num_tasks, *lock); }); diff --git a/tests/rwlock.rs b/tests/rwlock.rs index 370dcb9..1d33a45 100644 --- a/tests/rwlock.rs +++ b/tests/rwlock.rs @@ -10,6 +10,14 @@ use async_std::sync::RwLock; use async_std::task; use futures::channel::mpsc; +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + /// Generates a random number in `0..n`. pub fn random(n: u32) -> u32 { thread_local! { @@ -35,6 +43,7 @@ pub fn random(n: u32) -> u32 { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn smoke() { task::block_on(async { let lock = RwLock::new(()); @@ -46,6 +55,7 @@ fn smoke() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn try_write() { task::block_on(async { let lock = RwLock::new(0isize); @@ -56,12 +66,14 @@ fn try_write() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn into_inner() { let lock = RwLock::new(10); assert_eq!(lock.into_inner(), 10); } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn into_inner_and_drop() { struct Counter(Arc); @@ -84,6 +96,7 @@ fn into_inner_and_drop() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn get_mut() { let mut lock = RwLock::new(10); *lock.get_mut() = 20; @@ -91,6 +104,7 @@ fn get_mut() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn contention() { const N: u32 = 10; const M: usize = 1000; @@ -104,7 +118,7 @@ fn contention() { let tx = tx.clone(); let rw = rw.clone(); - task::spawn(async move { + spawn(async move { for _ in 0..M { if random(N) == 0 { drop(rw.write().await); @@ -116,7 +130,7 @@ fn contention() { }); } - task::block_on(async { + task::block_on(async move { for _ in 0..N { rx.next().await.unwrap(); } @@ -124,6 +138,7 @@ fn contention() { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn writer_and_readers() { #[derive(Default)] struct Yield(Cell); @@ -146,7 +161,7 @@ fn writer_and_readers() { let (tx, mut rx) = mpsc::unbounded(); // Spawn a writer task. - task::spawn({ + spawn({ let lock = lock.clone(); async move { let mut lock = lock.write().await; @@ -164,13 +179,13 @@ fn writer_and_readers() { let mut readers = Vec::new(); for _ in 0..5 { let lock = lock.clone(); - readers.push(task::spawn(async move { + readers.push(spawn(async move { let lock = lock.read().await; assert!(*lock >= 0); })); } - task::block_on(async { + task::block_on(async move { // Wait for readers to pass their asserts. for r in readers { r.await; diff --git a/tests/stream.rs b/tests/stream.rs index fdfa23c..47ff228 100644 --- a/tests/stream.rs +++ b/tests/stream.rs @@ -10,14 +10,23 @@ use async_std::stream; use async_std::sync::channel; use async_std::task; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] /// Checks that streams are merged fully even if one of the components /// experiences delay. fn merging_delayed_streams_work() { let (sender, receiver) = channel::(10); let mut s = receiver.merge(stream::empty()); - let t = task::spawn(async move { + let t = spawn(async move { let mut xs = Vec::new(); while let Some(x) = s.next().await { xs.push(x); @@ -36,7 +45,7 @@ fn merging_delayed_streams_work() { let (sender, receiver) = channel::(10); let mut s = stream::empty().merge(receiver); - let t = task::spawn(async move { + let t = spawn(async move { let mut xs = Vec::new(); while let Some(x) = s.next().await { xs.push(x); @@ -87,18 +96,19 @@ fn explode(s: S) -> Explode { } #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn merge_works_with_unfused_streams() { let s1 = explode(stream::once(92)); let s2 = explode(stream::once(92)); let mut s = s1.merge(s2); - let xs = task::block_on(async move { + + task::block_on(async move { let mut xs = Vec::new(); while let Some(x) = s.next().await { xs.push(x) } - xs + assert_eq!(xs, vec![92, 92]); }); - assert_eq!(xs, vec![92, 92]); } struct S(T); diff --git a/tests/task_local.rs b/tests/task_local.rs index 813185c..b5345fe 100644 --- a/tests/task_local.rs +++ b/tests/task_local.rs @@ -3,7 +3,16 @@ use std::sync::atomic::{AtomicBool, Ordering}; use async_std::task; use async_std::task_local; +#[cfg(not(target_os = "unknown"))] +use async_std::task::spawn; +#[cfg(target_os = "unknown")] +use async_std::task::spawn_local as spawn; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + #[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn drop_local() { static DROP_LOCAL: AtomicBool = AtomicBool::new(false); @@ -20,7 +29,7 @@ fn drop_local() { } // Spawn a task that just touches its task-local. - let handle = task::spawn(async { + let handle = spawn(async { LOCAL.with(|_| ()); }); let task = handle.task().clone(); diff --git a/tests/tcp.rs b/tests/tcp.rs index 00fa3a0..f21737e 100644 --- a/tests/tcp.rs +++ b/tests/tcp.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "unknown"))] + use async_std::io; use async_std::net::{TcpListener, TcpStream}; use async_std::prelude::*; @@ -94,3 +96,25 @@ fn smoke_async_stream_to_std_listener() -> io::Result<()> { Ok(()) } + +#[test] +fn cloned_streams() -> io::Result<()> { + task::block_on(async { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let addr = listener.local_addr()?; + + let mut stream = TcpStream::connect(&addr).await?; + let mut cloned_stream = stream.clone(); + let mut incoming = listener.incoming(); + let mut write_stream = incoming.next().await.unwrap()?; + write_stream.write_all(b"Each your doing").await?; + + let mut buf = [0; 15]; + stream.read_exact(&mut buf[..8]).await?; + cloned_stream.read_exact(&mut buf[8..]).await?; + + assert_eq!(&buf[..15], b"Each your doing"); + + Ok(()) + }) +} diff --git a/tests/timeout.rs b/tests/timeout.rs new file mode 100644 index 0000000..e09acdf --- /dev/null +++ b/tests/timeout.rs @@ -0,0 +1,26 @@ +use std::time::Duration; + +use async_std::future::timeout; +use async_std::task; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn timeout_future_many() { + task::block_on(async { + let futures = (0..100) + .map(|i| { + timeout(Duration::from_millis(i * 20), async move { + task::sleep(Duration::from_millis(i)).await; + Ok::<(), async_std::future::TimeoutError>(()) + }) + }) + .collect::>(); + + for future in futures { + future.await.unwrap().unwrap(); + } + }); +} diff --git a/tests/udp.rs b/tests/udp.rs index 319dc74..15404f8 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "unknown"))] + use async_std::io; use async_std::net::UdpSocket; use async_std::task; diff --git a/tests/uds.rs b/tests/uds.rs index 3ab4d6b..d081bda 100644 --- a/tests/uds.rs +++ b/tests/uds.rs @@ -1,4 +1,4 @@ -#![cfg(unix)] +#![cfg(all(unix, not(target_os = "unknown")))] use async_std::io; use async_std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; @@ -94,3 +94,27 @@ async fn ping_pong_client(socket: &std::path::PathBuf, iterations: u32) -> std:: } Ok(()) } + +#[test] +fn uds_clone() -> io::Result<()> { + task::block_on(async { + let tmp_dir = TempDir::new("socket_ping_pong").expect("Temp dir not created"); + let sock_path = tmp_dir.as_ref().join("sock"); + let input = UnixListener::bind(&sock_path).await?; + + let mut writer = UnixStream::connect(&sock_path).await?; + let mut reader = input.incoming().next().await.unwrap()?; + + writer.write(b"original").await.unwrap(); + let mut original_buf = [0; 8]; + reader.read(&mut original_buf).await?; + assert_eq!(&original_buf, b"original"); + + writer.clone().write(b"clone").await.unwrap(); + let mut clone_buf = [0; 5]; + reader.clone().read(&mut clone_buf).await?; + assert_eq!(&clone_buf, b"clone"); + + Ok(()) + }) +} diff --git a/tests/verbose_errors.rs b/tests/verbose_errors.rs index 17d4261..2876183 100644 --- a/tests/verbose_errors.rs +++ b/tests/verbose_errors.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_os = "unknown"))] + use async_std::{fs, io, net::ToSocketAddrs, task}; #[test] diff --git a/wasm-test.sh b/wasm-test.sh new file mode 100755 index 0000000..3d8be9f --- /dev/null +++ b/wasm-test.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +wasm-pack test --chrome --headless -- --features unstable --test buf_writer +wasm-pack test --chrome --headless -- --features unstable --test channel +wasm-pack test --chrome --headless -- --features unstable --test condvar +wasm-pack test --chrome --headless -- --features unstable --test mutex +wasm-pack test --chrome --headless -- --features unstable --test rwlock +wasm-pack test --chrome --headless -- --features unstable --test stream +wasm-pack test --chrome --headless -- --features unstable --test task_local +wasm-pack test --chrome --headless -- --features unstable --test timeout