-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add documentation for SyncIoBridge with examples and alternatives #6815
base: master
Are you sure you want to change the base?
Conversation
tokio-util/src/io/sync_bridge.rs
Outdated
/// let mut file = File::open("output.txt")?; | ||
/// std::io::copy(&mut sync_reader, &mut file)?; | ||
/// Ok::<_, std::io::Error>(()) | ||
/// }).await??; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To please rustfmt
in the CI I think you need to move the .await??;
into its own line.
/// }).await??; | |
/// }) | |
/// .await??; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have seen my mistake.
Thank you!
6673e3e
to
30d38d5
Compare
tokio-util/src/io/sync_bridge.rs
Outdated
/// | ||
/// ```rust, no_run | ||
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For async/await to work in the docs you actually also need a runtime since the doc test will be run by a synchronous main function by default.
See this from tokio as an example:
/// ```rust,no_run
/// use std::error::Error;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// let tokio_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
/// let std_listener = tokio_listener.into_std()?;
/// std_listener.set_nonblocking(false)?;
/// Ok(())
/// }
/// ```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
I think that most of the examples added here don't actually compile. Before we can merge this, we need to make them compile. I left some suggestions on how to do that.
tokio-util/src/io/sync_bridge.rs
Outdated
/// | ||
/// ## Example 1: Hashing Data | ||
/// | ||
/// When hashing data, using `SyncIoBridge` can lead to suboptimal performance and might not fully leverage the async capabilities of the system. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if this explained why using SyncIoBridge
"can lead to suboptimal performance and might not fully leverage the async capabilities of the system"; I think just saying "you might have bad performance" is not as helpful as explaining the specific details, so that the user can know what issues they are avoiding and why they matter?
tokio-util/src/io/sync_bridge.rs
Outdated
/// ```rust, no_run | ||
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; | ||
/// let hash = blake3::hash(&data); | ||
/// ``` | ||
/// | ||
/// Or, for more complex cases: | ||
/// | ||
/// ```rust, no_run | ||
/// let mut data = vec![0; 64 * 1024]; | ||
/// loop { | ||
/// let len = reader.read(&mut data).await?; | ||
/// if len == 0 { break; } | ||
/// hasher.update(&data[..len]); | ||
/// } | ||
/// let hash = hasher.finalize(); | ||
/// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have a few comments in these examples explaining what they're doing...
tokio-util/src/io/sync_bridge.rs
Outdated
/// ## Example 2: Compressing Data | ||
/// | ||
/// When compressing data, avoid using `SyncIoBridge`` with non-async compression libraries, as it may lead to inefficient and blocking code. | ||
/// Instead, use `async-compression`, which is designed to work with asynchronous data streams. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to recommend a specific library in the tokio documentation, because it might not work for all use-cases. I might say
/// Instead, use `async-compression`, which is designed to work with asynchronous data streams. | |
/// Instead, use an async compression library, such as the `async-compression` crate, which is designed to work with asynchronous data streams. |
Also, if we are going to recommend this crate specifically, we should probably link to its documentation.
tokio-util/src/io/sync_bridge.rs
Outdated
/// ## Example 3: Parsing `JSON` | ||
/// | ||
/// When parsing `JSON` data, avoid using `SyncIoBridge` with `serde_json::from_reader` as it may cause blocking operations. | ||
/// Instead, read the data into a `Vec<u8>` and parse it using `serde_json::from_slice`. | ||
/// | ||
/// ```rust, no_run | ||
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; | ||
/// let value: MyStruct = serde_json::from_slice(&data)?; | ||
/// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really specific to JSON; this advice applies equally to any other serialization format. What about something like:
When parsing serialization formats such as JSON, avoid using
SyncIoBridge
with functions that deserialize data from a type implementingstd::io::Read
, such asserde_json::from_reader
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a number of the examples in this file won't actually compile. While it's fine to not run the examples using no_run
, they should, at least, compile --- the test suite will fail if examples contain code that doesn't compile. In many cases, the examples reference names that aren't actually defined, such as &mut reader
and &mut writer
when there is no value named reader
or writer
in the example. We should add hidden definitions of these values to the examples to make sure they compile.
tokio-util/src/io/sync_bridge.rs
Outdated
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// let mut data = Vec::new(); | |
/// reader.read_to_end(&mut data).await?; | |
/// use tokio::io::AsyncReadExt; | |
/// | |
/// # let mut reader = tokio::io::empty(); | |
/// let mut data = Vec::new(); | |
/// reader.read_to_end(&mut data).await?; |
tokio-util/src/io/sync_bridge.rs
Outdated
/// ```rust, no_run | ||
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; | ||
/// let hash = blake3::hash(&data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this will compile, because there is no blake3
in scope. We can fix this by adding a hidden dummy version, like:
/// let hash = blake3::hash(&data); | |
/// let hash = blake3::hash(&data); | |
/// # mod blake3 { pub fn hash(_: &[u8]) {} } |
tokio-util/src/io/sync_bridge.rs
Outdated
/// ```rust, no_run | ||
/// let mut data = vec![0; 64 * 1024]; | ||
/// loop { | ||
/// let len = reader.read(&mut data).await?; | ||
/// if len == 0 { break; } | ||
/// hasher.update(&data[..len]); | ||
/// } | ||
/// let hash = hasher.finalize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this won't compile because hasher
and reader
are not defined in this scope.
tokio-util/src/io/sync_bridge.rs
Outdated
/// use async_compression::tokio::write::GzipEncoder; | ||
/// | ||
/// let mut encoder = GzipEncoder::new(writer); | ||
/// tokio::io::copy(&mut reader, &mut encoder).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this won't compile, because async_compression
is not a dependency, and reader
and writer
are not defined.
tokio-util/src/io/sync_bridge.rs
Outdated
/// let mut data = Vec::new(); | ||
/// reader.read_to_end(&mut data).await?; | ||
/// let value: MyStruct = serde_json::from_slice(&data)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this won't compile because reader
is undefined, and serde_json
is not a dependency of the example.
Hi @hawkw. |
cd8edbc
to
6bdda28
Compare
tokio-util/src/io/sync_bridge.rs
Outdated
/// When hashing data, using `SyncIoBridge` can lead to suboptimal performance and might not fully leverage the async capabilities of the system. | ||
/// | ||
/// ### Why It Matters: | ||
/// `SyncIoBridge` allows you to use synchronous I/O operations in an asynchronous context by blocking the current thread. However, this can be inefficient because: | ||
/// Blocking: The use of `SyncIoBridge` may block a valuable async runtime thread, which could otherwise be used to handle more tasks concurrently. | ||
/// Thread Pool Saturation: If many threads are blocked using `SyncIoBridge`, it can exhaust the async runtime's thread pool, leading to increased latency and reduced throughput. | ||
/// Lack of Parallelism: By blocking on synchronous operations, you may miss out on the benefits of running tasks concurrently, especially in I/O-bound operations where async tasks could be interleaved. | ||
/// | ||
/// Instead, consider reading the data into memory and then hashing it, or processing the data in chunks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are several opportunities to improve the wording here.
- The explanation for why you should try to avoid
SyncIoBridge
should go before## Example 1
and after# Alternatives
. It applies to all examples, not just the first one. - Each example should start by mentioning that the use of
SyncIoBridge
is unnecessary. - For each example, there should be a short explanation of what the example does. See below for explanations for the hashing example. Just saying "or for more complex cases" is very generic, and doesn't actually explain when a case is complex.
- Your text should be formatted according to markdown. You don't put newlines at the end of sentences like that. Instead, wrap the line after 80 characters.
/// ## Hashing data
///
/// It is common to use `SyncIoBridge` for hashing data, but this is unnecessary
/// in most cases.
///
/// There are two strategies for avoiding `SyncIoBridge` when hashing data. When
/// the data fits into memory, the easiest is to read the data into a `Vec<u8>`
/// and hash it:
///
/// [ first example ]
///
/// When the data doesn't fit into memory, the hashing library will usually
/// provide a hasher that you can repeatedly call `update` on to hash the data
/// one chunk at the time. For example:
///
/// [second example]
tokio-util/src/io/sync_bridge.rs
Outdated
/// #[tokio::main] | ||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
/// let mut reader = tokio::io::empty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using tokio::io::empty()
, I think it makes more sense to define a function that takes any AsyncRead
.
async fn hash_contents(reader: impl AsyncRead) -> std::io::Result<()> {
Also, please avoid using Box<dyn Error>
as that type is not Send
. Instead, you can use std::io::Error
for your examples.
Hi @Darksonn |
Motivation
The SyncIoBridge documentation lacked clarity on its usage, potentially leading to inefficient use and suboptimal performance. Users needed better guidance on when to use it and how to handle common use cases effectively.
Solution
This update enhances the SyncIoBridge documentation by:
These improvements aim to help users use SyncIoBridge more effectively and avoid common pitfalls.
Rendered