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

std::io::stdin() is causing significant trouble, losing data to its buffer #14434

Closed
chris-morgan opened this issue May 26, 2014 · 8 comments · Fixed by #19416
Closed

std::io::stdin() is causing significant trouble, losing data to its buffer #14434

chris-morgan opened this issue May 26, 2014 · 8 comments · Fixed by #19416

Comments

@chris-morgan
Copy link
Member

Currently (for three months, since #12422), stdin() returns a buffered reader.

This is causing lots of trouble with people that write

let a = io::stdin().read_line();
let b = io::stdin().read_line();

The first read will typically consume all the data from stdin, and so b ends up None, the rest of the first stdin() call having mysteriously vanished.

Documentation that you should only call stdin() once is not sufficient. It needs to be either a compile error in some way (once fn stdin()!?) or calling stdin() multiple times must be safe. (Causing task failure, “you’ve already called stdin()!”, would probably not be considered acceptable.)

One suggestion is to switch it to using task-local storage. This is imperfect (reading stdin from multiple tasks is broken), but generally acceptable.

Having stdin/stdout/stderr as true globals in some way is another possibility.

Yet another suggestion, aired by @o11c, is passing stdin, stdout and stderr as arguments to main. (There are certainly significant difficulties with that scheme.)

This needs to be fixed urgently. It appears to be tripping up a significant number of people and is very much non-obvious.

@huonw huonw added the A-io label May 26, 2014
@o11c
Copy link

o11c commented May 26, 2014

With a little further thought, stderr needs to be accessible to things like fail!. Of course, it's not necssarily the case that fail! has to use the same stderr ... but it might be best to exclude stderr. Actually, I'm not convinced that stderr should entirely be an ordinary stream.

However, I do still defend the notion of passing stdout as an argument to main, even though it doesn't have as many problems as stdin does. It just plain leads to better design. In C or C++, how many dump methods are there that are hard-coded to write to stdout because there is a family of functions that uses it implicitly? Sometimes stderr may be more appropriate, but in C stderr is a second-class citiizen, and often it would be nice to be able to specify an arbitrary file.

By "argument to main" I think there should be a single struct that contains the two standard streams and whatever else makes sense to put there. (personally, I would actually prefer that os::args and os::args also be members, but I'm not going to fight for that) That same struct should maybe be reused when creating tasks, but I'm unsure of what kind of lifetime it and its members will need to have.

@seanmonstar
Copy link
Contributor

it looks like there's already a with_local_stdout: https://github.com/mozilla/rust/blob/master/src/libstd/io/stdio.rs#L207

Would a solution here be that stdin(), stdout(), and stderr() all use a similar with_local_std<io>?

@sfackler
Copy link
Member

with_local_foo isn't a great solution since it's unsafe. A better solution would probably be to have the BufferedWriter/Reader be stored in the task's internals and stdin() etc would return smart pointers back to that Reader/Writer. You'd still run into issues if you try to read from stdin in multiple tasks, so it might need to be a global synchronized singleton which would be rather unfortunate.

@o11c
Copy link

o11c commented May 27, 2014

I'd rather see stdin() return an Option, where it returns None in all other tasks but the main one if you didn't specify a stdin when starting the task

@ben0x539
Copy link
Contributor

To begin with, we could rename it to create_buffered_stdin() or something similarly "you shouldn't call this for every line of input"-sounding. If I still mess it up, losing my input isn't really unsafe, so...

Even if we change the standard streams to be arguments to main, chances are people who feel like using them deep within some code without having them passed down all the way from main will just go "gee, rust is making this too complicated" and call the libc stdio functions (and that will conflict with whatever we do, unless we go the C++ route of building our standard streams on top of stdio).

@aturon
Copy link
Member

aturon commented Sep 17, 2014

cc me

@sfackler
Copy link
Member

It seems like stdin should really return a Reader wrapping a Arc<Mutex<BufferedReader<StdReader>>>.

@sfackler
Copy link
Member

So it seems like there are two options: We can use thread-local BufferedReader<StdReader>s or a global singleton Reader wrapping an Arc<Mutex<BufferedReader<StdReader>>>. The first option is easier, but it runs into problems if multiple threads call stdin(). The second option has some weird side effects. For example, we can't actually implement Buffer for it, since we can't safely implement fill_buf and consume doesn't make sense. We could manually implement read_line, read_until, read_char and BufferPrelude. I'm working on the second option right now since it seems like there will be less surprises to users.

sfackler added a commit to sfackler/rust that referenced this issue Dec 4, 2014
io::stdin returns a new `BufferedReader` each time it's called, which
results in some very confusing behavior with disappearing output. It now
returns a `StdinReader`, which wraps a global singleton
`Arc<Mutex<BufferedReader<StdReader>>`. `Reader` is implemented directly
on `StdinReader`. However, `Buffer` is not, as the `fill_buf` method is
fundamentaly un-thread safe. A `lock` method is defined on `StdinReader`
which returns a smart pointer wrapping the underlying `BufferedReader`
while guaranteeing mutual exclusion.

Code that treats the return value of io::stdin as implementing `Buffer`
will break. Add a call to `lock`:

```rust
io::stdin().lines()
// =>
io::stdin().lock().lines()
```

Closes rust-lang#14434

[breaking-change]
muggenhor added a commit to muggenhor/rudoku that referenced this issue Jan 4, 2015
Buffered reading from stdin now needs to lock stdin. This is to solve
[Rust issue #14434](rust-lang/rust#14434)
which was a race condition on buffered reads from stdin.

This change is to conform to the new API.
muggenhor added a commit to muggenhor/rudoku that referenced this issue Jan 4, 2015
Buffered reading from stdin now needs to lock stdin. This is to solve
rust-lang/rust#14434 which was a race condition on buffered reads from
stdin.

This change is to conform to the new API.
muggenhor added a commit to muggenhor/rudoku that referenced this issue Jan 4, 2015
Buffered reading from stdin now needs to lock stdin. This is to solve
rust-lang/rust#14434 which was a race condition on buffered reads from
stdin.

This change is to conform to the new API submitted as part of
rust-lang/rust#19416.
muggenhor added a commit to muggenhor/rudoku that referenced this issue Jan 4, 2015
Buffered reading from stdin now needs to lock stdin. This is to solve
rust-lang/rust#14434 which was a race condition on buffered reads from
stdin.

This change is to conform to the new API submitted in
rust-lang/rust@e7c1f57 as part of
rust-lang/rust#19416.
bors added a commit to rust-lang-ci/rust that referenced this issue Jun 5, 2023
fix: Use struct_tail_without_normalization in Expectation::rvalue_hint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants