Skip to content

Commit 0583081

Browse files
committed
Immediately print job output for fresh jobs
This prevents a deadlock where the message queue is filled with output messages but not emptied as the job producing the messages runs on the same thread as the message processing.
1 parent 49b63b7 commit 0583081

File tree

3 files changed

+51
-37
lines changed

3 files changed

+51
-37
lines changed

src/cargo/core/compiler/custom_build.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ fn emit_build_output(
128128
output: &BuildOutput,
129129
out_dir: &Path,
130130
package_id: PackageId,
131-
) {
131+
) -> CargoResult<()> {
132132
let library_paths = output
133133
.library_paths
134134
.iter()
@@ -144,7 +144,8 @@ fn emit_build_output(
144144
out_dir,
145145
}
146146
.to_json_string();
147-
state.stdout(msg);
147+
state.stdout(msg)?;
148+
Ok(())
148149
}
149150

150151
fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
@@ -353,13 +354,13 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
353354
warnings_in_case_of_panic.push(warning.to_owned());
354355
}
355356
if extra_verbose {
356-
state.stdout(format!("{}{}", prefix, stdout));
357+
state.stdout(format!("{}{}", prefix, stdout))?;
357358
}
358359
Ok(())
359360
},
360361
&mut |stderr| {
361362
if extra_verbose {
362-
state.stderr(format!("{}{}", prefix, stderr));
363+
state.stderr(format!("{}{}", prefix, stderr))?;
363364
}
364365
Ok(())
365366
},
@@ -396,7 +397,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
396397
BuildOutput::parse(&output.stdout, &pkg_name, &script_out_dir, &script_out_dir)?;
397398

398399
if json_messages {
399-
emit_build_output(state, &parsed_output, script_out_dir.as_path(), id);
400+
emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
400401
}
401402
build_script_outputs
402403
.lock()
@@ -421,7 +422,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
421422
};
422423

423424
if json_messages {
424-
emit_build_output(state, &output, script_out_dir.as_path(), id);
425+
emit_build_output(state, &output, script_out_dir.as_path(), id)?;
425426
}
426427

427428
build_script_outputs

src/cargo/core/compiler/job_queue.rs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,11 @@ pub struct JobState<'a> {
166166
/// Channel back to the main thread to coordinate messages and such.
167167
messages: Arc<Queue<Message>>,
168168

169-
/// Normally messages are handled in a bounded way. When the job is fresh
170-
/// however we need to immediately return to prevent a deadlock as the messages
171-
/// are processed on the same thread as they are sent from.
172-
messages_bounded: bool,
169+
/// Normally output is sent to the job queue with backpressure. When the job is fresh
170+
/// however we need to immediately display the output to prevent a deadlock as the
171+
/// output messages are processed on the same thread as they are sent from. `output`
172+
/// defines where to output in this case.
173+
output: Option<&'a Config>,
173174

174175
/// The job id that this state is associated with, used when sending
175176
/// messages back to the main thread.
@@ -236,20 +237,24 @@ impl<'a> JobState<'a> {
236237
.push(Message::BuildPlanMsg(module_name, cmd, filenames));
237238
}
238239

239-
pub fn stdout(&self, stdout: String) {
240-
if self.messages_bounded {
241-
self.messages.push_bounded(Message::Stdout(stdout));
240+
pub fn stdout(&self, stdout: String) -> CargoResult<()> {
241+
if let Some(config) = self.output {
242+
writeln!(config.shell().out(), "{}", stdout)?;
242243
} else {
243-
self.messages.push(Message::Stdout(stdout));
244+
self.messages.push_bounded(Message::Stdout(stdout));
244245
}
246+
Ok(())
245247
}
246248

247-
pub fn stderr(&self, stderr: String) {
248-
if self.messages_bounded {
249-
self.messages.push_bounded(Message::Stderr(stderr));
249+
pub fn stderr(&self, stderr: String) -> CargoResult<()> {
250+
if let Some(config) = self.output {
251+
let mut shell = config.shell();
252+
shell.print_ansi(stderr.as_bytes())?;
253+
shell.err().write_all(b"\n")?;
250254
} else {
251-
self.messages.push(Message::Stderr(stderr));
255+
self.messages.push_bounded(Message::Stderr(stderr));
252256
}
257+
Ok(())
253258
}
254259

255260
/// A method used to signal to the coordinator thread that the rmeta file
@@ -839,17 +844,9 @@ impl<'cfg> DrainState<'cfg> {
839844
self.note_working_on(cx.bcx.config, unit, fresh)?;
840845
}
841846

842-
let doit = move || {
843-
let state = JobState {
844-
id,
845-
messages: messages.clone(),
846-
messages_bounded: job.freshness() == Freshness::Dirty,
847-
rmeta_required: Cell::new(rmeta_required),
848-
_marker: marker::PhantomData,
849-
};
850-
847+
let doit = move |state: JobState<'_>| {
851848
let mut sender = FinishOnDrop {
852-
messages: &messages,
849+
messages: &state.messages,
853850
id,
854851
result: None,
855852
};
@@ -868,7 +865,9 @@ impl<'cfg> DrainState<'cfg> {
868865
// we need to make sure that the metadata is flagged as produced so
869866
// send a synthetic message here.
870867
if state.rmeta_required.get() && sender.result.as_ref().unwrap().is_ok() {
871-
messages.push(Message::Finish(id, Artifact::Metadata, Ok(())));
868+
state
869+
.messages
870+
.push(Message::Finish(state.id, Artifact::Metadata, Ok(())));
872871
}
873872

874873
// Use a helper struct with a `Drop` implementation to guarantee
@@ -898,11 +897,25 @@ impl<'cfg> DrainState<'cfg> {
898897
self.timings.add_fresh();
899898
// Running a fresh job on the same thread is often much faster than spawning a new
900899
// thread to run the job.
901-
doit();
900+
doit(JobState {
901+
id,
902+
messages: messages.clone(),
903+
output: Some(cx.bcx.config),
904+
rmeta_required: Cell::new(rmeta_required),
905+
_marker: marker::PhantomData,
906+
});
902907
}
903908
Freshness::Dirty => {
904909
self.timings.add_dirty();
905-
scope.spawn(move |_| doit());
910+
scope.spawn(move |_| {
911+
doit(JobState {
912+
id,
913+
messages: messages.clone(),
914+
output: None,
915+
rmeta_required: Cell::new(rmeta_required),
916+
_marker: marker::PhantomData,
917+
})
918+
});
906919
}
907920
}
908921

src/cargo/core/compiler/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ fn link_targets(cx: &mut Context<'_, '_>, unit: &Unit, fresh: bool) -> CargoResu
448448
fresh,
449449
}
450450
.to_json_string();
451-
state.stdout(msg);
451+
state.stdout(msg)?;
452452
}
453453
Ok(())
454454
}))
@@ -1139,7 +1139,7 @@ fn on_stdout_line(
11391139
_package_id: PackageId,
11401140
_target: &Target,
11411141
) -> CargoResult<()> {
1142-
state.stdout(line.to_string());
1142+
state.stdout(line.to_string())?;
11431143
Ok(())
11441144
}
11451145

@@ -1177,7 +1177,7 @@ fn on_stderr_line_inner(
11771177
// something like that), so skip over everything that doesn't look like a
11781178
// JSON message.
11791179
if !line.starts_with('{') {
1180-
state.stderr(line.to_string());
1180+
state.stderr(line.to_string())?;
11811181
return Ok(true);
11821182
}
11831183

@@ -1189,7 +1189,7 @@ fn on_stderr_line_inner(
11891189
// to stderr.
11901190
Err(e) => {
11911191
debug!("failed to parse json: {:?}", e);
1192-
state.stderr(line.to_string());
1192+
state.stderr(line.to_string())?;
11931193
return Ok(true);
11941194
}
11951195
};
@@ -1225,7 +1225,7 @@ fn on_stderr_line_inner(
12251225
.map(|v| String::from_utf8(v).expect("utf8"))
12261226
.expect("strip should never fail")
12271227
};
1228-
state.stderr(rendered);
1228+
state.stderr(rendered)?;
12291229
return Ok(true);
12301230
}
12311231
}
@@ -1316,7 +1316,7 @@ fn on_stderr_line_inner(
13161316
// Switch json lines from rustc/rustdoc that appear on stderr to stdout
13171317
// instead. We want the stdout of Cargo to always be machine parseable as
13181318
// stderr has our colorized human-readable messages.
1319-
state.stdout(msg);
1319+
state.stdout(msg)?;
13201320
Ok(true)
13211321
}
13221322

0 commit comments

Comments
 (0)