2937: Parse cargo output a line at a time. r=kiljacken a=kiljacken

We previously used serde's stream deserializer to read json blobs from
the cargo output. It has an issue though: If the deserializer encounters
invalid input, it gets stuck reporting the same error again and again
because it is unable to foward over the input until it reaches a new
valid object.

Reading a line at a time and manually deserializing fixes this issue,
because cargo makes sure to only outpu one json blob per line, so should
we encounter invalid input, we can just skip a line and continue.

The main reason this would happen is stray printf-debugging in
procedural macros, so we still report that an error occured, but we
handle it gracefully now.

Fixes #2935

Co-authored-by: Emil Lauridsen <mine809@gmail.com>
This commit is contained in:
bors[bot] 2020-01-29 12:51:52 +00:00 committed by GitHub
commit abc5828c05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 26 additions and 5 deletions

View file

@ -11,6 +11,7 @@ log = "0.4.3"
cargo_metadata = "0.9.1"
jod-thread = "0.1.0"
parking_lot = "0.10.0"
serde_json = "1.0.45"
[dev-dependencies]
insta = "0.13.0"

View file

@ -9,7 +9,7 @@ use lsp_types::{
};
use std::{
collections::HashMap,
io::BufReader,
io::{BufRead, BufReader},
path::PathBuf,
process::{Command, Stdio},
sync::Arc,
@ -350,13 +350,33 @@ impl WatchThread {
// which will break out of the loop, and continue the shutdown
let _ = message_send.send(CheckEvent::Begin);
for message in
cargo_metadata::parse_messages(BufReader::new(command.stdout.take().unwrap()))
{
// We manually read a line at a time, instead of using serde's
// stream deserializers, because the deserializer cannot recover
// from an error, resulting in it getting stuck, because we try to
// be resillient against failures.
//
// Because cargo only outputs one JSON object per line, we can
// simply skip a line if it doesn't parse, which just ignores any
// erroneus output.
let stdout = BufReader::new(command.stdout.take().unwrap());
for line in stdout.lines() {
let line = match line {
Ok(line) => line,
Err(err) => {
log::error!("Couldn't read line from cargo: {}", err);
continue;
}
};
let message = serde_json::from_str::<cargo_metadata::Message>(&line);
let message = match message {
Ok(message) => message,
Err(err) => {
log::error!("Invalid json from cargo check, ignoring: {}", err);
log::error!(
"Invalid json from cargo check, ignoring ({}): {:?} ",
err,
line
);
continue;
}
};