multithreadingrustrust-tokio

How to get the file/seek position of a file opened in a thread asynchronously


In the below code I'm copying a file(worker thread) and want to print the progress of the file copy operation in main thread. I would like to know, if it's possible to get the file/seek position of the reader file opened in worker thread asynchronously, so that progress info can be notified to main thread. This is what I've written so far

use std::io::{Read, Write};
use std::fs;
use std::sync::{Arc, Condvar, Mutex};

fn write_to_file<R: Read, W: Write>(mut reader: R, mut writer: W) {
    let mut buf = vec![0; 1024];
    let mut bytes_read = buf.capacity();
    while bytes_read > 0 {
        bytes_read = reader.read(&mut buf).expect("failed to read from the file");
        if bytes_read > 0 {
            writer.write_all(&buf[..bytes_read]).expect("failed to write to the file");
        }
    }
    writer.flush().expect("failed to flush the writer"); 
}

fn foo(s: Arc<(Condvar, Mutex<i32>)>) {
    let &(ref cv, ref m) = &*s;
    
    let src_file = fs::File::open("~/a.txt").expect("unable to open the file");
    let dst_file = fs::File::create("~/b.txt").expect("unable to create the file");
    write_to_file(src_file, dst_file);
    let mut progress = 0;
    // get the progress of above file operation using file/seek position of src file
    // if possible using async and store it in above progress
    // I cannot change the syntax of write_to_file
    {
        let mut mg = m.lock().unwrap();
        *mg = progress;
    }
    cv.notify_one();
}

fn main() {
    let s = Arc::new((Condvar::new(), Mutex::new(0)));
    let sa = Arc::clone(&s);
    let jh = std::thread::spawn(move || {
        foo(sa);
    });
    
    let mut prev_progress = 0;
    let &(ref cv, ref m) = &*s;
    loop {
        let mut mg = m.lock().unwrap();
        while *mg == prev_progress {
            mg = cv.wait(mg).unwrap();
        }
        prev_progress = *mg;
        println!("{}", *mg);
        if *mg == 100 {
            break;
        }
    }
    
    jh.join();
}

Solution

  • Since &File implements both Read and Seek you can simply pass references instead of the values themselves. That gives your foo function access the &File and it's Seek implementation after you pass one copy to write_to_file:

    fn foo(s: Arc<(Condvar, Mutex<u64>)>) {
        let &(ref cv, ref m) = &*s;
        let mut src_file = &fs::File::open("src/main.rs").expect("unable to open the file");
        let dst_file = &fs::File::create("b.txt").expect("unable to create the file");
        std::thread::scope(|s| {
            let w = s.spawn(move || {
                write_to_file(src_file, dst_file);
            });
            while !w.is_finished() {
                std::thread::sleep_ms(1);
                let progress = src_file.stream_position().unwrap();
                let total = src_file.metadata().unwrap().len();
                {
                    let mut mg = m.lock().unwrap();
                    *mg = 100 * progress / total;
                }
                cv.notify_one();
            }
            *m.lock().unwrap() = 100;
        });
    }
    

    If you can't easily pass around shared references to the File, for example because you want to open the File in a function, you can also use Arc<File> and clone that instead since it also implements Read in this way you can share the ownership and avoid having to use a non-static reference.

    Since the asynchronous versions of File do not implement AsyncRead/AsyncSeek for references you can't use them in a similar manner.