From 95cc51de42bfb2cabba1c0bbc0a6402caa73d671 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 22 Apr 2016 12:47:07 -0700 Subject: [PATCH] rustc: Globally lock link.exe on 32-bit MSVC Looks like `link.exe` can't be called in parallel when it is targeting 32-bit. There's some weird complications with `mspdbsrv.exe` which can't seem to be worked around. Instead, let's just globally lock all invocations of the linker to ensure there's only one linker running at a time. For more detail, see the comments in the commit itself. Closes #33145 --- src/librustc_trans/back/link.rs | 36 +++++++++ src/librustc_trans/back/msvc/lock.rs | 112 +++++++++++++++++++++++++++ src/librustc_trans/back/msvc/mod.rs | 3 + 3 files changed, 151 insertions(+) create mode 100644 src/librustc_trans/back/msvc/lock.rs diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 4e77b2bc06940..b4111434d7dfe 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -658,6 +658,42 @@ fn link_natively(sess: &Session, dylib: bool, // May have not found libraries in the right formats. sess.abort_if_errors(); + // Ensure that when we target 32-bit MSVC from a Windows host that we only + // invoke at most one linker at a time. + // + // Well gee "whomever wrote this comment", isn't that bad! It sure is + // "whomever is reading this comment"! + // + // The backstory on this "weird linker hack #3194" is that awhile back we + // started seeing a lot of spurious failures on the 32-bit MSVC bot. + // Specifically, rust-lang/rust#33145 started tracking that, and the issue + // was that the linker was complaining about weird PDB RPCs. Searching + // around seemed to turn up that this is related to mspdbsrv.exe not exactly + // being robust in the face of things like **being run in parallel**. + // + // Some investigation turned up that this is indeed the case. Crucially, the + // linker would fail if run in parallel. This was tested by just writing a + // script that ran `rustc foo.rs` in two different directories for two + // differently-named `foo.rs` files each containing `fn main() {}`. When + // targeting 32-bit MSVC the linker would occasionally fail with a similar + // error as in the issue mentioned above. + // + // So all in all, that means if we're targeting 32-bit MSVC we can't + // actually run the linker in parallel! Well isn't that just a joy. To work + // around this we acquire a global IPC lock for our platform in that case to + // ensure that we only run one linker at a time. + // + // Note that the `acquire_global_lock` function is only implemented on + // Windows, it's just a noop for other platforms. Hopefully the linker for + // 32-bit MSVC can actually run in parallel when ported to a platform other + // than Windows! + let _lock = if sess.target.target.options.is_like_msvc && + sess.target.target.target_pointer_width == "32" { + Some(msvc::acquire_global_lock("__rustc_32_msvc_glorious_hack")) + } else { + None + }; + // Invoke the system linker info!("{:?}", &cmd); let prog = time(sess.time_passes(), "running linker", || cmd.output()); diff --git a/src/librustc_trans/back/msvc/lock.rs b/src/librustc_trans/back/msvc/lock.rs new file mode 100644 index 0000000000000..72ca03de12d18 --- /dev/null +++ b/src/librustc_trans/back/msvc/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 only one can proceed during certain phases. 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(()) +} diff --git a/src/librustc_trans/back/msvc/mod.rs b/src/librustc_trans/back/msvc/mod.rs index 0112da57cc0a6..42dbaecf5176b 100644 --- a/src/librustc_trans/back/msvc/mod.rs +++ b/src/librustc_trans/back/msvc/mod.rs @@ -34,6 +34,9 @@ #[cfg(windows)] mod registry; +mod lock; +pub use self::lock::acquire_global_lock; + #[cfg(windows)] mod platform { use std::env;