Skip to content

Add a Once primitive #11187

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

Merged
merged 2 commits into from
Jan 1, 2014
Merged
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
13 changes: 4 additions & 9 deletions src/libnative/io/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,14 @@ pub fn init() {
}

unsafe {
use std::unstable::mutex::{Mutex, MUTEX_INIT};
static mut LOCK: Mutex = MUTEX_INIT;
static mut INITIALIZED: bool = false;
if INITIALIZED { return }
LOCK.lock();
if !INITIALIZED {
use std::unstable::mutex::{Once, ONCE_INIT};
static mut INIT: Once = ONCE_INIT;
INIT.doit(|| {
let mut data: WSADATA = intrinsics::init();
let ret = WSAStartup(0x202, // version 2.2
&mut data);
assert_eq!(ret, 0);
INITIALIZED = true;
}
LOCK.unlock();
});
}
}

Expand Down
12 changes: 4 additions & 8 deletions src/librustc/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,8 @@ pub mod write {
}

unsafe fn configure_llvm(sess: Session) {
use std::unstable::mutex::{MUTEX_INIT, Mutex};
static mut LOCK: Mutex = MUTEX_INIT;
static mut CONFIGURED: bool = false;
use std::unstable::mutex::{Once, ONCE_INIT};
static mut INIT: Once = ONCE_INIT;

// Copy what clan does by turning on loop vectorization at O2 and
// slp vectorization at O3
Expand Down Expand Up @@ -341,8 +340,7 @@ pub mod write {
add(*arg);
}

LOCK.lock();
if !CONFIGURED {
INIT.doit(|| {
llvm::LLVMInitializePasses();

// Only initialize the platforms supported by Rust here, because
Expand All @@ -369,9 +367,7 @@ pub mod write {

llvm::LLVMRustSetLLVMOptions(llvm_args.len() as c_int,
llvm_args.as_ptr());
CONFIGURED = true;
}
LOCK.unlock();
});
}

unsafe fn populate_llvm_passes(fpm: lib::llvm::PassManagerRef,
Expand Down
5 changes: 3 additions & 2 deletions src/librustc/lib/llvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1455,15 +1455,16 @@ pub mod llvm {
BufferName: *c_char)
-> MemoryBufferRef;

pub fn LLVMIsMultithreaded() -> Bool;
pub fn LLVMStartMultithreaded() -> Bool;

/** Returns a string describing the last error caused by an LLVMRust*
call. */
pub fn LLVMRustGetLastError() -> *c_char;

/// Print the pass timings since static dtors aren't picking them up.
pub fn LLVMRustPrintPassTimings();

pub fn LLVMRustStartMultithreading() -> bool;

pub fn LLVMStructCreateNamed(C: ContextRef, Name: *c_char) -> TypeRef;

pub fn LLVMStructSetBody(StructTy: TypeRef,
Expand Down
17 changes: 15 additions & 2 deletions src/librustc/middle/trans/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3189,8 +3189,21 @@ pub fn trans_crate(sess: session::Session,
analysis: &CrateAnalysis,
output: &Path) -> CrateTranslation {
// Before we touch LLVM, make sure that multithreading is enabled.
if unsafe { !llvm::LLVMRustStartMultithreading() } {
sess.bug("couldn't enable multi-threaded LLVM");
unsafe {
use std::unstable::mutex::{Once, ONCE_INIT};
static mut INIT: Once = ONCE_INIT;
static mut POISONED: bool = false;
INIT.doit(|| {
if llvm::LLVMStartMultithreaded() != 1 {
// use an extra bool to make sure that all future usage of LLVM
// cannot proceed despite the Once not running more than once.
POISONED = true;
}
});

if POISONED {
sess.bug("couldn't enable multi-threaded LLVM");
}
}

let mut symbol_hasher = Sha256::new();
Expand Down
14 changes: 2 additions & 12 deletions src/libstd/rt/local_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,30 +160,20 @@ pub mod native {
use option::{Option, Some, None};
use ptr;
use tls = rt::thread_local_storage;
use unstable::mutex::{Mutex, MUTEX_INIT};

static mut LOCK: Mutex = MUTEX_INIT;
static mut INITIALIZED: bool = false;
static mut RT_TLS_KEY: tls::Key = -1;

/// Initialize the TLS key. Other ops will fail if this isn't executed
/// first.
pub fn init() {
unsafe {
LOCK.lock();
if !INITIALIZED {
tls::create(&mut RT_TLS_KEY);
INITIALIZED = true;
}
LOCK.unlock();
tls::create(&mut RT_TLS_KEY);
}
}

pub unsafe fn cleanup() {
rtassert!(INITIALIZED);
rtassert!(RT_TLS_KEY != -1);
tls::destroy(RT_TLS_KEY);
LOCK.destroy();
INITIALIZED = false;
}

/// Give a pointer to thread-local storage.
Expand Down
149 changes: 148 additions & 1 deletion src/libstd/unstable/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

#[allow(non_camel_case_types)];

use int;
use libc::c_void;
use sync::atomics;

Expand Down Expand Up @@ -315,10 +316,156 @@ mod imp {
}
}

/// A type which can be used to run a one-time global initialization. This type
/// is *unsafe* to use because it is built on top of the `Mutex` in this module.
/// It does not know whether the currently running task is in a green or native
/// context, and a blocking mutex should *not* be used under normal
/// circumstances on a green task.
///
/// Despite its unsafety, it is often useful to have a one-time initialization
/// routine run for FFI bindings or related external functionality. This type
/// can only be statically constructed with the `ONCE_INIT` value.
///
/// # Example
///
/// ```rust
/// use std::unstable::mutex::{Once, ONCE_INIT};
///
/// static mut START: Once = ONCE_INIT;
/// unsafe {
/// START.doit(|| {
/// // run initialization here
/// });
/// }
/// ```
pub struct Once {
priv mutex: Mutex,
priv cnt: atomics::AtomicInt,
priv lock_cnt: atomics::AtomicInt,
}

/// Initialization value for static `Once` values.
pub static ONCE_INIT: Once = Once {
mutex: MUTEX_INIT,
cnt: atomics::INIT_ATOMIC_INT,
lock_cnt: atomics::INIT_ATOMIC_INT,
};

impl Once {
/// Perform an initialization routine once and only once. The given closure
/// will be executed if this is the first time `doit` has been called, and
/// otherwise the routine will *not* be invoked.
///
/// This method will block the calling *os thread* if another initialization
/// routine is currently running.
///
/// When this function returns, it is guaranteed that some initialization
/// has run and completed (it may not be the closure specified).
pub fn doit(&mut self, f: ||) {
// Implementation-wise, this would seem like a fairly trivial primitive.
// The stickler part is where our mutexes currently require an
// allocation, and usage of a `Once` should't leak this allocation.
//
// This means that there must be a deterministic destroyer of the mutex
// contained within (because it's not needed after the initialization
// has run).
//
// The general scheme here is to gate all future threads once
// initialization has completed with a "very negative" count, and to
// allow through threads to lock the mutex if they see a non negative
// count. For all threads grabbing the mutex, exactly one of them should
// be responsible for unlocking the mutex, and this should only be done
// once everyone else is done with the mutex.
//
// This atomicity is achieved by swapping a very negative value into the
// shared count when the initialization routine has completed. This will
// read the number of threads which will at some point attempt to
// acquire the mutex. This count is then squirreled away in a separate
// variable, and the last person on the way out of the mutex is then
// responsible for destroying the mutex.
//
// It is crucial that the negative value is swapped in *after* the
// initialization routine has completed because otherwise new threads
// calling `doit` will return immediately before the initialization has
// completed.

let prev = self.cnt.fetch_add(1, atomics::SeqCst);
if prev < 0 {
// Make sure we never overflow, we'll never have int::min_value
// simultaneous calls to `doit` to make this value go back to 0
self.cnt.store(int::min_value, atomics::SeqCst);
return
}

// If the count is negative, then someone else finished the job,
// otherwise we run the job and record how many people will try to grab
// this lock
unsafe { self.mutex.lock() }
if self.cnt.load(atomics::SeqCst) > 0 {
f();
let prev = self.cnt.swap(int::min_value, atomics::SeqCst);
self.lock_cnt.store(prev, atomics::SeqCst);
}
unsafe { self.mutex.unlock() }

// Last one out cleans up after everyone else, no leaks!
if self.lock_cnt.fetch_add(-1, atomics::SeqCst) == 1 {
unsafe { self.mutex.destroy() }
}
}
}

#[cfg(test)]
mod test {
use super::{Mutex, MUTEX_INIT};
use prelude::*;

use rt::thread::Thread;
use super::{ONCE_INIT, Once, Mutex, MUTEX_INIT};
use task;

#[test]
fn smoke_once() {
static mut o: Once = ONCE_INIT;
let mut a = 0;
unsafe { o.doit(|| a += 1); }
assert_eq!(a, 1);
unsafe { o.doit(|| a += 1); }
assert_eq!(a, 1);
}

#[test]
fn stampede_once() {
static mut o: Once = ONCE_INIT;
static mut run: bool = false;

let (p, c) = SharedChan::new();
for _ in range(0, 10) {
let c = c.clone();
do spawn {
for _ in range(0, 4) { task::deschedule() }
unsafe {
o.doit(|| {
assert!(!run);
run = true;
});
assert!(run);
}
c.send(());
}
}

unsafe {
o.doit(|| {
assert!(!run);
run = true;
});
assert!(run);
}

for _ in range(0, 10) {
p.recv();
}
}

#[test]
fn somke_lock() {
Expand Down
22 changes: 0 additions & 22 deletions src/rustllvm/RustWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,28 +149,6 @@ extern "C" LLVMValueRef LLVMInlineAsm(LLVMTypeRef Ty,
IsAlignStack, (InlineAsm::AsmDialect) Dialect));
}

/**
* This function is intended to be a threadsafe interface into enabling a
* multithreaded LLVM. This is invoked at the start of the translation phase of
* compilation to ensure that LLVM is ready.
*
* All of trans properly isolates LLVM with the use of a different
* LLVMContextRef per task, thus allowing parallel compilation of different
* crates in the same process. At the time of this writing, the use case for
* this is unit tests for rusti, but there are possible other applications.
*/
extern "C" bool LLVMRustStartMultithreading() {
static Mutex lock;
bool ret = true;
assert(lock.acquire());
if (!LLVMIsMultithreaded()) {
ret = LLVMStartMultithreaded();
}
assert(lock.release());
return ret;
}


typedef DIBuilder* DIBuilderRef;

template<typename DIT>
Expand Down