Skip to content
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

Writing to stdout with write() changed behavior between version 1.47 and >= 1.48 #83528

Closed
sdragic opened this issue Mar 26, 2021 · 4 comments
Closed
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` P-low Low priority T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@sdragic
Copy link

sdragic commented Mar 26, 2021

I'm not sure this is worth reporting, but it has caused confusion at least once, so maybe it makes sense to be documented somewhere.

When writing to stdout with write(), write will return smaller number bytes than direct write() call would do in some cases. Here is example code:

use std::io::{self, Write};

fn main() {
    let mut a = ['a' as u8; 2048];
    eprintln!("{}", a.len());
    eprintln!("{:?}", io::stdout().write(&a));
    a[50] = '\n' as u8;
    eprintln!("{:?}", io::stdout().write(&a));
}

Rustc 1.47 gives expected result:

#./main > /dev/null
2048
Ok(2048)
Ok(2048)

This is also in accordance with similar programs written in C, Python and Rust using libc::write().

However rustc 1.48 and greater return this:

#./main > /dev/null
2048
Ok(2048)
Ok(1075)

This is technically not a bug, but IMHO it is unexpected behavior. Rust docs says this:

This function will attempt to write the entire contents of buf, but the entire write may not succeed, or the write may also generate an error. A call to write represents at most one attempt to write to any wrapped object.

I'm aware "wrapped object" here is some internal std library structure, but most people would expect it should behave similarly to underlying system write().

man 2 write says:

The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setr‐limit(2)), or the call was interrupted by a signal handler after having written less than count bytes.

As I understand this, returning less than count should be affected only by OS state and not by content of data (newline in this case).

This behavior seems to be introduced with PR #72808, and pending PR #78515 seems to revert behavior to expected.

Just a disclaimer, I do not condone use of unchecked write(). Reason for reporting this is inconsistency between io::stdout().write() and similar approaches.

@camelid camelid added A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` I-prioritize Issue: Indicates that prioritization has been requested for this issue. regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 26, 2021
@m-ou-se m-ou-se added P-low Low priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Mar 27, 2021
@m-ou-se
Copy link
Member

m-ou-se commented Mar 27, 2021

Thanks for your detailed report!

This is technically not a bug, but IMHO it is unexpected behavior.

Agreed. Marking this as low priority.

@Lucretiel
Copy link
Contributor

The basic issue at play here is that, in line buffering logic, the buffer will generally attempt to only forward whole lines to the underlying writer; the only exception is when the line length exceeds the size of the buffer. Once it forwards the line, it can't attempt any more fallible operations, because it must return an Ok result at that point. This means it will:

  • Write the line to the underlying device (a[..51])
  • Buffer as much as possible of the remaining content (a[51..(51 + 1024)])

However, there's an interesting issue at play here. In the sample code, the write method should be able to tell that even after the line write succeeds, the upcoming content (a[51..]) will on its own exceed the size of the buffer, which means that it probably warrants being written directly.

This is technically unrelated to #72808; that PR will only fix this issue when stdout is in buffered writing mode. However, it's fairly easy to add the logic, and it makes sense that it should be present, so I'm writing a separate PR for it.

@the8472
Copy link
Member

the8472 commented Jul 4, 2021

@Lucretiel #78515 should provide the necessary control to solve this issue by switching stdout to immediate mode, right?

Edit: silly question, this already is mentioned in OP

This behavior seems to be introduced with PR #72808, and pending PR #78515 seems to revert behavior to expected.

@joshtriplett joshtriplett removed the regression-from-stable-to-stable Performance or correctness regression from one stable version to another. label Jul 21, 2021
@joshtriplett
Copy link
Member

We talked about this in the @rust-lang/libs meeting today.

This is behavior we care about, and we think it's valuable to work on stdout buffering (including the ongoing work in issues like #78515 and similar).

However:

This is technically not a bug, but IMHO it is unexpected behavior.

Given this, we don't plan to track this as a regression. We'd encourage people to get involved with the stdout buffering rework to improve the behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` P-low Low priority T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants