Skip to content

Commit 95cc51d

Browse files
committed
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
1 parent 6ece144 commit 95cc51d

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

src/librustc_trans/back/link.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,42 @@ fn link_natively(sess: &Session, dylib: bool,
658658
// May have not found libraries in the right formats.
659659
sess.abort_if_errors();
660660

661+
// Ensure that when we target 32-bit MSVC from a Windows host that we only
662+
// invoke at most one linker at a time.
663+
//
664+
// Well gee "whomever wrote this comment", isn't that bad! It sure is
665+
// "whomever is reading this comment"!
666+
//
667+
// The backstory on this "weird linker hack #3194" is that awhile back we
668+
// started seeing a lot of spurious failures on the 32-bit MSVC bot.
669+
// Specifically, rust-lang/rust#33145 started tracking that, and the issue
670+
// was that the linker was complaining about weird PDB RPCs. Searching
671+
// around seemed to turn up that this is related to mspdbsrv.exe not exactly
672+
// being robust in the face of things like **being run in parallel**.
673+
//
674+
// Some investigation turned up that this is indeed the case. Crucially, the
675+
// linker would fail if run in parallel. This was tested by just writing a
676+
// script that ran `rustc foo.rs` in two different directories for two
677+
// differently-named `foo.rs` files each containing `fn main() {}`. When
678+
// targeting 32-bit MSVC the linker would occasionally fail with a similar
679+
// error as in the issue mentioned above.
680+
//
681+
// So all in all, that means if we're targeting 32-bit MSVC we can't
682+
// actually run the linker in parallel! Well isn't that just a joy. To work
683+
// around this we acquire a global IPC lock for our platform in that case to
684+
// ensure that we only run one linker at a time.
685+
//
686+
// Note that the `acquire_global_lock` function is only implemented on
687+
// Windows, it's just a noop for other platforms. Hopefully the linker for
688+
// 32-bit MSVC can actually run in parallel when ported to a platform other
689+
// than Windows!
690+
let _lock = if sess.target.target.options.is_like_msvc &&
691+
sess.target.target.target_pointer_width == "32" {
692+
Some(msvc::acquire_global_lock("__rustc_32_msvc_glorious_hack"))
693+
} else {
694+
None
695+
};
696+
661697
// Invoke the system linker
662698
info!("{:?}", &cmd);
663699
let prog = time(sess.time_passes(), "running linker", || cmd.output());

src/librustc_trans/back/msvc/lock.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Bindings to acquire a global named lock.
12+
//!
13+
//! This is intended to be used to synchronize multiple compiler processes to
14+
//! ensure that only one can proceed during certain phases. Note that this is
15+
//! currently only needed for allowing only one 32-bit MSVC linker to execute at
16+
//! once on MSVC hosts, so this is only implemented for `cfg(windows)`. Also
17+
//! note that this may not always be used on Windows, only when targeting 32-bit
18+
//! MSVC.
19+
//!
20+
//! For more information about why this is necessary, see where this is called.
21+
22+
use std::any::Any;
23+
24+
#[cfg(windows)]
25+
#[allow(bad_style)]
26+
pub fn acquire_global_lock(name: &str) -> Box<Any> {
27+
use std::ffi::CString;
28+
use std::io;
29+
30+
type LPSECURITY_ATTRIBUTES = *mut u8;
31+
type BOOL = i32;
32+
type LPCSTR = *const u8;
33+
type HANDLE = *mut u8;
34+
type DWORD = u32;
35+
36+
const INFINITE: DWORD = !0;
37+
const WAIT_OBJECT_0: DWORD = 0;
38+
const WAIT_ABANDONED: DWORD = 0x00000080;
39+
40+
extern "system" {
41+
fn CreateMutexA(lpMutexAttributes: LPSECURITY_ATTRIBUTES,
42+
bInitialOwner: BOOL,
43+
lpName: LPCSTR) -> HANDLE;
44+
fn WaitForSingleObject(hHandle: HANDLE,
45+
dwMilliseconds: DWORD) -> DWORD;
46+
fn ReleaseMutex(hMutex: HANDLE) -> BOOL;
47+
fn CloseHandle(hObject: HANDLE) -> BOOL;
48+
}
49+
50+
struct Handle(HANDLE);
51+
52+
impl Drop for Handle {
53+
fn drop(&mut self) {
54+
unsafe {
55+
CloseHandle(self.0);
56+
}
57+
}
58+
}
59+
60+
struct Guard(Handle);
61+
62+
impl Drop for Guard {
63+
fn drop(&mut self) {
64+
unsafe {
65+
ReleaseMutex((self.0).0);
66+
}
67+
}
68+
}
69+
70+
let cname = CString::new(name).unwrap();
71+
unsafe {
72+
// Create a named mutex, with no security attributes and also not
73+
// acquired when we create it.
74+
//
75+
// This will silently create one if it doesn't already exist, or it'll
76+
// open up a handle to one if it already exists.
77+
let mutex = CreateMutexA(0 as *mut _, 0, cname.as_ptr() as *const u8);
78+
if mutex.is_null() {
79+
panic!("failed to create global mutex named `{}`: {}", name,
80+
io::Error::last_os_error());
81+
}
82+
let mutex = Handle(mutex);
83+
84+
// Acquire the lock through `WaitForSingleObject`.
85+
//
86+
// A return value of `WAIT_OBJECT_0` means we successfully acquired it.
87+
//
88+
// A return value of `WAIT_ABANDONED` means that the previous holder of
89+
// the thread exited without calling `ReleaseMutex`. This can happen,
90+
// for example, when the compiler crashes or is interrupted via ctrl-c
91+
// or the like. In this case, however, we are still transferred
92+
// ownership of the lock so we continue.
93+
//
94+
// If an error happens.. well... that's surprising!
95+
match WaitForSingleObject(mutex.0, INFINITE) {
96+
WAIT_OBJECT_0 | WAIT_ABANDONED => {}
97+
code => {
98+
panic!("WaitForSingleObject failed on global mutex named \
99+
`{}`: {} (ret={:x})", name,
100+
io::Error::last_os_error(), code);
101+
}
102+
}
103+
104+
// Return a guard which will call `ReleaseMutex` when dropped.
105+
Box::new(Guard(mutex))
106+
}
107+
}
108+
109+
#[cfg(unix)]
110+
pub fn acquire_global_lock(_name: &str) -> Box<Any> {
111+
Box::new(())
112+
}

src/librustc_trans/back/msvc/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
#[cfg(windows)]
3535
mod registry;
3636

37+
mod lock;
38+
pub use self::lock::acquire_global_lock;
39+
3740
#[cfg(windows)]
3841
mod platform {
3942
use std::env;

0 commit comments

Comments
 (0)