Skip to content

A call to BufReader<File>::lines().count() on a non-read opened file in Linux never returns. #34703

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

Closed
joelself opened this issue Jul 7, 2016 · 5 comments
Labels
C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@joelself
Copy link

joelself commented Jul 7, 2016

Not much to it. I have this bit of code:

fn get_line_count(file_name: String) -> Result<usize, Error> {
    let file = try!(OpenOptions::new().append(true).create(true).open(&file_name));
    let f = BufReader::new(file);
    Ok(f.lines().count())
}

And I'm passing in a file that exists, but is empty (actually, by the timestamp, the file probably didn't exist until the first line in the function created it):

ls -la new_chunks.txt 
-rw-rw-r-- 1 joel joel 0 Jul  7 07:38 new_chunks.txt

My program wasn't going anywhere, but it was using up 100% of one core. Some printlns later and I found that it couldn't get past Ok(f.lines().count()). Specifically it was the call to count() that wasn't returning.

This is Ubuntu 14.04. rustup show:

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.9.0 (e4e8b6668 2016-05-18)

Edit: This was running in release mode, but I confirmed it in debug mode too. I also tried the current beta and nightly channels in release and debug mode:

beta-x86_64-unknown-linux-gnu (directory override for '/home/joel/dirname/dirname/project')
rustc 1.11.0-beta.1 (8dc253bcf 2016-07-05)

nightly-x86_64-unknown-linux-gnu (directory override for '/home/joel/dirname/dirname/project')
rustc 1.11.0-nightly (696b703b5 2016-07-03)
@joelself
Copy link
Author

joelself commented Jul 7, 2016

In case anybody else encounters this and needs a workaround, here's what I've been doing before I discovered the Lines Iterator and it seems to work fine on even really sketchy files that have encoding that varies within the file (including lines with invalid encoding):

    let mut f = BufReader::new(file);
    let mut count = 0;
    let mut line = String::new();
    if let Ok(mut bytes) = f.read_line(&mut line) {
        while bytes > 0 {
            // Do something with the line
            count += 1;
            line.clear();
            match f.read_line(&mut line) {
                Ok(b) => bytes = b,
                Err(_) => {},
            }
        }
    }

Edit: Alternatively, if you want to open a file for read you could, you know, just open the file for read I guess. If you're into that sort of thing.

@alexcrichton
Copy link
Member

This appears to be a few things at play:

  • The file descriptor is not opened for reading, so attempts to read it (at least on linux) are returning EBADF
  • This causes the iterator to continuously yield Some(Err(..)) values
  • The implementation of count exhausts an iterator (calls next until None) so it never returns

The "fix" here would be to fuse the lines iterator once an error is returned once and force it to return None afterwards, but that's also unfortunately not a great fix.

@joelself
Copy link
Author

joelself commented Jul 7, 2016

Ah, this the first time I tried OpenOptions and I just copied from another spot in my code that doesn't need read(true).

@steveklabnik steveklabnik added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed A-libs labels Mar 24, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-bug Category: This is a bug. label Jul 25, 2017
@Mark-Simulacrum Mark-Simulacrum changed the title A call to BufReader<File>::lines().count() on an empty file in Linux never returns. A call to BufReader<File>::lines().count() on a non-read opened file in Linux never returns. Dec 8, 2019
@Dylan-DPC
Copy link
Member

Closing this as this error was formed due to a misuse of the API

@hkBst
Copy link
Member

hkBst commented Mar 12, 2025

Coming here from #64144. The example code here is old, but I've updated it and it still behaves the same. Eyeballing the code it seems harmless, so it is unfortunate that it is an infinite loop.

use std::path::Path;
use std::io::{BufReader, BufRead};
use std::fs::OpenOptions;

fn get_line_count(file_name: impl AsRef<Path>) -> Result<usize, std::io::Error> {
    let file = OpenOptions::new().append(true).create(true).open(&file_name)?;
    let f = BufReader::new(file);
    Ok(f.lines().count())
}

#[test]
fn t() {
    assert_eq!(get_line_count("test").unwrap(), 0);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants