Skip to content

Commit f337029

Browse files
committed
Implement a Once primitive for initialization
Of the 8 static mutexes that are currently in-use by the compiler and its libraries, 4 of them are currently used for one-time initialization. The unforunate side effect of using a static mutex is that the mutex is leaked. This primitive should provide the basis for efficiently keeping track of one-time initialization as well as ensuring that it does not leak the internal mutex that is used. I have chosen to put this in libstd because libstd is currently making use of a static initialization mutex (rt::local_ptr), but I can also see a more refined version of this type being suitable to initialize FFI bindings (such as initializing LLVM and initializing winsock networking on windows). I also intend on adding "helper threads" to libnative, and those will greatly benefit from a simple "once" primitive rather than always reinventing the wheel by using mutexes and bools. I would much rather see this primitive built on a mutex that blocks green threads appropriately, but that does not exist at this time, so it does not belong outside of `std::unstable`.
1 parent c0d4abf commit f337029

File tree

1 file changed

+145
-1
lines changed

1 file changed

+145
-1
lines changed

src/libstd/unstable/mutex.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,154 @@ mod imp {
315315
}
316316
}
317317

318+
/// A type which can be used to run a one-time global initialization. This type
319+
/// is *unsafe* to use because it is built on top of the `Mutex` in this module.
320+
/// It does not know whether the currently running task is in a green or native
321+
/// context, and a blocking mutex should *not* be used under normal
322+
/// circumstances on a green task.
323+
///
324+
/// Despite its unsafety, it is often useful to have a one-time initialization
325+
/// routine run for FFI bindings or related external functionality. This type
326+
/// can only be statically constructed with the `ONCE_INIT` value.
327+
///
328+
/// # Example
329+
///
330+
/// ```rust
331+
/// use std::unstable::mutex::{Once, ONCE_INIT};
332+
///
333+
/// static mut START: Once = ONCE_INIT;
334+
/// unsafe {
335+
/// START.doit(|| {
336+
/// // run initialization here
337+
/// });
338+
/// }
339+
/// ```
340+
pub struct Once {
341+
priv mutex: Mutex,
342+
priv cnt: AtomicInt,
343+
priv lock_cnt: AtomicInt,
344+
}
345+
346+
/// Initialization value for static `Once` values.
347+
pub static ONCE_INIT: Once = Once {
348+
mutex: MUTEX_INIT,
349+
cnt: INIT_ATOMIC_INT,
350+
lock_cnt: INIT_ATOMIC_INT,
351+
};
352+
353+
impl Once {
354+
/// Perform an initialization routine once and only once. The given closure
355+
/// will be executed if this is the first time `doit` has been called, and
356+
/// otherwise the routine will *not* be invoked.
357+
///
358+
/// This method will block the calling *os thread* if another initialization
359+
/// routine is currently running.
360+
///
361+
/// When this function returns, it is guaranteed that some initialization
362+
/// has run and completed (it may not be the closure specified).
363+
pub fn doit(&mut self, f: ||) {
364+
// Implementation-wise, this would seem like a fairly trivial primitive.
365+
// The stickler part is where our mutexes currently require an
366+
// allocation, and usage of a `Once` should't leak this allocation.
367+
//
368+
// This means that there must be a deterministic destroyer of the mutex
369+
// contained within (because it's not needed after the initialization
370+
// has run).
371+
//
372+
// The general scheme here is to gate all future threads once
373+
// initialization has completed with a "very negative" count, and to
374+
// allow through threads to lock the mutex if they see a non negative
375+
// count. For all threads grabbing the mutex, exactly one of them should
376+
// be responsible for unlocking the mutex, and this should only be done
377+
// once everyone else is done with the mutex.
378+
//
379+
// This atomicity is achieved by swapping a very negative value into the
380+
// shared count when the initialization routine has completed. This will
381+
// read the number of threads which will at some point attempt to
382+
// acquire the mutex. This count is then squirreled away in a separate
383+
// variable, and the last person on the way out of the mutex is then
384+
// responsible for destroying the mutex.
385+
//
386+
// It is crucial that the negative value is swapped in *after* the
387+
// initialization routine has completed because otherwise new threads
388+
// calling `doit` will return immediately before the initialization has
389+
// completed.
390+
391+
let prev = self.cnt.fetch_add(1, SeqCst);
392+
if prev < 0 {
393+
// Make sure we never overflow, we'll never have int::min_value
394+
// simultaneous calls to `doit` to make this value go back to 0
395+
self.cnt.store(int::min_value, SeqCst);
396+
return
397+
}
398+
399+
// If the count is negative, then someone else finished the job,
400+
// otherwise we run the job and record how many people will try to grab
401+
// this lock
402+
unsafe { self.mutex.lock() }
403+
if self.cnt.load(SeqCst) > 0 {
404+
f();
405+
let prev = self.cnt.swap(int::min_value, SeqCst);
406+
self.lock_cnt.store(prev, SeqCst);
407+
}
408+
unsafe { self.mutex.unlock() }
409+
410+
// Last one out cleans up after everyone else, no leaks!
411+
if self.lock_cnt.fetch_add(-1, SeqCst) == 1 {
412+
unsafe { self.mutex.destroy() }
413+
}
414+
}
415+
}
416+
318417
#[cfg(test)]
319418
mod test {
320-
use super::{Mutex, MUTEX_INIT};
321419
use rt::thread::Thread;
420+
use super::{ONCE_INIT, Once, Mutex, MUTEX_INIT};
421+
use task;
422+
423+
#[test]
424+
fn smoke_once() {
425+
static mut o: Once = ONCE_INIT;
426+
let mut a = 0;
427+
unsafe { o.doit(|| a += 1); }
428+
assert_eq!(a, 1);
429+
unsafe { o.doit(|| a += 1); }
430+
assert_eq!(a, 1);
431+
}
432+
433+
#[test]
434+
fn stampede_once() {
435+
static mut o: Once = ONCE_INIT;
436+
static mut run: bool = false;
437+
438+
let (p, c) = SharedChan::new();
439+
for _ in range(0, 10) {
440+
let c = c.clone();
441+
do spawn {
442+
for _ in range(0, 4) { task::deschedule() }
443+
unsafe {
444+
o.doit(|| {
445+
assert!(!run);
446+
run = true;
447+
});
448+
assert!(run);
449+
}
450+
c.send(());
451+
}
452+
}
453+
454+
unsafe {
455+
o.doit(|| {
456+
assert!(!run);
457+
run = true;
458+
});
459+
assert!(run);
460+
}
461+
462+
for _ in range(0, 10) {
463+
p.recv();
464+
}
465+
}
322466

323467
#[test]
324468
fn somke_lock() {

0 commit comments

Comments
 (0)