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

Wait for gnuplot to create the output file before continuing #32

Open
jonasbb opened this issue Dec 13, 2018 · 4 comments
Open

Wait for gnuplot to create the output file before continuing #32

jonasbb opened this issue Dec 13, 2018 · 4 comments

Comments

@jonasbb
Copy link

jonasbb commented Dec 13, 2018

I want to create a plot using gnuplot and directly afterwards read the file and process it further. Something like:

let fpath = Path::new("/tmp/plot.png");
let mut fg = Figure::new();
// Plotting code
fg.set_terminal("pngcairo", fpath.to_string_lossy());
fg.show();

// Now read the file again
let res = fs::read(fpath).unwrap();

However, reading the file fails with "No such file or directory". Putting a sleep before the read "solves" the problem. As such, the problem seems to be, that show() returns, before the file is actually shown/created.

Is there a way to wait for the gnuplot process to finish creating the output file, before show() returns?
The only thing I can imagine right now, is spawning a new gnuplot process for each image and using the termination of the process as a signal that the output file was created.

@SiegeLord
Copy link
Owner

As a workaround, as of version 0.0.27 you can do fg.set_post_commands("unset output").show() and that should finish writing the file.

@jonasbb
Copy link
Author

jonasbb commented Jan 31, 2019

Thanks for the feedback. I will try this with the new version.

@jonasbb
Copy link
Author

jonasbb commented Feb 3, 2019

@SiegeLord Unfortunately, specifying a post command still leaves the race condition.

This is the exact code I used. Make sure to remove the /tmp/plot.png file before executing it, in case it still exists from a previous run.

use gnuplot::Figure;
use std::{fs, path::Path, thread};

fn main() {
    let fpath = Path::new("/tmp/plot.png");
    let mut fg = Figure::new();
    fg.axes2d().boxes(0..5, 0..5, &[]);
    fg.set_terminal("pngcairo", &*fpath.to_string_lossy());
    fg.set_post_commands("unset output").show();

    // Now read the file again
    // thread::sleep_ms(1000); // <== If this line is commented out, the next unwrap fails. Otherwise it works.
    let res = fs::read(fpath).unwrap();
    println!("{:?}", res);
}

The problem is that in the code which sends the print commands to gnuplot, there is no way to wait for gnuplot to finish writing the file.

RustGnuplot/src/figure.rs

Lines 148 to 150 in abb243b

self.gnuplot.borrow_mut().as_mut().map(|p| {
self.echo(p.stdin.as_mut().expect("No stdin!?"));
});

This code writes the commands to stdin of gnuplot. This might be buffered. The code continues as soon as writing to stdin succeeded. Now, it will take some time for gnuplot to process the commands and write the final output file. During the time gnuplot draws, the Rust code continues and tries to read the file, which was not yet written. The gnuplot field is private to the Figure instance, so there is no way for outside code to block until gnuplot finished writing the file. This leaves the unavoidable race condition for any code using the show function

My workaround is this:

    // Start gnuplot process
    let mut child = Command::new("gnuplot-nox")
        .stdin(Stdio::piped())
        .spawn()
        .unwrap();
    fg.echo(
        child
            .stdin
            .as_mut()
            .expect("Stdin exists, because we configured it"),
    );
child.wait().unwrap(); // <== this line waits untils gnuplot has finished executing. At that point the output file must exist

I spawn a new gnuplot process for each image I want to draw, without the --persist flag. This way I can wait on the gnuplot process to finish before executing more Rust code. After the gnuplot process finished, the image file should have been written.

@SiegeLord
Copy link
Owner

Ok, I see. What did is that as of version 0.0.29 Figure now has a close method which closes the gnuplot process, hopefully accomplishing the same thing as your code. Here's an excerpt from the test I added:

use std::fs;
use tempfile::TempDir;

let tmp_path = TempDir::new().unwrap().into_path();
let file_path = tmp_path.join("plot.png");
let mut fg = Figure::new();
fg.axes2d().boxes(0..5, 0..5, &[]);
fg.set_terminal("pngcairo", &*file_path.to_string_lossy());
fg.show().close();
fs::read(file_path).unwrap();
fs::remove_dir_all(&tmp_path);

I.e. add a close call after show, no more set_post_commands necessary.

I still think there's some way to get it working without shutting down the entire process, but that'll have to wait for the future.

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

No branches or pull requests

2 participants