Repository: tokio-rs/tokio (language: rust)

Issue:
Improve documentation of `tokio::io::stdout` regarding interleaved writes
**Version**
tokio v1.38.0

**Platform**
Linux strix 6.9.6-200.fc40.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Jun 21 15:48:21 UTC 2024 x86_64 GNU/Linux

**Description**
Hi there, 
I ran into an issue with `tokio::io::stdout` that would be really good to put into the documentation to save people from encountering that issue.

Setup: two threads, one of them produces byte chunks the other one consumes them. They are sent through a channel.

The consuming thread does the following:
```rust
while let Some(chunk) = rx.recv().await {
    tokio::io::stdout()
        .write_all(&chunk)
        .await
        .context("Unable to write chunk to stdout")?;
}
```

Now this doesn't look particularly interesting, however, this code produces mangled output on stdout. I was convinced that this must be caused by the threads producing the bytes (because of unsafe usage), but it is not.

It is caused by the repeated calls to `tokio::io::stdout()` in the loop. If you extract it out of the loop, like so:
```rust
let mut stdout = tokio::io::stdout();

while let Some(chunk) = rx.recv().await {
    stdout
        .write_all(&chunk)
        .await
        .context("Unable to write chunk to stdout")?;
}
```
it works properly.

Now, the documentation of `tokio::io::stdout()` states that output may interleave when multiple threads are writing to it in parallel and I was aware of that. But by just looking at it, there is only a single thread writing to stdout in this example. I have not deeply looked into the implementation of `tokio::io::stdout` but I guess something of this sort must be going on there. An explaination what happens here would be appreciated, just out of curiousity :)

It would be really good if you could add a paragraph to the documentation that states that this is invalid (maybe with an explaination as to why).

I get that repeated calls to `tokio::io::stdout()` are kinda weird and non-optimal, I just didn't think very deeply about it when I wrote that, and since there is no `.lock()` function that would encourage only calling it once, that just happened I guess.


Discussion / hints:
> this code produces mangled output on stdout

I'm assuming you mean that messages may not appear in order in stdout, and I think such things could happen.

Tokio's stdout operation is performed on a dedicated blocking thread, and if multiple stdio instances are created in each loop and perform write operations, each write operation is performed in its own thread, which may be in any order.

On the other hand, when a write is performed to a single stdio instance within a loop, the state is properly updated and the order would be preserved.


And I agree, it would be good to have a description that mentions this behavior.
Yes, I meant wrong order. Follow up question if you don't mind: why does the `await` on `write_all` not prevent this. Shouldnt it wait until all bytes are written? And therefore prevent the next write (next iteration) from even being queued
`write_all` itself doesn't guarantee that the write is complete. (Looking at the [implementation of write_all](https://github.com/tokio-rs/tokio/blob/c8f3539bc11e57843745c68ee60ca5276248f9f9/tokio/src/io/blocking.rs#L117-L125), it returns Poll::Ready immediately after spawning the thread.)

Calling `flush` explicitly in a loop should work well even if instances are created each time.

```rust
while let Some(chunk) = rx.recv().await {
    let mut stdout = tokio::io::stdout();
    stdout
        .write_all(&chunk)
        .await
        .context("Unable to write chunk to stdout")?;

    stdout.flush().await.unwrap();
}
```

Thanks for explaining, makes sense. I didn't think about the fact that it might be buffered 😅

Fix patch:
diff --git a/tokio/src/io/stdout.rs b/tokio/src/io/stdout.rs
index 7636fe9708c..b4621469202 100644
--- a/tokio/src/io/stdout.rs
+++ b/tokio/src/io/stdout.rs
@@ -33,6 +33,31 @@ cfg_io_std! {
     ///     Ok(())
     /// }
     /// ```
+    ///
+    /// The following is an example of using `stdio` with loop.
+    ///
+    /// ```
+    /// use tokio::io::{self, AsyncWriteExt};
+    ///
+    /// #[tokio::main]
+    /// async fn main() {
+    ///     let messages = vec!["hello", " world\n"];
+    ///
+    ///     // When you use `stdio` in a loop, it is recommended to create
+    ///     // a single `stdio` instance outside the loop and call a write
+    ///     // operation against that instance on each loop.
+    ///     //
+    ///     // Repeatedly creating `stdout` instances inside the loop and
+    ///     // writing to that handle could result in mangled output since
+    ///     // each write operation is handled by a different blocking thread.
+    ///     let mut stdout = io::stdout();
+    ///
+    ///     for message in &messages {
+    ///         stdout.write_all(message.as_bytes()).await.unwrap();
+    ///         stdout.flush().await.unwrap();
+    ///     }
+    /// }
+    /// ```
     #[derive(Debug)]
     pub struct Stdout {
         std: SplitByUtf8BoundaryIfWindows<Blocking<std::io::Stdout>>,
@@ -64,6 +89,31 @@ cfg_io_std! {
     ///     Ok(())
     /// }
     /// ```
+    ///
+    /// The following is an example of using `stdio` with loop.
+    ///
+    /// ```
+    /// use tokio::io::{self, AsyncWriteExt};
+    ///
+    /// #[tokio::main]
+    /// async fn main() {
+    ///     let messages = vec!["hello", " world\n"];
+    ///
+    ///     // When you use `stdio` in a loop, it is recommended to create
+    ///     // a single `stdio` instance outside the loop and call a write
+    ///     // operation against that instance on each loop.
+    ///     //
+    ///     // Repeatedly creating `stdout` instances inside the loop and
+    ///     // writing to that handle could result in mangled output since
+    ///     // each write operation is handled by a different blocking thread.
+    ///     let mut stdout = io::stdout();
+    ///
+    ///     for message in &messages {
+    ///         stdout.write_all(message.as_bytes()).await.unwrap();
+    ///         stdout.flush().await.unwrap();
+    ///     }
+    /// }
+    /// ```
     pub fn stdout() -> Stdout {
         let std = io::stdout();
         Stdout {


Test patch: