Skip to content

Commit 683f014

Browse files
Found a race condition when backends exit really quickly
1 parent 38eecb8 commit 683f014

File tree

4 files changed

+61
-10
lines changed

4 files changed

+61
-10
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ open = "1.1"
3232
regex = "0.2.1"
3333
tempdir = "0.3.4"
3434
itertools = "0.7.4"
35+
tempfile = "2.2.0"
3536

3637
# Watch feature
3738
notify = { version = "4.0", optional = true }

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ extern crate serde_derive;
110110
#[macro_use]
111111
extern crate serde_json;
112112
extern crate tempdir;
113+
extern crate tempfile;
113114
extern crate toml;
114115

115116
#[cfg(test)]

src/renderer/mod.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ mod html_handlebars;
1717

1818
use std::io::Read;
1919
use std::path::PathBuf;
20-
use std::process::{Command, Stdio};
20+
use std::process::Command;
2121
use serde_json;
22+
use tempfile;
2223

2324
use errors::*;
2425
use config::Config;
@@ -135,17 +136,20 @@ impl Renderer for CmdRenderer {
135136
fn render(&self, ctx: &RenderContext) -> Result<()> {
136137
info!("Invoking the \"{}\" renderer", self.cmd);
137138

138-
let mut child = Command::new(&self.cmd)
139-
.stdin(Stdio::piped())
140-
.spawn()
139+
// We need to write the RenderContext to a temporary file here instead
140+
// of passing it in via a pipe. This prevents a race condition where
141+
// some quickly executing command (e.g. `/bin/true`) may exit before we
142+
// finish writing the render context (closing the stdin pipe and
143+
// throwing a write error).
144+
let mut temp = tempfile::tempfile().chain_err(|| "Unable to create a temporary file")?;
145+
serde_json::to_writer(&mut temp, &ctx)
146+
.chain_err(|| "Unable to serialize the RenderContext")?;
147+
148+
let status = Command::new(&self.cmd)
149+
.stdin(temp)
150+
.status()
141151
.chain_err(|| "Unable to start the renderer")?;
142152

143-
serde_json::to_writer(
144-
child.stdin.as_mut().expect("stdin is always attached"),
145-
&ctx,
146-
).chain_err(|| "Error occurred while sending the render context to the renderer")?;
147-
148-
let status = child.wait()?;
149153
trace!("{} exited with output: {:?}", self.cmd, status);
150154

151155
if !status.success() {

tests/alternate_backends.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! Integration tests to make sure alternate backends work.
2+
3+
extern crate mdbook;
4+
extern crate tempdir;
5+
6+
use tempdir::TempDir;
7+
use mdbook::config::Config;
8+
use mdbook::MDBook;
9+
10+
#[test]
11+
fn passing_alternate_backend() {
12+
let (md, _temp) = dummy_book_with_backend("passing", "true");
13+
14+
md.build().unwrap();
15+
}
16+
17+
#[test]
18+
fn failing_alternate_backend() {
19+
let (md, _temp) = dummy_book_with_backend("failing", "false");
20+
21+
md.build().unwrap_err();
22+
}
23+
24+
#[test]
25+
fn alternate_backend_with_arguments() {
26+
let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!");
27+
28+
md.build().unwrap();
29+
}
30+
31+
fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
32+
let temp = TempDir::new("mdbook").unwrap();
33+
34+
let mut config = Config::default();
35+
config
36+
.set(format!("output.{}.command", name), command)
37+
.unwrap();
38+
39+
let md = MDBook::init(temp.path())
40+
.with_config(config)
41+
.build()
42+
.unwrap();
43+
44+
(md, temp)
45+
}

0 commit comments

Comments
 (0)