Made it so the CmdRenderer writes directly to the child's stdin (#544)

This commit is contained in:
Michael Bryan 2018-01-14 19:14:27 +08:00 committed by GitHub
parent be949ceae8
commit 9ab54412ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 40 additions and 16 deletions

View File

@ -32,7 +32,6 @@ open = "1.1"
regex = "0.2.1" regex = "0.2.1"
tempdir = "0.3.4" tempdir = "0.3.4"
itertools = "0.7.4" itertools = "0.7.4"
tempfile = "2.2.0"
shlex = "0.1.1" shlex = "0.1.1"
toml-query = "0.6" toml-query = "0.6"

View File

@ -111,7 +111,6 @@ extern crate serde_derive;
extern crate serde_json; extern crate serde_json;
extern crate shlex; extern crate shlex;
extern crate tempdir; extern crate tempdir;
extern crate tempfile;
extern crate toml; extern crate toml;
extern crate toml_query; extern crate toml_query;

View File

@ -17,9 +17,8 @@ mod html_handlebars;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::{Command, Stdio};
use serde_json; use serde_json;
use tempfile;
use shlex::Shlex; use shlex::Shlex;
use errors::*; use errors::*;
@ -153,21 +152,30 @@ impl Renderer for CmdRenderer {
fn render(&self, ctx: &RenderContext) -> Result<()> { fn render(&self, ctx: &RenderContext) -> Result<()> {
info!("Invoking the \"{}\" renderer", self.cmd); info!("Invoking the \"{}\" renderer", self.cmd);
// We need to write the RenderContext to a temporary file here instead let mut child = self.compose_command()?
// of passing it in via a pipe. This prevents a race condition where .stdin(Stdio::piped())
// some quickly executing command (e.g. `/bin/true`) may exit before we .stdout(Stdio::inherit())
// finish writing the render context (closing the stdin pipe and .stderr(Stdio::inherit())
// throwing a write error).
let mut temp = tempfile::tempfile().chain_err(|| "Unable to create a temporary file")?;
serde_json::to_writer(&mut temp, &ctx)
.chain_err(|| "Unable to serialize the RenderContext")?;
let status = self.compose_command()?
.stdin(temp)
.current_dir(&ctx.destination) .current_dir(&ctx.destination)
.status() .spawn()
.chain_err(|| "Unable to start the renderer")?; .chain_err(|| "Unable to start the renderer")?;
{
let mut stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = serde_json::to_writer(&mut stdin, &ctx) {
// Looks like the backend hung up before we could finish
// sending it the render context. Log the error and keep going
warn!("Error writing the RenderContext to the backend, {}", e);
}
// explicitly close the `stdin` file handle
drop(stdin);
}
let status = child
.wait()
.chain_err(|| "Error waiting for the backend to complete")?;
trace!("{} exited with output: {:?}", self.cmd, status); trace!("{} exited with output: {:?}", self.cmd, status);
if !status.success() { if !status.success() {

View File

@ -3,9 +3,11 @@
extern crate mdbook; extern crate mdbook;
extern crate tempdir; extern crate tempdir;
use std::fs::File;
use tempdir::TempDir; use tempdir::TempDir;
use mdbook::config::Config; use mdbook::config::Config;
use mdbook::MDBook; use mdbook::MDBook;
use mdbook::renderer::RenderContext;
#[test] #[test]
fn passing_alternate_backend() { fn passing_alternate_backend() {
@ -28,6 +30,22 @@ fn alternate_backend_with_arguments() {
md.build().unwrap(); md.build().unwrap();
} }
#[test]
fn backends_receive_render_context_via_stdin() {
let temp = TempDir::new("output").unwrap();
let out_file = temp.path().join("out.txt");
let cmd = format!("tee {}", out_file.display());
let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd);
assert!(!out_file.exists());
md.build().unwrap();
assert!(out_file.exists());
let got = RenderContext::from_json(File::open(&out_file).unwrap());
assert!(got.is_ok());
}
fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) { fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
let temp = TempDir::new("mdbook").unwrap(); let temp = TempDir::new("mdbook").unwrap();