Compare commits

...
Author SHA1 Message Date
Carl Lerche 998a125717 chore: prepare Tokio 1.7.2 release 2021-07-06 14:35:36 -07:00
Carl Lerche cbfdc9d69e Merge branch 'tokio-1.6.x' into merge-1.6.3 2021-07-06 14:34:34 -07:00
Carl Lerche 08337c5f79 chore: prepare Tokio v1.6.3 2021-07-06 13:45:47 -07:00
Carl Lerche 87f4969fbc Merge branch 'tokio-1.5.x' into merge-1.5.x 2021-07-06 13:44:11 -07:00
Alice Ryhl e385108920 rt: remotely abort tasks on JoinHandle::abort (#3934) 2021-07-06 13:41:25 -07:00
Carl Lerche 7601dc6d2a rt: avoid early task shutdown (#3870)
Tokio 1.7.0 introduced a change intended to eagerly shutdown newly
spawned tasks if the runtime is in the process of shutting down.
However, it introduced a bug where already spawned tasks could be
shutdown too early, resulting in the potential introduction of deadlocks
if tasks acquired mutexes in drop handlers.

Fixes #3869
2021-06-18 16:32:53 -07:00
Alice Ryhl 34c6a26c01 chore: prepare Tokio v1.7.0 (#3863) 2021-06-15 19:39:33 +02:00
John-John TedroandAlice Ryhl 97e7830364 net: provide NamedPipe{Client, Server} types and builders (#3760)
This builds on https://github.com/tokio-rs/mio/pull/1351 and introduces the
tokio::net::windows::named_pipe module which provides low level types for
building and communicating asynchronously over windows named pipes.

Named pipes require the `net` feature flag to be enabled on Windows.

Co-authored-by: Alice Ryhl <[email protected]>
2021-06-15 08:13:31 +02:00
Carl Lerche 606206ecad Merge branch 'tokio-1.6.x' into merge-1.6.x 2021-06-14 17:51:30 -07:00
Carl Lerche dfe4013ff2 chore: prepare Tokio 1.6.2 release (#3859) 2021-06-14 17:50:34 -07:00
Carl Lerche 18779aa2e2 time: fix time::advance() with sub-ms durations (#3852)
Instead of using sleep in time::advance, this fixes the root of the issue. When futures passed
to `Runtime::block_on` are woken, it bypassed all the machinery around advancing time. By
intercepting wakes in the time driver, we know when the block_on task is woken and skip
advancing time in that case.

Fixes #3837
2021-06-14 16:36:04 -07:00
Alan Somers 2c24a028f6 chore: re-enable test_socket_pair on FreeBSD (#3844)
It was disabled due to an OS bug that has been fixed on all currently
supported releases of FreeBSD
2021-06-14 13:14:35 -07:00
Sean McArthur f55b77aadd tracing: emit waker op as str instead as Debug (#3853) 2021-06-14 10:37:24 -07:00
Yotam Ofek 21de476ae7 sync: export sync::notify::Notified future publicly (#3840) 2021-06-14 10:05:34 +02:00
Alice Ryhl cb147a2b3f sync: deprecate mpsc::RecvError (#3833) 2021-06-11 11:01:34 +02:00
Bart Pelle f759240254 chore: update version in README.md (#3851) 2021-06-11 11:01:22 +02:00
AtulJatia 2e44cd29df net: TcpSocket from std::net::TcpStream (#3838) 2021-06-11 10:58:58 +02:00
Oğuz Bilgener 2ab1fb00a9 sync: add examples to Semaphore (#3808) 2021-06-11 10:46:16 +02:00
Evan Mesterhazy d16e50639a doc: clarify EOF condition for AsyncReadExt::read_buf (#3850) 2021-06-09 15:00:32 +02:00
Sean McArthur e7d74b3119 tracing: instrument task wakers (#3836)
## Motivation

In support of tokio-rs/console#37, we want to understand when a specific task's waker has been interacted with, such as when it is awoken, or if it's forgotten (not cloned), etc.

## Solution

When the tracing feature is enabled, a super trait of Future (InstrumentedFuture) is implemented for Instrumented<F> that allows grabbing the task's ID (well, its span ID), and stores that in the raw task trailer. The waker vtable then emits events and includes that ID.
2021-06-08 14:50:40 -07:00
Tony Han d101feac50 runtime: fix typo in task state doc (#3835) 2021-06-04 22:31:04 +02:00
Taiki Endo 9d8b37d51a macros: suppress clippy::default_numeric_fallback lint in generated code (#3831) 2021-06-02 18:16:23 +09:00
James Hallowell d4c89758fc sync: fix spelling mistake in mpsc::Sender docs (#3828) 2021-06-01 09:56:34 +02:00
Taiki Endo 932be12481 ci: ignore private crates in minimal-versions check (#3827) 2021-05-31 23:39:06 +09:00
Edward Shen bdd6765016 doc: clarify limits on return values of AsyncWrite::poll_write (#3820) 2021-05-31 19:55:55 +09:00
Carl Lerche 8f6d8b25bf chore: add FUNDING.yml (#3824)
Reference Tokio sponsorship
2021-05-28 13:55:16 -07:00
Carl Lerche eb7aee980c Merge branch 'tokio-1.6.x' into merge-1.6.1 2021-05-28 10:04:08 -07:00
Alice Ryhl 1baea398c4 chore: prepare Tokio v1.6.1 2021-05-28 09:59:28 -07:00
Alice Ryhl 3fbcf1ba50 Revert "fs: try doing a non-blocking read before punting to the threadpool (#3518)"
This reverts commit 39706b198c.
2021-05-28 09:59:28 -07:00
j-vanderstoep 21264f1d33 tcp: assign an unused port (#3817) 2021-05-28 09:03:27 +02:00
Rafael Bachmann 81ee3d202a doc: fix minor doc typo (stray backtick) (#3814) 2021-05-26 14:23:39 +02:00
Alice Ryhl a39e6c2439 runtime: immediately drop new task when runtime is shut down (#3752) 2021-05-26 10:24:56 +02:00
Petros Angelatos 4abeca7bc5 io: impl AsyncSeek for BufStream (#3810)
Signed-off-by: Petros Angelatos <[email protected]>
2021-05-26 07:11:47 +09:00
GITSRC 5ed84e1cd8 doc: update README.md (#3806) 2021-05-21 09:55:53 +02:00
Lucio Franco 44a070d1b4 doc: add missing backtick (#3799) 2021-05-19 10:14:08 +02:00
Aaron Taner ce9ca45c92 doc: fix invalid #[doc(inline)] warnings on latest nightly. (#3788)
This commit fixed issue #3787 by removing [doc(inline)] from
macro `cfg_macros` and added proper #[doc(inline)] attributes
to `pub use` items inside `cfg_macros` calls.

It's probably not `cfg_macros`s responsibility to inlining public
macros, though it's conveninent to do so. Notice that in lib.rs:

cfg_macros! {
    /// Implementation detail of the `select!` macro. This macro is **not**
    /// intended to be used as part of the public API and is permitted to
    /// change.
    #[doc(hidden)]
    pub use tokio_macros::select_priv_declare_output_enum;

    ...
}

`#[doc(hidden)]` and `#[doc(inline)]` are conflict with each other
in the sense of correctness.

Fixes: #3787
2021-05-18 01:28:17 +09:00
Folyd f3ed064a26 chore: update *::{max, min}_value() to const *::MAX and *::MIN respectively (#3794) 2021-05-17 12:31:58 +02:00
Sören Meier 652f0ae728 sync: add receiver_count to watch::Sender (#3729) 2021-05-15 11:56:24 +02:00
Alice Ryhl ff9b0ef7ca chore: prepare tokio-test v0.4.2 (#3786) 2021-05-14 18:24:13 +02:00
Alice Ryhl aaa150d211 chore: prepare tokio-stream v0.1.6 (#3785) 2021-05-14 18:22:44 +02:00
Alice Ryhl deb1f98125 chore: prepare tokio-util v0.6.7 (#3784) 2021-05-14 18:22:08 +02:00
Alice Ryhl 3a659c47c3 chore: prepare tokio-mcaros v1.2.0 (#3783) 2021-05-14 18:20:17 +02:00
Alice Ryhl 580dc9594c chore: prepare Tokio v1.6.0 (#3782) 2021-05-14 18:19:10 +02:00
Taiki Endo 8c395dfe61 io: impl AsyncSeek for BufReader/BufWriter/BufStream (#3491) 2021-05-14 19:32:00 +09:00
Ivan Petkov e188e99ca3 process: avoid redundant effort to reap orphan processes (#3743) 2021-05-14 10:21:21 +02:00
Kestrer 0b93bd511d net: support non-blocking vectored I/O (#3761) 2021-05-14 09:38:01 +02:00
Alice Ryhl d846bf24b1 sync: Barrier doc should use task, not thread (#3780) 2021-05-13 10:14:10 +02:00
Taiki Endo 9ff7d8c352 net: hide net::unix::datagram module from docs (#3775) 2021-05-10 04:35:09 +09:00
Taiki Endo 8324317005 doc: fix doc-cfg on io::duplex, io::copy_bidirectional, and task::unconstrained (#3773) 2021-05-10 04:03:42 +09:00
Taiki Endo 7207e3ca43 signal: use std::os::raw::c_int instead of libc::c_int on public API (#3774) 2021-05-10 02:53:57 +09:00
Taiki Endo 312321cbd3 chore: enable syntax highlighting for toml in readme 2021-05-09 16:21:36 +02:00
Taiki Endo 17c7ce616c benches: fix build error (#3769) 2021-05-09 22:26:20 +09:00
Taiki Endo f9ce18a524 macros: improve diagnostics on type mismatch (#3766) 2021-05-09 18:58:05 +09:00
Chris Moore 05c3cf3a0a util: impl AsRawFd/AsRawHandle for Compat<T> (#3765) 2021-05-08 12:55:04 +02:00
John-John Tedro 2c5dc83019 cargo: path dependencies for all tokio things (#3764) 2021-05-08 12:54:30 +02:00
sb64 6b9bdd5ca2 chore: fix CI for Rust 1.52 (#3758) 2021-05-06 22:26:27 +02:00
Stefan d4075a4457 io: wake pending writers on DuplexStream close (#3756) 2021-05-06 18:30:39 +02:00
Stefan Sydow 177522cd43 benchmark: add file reading benchmarks (#3013) 2021-05-05 21:49:00 +02:00
Alice Ryhl 7ac341b526 task: update documentation on block_in_place (#3753) 2021-05-05 21:44:02 +02:00
Taiki Endo c4b6b130f3 chore: bump nightly to nightly-2021-04-25 (#3754) 2021-05-05 17:39:17 +02:00
Simon Lindholm 6845a93cbf sync: preserve permit state in notify_waiters (#3660) 2021-05-05 15:20:24 +02:00
Russell Cohen 8ef39dfb22 tokio-test: add assert_elapsed macro (#3728) 2021-05-05 12:09:28 +02:00
MGlolenstine 541b0c3af2 process: updated example (#3748) 2021-05-05 11:51:05 +02:00
Kai Mast 55c5d12451 util: require full feature in tokio-util tests (#3636) 2021-05-05 18:21:25 +09:00
Chế Vũ Gia Hy a945ce0996 stream: implement Error and Display for BroadcastStreamRecvError (#3745) 2021-05-05 11:15:42 +02:00
Russell Cohen 14bb2f624f io: add write_all_buf to AsyncWriteExt (#3737) 2021-05-01 20:54:38 +02:00
Marco Ieni a08ce0d3e0 ci: check docs of private items (#3715) 2021-04-30 13:25:50 +02:00
Arthur Silva 078e317c0b tokio: update CHANGELOG.md (#3735) 2021-04-30 00:26:28 +02:00
kamulos ce9697f2a2 doc: fix missing backtick in select doc (#3732) 2021-04-27 22:23:03 +09:00
Alice Ryhl 73466f4b6c task: update JoinHandle panic message (#3727) 2021-04-25 09:03:14 +02:00
Sunjay Varma 0ba1e3c3e1 sync: add a MutexGuard::map method that returns a MappedMutexGuard (#2472) 2021-04-23 17:26:02 +02:00
Nylonicious 1e2f893da2 chore: fix some clippy lints (#3720) 2021-04-22 13:33:42 +02:00
Carl Lerche 2b9b558108 time: prevent time::advance from going too far (#3712)
Previously, `time::advance` would set the mocked clock forward the
requested amount, then yield. However, if there was no work ready to
perform immediately, this would result in advancing to the next expiring
sleep.

Now, `time::advance(...)` will unblock at the requested time. The
difference between `time::advance(...)` and `time::sleep(...)` is a bit
fuzzy. The main difference is `time::sleep(...)` operates on the current
task and `time::advance(...)` operates at the runtime level.

Fixes #3710
2021-04-21 15:23:35 -07:00
Eliza Weisman d6da67b2e6 sync: add mpsc::Sender::{reserve_owned, try_reserve_owned} (#3704)
* sync: add `mpsc::Sender::{reserve_owned, try_reserve_owned}`

## Motivation

The `mpsc::Sender::reserve` method currently returns a permit that borrows
from the `Sender`. It would be nice to have a version of it that returns
an owned permit.

## Solution

This branch adds an `OwnedPermit` type and `Sender::{reserve_owned,
try_reserve_owned}` methods. Unlike the comparable methods on
`Semaphore`, these methods do *not* require an `Arc<Sender>` as the
receiver; this is because the sender internally reference counts the
channel and is already cheap to clone. Requiring an `Arc` would simply
add an unnecessary second layer of reference counting, which is not
ideal; instead, the documentation encourages the user to clone the
sender prior to calling `reserve_owned` when necessary.

Since these methods take the `Sender` by value, they also have the ability
to _return_ the `Sender` from a successful `OwnedPermit::send`. This
allows them to be used without additional clones. Essentially, this is a
very simple type-level encoding of the sender's state, with the
transitions
```
      ┌──────┐
 ┌───►│Sender├───┐
 │    └──────┘   │
send          reserve
 │ ┌───────────┐ │
 └─┤OwnedPermit│◄┘
   └───────────┘
```

Additionally, I added an `OwnedPermit::release`, which returns the
`Sender` and releases the permit *without* sending a message.

Closes #3688 

Signed-off-by: Eliza Weisman <[email protected]>
2021-04-15 12:55:57 -07:00
madjack c8a6bb0b90 time: add getter for Interval's period (#3705) 2021-04-15 14:21:52 +02:00
Evan Cameron 9eeec039f2 util: make UdpFramed take Borrow<UdpSocket> (#3451) 2021-04-14 20:16:23 +02:00
William Manley 39706b198c fs: try doing a non-blocking read before punting to the threadpool (#3518) 2021-04-14 15:44:49 +02:00
baoyachi. Aka Rust Hairy crabs cab4a592ac chore: update version in README.md (#3698) 2021-04-13 16:17:14 +09:00
Alice Ryhl a5ee2f0d3d chore: prepare Tokio v1.5.0 (#3695) 2021-04-12 21:24:47 +02:00
Alice Ryhl 917aad684b chore: prepare tokio-util 0.6.6 (#3696) 2021-04-12 21:23:20 +02:00
Alice Ryhl e366cf9b3e chore: upgrade loom to 0.5.0 (#3697) 2021-04-12 20:28:48 +02:00
Alice Ryhl 3a02d34d3a sync: document that Semaphore is fair (#3693) 2021-04-12 15:46:42 +02:00
conblem adad8fc3cd io: add a copy_bidirectional utility (#3572) 2021-04-12 14:18:45 +02:00
David Pedersen 08f1b67fcb sync: add mpsc::Sender::capacity (#3690)
Simply exposes the number of available permits of the semaphore.

This makes some kinds of bookkeeping easier without having to manually keep counts using atomics.

Fixes #2642
2021-04-12 09:23:03 +02:00
David Pedersen 28d6879897 macros: forward input arguments in #[tokio::test] (#3691)
Fixes #2388

Previously `#[tokio::test]` would error on functions that took
arguments. That meant other attribute macros couldn't do further
transformations on them. This changes that so arguments are forwarded as
is.

Whatever else might be included on the function is forwarded as well.
For example return type, generics, etc.

Worth noting that this is only for compatibility with other macros.
`#[test]`s that take arguments will still fail to compile.

A bit odd that [trybuild] tests don't fail `#[test]` functions with
arguments which is why the new tests are run with `t.pass(...)`. They do
actually fail if part of a real crate.

[trybuild]: https://crates.io/crates/trybuild
2021-04-11 20:39:05 +02:00
John-John Tedro 1a72b28f53 rt: fix panic in JoinHandle::abort() when called from other thread (#3672)
When aborting a task registered with a current-thread scheduler from off runtime, the tasks may not
be immediately unlinked from the runtime. Instead, send a message to the runtime, notifying it to
remove the aborted task.

Fixes #3662
2021-04-08 13:49:39 -07:00
Geoffry Song 1d5655272b runtime: use Park::Error: Debug for better panic messages (#3641) 2021-04-08 20:30:43 +02:00
Matthias Beyer 5513b6b825 time: change 300ms to 1s in documentation (#3685) 2021-04-08 11:04:27 +02:00
ty 787aca1826 net: implement IntoRawFd for TcpSocket (#3684) 2021-04-08 09:28:44 +02:00
Zahari Dichev e89c8981f1 sync: allow configuring RwLock max reads (#3644) 2021-04-07 20:44:29 +02:00
Eliza Weisman bf8c77bea1 util: add PollSemaphore::{add_permits, available_permits} (#3683)
## Motivation

The `tokio::sync::Semaphore` type provides a
[`Semaphore::available_permits` method][1] which returns the current
number of permits available on the semaphore.
`tokio_util::sync::PollSemaphore` [does not expose such a method][2]. It
is possible to use `PollSemaphore::into_inner` or
`PollSemaphore::clone_inner` to unwrap the inner semaphore, but this may
require cloning/dropping the semaphore's `Arc` which shouldn't be
necessary to access the permits.

## Solution

This commit adds `PollSemaphore::available_permits`. It also adds
`PollSemaphore::add_permits` and an `AsRef<Semaphore>` impl while
we're here.

[1]: https://docs.rs/tokio/1.4.0/tokio/sync/struct.Semaphore.html#method.available_permits
[2]: https://docs.rs/tokio-util/0.6.5/tokio_util/sync/struct.PollSemaphore.html#implementations

Closes #3682
2021-04-07 11:40:03 -07:00
Lucas Zanela 0074b963b8 doc: add features = "full" to readme (#3680) 2021-04-06 11:03:21 +02:00
David Pedersen eabb7ce61c io: add AsyncWriteExt::write_vectored (#3678)
Fixes #3676
2021-04-06 00:02:00 +02:00
David Pedersen 618d2bfc71 macros: various error message improvements (#3677)
Improves a few of the error messages for `#[tokio::main]` and `#[tokio::test]`.

Also adds a note to the docs about `start_paused` requiring the `test-util` feature which wasn't mentioned previously.
2021-04-05 22:40:42 +02:00
b-naber f6e4e85dfb sync: add OnceCell (#3591) 2021-04-05 22:03:57 +02:00
xd009642 f93bc9bad1 sync: don't panic in oneshot::try_recv (#3674) 2021-04-04 20:03:00 +02:00
Michael P. Jung b05b9a1788 sync: add Semaphore::is_closed (#3673) 2021-04-04 19:14:41 +02:00
Donough Liu b42f21ec3e doc: fix incorrect link in doc (#3670) 2021-04-01 15:24:29 +02:00
Nylonicious 8fc49dc522 chore: update years in all licenses (#3665) 2021-03-30 21:45:13 +02:00
Amos Wenger 9ec3393650 net: typo in socket.rs (#3663) 2021-03-30 20:44:54 +02:00
Simon Lindholm f7c181c2c4 sync: fix notifications getting dropped on receiver drop (#3652) 2021-03-30 09:18:07 +02:00
Alice Ryhl fee76ea7d5 chore: try to avoid noalias attributes on intrusive linked list (#3654) 2021-03-29 22:38:29 +02:00
CorinJG 1a80d6eee5 sync: improve doc on blocking mutex (#3645) 2021-03-27 13:26:10 +01:00
Taiki Endo 7384813979 chore: update proptest to 1 (#3653) 2021-03-27 19:34:11 +09:00
Taiki Endo a257a3a2b1 io: add AsyncSeekExt::stream_position (#3650) 2021-03-27 12:28:51 +09:00
Alexandr 9a3603fa75 chore: fix several typos in docs (#3651) 2021-03-27 12:27:52 +09:00
Alice Ryhl 724ba348d1 chore: fix warning from new Rust version (#3647) 2021-03-26 19:22:43 +09:00
Kai Jewson 0dc4769708 sync: add OwnedRwLockReadGuard and OwnedRwLockWriteGuard (#3340) 2021-03-23 13:40:17 +01:00
Mihail Malo 6f896d8846 macros: add missing => in doc (#3638) 2021-03-22 21:37:29 +01:00
Daniel Franklin 10abc45da1 io: clarify requirements of AsyncFd (#3635) 2021-03-22 21:16:40 +01:00
Moritz Gunz 227b3e0d9c chore: avoid allocation if PollSemaphore is unused (#3634) 2021-03-22 17:02:30 +01:00
quininer c659e4a757 task: add sync_scope for LocalKey (#3612) 2021-03-22 17:02:10 +01:00
somethingelseentirely 8ed825fd49 util: makes Framed and FramedStream resumable after eof (#3272) 2021-03-22 09:09:01 +01:00
Ibraheem Ahmed 63395f061e macros: fix typo in select macro docs (#3629) 2021-03-21 20:10:28 +01:00
Kai Jewson 69b129b405 stream: avoid yielding in AllFuture and AnyFuture (#3625) 2021-03-21 09:34:18 +01:00
David Pedersen 0bfcbc8be5 runtime: fix unclear docs for {Handle,Runtime}::block_on (#3628) 2021-03-20 14:52:25 +01:00
David Pedersen 81f47e8866 util: fix indentation and typos in DelayQueue docs (#3626) 2021-03-20 14:31:12 +01:00
David Pedersen b4918adbd8 chore: prepare tokio-stream v0.1.5 (#3624) 2021-03-20 12:17:37 +01:00
181 changed files with 8659 additions and 876 deletions
+1
View File
@@ -0,0 +1 @@
msrv = "1.45"
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [tokio-rs]
+12 -5
View File
@@ -9,7 +9,7 @@ name: CI
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
nightly: nightly-2020-09-21
nightly: nightly-2021-04-25
minrust: 1.45.2
jobs:
@@ -68,6 +68,13 @@ jobs:
run: cargo hack test --each-feature
working-directory: tests-build
# Build benchmarks. Run of benchmarks is done by bench.yml workflow.
- name: build benches
run: cargo build --benches
working-directory: benches
# bench.yml workflow runs benchmarks only on linux.
if: startsWith(matrix.os, 'ubuntu')
valgrind:
name: valgrind
runs-on: ubuntu-latest
@@ -231,7 +238,7 @@ jobs:
cargo hack --remove-dev-deps --workspace
# Update Cargo.lock to minimal version dependencies.
cargo update -Z minimal-versions
cargo check --all-features
cargo hack check --all-features --ignore-private
fmt:
name: fmt
@@ -258,7 +265,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
run: rustup update 1.52.1 && rustup default 1.52.1
- name: Install clippy
run: rustup component add clippy
@@ -277,9 +284,9 @@ jobs:
override: true
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features
run: cargo doc --lib --no-deps --all-features --document-private-items
env:
RUSTDOCFLAGS: --cfg docsrs
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
loom:
name: loom
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2019 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+10 -2
View File
@@ -50,7 +50,15 @@ an asynchronous application.
## Example
A basic TCP echo server with Tokio:
A basic TCP echo server with Tokio.
Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.7.0", features = ["full"] }
```
Then, on your main.rs:
```rust,no_run
use tokio::net::TcpListener;
@@ -58,7 +66,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
+10 -1
View File
@@ -5,9 +5,13 @@ publish = false
edition = "2018"
[dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
bencher = "0.1.5"
[dev-dependencies]
tokio-util = { version = "0.6.6", path = "../tokio-util", features = ["full"] }
tokio-stream = { path = "../tokio-stream" }
[target.'cfg(unix)'.dependencies]
libc = "0.2.42"
@@ -41,3 +45,8 @@ harness = false
name = "signal"
path = "signal.rs"
harness = false
[[bench]]
name = "fs"
path = "fs.rs"
harness = false
+103
View File
@@ -0,0 +1,103 @@
#![cfg(unix)]
use tokio_stream::StreamExt;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
use bencher::{benchmark_group, benchmark_main, Bencher};
use std::fs::File as StdFile;
use std::io::Read as StdRead;
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.build()
.unwrap()
}
const BLOCK_COUNT: usize = 1_000;
const BUFFER_SIZE: usize = 4096;
const DEV_ZERO: &'static str = "/dev/zero";
fn async_read_codec(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let file = File::open(DEV_ZERO).await.unwrap();
let mut input_stream = FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
for _i in 0..BLOCK_COUNT {
let _bytes = input_stream.next().await.unwrap();
}
};
rt.block_on(task());
});
}
fn async_read_buf(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut file = File::open(DEV_ZERO).await.unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
let count = file.read(&mut buffer).await.unwrap();
if count == 0 {
break;
}
}
};
rt.block_on(task());
});
}
fn async_read_std_file(b: &mut Bencher) {
let rt = rt();
let task = || async {
let mut file = tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
for _i in 0..BLOCK_COUNT {
let mut buffer = [0u8; BUFFER_SIZE];
let mut file_ref = file.as_mut();
tokio::task::block_in_place(move || {
file_ref.read_exact(&mut buffer).unwrap();
});
}
};
b.iter(|| {
rt.block_on(task());
});
}
fn sync_read(b: &mut Bencher) {
b.iter(|| {
let mut file = StdFile::open(DEV_ZERO).unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
file.read_exact(&mut buffer).unwrap();
}
});
}
benchmark_group!(
file,
async_read_std_file,
async_read_buf,
async_read_codec,
sync_read
);
benchmark_main!(file);
+14 -3
View File
@@ -7,9 +7,9 @@ edition = "2018"
# If you copy one of the examples into a new project, you should be using
# [dependencies] instead.
[dev-dependencies]
tokio = { version = "1.0.0", features = ["full", "tracing"] }
tokio-util = { version = "0.6.3", features = ["full"] }
tokio-stream = { version = "0.1" }
tokio = { version = "1.0.0", path = "../tokio",features = ["full", "tracing"] }
tokio-util = { version = "0.6.3", path = "../tokio-util",features = ["full"] }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
tracing = "0.1"
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
@@ -22,7 +22,10 @@ serde_json = "1.0"
httparse = "1.0"
time = "0.1"
once_cell = "1.5.2"
rand = "0.8.3"
[target.'cfg(windows)'.dev-dependencies.winapi]
version = "0.3.8"
[[example]]
name = "chat"
@@ -76,3 +79,11 @@ path = "custom-executor.rs"
[[example]]
name = "custom-executor-tokio-context"
path = "custom-executor-tokio-context.rs"
[[example]]
name = "named-pipe"
path = "named-pipe.rs"
[[example]]
name = "named-pipe-multi-client"
path = "named-pipe-multi-client.rs"
+98
View File
@@ -0,0 +1,98 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
use tokio::time;
use winapi::shared::winerror;
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
const N: usize = 10;
// The first server needs to be constructed early so that clients can
// be correctly connected. Otherwise a waiting client will error.
//
// Here we also make use of `first_pipe_instance`, which will ensure
// that there are no other servers up and running already.
let mut server = ServerOptions::new()
.first_pipe_instance(true)
.create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Artificial workload.
time::sleep(Duration::from_secs(1)).await;
for _ in 0..N {
// Wait for client to connect.
server.connect().await?;
let mut inner = server;
// Construct the next server to be connected before sending the one
// we already have of onto a task. This ensures that the server
// isn't closed (after it's done in the task) before a new one is
// available. Otherwise the client might error with
// `io::ErrorKind::NotFound`.
server = ServerOptions::new().create(PIPE_NAME)?;
let _ = tokio::spawn(async move {
let mut buf = vec![0u8; 4];
inner.read_exact(&mut buf).await?;
inner.write_all(b"pong").await?;
Ok::<_, io::Error>(())
});
}
Ok::<_, io::Error>(())
});
let mut clients = Vec::new();
for _ in 0..N {
clients.push(tokio::spawn(async move {
// This showcases a generic connect loop.
//
// We immediately try to create a client, if it's not found or
// the pipe is busy we use the specialized wait function on the
// client builder.
let mut client = loop {
match ClientOptions::new().open(PIPE_NAME) {
Ok(client) => break client,
Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (),
Err(e) => return Err(e),
}
time::sleep(Duration::from_millis(5)).await;
};
let mut buf = [0u8; 4];
client.write_all(b"ping").await?;
client.read_exact(&mut buf).await?;
Ok::<_, io::Error>(buf)
}));
}
for client in clients {
let result = client.await?;
assert_eq!(&result?[..], b"pong");
}
server.await??;
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
+60
View File
@@ -0,0 +1,60 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use tokio::io::AsyncWriteExt;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
let server = ServerOptions::new().create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Note: we wait for a client to connect.
server.connect().await?;
let mut server = BufReader::new(server);
let mut buf = String::new();
server.read_line(&mut buf).await?;
server.write_all(b"pong\n").await?;
Ok::<_, io::Error>(buf)
});
let client = tokio::spawn(async move {
// There's no need to use a connect loop here, since we know that the
// server is already up - `open` was called before spawning any of the
// tasks.
let client = ClientOptions::new().open(PIPE_NAME)?;
let mut client = BufReader::new(client);
let mut buf = String::new();
client.write_all(b"ping\n").await?;
client.read_line(&mut buf).await?;
Ok::<_, io::Error>(buf)
});
let (server, client) = tokio::try_join!(server, client)?;
assert_eq!(server?, "ping\n");
assert_eq!(client?, "pong\n");
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
@@ -0,0 +1,8 @@
#![deny(dead_code)]
use tests_build::tokio;
#[tokio::main]
async fn f() {}
fn main() {}
@@ -0,0 +1,11 @@
error: function is never used: `f`
--> $DIR/macros_dead_code.rs:6:10
|
6 | async fn f() {}
| ^
|
note: the lint level is defined here
--> $DIR/macros_dead_code.rs:1:9
|
1 | #![deny(dead_code)]
| ^^^^^^^^^
+18 -3
View File
@@ -12,12 +12,27 @@ async fn main_attr_has_path_args() {}
#[tokio::test]
fn test_is_not_async() {}
#[tokio::test]
async fn test_fn_has_args(_x: u8) {}
#[tokio::test(foo)]
async fn test_attr_has_args() {}
#[tokio::test(foo = 123)]
async fn test_unexpected_attr() {}
#[tokio::test(flavor = 123)]
async fn test_flavor_not_string() {}
#[tokio::test(flavor = "foo")]
async fn test_unknown_flavor() {}
#[tokio::test(flavor = "multi_thread", start_paused = false)]
async fn test_multi_thread_with_start_paused() {}
#[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
async fn test_worker_threads_not_int() {}
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
async fn test_worker_threads_and_current_thread() {}
#[tokio::test]
#[test]
async fn test_has_second_test_attr() {}
@@ -1,4 +1,4 @@
error: the async keyword is missing from the function declaration
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:4:1
|
4 | fn main_is_not_async() {}
@@ -16,26 +16,56 @@ error: Must have specified ident
9 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
error: the async keyword is missing from the function declaration
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:13:1
|
13 | fn test_is_not_async() {}
| ^^
error: the test function cannot accept arguments
--> $DIR/macros_invalid_input.rs:16:27
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
--> $DIR/macros_invalid_input.rs:15:15
|
16 | async fn test_fn_has_args(_x: u8) {}
| ^^^^^^
15 | #[tokio::test(foo)]
| ^^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
--> $DIR/macros_invalid_input.rs:18:15
|
18 | #[tokio::test(foo)]
| ^^^
18 | #[tokio::test(foo = 123)]
| ^^^^^^^^^
error: Failed to parse value of `flavor` as string.
--> $DIR/macros_invalid_input.rs:21:24
|
21 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
--> $DIR/macros_invalid_input.rs:24:24
|
24 | #[tokio::test(flavor = "foo")]
| ^^^^^
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> $DIR/macros_invalid_input.rs:27:55
|
27 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
| ^^^^^
error: Failed to parse value of `worker_threads` as integer.
--> $DIR/macros_invalid_input.rs:30:57
|
30 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
| ^^^^^
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
--> $DIR/macros_invalid_input.rs:33:59
|
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^
error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:22:1
--> $DIR/macros_invalid_input.rs:37:1
|
22 | #[test]
37 | #[test]
| ^^^^^^^
@@ -0,0 +1,32 @@
use tests_build::tokio;
#[tokio::main]
async fn missing_semicolon_or_return_type() {
Ok(())
}
#[tokio::main]
async fn missing_return_type() {
/* TODO(taiki-e): one of help messages still wrong
help: consider using a semicolon here
|
16 | return Ok(());;
|
*/
return Ok(());
}
#[tokio::main]
async fn extra_semicolon() -> Result<(), ()> {
/* TODO(taiki-e): help message still wrong
help: try using a variant of the expected enum
|
29 | Ok(Ok(());)
|
29 | Err(Ok(());)
|
*/
Ok(());
}
fn main() {}
@@ -0,0 +1,51 @@
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:5:5
|
5 | Ok(())
| ^^^^^^ expected `()`, found enum `Result`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: consider using a semicolon here
|
5 | Ok(());
| ^
help: try adding a return type
|
4 | async fn missing_semicolon_or_return_type() -> Result<(), _> {
| ^^^^^^^^^^^^^^^^
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:16:5
|
16 | return Ok(());
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: consider using a semicolon here
|
16 | return Ok(());;
| ^
help: try adding a return type
|
9 | async fn missing_return_type() -> Result<(), _> {
| ^^^^^^^^^^^^^^^^
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:29:5
|
20 | async fn extra_semicolon() -> Result<(), ()> {
| -------------- expected `Result<(), ()>` because of return type
...
29 | Ok(());
| ^^^^^^^ expected enum `Result`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
help: try using a variant of the expected enum
|
29 | Ok(Ok(());)
|
29 | Err(Ok(());)
|
+9
View File
@@ -2,9 +2,18 @@
fn compile_fail_full() {
let t = trybuild::TestCases::new();
#[cfg(feature = "full")]
t.pass("tests/pass/forward_args_and_output.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_invalid_input.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_dead_code.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_type_mismatch.rs");
#[cfg(all(feature = "rt", not(feature = "full")))]
t.compile_fail("tests/fail/macros_core_no_default.rs");
@@ -0,0 +1,13 @@
use tests_build::tokio;
fn main() {}
// arguments and output type is forwarded so other macros can access them
#[tokio::test]
async fn test_fn_has_args(_x: u8) {}
#[tokio::test]
async fn test_has_output() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
+10
View File
@@ -1,3 +1,13 @@
# 1.2.0 (May 14, 2021)
- macros: forward input arguments in `#[tokio::test]` ([#3691])
- macros: improve diagnostics on type mismatch ([#3766])
- macros: various error message improvements ([#3677])
[#3677]: https://github.com/tokio-rs/tokio/pull/3677
[#3691]: https://github.com/tokio-rs/tokio/pull/3691
[#3766]: https://github.com/tokio-rs/tokio/pull/3766
# 1.1.0 (February 5, 2021)
- add `start_paused` option to macros ([#3492])
+3 -3
View File
@@ -6,13 +6,13 @@ name = "tokio-macros"
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.0.x" git tag.
version = "1.1.0"
version = "1.2.0"
edition = "2018"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-macros/1.1.0/tokio_macros"
documentation = "https://docs.rs/tokio-macros/1.2.0/tokio_macros"
description = """
Tokio's proc macros.
"""
@@ -26,7 +26,7 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0.7"
quote = "1"
syn = { version = "1.0.3", features = ["full"] }
syn = { version = "1.0.56", features = ["full"] }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2020 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+81 -53
View File
@@ -1,7 +1,6 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::spanned::Spanned;
use quote::{quote, quote_spanned, ToTokens};
#[derive(Clone, Copy, PartialEq)]
enum RuntimeFlavor {
@@ -34,6 +33,7 @@ struct Configuration {
flavor: Option<RuntimeFlavor>,
worker_threads: Option<(usize, Span)>,
start_paused: Option<(bool, Span)>,
is_test: bool,
}
impl Configuration {
@@ -47,6 +47,7 @@ impl Configuration {
flavor: None,
worker_threads: None,
start_paused: None,
is_test,
}
}
@@ -92,16 +93,25 @@ impl Configuration {
Ok(())
}
fn macro_name(&self) -> &'static str {
if self.is_test {
"tokio::test"
} else {
"tokio::main"
}
}
fn build(&self) -> Result<FinalConfig, syn::Error> {
let flavor = self.flavor.unwrap_or(self.default_flavor);
use RuntimeFlavor::*;
let worker_threads = match (flavor, self.worker_threads) {
(CurrentThread, Some((_, worker_threads_span))) => {
return Err(syn::Error::new(
worker_threads_span,
"The `worker_threads` option requires the `multi_thread` runtime flavor.",
))
let msg = format!(
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
self.macro_name(),
);
return Err(syn::Error::new(worker_threads_span, msg));
}
(CurrentThread, None) => None,
(Threaded, worker_threads) if self.rt_multi_thread_available => {
@@ -119,10 +129,11 @@ impl Configuration {
let start_paused = match (flavor, self.start_paused) {
(Threaded, Some((_, start_paused_span))) => {
return Err(syn::Error::new(
start_paused_span,
"The `start_paused` option requires the `current_thread` runtime flavor.",
));
let msg = format!(
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
self.macro_name(),
);
return Err(syn::Error::new(start_paused_span, msg));
}
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
(_, None) => None,
@@ -142,12 +153,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error
Ok(value) => Ok(value),
Err(e) => Err(syn::Error::new(
span,
format!("Failed to parse {} as integer: {}", field, e),
format!("Failed to parse value of `{}` as integer: {}", field, e),
)),
},
_ => Err(syn::Error::new(
span,
format!("Failed to parse {} as integer.", field),
format!("Failed to parse value of `{}` as integer.", field),
)),
}
}
@@ -158,7 +169,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
syn::Lit::Verbatim(s) => Ok(s.to_string()),
_ => Err(syn::Error::new(
span,
format!("Failed to parse {} as string.", field),
format!("Failed to parse value of `{}` as string.", field),
)),
}
}
@@ -168,7 +179,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
syn::Lit::Bool(b) => Ok(b.value),
_ => Err(syn::Error::new(
span,
format!("Failed to parse {} as bool.", field),
format!("Failed to parse value of `{}` as bool.", field),
)),
}
}
@@ -179,24 +190,13 @@ fn parse_knobs(
is_test: bool,
rt_multi_thread: bool,
) -> Result<TokenStream, syn::Error> {
let sig = &mut input.sig;
let body = &input.block;
let attrs = &input.attrs;
let vis = input.vis;
if sig.asyncness.is_none() {
let msg = "the async keyword is missing from the function declaration";
return Err(syn::Error::new_spanned(sig.fn_token, msg));
if input.sig.asyncness.take().is_none() {
let msg = "the `async` keyword is missing from the function declaration";
return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
}
sig.asyncness = None;
let macro_name = if is_test {
"tokio::test"
} else {
"tokio::main"
};
let mut config = Configuration::new(is_test, rt_multi_thread);
let macro_name = config.macro_name();
for arg in args {
match arg {
@@ -208,20 +208,32 @@ fn parse_knobs(
}
match ident.unwrap().to_string().to_lowercase().as_str() {
"worker_threads" => {
config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?;
config.set_worker_threads(
namevalue.lit.clone(),
syn::spanned::Spanned::span(&namevalue.lit),
)?;
}
"flavor" => {
config.set_flavor(namevalue.lit.clone(), namevalue.span())?;
config.set_flavor(
namevalue.lit.clone(),
syn::spanned::Spanned::span(&namevalue.lit),
)?;
}
"start_paused" => {
config.set_start_paused(namevalue.lit.clone(), namevalue.span())?;
config.set_start_paused(
namevalue.lit.clone(),
syn::spanned::Spanned::span(&namevalue.lit),
)?;
}
"core_threads" => {
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
return Err(syn::Error::new_spanned(namevalue, msg));
}
name => {
let msg = format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name);
let msg = format!(
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`",
name,
);
return Err(syn::Error::new_spanned(namevalue, msg));
}
}
@@ -266,11 +278,29 @@ fn parse_knobs(
let config = config.build()?;
// If type mismatch occurs, the current rustc points to the last statement.
let (last_stmt_start_span, last_stmt_end_span) = {
let mut last_stmt = input
.block
.stmts
.last()
.map(ToTokens::into_token_stream)
.unwrap_or_default()
.into_iter();
// `Span` on stable Rust has a limitation that only points to the first
// token, not the whole tokens. We can work around this limitation by
// using the first/last span of the tokens like
// `syn::Error::new_spanned` does.
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
let end = last_stmt.last().map_or(start, |t| t.span());
(start, end)
};
let mut rt = match config.flavor {
RuntimeFlavor::CurrentThread => quote! {
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_current_thread()
},
RuntimeFlavor::Threaded => quote! {
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_multi_thread()
},
};
@@ -281,26 +311,31 @@ fn parse_knobs(
rt = quote! { #rt.start_paused(#v) };
}
let header = {
if is_test {
quote! {
#[::core::prelude::v1::test]
}
} else {
quote! {}
let header = if is_test {
quote! {
#[::core::prelude::v1::test]
}
} else {
quote! {}
};
let result = quote! {
#header
#(#attrs)*
#vis #sig {
let body = &input.block;
let brace_token = input.block.brace_token;
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
{
#rt
.enable_all()
.build()
.unwrap()
.block_on(async #body)
}
})
.unwrap();
input.block.brace_token = brace_token;
let result = quote! {
#header
#input
};
Ok(result.into())
@@ -334,12 +369,5 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
}
}
if !input.sig.inputs.is_empty() {
let msg = "the test function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.inputs, msg)
.to_compile_error()
.into();
}
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
}
+4
View File
@@ -167,6 +167,8 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
@@ -257,6 +259,8 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
+21
View File
@@ -1,3 +1,24 @@
# 0.1.6 (May 14, 2021)
### Added
- stream: implement `Error` and `Display` for `BroadcastStreamRecvError` ([#3745])
### Fixed
- stream: avoid yielding in `AllFuture` and `AnyFuture` ([#3625])
[#3745]: https://github.com/tokio-rs/tokio/pull/3745
[#3625]: https://github.com/tokio-rs/tokio/pull/3625
# 0.1.5 (March 20, 2021)
### Fixed
- stream: documentation note for throttle `Unpin` ([#3600])
[#3600]: https://github.com/tokio-rs/tokio/pull/3600
# 0.1.4 (March 9, 2021)
Added
+4 -4
View File
@@ -6,13 +6,13 @@ name = "tokio-stream"
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.4"
version = "0.1.6"
edition = "2018"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-stream/0.1.4/tokio_stream"
documentation = "https://docs.rs/tokio-stream/0.1.6/tokio_stream"
description = """
Utilities to work with `Stream` and `tokio`.
"""
@@ -31,7 +31,7 @@ signal = ["tokio/signal"]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.0"
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
tokio-util = { version = "0.6.3", optional = true }
tokio-util = { version = "0.6.3", path = "../tokio-util", optional = true }
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
@@ -39,7 +39,7 @@ async-stream = "0.3"
tokio-test = { path = "../tokio-test" }
futures = { version = "0.3", default-features = false }
proptest = "0.10.0"
proptest = "1"
[package.metadata.docs.rs]
all-features = true
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2020 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+12 -9
View File
@@ -38,18 +38,21 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = self.project();
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
let mut stream = Pin::new(me.stream);
match next {
Some(v) => {
if !(me.f)(v) {
Poll::Ready(false)
} else {
cx.waker().wake_by_ref();
Poll::Pending
// Take a maximum of 32 items from the stream before yielding.
for _ in 0..32 {
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
Some(v) => {
if !(me.f)(v) {
return Poll::Ready(false);
}
}
None => return Poll::Ready(true),
}
None => Poll::Ready(true),
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
+12 -9
View File
@@ -38,18 +38,21 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = self.project();
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
let mut stream = Pin::new(me.stream);
match next {
Some(v) => {
if (me.f)(v) {
Poll::Ready(true)
} else {
cx.waker().wake_by_ref();
Poll::Pending
// Take a maximum of 32 items from the stream before yielding.
for _ in 0..32 {
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
Some(v) => {
if (me.f)(v) {
return Poll::Ready(true);
}
}
None => return Poll::Ready(false),
}
None => Poll::Ready(false),
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
+3 -3
View File
@@ -605,10 +605,10 @@ mod rand {
/// Fast random number generate
///
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
/// Shift triplet [17,7,16] was calculated as indicated in Marsaglia's
/// Xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's
/// Xorshift paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
/// This generator passes the SmallCrush suite, part of TestU01 framework:
/// http://simul.iro.umontreal.ca/testu01/tu01.html
/// <http://simul.iro.umontreal.ca/testu01/tu01.html>
#[derive(Debug)]
pub(crate) struct FastRand {
one: Cell<u32>,
+10
View File
@@ -27,6 +27,16 @@ pub enum BroadcastStreamRecvError {
Lagged(u64),
}
impl fmt::Display for BroadcastStreamRecvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt),
}
}
}
impl std::error::Error for BroadcastStreamRecvError {}
async fn make_future<T: Clone>(mut rx: Receiver<T>) -> (Result<T, RecvError>, Receiver<T>) {
let result = rx.recv().await;
(result, rx)
+2
View File
@@ -1,3 +1,5 @@
#![allow(clippy::diverging_sub_expression)]
use std::rc::Rc;
#[allow(dead_code)]
+2 -2
View File
@@ -89,12 +89,12 @@ fn size_overflow() {
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::max_value(), Some(usize::max_value()))
(usize::MAX, Some(usize::MAX))
}
}
let m1 = Monster;
let m2 = Monster;
let m = m1.chain(m2);
assert_eq!(m.size_hint(), (usize::max_value(), None));
assert_eq!(m.size_hint(), (usize::MAX, None));
}
+2 -2
View File
@@ -72,12 +72,12 @@ fn size_overflow() {
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::max_value(), Some(usize::max_value()))
(usize::MAX, Some(usize::MAX))
}
}
let m1 = Monster;
let m2 = Monster;
let m = m1.merge(m2);
assert_eq!(m.size_hint(), (usize::max_value(), None));
assert_eq!(m.size_hint(), (usize::MAX, None));
}
+6
View File
@@ -1,3 +1,9 @@
# 0.4.2 (May 14, 2021)
- test: add `assert_elapsed!` macro ([#3728])
[#3728]: https://github.com/tokio-rs/tokio/pull/3728
# 0.4.1 (March 10, 2021)
- Fix `io::Mock` to be `Send` and `Sync` ([#3594])
+2 -2
View File
@@ -6,13 +6,13 @@ name = "tokio-test"
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-test-0.4.x" git tag.
version = "0.4.1"
version = "0.4.2"
edition = "2018"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-test/0.4.1/tokio_test"
documentation = "https://docs.rs/tokio-test/0.4.2/tokio_test"
description = """
Testing utilities for Tokio- and futures-based code
"""
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2020 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+34
View File
@@ -259,3 +259,37 @@ macro_rules! assert_err {
}
}};
}
/// Asserts that an exact duration has elapsed since since the start instant ±1ms.
///
/// ```rust
/// use tokio::time::{self, Instant};
/// use std::time::Duration;
/// use tokio_test::assert_elapsed;
/// # async fn test_time_passed() {
///
/// let start = Instant::now();
/// let dur = Duration::from_millis(50);
/// time::sleep(dur).await;
/// assert_elapsed!(start, dur);
/// # }
/// ```
///
/// This 1ms buffer is required because Tokio's hashed-wheel timer has finite time resolution and
/// will not always sleep for the exact interval.
#[macro_export]
macro_rules! assert_elapsed {
($start:expr, $dur:expr) => {{
let elapsed = $start.elapsed();
// type ascription improves compiler error when wrong type is passed
let lower: std::time::Duration = $dur;
// Handles ms rounding
assert!(
elapsed >= lower && elapsed <= lower + std::time::Duration::from_millis(1),
"actual = {:?}, expected = {:?}",
elapsed,
lower
);
}};
}
+25
View File
@@ -1,3 +1,28 @@
# 0.6.7 (May 14, 2021)
### Added
- udp: make `UdpFramed` take `Borrow<UdpSocket>` ([#3451])
- compat: implement `AsRawFd`/`AsRawHandle` for `Compat<T>` ([#3765])
[#3451]: https://github.com/tokio-rs/tokio/pull/3451
[#3765]: https://github.com/tokio-rs/tokio/pull/3765
# 0.6.6 (April 12, 2021)
### Added
- util: makes `Framed` and `FramedStream` resumable after eof ([#3272])
- util: add `PollSemaphore::{add_permits, available_permits}` ([#3683])
### Fixed
- chore: avoid allocation if `PollSemaphore` is unused ([#3634])
[#3272]: https://github.com/tokio-rs/tokio/pull/3272
[#3634]: https://github.com/tokio-rs/tokio/pull/3634
[#3683]: https://github.com/tokio-rs/tokio/pull/3683
# 0.6.5 (March 20, 2021)
### Fixed
+6 -6
View File
@@ -6,13 +6,13 @@ name = "tokio-util"
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-util-0.6.x" git tag.
version = "0.6.5"
version = "0.6.7"
edition = "2018"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-util/0.6.5/tokio_util"
documentation = "https://docs.rs/tokio-util/0.6.7/tokio_util"
description = """
Additional utilities for working with Tokio.
"""
@@ -35,7 +35,7 @@ rt = ["tokio/rt"]
__docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.0.0", features = ["sync"] }
tokio = { version = "1.0.0", path = "../tokio", features = ["sync"] }
bytes = "1.0.0"
futures-core = "0.3.0"
@@ -47,9 +47,9 @@ pin-project-lite = "0.2.0"
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
[dev-dependencies]
tokio = { version = "1.0.0", features = ["full"] }
tokio-test = { version = "0.4.0" }
tokio-stream = { version = "0.1" }
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
tokio-test = { version = "0.4.0", path = "../tokio-test" }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
async-stream = "0.3.0"
futures = "0.3.0"
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2020 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+20 -1
View File
@@ -16,6 +16,20 @@ use std::io;
/// implementing stateful streaming parsers. In many cases, though, this type
/// will simply be a unit struct (e.g. `struct HttpDecoder`).
///
/// For some underlying data-sources, namely files and FIFOs,
/// it's possible to temporarily read 0 bytes by reaching EOF.
///
/// In these cases `decode_eof` will be called until it signals
/// fullfillment of all closing frames by returning `Ok(None)`.
/// After that, repeated attempts to read from the [`Framed`] or [`FramedRead`]
/// will not invoke `decode` or `decode_eof` again, until data can be read
/// during a retry.
///
/// It is up to the Decoder to keep track of a restart after an EOF,
/// and to decide how to handle such an event by, for example,
/// allowing frames to cross EOF boundaries, re-emitting opening frames, or
/// reseting the entire internal state.
///
/// [`Framed`]: crate::codec::Framed
/// [`FramedRead`]: crate::codec::FramedRead
pub trait Decoder {
@@ -115,13 +129,18 @@ pub trait Decoder {
/// This method defaults to calling `decode` and returns an error if
/// `Ok(None)` is returned while there is unconsumed data in `buf`.
/// Typically this doesn't need to be implemented unless the framing
/// protocol differs near the end of the stream.
/// protocol differs near the end of the stream, or if you need to construct
/// frames _across_ eof boundaries on sources that can be resumed.
///
/// Note that the `buf` argument may be empty. If a previous call to
/// `decode_eof` consumed all the bytes in the buffer, `decode_eof` will be
/// called again until it returns `None`, indicating that there are no more
/// frames to yield. This behavior enables returning finalization frames
/// that may not be based on inbound data.
///
/// Once `None` has been returned, `decode_eof` won't be called again until
/// an attempt to resume the stream has been made, where the underlying stream
/// actually returned more data.
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match self.decode(buf)? {
Some(frame) => Ok(Some(frame)),
+5
View File
@@ -52,6 +52,11 @@ where
/// calling [`split`] on the `Framed` returned by this method, which will
/// break them into separate objects, allowing them to interact more easily.
///
/// Note that, for some byte sources, the stream can be resumed after an EOF
/// by reading from it, even after it has returned `None`. Repeated attempts
/// to do so, without new data available, continue to return `None` without
/// creating more (closing) frames.
///
/// [`Stream`]: futures_core::Stream
/// [`Sink`]: futures_sink::Sink
/// [`Decode`]: crate::codec::Decoder
+66 -11
View File
@@ -120,42 +120,97 @@ where
let mut pinned = self.project();
let state: &mut ReadFrame = pinned.state.borrow_mut();
// The following loops implements a state machine with each state corresponding
// to a combination of the `is_readable` and `eof` flags. States persist across
// loop entries and most state transitions occur with a return.
//
// The intitial state is `reading`.
//
// | state | eof | is_readable |
// |---------|-------|-------------|
// | reading | false | false |
// | framing | false | true |
// | pausing | true | true |
// | paused | true | false |
//
// `decode_eof`
// returns `Some` read 0 bytes
// │ │ │ │
// │ ▼ │ ▼
// ┌───────┐ `decode_eof` ┌──────┐
// ┌──read 0 bytes──▶│pausing│─returns `None`─▶│paused│──┐
// │ └───────┘ └──────┘ │
// pending read┐ │ ┌──────┐ │ ▲ │
// │ │ │ │ │ │ │ │
// │ ▼ │ │ `decode` returns `Some`│ pending read
// │ ╔═══════╗ ┌───────┐◀─┘ │
// └──║reading║─read n>0 bytes─▶│framing│ │
// ╚═══════╝ └───────┘◀──────read n>0 bytes┘
// ▲ │
// │ │
// └─`decode` returns `None`─┘
loop {
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
// Repeatedly call `decode` or `decode_eof` while the buffer is "readable",
// i.e. it _might_ contain data consumable as a frame or closing frame.
// Both signal that there is no such data by returning `None`.
//
// If `decode` couldn't read a frame and the upstream source has returned eof,
// `decode_eof` will attemp to decode the remaining bytes as closing frames.
//
// If the underlying AsyncRead is resumable, we may continue after an EOF,
// but must finish emmiting all of it's associated `decode_eof` frames.
// Furthermore, we don't want to emit any `decode_eof` frames on retried
// reads after an EOF unless we've actually read more data.
if state.is_readable {
// pausing or framing
if state.eof {
// pausing
let frame = pinned.codec.decode_eof(&mut state.buffer)?;
if frame.is_none() {
state.is_readable = false; // prepare pausing -> paused
}
// implicit pausing -> pausing or pausing -> paused
return Poll::Ready(frame.map(Ok));
}
// framing
trace!("attempting to decode a frame");
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {
trace!("frame decoded from buffer");
// implicit framing -> framing
return Poll::Ready(Some(Ok(frame)));
}
// framing -> reading
state.is_readable = false;
}
assert!(!state.eof);
// Otherwise, try to read more data and try again. Make sure we've
// got room for at least one byte to read to ensure that we don't
// get a spurious 0 that looks like EOF
// reading or paused
// If we can't build a frame yet, try to read more data and try again.
// Make sure we've got room for at least one byte to read to ensure
// that we don't get a spurious 0 that looks like EOF.
state.buffer.reserve(1);
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
Poll::Ready(ct) => ct,
// implicit reading -> reading or implicit paused -> paused
Poll::Pending => return Poll::Pending,
};
if bytect == 0 {
if state.eof {
// We're already at an EOF, and since we've reached this path
// we're also not readable. This implies that we've already finished
// our `decode_eof` handling, so we can simply return `None`.
// implicit paused -> paused
return Poll::Ready(None);
}
// prepare reading -> paused
state.eof = true;
} else {
// prepare paused -> framing or noop reading -> framing
state.eof = false;
}
// paused -> framing or reading -> framing or reading -> pausing
state.is_readable = true;
}
}
+14
View File
@@ -215,3 +215,17 @@ where
tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx)
}
}
#[cfg(unix)]
impl<T: std::os::unix::io::AsRawFd> std::os::unix::io::AsRawFd for Compat<T> {
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
self.inner.as_raw_fd()
}
}
#[cfg(windows)]
impl<T: std::os::windows::io::AsRawHandle> std::os::windows::io::AsRawHandle for Compat<T> {
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
self.inner.as_raw_handle()
}
}
+54 -8
View File
@@ -3,7 +3,7 @@ use std::fmt;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore};
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore, TryAcquireError};
use super::ReusableBoxFuture;
@@ -12,17 +12,15 @@ use super::ReusableBoxFuture;
/// [`Semaphore`]: tokio::sync::Semaphore
pub struct PollSemaphore {
semaphore: Arc<Semaphore>,
permit_fut: ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>,
permit_fut: Option<ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>>,
}
impl PollSemaphore {
/// Create a new `PollSemaphore`.
pub fn new(semaphore: Arc<Semaphore>) -> Self {
let fut = Arc::clone(&semaphore).acquire_owned();
Self {
semaphore,
permit_fut: ReusableBoxFuture::new(fut),
permit_fut: None,
}
}
@@ -55,16 +53,58 @@ impl PollSemaphore {
/// the `Waker` from the `Context` passed to the most recent call is
/// scheduled to receive a wakeup.
pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
let result = ready!(self.permit_fut.poll(cx));
let permit_future = match self.permit_fut.as_mut() {
Some(fut) => fut,
None => {
// avoid allocations completely if we can grab a permit immediately
match Arc::clone(&self.semaphore).try_acquire_owned() {
Ok(permit) => return Poll::Ready(Some(permit)),
Err(TryAcquireError::Closed) => return Poll::Ready(None),
Err(TryAcquireError::NoPermits) => {}
}
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
self.permit_fut
.get_or_insert(ReusableBoxFuture::new(next_fut))
}
};
let result = ready!(permit_future.poll(cx));
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
self.permit_fut.set(next_fut);
permit_future.set(next_fut);
match result {
Ok(permit) => Poll::Ready(Some(permit)),
Err(_closed) => Poll::Ready(None),
Err(_closed) => {
self.permit_fut = None;
Poll::Ready(None)
}
}
}
/// Returns the current number of available permits.
///
/// This is equivalent to the [`Semaphore::available_permits`] method on the
/// `tokio::sync::Semaphore` type.
///
/// [`Semaphore::available_permits`]: tokio::sync::Semaphore::available_permits
pub fn available_permits(&self) -> usize {
self.semaphore.available_permits()
}
/// Adds `n` new permits to the semaphore.
///
/// The maximum number of permits is `usize::MAX >> 3`, and this function
/// will panic if the limit is exceeded.
///
/// This is equivalent to the [`Semaphore::add_permits`] method on the
/// `tokio::sync::Semaphore` type.
///
/// [`Semaphore::add_permits`]: tokio::sync::Semaphore::add_permits
pub fn add_permits(&self, n: usize) {
self.semaphore.add_permits(n);
}
}
impl Stream for PollSemaphore {
@@ -88,3 +128,9 @@ impl fmt::Debug for PollSemaphore {
.finish()
}
}
impl AsRef<Semaphore> for PollSemaphore {
fn as_ref(&self) -> &Semaphore {
&*self.semaphore
}
}
+67 -66
View File
@@ -30,24 +30,24 @@ use std::task::{self, Poll, Waker};
///
/// Once delays have been configured, the `DelayQueue` is used via its
/// [`Stream`] implementation. [`poll_expired`] is called. If an entry has reached its
/// deadline, it is returned. If not, `Poll::Pending` indicating that the
/// deadline, it is returned. If not, `Poll::Pending` is returned indicating that the
/// current task will be notified once the deadline has been reached.
///
/// # `Stream` implementation
///
/// Items are retrieved from the queue via [`DelayQueue::poll_expired`]. If no delays have
/// expired, no items are returned. In this case, `Pending` is returned and the
/// expired, no items are returned. In this case, `Poll::Pending` is returned and the
/// current task is registered to be notified once the next item's delay has
/// expired.
///
/// If no items are in the queue, i.e. `is_empty()` returns `true`, then `poll`
/// returns `Ready(None)`. This indicates that the stream has reached an end.
/// returns `Poll::Ready(None)`. This indicates that the stream has reached an end.
/// However, if a new item is inserted *after*, `poll` will once again start
/// returning items or `Pending.
/// returning items or `Poll::Pending`.
///
/// Items are returned ordered by their expirations. Items that are configured
/// to expire first will be returned first. There are no ordering guarantees
/// for items configured to expire the same instant. Also note that delays are
/// for items configured to expire at the same instant. Also note that delays are
/// rounded to the closest millisecond.
///
/// # Implementation
@@ -152,7 +152,7 @@ pub struct DelayQueue<T> {
waker: Option<Waker>,
}
/// An entry in `DelayQueue` that has expired and removed.
/// An entry in `DelayQueue` that has expired and been removed.
///
/// Values are returned by [`DelayQueue::poll_expired`].
///
@@ -211,7 +211,7 @@ struct Data<T> {
const MAX_ENTRIES: usize = (1 << 30) - 1;
impl<T> DelayQueue<T> {
/// Creates a new, empty, `DelayQueue`
/// Creates a new, empty, `DelayQueue`.
///
/// The queue will not allocate storage until items are inserted into it.
///
@@ -239,15 +239,15 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::with_capacity(10);
/// let mut delay_queue = DelayQueue::with_capacity(10);
///
/// // These insertions are done without further allocation
/// for i in 0..10 {
/// delay_queue.insert(i, Duration::from_secs(i));
/// }
/// // These insertions are done without further allocation
/// for i in 0..10 {
/// delay_queue.insert(i, Duration::from_secs(i));
/// }
///
/// // This will make the queue allocate additional storage
/// delay_queue.insert(11, Duration::from_secs(11));
/// // This will make the queue allocate additional storage
/// delay_queue.insert(11, Duration::from_secs(11));
/// # }
/// ```
pub fn with_capacity(capacity: usize) -> DelayQueue<T> {
@@ -272,8 +272,8 @@ impl<T> DelayQueue<T> {
/// `value` will be returned from [`poll_expired`]. If `when` has already been
/// reached, then `value` is immediately made available to poll.
///
/// The return value represents the insertion and is used at an argument to
/// [`remove`] and [`reset`]. Note that [`Key`] is token and is reused once
/// The return value represents the insertion and is used as an argument to
/// [`remove`] and [`reset`]. Note that [`Key`] is a token and is reused once
/// `value` is removed from the queue either by calling [`poll_expired`] after
/// `when` is reached or by calling [`remove`]. At this point, the caller
/// must take care to not use the returned [`Key`] again as it may reference
@@ -295,13 +295,13 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert_at(
/// "foo", Instant::now() + Duration::from_secs(5));
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert_at(
/// "foo", Instant::now() + Duration::from_secs(5));
///
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// # }
/// ```
///
@@ -353,7 +353,7 @@ impl<T> DelayQueue<T> {
/// Attempts to pull out the next value of the delay queue, registering the
/// current task for wakeup if the value is not yet available, and returning
/// None if the queue is exhausted.
/// `None` if the queue is exhausted.
pub fn poll_expired(
&mut self,
cx: &mut task::Context<'_>,
@@ -391,7 +391,7 @@ impl<T> DelayQueue<T> {
///
/// `value` is stored in the queue until `timeout` duration has
/// elapsed after `insert` was called. At that point, `value` will
/// be returned from [`poll_expired`]. If `timeout` a Duration of
/// be returned from [`poll_expired`]. If `timeout` is a `Duration` of
/// zero, then `value` is immediately made available to poll.
///
/// The return value represents the insertion and is used as an
@@ -419,12 +419,12 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
///
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// # }
/// ```
///
@@ -452,11 +452,12 @@ impl<T> DelayQueue<T> {
}
}
/// Removes the key fom the expired queue or the timer wheel
/// depending on its expiration status
/// Removes the key from the expired queue or the timer wheel
/// depending on its expiration status.
///
/// # Panics
/// Panics if the key is not contained in the expired queue or the wheel
///
/// Panics if the key is not contained in the expired queue or the wheel.
fn remove_key(&mut self, key: &Key) {
use crate::time::wheel::Stack;
@@ -488,12 +489,12 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
///
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// // Remove the entry
/// let item = delay_queue.remove(&key);
/// assert_eq!(*item.get_ref(), "foo");
/// # }
/// ```
pub fn remove(&mut self, key: &Key) -> Expired<T> {
@@ -531,14 +532,14 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
///
/// // "foo" is scheduled to be returned in 5 seconds
/// // "foo" is scheduled to be returned in 5 seconds
///
/// delay_queue.reset_at(&key, Instant::now() + Duration::from_secs(10));
/// delay_queue.reset_at(&key, Instant::now() + Duration::from_secs(10));
///
/// // "foo" is now scheduled to be returned in 10 seconds
/// // "foo" is now scheduled to be returned in 10 seconds
/// # }
/// ```
pub fn reset_at(&mut self, key: &Key, when: Instant) {
@@ -559,7 +560,7 @@ impl<T> DelayQueue<T> {
}
}
/// Returns the next time poll as determined by the wheel
/// Returns the next time to poll as determined by the wheel
fn next_deadline(&mut self) -> Option<Instant> {
self.wheel
.poll_at()
@@ -591,14 +592,14 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
///
/// // "foo" is scheduled to be returned in 5 seconds
/// // "foo" is scheduled to be returned in 5 seconds
///
/// delay_queue.reset(&key, Duration::from_secs(10));
/// delay_queue.reset(&key, Duration::from_secs(10));
///
/// // "foo"is now scheduled to be returned in 10 seconds
/// // "foo"is now scheduled to be returned in 10 seconds
/// # }
/// ```
pub fn reset(&mut self, key: &Key, timeout: Duration) {
@@ -621,15 +622,15 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let mut delay_queue = DelayQueue::new();
///
/// delay_queue.insert("foo", Duration::from_secs(5));
/// delay_queue.insert("foo", Duration::from_secs(5));
///
/// assert!(!delay_queue.is_empty());
/// assert!(!delay_queue.is_empty());
///
/// delay_queue.clear();
/// delay_queue.clear();
///
/// assert!(delay_queue.is_empty());
/// assert!(delay_queue.is_empty());
/// # }
/// ```
pub fn clear(&mut self) {
@@ -663,10 +664,10 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue: DelayQueue<i32> = DelayQueue::with_capacity(10);
/// assert_eq!(delay_queue.len(), 0);
/// delay_queue.insert(3, Duration::from_secs(5));
/// assert_eq!(delay_queue.len(), 1);
/// let mut delay_queue: DelayQueue<i32> = DelayQueue::with_capacity(10);
/// assert_eq!(delay_queue.len(), 0);
/// delay_queue.insert(3, Duration::from_secs(5));
/// assert_eq!(delay_queue.len(), 1);
/// # }
/// ```
pub fn len(&self) -> usize {
@@ -698,12 +699,12 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let mut delay_queue = DelayQueue::new();
///
/// delay_queue.insert("hello", Duration::from_secs(10));
/// delay_queue.reserve(10);
/// delay_queue.insert("hello", Duration::from_secs(10));
/// delay_queue.reserve(10);
///
/// assert!(delay_queue.capacity() >= 11);
/// assert!(delay_queue.capacity() >= 11);
/// # }
/// ```
pub fn reserve(&mut self, additional: usize) {
@@ -713,7 +714,7 @@ impl<T> DelayQueue<T> {
/// Returns `true` if there are no items in the queue.
///
/// Note that this function returns `false` even if all items have not yet
/// expired and a call to `poll` will return `Pending`.
/// expired and a call to `poll` will return `Poll::Pending`.
///
/// # Examples
///
@@ -723,11 +724,11 @@ impl<T> DelayQueue<T> {
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// assert!(delay_queue.is_empty());
/// let mut delay_queue = DelayQueue::new();
/// assert!(delay_queue.is_empty());
///
/// delay_queue.insert("hello", Duration::from_secs(5));
/// assert!(!delay_queue.is_empty());
/// delay_queue.insert("hello", Duration::from_secs(5));
/// assert!(!delay_queue.is_empty());
/// # }
/// ```
pub fn is_empty(&self) -> bool {
+1 -1
View File
@@ -12,9 +12,9 @@ use std::time::Duration;
mod wheel;
#[doc(inline)]
pub mod delay_queue;
#[doc(inline)]
pub use delay_queue::DelayQueue;
// ===== Internal utils =====
+35 -20
View File
@@ -6,9 +6,12 @@ use tokio::{io::ReadBuf, net::UdpSocket};
use bytes::{BufMut, BytesMut};
use futures_core::ready;
use futures_sink::Sink;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{
borrow::Borrow,
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
};
use std::{io, mem::MaybeUninit};
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
@@ -32,10 +35,10 @@ use std::{io, mem::MaybeUninit};
/// [`Sink`]: futures_sink::Sink
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
#[must_use = "sinks do nothing unless polled"]
#[cfg_attr(docsrs, doc(all(feature = "codec", feature = "udp")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "codec", feature = "udp"))))]
#[derive(Debug)]
pub struct UdpFramed<C> {
socket: UdpSocket,
pub struct UdpFramed<C, T = UdpSocket> {
socket: T,
codec: C,
rd: BytesMut,
wr: BytesMut,
@@ -48,7 +51,13 @@ pub struct UdpFramed<C> {
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
impl<C, T> Unpin for UdpFramed<C, T> {}
impl<C, T> Stream for UdpFramed<C, T>
where
T: Borrow<UdpSocket>,
C: Decoder,
{
type Item = Result<(C::Item, SocketAddr), C::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
@@ -79,7 +88,7 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
let buf = &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]);
let mut read = ReadBuf::uninit(buf);
let ptr = read.filled().as_ptr();
let res = ready!(Pin::new(&mut pin.socket).poll_recv_from(cx, &mut read));
let res = ready!(pin.socket.borrow().poll_recv_from(cx, &mut read));
assert_eq!(ptr, read.filled().as_ptr());
let addr = res?;
@@ -93,7 +102,11 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
}
}
impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
impl<I, C, T> Sink<(I, SocketAddr)> for UdpFramed<C, T>
where
T: Borrow<UdpSocket>,
C: Encoder<I>,
{
type Error = C::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -125,13 +138,13 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
}
let Self {
ref mut socket,
ref socket,
ref mut out_addr,
ref mut wr,
..
} = *self;
let n = ready!(socket.poll_send_to(cx, &wr, *out_addr))?;
let n = ready!(socket.borrow().poll_send_to(cx, &wr, *out_addr))?;
let wrote_all = n == self.wr.len();
self.wr.clear();
@@ -156,11 +169,14 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
}
}
impl<C> UdpFramed<C> {
impl<C, T> UdpFramed<C, T>
where
T: Borrow<UdpSocket>,
{
/// Create a new `UdpFramed` backed by the given socket and codec.
///
/// See struct level documentation for more details.
pub fn new(socket: UdpSocket, codec: C) -> UdpFramed<C> {
pub fn new(socket: T, codec: C) -> UdpFramed<C, T> {
Self {
socket,
codec,
@@ -180,27 +196,21 @@ impl<C> UdpFramed<C> {
/// Care should be taken to not tamper with the underlying stream of data
/// coming in as it may corrupt the stream of frames otherwise being worked
/// with.
pub fn get_ref(&self) -> &UdpSocket {
pub fn get_ref(&self) -> &T {
&self.socket
}
/// Returns a mutable reference to the underlying I/O stream wrapped by
/// `Framed`.
/// Returns a mutable reference to the underlying I/O stream wrapped by `Framed`.
///
/// # Note
///
/// Care should be taken to not tamper with the underlying stream of data
/// coming in as it may corrupt the stream of frames otherwise being worked
/// with.
pub fn get_mut(&mut self) -> &mut UdpSocket {
pub fn get_mut(&mut self) -> &mut T {
&mut self.socket
}
/// Consumes the `Framed`, returning its underlying I/O stream.
pub fn into_inner(self) -> UdpSocket {
self.socket
}
/// Returns a reference to the underlying codec wrapped by
/// `Framed`.
///
@@ -228,4 +238,9 @@ impl<C> UdpFramed<C> {
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
&mut self.rd
}
/// Consumes the `Framed`, returning its underlying I/O stream.
pub fn into_inner(self) -> T {
self.socket
}
}
+2
View File
@@ -0,0 +1,2 @@
#![cfg(not(feature = "full"))]
compile_error!("run tokio-util tests with `--features full`");
+23
View File
@@ -254,6 +254,29 @@ fn multi_frames_on_eof() {
});
}
#[test]
fn read_eof_then_resume() {
let mut task = task::spawn(());
let mock = mock! {
Ok(b"\x00\x00\x00\x01".to_vec()),
Ok(b"".to_vec()),
Ok(b"\x00\x00\x00\x02".to_vec()),
Ok(b"".to_vec()),
Ok(b"\x00\x00\x00\x03".to_vec()),
};
let mut framed = FramedRead::new(mock, U32Decoder);
task.enter(|cx, _| {
assert_read!(pin!(framed).poll_next(cx), 1);
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
assert_read!(pin!(framed).poll_next(cx), 2);
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
assert_read!(pin!(framed).poll_next(cx), 3);
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
});
}
// ===== Mock ======
struct Mock {
+3 -3
View File
@@ -6,9 +6,9 @@ use tokio_util::sync::PollSemaphore;
type SemRet = Option<OwnedSemaphorePermit>;
fn semaphore_poll<'a>(
sem: &'a mut PollSemaphore,
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + 'a> {
fn semaphore_poll(
sem: &mut PollSemaphore,
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + '_> {
let fut = futures::future::poll_fn(move |cx| sem.poll_acquire(cx));
tokio_test::task::spawn(fut)
}
+29
View File
@@ -10,6 +10,7 @@ use futures::future::try_join;
use futures::future::FutureExt;
use futures::sink::SinkExt;
use std::io;
use std::sync::Arc;
#[cfg_attr(any(target_os = "macos", target_os = "ios"), allow(unused_assignments))]
#[tokio::test]
@@ -101,3 +102,31 @@ async fn send_framed_lines_codec() -> std::io::Result<()> {
Ok(())
}
#[tokio::test]
async fn framed_half() -> std::io::Result<()> {
let a_soc = Arc::new(UdpSocket::bind("127.0.0.1:0").await?);
let b_soc = a_soc.clone();
let a_addr = a_soc.local_addr()?;
let b_addr = b_soc.local_addr()?;
let mut a = UdpFramed::new(a_soc, ByteCodec);
let mut b = UdpFramed::new(b_soc, LinesCodec::new());
let msg = b"1\r\n2\r\n3\r\n".to_vec();
a.send((&msg, b_addr)).await?;
let msg = b"4\r\n5\r\n6\r\n".to_vec();
a.send((&msg, b_addr)).await?;
assert_eq!(b.next().await.unwrap().unwrap(), ("1".to_string(), a_addr));
assert_eq!(b.next().await.unwrap().unwrap(), ("2".to_string(), a_addr));
assert_eq!(b.next().await.unwrap().unwrap(), ("3".to_string(), a_addr));
assert_eq!(b.next().await.unwrap().unwrap(), ("4".to_string(), a_addr));
assert_eq!(b.next().await.unwrap().unwrap(), ("5".to_string(), a_addr));
assert_eq!(b.next().await.unwrap().unwrap(), ("6".to_string(), a_addr));
Ok(())
}
+181
View File
@@ -1,3 +1,184 @@
# 1.7.2 (July 6, 2021)
Forward ports 1.5.1 fixes.
### Fixed
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
# 1.7.1 (June 18, 2021)
### Fixed
- runtime: fix early task shutdown during runtime shutdown ([#3870])
[#3870]: https://github.com/tokio-rs/tokio/pull/3870
# 1.7.0 (June 15, 2021)
### Added
- net: add named pipes on windows ([#3760])
- net: add `TcpSocket` from `std::net::TcpStream` conversion ([#3838])
- sync: add `receiver_count` to `watch::Sender` ([#3729])
- sync: export `sync::notify::Notified` future publicly ([#3840])
- tracing: instrument task wakers ([#3836])
### Fixed
- macros: suppress `clippy::default_numeric_fallback` lint in generated code ([#3831])
- runtime: immediately drop new tasks when runtime is shut down ([#3752])
- sync: deprecate unused `mpsc::RecvError` type ([#3833])
### Documented
- io: clarify EOF condition for `AsyncReadExt::read_buf` ([#3850])
- io: clarify limits on return values of `AsyncWrite::poll_write` ([#3820])
- sync: add examples to Semaphore ([#3808])
[#3729]: https://github.com/tokio-rs/tokio/pull/3729
[#3752]: https://github.com/tokio-rs/tokio/pull/3752
[#3760]: https://github.com/tokio-rs/tokio/pull/3760
[#3808]: https://github.com/tokio-rs/tokio/pull/3808
[#3820]: https://github.com/tokio-rs/tokio/pull/3820
[#3831]: https://github.com/tokio-rs/tokio/pull/3831
[#3833]: https://github.com/tokio-rs/tokio/pull/3833
[#3836]: https://github.com/tokio-rs/tokio/pull/3836
[#3838]: https://github.com/tokio-rs/tokio/pull/3838
[#3840]: https://github.com/tokio-rs/tokio/pull/3840
[#3850]: https://github.com/tokio-rs/tokio/pull/3850
# 1.6.3 (July 6, 2021)
Forward ports 1.5.1 fixes.
### Fixed
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
# 1.6.2 (June 14, 2021)
### Fixes
- test: sub-ms `time:advance` regression introduced in 1.6 ([#3852])
[#3852]: https://github.com/tokio-rs/tokio/pull/3852
# 1.6.1 (May 28, 2021)
This release reverts [#3518] because it doesn't work on some kernels due to
a kernel bug. ([#3803])
[#3518]: https://github.com/tokio-rs/tokio/issues/3518
[#3803]: https://github.com/tokio-rs/tokio/issues/3803
# 1.6.0 (May 14, 2021)
### Added
- fs: try doing a non-blocking read before punting to the threadpool ([#3518])
- io: add `write_all_buf` to `AsyncWriteExt` ([#3737])
- io: implement `AsyncSeek` for `BufReader`, `BufWriter`, and `BufStream` ([#3491])
- net: support non-blocking vectored I/O ([#3761])
- sync: add `mpsc::Sender::{reserve_owned, try_reserve_owned}` ([#3704])
- sync: add a `MutexGuard::map` method that returns a `MappedMutexGuard` ([#2472])
- time: add getter for Interval's period ([#3705])
### Fixed
- io: wake pending writers on `DuplexStream` close ([#3756])
- process: avoid redundant effort to reap orphan processes ([#3743])
- signal: use `std::os::raw::c_int` instead of `libc::c_int` on public API ([#3774])
- sync: preserve permit state in `notify_waiters` ([#3660])
- task: update `JoinHandle` panic message ([#3727])
- time: prevent `time::advance` from going too far ([#3712])
### Documented
- net: hide `net::unix::datagram` module from docs ([#3775])
- process: updated example ([#3748])
- sync: `Barrier` doc should use task, not thread ([#3780])
- task: update documentation on `block_in_place` ([#3753])
[#2472]: https://github.com/tokio-rs/tokio/pull/2472
[#3491]: https://github.com/tokio-rs/tokio/pull/3491
[#3518]: https://github.com/tokio-rs/tokio/pull/3518
[#3660]: https://github.com/tokio-rs/tokio/pull/3660
[#3704]: https://github.com/tokio-rs/tokio/pull/3704
[#3705]: https://github.com/tokio-rs/tokio/pull/3705
[#3712]: https://github.com/tokio-rs/tokio/pull/3712
[#3727]: https://github.com/tokio-rs/tokio/pull/3727
[#3737]: https://github.com/tokio-rs/tokio/pull/3737
[#3743]: https://github.com/tokio-rs/tokio/pull/3743
[#3748]: https://github.com/tokio-rs/tokio/pull/3748
[#3753]: https://github.com/tokio-rs/tokio/pull/3753
[#3756]: https://github.com/tokio-rs/tokio/pull/3756
[#3761]: https://github.com/tokio-rs/tokio/pull/3761
[#3774]: https://github.com/tokio-rs/tokio/pull/3774
[#3775]: https://github.com/tokio-rs/tokio/pull/3775
[#3780]: https://github.com/tokio-rs/tokio/pull/3780
# 1.5.1 (July 6, 2021)
### Fixed
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
# 1.5.0 (April 12, 2021)
### Added
- io: add `AsyncSeekExt::stream_position` ([#3650])
- io: add `AsyncWriteExt::write_vectored` ([#3678])
- io: add a `copy_bidirectional` utility ([#3572])
- net: implement `IntoRawFd` for `TcpSocket` ([#3684])
- sync: add `OnceCell` ([#3591])
- sync: add `OwnedRwLockReadGuard` and `OwnedRwLockWriteGuard` ([#3340])
- sync: add `Semaphore::is_closed` ([#3673])
- sync: add `mpsc::Sender::capacity` ([#3690])
- sync: allow configuring `RwLock` max reads ([#3644])
- task: add `sync_scope` for `LocalKey` ([#3612])
### Fixed
- chore: try to avoid `noalias` attributes on intrusive linked list ([#3654])
- rt: fix panic in `JoinHandle::abort()` when called from other threads ([#3672])
- sync: don't panic in `oneshot::try_recv` ([#3674])
- sync: fix notifications getting dropped on receiver drop ([#3652])
- sync: fix `Semaphore` permit overflow calculation ([#3644])
### Documented
- io: clarify requirements of `AsyncFd` ([#3635])
- runtime: fix unclear docs for `{Handle,Runtime}::block_on` ([#3628])
- sync: document that `Semaphore` is fair ([#3693])
- sync: improve doc on blocking mutex ([#3645])
[#3340]: https://github.com/tokio-rs/tokio/pull/3340
[#3572]: https://github.com/tokio-rs/tokio/pull/3572
[#3591]: https://github.com/tokio-rs/tokio/pull/3591
[#3612]: https://github.com/tokio-rs/tokio/pull/3612
[#3628]: https://github.com/tokio-rs/tokio/pull/3628
[#3635]: https://github.com/tokio-rs/tokio/pull/3635
[#3644]: https://github.com/tokio-rs/tokio/pull/3644
[#3645]: https://github.com/tokio-rs/tokio/pull/3645
[#3650]: https://github.com/tokio-rs/tokio/pull/3650
[#3652]: https://github.com/tokio-rs/tokio/pull/3652
[#3654]: https://github.com/tokio-rs/tokio/pull/3654
[#3672]: https://github.com/tokio-rs/tokio/pull/3672
[#3673]: https://github.com/tokio-rs/tokio/pull/3673
[#3674]: https://github.com/tokio-rs/tokio/pull/3674
[#3678]: https://github.com/tokio-rs/tokio/pull/3678
[#3684]: https://github.com/tokio-rs/tokio/pull/3684
[#3690]: https://github.com/tokio-rs/tokio/pull/3690
[#3693]: https://github.com/tokio-rs/tokio/pull/3693
# 1.4.0 (March 20, 2021)
### Added
+9 -4
View File
@@ -7,12 +7,12 @@ name = "tokio"
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.0.x" git tag.
version = "1.4.0"
version = "1.7.2"
edition = "2018"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
readme = "README.md"
documentation = "https://docs.rs/tokio/1.4.0/tokio/"
documentation = "https://docs.rs/tokio/1.7.2/tokio/"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
description = """
@@ -54,6 +54,7 @@ net = [
"mio/tcp",
"mio/udp",
"mio/uds",
"winapi/namedpipeapi",
]
process = [
"bytes",
@@ -115,17 +116,21 @@ version = "0.3.8"
default-features = false
optional = true
[target.'cfg(windows)'.dev-dependencies.ntapi]
version = "0.3.6"
[dev-dependencies]
tokio-test = { version = "0.4.0", path = "../tokio-test" }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
futures = { version = "0.3.0", features = ["async-await"] }
proptest = "0.10.0"
proptest = "1"
rand = "0.8.0"
tempfile = "3.1.0"
async-stream = "0.3"
socket2 = "0.4"
[target.'cfg(loom)'.dev-dependencies]
loom = { version = "0.4", features = ["futures", "checkpoint"] }
loom = { version = "0.5", features = ["futures", "checkpoint"] }
[build-dependencies]
autocfg = "1" # Needed for conditionally enabling `track-caller`
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2020 Tokio Contributors
Copyright (c) 2021 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+23
View File
@@ -0,0 +1,23 @@
//! Types which are documented locally in the Tokio crate, but does not actually
//! live here.
//!
//! **Note** this module is only visible on docs.rs, you cannot use it directly
//! in your own code.
/// The name of a type which is not defined here.
///
/// This is typically used as an alias for another type, like so:
///
/// ```rust,ignore
/// /// See [some::other::location](https://example.com).
/// type DEFINED_ELSEWHERE = crate::doc::NotDefinedHere;
/// ```
///
/// This type is uninhabitable like the [`never` type] to ensure that no one
/// will ever accidentally use it.
///
/// [`never` type]: https://doc.rust-lang.org/std/primitive.never.html
pub enum NotDefinedHere {}
pub mod os;
pub mod winapi;
+26
View File
@@ -0,0 +1,26 @@
//! See [std::os](https://doc.rust-lang.org/std/os/index.html).
/// Platform-specific extensions to `std` for Windows.
///
/// See [std::os::windows](https://doc.rust-lang.org/std/os/windows/index.html).
pub mod windows {
/// Windows-specific extensions to general I/O primitives.
///
/// See [std::os::windows::io](https://doc.rust-lang.org/std/os/windows/io/index.html).
pub mod io {
/// See [std::os::windows::io::RawHandle](https://doc.rust-lang.org/std/os/windows/io/type.RawHandle.html)
pub type RawHandle = crate::doc::NotDefinedHere;
/// See [std::os::windows::io::AsRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html)
pub trait AsRawHandle {
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle)
fn as_raw_handle(&self) -> RawHandle;
}
/// See [std::os::windows::io::FromRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html)
pub trait FromRawHandle {
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html#tymethod.from_raw_handle)
unsafe fn from_raw_handle(handle: RawHandle) -> Self;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
//! See [winapi].
//!
//! [winapi]: https://docs.rs/winapi
/// See [winapi::shared](https://docs.rs/winapi/*/winapi/shared/index.html).
pub mod shared {
/// See [winapi::shared::winerror](https://docs.rs/winapi/*/winapi/shared/winerror/index.html).
#[allow(non_camel_case_types)]
pub mod winerror {
/// See [winapi::shared::winerror::ERROR_ACCESS_DENIED][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_ACCESS_DENIED.html
pub type ERROR_ACCESS_DENIED = crate::doc::NotDefinedHere;
/// See [winapi::shared::winerror::ERROR_PIPE_BUSY][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_PIPE_BUSY.html
pub type ERROR_PIPE_BUSY = crate::doc::NotDefinedHere;
/// See [winapi::shared::winerror::ERROR_MORE_DATA][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_MORE_DATA.html
pub type ERROR_MORE_DATA = crate::doc::NotDefinedHere;
}
}
/// See [winapi::um](https://docs.rs/winapi/*/winapi/um/index.html).
pub mod um {
/// See [winapi::um::winbase](https://docs.rs/winapi/*/winapi/um/winbase/index.html).
#[allow(non_camel_case_types)]
pub mod winbase {
/// See [winapi::um::winbase::PIPE_TYPE_MESSAGE][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_MESSAGE.html
pub type PIPE_TYPE_MESSAGE = crate::doc::NotDefinedHere;
/// See [winapi::um::winbase::PIPE_TYPE_BYTE][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_BYTE.html
pub type PIPE_TYPE_BYTE = crate::doc::NotDefinedHere;
/// See [winapi::um::winbase::PIPE_CLIENT_END][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_CLIENT_END.html
pub type PIPE_CLIENT_END = crate::doc::NotDefinedHere;
/// See [winapi::um::winbase::PIPE_SERVER_END][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_SERVER_END.html
pub type PIPE_SERVER_END = crate::doc::NotDefinedHere;
/// See [winapi::um::winbase::SECURITY_IDENTIFICATION][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.SECURITY_IDENTIFICATION.html
pub type SECURITY_IDENTIFICATION = crate::doc::NotDefinedHere;
}
/// See [winapi::um::minwinbase](https://docs.rs/winapi/*/winapi/um/minwinbase/index.html).
#[allow(non_camel_case_types)]
pub mod minwinbase {
/// See [winapi::um::minwinbase::SECURITY_ATTRIBUTES][winapi]
///
/// [winapi]: https://docs.rs/winapi/*/winapi/um/minwinbase/constant.SECURITY_ATTRIBUTES.html
pub type SECURITY_ATTRIBUTES = crate::doc::NotDefinedHere;
}
}
+11
View File
@@ -22,3 +22,14 @@ cfg_sync! {
mod block_on;
pub(crate) use block_on::block_on;
}
cfg_trace! {
mod trace;
pub(crate) use trace::InstrumentedFuture as Future;
}
cfg_not_trace! {
cfg_rt! {
pub(crate) use std::future::Future;
}
}
+11
View File
@@ -0,0 +1,11 @@
use std::future::Future;
pub(crate) trait InstrumentedFuture: Future {
fn id(&self) -> Option<tracing::Id>;
}
impl<F: Future> InstrumentedFuture for tracing::instrument::Instrumented<F> {
fn id(&self) -> Option<tracing::Id> {
self.span().id()
}
}
+13 -11
View File
@@ -8,7 +8,8 @@ use std::{task::Context, task::Poll};
/// Associates an IO object backed by a Unix file descriptor with the tokio
/// reactor, allowing for readiness to be polled. The file descriptor must be of
/// a type that can be used with the OS polling facilities (ie, `poll`, `epoll`,
/// `kqueue`, etc), such as a network socket or pipe.
/// `kqueue`, etc), such as a network socket or pipe, and the file descriptor
/// must have the nonblocking mode set to true.
///
/// Creating an AsyncFd registers the file descriptor with the current tokio
/// Reactor, allowing you to directly await the file descriptor being readable
@@ -36,18 +37,19 @@ use std::{task::Context, task::Poll};
///
/// On some platforms, the readiness detecting mechanism relies on
/// edge-triggered notifications. This means that the OS will only notify Tokio
/// when the file descriptor transitions from not-ready to ready. Tokio
/// internally tracks when it has received a ready notification, and when
/// when the file descriptor transitions from not-ready to ready. For this to
/// work you should first try to read or write and only poll for readiness
/// if that fails with an error of [`std::io::ErrorKind::WouldBlock`].
///
/// Tokio internally tracks when it has received a ready notification, and when
/// readiness checking functions like [`readable`] and [`writable`] are called,
/// if the readiness flag is set, these async functions will complete
/// immediately.
///
/// This however does mean that it is critical to ensure that this ready flag is
/// cleared when (and only when) the file descriptor ceases to be ready. The
/// [`AsyncFdReadyGuard`] returned from readiness checking functions serves this
/// function; after calling a readiness-checking async function, you must use
/// this [`AsyncFdReadyGuard`] to signal to tokio whether the file descriptor is no
/// longer in a ready state.
/// immediately. This however does mean that it is critical to ensure that this
/// ready flag is cleared when (and only when) the file descriptor ceases to be
/// ready. The [`AsyncFdReadyGuard`] returned from readiness checking functions
/// serves this function; after calling a readiness-checking async function,
/// you must use this [`AsyncFdReadyGuard`] to signal to tokio whether the file
/// descriptor is no longer in a ready state.
///
/// ## Use with to a poll-based API
///
+5 -1
View File
@@ -45,7 +45,11 @@ use std::task::{Context, Poll};
pub trait AsyncWrite {
/// Attempt to write bytes from `buf` into the object.
///
/// On success, returns `Poll::Ready(Ok(num_bytes_written))`.
/// On success, returns `Poll::Ready(Ok(num_bytes_written))`. If successful,
/// then it must be guaranteed that `n <= buf.len()`. A return value of `0`
/// typically means that the underlying object is no longer able to accept
/// bytes and will likely not be able to in the future as well, or that the
/// buffer provided is empty.
///
/// If the object is not ready for writing, the method returns
/// `Poll::Pending` and arranges for the current task (via
+1 -1
View File
@@ -273,7 +273,7 @@ cfg_not_rt! {
/// This function panics if there is no current reactor set, or if the `rt`
/// feature flag is not enabled.
pub(super) fn current() -> Self {
panic!(crate::util::error::CONTEXT_MISSING_ERROR)
panic!("{}", crate::util::error::CONTEXT_MISSING_ERROR)
}
}
}
+4 -3
View File
@@ -14,8 +14,9 @@ cfg_io_driver! {
/// that it will receive task notifications on readiness. This is the lowest
/// level API for integrating with a reactor.
///
/// The association between an I/O resource is made by calling [`new`]. Once
/// the association is established, it remains established until the
/// The association between an I/O resource is made by calling
/// [`new_with_interest_and_handle`].
/// Once the association is established, it remains established until the
/// registration instance is dropped.
///
/// A registration instance represents two separate readiness streams. One
@@ -36,7 +37,7 @@ cfg_io_driver! {
/// stream. The write readiness event stream is only for `Ready::writable()`
/// events.
///
/// [`new`]: method@Self::new
/// [`new_with_interest_and_handle`]: method@Self::new_with_interest_and_handle
/// [`poll_read_ready`]: method@Self::poll_read_ready`
/// [`poll_write_ready`]: method@Self::poll_write_ready`
#[derive(Debug)]
+1 -1
View File
@@ -246,7 +246,7 @@ cfg_io_util! {
pub(crate) mod seek;
pub(crate) mod util;
pub use util::{
copy, copy_buf, duplex, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt,
copy, copy_bidirectional, copy_buf, duplex, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt,
BufReader, BufStream, BufWriter, DuplexStream, Empty, Lines, Repeat, Sink, Split, Take,
};
}
+10 -16
View File
@@ -10,10 +10,10 @@ cfg_io_driver! {
/// [`std::io::Write`] traits with the reactor that drives it.
///
/// `PollEvented` uses [`Registration`] internally to take a type that
/// implements [`mio::Evented`] as well as [`std::io::Read`] and or
/// implements [`mio::event::Source`] as well as [`std::io::Read`] and or
/// [`std::io::Write`] and associate it with a reactor that will drive it.
///
/// Once the [`mio::Evented`] type is wrapped by `PollEvented`, it can be
/// Once the [`mio::event::Source`] type is wrapped by `PollEvented`, it can be
/// used from within the future's execution model. As such, the
/// `PollEvented` type provides [`AsyncRead`] and [`AsyncWrite`]
/// implementations using the underlying I/O resource as well as readiness
@@ -40,13 +40,13 @@ cfg_io_driver! {
/// [`poll_read_ready`] again will also indicate read readiness.
///
/// When the operation is attempted and is unable to succeed due to the I/O
/// resource not being ready, the caller must call [`clear_read_ready`] or
/// [`clear_write_ready`]. This clears the readiness state until a new
/// resource not being ready, the caller must call `clear_read_ready` or
/// `clear_write_ready`. This clears the readiness state until a new
/// readiness event is received.
///
/// This allows the caller to implement additional functions. For example,
/// [`TcpListener`] implements poll_accept by using [`poll_read_ready`] and
/// [`clear_read_ready`].
/// `clear_read_ready`.
///
/// ## Platform-specific events
///
@@ -54,17 +54,11 @@ cfg_io_driver! {
/// These events are included as part of the read readiness event stream. The
/// write readiness event stream is only for `Ready::writable()` events.
///
/// [`std::io::Read`]: trait@std::io::Read
/// [`std::io::Write`]: trait@std::io::Write
/// [`AsyncRead`]: trait@AsyncRead
/// [`AsyncWrite`]: trait@AsyncWrite
/// [`mio::Evented`]: trait@mio::Evented
/// [`Registration`]: struct@Registration
/// [`TcpListener`]: struct@crate::net::TcpListener
/// [`clear_read_ready`]: method@Self::clear_read_ready
/// [`clear_write_ready`]: method@Self::clear_write_ready
/// [`poll_read_ready`]: method@Self::poll_read_ready
/// [`poll_write_ready`]: method@Self::poll_write_ready
/// [`AsyncRead`]: crate::io::AsyncRead
/// [`AsyncWrite`]: crate::io::AsyncWrite
/// [`TcpListener`]: crate::net::TcpListener
/// [`poll_read_ready`]: Registration::poll_read_ready
/// [`poll_write_ready`]: Registration::poll_write_ready
pub(crate) struct PollEvented<E: Source> {
io: Option<E>,
registration: Registration,
+12 -5
View File
@@ -108,6 +108,8 @@ cfg_io_util! {
/// This function does not provide any guarantees about whether it
/// completes immediately or asynchronously
///
/// # Return
///
/// If the return value of this method is `Ok(n)`, then it must be
/// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value indicates
/// that the buffer `buf` has been filled in with `n` bytes of data from
@@ -180,9 +182,14 @@ cfg_io_util! {
///
/// # Return
///
/// On a successful read, the number of read bytes is returned. If the
/// supplied buffer is not empty and the function returns `Ok(0)` then
/// the source has reached an "end-of-file" event.
/// A nonzero `n` value indicates that the buffer `buf` has been filled
/// in with `n` bytes of data from this source. If `n` is `0`, then it
/// can indicate one of two scenarios:
///
/// 1. This reader has reached its "end of file" and will likely no longer
/// be able to produce bytes. Note that this does not mean that the
/// reader will *always* no longer be able to produce bytes.
/// 2. The buffer specified had a remaining capacity of zero.
///
/// # Errors
///
@@ -579,7 +586,7 @@ cfg_io_util! {
/// async fn main() -> io::Result<()> {
/// let mut reader = Cursor::new(vec![0x80, 0, 0, 0, 0, 0, 0, 0]);
///
/// assert_eq!(i64::min_value(), reader.read_i64().await?);
/// assert_eq!(i64::MIN, reader.read_i64().await?);
/// Ok(())
/// }
/// ```
@@ -659,7 +666,7 @@ cfg_io_util! {
/// 0, 0, 0, 0, 0, 0, 0, 0
/// ]);
///
/// assert_eq!(i128::min_value(), reader.read_i128().await?);
/// assert_eq!(i128::MIN, reader.read_i128().await?);
/// Ok(())
/// }
/// ```
+11
View File
@@ -66,6 +66,17 @@ cfg_io_util! {
{
seek(self, pos)
}
/// Creates a future which will return the current seek position from the
/// start of the stream.
///
/// This is equivalent to `self.seek(SeekFrom::Current(0))`.
fn stream_position(&mut self) -> Seek<'_, Self>
where
Self: Unpin,
{
self.seek(SeekFrom::Current(0))
}
}
}
+104 -7
View File
@@ -2,6 +2,7 @@ use crate::io::util::flush::{flush, Flush};
use crate::io::util::shutdown::{shutdown, Shutdown};
use crate::io::util::write::{write, Write};
use crate::io::util::write_all::{write_all, WriteAll};
use crate::io::util::write_all_buf::{write_all_buf, WriteAllBuf};
use crate::io::util::write_buf::{write_buf, WriteBuf};
use crate::io::util::write_int::{
WriteI128, WriteI128Le, WriteI16, WriteI16Le, WriteI32, WriteI32Le, WriteI64, WriteI64Le,
@@ -11,7 +12,9 @@ use crate::io::util::write_int::{
WriteU128, WriteU128Le, WriteU16, WriteU16Le, WriteU32, WriteU32Le, WriteU64, WriteU64Le,
WriteU8,
};
use crate::io::util::write_vectored::{write_vectored, WriteVectored};
use crate::io::AsyncWrite;
use std::io::IoSlice;
use bytes::Buf;
@@ -116,6 +119,46 @@ cfg_io_util! {
write(self, src)
}
/// Like [`write`], except that it writes from a slice of buffers.
///
/// Equivalent to:
///
/// ```ignore
/// async fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize>;
/// ```
///
/// See [`AsyncWrite::poll_write_vectored`] for more details.
///
/// # Examples
///
/// ```no_run
/// use tokio::io::{self, AsyncWriteExt};
/// use tokio::fs::File;
/// use std::io::IoSlice;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt").await?;
///
/// let bufs: &[_] = &[
/// IoSlice::new(b"hello"),
/// IoSlice::new(b" "),
/// IoSlice::new(b"world"),
/// ];
///
/// file.write_vectored(&bufs).await?;
///
/// Ok(())
/// }
/// ```
///
/// [`write`]: AsyncWriteExt::write
fn write_vectored<'a, 'b>(&'a mut self, bufs: &'a [IoSlice<'b>]) -> WriteVectored<'a, 'b, Self>
where
Self: Unpin,
{
write_vectored(self, bufs)
}
/// Writes a buffer into this writer, advancing the buffer's internal
/// cursor.
@@ -154,10 +197,11 @@ cfg_io_util! {
///
/// # Examples
///
/// [`File`] implements `Read` and [`Cursor<&[u8]>`] implements [`Buf`]:
/// [`File`] implements [`AsyncWrite`] and [`Cursor`]`<&[u8]>` implements [`Buf`]:
///
/// [`File`]: crate::fs::File
/// [`Buf`]: bytes::Buf
/// [`Cursor`]: std::io::Cursor
///
/// ```no_run
/// use tokio::io::{self, AsyncWriteExt};
@@ -190,6 +234,59 @@ cfg_io_util! {
write_buf(self, src)
}
/// Attempts to write an entire buffer into this writer
///
/// Equivalent to:
///
/// ```ignore
/// async fn write_all_buf(&mut self, buf: impl Buf) -> Result<(), io::Error> {
/// while buf.has_remaining() {
/// self.write_buf(&mut buf).await?;
/// }
/// }
/// ```
///
/// This method will continuously call [`write`] until
/// [`buf.has_remaining()`](bytes::Buf::has_remaining) returns false. This method will not
/// return until the entire buffer has been successfully written or an error occurs. The
/// first error generated will be returned.
///
/// The buffer is advanced after each chunk is successfully written. After failure,
/// `src.chunk()` will return the chunk that failed to write.
///
/// # Examples
///
/// [`File`] implements [`AsyncWrite`] and [`Cursor`]`<&[u8]>` implements [`Buf`]:
///
/// [`File`]: crate::fs::File
/// [`Buf`]: bytes::Buf
/// [`Cursor`]: std::io::Cursor
///
/// ```no_run
/// use tokio::io::{self, AsyncWriteExt};
/// use tokio::fs::File;
///
/// use std::io::Cursor;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt").await?;
/// let mut buffer = Cursor::new(b"data to write");
///
/// file.write_all_buf(&mut buffer).await?;
/// Ok(())
/// }
/// ```
///
/// [`write`]: AsyncWriteExt::write
fn write_all_buf<'a, B>(&'a mut self, src: &'a mut B) -> WriteAllBuf<'a, Self, B>
where
Self: Sized + Unpin,
B: Buf,
{
write_all_buf(self, src)
}
/// Attempts to write an entire buffer into this writer.
///
/// Equivalent to:
@@ -524,8 +621,8 @@ cfg_io_util! {
/// async fn main() -> io::Result<()> {
/// let mut writer = Vec::new();
///
/// writer.write_i64(i64::min_value()).await?;
/// writer.write_i64(i64::max_value()).await?;
/// writer.write_i64(i64::MIN).await?;
/// writer.write_i64(i64::MAX).await?;
///
/// assert_eq!(writer, b"\x80\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff");
/// Ok(())
@@ -602,7 +699,7 @@ cfg_io_util! {
/// async fn main() -> io::Result<()> {
/// let mut writer = Vec::new();
///
/// writer.write_i128(i128::min_value()).await?;
/// writer.write_i128(i128::MIN).await?;
///
/// assert_eq!(writer, vec![
/// 0x80, 0, 0, 0, 0, 0, 0, 0,
@@ -833,8 +930,8 @@ cfg_io_util! {
/// async fn main() -> io::Result<()> {
/// let mut writer = Vec::new();
///
/// writer.write_i64_le(i64::min_value()).await?;
/// writer.write_i64_le(i64::max_value()).await?;
/// writer.write_i64_le(i64::MIN).await?;
/// writer.write_i64_le(i64::MAX).await?;
///
/// assert_eq!(writer, b"\x00\x00\x00\x00\x00\x00\x00\x80\xff\xff\xff\xff\xff\xff\xff\x7f");
/// Ok(())
@@ -911,7 +1008,7 @@ cfg_io_util! {
/// async fn main() -> io::Result<()> {
/// let mut writer = Vec::new();
///
/// writer.write_i128_le(i128::min_value()).await?;
/// writer.write_i128_le(i128::MIN).await?;
///
/// assert_eq!(writer, vec![
/// 0, 0, 0, 0, 0, 0, 0,
+121 -3
View File
@@ -1,11 +1,11 @@
use crate::io::util::DEFAULT_BUF_SIZE;
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use pin_project_lite::pin_project;
use std::io;
use std::io::{self, SeekFrom};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{cmp, fmt};
use std::{cmp, fmt, mem};
pin_project! {
/// The `BufReader` struct adds buffering to any reader.
@@ -30,6 +30,7 @@ pin_project! {
pub(super) buf: Box<[u8]>,
pub(super) pos: usize,
pub(super) cap: usize,
pub(super) seek_state: SeekState,
}
}
@@ -48,6 +49,7 @@ impl<R: AsyncRead> BufReader<R> {
buf: buffer.into_boxed_slice(),
pos: 0,
cap: 0,
seek_state: SeekState::Init,
}
}
@@ -141,6 +143,122 @@ impl<R: AsyncRead> AsyncBufRead for BufReader<R> {
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum SeekState {
/// start_seek has not been called.
Init,
/// start_seek has been called, but poll_complete has not yet been called.
Start(SeekFrom),
/// Waiting for completion of the first poll_complete in the `n.checked_sub(remainder).is_none()` branch.
PendingOverflowed(i64),
/// Waiting for completion of poll_complete.
Pending,
}
/// Seek to an offset, in bytes, in the underlying reader.
///
/// The position used for seeking with `SeekFrom::Current(_)` is the
/// position the underlying reader would be at if the `BufReader` had no
/// internal buffer.
///
/// Seeking always discards the internal buffer, even if the seek position
/// would otherwise fall within it. This guarantees that calling
/// `.into_inner()` immediately after a seek yields the underlying reader
/// at the same position.
///
/// See [`AsyncSeek`] for more details.
///
/// Note: In the edge case where you're seeking with `SeekFrom::Current(n)`
/// where `n` minus the internal buffer length overflows an `i64`, two
/// seeks will be performed instead of one. If the second seek returns
/// `Err`, the underlying reader will be left at the same position it would
/// have if you called `seek` with `SeekFrom::Current(0)`.
impl<R: AsyncRead + AsyncSeek> AsyncSeek for BufReader<R> {
fn start_seek(self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> {
// We needs to call seek operation multiple times.
// And we should always call both start_seek and poll_complete,
// as start_seek alone cannot guarantee that the operation will be completed.
// poll_complete receives a Context and returns a Poll, so it cannot be called
// inside start_seek.
*self.project().seek_state = SeekState::Start(pos);
Ok(())
}
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
let res = match mem::replace(self.as_mut().project().seek_state, SeekState::Init) {
SeekState::Init => {
// 1.x AsyncSeek recommends calling poll_complete before start_seek.
// We don't have to guarantee that the value returned by
// poll_complete called without start_seek is correct,
// so we'll return 0.
return Poll::Ready(Ok(0));
}
SeekState::Start(SeekFrom::Current(n)) => {
let remainder = (self.cap - self.pos) as i64;
// it should be safe to assume that remainder fits within an i64 as the alternative
// means we managed to allocate 8 exbibytes and that's absurd.
// But it's not out of the realm of possibility for some weird underlying reader to
// support seeking by i64::MIN so we need to handle underflow when subtracting
// remainder.
if let Some(offset) = n.checked_sub(remainder) {
self.as_mut()
.get_pin_mut()
.start_seek(SeekFrom::Current(offset))?;
self.as_mut().get_pin_mut().poll_complete(cx)?
} else {
// seek backwards by our remainder, and then by the offset
self.as_mut()
.get_pin_mut()
.start_seek(SeekFrom::Current(-remainder))?;
if self.as_mut().get_pin_mut().poll_complete(cx)?.is_pending() {
*self.as_mut().project().seek_state = SeekState::PendingOverflowed(n);
return Poll::Pending;
}
// https://github.com/rust-lang/rust/pull/61157#issuecomment-495932676
self.as_mut().discard_buffer();
self.as_mut()
.get_pin_mut()
.start_seek(SeekFrom::Current(n))?;
self.as_mut().get_pin_mut().poll_complete(cx)?
}
}
SeekState::PendingOverflowed(n) => {
if self.as_mut().get_pin_mut().poll_complete(cx)?.is_pending() {
*self.as_mut().project().seek_state = SeekState::PendingOverflowed(n);
return Poll::Pending;
}
// https://github.com/rust-lang/rust/pull/61157#issuecomment-495932676
self.as_mut().discard_buffer();
self.as_mut()
.get_pin_mut()
.start_seek(SeekFrom::Current(n))?;
self.as_mut().get_pin_mut().poll_complete(cx)?
}
SeekState::Start(pos) => {
// Seeking with Start/End doesn't care about our buffer length.
self.as_mut().get_pin_mut().start_seek(pos)?;
self.as_mut().get_pin_mut().poll_complete(cx)?
}
SeekState::Pending => self.as_mut().get_pin_mut().poll_complete(cx)?,
};
match res {
Poll::Ready(res) => {
self.discard_buffer();
Poll::Ready(Ok(res))
}
Poll::Pending => {
*self.as_mut().project().seek_state = SeekState::Pending;
Poll::Pending
}
}
}
}
impl<R: AsyncRead + AsyncWrite> AsyncWrite for BufReader<R> {
fn poll_write(
self: Pin<&mut Self>,
+34 -2
View File
@@ -1,8 +1,8 @@
use crate::io::util::{BufReader, BufWriter};
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use pin_project_lite::pin_project;
use std::io;
use std::io::{self, SeekFrom};
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -94,9 +94,11 @@ impl<RW> From<BufWriter<BufReader<RW>>> for BufStream<RW> {
buf: rbuf,
pos,
cap,
seek_state: rseek_state,
},
buf: wbuf,
written,
seek_state: wseek_state,
} = b;
BufStream {
@@ -105,10 +107,12 @@ impl<RW> From<BufWriter<BufReader<RW>>> for BufStream<RW> {
inner,
buf: wbuf,
written,
seek_state: wseek_state,
},
buf: rbuf,
pos,
cap,
seek_state: rseek_state,
},
}
}
@@ -142,6 +146,34 @@ impl<RW: AsyncRead + AsyncWrite> AsyncRead for BufStream<RW> {
}
}
/// Seek to an offset, in bytes, in the underlying stream.
///
/// The position used for seeking with `SeekFrom::Current(_)` is the
/// position the underlying stream would be at if the `BufStream` had no
/// internal buffer.
///
/// Seeking always discards the internal buffer, even if the seek position
/// would otherwise fall within it. This guarantees that calling
/// `.into_inner()` immediately after a seek yields the underlying reader
/// at the same position.
///
/// See [`AsyncSeek`] for more details.
///
/// Note: In the edge case where you're seeking with `SeekFrom::Current(n)`
/// where `n` minus the internal buffer length overflows an `i64`, two
/// seeks will be performed instead of one. If the second seek returns
/// `Err`, the underlying reader will be left at the same position it would
/// have if you called `seek` with `SeekFrom::Current(0)`.
impl<RW: AsyncRead + AsyncWrite + AsyncSeek> AsyncSeek for BufStream<RW> {
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
self.project().inner.start_seek(position)
}
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
self.project().inner.poll_complete(cx)
}
}
impl<RW: AsyncRead + AsyncWrite> AsyncBufRead for BufStream<RW> {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
self.project().inner.poll_fill_buf(cx)
+60 -2
View File
@@ -1,9 +1,9 @@
use crate::io::util::DEFAULT_BUF_SIZE;
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use pin_project_lite::pin_project;
use std::fmt;
use std::io::{self, Write};
use std::io::{self, SeekFrom, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -34,6 +34,7 @@ pin_project! {
pub(super) inner: W,
pub(super) buf: Vec<u8>,
pub(super) written: usize,
pub(super) seek_state: SeekState,
}
}
@@ -50,6 +51,7 @@ impl<W: AsyncWrite> BufWriter<W> {
inner,
buf: Vec::with_capacity(cap),
written: 0,
seek_state: SeekState::Init,
}
}
@@ -142,6 +144,62 @@ impl<W: AsyncWrite> AsyncWrite for BufWriter<W> {
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum SeekState {
/// start_seek has not been called.
Init,
/// start_seek has been called, but poll_complete has not yet been called.
Start(SeekFrom),
/// Waiting for completion of poll_complete.
Pending,
}
/// Seek to the offset, in bytes, in the underlying writer.
///
/// Seeking always writes out the internal buffer before seeking.
impl<W: AsyncWrite + AsyncSeek> AsyncSeek for BufWriter<W> {
fn start_seek(self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> {
// We need to flush the internal buffer before seeking.
// It receives a `Context` and returns a `Poll`, so it cannot be called
// inside `start_seek`.
*self.project().seek_state = SeekState::Start(pos);
Ok(())
}
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
let pos = match self.seek_state {
SeekState::Init => {
return self.project().inner.poll_complete(cx);
}
SeekState::Start(pos) => Some(pos),
SeekState::Pending => None,
};
// Flush the internal buffer before seeking.
ready!(self.as_mut().flush_buf(cx))?;
let mut me = self.project();
if let Some(pos) = pos {
// Ensure previous seeks have finished before starting a new one
ready!(me.inner.as_mut().poll_complete(cx))?;
if let Err(e) = me.inner.as_mut().start_seek(pos) {
*me.seek_state = SeekState::Init;
return Poll::Ready(Err(e));
}
}
match me.inner.poll_complete(cx) {
Poll::Ready(res) => {
*me.seek_state = SeekState::Init;
Poll::Ready(res)
}
Poll::Pending => {
*me.seek_state = SeekState::Pending;
Poll::Pending
}
}
}
}
impl<W: AsyncWrite + AsyncRead> AsyncRead for BufWriter<W> {
fn poll_read(
self: Pin<&mut Self>,
+78 -50
View File
@@ -5,18 +5,85 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug)]
pub(super) struct CopyBuffer {
read_done: bool,
pos: usize,
cap: usize,
amt: u64,
buf: Box<[u8]>,
}
impl CopyBuffer {
pub(super) fn new() -> Self {
Self {
read_done: false,
pos: 0,
cap: 0,
amt: 0,
buf: vec![0; 2048].into_boxed_slice(),
}
}
pub(super) fn poll_copy<R, W>(
&mut self,
cx: &mut Context<'_>,
mut reader: Pin<&mut R>,
mut writer: Pin<&mut W>,
) -> Poll<io::Result<u64>>
where
R: AsyncRead + ?Sized,
W: AsyncWrite + ?Sized,
{
loop {
// If our buffer is empty, then we need to read some data to
// continue.
if self.pos == self.cap && !self.read_done {
let me = &mut *self;
let mut buf = ReadBuf::new(&mut me.buf);
ready!(reader.as_mut().poll_read(cx, &mut buf))?;
let n = buf.filled().len();
if n == 0 {
self.read_done = true;
} else {
self.pos = 0;
self.cap = n;
}
}
// If our buffer has some data, let's write it out!
while self.pos < self.cap {
let me = &mut *self;
let i = ready!(writer.as_mut().poll_write(cx, &me.buf[me.pos..me.cap]))?;
if i == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"write zero byte into writer",
)));
} else {
self.pos += i;
self.amt += i as u64;
}
}
// If we've written all the data and we've seen EOF, flush out the
// data and finish the transfer.
if self.pos == self.cap && self.read_done {
ready!(writer.as_mut().poll_flush(cx))?;
return Poll::Ready(Ok(self.amt));
}
}
}
}
/// A future that asynchronously copies the entire contents of a reader into a
/// writer.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct Copy<'a, R: ?Sized, W: ?Sized> {
reader: &'a mut R,
read_done: bool,
writer: &'a mut W,
pos: usize,
cap: usize,
amt: u64,
buf: Box<[u8]>,
buf: CopyBuffer,
}
cfg_io_util! {
@@ -35,8 +102,8 @@ cfg_io_util! {
///
/// # Errors
///
/// The returned future will finish with an error will return an error
/// immediately if any call to `poll_read` or `poll_write` returns an error.
/// The returned future will return an error immediately if any call to
/// `poll_read` or `poll_write` returns an error.
///
/// # Examples
///
@@ -60,12 +127,8 @@ cfg_io_util! {
{
Copy {
reader,
read_done: false,
writer,
amt: 0,
pos: 0,
cap: 0,
buf: vec![0; 2048].into_boxed_slice(),
buf: CopyBuffer::new()
}.await
}
}
@@ -78,44 +141,9 @@ where
type Output = io::Result<u64>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
loop {
// If our buffer is empty, then we need to read some data to
// continue.
if self.pos == self.cap && !self.read_done {
let me = &mut *self;
let mut buf = ReadBuf::new(&mut me.buf);
ready!(Pin::new(&mut *me.reader).poll_read(cx, &mut buf))?;
let n = buf.filled().len();
if n == 0 {
self.read_done = true;
} else {
self.pos = 0;
self.cap = n;
}
}
let me = &mut *self;
// If our buffer has some data, let's write it out!
while self.pos < self.cap {
let me = &mut *self;
let i = ready!(Pin::new(&mut *me.writer).poll_write(cx, &me.buf[me.pos..me.cap]))?;
if i == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"write zero byte into writer",
)));
} else {
self.pos += i;
self.amt += i as u64;
}
}
// If we've written all the data and we've seen EOF, flush out the
// data and finish the transfer.
if self.pos == self.cap && self.read_done {
let me = &mut *self;
ready!(Pin::new(&mut *me.writer).poll_flush(cx))?;
return Poll::Ready(Ok(self.amt));
}
}
me.buf
.poll_copy(cx, Pin::new(&mut *me.reader), Pin::new(&mut *me.writer))
}
}
+120
View File
@@ -0,0 +1,120 @@
use super::copy::CopyBuffer;
use crate::io::{AsyncRead, AsyncWrite};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
enum TransferState {
Running(CopyBuffer),
ShuttingDown(u64),
Done(u64),
}
struct CopyBidirectional<'a, A: ?Sized, B: ?Sized> {
a: &'a mut A,
b: &'a mut B,
a_to_b: TransferState,
b_to_a: TransferState,
}
fn transfer_one_direction<A, B>(
cx: &mut Context<'_>,
state: &mut TransferState,
r: &mut A,
w: &mut B,
) -> Poll<io::Result<u64>>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
let mut r = Pin::new(r);
let mut w = Pin::new(w);
loop {
match state {
TransferState::Running(buf) => {
let count = ready!(buf.poll_copy(cx, r.as_mut(), w.as_mut()))?;
*state = TransferState::ShuttingDown(count);
}
TransferState::ShuttingDown(count) => {
ready!(w.as_mut().poll_shutdown(cx))?;
*state = TransferState::Done(*count);
}
TransferState::Done(count) => return Poll::Ready(Ok(*count)),
}
}
}
impl<'a, A, B> Future for CopyBidirectional<'a, A, B>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
type Output = io::Result<(u64, u64)>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Unpack self into mut refs to each field to avoid borrow check issues.
let CopyBidirectional {
a,
b,
a_to_b,
b_to_a,
} = &mut *self;
let a_to_b = transfer_one_direction(cx, a_to_b, &mut *a, &mut *b)?;
let b_to_a = transfer_one_direction(cx, b_to_a, &mut *b, &mut *a)?;
// It is not a problem if ready! returns early because transfer_one_direction for the
// other direction will keep returning TransferState::Done(count) in future calls to poll
let a_to_b = ready!(a_to_b);
let b_to_a = ready!(b_to_a);
Poll::Ready(Ok((a_to_b, b_to_a)))
}
}
/// Copies data in both directions between `a` and `b`.
///
/// This function returns a future that will read from both streams,
/// writing any data read to the opposing stream.
/// This happens in both directions concurrently.
///
/// If an EOF is observed on one stream, [`shutdown()`] will be invoked on
/// the other, and reading from that stream will stop. Copying of data in
/// the other direction will continue.
///
/// The future will complete successfully once both directions of communication has been shut down.
/// A direction is shut down when the reader reports EOF,
/// at which point [`shutdown()`] is called on the corresponding writer. When finished,
/// it will return a tuple of the number of bytes copied from a to b
/// and the number of bytes copied from b to a, in that order.
///
/// [`shutdown()`]: crate::io::AsyncWriteExt::shutdown
///
/// # Errors
///
/// The future will immediately return an error if any IO operation on `a`
/// or `b` returns an error. Some data read from either stream may be lost (not
/// written to the other stream) in this case.
///
/// # Return value
///
/// Returns a tuple of bytes copied `a` to `b` and bytes copied `b` to `a`.
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
pub async fn copy_bidirectional<A, B>(a: &mut A, b: &mut B) -> Result<(u64, u64), std::io::Error>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
CopyBidirectional {
a,
b,
a_to_b: TransferState::Running(CopyBuffer::new()),
b_to_a: TransferState::Running(CopyBuffer::new()),
}
.await
}
+1 -1
View File
@@ -128,7 +128,7 @@ where
}
}
Poll::Ready(Ok(Some(mem::replace(me.buf, String::new()))))
Poll::Ready(Ok(Some(mem::take(me.buf))))
}
}
+23 -3
View File
@@ -16,6 +16,14 @@ use std::{
/// that can be used as in-memory IO types. Writing to one of the pairs will
/// allow that data to be read from the other, and vice versa.
///
/// # Closing a `DuplexStream`
///
/// If one end of the `DuplexStream` channel is dropped, any pending reads on
/// the other side will continue to read data until the buffer is drained, then
/// they will signal EOF by returning 0 bytes. Any writes to the other side,
/// including pending ones (that are waiting for free space in the buffer) will
/// return `Err(BrokenPipe)` immediately.
///
/// # Example
///
/// ```
@@ -37,6 +45,7 @@ use std::{
/// # }
/// ```
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
pub struct DuplexStream {
read: Arc<Mutex<Pipe>>,
write: Arc<Mutex<Pipe>>,
@@ -72,6 +81,7 @@ struct Pipe {
///
/// The `max_buf_size` argument is the maximum amount of bytes that can be
/// written to a side before the write returns `Poll::Pending`.
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
pub fn duplex(max_buf_size: usize) -> (DuplexStream, DuplexStream) {
let one = Arc::new(Mutex::new(Pipe::new(max_buf_size)));
let two = Arc::new(Mutex::new(Pipe::new(max_buf_size)));
@@ -134,7 +144,8 @@ impl AsyncWrite for DuplexStream {
impl Drop for DuplexStream {
fn drop(&mut self) {
// notify the other side of the closure
self.write.lock().close();
self.write.lock().close_write();
self.read.lock().close_read();
}
}
@@ -151,12 +162,21 @@ impl Pipe {
}
}
fn close(&mut self) {
fn close_write(&mut self) {
self.is_closed = true;
// needs to notify any readers that no more data will come
if let Some(waker) = self.read_waker.take() {
waker.wake();
}
}
fn close_read(&mut self) {
self.is_closed = true;
// needs to notify any writers that they have to abort
if let Some(waker) = self.write_waker.take() {
waker.wake();
}
}
}
impl AsyncRead for Pipe {
@@ -217,7 +237,7 @@ impl AsyncWrite for Pipe {
mut self: Pin<&mut Self>,
_: &mut task::Context<'_>,
) -> Poll<std::io::Result<()>> {
self.close();
self.close_write();
Poll::Ready(Ok(()))
}
}
+5
View File
@@ -27,6 +27,9 @@ cfg_io_util! {
mod copy;
pub use copy::copy;
mod copy_bidirectional;
pub use copy_bidirectional::copy_bidirectional;
mod copy_buf;
pub use copy_buf::copy_buf;
@@ -71,8 +74,10 @@ cfg_io_util! {
pub use take::Take;
mod write;
mod write_vectored;
mod write_all;
mod write_buf;
mod write_all_buf;
mod write_int;
+2 -2
View File
@@ -36,7 +36,7 @@ where
{
ReadLine {
reader,
buf: mem::replace(string, String::new()).into_bytes(),
buf: mem::take(string).into_bytes(),
output: string,
read: 0,
_pin: PhantomPinned,
@@ -99,7 +99,7 @@ pub(super) fn read_line_internal<R: AsyncBufRead + ?Sized>(
read: &mut usize,
) -> Poll<io::Result<usize>> {
let io_res = ready!(read_until_internal(reader, cx, b'\n', buf, read));
let utf8_res = String::from_utf8(mem::replace(buf, Vec::new()));
let utf8_res = String::from_utf8(mem::take(buf));
// At this point both buf and output are empty. The allocation is in utf8_res.
+1 -1
View File
@@ -37,7 +37,7 @@ pub(crate) fn read_to_string<'a, R>(
where
R: AsyncRead + ?Sized + Unpin,
{
let buf = mem::replace(string, String::new()).into_bytes();
let buf = mem::take(string).into_bytes();
ReadToString {
reader,
buf: VecWithInitialized::new(buf),
+1 -1
View File
@@ -106,7 +106,7 @@ where
me.buf.pop();
}
Poll::Ready(Ok(Some(mem::replace(me.buf, Vec::new()))))
Poll::Ready(Ok(Some(mem::take(me.buf))))
}
}
+56
View File
@@ -0,0 +1,56 @@
use crate::io::AsyncWrite;
use bytes::Buf;
use pin_project_lite::pin_project;
use std::future::Future;
use std::io;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::task::{Context, Poll};
pin_project! {
/// A future to write some of the buffer to an `AsyncWrite`.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct WriteAllBuf<'a, W, B> {
writer: &'a mut W,
buf: &'a mut B,
#[pin]
_pin: PhantomPinned,
}
}
/// Tries to write some bytes from the given `buf` to the writer in an
/// asynchronous manner, returning a future.
pub(crate) fn write_all_buf<'a, W, B>(writer: &'a mut W, buf: &'a mut B) -> WriteAllBuf<'a, W, B>
where
W: AsyncWrite + Unpin,
B: Buf,
{
WriteAllBuf {
writer,
buf,
_pin: PhantomPinned,
}
}
impl<W, B> Future for WriteAllBuf<'_, W, B>
where
W: AsyncWrite + Unpin,
B: Buf,
{
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let me = self.project();
while me.buf.has_remaining() {
let n = ready!(Pin::new(&mut *me.writer).poll_write(cx, me.buf.chunk())?);
me.buf.advance(n);
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
}
Poll::Ready(Ok(()))
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::io::AsyncWrite;
use pin_project_lite::pin_project;
use std::io;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{future::Future, io::IoSlice};
pin_project! {
/// A future to write a slice of buffers to an `AsyncWrite`.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct WriteVectored<'a, 'b, W: ?Sized> {
writer: &'a mut W,
bufs: &'a [IoSlice<'b>],
// Make this future `!Unpin` for compatibility with async trait methods.
#[pin]
_pin: PhantomPinned,
}
}
pub(crate) fn write_vectored<'a, 'b, W>(
writer: &'a mut W,
bufs: &'a [IoSlice<'b>],
) -> WriteVectored<'a, 'b, W>
where
W: AsyncWrite + Unpin + ?Sized,
{
WriteVectored {
writer,
bufs,
_pin: PhantomPinned,
}
}
impl<W> Future for WriteVectored<'_, '_, W>
where
W: AsyncWrite + Unpin + ?Sized,
{
type Output = io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
let me = self.project();
Pin::new(&mut *me.writer).poll_write_vectored(cx, me.bufs)
}
}
+33 -2
View File
@@ -9,6 +9,7 @@
rust_2018_idioms,
unreachable_pub
)]
#![deny(unused_must_use)]
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
#![doc(test(
no_crate_inject,
@@ -76,7 +77,7 @@
//!
//! ### Authoring libraries
//!
//! As a library author your goal should be to provide the lighest weight crate
//! As a library author your goal should be to provide the lightest weight crate
//! that is based on Tokio. To achieve this you should ensure that you only enable
//! the features you need. This allows users to pick up your crate without having
//! to enable unnecessary features.
@@ -410,7 +411,7 @@ mod util;
/// # Why was `Stream` not included in Tokio 1.0?
///
/// Originally, we had planned to ship Tokio 1.0 with a stable `Stream` type
/// but unfortunetly the [RFC] had not been merged in time for `Stream` to
/// but unfortunately the [RFC] had not been merged in time for `Stream` to
/// reach `std` on a stable compiler in time for the 1.0 release of Tokio. For
/// this reason, the team has decided to move all `Stream` based utilities to
/// the [`tokio-stream`] crate. While this is not ideal, once `Stream` has made
@@ -442,6 +443,28 @@ mod util;
/// ```
pub mod stream {}
// local re-exports of platform specific things, allowing for decent
// documentation to be shimmed in on docs.rs
#[cfg(docsrs)]
pub mod doc;
#[cfg(docsrs)]
#[allow(unused)]
pub(crate) use self::doc::os;
#[cfg(not(docsrs))]
#[allow(unused)]
pub(crate) use std::os;
#[cfg(docsrs)]
#[allow(unused)]
pub(crate) use self::doc::winapi;
#[cfg(all(not(docsrs), windows, feature = "net"))]
#[allow(unused)]
pub(crate) use ::winapi;
cfg_macros! {
/// Implementation detail of the `select!` macro. This macro is **not**
/// intended to be used as part of the public API and is permitted to
@@ -453,15 +476,20 @@ cfg_macros! {
#[cfg(feature = "rt-multi-thread")]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[doc(inline)]
pub use tokio_macros::main;
#[cfg(feature = "rt-multi-thread")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[doc(inline)]
pub use tokio_macros::test;
cfg_not_rt_multi_thread! {
#[cfg(not(test))] // Work around for rust-lang/rust#62127
#[doc(inline)]
pub use tokio_macros::main_rt as main;
#[doc(inline)]
pub use tokio_macros::test_rt as test;
}
}
@@ -469,7 +497,10 @@ cfg_macros! {
// Always fail if rt is not enabled.
cfg_not_rt! {
#[cfg(not(test))]
#[doc(inline)]
pub use tokio_macros::main_fail as main;
#[doc(inline)]
pub use tokio_macros::test_fail as test;
}
}
+10 -1
View File
@@ -157,7 +157,6 @@ macro_rules! cfg_macros {
$(
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[doc(inline)]
$item
)*
}
@@ -183,6 +182,16 @@ macro_rules! cfg_net_unix {
}
}
macro_rules! cfg_net_windows {
($($item:item)*) => {
$(
#[cfg(all(any(docsrs, windows), feature = "net"))]
#[cfg_attr(docsrs, doc(cfg(all(windows, feature = "net"))))]
$item
)*
}
}
macro_rules! cfg_process {
($($item:item)*) => {
$(
+1 -1
View File
@@ -44,7 +44,7 @@
/// Stream + Unpin`.
///
/// [`Future`]: trait@std::future::Future
/// [`Box::pin`]: #
/// [`Box::pin`]: std::boxed::Box::pin
///
/// # Usage
///
+4 -4
View File
@@ -14,7 +14,7 @@
/// branch, which evaluates if none of the other branches match their patterns:
///
/// ```text
/// else <expression>
/// else => <expression>
/// ```
///
/// The macro aggregates all `<async expression>` expressions and runs them
@@ -134,7 +134,7 @@
/// are always ready.
///
/// This behavior can be overridden by adding `biased;` to the beginning of the
/// macro usage. See the exmples for details. This will cause `select` to poll
/// macro usage. See the examples for details. This will cause `select` to poll
/// the futures in the order they appear from top to bottom. There are a few
/// reasons you may want this:
///
@@ -154,7 +154,7 @@
/// `select!` panics if all branches are disabled **and** there is no provided
/// `else` branch. A branch is disabled when the provided `if` precondition
/// returns `false` **or** when the pattern does not match the result of `<async
/// expression>.
/// expression>`.
///
/// # Examples
///
@@ -398,7 +398,7 @@ macro_rules! select {
// set the appropriate bit in `disabled`.
$(
if !$c {
let mask = 1 << $crate::count!( $($skip)* );
let mask: util::Mask = 1 << $crate::count!( $($skip)* );
disabled |= mask;
}
)*
+4
View File
@@ -46,3 +46,7 @@ cfg_net_unix! {
pub use unix::listener::UnixListener;
pub use unix::stream::UnixStream;
}
cfg_net_windows! {
pub mod windows;
}
+51 -2
View File
@@ -5,7 +5,7 @@ use std::io;
use std::net::SocketAddr;
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
@@ -448,7 +448,7 @@ impl TcpSocket {
/// `backlog` defines the maximum number of pending connections are queued
/// by the operating system at any given time. Connection are removed from
/// the queue with [`TcpListener::accept`]. When the queue is full, the
/// operationg-system will start rejecting connections.
/// operating-system will start rejecting connections.
///
/// [`TcpListener::accept`]: TcpListener::accept
///
@@ -482,6 +482,48 @@ impl TcpSocket {
let mio = self.inner.listen(backlog)?;
TcpListener::new(mio)
}
/// Converts a [`std::net::TcpStream`] into a `TcpSocket`. The provided
/// socket must not have been connected prior to calling this function. This
/// function is typically used together with crates such as [`socket2`] to
/// configure socket options that are not available on `TcpSocket`.
///
/// [`std::net::TcpStream`]: struct@std::net::TcpStream
/// [`socket2`]: https://docs.rs/socket2/
///
/// # Examples
///
/// ```
/// use tokio::net::TcpSocket;
/// use socket2::{Domain, Socket, Type};
///
/// #[tokio::main]
/// async fn main() -> std::io::Result<()> {
///
/// let socket2_socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
///
/// let socket = TcpSocket::from_std_stream(socket2_socket.into());
///
/// Ok(())
/// }
/// ```
pub fn from_std_stream(std_stream: std::net::TcpStream) -> TcpSocket {
#[cfg(unix)]
{
use std::os::unix::io::{FromRawFd, IntoRawFd};
let raw_fd = std_stream.into_raw_fd();
unsafe { TcpSocket::from_raw_fd(raw_fd) }
}
#[cfg(windows)]
{
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
let raw_socket = std_stream.into_raw_socket();
unsafe { TcpSocket::from_raw_socket(raw_socket) }
}
}
}
impl fmt::Debug for TcpSocket {
@@ -511,6 +553,13 @@ impl FromRawFd for TcpSocket {
}
}
#[cfg(unix)]
impl IntoRawFd for TcpSocket {
fn into_raw_fd(self) -> RawFd {
self.inner.into_raw_fd()
}
}
#[cfg(windows)]
impl IntoRawSocket for TcpSocket {
fn into_raw_socket(self) -> RawSocket {
+140
View File
@@ -563,6 +563,84 @@ impl TcpStream {
.try_io(Interest::READABLE, || (&*self.io).read(buf))
}
/// Try to read data from the stream into the provided buffers, returning
/// how many bytes were read.
///
/// Data is copied to fill each buffer in order, with the final buffer
/// written to possibly being only partially filled. This method behaves
/// equivalently to a single call to [`try_read()`] with concatenated
/// buffers.
///
/// Receives any pending data from the socket but does not wait for new data
/// to arrive. On success, returns the number of bytes read. Because
/// `try_read_vectored()` is non-blocking, the buffer does not have to be
/// stored by the async task and can exist entirely on the stack.
///
/// Usually, [`readable()`] or [`ready()`] is used with this function.
///
/// [`try_read()`]: TcpStream::try_read()
/// [`readable()`]: TcpStream::readable()
/// [`ready()`]: TcpStream::ready()
///
/// # Return
///
/// If data is successfully read, `Ok(n)` is returned, where `n` is the
/// number of bytes read. `Ok(0)` indicates the stream's read half is closed
/// and will no longer yield data. If the stream is not ready to read data
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Examples
///
/// ```no_run
/// use tokio::net::TcpStream;
/// use std::error::Error;
/// use std::io::{self, IoSliceMut};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// // Connect to a peer
/// let stream = TcpStream::connect("127.0.0.1:8080").await?;
///
/// loop {
/// // Wait for the socket to be readable
/// stream.readable().await?;
///
/// // Creating the buffer **after** the `await` prevents it from
/// // being stored in the async task.
/// let mut buf_a = [0; 512];
/// let mut buf_b = [0; 1024];
/// let mut bufs = [
/// IoSliceMut::new(&mut buf_a),
/// IoSliceMut::new(&mut buf_b),
/// ];
///
/// // Try to read data, this may still fail with `WouldBlock`
/// // if the readiness event is a false positive.
/// match stream.try_read_vectored(&mut bufs) {
/// Ok(0) => break,
/// Ok(n) => {
/// println!("read {} bytes", n);
/// }
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
/// continue;
/// }
/// Err(e) => {
/// return Err(e.into());
/// }
/// }
/// }
///
/// Ok(())
/// }
/// ```
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
use std::io::Read;
self.io
.registration()
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
}
cfg_io_util! {
/// Try to read data from the stream into the provided buffer, advancing the
/// buffer's internal cursor, returning how many bytes were read.
@@ -775,6 +853,68 @@ impl TcpStream {
.try_io(Interest::WRITABLE, || (&*self.io).write(buf))
}
/// Try to write several buffers to the stream, returning how many bytes
/// were written.
///
/// Data is written from each buffer in order, with the final buffer read
/// from possible being only partially consumed. This method behaves
/// equivalently to a single call to [`try_write()`] with concatenated
/// buffers.
///
/// This function is usually paired with `writable()`.
///
/// [`try_write()`]: TcpStream::try_write()
///
/// # Return
///
/// If data is successfully written, `Ok(n)` is returned, where `n` is the
/// number of bytes written. If the stream is not ready to write data,
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Examples
///
/// ```no_run
/// use tokio::net::TcpStream;
/// use std::error::Error;
/// use std::io;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// // Connect to a peer
/// let stream = TcpStream::connect("127.0.0.1:8080").await?;
///
/// let bufs = [io::IoSlice::new(b"hello "), io::IoSlice::new(b"world")];
///
/// loop {
/// // Wait for the socket to be writable
/// stream.writable().await?;
///
/// // Try to write data, this may still fail with `WouldBlock`
/// // if the readiness event is a false positive.
/// match stream.try_write_vectored(&bufs) {
/// Ok(n) => {
/// break;
/// }
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
/// continue;
/// }
/// Err(e) => {
/// return Err(e.into());
/// }
/// }
/// }
///
/// Ok(())
/// }
/// ```
pub fn try_write_vectored(&self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
use std::io::Write;
self.io
.registration()
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(bufs))
}
/// Receives data on the socket from the remote address to which it is
/// connected, without removing that data from the queue. On success,
/// returns the number of bytes peeked.
+4
View File
@@ -1,5 +1,9 @@
//! Unix domain socket utility types
// This module does not currently provide any public API, but it was
// unintentionally defined as a public module. Hide it from the documentation
// instead of changing it to a private module to avoid breakage.
#[doc(hidden)]
pub mod datagram;
pub(crate) mod listener;
+140
View File
@@ -271,6 +271,84 @@ impl UnixStream {
.try_io(Interest::READABLE, || (&*self.io).read(buf))
}
/// Try to read data from the stream into the provided buffers, returning
/// how many bytes were read.
///
/// Data is copied to fill each buffer in order, with the final buffer
/// written to possibly being only partially filled. This method behaves
/// equivalently to a single call to [`try_read()`] with concatenated
/// buffers.
///
/// Receives any pending data from the socket but does not wait for new data
/// to arrive. On success, returns the number of bytes read. Because
/// `try_read_vectored()` is non-blocking, the buffer does not have to be
/// stored by the async task and can exist entirely on the stack.
///
/// Usually, [`readable()`] or [`ready()`] is used with this function.
///
/// [`try_read()`]: UnixStream::try_read()
/// [`readable()`]: UnixStream::readable()
/// [`ready()`]: UnixStream::ready()
///
/// # Return
///
/// If data is successfully read, `Ok(n)` is returned, where `n` is the
/// number of bytes read. `Ok(0)` indicates the stream's read half is closed
/// and will no longer yield data. If the stream is not ready to read data
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Examples
///
/// ```no_run
/// use tokio::net::UnixStream;
/// use std::error::Error;
/// use std::io::{self, IoSliceMut};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// // Connect to a peer
/// let dir = tempfile::tempdir().unwrap();
/// let bind_path = dir.path().join("bind_path");
/// let stream = UnixStream::connect(bind_path).await?;
///
/// loop {
/// // Wait for the socket to be readable
/// stream.readable().await?;
///
/// // Creating the buffer **after** the `await` prevents it from
/// // being stored in the async task.
/// let mut buf_a = [0; 512];
/// let mut buf_b = [0; 1024];
/// let mut bufs = [
/// IoSliceMut::new(&mut buf_a),
/// IoSliceMut::new(&mut buf_b),
/// ];
///
/// // Try to read data, this may still fail with `WouldBlock`
/// // if the readiness event is a false positive.
/// match stream.try_read_vectored(&mut bufs) {
/// Ok(0) => break,
/// Ok(n) => {
/// println!("read {} bytes", n);
/// }
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
/// continue;
/// }
/// Err(e) => {
/// return Err(e.into());
/// }
/// }
/// }
///
/// Ok(())
/// }
/// ```
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.io
.registration()
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
}
cfg_io_util! {
/// Try to read data from the stream into the provided buffer, advancing the
/// buffer's internal cursor, returning how many bytes were read.
@@ -487,6 +565,68 @@ impl UnixStream {
.try_io(Interest::WRITABLE, || (&*self.io).write(buf))
}
/// Try to write several buffers to the stream, returning how many bytes
/// were written.
///
/// Data is written from each buffer in order, with the final buffer read
/// from possible being only partially consumed. This method behaves
/// equivalently to a single call to [`try_write()`] with concatenated
/// buffers.
///
/// This function is usually paired with `writable()`.
///
/// [`try_write()`]: UnixStream::try_write()
///
/// # Return
///
/// If data is successfully written, `Ok(n)` is returned, where `n` is the
/// number of bytes written. If the stream is not ready to write data,
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Examples
///
/// ```no_run
/// use tokio::net::UnixStream;
/// use std::error::Error;
/// use std::io;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// // Connect to a peer
/// let dir = tempfile::tempdir().unwrap();
/// let bind_path = dir.path().join("bind_path");
/// let stream = UnixStream::connect(bind_path).await?;
///
/// let bufs = [io::IoSlice::new(b"hello "), io::IoSlice::new(b"world")];
///
/// loop {
/// // Wait for the socket to be writable
/// stream.writable().await?;
///
/// // Try to write data, this may still fail with `WouldBlock`
/// // if the readiness event is a false positive.
/// match stream.try_write_vectored(&bufs) {
/// Ok(n) => {
/// break;
/// }
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
/// continue;
/// }
/// Err(e) => {
/// return Err(e.into());
/// }
/// }
/// }
///
/// Ok(())
/// }
/// ```
pub fn try_write_vectored(&self, buf: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.io
.registration()
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(buf))
}
/// Creates new `UnixStream` from a `std::os::unix::net::UnixStream`.
///
/// This function is intended to be used to wrap a UnixStream from the
+1 -1
View File
@@ -73,7 +73,7 @@ pub(crate) mod impl_linux {
// These paranoid checks should be optimized-out
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
assert!(ucred_size <= u32::max_value() as usize);
assert!(ucred_size <= u32::MAX as usize);
let mut ucred_size = ucred_size as socklen_t;
+3
View File
@@ -0,0 +1,3 @@
//! Windows specific network types.
pub mod named_pipe;
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -41,6 +41,7 @@ cfg_rt! {
#[cfg(any(feature = "rt", feature = "sync"))]
pub(crate) mod thread;
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;
@@ -50,7 +51,7 @@ pub(crate) trait Park {
type Unpark: Unpark;
/// Error returned by `park`
type Error;
type Error: Debug;
/// Gets a new `Unpark` handle associated with this `Park` instance.
fn unpark(&self) -> Self::Unpark;
+13 -4
View File
@@ -479,7 +479,7 @@ impl Command {
/// Basic usage:
///
/// ```no_run
/// use tokio::process::Command;;
/// use tokio::process::Command;
/// use std::process::Stdio;
///
/// let command = Command::new("ls")
@@ -503,7 +503,7 @@ impl Command {
/// Basic usage:
///
/// ```no_run
/// use tokio::process::Command;;
/// use tokio::process::Command;
/// use std::process::{Stdio};
///
/// let command = Command::new("ls")
@@ -994,13 +994,22 @@ impl Child {
/// If the caller wishes to explicitly control when the child's stdin
/// handle is closed, they may `.take()` it before calling `.wait()`:
///
/// ```no_run
/// ```
/// # #[cfg(not(unix))]fn main(){}
/// # #[cfg(unix)]
/// use tokio::io::AsyncWriteExt;
/// # #[cfg(unix)]
/// use tokio::process::Command;
/// # #[cfg(unix)]
/// use std::process::Stdio;
///
/// # #[cfg(unix)]
/// #[tokio::main]
/// async fn main() {
/// let mut child = Command::new("cat").spawn().unwrap();
/// let mut child = Command::new("cat")
/// .stdin(Stdio::piped())
/// .spawn()
/// .unwrap();
///
/// let mut stdin = child.stdin.take().unwrap();
/// tokio::spawn(async move {
+10 -84
View File
@@ -3,11 +3,8 @@
//! Process driver
use crate::park::Park;
use crate::process::unix::orphan::ReapOrphanQueue;
use crate::process::unix::GlobalOrphanQueue;
use crate::signal::unix::driver::Driver as SignalDriver;
use crate::signal::unix::{signal_with_handle, SignalKind};
use crate::sync::watch;
use crate::signal::unix::driver::{Driver as SignalDriver, Handle as SignalHandle};
use std::io;
use std::time::Duration;
@@ -16,51 +13,20 @@ use std::time::Duration;
#[derive(Debug)]
pub(crate) struct Driver {
park: SignalDriver,
inner: CoreDriver<watch::Receiver<()>, GlobalOrphanQueue>,
}
#[derive(Debug)]
struct CoreDriver<S, Q> {
sigchild: S,
orphan_queue: Q,
}
trait HasChanged {
fn has_changed(&mut self) -> bool;
}
impl<T> HasChanged for watch::Receiver<T> {
fn has_changed(&mut self) -> bool {
self.try_has_changed().and_then(Result::ok).is_some()
}
}
// ===== impl CoreDriver =====
impl<S, Q> CoreDriver<S, Q>
where
S: HasChanged,
Q: ReapOrphanQueue,
{
fn process(&mut self) {
if self.sigchild.has_changed() {
self.orphan_queue.reap_orphans();
}
}
signal_handle: SignalHandle,
}
// ===== impl Driver =====
impl Driver {
/// Creates a new signal `Driver` instance that delegates wakeups to `park`.
pub(crate) fn new(park: SignalDriver) -> io::Result<Self> {
let sigchild = signal_with_handle(SignalKind::child(), park.handle())?;
let inner = CoreDriver {
sigchild,
orphan_queue: GlobalOrphanQueue,
};
pub(crate) fn new(park: SignalDriver) -> Self {
let signal_handle = park.handle();
Ok(Self { park, inner })
Self {
park,
signal_handle,
}
}
}
@@ -76,13 +42,13 @@ impl Park for Driver {
fn park(&mut self) -> Result<(), Self::Error> {
self.park.park()?;
self.inner.process();
GlobalOrphanQueue::reap_orphans(&self.signal_handle);
Ok(())
}
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
self.park.park_timeout(duration)?;
self.inner.process();
GlobalOrphanQueue::reap_orphans(&self.signal_handle);
Ok(())
}
@@ -90,43 +56,3 @@ impl Park for Driver {
self.park.shutdown()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::process::unix::orphan::test::MockQueue;
struct MockStream {
total_try_recv: usize,
values: Vec<Option<()>>,
}
impl MockStream {
fn new(values: Vec<Option<()>>) -> Self {
Self {
total_try_recv: 0,
values,
}
}
}
impl HasChanged for MockStream {
fn has_changed(&mut self) -> bool {
self.total_try_recv += 1;
self.values.remove(0).is_some()
}
}
#[test]
fn no_reap_if_no_signal() {
let mut driver = CoreDriver {
sigchild: MockStream::new(vec![None]),
orphan_queue: MockQueue::<()>::new(),
};
driver.process();
assert_eq!(1, driver.sigchild.total_try_recv);
assert_eq!(0, driver.orphan_queue.total_reaps.get());
}
}
+5 -4
View File
@@ -24,7 +24,7 @@
pub(crate) mod driver;
pub(crate) mod orphan;
use orphan::{OrphanQueue, OrphanQueueImpl, ReapOrphanQueue, Wait};
use orphan::{OrphanQueue, OrphanQueueImpl, Wait};
mod reap;
use reap::Reaper;
@@ -32,6 +32,7 @@ use reap::Reaper;
use crate::io::PollEvented;
use crate::process::kill::Kill;
use crate::process::SpawnedChild;
use crate::signal::unix::driver::Handle as SignalHandle;
use crate::signal::unix::{signal, Signal, SignalKind};
use mio::event::Source;
@@ -73,9 +74,9 @@ impl fmt::Debug for GlobalOrphanQueue {
}
}
impl ReapOrphanQueue for GlobalOrphanQueue {
fn reap_orphans(&self) {
ORPHAN_QUEUE.reap_orphans()
impl GlobalOrphanQueue {
fn reap_orphans(handle: &SignalHandle) {
ORPHAN_QUEUE.reap_orphans(handle)
}
}
+148 -45
View File
@@ -1,6 +1,9 @@
use crate::loom::sync::{Mutex, MutexGuard};
use crate::signal::unix::driver::Handle as SignalHandle;
use crate::signal::unix::{signal_with_handle, SignalKind};
use crate::sync::watch;
use std::io;
use std::process::ExitStatus;
use std::sync::Mutex;
/// An interface for waiting on a process to exit.
pub(crate) trait Wait {
@@ -20,21 +23,8 @@ impl<T: Wait> Wait for &mut T {
}
}
/// An interface for reaping a set of orphaned processes.
pub(crate) trait ReapOrphanQueue {
/// Attempts to reap every process in the queue, ignoring any errors and
/// enqueueing any orphans which have not yet exited.
fn reap_orphans(&self);
}
impl<T: ReapOrphanQueue> ReapOrphanQueue for &T {
fn reap_orphans(&self) {
(**self).reap_orphans()
}
}
/// An interface for queueing up an orphaned process so that it can be reaped.
pub(crate) trait OrphanQueue<T>: ReapOrphanQueue {
pub(crate) trait OrphanQueue<T> {
/// Adds an orphan to the queue.
fn push_orphan(&self, orphan: T);
}
@@ -48,50 +38,91 @@ impl<T, O: OrphanQueue<T>> OrphanQueue<T> for &O {
/// An implementation of `OrphanQueue`.
#[derive(Debug)]
pub(crate) struct OrphanQueueImpl<T> {
sigchild: Mutex<Option<watch::Receiver<()>>>,
queue: Mutex<Vec<T>>,
}
impl<T> OrphanQueueImpl<T> {
pub(crate) fn new() -> Self {
Self {
sigchild: Mutex::new(None),
queue: Mutex::new(Vec::new()),
}
}
#[cfg(test)]
fn len(&self) -> usize {
self.queue.lock().unwrap().len()
self.queue.lock().len()
}
}
impl<T: Wait> OrphanQueue<T> for OrphanQueueImpl<T> {
fn push_orphan(&self, orphan: T) {
self.queue.lock().unwrap().push(orphan)
pub(crate) fn push_orphan(&self, orphan: T)
where
T: Wait,
{
self.queue.lock().push(orphan)
}
}
impl<T: Wait> ReapOrphanQueue for OrphanQueueImpl<T> {
fn reap_orphans(&self) {
let mut queue = self.queue.lock().unwrap();
let queue = &mut *queue;
/// Attempts to reap every process in the queue, ignoring any errors and
/// enqueueing any orphans which have not yet exited.
pub(crate) fn reap_orphans(&self, handle: &SignalHandle)
where
T: Wait,
{
// If someone else is holding the lock, they will be responsible for draining
// the queue as necessary, so we can safely bail if that happens
if let Some(mut sigchild_guard) = self.sigchild.try_lock() {
match &mut *sigchild_guard {
Some(sigchild) => {
if sigchild.try_has_changed().and_then(Result::ok).is_some() {
drain_orphan_queue(self.queue.lock());
}
}
None => {
let queue = self.queue.lock();
for i in (0..queue.len()).rev() {
match queue[i].try_wait() {
Ok(None) => {}
Ok(Some(_)) | Err(_) => {
// The stdlib handles interruption errors (EINTR) when polling a child process.
// All other errors represent invalid inputs or pids that have already been
// reaped, so we can drop the orphan in case an error is raised.
queue.swap_remove(i);
// Be lazy and only initialize the SIGCHLD listener if there
// are any orphaned processes in the queue.
if !queue.is_empty() {
// An errors shouldn't really happen here, but if it does it
// means that the signal driver isn't running, in
// which case there isn't anything we can
// register/initialize here, so we can try again later
if let Ok(sigchild) = signal_with_handle(SignalKind::child(), &handle) {
*sigchild_guard = Some(sigchild);
drain_orphan_queue(queue);
}
}
}
}
}
}
}
fn drain_orphan_queue<T>(mut queue: MutexGuard<'_, Vec<T>>)
where
T: Wait,
{
for i in (0..queue.len()).rev() {
match queue[i].try_wait() {
Ok(None) => {}
Ok(Some(_)) | Err(_) => {
// The stdlib handles interruption errors (EINTR) when polling a child process.
// All other errors represent invalid inputs or pids that have already been
// reaped, so we can drop the orphan in case an error is raised.
queue.swap_remove(i);
}
}
}
drop(queue);
}
#[cfg(all(test, not(loom)))]
pub(crate) mod test {
use super::*;
use crate::io::driver::Driver as IoDriver;
use crate::signal::unix::driver::{Driver as SignalDriver, Handle as SignalHandle};
use crate::sync::watch;
use std::cell::{Cell, RefCell};
use std::io;
use std::os::unix::process::ExitStatusExt;
@@ -100,14 +131,12 @@ pub(crate) mod test {
pub(crate) struct MockQueue<W> {
pub(crate) all_enqueued: RefCell<Vec<W>>,
pub(crate) total_reaps: Cell<usize>,
}
impl<W> MockQueue<W> {
pub(crate) fn new() -> Self {
Self {
all_enqueued: RefCell::new(Vec::new()),
total_reaps: Cell::new(0),
}
}
}
@@ -118,12 +147,6 @@ pub(crate) mod test {
}
}
impl<W> ReapOrphanQueue for MockQueue<W> {
fn reap_orphans(&self) {
self.total_reaps.set(self.total_reaps.get() + 1);
}
}
struct MockWait {
total_waits: Rc<Cell<usize>>,
num_wait_until_status: usize,
@@ -191,27 +214,107 @@ pub(crate) mod test {
assert_eq!(orphanage.len(), 4);
orphanage.reap_orphans();
drain_orphan_queue(orphanage.queue.lock());
assert_eq!(orphanage.len(), 2);
assert_eq!(first_waits.get(), 1);
assert_eq!(second_waits.get(), 1);
assert_eq!(third_waits.get(), 1);
assert_eq!(fourth_waits.get(), 1);
orphanage.reap_orphans();
drain_orphan_queue(orphanage.queue.lock());
assert_eq!(orphanage.len(), 1);
assert_eq!(first_waits.get(), 1);
assert_eq!(second_waits.get(), 2);
assert_eq!(third_waits.get(), 2);
assert_eq!(fourth_waits.get(), 1);
orphanage.reap_orphans();
drain_orphan_queue(orphanage.queue.lock());
assert_eq!(orphanage.len(), 0);
assert_eq!(first_waits.get(), 1);
assert_eq!(second_waits.get(), 2);
assert_eq!(third_waits.get(), 3);
assert_eq!(fourth_waits.get(), 1);
orphanage.reap_orphans(); // Safe to reap when empty
// Safe to reap when empty
drain_orphan_queue(orphanage.queue.lock());
}
#[test]
fn no_reap_if_no_signal_received() {
let (tx, rx) = watch::channel(());
let handle = SignalHandle::default();
let orphanage = OrphanQueueImpl::new();
*orphanage.sigchild.lock() = Some(rx);
let orphan = MockWait::new(2);
let waits = orphan.total_waits.clone();
orphanage.push_orphan(orphan);
orphanage.reap_orphans(&handle);
assert_eq!(waits.get(), 0);
orphanage.reap_orphans(&handle);
assert_eq!(waits.get(), 0);
tx.send(()).unwrap();
orphanage.reap_orphans(&handle);
assert_eq!(waits.get(), 1);
}
#[test]
fn no_reap_if_signal_lock_held() {
let handle = SignalHandle::default();
let orphanage = OrphanQueueImpl::new();
let signal_guard = orphanage.sigchild.lock();
let orphan = MockWait::new(2);
let waits = orphan.total_waits.clone();
orphanage.push_orphan(orphan);
orphanage.reap_orphans(&handle);
assert_eq!(waits.get(), 0);
drop(signal_guard);
}
#[test]
fn does_not_register_signal_if_queue_empty() {
let signal_driver = IoDriver::new().and_then(SignalDriver::new).unwrap();
let handle = signal_driver.handle();
let orphanage = OrphanQueueImpl::new();
assert!(orphanage.sigchild.lock().is_none()); // Sanity
// No register when queue empty
orphanage.reap_orphans(&handle);
assert!(orphanage.sigchild.lock().is_none());
let orphan = MockWait::new(2);
let waits = orphan.total_waits.clone();
orphanage.push_orphan(orphan);
orphanage.reap_orphans(&handle);
assert!(orphanage.sigchild.lock().is_some());
assert_eq!(waits.get(), 1); // Eager reap when registering listener
}
#[test]
fn does_nothing_if_signal_could_not_be_registered() {
let handle = SignalHandle::default();
let orphanage = OrphanQueueImpl::new();
assert!(orphanage.sigchild.lock().is_none());
let orphan = MockWait::new(2);
let waits = orphan.total_waits.clone();
orphanage.push_orphan(orphan);
// Signal handler has "gone away", nothing to register or reap
orphanage.reap_orphans(&handle);
assert!(orphanage.sigchild.lock().is_none());
assert_eq!(waits.get(), 0);
}
}
-6
View File
@@ -224,7 +224,6 @@ mod test {
assert!(grim.poll_unpin(&mut context).is_pending());
assert_eq!(1, grim.signal.total_polls);
assert_eq!(1, grim.total_waits);
assert_eq!(0, grim.orphan_queue.total_reaps.get());
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
// Not yet exited, couldn't register interest the first time
@@ -232,7 +231,6 @@ mod test {
assert!(grim.poll_unpin(&mut context).is_pending());
assert_eq!(3, grim.signal.total_polls);
assert_eq!(3, grim.total_waits);
assert_eq!(0, grim.orphan_queue.total_reaps.get());
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
// Exited
@@ -245,7 +243,6 @@ mod test {
}
assert_eq!(4, grim.signal.total_polls);
assert_eq!(4, grim.total_waits);
assert_eq!(0, grim.orphan_queue.total_reaps.get());
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
}
@@ -260,7 +257,6 @@ mod test {
grim.kill().unwrap();
assert_eq!(1, grim.total_kills);
assert_eq!(0, grim.orphan_queue.total_reaps.get());
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
}
@@ -276,7 +272,6 @@ mod test {
drop(grim);
assert_eq!(0, queue.total_reaps.get());
assert!(queue.all_enqueued.borrow().is_empty());
}
@@ -294,7 +289,6 @@ mod test {
let grim = Reaper::new(&mut mock, &queue, MockStream::new(vec![]));
drop(grim);
assert_eq!(0, queue.total_reaps.get());
assert_eq!(1, queue.all_enqueued.borrow().len());
}

Some files were not shown because too many files have changed in this diff Show More