From a65b201d94422eb81addef4bed2d37211e39661b Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 25 Aug 2016 13:28:35 -0700 Subject: [PATCH] prevent error message interleaving on win/unix --- src/librustc_errors/emitter.rs | 87 ++++++++++++++++++++++++- src/librustc_errors/lib.rs | 1 + src/librustc_errors/lock.rs | 112 +++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/librustc_errors/lock.rs diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs index 793155cfa8f..ed133d21b8a 100644 --- a/src/librustc_errors/emitter.rs +++ b/src/librustc_errors/emitter.rs @@ -724,7 +724,10 @@ fn emit_messages_default(&mut self, } match write!(&mut self.dst, "\n") { Err(e) => panic!("failed to emit error: {}", e), - _ => () + _ => match self.dst.flush() { + Err(e) => panic!("failed to emit error: {}", e), + _ => () + } } } } @@ -749,6 +752,21 @@ fn overlaps(a1: &Annotation, a2: &Annotation) -> bool { fn emit_to_destination(rendered_buffer: &Vec>, lvl: &Level, dst: &mut Destination) -> io::Result<()> { + use lock; + + // In order to prevent error message interleaving, where multiple error lines get intermixed + // when multiple compiler processes error simultaneously, we emit errors with additional + // steps. + // + // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When + // the .flush() is called we take the buffer created from the buffered writes and write it at + // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling + // scheme, this buffered approach works and maintains the styling. + // + // On Windows, styling happens through calls to a terminal API. This prevents us from using the + // same buffering approach. Instead, we use a global Windows mutex, which we acquire long + // enough to output the full error message, then we release. + let _buffer_lock = lock::acquire_global_lock("rustc_errors"); for line in rendered_buffer { for part in line { dst.apply_style(lvl.clone(), part.style)?; @@ -757,6 +775,7 @@ fn emit_to_destination(rendered_buffer: &Vec>, } write!(dst, "\n")?; } + dst.flush()?; Ok(()) } @@ -783,14 +802,74 @@ fn GetConsoleMode(hConsoleHandle: HANDLE, } } +pub type BufferedStderr = term::Terminal + Send; + pub enum Destination { Terminal(Box), + BufferedTerminal(Box), Raw(Box), } +/// Buffered writer gives us a way on Unix to buffer up an entire error message before we output +/// it. This helps to prevent interleaving of multiple error messages when multiple compiler +/// processes error simultaneously +pub struct BufferedWriter { + buffer: Vec, +} + +impl BufferedWriter { + // note: we use _new because the conditional compilation at its use site may make this + // this function unused on some platforms + fn _new() -> BufferedWriter { + BufferedWriter { + buffer: vec![] + } + } +} + +impl Write for BufferedWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + for b in buf { + self.buffer.push(*b); + } + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + let mut stderr = io::stderr(); + let result = (|| { + stderr.write_all(&self.buffer)?; + stderr.flush() + })(); + self.buffer.clear(); + result + } +} + impl Destination { + #[cfg(not(windows))] + /// When not on Windows, prefer the buffered terminal so that we can buffer an entire error + /// to be emitted at one time. + fn from_stderr() -> Destination { + let stderr: Option> = + term::TerminfoTerminal::new(BufferedWriter::_new()) + .map(|t| Box::new(t) as Box); + + match stderr { + Some(t) => BufferedTerminal(t), + None => Raw(Box::new(io::stderr())), + } + } + + #[cfg(windows)] + /// Return a normal, unbuffered terminal when on Windows. fn from_stderr() -> Destination { - match term::stderr() { + let stderr: Option> = + term::TerminfoTerminal::new(io::stderr()) + .map(|t| Box::new(t) as Box) + .or_else(|| term::WinConsole::new(io::stderr()).ok() + .map(|t| Box::new(t) as Box)); + + match stderr { Some(t) => Terminal(t), None => Raw(Box::new(io::stderr())), } @@ -839,6 +918,7 @@ fn apply_style(&mut self, fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { match *self { Terminal(ref mut t) => { t.attr(attr)?; } + BufferedTerminal(ref mut t) => { t.attr(attr)?; } Raw(_) => { } } Ok(()) @@ -847,6 +927,7 @@ fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { fn reset_attrs(&mut self) -> io::Result<()> { match *self { Terminal(ref mut t) => { t.reset()?; } + BufferedTerminal(ref mut t) => { t.reset()?; } Raw(_) => { } } Ok(()) @@ -857,12 +938,14 @@ impl Write for Destination { fn write(&mut self, bytes: &[u8]) -> io::Result { match *self { Terminal(ref mut t) => t.write(bytes), + BufferedTerminal(ref mut t) => t.write(bytes), Raw(ref mut w) => w.write(bytes), } } fn flush(&mut self) -> io::Result<()> { match *self { Terminal(ref mut t) => t.flush(), + BufferedTerminal(ref mut t) => t.flush(), Raw(ref mut w) => w.flush(), } } diff --git a/src/librustc_errors/lib.rs b/src/librustc_errors/lib.rs index 4b3e53d931f..c99bc470448 100644 --- a/src/librustc_errors/lib.rs +++ b/src/librustc_errors/lib.rs @@ -50,6 +50,7 @@ pub mod snippet; pub mod registry; pub mod styled_buffer; +mod lock; use syntax_pos::{BytePos, Loc, FileLinesResult, FileName, MultiSpan, Span, NO_EXPANSION }; use syntax_pos::{MacroBacktrace}; diff --git a/src/librustc_errors/lock.rs b/src/librustc_errors/lock.rs new file mode 100644 index 00000000000..0a9e0c4bbef --- /dev/null +++ b/src/librustc_errors/lock.rs @@ -0,0 +1,112 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Bindings to acquire a global named lock. +//! +//! This is intended to be used to synchronize multiple compiler processes to +//! ensure that we can output complete errors without interleaving on Windows. +//! Note that this is currently only needed for allowing only one 32-bit MSVC +//! linker to execute at once on MSVC hosts, so this is only implemented for +//! `cfg(windows)`. Also note that this may not always be used on Windows, +//! only when targeting 32-bit MSVC. +//! +//! For more information about why this is necessary, see where this is called. + +use std::any::Any; + +#[cfg(windows)] +#[allow(bad_style)] +pub fn acquire_global_lock(name: &str) -> Box { + use std::ffi::CString; + use std::io; + + type LPSECURITY_ATTRIBUTES = *mut u8; + type BOOL = i32; + type LPCSTR = *const u8; + type HANDLE = *mut u8; + type DWORD = u32; + + const INFINITE: DWORD = !0; + const WAIT_OBJECT_0: DWORD = 0; + const WAIT_ABANDONED: DWORD = 0x00000080; + + extern "system" { + fn CreateMutexA(lpMutexAttributes: LPSECURITY_ATTRIBUTES, + bInitialOwner: BOOL, + lpName: LPCSTR) -> HANDLE; + fn WaitForSingleObject(hHandle: HANDLE, + dwMilliseconds: DWORD) -> DWORD; + fn ReleaseMutex(hMutex: HANDLE) -> BOOL; + fn CloseHandle(hObject: HANDLE) -> BOOL; + } + + struct Handle(HANDLE); + + impl Drop for Handle { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } + } + + struct Guard(Handle); + + impl Drop for Guard { + fn drop(&mut self) { + unsafe { + ReleaseMutex((self.0).0); + } + } + } + + let cname = CString::new(name).unwrap(); + unsafe { + // Create a named mutex, with no security attributes and also not + // acquired when we create it. + // + // This will silently create one if it doesn't already exist, or it'll + // open up a handle to one if it already exists. + let mutex = CreateMutexA(0 as *mut _, 0, cname.as_ptr() as *const u8); + if mutex.is_null() { + panic!("failed to create global mutex named `{}`: {}", name, + io::Error::last_os_error()); + } + let mutex = Handle(mutex); + + // Acquire the lock through `WaitForSingleObject`. + // + // A return value of `WAIT_OBJECT_0` means we successfully acquired it. + // + // A return value of `WAIT_ABANDONED` means that the previous holder of + // the thread exited without calling `ReleaseMutex`. This can happen, + // for example, when the compiler crashes or is interrupted via ctrl-c + // or the like. In this case, however, we are still transferred + // ownership of the lock so we continue. + // + // If an error happens.. well... that's surprising! + match WaitForSingleObject(mutex.0, INFINITE) { + WAIT_OBJECT_0 | WAIT_ABANDONED => {} + code => { + panic!("WaitForSingleObject failed on global mutex named \ + `{}`: {} (ret={:x})", name, + io::Error::last_os_error(), code); + } + } + + // Return a guard which will call `ReleaseMutex` when dropped. + Box::new(Guard(mutex)) + } +} + +#[cfg(unix)] +pub fn acquire_global_lock(_name: &str) -> Box { + Box::new(()) +} -- 2.44.0