Skip to content

rustc: Globally lock link.exe on 32-bit MSVC #33155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/librustc_trans/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
112 changes: 112 additions & 0 deletions src/librustc_trans/back/msvc/lock.rs
Original file line number Diff line number Diff line change
@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Any> {
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<Any> {
Box::new(())
}
3 changes: 3 additions & 0 deletions src/librustc_trans/back/msvc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
#[cfg(windows)]
mod registry;

mod lock;
pub use self::lock::acquire_global_lock;

#[cfg(windows)]
mod platform {
use std::env;
Expand Down