std: Add a new top-level thread_local module
This commit removes the `std::local_data` module in favor of a new `std::thread_local` module providing thread local storage. The module provides two variants of TLS: one which owns its contents and one which is based on scoped references. Each implementation has pros and cons listed in the documentation. Both flavors have accessors through a function called `with` which yield a reference to a closure provided. Both flavors also panic if a reference cannot be yielded and provide a function to test whether an access would panic or not. This is an implementation of [RFC 461][rfc] and full details can be found in that RFC. This is a breaking change due to the removal of the `std::local_data` module. All users can migrate to the new thread local system like so: thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None))) The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as an implementation detail which must now be explicitly stated by users. [rfc]: https://github.com/rust-lang/rfcs/pull/461 [breaking-change]
This commit is contained in:
parent
4e5259503c
commit
a9c1152c4b
38 changed files with 1810 additions and 1151 deletions
|
@ -171,7 +171,7 @@
|
||||||
|
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
|
||||||
use regex::Regex;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::LineBufferedWriter;
|
use std::io::LineBufferedWriter;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -181,6 +181,8 @@ use std::rt;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::sync::{Once, ONCE_INIT};
|
use std::sync::{Once, ONCE_INIT};
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use directive::LOG_LEVEL_NAMES;
|
use directive::LOG_LEVEL_NAMES;
|
||||||
|
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
@ -213,7 +215,9 @@ pub const WARN: u32 = 2;
|
||||||
/// Error log level
|
/// Error log level
|
||||||
pub const ERROR: u32 = 1;
|
pub const ERROR: u32 = 1;
|
||||||
|
|
||||||
local_data_key!(local_logger: Box<Logger + Send>)
|
thread_local!(static LOCAL_LOGGER: RefCell<Option<Box<Logger + Send>>> = {
|
||||||
|
RefCell::new(None)
|
||||||
|
})
|
||||||
|
|
||||||
/// A trait used to represent an interface to a task-local logger. Each task
|
/// A trait used to represent an interface to a task-local logger. Each task
|
||||||
/// can have its own custom logger which can respond to logging messages
|
/// can have its own custom logger which can respond to logging messages
|
||||||
|
@ -283,7 +287,9 @@ pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) {
|
||||||
// Completely remove the local logger from TLS in case anyone attempts to
|
// Completely remove the local logger from TLS in case anyone attempts to
|
||||||
// frob the slot while we're doing the logging. This will destroy any logger
|
// frob the slot while we're doing the logging. This will destroy any logger
|
||||||
// set during logging.
|
// set during logging.
|
||||||
let mut logger = local_logger.replace(None).unwrap_or_else(|| {
|
let mut logger = LOCAL_LOGGER.with(|s| {
|
||||||
|
s.borrow_mut().take()
|
||||||
|
}).unwrap_or_else(|| {
|
||||||
box DefaultLogger { handle: io::stderr() } as Box<Logger + Send>
|
box DefaultLogger { handle: io::stderr() } as Box<Logger + Send>
|
||||||
});
|
});
|
||||||
logger.log(&LogRecord {
|
logger.log(&LogRecord {
|
||||||
|
@ -293,7 +299,7 @@ pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) {
|
||||||
module_path: loc.module_path,
|
module_path: loc.module_path,
|
||||||
line: loc.line,
|
line: loc.line,
|
||||||
});
|
});
|
||||||
local_logger.replace(Some(logger));
|
set_logger(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Getter for the global log level. This is a function so that it can be called
|
/// Getter for the global log level. This is a function so that it can be called
|
||||||
|
@ -305,7 +311,10 @@ pub fn log_level() -> u32 { unsafe { LOG_LEVEL } }
|
||||||
/// Replaces the task-local logger with the specified logger, returning the old
|
/// Replaces the task-local logger with the specified logger, returning the old
|
||||||
/// logger.
|
/// logger.
|
||||||
pub fn set_logger(logger: Box<Logger + Send>) -> Option<Box<Logger + Send>> {
|
pub fn set_logger(logger: Box<Logger + Send>) -> Option<Box<Logger + Send>> {
|
||||||
local_logger.replace(Some(logger))
|
let mut l = Some(logger);
|
||||||
|
LOCAL_LOGGER.with(|slot| {
|
||||||
|
mem::replace(&mut *slot.borrow_mut(), l.take())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A LogRecord is created by the logging macros, and passed as the only
|
/// A LogRecord is created by the logging macros, and passed as the only
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::{RefCell, Cell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Show;
|
use std::fmt::Show;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
@ -26,11 +26,14 @@ use syntax::visit::Visitor;
|
||||||
pub struct ErrorReported;
|
pub struct ErrorReported;
|
||||||
|
|
||||||
pub fn time<T, U>(do_it: bool, what: &str, u: U, f: |U| -> T) -> T {
|
pub fn time<T, U>(do_it: bool, what: &str, u: U, f: |U| -> T) -> T {
|
||||||
local_data_key!(depth: uint);
|
thread_local!(static DEPTH: Cell<uint> = Cell::new(0));
|
||||||
if !do_it { return f(u); }
|
if !do_it { return f(u); }
|
||||||
|
|
||||||
let old = depth.get().map(|d| *d).unwrap_or(0);
|
let old = DEPTH.with(|slot| {
|
||||||
depth.replace(Some(old + 1));
|
let r = slot.get();
|
||||||
|
slot.set(r + 1);
|
||||||
|
r
|
||||||
|
});
|
||||||
|
|
||||||
let mut u = Some(u);
|
let mut u = Some(u);
|
||||||
let mut rv = None;
|
let mut rv = None;
|
||||||
|
@ -41,7 +44,7 @@ pub fn time<T, U>(do_it: bool, what: &str, u: U, f: |U| -> T) -> T {
|
||||||
|
|
||||||
println!("{}time: {}.{:03} \t{}", " ".repeat(old),
|
println!("{}time: {}.{:03} \t{}", " ".repeat(old),
|
||||||
dur.num_seconds(), dur.num_milliseconds() % 1000, what);
|
dur.num_seconds(), dur.num_milliseconds() % 1000, what);
|
||||||
depth.replace(Some(old));
|
DEPTH.with(|slot| slot.set(old));
|
||||||
|
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,17 +100,20 @@ use syntax::visit::Visitor;
|
||||||
use syntax::visit;
|
use syntax::visit;
|
||||||
use syntax::{ast, ast_util, ast_map};
|
use syntax::{ast, ast_util, ast_map};
|
||||||
|
|
||||||
local_data_key!(task_local_insn_key: RefCell<Vec<&'static str>>)
|
thread_local!(static TASK_LOCAL_INSN_KEY: RefCell<Option<Vec<&'static str>>> = {
|
||||||
|
RefCell::new(None)
|
||||||
|
})
|
||||||
|
|
||||||
pub fn with_insn_ctxt(blk: |&[&'static str]|) {
|
pub fn with_insn_ctxt(blk: |&[&'static str]|) {
|
||||||
match task_local_insn_key.get() {
|
TASK_LOCAL_INSN_KEY.with(|slot| {
|
||||||
Some(ctx) => blk(ctx.borrow().as_slice()),
|
slot.borrow().as_ref().map(|s| blk(s.as_slice()));
|
||||||
None => ()
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_insn_ctxt() {
|
pub fn init_insn_ctxt() {
|
||||||
task_local_insn_key.replace(Some(RefCell::new(Vec::new())));
|
TASK_LOCAL_INSN_KEY.with(|slot| {
|
||||||
|
*slot.borrow_mut() = Some(Vec::new());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct _InsnCtxt {
|
pub struct _InsnCtxt {
|
||||||
|
@ -120,19 +123,23 @@ pub struct _InsnCtxt {
|
||||||
#[unsafe_destructor]
|
#[unsafe_destructor]
|
||||||
impl Drop for _InsnCtxt {
|
impl Drop for _InsnCtxt {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
match task_local_insn_key.get() {
|
TASK_LOCAL_INSN_KEY.with(|slot| {
|
||||||
Some(ctx) => { ctx.borrow_mut().pop(); }
|
match slot.borrow_mut().as_mut() {
|
||||||
None => {}
|
Some(ctx) => { ctx.pop(); }
|
||||||
}
|
None => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_ctxt(s: &'static str) -> _InsnCtxt {
|
pub fn push_ctxt(s: &'static str) -> _InsnCtxt {
|
||||||
debug!("new InsnCtxt: {}", s);
|
debug!("new InsnCtxt: {}", s);
|
||||||
match task_local_insn_key.get() {
|
TASK_LOCAL_INSN_KEY.with(|slot| {
|
||||||
Some(ctx) => ctx.borrow_mut().push(s),
|
match slot.borrow_mut().as_mut() {
|
||||||
None => {}
|
Some(ctx) => ctx.push(s),
|
||||||
}
|
None => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
_InsnCtxt { _cannot_construct_outside_of_this_module: () }
|
_InsnCtxt { _cannot_construct_outside_of_this_module: () }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ use stability_summary::ModuleSummary;
|
||||||
use html::item_type;
|
use html::item_type;
|
||||||
use html::item_type::ItemType;
|
use html::item_type::ItemType;
|
||||||
use html::render;
|
use html::render;
|
||||||
use html::render::{cache_key, current_location_key};
|
use html::render::{cache, CURRENT_LOCATION_KEY};
|
||||||
|
|
||||||
/// Helper to render an optional visibility with a space after it (if the
|
/// Helper to render an optional visibility with a space after it (if the
|
||||||
/// visibility is preset)
|
/// visibility is preset)
|
||||||
|
@ -236,9 +236,9 @@ fn path(w: &mut fmt::Formatter, path: &clean::Path, print_all: bool,
|
||||||
generics.push_str(">");
|
generics.push_str(">");
|
||||||
}
|
}
|
||||||
|
|
||||||
let loc = current_location_key.get().unwrap();
|
let loc = CURRENT_LOCATION_KEY.with(|l| l.borrow().clone());
|
||||||
let cache = cache_key.get().unwrap();
|
let cache = cache();
|
||||||
let abs_root = root(&**cache, loc.as_slice());
|
let abs_root = root(&*cache, loc.as_slice());
|
||||||
let rel_root = match path.segments[0].name.as_slice() {
|
let rel_root = match path.segments[0].name.as_slice() {
|
||||||
"self" => Some("./".to_string()),
|
"self" => Some("./".to_string()),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -271,7 +271,7 @@ fn path(w: &mut fmt::Formatter, path: &clean::Path, print_all: bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match info(&**cache) {
|
match info(&*cache) {
|
||||||
// This is a documented path, link to it!
|
// This is a documented path, link to it!
|
||||||
Some((ref fqp, shortty)) if abs_root.is_some() => {
|
Some((ref fqp, shortty)) if abs_root.is_some() => {
|
||||||
let mut url = String::from_str(abs_root.unwrap().as_slice());
|
let mut url = String::from_str(abs_root.unwrap().as_slice());
|
||||||
|
@ -308,12 +308,12 @@ fn path(w: &mut fmt::Formatter, path: &clean::Path, print_all: bool,
|
||||||
fn primitive_link(f: &mut fmt::Formatter,
|
fn primitive_link(f: &mut fmt::Formatter,
|
||||||
prim: clean::PrimitiveType,
|
prim: clean::PrimitiveType,
|
||||||
name: &str) -> fmt::Result {
|
name: &str) -> fmt::Result {
|
||||||
let m = cache_key.get().unwrap();
|
let m = cache();
|
||||||
let mut needs_termination = false;
|
let mut needs_termination = false;
|
||||||
match m.primitive_locations.get(&prim) {
|
match m.primitive_locations.get(&prim) {
|
||||||
Some(&ast::LOCAL_CRATE) => {
|
Some(&ast::LOCAL_CRATE) => {
|
||||||
let loc = current_location_key.get().unwrap();
|
let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len());
|
||||||
let len = if loc.len() == 0 {0} else {loc.len() - 1};
|
let len = if len == 0 {0} else {len - 1};
|
||||||
try!(write!(f, "<a href='{}primitive.{}.html'>",
|
try!(write!(f, "<a href='{}primitive.{}.html'>",
|
||||||
"../".repeat(len),
|
"../".repeat(len),
|
||||||
prim.to_url_str()));
|
prim.to_url_str()));
|
||||||
|
@ -327,8 +327,8 @@ fn primitive_link(f: &mut fmt::Formatter,
|
||||||
let loc = match m.extern_locations[cnum] {
|
let loc = match m.extern_locations[cnum] {
|
||||||
render::Remote(ref s) => Some(s.to_string()),
|
render::Remote(ref s) => Some(s.to_string()),
|
||||||
render::Local => {
|
render::Local => {
|
||||||
let loc = current_location_key.get().unwrap();
|
let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len());
|
||||||
Some("../".repeat(loc.len()))
|
Some("../".repeat(len))
|
||||||
}
|
}
|
||||||
render::Unknown => None,
|
render::Unknown => None,
|
||||||
};
|
};
|
||||||
|
@ -371,12 +371,10 @@ impl fmt::Show for clean::Type {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
clean::TyParamBinder(id) => {
|
clean::TyParamBinder(id) => {
|
||||||
let m = cache_key.get().unwrap();
|
f.write(cache().typarams[ast_util::local_def(id)].as_bytes())
|
||||||
f.write(m.typarams[ast_util::local_def(id)].as_bytes())
|
|
||||||
}
|
}
|
||||||
clean::Generic(did) => {
|
clean::Generic(did) => {
|
||||||
let m = cache_key.get().unwrap();
|
f.write(cache().typarams[did].as_bytes())
|
||||||
f.write(m.typarams[did].as_bytes())
|
|
||||||
}
|
}
|
||||||
clean::ResolvedPath{ did, ref typarams, ref path } => {
|
clean::ResolvedPath{ did, ref typarams, ref path } => {
|
||||||
try!(resolved_path(f, did, path, false));
|
try!(resolved_path(f, did, path, false));
|
||||||
|
|
|
@ -147,10 +147,14 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local_data_key!(used_header_map: RefCell<HashMap<String, uint>>)
|
thread_local!(static USED_HEADER_MAP: RefCell<HashMap<String, uint>> = {
|
||||||
local_data_key!(test_idx: Cell<uint>)
|
RefCell::new(HashMap::new())
|
||||||
// None == render an example, but there's no crate name
|
})
|
||||||
local_data_key!(pub playground_krate: Option<String>)
|
thread_local!(static TEST_IDX: Cell<uint> = Cell::new(0))
|
||||||
|
|
||||||
|
thread_local!(pub static PLAYGROUND_KRATE: RefCell<Option<Option<String>>> = {
|
||||||
|
RefCell::new(None)
|
||||||
|
})
|
||||||
|
|
||||||
pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||||
extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
|
extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
|
||||||
|
@ -183,12 +187,15 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||||
stripped_filtered_line(*l).is_none()
|
stripped_filtered_line(*l).is_none()
|
||||||
});
|
});
|
||||||
let text = lines.collect::<Vec<&str>>().connect("\n");
|
let text = lines.collect::<Vec<&str>>().connect("\n");
|
||||||
if !rendered {
|
if rendered { return }
|
||||||
|
PLAYGROUND_KRATE.with(|krate| {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let id = playground_krate.get().map(|krate| {
|
let id = krate.borrow().as_ref().map(|krate| {
|
||||||
let idx = test_idx.get().unwrap();
|
let idx = TEST_IDX.with(|slot| {
|
||||||
let i = idx.get();
|
let i = slot.get();
|
||||||
idx.set(i + 1);
|
slot.set(i + 1);
|
||||||
|
i
|
||||||
|
});
|
||||||
|
|
||||||
let test = origtext.lines().map(|l| {
|
let test = origtext.lines().map(|l| {
|
||||||
stripped_filtered_line(l).unwrap_or(l)
|
stripped_filtered_line(l).unwrap_or(l)
|
||||||
|
@ -197,15 +204,15 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||||
let test = test::maketest(test.as_slice(), krate, false, false);
|
let test = test::maketest(test.as_slice(), krate, false, false);
|
||||||
s.push_str(format!("<span id='rust-example-raw-{}' \
|
s.push_str(format!("<span id='rust-example-raw-{}' \
|
||||||
class='rusttest'>{}</span>",
|
class='rusttest'>{}</span>",
|
||||||
i, Escape(test.as_slice())).as_slice());
|
idx, Escape(test.as_slice())).as_slice());
|
||||||
format!("rust-example-rendered-{}", i)
|
format!("rust-example-rendered-{}", idx)
|
||||||
});
|
});
|
||||||
let id = id.as_ref().map(|a| a.as_slice());
|
let id = id.as_ref().map(|a| a.as_slice());
|
||||||
s.push_str(highlight::highlight(text.as_slice(), None, id)
|
s.push_str(highlight::highlight(text.as_slice(), None, id)
|
||||||
.as_slice());
|
.as_slice());
|
||||||
let output = s.to_c_str();
|
let output = s.to_c_str();
|
||||||
hoedown_buffer_puts(ob, output.as_ptr());
|
hoedown_buffer_puts(ob, output.as_ptr());
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,18 +236,20 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||||
|
|
||||||
// This is a terrible hack working around how hoedown gives us rendered
|
// This is a terrible hack working around how hoedown gives us rendered
|
||||||
// html for text rather than the raw text.
|
// html for text rather than the raw text.
|
||||||
let id = id.replace("<code>", "").replace("</code>", "").to_string();
|
|
||||||
|
|
||||||
let opaque = opaque as *mut hoedown_html_renderer_state;
|
let opaque = opaque as *mut hoedown_html_renderer_state;
|
||||||
let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
|
let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
|
||||||
|
|
||||||
// Make sure our hyphenated ID is unique for this page
|
// Make sure our hyphenated ID is unique for this page
|
||||||
let map = used_header_map.get().unwrap();
|
let id = USED_HEADER_MAP.with(|map| {
|
||||||
let id = match map.borrow_mut().get_mut(&id) {
|
let id = id.replace("<code>", "").replace("</code>", "").to_string();
|
||||||
None => id,
|
let id = match map.borrow_mut().get_mut(&id) {
|
||||||
Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) }
|
None => id,
|
||||||
};
|
Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) }
|
||||||
map.borrow_mut().insert(id.clone(), 1);
|
};
|
||||||
|
map.borrow_mut().insert(id.clone(), 1);
|
||||||
|
id
|
||||||
|
});
|
||||||
|
|
||||||
let sec = match opaque.toc_builder {
|
let sec = match opaque.toc_builder {
|
||||||
Some(ref mut builder) => {
|
Some(ref mut builder) => {
|
||||||
|
@ -262,9 +271,7 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
||||||
text.with_c_str(|p| unsafe { hoedown_buffer_puts(ob, p) });
|
text.with_c_str(|p| unsafe { hoedown_buffer_puts(ob, p) });
|
||||||
}
|
}
|
||||||
|
|
||||||
if used_header_map.get().is_none() {
|
reset_headers();
|
||||||
reset_headers();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ob = hoedown_buffer_new(DEF_OUNIT);
|
let ob = hoedown_buffer_new(DEF_OUNIT);
|
||||||
|
@ -418,8 +425,8 @@ impl LangString {
|
||||||
/// used at the beginning of rendering an entire HTML page to reset from the
|
/// used at the beginning of rendering an entire HTML page to reset from the
|
||||||
/// previous state (if any).
|
/// previous state (if any).
|
||||||
pub fn reset_headers() {
|
pub fn reset_headers() {
|
||||||
used_header_map.replace(Some(RefCell::new(HashMap::new())));
|
USED_HEADER_MAP.with(|s| s.borrow_mut().clear());
|
||||||
test_idx.replace(Some(Cell::new(0)));
|
TEST_IDX.with(|s| s.set(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Show for Markdown<'a> {
|
impl<'a> fmt::Show for Markdown<'a> {
|
||||||
|
|
|
@ -34,8 +34,10 @@
|
||||||
//! both occur before the crate is rendered.
|
//! both occur before the crate is rendered.
|
||||||
pub use self::ExternalLocation::*;
|
pub use self::ExternalLocation::*;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::cell::RefCell;
|
||||||
use std::collections::hash_map::{Occupied, Vacant};
|
use std::collections::hash_map::{Occupied, Vacant};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::default::Default;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::fs::PathExtensions;
|
use std::io::fs::PathExtensions;
|
||||||
use std::io::{fs, File, BufferedWriter, BufferedReader};
|
use std::io::{fs, File, BufferedWriter, BufferedReader};
|
||||||
|
@ -141,6 +143,7 @@ pub struct Impl {
|
||||||
/// to be a fairly large and expensive structure to clone. Instead this adheres
|
/// to be a fairly large and expensive structure to clone. Instead this adheres
|
||||||
/// to `Send` so it may be stored in a `Arc` instance and shared among the various
|
/// to `Send` so it may be stored in a `Arc` instance and shared among the various
|
||||||
/// rendering tasks.
|
/// rendering tasks.
|
||||||
|
#[deriving(Default)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
/// Mapping of typaram ids to the name of the type parameter. This is used
|
/// Mapping of typaram ids to the name of the type parameter. This is used
|
||||||
/// when pretty-printing a type (so pretty printing doesn't have to
|
/// when pretty-printing a type (so pretty printing doesn't have to
|
||||||
|
@ -235,8 +238,9 @@ struct IndexItem {
|
||||||
|
|
||||||
// TLS keys used to carry information around during rendering.
|
// TLS keys used to carry information around during rendering.
|
||||||
|
|
||||||
local_data_key!(pub cache_key: Arc<Cache>)
|
thread_local!(static CACHE_KEY: RefCell<Arc<Cache>> = Default::default())
|
||||||
local_data_key!(pub current_location_key: Vec<String> )
|
thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> =
|
||||||
|
RefCell::new(Vec::new()))
|
||||||
|
|
||||||
/// Generates the documentation for `crate` into the directory `dst`
|
/// Generates the documentation for `crate` into the directory `dst`
|
||||||
pub fn run(mut krate: clean::Crate,
|
pub fn run(mut krate: clean::Crate,
|
||||||
|
@ -280,10 +284,12 @@ pub fn run(mut krate: clean::Crate,
|
||||||
clean::NameValue(ref x, ref s)
|
clean::NameValue(ref x, ref s)
|
||||||
if "html_playground_url" == x.as_slice() => {
|
if "html_playground_url" == x.as_slice() => {
|
||||||
cx.layout.playground_url = s.to_string();
|
cx.layout.playground_url = s.to_string();
|
||||||
let name = krate.name.clone();
|
markdown::PLAYGROUND_KRATE.with(|slot| {
|
||||||
if markdown::playground_krate.get().is_none() {
|
if slot.borrow().is_none() {
|
||||||
markdown::playground_krate.replace(Some(Some(name)));
|
let name = krate.name.clone();
|
||||||
}
|
*slot.borrow_mut() = Some(Some(name));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
clean::Word(ref x)
|
clean::Word(ref x)
|
||||||
if "html_no_source" == x.as_slice() => {
|
if "html_no_source" == x.as_slice() => {
|
||||||
|
@ -297,7 +303,8 @@ pub fn run(mut krate: clean::Crate,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crawl the crate to build various caches used for the output
|
// Crawl the crate to build various caches used for the output
|
||||||
let analysis = ::analysiskey.get();
|
let analysis = ::ANALYSISKEY.with(|a| a.clone());
|
||||||
|
let analysis = analysis.borrow();
|
||||||
let public_items = analysis.as_ref().map(|a| a.public_items.clone());
|
let public_items = analysis.as_ref().map(|a| a.public_items.clone());
|
||||||
let public_items = public_items.unwrap_or(NodeSet::new());
|
let public_items = public_items.unwrap_or(NodeSet::new());
|
||||||
let paths: HashMap<ast::DefId, (Vec<String>, ItemType)> =
|
let paths: HashMap<ast::DefId, (Vec<String>, ItemType)> =
|
||||||
|
@ -370,8 +377,8 @@ pub fn run(mut krate: clean::Crate,
|
||||||
// Freeze the cache now that the index has been built. Put an Arc into TLS
|
// Freeze the cache now that the index has been built. Put an Arc into TLS
|
||||||
// for future parallelization opportunities
|
// for future parallelization opportunities
|
||||||
let cache = Arc::new(cache);
|
let cache = Arc::new(cache);
|
||||||
cache_key.replace(Some(cache.clone()));
|
CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
|
||||||
current_location_key.replace(Some(Vec::new()));
|
CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
|
||||||
|
|
||||||
try!(write_shared(&cx, &krate, &*cache, index));
|
try!(write_shared(&cx, &krate, &*cache, index));
|
||||||
let krate = try!(render_sources(&mut cx, krate));
|
let krate = try!(render_sources(&mut cx, krate));
|
||||||
|
@ -1134,7 +1141,9 @@ impl Context {
|
||||||
info!("Rendering an item to {}", w.path().display());
|
info!("Rendering an item to {}", w.path().display());
|
||||||
// A little unfortunate that this is done like this, but it sure
|
// A little unfortunate that this is done like this, but it sure
|
||||||
// does make formatting *a lot* nicer.
|
// does make formatting *a lot* nicer.
|
||||||
current_location_key.replace(Some(cx.current.clone()));
|
CURRENT_LOCATION_KEY.with(|slot| {
|
||||||
|
*slot.borrow_mut() = cx.current.clone();
|
||||||
|
});
|
||||||
|
|
||||||
let mut title = cx.current.connect("::");
|
let mut title = cx.current.connect("::");
|
||||||
if pushname {
|
if pushname {
|
||||||
|
@ -1177,7 +1186,7 @@ impl Context {
|
||||||
&Item{ cx: cx, item: it }));
|
&Item{ cx: cx, item: it }));
|
||||||
} else {
|
} else {
|
||||||
let mut url = "../".repeat(cx.current.len());
|
let mut url = "../".repeat(cx.current.len());
|
||||||
match cache_key.get().unwrap().paths.get(&it.def_id) {
|
match cache().paths.get(&it.def_id) {
|
||||||
Some(&(ref names, _)) => {
|
Some(&(ref names, _)) => {
|
||||||
for name in names[..names.len() - 1].iter() {
|
for name in names[..names.len() - 1].iter() {
|
||||||
url.push_str(name.as_slice());
|
url.push_str(name.as_slice());
|
||||||
|
@ -1324,7 +1333,7 @@ impl<'a> Item<'a> {
|
||||||
// If we don't know where the external documentation for this crate is
|
// If we don't know where the external documentation for this crate is
|
||||||
// located, then we return `None`.
|
// located, then we return `None`.
|
||||||
} else {
|
} else {
|
||||||
let cache = cache_key.get().unwrap();
|
let cache = cache();
|
||||||
let path = &cache.external_paths[self.item.def_id];
|
let path = &cache.external_paths[self.item.def_id];
|
||||||
let root = match cache.extern_locations[self.item.def_id.krate] {
|
let root = match cache.extern_locations[self.item.def_id.krate] {
|
||||||
Remote(ref s) => s.to_string(),
|
Remote(ref s) => s.to_string(),
|
||||||
|
@ -1751,7 +1760,7 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
|
||||||
try!(write!(w, "</div>"));
|
try!(write!(w, "</div>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache = cache_key.get().unwrap();
|
let cache = cache();
|
||||||
try!(write!(w, "
|
try!(write!(w, "
|
||||||
<h2 id='implementors'>Implementors</h2>
|
<h2 id='implementors'>Implementors</h2>
|
||||||
<ul class='item-list' id='implementors-list'>
|
<ul class='item-list' id='implementors-list'>
|
||||||
|
@ -2013,7 +2022,7 @@ fn render_struct(w: &mut fmt::Formatter, it: &clean::Item,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_methods(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
|
fn render_methods(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
|
||||||
match cache_key.get().unwrap().impls.get(&it.def_id) {
|
match cache().impls.get(&it.def_id) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
let (non_trait, traits) = v.partitioned(|i| i.impl_.trait_.is_none());
|
let (non_trait, traits) = v.partitioned(|i| i.impl_.trait_.is_none());
|
||||||
if non_trait.len() > 0 {
|
if non_trait.len() > 0 {
|
||||||
|
@ -2101,7 +2110,7 @@ fn render_impl(w: &mut fmt::Formatter, i: &Impl) -> fmt::Result {
|
||||||
match i.impl_.trait_ {
|
match i.impl_.trait_ {
|
||||||
Some(clean::ResolvedPath { did, .. }) => {
|
Some(clean::ResolvedPath { did, .. }) => {
|
||||||
try!({
|
try!({
|
||||||
match cache_key.get().unwrap().traits.get(&did) {
|
match cache().traits.get(&did) {
|
||||||
Some(t) => try!(render_default_methods(w, t, &i.impl_)),
|
Some(t) => try!(render_default_methods(w, t, &i.impl_)),
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
@ -2220,3 +2229,7 @@ fn get_basic_keywords() -> &'static str {
|
||||||
fn make_item_keywords(it: &clean::Item) -> String {
|
fn make_item_keywords(it: &clean::Item) -> String {
|
||||||
format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap())
|
format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cache() -> Arc<Cache> {
|
||||||
|
CACHE_KEY.with(|c| c.borrow().clone())
|
||||||
|
}
|
||||||
|
|
|
@ -28,12 +28,14 @@ extern crate syntax;
|
||||||
extern crate "test" as testing;
|
extern crate "test" as testing;
|
||||||
#[phase(plugin, link)] extern crate log;
|
#[phase(plugin, link)] extern crate log;
|
||||||
|
|
||||||
use std::io;
|
use std::cell::RefCell;
|
||||||
use std::io::File;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::{Occupied, Vacant};
|
use std::collections::hash_map::{Occupied, Vacant};
|
||||||
use serialize::{json, Decodable, Encodable};
|
use std::io::File;
|
||||||
|
use std::io;
|
||||||
|
use std::rc::Rc;
|
||||||
use externalfiles::ExternalHtml;
|
use externalfiles::ExternalHtml;
|
||||||
|
use serialize::{json, Decodable, Encodable};
|
||||||
|
|
||||||
// reexported from `clean` so it can be easily updated with the mod itself
|
// reexported from `clean` so it can be easily updated with the mod itself
|
||||||
pub use clean::SCHEMA_VERSION;
|
pub use clean::SCHEMA_VERSION;
|
||||||
|
@ -84,7 +86,9 @@ static DEFAULT_PASSES: &'static [&'static str] = &[
|
||||||
"unindent-comments",
|
"unindent-comments",
|
||||||
];
|
];
|
||||||
|
|
||||||
local_data_key!(pub analysiskey: core::CrateAnalysis)
|
thread_local!(pub static ANALYSISKEY: Rc<RefCell<Option<core::CrateAnalysis>>> = {
|
||||||
|
Rc::new(RefCell::new(None))
|
||||||
|
})
|
||||||
|
|
||||||
struct Output {
|
struct Output {
|
||||||
krate: clean::Crate,
|
krate: clean::Crate,
|
||||||
|
@ -338,7 +342,10 @@ fn rust_input(cratefile: &str, externs: core::Externs, matches: &getopts::Matche
|
||||||
core::run_core(libs, cfgs, externs, &cr, triple)
|
core::run_core(libs, cfgs, externs, &cr, triple)
|
||||||
}).map_err(|_| "rustc failed").unwrap();
|
}).map_err(|_| "rustc failed").unwrap();
|
||||||
info!("finished with rustc");
|
info!("finished with rustc");
|
||||||
analysiskey.replace(Some(analysis));
|
let mut analysis = Some(analysis);
|
||||||
|
ANALYSISKEY.with(|s| {
|
||||||
|
*s.borrow_mut() = analysis.take();
|
||||||
|
});
|
||||||
|
|
||||||
match matches.opt_str("crate-name") {
|
match matches.opt_str("crate-name") {
|
||||||
Some(name) => krate.name = name,
|
Some(name) => krate.name = name,
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub fn render(input: &str, mut output: Path, matches: &getopts::Matches,
|
||||||
let input_str = load_or_return!(input, 1, 2);
|
let input_str = load_or_return!(input, 1, 2);
|
||||||
let playground = matches.opt_str("markdown-playground-url");
|
let playground = matches.opt_str("markdown-playground-url");
|
||||||
if playground.is_some() {
|
if playground.is_some() {
|
||||||
markdown::playground_krate.replace(Some(None));
|
markdown::PLAYGROUND_KRATE.with(|s| { *s.borrow_mut() = None; });
|
||||||
}
|
}
|
||||||
let playground = playground.unwrap_or("".to_string());
|
let playground = playground.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,9 @@ pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
|
||||||
pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
|
pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
|
||||||
// This stripper collects all *retained* nodes.
|
// This stripper collects all *retained* nodes.
|
||||||
let mut retained = HashSet::new();
|
let mut retained = HashSet::new();
|
||||||
let analysis = super::analysiskey.get().unwrap();
|
let analysis = super::ANALYSISKEY.with(|a| a.clone());
|
||||||
|
let analysis = analysis.borrow();
|
||||||
|
let analysis = analysis.as_ref().unwrap();
|
||||||
let exported_items = analysis.exported_items.clone();
|
let exported_items = analysis.exported_items.clone();
|
||||||
|
|
||||||
// strip all private items
|
// strip all private items
|
||||||
|
|
|
@ -23,7 +23,7 @@ use clean::{Crate, Item, ModuleItem, Module, EnumItem, Enum};
|
||||||
use clean::{ImplItem, Impl, Trait, TraitItem, TraitMethod, ProvidedMethod, RequiredMethod};
|
use clean::{ImplItem, Impl, Trait, TraitItem, TraitMethod, ProvidedMethod, RequiredMethod};
|
||||||
use clean::{TypeTraitItem, ViewItemItem, PrimitiveItem, Stability};
|
use clean::{TypeTraitItem, ViewItemItem, PrimitiveItem, Stability};
|
||||||
|
|
||||||
use html::render::cache_key;
|
use html::render::cache;
|
||||||
|
|
||||||
#[deriving(Zero, Encodable, Decodable, PartialEq, Eq)]
|
#[deriving(Zero, Encodable, Decodable, PartialEq, Eq)]
|
||||||
/// The counts for each stability level.
|
/// The counts for each stability level.
|
||||||
|
@ -116,7 +116,7 @@ fn count_stability(stab: Option<&Stability>) -> Counts {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn summarize_methods(item: &Item) -> Counts {
|
fn summarize_methods(item: &Item) -> Counts {
|
||||||
match cache_key.get().unwrap().impls.get(&item.def_id) {
|
match cache().impls.get(&item.def_id) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
v.iter().map(|i| {
|
v.iter().map(|i| {
|
||||||
let count = count_stability(i.stability.as_ref());
|
let count = count_stability(i.stability.as_ref());
|
||||||
|
|
|
@ -52,7 +52,6 @@ pub mod bookkeeping;
|
||||||
pub mod c_str;
|
pub mod c_str;
|
||||||
pub mod exclusive;
|
pub mod exclusive;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
pub mod local_data;
|
|
||||||
pub mod mutex;
|
pub mod mutex;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|
|
@ -1,696 +0,0 @@
|
||||||
// Copyright 2012-2013 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.
|
|
||||||
|
|
||||||
/*!
|
|
||||||
|
|
||||||
Task local data management
|
|
||||||
|
|
||||||
Allows storing arbitrary types inside task-local-data (TLD), to be accessed
|
|
||||||
anywhere within a task, keyed by a global pointer parameterized over the type of
|
|
||||||
the TLD slot. Useful for dynamic variables, singletons, and interfacing with
|
|
||||||
foreign code with bad callback interfaces.
|
|
||||||
|
|
||||||
To declare a new key for storing local data of a particular type, use the
|
|
||||||
`local_data_key!` macro. This macro will expand to a `static` item appropriately
|
|
||||||
named and annotated. This name is then passed to the functions in this module to
|
|
||||||
modify/read the slot specified by the key.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
local_data_key!(key_int: int)
|
|
||||||
local_data_key!(key_vector: Vec<int>)
|
|
||||||
|
|
||||||
key_int.replace(Some(3));
|
|
||||||
assert_eq!(*key_int.get().unwrap(), 3);
|
|
||||||
|
|
||||||
key_vector.replace(Some(vec![4]));
|
|
||||||
assert_eq!(*key_vector.get().unwrap(), vec![4]);
|
|
||||||
```
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation
|
|
||||||
// magic.
|
|
||||||
|
|
||||||
pub use self::KeyValue::*;
|
|
||||||
|
|
||||||
use core::prelude::*;
|
|
||||||
|
|
||||||
use alloc::heap;
|
|
||||||
use collections::TreeMap;
|
|
||||||
use core::cmp;
|
|
||||||
use core::kinds::marker;
|
|
||||||
use core::mem;
|
|
||||||
use core::ptr;
|
|
||||||
use core::fmt;
|
|
||||||
use core::cell::UnsafeCell;
|
|
||||||
|
|
||||||
use local::Local;
|
|
||||||
use task::{Task, LocalStorage};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indexes a task-local data slot. This pointer is used for comparison to
|
|
||||||
* differentiate keys from one another. The actual type `T` is not used anywhere
|
|
||||||
* as a member of this type, except that it is parameterized with it to define
|
|
||||||
* the type of each key's value.
|
|
||||||
*
|
|
||||||
* The value of each Key is of the singleton enum KeyValue. These also have the
|
|
||||||
* same name as `Key` and their purpose is to take up space in the programs data
|
|
||||||
* sections to ensure that each value of the `Key` type points to a unique
|
|
||||||
* location.
|
|
||||||
*/
|
|
||||||
pub type Key<T> = &'static KeyValue<T>;
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum KeyValue<T> { KeyValueKey }
|
|
||||||
|
|
||||||
// The task-local-map stores all TLD information for the currently running
|
|
||||||
// task. It is stored as an owned pointer into the runtime, and it's only
|
|
||||||
// allocated when TLD is used for the first time.
|
|
||||||
//
|
|
||||||
// TLD values are boxed up, with a loan count stored in the box. The box is
|
|
||||||
// necessary given how TLD maps are constructed, but theoretically in the
|
|
||||||
// future this could be rewritten to statically construct TLD offsets at
|
|
||||||
// compile-time to get O(1) lookup. At that time, the box can be removed.
|
|
||||||
//
|
|
||||||
// A very common usage pattern for TLD is to use replace(None) to extract a
|
|
||||||
// value from TLD, work with it, and then store it (or a derived/new value)
|
|
||||||
// back with replace(v). We take special care to reuse the allocation in this
|
|
||||||
// case for performance reasons.
|
|
||||||
//
|
|
||||||
// However, that does mean that if a value is replaced with None, the
|
|
||||||
// allocation will stay alive and the entry will stay in the TLD map until the
|
|
||||||
// task deallocates. This makes the assumption that every key inserted into a
|
|
||||||
// given task's TLD is going to be present for a majority of the rest of the
|
|
||||||
// task's lifetime, but that's a fairly safe assumption, and there's very
|
|
||||||
// little downside as long as it holds true for most keys.
|
|
||||||
//
|
|
||||||
// The Map type must be public in order to allow rustrt to see it.
|
|
||||||
//
|
|
||||||
// We'd like to use HashMap here, but it uses TLD in its construction (it uses
|
|
||||||
// the task-local rng). We could try to provide our own source of randomness,
|
|
||||||
// except it also lives in libstd (which is a client of us) so we can't even
|
|
||||||
// reference it. Instead, use TreeMap, which provides reasonable performance.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub type Map = TreeMap<uint, TLDValue>;
|
|
||||||
#[unsafe_no_drop_flag]
|
|
||||||
struct TLDValue {
|
|
||||||
// box_ptr is a pointer to TLDValueBox<T>. It can never be null.
|
|
||||||
box_ptr: *mut (),
|
|
||||||
// drop_fn is the function that knows how to drop the box_ptr.
|
|
||||||
drop_fn: unsafe fn(p: *mut ())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TLDValueBox<T> {
|
|
||||||
// value is only initialized when refcount >= 1.
|
|
||||||
value: T,
|
|
||||||
// refcount of 0 means uninitialized value, 1 means initialized, 2+ means
|
|
||||||
// borrowed.
|
|
||||||
// NB: we use UnsafeCell instead of Cell because Ref should be allowed to
|
|
||||||
// be Sync. The only mutation occurs when a Ref is created or destroyed,
|
|
||||||
// so there's no issue with &Ref being thread-safe.
|
|
||||||
refcount: UnsafeCell<uint>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the map from the runtime. Lazily initialises if not done so already.
|
|
||||||
unsafe fn get_local_map<'a>() -> Option<&'a mut Map> {
|
|
||||||
if !Local::exists(None::<Task>) { return None }
|
|
||||||
|
|
||||||
let task: *mut Task = Local::unsafe_borrow();
|
|
||||||
match &mut (*task).storage {
|
|
||||||
// If the at_exit function is already set, then we just need to take
|
|
||||||
// a loan out on the TLD map stored inside
|
|
||||||
&LocalStorage(Some(ref mut map_ptr)) => {
|
|
||||||
return Some(map_ptr);
|
|
||||||
}
|
|
||||||
// If this is the first time we've accessed TLD, perform similar
|
|
||||||
// actions to the oldsched way of doing things.
|
|
||||||
&LocalStorage(ref mut slot) => {
|
|
||||||
*slot = Some(TreeMap::new());
|
|
||||||
match *slot {
|
|
||||||
Some(ref mut map_ptr) => { return Some(map_ptr) }
|
|
||||||
None => panic!("unreachable code"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A RAII immutable reference to a task-local value.
|
|
||||||
///
|
|
||||||
/// The task-local data can be accessed through this value, and when this
|
|
||||||
/// structure is dropped it will return the borrow on the data.
|
|
||||||
pub struct Ref<T:'static> {
|
|
||||||
// FIXME #12808: strange names to try to avoid interfering with
|
|
||||||
// field accesses of the contained type via Deref
|
|
||||||
_inner: &'static TLDValueBox<T>,
|
|
||||||
_marker: marker::NoSend
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_to_key_value<T: 'static>(key: Key<T>) -> uint {
|
|
||||||
key as *const _ as uint
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> KeyValue<T> {
|
|
||||||
/// Replaces a value in task local data.
|
|
||||||
///
|
|
||||||
/// If this key is already present in TLD, then the previous value is
|
|
||||||
/// replaced with the provided data, and then returned.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This function will panic if the key is present in TLD and currently on
|
|
||||||
/// loan with the `get` method.
|
|
||||||
///
|
|
||||||
/// It will also panic if there is no local task (because the current thread
|
|
||||||
/// is not owned by the runtime).
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// local_data_key!(foo: int)
|
|
||||||
///
|
|
||||||
/// assert_eq!(foo.replace(Some(10)), None);
|
|
||||||
/// assert_eq!(foo.replace(Some(4)), Some(10));
|
|
||||||
/// assert_eq!(foo.replace(None), Some(4));
|
|
||||||
/// ```
|
|
||||||
pub fn replace(&'static self, data: Option<T>) -> Option<T> {
|
|
||||||
let map = match unsafe { get_local_map() } {
|
|
||||||
Some(map) => map,
|
|
||||||
None => panic!("must have a local task to insert into TLD"),
|
|
||||||
};
|
|
||||||
let keyval = key_to_key_value(self);
|
|
||||||
|
|
||||||
// The following match takes a mutable borrow on the map. In order to insert
|
|
||||||
// our data if the key isn't present, we need to let the match end first.
|
|
||||||
let data = match (map.get_mut(&keyval), data) {
|
|
||||||
(None, Some(data)) => {
|
|
||||||
// The key doesn't exist and we need to insert it. To make borrowck
|
|
||||||
// happy, return it up a scope and insert it there.
|
|
||||||
data
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
// The key doesn't exist and we're trying to replace it with nothing.
|
|
||||||
// Do nothing.
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
(Some(slot), data) => {
|
|
||||||
// We have a slot with a box.
|
|
||||||
let value_box = slot.box_ptr as *mut TLDValueBox<T>;
|
|
||||||
let refcount = unsafe { *(*value_box).refcount.get() };
|
|
||||||
return match (refcount, data) {
|
|
||||||
(0, None) => {
|
|
||||||
// The current value is uninitialized and we have no new value.
|
|
||||||
// Do nothing.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
(0, Some(new_value)) => {
|
|
||||||
// The current value is uninitialized and we're storing a new value.
|
|
||||||
unsafe {
|
|
||||||
ptr::write(&mut (*value_box).value, new_value);
|
|
||||||
*(*value_box).refcount.get() = 1;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(1, None) => {
|
|
||||||
// We have an initialized value and we're removing it.
|
|
||||||
unsafe {
|
|
||||||
let ret = ptr::read(&(*value_box).value);
|
|
||||||
*(*value_box).refcount.get() = 0;
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(1, Some(new_value)) => {
|
|
||||||
// We have an initialized value and we're replacing it.
|
|
||||||
let value_ref = unsafe { &mut (*value_box).value };
|
|
||||||
let ret = mem::replace(value_ref, new_value);
|
|
||||||
// Refcount is already 1, leave it as that.
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Refcount is 2+, which means we have a live borrow.
|
|
||||||
panic!("TLD value cannot be replaced because it is already borrowed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// If we've reached this point, we need to insert into the map.
|
|
||||||
map.insert(keyval, TLDValue::new(data));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrows a value from TLD.
|
|
||||||
///
|
|
||||||
/// If `None` is returned, then this key is not present in TLD. If `Some`
|
|
||||||
/// is returned, then the returned data is a smart pointer representing a
|
|
||||||
/// new loan on this TLD key. While on loan, this key cannot be altered via
|
|
||||||
/// the `replace` method.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// local_data_key!(key: int)
|
|
||||||
///
|
|
||||||
/// assert!(key.get().is_none());
|
|
||||||
///
|
|
||||||
/// key.replace(Some(3));
|
|
||||||
/// assert_eq!(*key.get().unwrap(), 3);
|
|
||||||
/// ```
|
|
||||||
pub fn get(&'static self) -> Option<Ref<T>> {
|
|
||||||
let map = match unsafe { get_local_map() } {
|
|
||||||
Some(map) => map,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
let keyval = key_to_key_value(self);
|
|
||||||
|
|
||||||
match map.get(&keyval) {
|
|
||||||
Some(slot) => {
|
|
||||||
let value_box = slot.box_ptr as *mut TLDValueBox<T>;
|
|
||||||
if unsafe { *(*value_box).refcount.get() } >= 1 {
|
|
||||||
unsafe {
|
|
||||||
*(*value_box).refcount.get() += 1;
|
|
||||||
Some(Ref {
|
|
||||||
_inner: &*value_box,
|
|
||||||
_marker: marker::NoSend
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's not clear if this is the right design for a public API, or if
|
|
||||||
// there's even a need for this as a public API, but our benchmarks need
|
|
||||||
// this to ensure consistent behavior on each run.
|
|
||||||
#[cfg(test)]
|
|
||||||
fn clear(&'static self) {
|
|
||||||
let map = match unsafe { get_local_map() } {
|
|
||||||
Some(map) => map,
|
|
||||||
None => return
|
|
||||||
};
|
|
||||||
let keyval = key_to_key_value(self);
|
|
||||||
self.replace(None); // ensure we have no outstanding borrows
|
|
||||||
map.remove(&keyval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Deref<T> for Ref<T> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn deref<'a>(&'a self) -> &'a T {
|
|
||||||
&self._inner.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static + fmt::Show> fmt::Show for Ref<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
(**self).fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: cmp::PartialEq + 'static> cmp::PartialEq for Ref<T> {
|
|
||||||
fn eq(&self, other: &Ref<T>) -> bool {
|
|
||||||
(**self).eq(&**other)
|
|
||||||
}
|
|
||||||
fn ne(&self, other: &Ref<T>) -> bool {
|
|
||||||
(**self).ne(&**other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: cmp::Eq + 'static> cmp::Eq for Ref<T> {}
|
|
||||||
|
|
||||||
impl<T: cmp::PartialOrd + 'static> cmp::PartialOrd for Ref<T> {
|
|
||||||
fn partial_cmp(&self, other: &Ref<T>) -> Option<cmp::Ordering> {
|
|
||||||
(**self).partial_cmp(&**other)
|
|
||||||
}
|
|
||||||
fn lt(&self, other: &Ref<T>) -> bool { (**self).lt(&**other) }
|
|
||||||
fn le(&self, other: &Ref<T>) -> bool { (**self).le(&**other) }
|
|
||||||
fn gt(&self, other: &Ref<T>) -> bool { (**self).gt(&**other) }
|
|
||||||
fn ge(&self, other: &Ref<T>) -> bool { (**self).ge(&**other) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: cmp::Ord + 'static> cmp::Ord for Ref<T> {
|
|
||||||
fn cmp(&self, other: &Ref<T>) -> cmp::Ordering {
|
|
||||||
(**self).cmp(&**other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe_destructor]
|
|
||||||
impl<T: 'static> Drop for Ref<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
*self._inner.refcount.get() -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TLDValue {
|
|
||||||
fn new<T>(value: T) -> TLDValue {
|
|
||||||
let box_ptr = unsafe {
|
|
||||||
let allocation = heap::allocate(mem::size_of::<TLDValueBox<T>>(),
|
|
||||||
mem::min_align_of::<TLDValueBox<T>>());
|
|
||||||
if allocation.is_null() { ::alloc::oom() }
|
|
||||||
let value_box = allocation as *mut TLDValueBox<T>;
|
|
||||||
ptr::write(value_box, TLDValueBox {
|
|
||||||
value: value,
|
|
||||||
refcount: UnsafeCell::new(1)
|
|
||||||
});
|
|
||||||
value_box as *mut ()
|
|
||||||
};
|
|
||||||
// Destruction of TLDValue needs to know how to properly deallocate the TLDValueBox,
|
|
||||||
// so we need our own custom destructor function.
|
|
||||||
unsafe fn d<T>(p: *mut ()) {
|
|
||||||
let value_box = p as *mut TLDValueBox<T>;
|
|
||||||
debug_assert!(*(*value_box).refcount.get() < 2, "TLDValue destructed while borrowed");
|
|
||||||
// use a RAII type here to ensure we always deallocate even if we panic while
|
|
||||||
// running the destructor for the value.
|
|
||||||
struct Guard<T> {
|
|
||||||
p: *mut TLDValueBox<T>
|
|
||||||
}
|
|
||||||
#[unsafe_destructor]
|
|
||||||
impl<T> Drop for Guard<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let size = mem::size_of::<TLDValueBox<T>>();
|
|
||||||
let align = mem::align_of::<TLDValueBox<T>>();
|
|
||||||
unsafe { heap::deallocate(self.p as *mut u8, size, align); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _guard = Guard::<T> { p: value_box };
|
|
||||||
if *(*value_box).refcount.get() != 0 {
|
|
||||||
// the contained value is valid; drop it
|
|
||||||
ptr::read(&(*value_box).value);
|
|
||||||
}
|
|
||||||
// the box will be deallocated by the guard
|
|
||||||
}
|
|
||||||
TLDValue {
|
|
||||||
box_ptr: box_ptr,
|
|
||||||
drop_fn: d::<T>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Drop for TLDValue {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// box_ptr should always be non-null. Check it anyway just to be thorough
|
|
||||||
if !self.box_ptr.is_null() {
|
|
||||||
unsafe { (self.drop_fn)(self.box_ptr) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use std::prelude::*;
|
|
||||||
use super::*;
|
|
||||||
use std::task;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_multitask() {
|
|
||||||
static MY_KEY: Key<String> = &KeyValueKey;
|
|
||||||
MY_KEY.replace(Some("parent data".to_string()));
|
|
||||||
task::spawn(proc() {
|
|
||||||
// TLD shouldn't carry over.
|
|
||||||
assert!(MY_KEY.get().is_none());
|
|
||||||
MY_KEY.replace(Some("child data".to_string()));
|
|
||||||
assert!(MY_KEY.get().as_ref().unwrap().as_slice() == "child data");
|
|
||||||
// should be cleaned up for us
|
|
||||||
});
|
|
||||||
|
|
||||||
// Must work multiple times
|
|
||||||
assert!(MY_KEY.get().unwrap().as_slice() == "parent data");
|
|
||||||
assert!(MY_KEY.get().unwrap().as_slice() == "parent data");
|
|
||||||
assert!(MY_KEY.get().unwrap().as_slice() == "parent data");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_overwrite() {
|
|
||||||
static MY_KEY: Key<String> = &KeyValueKey;
|
|
||||||
MY_KEY.replace(Some("first data".to_string()));
|
|
||||||
MY_KEY.replace(Some("next data".to_string())); // Shouldn't leak.
|
|
||||||
assert!(MY_KEY.get().unwrap().as_slice() == "next data");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_pop() {
|
|
||||||
static MY_KEY: Key<String> = &KeyValueKey;
|
|
||||||
MY_KEY.replace(Some("weasel".to_string()));
|
|
||||||
assert!(MY_KEY.replace(None).unwrap() == "weasel".to_string());
|
|
||||||
// Pop must remove the data from the map.
|
|
||||||
assert!(MY_KEY.replace(None).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_crust_automorestack_memorial_bug() {
|
|
||||||
// This might result in a stack-canary clobber if the runtime fails to
|
|
||||||
// set sp_limit to 0 when calling the cleanup extern - it might
|
|
||||||
// automatically jump over to the rust stack, which causes next_c_sp
|
|
||||||
// to get recorded as something within a rust stack segment. Then a
|
|
||||||
// subsequent upcall (esp. for logging, think vsnprintf) would run on
|
|
||||||
// a stack smaller than 1 MB.
|
|
||||||
static MY_KEY: Key<String> = &KeyValueKey;
|
|
||||||
task::spawn(proc() {
|
|
||||||
MY_KEY.replace(Some("hax".to_string()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_multiple_types() {
|
|
||||||
static STR_KEY: Key<String> = &KeyValueKey;
|
|
||||||
static BOX_KEY: Key<Box<int>> = &KeyValueKey;
|
|
||||||
static INT_KEY: Key<int> = &KeyValueKey;
|
|
||||||
task::spawn(proc() {
|
|
||||||
STR_KEY.replace(Some("string data".to_string()));
|
|
||||||
BOX_KEY.replace(Some(box 0));
|
|
||||||
INT_KEY.replace(Some(42));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tls_overwrite_multiple_types() {
|
|
||||||
static STR_KEY: Key<String> = &KeyValueKey;
|
|
||||||
static BOX_KEY: Key<Box<int>> = &KeyValueKey;
|
|
||||||
static INT_KEY: Key<int> = &KeyValueKey;
|
|
||||||
task::spawn(proc() {
|
|
||||||
STR_KEY.replace(Some("string data".to_string()));
|
|
||||||
STR_KEY.replace(Some("string data 2".to_string()));
|
|
||||||
BOX_KEY.replace(Some(box 0));
|
|
||||||
BOX_KEY.replace(Some(box 1));
|
|
||||||
INT_KEY.replace(Some(42));
|
|
||||||
// This could cause a segfault if overwriting-destruction is done
|
|
||||||
// with the crazy polymorphic transmute rather than the provided
|
|
||||||
// finaliser.
|
|
||||||
INT_KEY.replace(Some(31337));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_fail]
|
|
||||||
fn test_tls_cleanup_on_panic() {
|
|
||||||
static STR_KEY: Key<String> = &KeyValueKey;
|
|
||||||
static BOX_KEY: Key<Box<int>> = &KeyValueKey;
|
|
||||||
static INT_KEY: Key<int> = &KeyValueKey;
|
|
||||||
STR_KEY.replace(Some("parent data".to_string()));
|
|
||||||
BOX_KEY.replace(Some(box 0));
|
|
||||||
task::spawn(proc() {
|
|
||||||
STR_KEY.replace(Some("string data".to_string()));
|
|
||||||
BOX_KEY.replace(Some(box 2));
|
|
||||||
INT_KEY.replace(Some(42));
|
|
||||||
panic!();
|
|
||||||
});
|
|
||||||
// Not quite nondeterministic.
|
|
||||||
INT_KEY.replace(Some(31337));
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cleanup_drops_values() {
|
|
||||||
let (tx, rx) = channel::<()>();
|
|
||||||
struct Dropper {
|
|
||||||
tx: Sender<()>
|
|
||||||
};
|
|
||||||
impl Drop for Dropper {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.tx.send(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static KEY: Key<Dropper> = &KeyValueKey;
|
|
||||||
let _ = task::try(proc() {
|
|
||||||
KEY.replace(Some(Dropper{ tx: tx }));
|
|
||||||
});
|
|
||||||
// At this point the task has been cleaned up and the TLD dropped.
|
|
||||||
// If the channel doesn't have a value now, then the Sender was leaked.
|
|
||||||
assert_eq!(rx.try_recv(), Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_static_pointer() {
|
|
||||||
static KEY: Key<&'static int> = &KeyValueKey;
|
|
||||||
static VALUE: int = 0;
|
|
||||||
KEY.replace(Some(&VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_owned() {
|
|
||||||
static KEY: Key<Box<int>> = &KeyValueKey;
|
|
||||||
KEY.replace(Some(box 1));
|
|
||||||
|
|
||||||
{
|
|
||||||
let k1 = KEY.get().unwrap();
|
|
||||||
let k2 = KEY.get().unwrap();
|
|
||||||
let k3 = KEY.get().unwrap();
|
|
||||||
assert_eq!(**k1, 1);
|
|
||||||
assert_eq!(**k2, 1);
|
|
||||||
assert_eq!(**k3, 1);
|
|
||||||
}
|
|
||||||
KEY.replace(Some(box 2));
|
|
||||||
assert_eq!(**KEY.get().unwrap(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_same_key_type() {
|
|
||||||
static KEY1: Key<int> = &KeyValueKey;
|
|
||||||
static KEY2: Key<int> = &KeyValueKey;
|
|
||||||
static KEY3: Key<int> = &KeyValueKey;
|
|
||||||
static KEY4: Key<int> = &KeyValueKey;
|
|
||||||
static KEY5: Key<int> = &KeyValueKey;
|
|
||||||
KEY1.replace(Some(1));
|
|
||||||
KEY2.replace(Some(2));
|
|
||||||
KEY3.replace(Some(3));
|
|
||||||
KEY4.replace(Some(4));
|
|
||||||
KEY5.replace(Some(5));
|
|
||||||
|
|
||||||
assert_eq!(*KEY1.get().unwrap(), 1);
|
|
||||||
assert_eq!(*KEY2.get().unwrap(), 2);
|
|
||||||
assert_eq!(*KEY3.get().unwrap(), 3);
|
|
||||||
assert_eq!(*KEY4.get().unwrap(), 4);
|
|
||||||
assert_eq!(*KEY5.get().unwrap(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_fail]
|
|
||||||
fn test_nested_get_set1() {
|
|
||||||
static KEY: Key<int> = &KeyValueKey;
|
|
||||||
assert_eq!(KEY.replace(Some(4)), None);
|
|
||||||
|
|
||||||
let _k = KEY.get();
|
|
||||||
KEY.replace(Some(4));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearKey is a RAII class that ensures the keys are cleared from the map.
|
|
||||||
// This is so repeated runs of a benchmark don't bloat the map with extra
|
|
||||||
// keys and distort the measurements.
|
|
||||||
// It's not used on the tests because the tests run in separate tasks.
|
|
||||||
struct ClearKey<T>(Key<T>);
|
|
||||||
#[unsafe_destructor]
|
|
||||||
impl<T: 'static> Drop for ClearKey<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let ClearKey(ref key) = *self;
|
|
||||||
key.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_replace_none(b: &mut test::Bencher) {
|
|
||||||
static KEY: Key<uint> = &KeyValueKey;
|
|
||||||
let _clear = ClearKey(KEY);
|
|
||||||
KEY.replace(None);
|
|
||||||
b.iter(|| {
|
|
||||||
KEY.replace(None)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_replace_some(b: &mut test::Bencher) {
|
|
||||||
static KEY: Key<uint> = &KeyValueKey;
|
|
||||||
let _clear = ClearKey(KEY);
|
|
||||||
KEY.replace(Some(1u));
|
|
||||||
b.iter(|| {
|
|
||||||
KEY.replace(Some(2))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_replace_none_some(b: &mut test::Bencher) {
|
|
||||||
static KEY: Key<uint> = &KeyValueKey;
|
|
||||||
let _clear = ClearKey(KEY);
|
|
||||||
KEY.replace(Some(0u));
|
|
||||||
b.iter(|| {
|
|
||||||
let old = KEY.replace(None).unwrap();
|
|
||||||
let new = old + 1;
|
|
||||||
KEY.replace(Some(new))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_100_keys_replace_last(b: &mut test::Bencher) {
|
|
||||||
static KEYS: [KeyValue<uint>, ..100] = [KeyValueKey, ..100];
|
|
||||||
let _clear = KEYS.iter().map(ClearKey).collect::<Vec<ClearKey<uint>>>();
|
|
||||||
for (i, key) in KEYS.iter().enumerate() {
|
|
||||||
key.replace(Some(i));
|
|
||||||
}
|
|
||||||
b.iter(|| {
|
|
||||||
let key: Key<uint> = &KEYS[99];
|
|
||||||
key.replace(Some(42))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_1000_keys_replace_last(b: &mut test::Bencher) {
|
|
||||||
static KEYS: [KeyValue<uint>, ..1000] = [KeyValueKey, ..1000];
|
|
||||||
let _clear = KEYS.iter().map(ClearKey).collect::<Vec<ClearKey<uint>>>();
|
|
||||||
for (i, key) in KEYS.iter().enumerate() {
|
|
||||||
key.replace(Some(i));
|
|
||||||
}
|
|
||||||
b.iter(|| {
|
|
||||||
let key: Key<uint> = &KEYS[999];
|
|
||||||
key.replace(Some(42))
|
|
||||||
});
|
|
||||||
for key in KEYS.iter() { key.clear(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_get(b: &mut test::Bencher) {
|
|
||||||
static KEY: Key<uint> = &KeyValueKey;
|
|
||||||
let _clear = ClearKey(KEY);
|
|
||||||
KEY.replace(Some(42));
|
|
||||||
b.iter(|| {
|
|
||||||
KEY.get()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_100_keys_get_last(b: &mut test::Bencher) {
|
|
||||||
static KEYS: [KeyValue<uint>, ..100] = [KeyValueKey, ..100];
|
|
||||||
let _clear = KEYS.iter().map(ClearKey).collect::<Vec<ClearKey<uint>>>();
|
|
||||||
for (i, key) in KEYS.iter().enumerate() {
|
|
||||||
key.replace(Some(i));
|
|
||||||
}
|
|
||||||
b.iter(|| {
|
|
||||||
let key: Key<uint> = &KEYS[99];
|
|
||||||
key.get()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_1000_keys_get_last(b: &mut test::Bencher) {
|
|
||||||
static KEYS: [KeyValue<uint>, ..1000] = [KeyValueKey, ..1000];
|
|
||||||
let _clear = KEYS.iter().map(ClearKey).collect::<Vec<ClearKey<uint>>>();
|
|
||||||
for (i, key) in KEYS.iter().enumerate() {
|
|
||||||
key.replace(Some(i));
|
|
||||||
}
|
|
||||||
b.iter(|| {
|
|
||||||
let key: Key<uint> = &KEYS[999];
|
|
||||||
key.get()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,6 @@ use core::prelude::{drop};
|
||||||
|
|
||||||
use bookkeeping;
|
use bookkeeping;
|
||||||
use mutex::NativeMutex;
|
use mutex::NativeMutex;
|
||||||
use local_data;
|
|
||||||
use local::Local;
|
use local::Local;
|
||||||
use thread::{mod, Thread};
|
use thread::{mod, Thread};
|
||||||
use stack;
|
use stack;
|
||||||
|
@ -40,7 +39,6 @@ use collections::str::SendStr;
|
||||||
/// This structure is currently undergoing major changes, and is
|
/// This structure is currently undergoing major changes, and is
|
||||||
/// likely to be move/be merged with a `Thread` structure.
|
/// likely to be move/be merged with a `Thread` structure.
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub storage: LocalStorage,
|
|
||||||
pub unwinder: Unwinder,
|
pub unwinder: Unwinder,
|
||||||
pub death: Death,
|
pub death: Death,
|
||||||
pub name: Option<SendStr>,
|
pub name: Option<SendStr>,
|
||||||
|
@ -83,8 +81,6 @@ pub struct TaskOpts {
|
||||||
/// children tasks complete, recommend using a result future.
|
/// children tasks complete, recommend using a result future.
|
||||||
pub type Result = ::core::result::Result<(), Box<Any + Send>>;
|
pub type Result = ::core::result::Result<(), Box<Any + Send>>;
|
||||||
|
|
||||||
pub struct LocalStorage(pub Option<local_data::Map>);
|
|
||||||
|
|
||||||
/// A handle to a blocked task. Usually this means having the Box<Task>
|
/// A handle to a blocked task. Usually this means having the Box<Task>
|
||||||
/// pointer by ownership, but if the task is killable, a killer can steal it
|
/// pointer by ownership, but if the task is killable, a killer can steal it
|
||||||
/// at any time.
|
/// at any time.
|
||||||
|
@ -107,7 +103,6 @@ impl Task {
|
||||||
/// Creates a new uninitialized task.
|
/// Creates a new uninitialized task.
|
||||||
pub fn new(stack_bounds: Option<(uint, uint)>, stack_guard: Option<uint>) -> Task {
|
pub fn new(stack_bounds: Option<(uint, uint)>, stack_guard: Option<uint>) -> Task {
|
||||||
Task {
|
Task {
|
||||||
storage: LocalStorage(None),
|
|
||||||
unwinder: Unwinder::new(),
|
unwinder: Unwinder::new(),
|
||||||
death: Death::new(),
|
death: Death::new(),
|
||||||
state: New,
|
state: New,
|
||||||
|
@ -230,54 +225,11 @@ impl Task {
|
||||||
/// This function consumes ownership of the task, deallocating it once it's
|
/// This function consumes ownership of the task, deallocating it once it's
|
||||||
/// done being processed. It is assumed that TLD and the local heap have
|
/// done being processed. It is assumed that TLD and the local heap have
|
||||||
/// already been destroyed and/or annihilated.
|
/// already been destroyed and/or annihilated.
|
||||||
fn cleanup(self: Box<Task>, result: Result) -> Box<Task> {
|
fn cleanup(mut self: Box<Task>, result: Result) -> Box<Task> {
|
||||||
// The first thing to do when cleaning up is to deallocate our local
|
|
||||||
// resources, such as TLD.
|
|
||||||
//
|
|
||||||
// FIXME: there are a number of problems with this code
|
|
||||||
//
|
|
||||||
// 1. If any TLD object fails destruction, then all of TLD will leak.
|
|
||||||
// This appears to be a consequence of #14875.
|
|
||||||
//
|
|
||||||
// 2. Setting a TLD key while destroying TLD will abort the runtime #14807.
|
|
||||||
//
|
|
||||||
// 3. The order of destruction of TLD matters, but either way is
|
|
||||||
// susceptible to leaks (see 2) #8302.
|
|
||||||
//
|
|
||||||
// That being said, there are a few upshots to this code
|
|
||||||
//
|
|
||||||
// 1. If TLD destruction fails, heap destruction will be attempted.
|
|
||||||
// There is a test for this at fail-during-tld-destroy.rs.
|
|
||||||
//
|
|
||||||
// 2. One failure in destruction is tolerable, so long as the task
|
|
||||||
// didn't originally panic while it was running.
|
|
||||||
//
|
|
||||||
// And with all that in mind, we attempt to clean things up!
|
|
||||||
let mut task = self.run(|| {
|
|
||||||
let mut task = Local::borrow(None::<Task>);
|
|
||||||
let tld = {
|
|
||||||
let &LocalStorage(ref mut optmap) = &mut task.storage;
|
|
||||||
optmap.take()
|
|
||||||
};
|
|
||||||
drop(task);
|
|
||||||
|
|
||||||
// First, destroy task-local storage. This may run user dtors.
|
|
||||||
drop(tld);
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the above `run` block panicked, then it must be the case that the
|
|
||||||
// task had previously succeeded. This also means that the code below
|
|
||||||
// was recursively run via the `run` method invoking this method. In
|
|
||||||
// this case, we just make sure the world is as we thought, and return.
|
|
||||||
if task.is_destroyed() {
|
|
||||||
rtassert!(result.is_ok())
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
// After taking care of the data above, we need to transmit the result
|
// After taking care of the data above, we need to transmit the result
|
||||||
// of this task.
|
// of this task.
|
||||||
let what_to_do = task.death.on_exit.take();
|
let what_to_do = self.death.on_exit.take();
|
||||||
Local::put(task);
|
Local::put(self);
|
||||||
|
|
||||||
// FIXME: this is running in a seriously constrained context. If this
|
// FIXME: this is running in a seriously constrained context. If this
|
||||||
// allocates TLD then it will likely abort the runtime. Similarly,
|
// allocates TLD then it will likely abort the runtime. Similarly,
|
||||||
|
@ -549,16 +501,6 @@ mod test {
|
||||||
use std::task;
|
use std::task;
|
||||||
use unwind;
|
use unwind;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tls() {
|
|
||||||
local_data_key!(key: String)
|
|
||||||
key.replace(Some("data".to_string()));
|
|
||||||
assert_eq!(key.get().unwrap().as_slice(), "data");
|
|
||||||
local_data_key!(key2: String)
|
|
||||||
key2.replace(Some("data".to_string()));
|
|
||||||
assert_eq!(key2.get().unwrap().as_slice(), "data");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unwind() {
|
fn unwind() {
|
||||||
let result = task::try(proc()());
|
let result = task::try(proc()());
|
||||||
|
|
|
@ -1471,7 +1471,7 @@ mod test_map {
|
||||||
assert_eq!(*m.get(&2).unwrap(), 4);
|
assert_eq!(*m.get(&2).unwrap(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
local_data_key!(drop_vector: RefCell<Vec<int>>)
|
thread_local!(static DROP_VECTOR: RefCell<Vec<int>> = RefCell::new(Vec::new()))
|
||||||
|
|
||||||
#[deriving(Hash, PartialEq, Eq)]
|
#[deriving(Hash, PartialEq, Eq)]
|
||||||
struct Dropable {
|
struct Dropable {
|
||||||
|
@ -1480,8 +1480,9 @@ mod test_map {
|
||||||
|
|
||||||
impl Dropable {
|
impl Dropable {
|
||||||
fn new(k: uint) -> Dropable {
|
fn new(k: uint) -> Dropable {
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|slot| {
|
||||||
v.borrow_mut().as_mut_slice()[k] += 1;
|
slot.borrow_mut()[k] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
Dropable { k: k }
|
Dropable { k: k }
|
||||||
}
|
}
|
||||||
|
@ -1489,8 +1490,9 @@ mod test_map {
|
||||||
|
|
||||||
impl Drop for Dropable {
|
impl Drop for Dropable {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|slot| {
|
||||||
v.borrow_mut().as_mut_slice()[self.k] -= 1;
|
slot.borrow_mut()[self.k] -= 1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1502,16 +1504,18 @@ mod test_map {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_drops() {
|
fn test_drops() {
|
||||||
drop_vector.replace(Some(RefCell::new(Vec::from_elem(200, 0i))));
|
DROP_VECTOR.with(|slot| {
|
||||||
|
*slot.borrow_mut() = Vec::from_elem(200, 0i);
|
||||||
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 0);
|
assert_eq!(v.borrow().as_slice()[i], 0);
|
||||||
}
|
}
|
||||||
drop(v);
|
});
|
||||||
|
|
||||||
for i in range(0u, 100) {
|
for i in range(0u, 100) {
|
||||||
let d1 = Dropable::new(i);
|
let d1 = Dropable::new(i);
|
||||||
|
@ -1519,11 +1523,11 @@ mod test_map {
|
||||||
m.insert(d1, d2);
|
m.insert(d1, d2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 1);
|
assert_eq!(v.borrow().as_slice()[i], 1);
|
||||||
}
|
}
|
||||||
drop(v);
|
});
|
||||||
|
|
||||||
for i in range(0u, 50) {
|
for i in range(0u, 50) {
|
||||||
let k = Dropable::new(i);
|
let k = Dropable::new(i);
|
||||||
|
@ -1531,41 +1535,46 @@ mod test_map {
|
||||||
|
|
||||||
assert!(v.is_some());
|
assert!(v.is_some());
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 1);
|
assert_eq!(v.borrow().as_slice()[i], 1);
|
||||||
assert_eq!(v.borrow().as_slice()[i+100], 1);
|
assert_eq!(v.borrow().as_slice()[i+100], 1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 50) {
|
for i in range(0u, 50) {
|
||||||
|
assert_eq!(v.borrow().as_slice()[i], 0);
|
||||||
|
assert_eq!(v.borrow().as_slice()[i+100], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in range(50u, 100) {
|
||||||
|
assert_eq!(v.borrow().as_slice()[i], 1);
|
||||||
|
assert_eq!(v.borrow().as_slice()[i+100], 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DROP_VECTOR.with(|v| {
|
||||||
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 0);
|
assert_eq!(v.borrow().as_slice()[i], 0);
|
||||||
assert_eq!(v.borrow().as_slice()[i+100], 0);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
for i in range(50u, 100) {
|
|
||||||
assert_eq!(v.borrow().as_slice()[i], 1);
|
|
||||||
assert_eq!(v.borrow().as_slice()[i+100], 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
|
||||||
for i in range(0u, 200) {
|
|
||||||
assert_eq!(v.borrow().as_slice()[i], 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_move_iter_drops() {
|
fn test_move_iter_drops() {
|
||||||
drop_vector.replace(Some(RefCell::new(Vec::from_elem(200, 0i))));
|
DROP_VECTOR.with(|v| {
|
||||||
|
*v.borrow_mut() = Vec::from_elem(200, 0i);
|
||||||
|
});
|
||||||
|
|
||||||
let hm = {
|
let hm = {
|
||||||
let mut hm = HashMap::new();
|
let mut hm = HashMap::new();
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 0);
|
assert_eq!(v.borrow().as_slice()[i], 0);
|
||||||
}
|
}
|
||||||
drop(v);
|
});
|
||||||
|
|
||||||
for i in range(0u, 100) {
|
for i in range(0u, 100) {
|
||||||
let d1 = Dropable::new(i);
|
let d1 = Dropable::new(i);
|
||||||
|
@ -1573,11 +1582,11 @@ mod test_map {
|
||||||
hm.insert(d1, d2);
|
hm.insert(d1, d2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 1);
|
assert_eq!(v.borrow().as_slice()[i], 1);
|
||||||
}
|
}
|
||||||
drop(v);
|
});
|
||||||
|
|
||||||
hm
|
hm
|
||||||
};
|
};
|
||||||
|
@ -1588,31 +1597,33 @@ mod test_map {
|
||||||
{
|
{
|
||||||
let mut half = hm.into_iter().take(50);
|
let mut half = hm.into_iter().take(50);
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 1);
|
assert_eq!(v.borrow().as_slice()[i], 1);
|
||||||
}
|
}
|
||||||
drop(v);
|
});
|
||||||
|
|
||||||
for _ in half {}
|
for _ in half {}
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
let nk = range(0u, 100).filter(|&i| {
|
let nk = range(0u, 100).filter(|&i| {
|
||||||
v.borrow().as_slice()[i] == 1
|
v.borrow().as_slice()[i] == 1
|
||||||
}).count();
|
}).count();
|
||||||
|
|
||||||
let nv = range(0u, 100).filter(|&i| {
|
let nv = range(0u, 100).filter(|&i| {
|
||||||
v.borrow().as_slice()[i+100] == 1
|
v.borrow().as_slice()[i+100] == 1
|
||||||
}).count();
|
}).count();
|
||||||
|
|
||||||
assert_eq!(nk, 50);
|
assert_eq!(nk, 50);
|
||||||
assert_eq!(nv, 50);
|
assert_eq!(nv, 50);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let v = drop_vector.get().unwrap();
|
DROP_VECTOR.with(|v| {
|
||||||
for i in range(0u, 200) {
|
for i in range(0u, 200) {
|
||||||
assert_eq!(v.borrow().as_slice()[i], 0);
|
assert_eq!(v.borrow().as_slice()[i], 0);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use any::{Any, AnyRefExt};
|
use any::{Any, AnyRefExt};
|
||||||
|
use cell::RefCell;
|
||||||
use fmt;
|
use fmt;
|
||||||
use io::{Writer, IoResult};
|
use io::{Writer, IoResult};
|
||||||
use kinds::Send;
|
use kinds::Send;
|
||||||
use option::{Some, None};
|
use option::{Some, None, Option};
|
||||||
use result::Ok;
|
use result::Ok;
|
||||||
use rt::backtrace;
|
use rt::backtrace;
|
||||||
use rustrt::{Stderr, Stdio};
|
use rustrt::{Stderr, Stdio};
|
||||||
|
@ -25,7 +26,9 @@ use str::Str;
|
||||||
use string::String;
|
use string::String;
|
||||||
|
|
||||||
// Defined in this module instead of io::stdio so that the unwinding
|
// Defined in this module instead of io::stdio so that the unwinding
|
||||||
local_data_key!(pub local_stderr: Box<Writer + Send>)
|
thread_local!(pub static LOCAL_STDERR: RefCell<Option<Box<Writer + Send>>> = {
|
||||||
|
RefCell::new(None)
|
||||||
|
})
|
||||||
|
|
||||||
impl Writer for Stdio {
|
impl Writer for Stdio {
|
||||||
fn write(&mut self, bytes: &[u8]) -> IoResult<()> {
|
fn write(&mut self, bytes: &[u8]) -> IoResult<()> {
|
||||||
|
@ -74,7 +77,8 @@ pub fn on_fail(obj: &Any + Send, file: &'static str, line: uint) {
|
||||||
{
|
{
|
||||||
let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
||||||
|
|
||||||
match local_stderr.replace(None) {
|
let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take());
|
||||||
|
match prev {
|
||||||
Some(mut stderr) => {
|
Some(mut stderr) => {
|
||||||
// FIXME: what to do when the task printing panics?
|
// FIXME: what to do when the task printing panics?
|
||||||
let _ = writeln!(stderr,
|
let _ = writeln!(stderr,
|
||||||
|
@ -83,7 +87,10 @@ pub fn on_fail(obj: &Any + Send, file: &'static str, line: uint) {
|
||||||
if backtrace::log_enabled() {
|
if backtrace::log_enabled() {
|
||||||
let _ = backtrace::write(&mut *stderr);
|
let _ = backtrace::write(&mut *stderr);
|
||||||
}
|
}
|
||||||
local_stderr.replace(Some(stderr));
|
let mut s = Some(stderr);
|
||||||
|
LOCAL_STDERR.with(|slot| {
|
||||||
|
*slot.borrow_mut() = s.take();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let _ = writeln!(&mut err, "task '{}' panicked at '{}', {}:{}",
|
let _ = writeln!(&mut err, "task '{}' panicked at '{}', {}:{}",
|
||||||
|
|
|
@ -29,22 +29,24 @@ out.write(b"Hello, world!");
|
||||||
|
|
||||||
use self::StdSource::*;
|
use self::StdSource::*;
|
||||||
|
|
||||||
use failure::local_stderr;
|
use boxed::Box;
|
||||||
|
use cell::RefCell;
|
||||||
|
use failure::LOCAL_STDERR;
|
||||||
use fmt;
|
use fmt;
|
||||||
use io::{Reader, Writer, IoResult, IoError, OtherIoError,
|
use io::{Reader, Writer, IoResult, IoError, OtherIoError,
|
||||||
standard_error, EndOfFile, LineBufferedWriter, BufferedReader};
|
standard_error, EndOfFile, LineBufferedWriter, BufferedReader};
|
||||||
use iter::Iterator;
|
use iter::Iterator;
|
||||||
use kinds::Send;
|
use kinds::Send;
|
||||||
use libc;
|
use libc;
|
||||||
|
use mem;
|
||||||
use option::{Option, Some, None};
|
use option::{Option, Some, None};
|
||||||
use boxed::Box;
|
|
||||||
use sys::{fs, tty};
|
|
||||||
use result::{Ok, Err};
|
use result::{Ok, Err};
|
||||||
use rustrt;
|
use rustrt;
|
||||||
use rustrt::local::Local;
|
use rustrt::local::Local;
|
||||||
use rustrt::task::Task;
|
use rustrt::task::Task;
|
||||||
use slice::SlicePrelude;
|
use slice::SlicePrelude;
|
||||||
use str::StrPrelude;
|
use str::StrPrelude;
|
||||||
|
use sys::{fs, tty};
|
||||||
use uint;
|
use uint;
|
||||||
|
|
||||||
// And so begins the tale of acquiring a uv handle to a stdio stream on all
|
// And so begins the tale of acquiring a uv handle to a stdio stream on all
|
||||||
|
@ -87,7 +89,9 @@ fn src<T>(fd: libc::c_int, _readable: bool, f: |StdSource| -> T) -> T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local_data_key!(local_stdout: Box<Writer + Send>)
|
thread_local!(static LOCAL_STDOUT: RefCell<Option<Box<Writer + Send>>> = {
|
||||||
|
RefCell::new(None)
|
||||||
|
})
|
||||||
|
|
||||||
/// Creates a new non-blocking handle to the stdin of the current process.
|
/// Creates a new non-blocking handle to the stdin of the current process.
|
||||||
///
|
///
|
||||||
|
@ -167,7 +171,10 @@ pub fn stderr_raw() -> StdWriter {
|
||||||
/// Note that this does not need to be called for all new tasks; the default
|
/// Note that this does not need to be called for all new tasks; the default
|
||||||
/// output handle is to the process's stdout stream.
|
/// output handle is to the process's stdout stream.
|
||||||
pub fn set_stdout(stdout: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
pub fn set_stdout(stdout: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
||||||
local_stdout.replace(Some(stdout)).and_then(|mut s| {
|
let mut new = Some(stdout);
|
||||||
|
LOCAL_STDOUT.with(|slot| {
|
||||||
|
mem::replace(&mut *slot.borrow_mut(), new.take())
|
||||||
|
}).and_then(|mut s| {
|
||||||
let _ = s.flush();
|
let _ = s.flush();
|
||||||
Some(s)
|
Some(s)
|
||||||
})
|
})
|
||||||
|
@ -182,7 +189,10 @@ pub fn set_stdout(stdout: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
||||||
/// Note that this does not need to be called for all new tasks; the default
|
/// Note that this does not need to be called for all new tasks; the default
|
||||||
/// output handle is to the process's stderr stream.
|
/// output handle is to the process's stderr stream.
|
||||||
pub fn set_stderr(stderr: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
pub fn set_stderr(stderr: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
||||||
local_stderr.replace(Some(stderr)).and_then(|mut s| {
|
let mut new = Some(stderr);
|
||||||
|
LOCAL_STDERR.with(|slot| {
|
||||||
|
mem::replace(&mut *slot.borrow_mut(), new.take())
|
||||||
|
}).and_then(|mut s| {
|
||||||
let _ = s.flush();
|
let _ = s.flush();
|
||||||
Some(s)
|
Some(s)
|
||||||
})
|
})
|
||||||
|
@ -200,11 +210,16 @@ pub fn set_stderr(stderr: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
|
||||||
// })
|
// })
|
||||||
fn with_task_stdout(f: |&mut Writer| -> IoResult<()>) {
|
fn with_task_stdout(f: |&mut Writer| -> IoResult<()>) {
|
||||||
let result = if Local::exists(None::<Task>) {
|
let result = if Local::exists(None::<Task>) {
|
||||||
let mut my_stdout = local_stdout.replace(None).unwrap_or_else(|| {
|
let mut my_stdout = LOCAL_STDOUT.with(|slot| {
|
||||||
|
slot.borrow_mut().take()
|
||||||
|
}).unwrap_or_else(|| {
|
||||||
box stdout() as Box<Writer + Send>
|
box stdout() as Box<Writer + Send>
|
||||||
});
|
});
|
||||||
let result = f(&mut *my_stdout);
|
let result = f(&mut *my_stdout);
|
||||||
local_stdout.replace(Some(my_stdout));
|
let mut var = Some(my_stdout);
|
||||||
|
LOCAL_STDOUT.with(|slot| {
|
||||||
|
*slot.borrow_mut() = var.take();
|
||||||
|
});
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
let mut io = rustrt::Stdout;
|
let mut io = rustrt::Stdout;
|
||||||
|
|
|
@ -170,7 +170,6 @@ pub use core_collections::string;
|
||||||
pub use core_collections::vec;
|
pub use core_collections::vec;
|
||||||
|
|
||||||
pub use rustrt::c_str;
|
pub use rustrt::c_str;
|
||||||
pub use rustrt::local_data;
|
|
||||||
|
|
||||||
pub use unicode::char;
|
pub use unicode::char;
|
||||||
|
|
||||||
|
@ -209,17 +208,25 @@ pub mod prelude;
|
||||||
#[path = "num/f32.rs"] pub mod f32;
|
#[path = "num/f32.rs"] pub mod f32;
|
||||||
#[path = "num/f64.rs"] pub mod f64;
|
#[path = "num/f64.rs"] pub mod f64;
|
||||||
|
|
||||||
pub mod rand;
|
|
||||||
|
|
||||||
pub mod ascii;
|
pub mod ascii;
|
||||||
|
|
||||||
pub mod time;
|
|
||||||
|
|
||||||
/* Common traits */
|
/* Common traits */
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod num;
|
pub mod num;
|
||||||
|
|
||||||
|
/* Runtime and platform support */
|
||||||
|
|
||||||
|
pub mod thread_local;
|
||||||
|
pub mod c_vec;
|
||||||
|
pub mod dynamic_lib;
|
||||||
|
pub mod fmt;
|
||||||
|
pub mod io;
|
||||||
|
pub mod os;
|
||||||
|
pub mod path;
|
||||||
|
pub mod rand;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
/* Common data structures */
|
/* Common data structures */
|
||||||
|
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
|
@ -230,15 +237,6 @@ pub mod hash;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
|
||||||
/* Runtime and platform support */
|
|
||||||
|
|
||||||
pub mod c_vec;
|
|
||||||
pub mod dynamic_lib;
|
|
||||||
pub mod os;
|
|
||||||
pub mod io;
|
|
||||||
pub mod path;
|
|
||||||
pub mod fmt;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[path = "sys/unix/mod.rs"] mod sys;
|
#[path = "sys/unix/mod.rs"] mod sys;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -263,10 +261,12 @@ mod std {
|
||||||
pub use error; // used for try!()
|
pub use error; // used for try!()
|
||||||
pub use fmt; // used for any formatting strings
|
pub use fmt; // used for any formatting strings
|
||||||
pub use io; // used for println!()
|
pub use io; // used for println!()
|
||||||
pub use local_data; // used for local_data_key!()
|
|
||||||
pub use option; // used for bitflags!{}
|
pub use option; // used for bitflags!{}
|
||||||
pub use rt; // used for panic!()
|
pub use rt; // used for panic!()
|
||||||
pub use vec; // used for vec![]
|
pub use vec; // used for vec![]
|
||||||
|
pub use cell; // used for tls!
|
||||||
|
pub use thread_local; // used for thread_local!
|
||||||
|
pub use kinds; // used for tls!
|
||||||
|
|
||||||
// The test runner calls ::std::os::args() but really wants realstd
|
// The test runner calls ::std::os::args() but really wants realstd
|
||||||
#[cfg(test)] pub use realstd::os as os;
|
#[cfg(test)] pub use realstd::os as os;
|
||||||
|
@ -276,4 +276,5 @@ mod std {
|
||||||
pub use slice;
|
pub use slice;
|
||||||
|
|
||||||
pub use boxed; // used for vec![]
|
pub use boxed; // used for vec![]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,28 +304,6 @@ macro_rules! println(
|
||||||
($($arg:tt)*) => (format_args!(::std::io::stdio::println_args, $($arg)*))
|
($($arg:tt)*) => (format_args!(::std::io::stdio::println_args, $($arg)*))
|
||||||
)
|
)
|
||||||
|
|
||||||
/// Declare a task-local key with a specific type.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// local_data_key!(my_integer: int)
|
|
||||||
///
|
|
||||||
/// my_integer.replace(Some(2));
|
|
||||||
/// println!("{}", my_integer.get().map(|a| *a));
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! local_data_key(
|
|
||||||
($name:ident: $ty:ty) => (
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
static $name: ::std::local_data::Key<$ty> = &::std::local_data::KeyValueKey;
|
|
||||||
);
|
|
||||||
(pub $name:ident: $ty:ty) => (
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub static $name: ::std::local_data::Key<$ty> = &::std::local_data::KeyValueKey;
|
|
||||||
);
|
|
||||||
)
|
|
||||||
|
|
||||||
/// Helper macro for unwrapping `Result` values while returning early with an
|
/// Helper macro for unwrapping `Result` values while returning early with an
|
||||||
/// error if the value of the expression is `Err`. For more information, see
|
/// error if the value of the expression is `Err`. For more information, see
|
||||||
/// `std::io`.
|
/// `std::io`.
|
||||||
|
|
|
@ -226,7 +226,6 @@ use clone::Clone;
|
||||||
use io::IoResult;
|
use io::IoResult;
|
||||||
use iter::Iterator;
|
use iter::Iterator;
|
||||||
use mem;
|
use mem;
|
||||||
use option::{Some, None};
|
|
||||||
use rc::Rc;
|
use rc::Rc;
|
||||||
use result::{Ok, Err};
|
use result::{Ok, Err};
|
||||||
use vec::Vec;
|
use vec::Vec;
|
||||||
|
@ -337,24 +336,18 @@ pub struct TaskRng {
|
||||||
/// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`.
|
/// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`.
|
||||||
pub fn task_rng() -> TaskRng {
|
pub fn task_rng() -> TaskRng {
|
||||||
// used to make space in TLS for a random number generator
|
// used to make space in TLS for a random number generator
|
||||||
local_data_key!(TASK_RNG_KEY: Rc<RefCell<TaskRngInner>>)
|
thread_local!(static TASK_RNG_KEY: Rc<RefCell<TaskRngInner>> = {
|
||||||
|
let r = match StdRng::new() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => panic!("could not initialize task_rng: {}", e)
|
||||||
|
};
|
||||||
|
let rng = reseeding::ReseedingRng::new(r,
|
||||||
|
TASK_RNG_RESEED_THRESHOLD,
|
||||||
|
TaskRngReseeder);
|
||||||
|
Rc::new(RefCell::new(rng))
|
||||||
|
})
|
||||||
|
|
||||||
match TASK_RNG_KEY.get() {
|
TaskRng { rng: TASK_RNG_KEY.with(|t| t.clone()) }
|
||||||
None => {
|
|
||||||
let r = match StdRng::new() {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(e) => panic!("could not initialize task_rng: {}", e)
|
|
||||||
};
|
|
||||||
let rng = reseeding::ReseedingRng::new(r,
|
|
||||||
TASK_RNG_RESEED_THRESHOLD,
|
|
||||||
TaskRngReseeder);
|
|
||||||
let rng = Rc::new(RefCell::new(rng));
|
|
||||||
TASK_RNG_KEY.replace(Some(rng.clone()));
|
|
||||||
|
|
||||||
TaskRng { rng: rng }
|
|
||||||
}
|
|
||||||
Some(rng) => TaskRng { rng: rng.clone() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rng for TaskRng {
|
impl Rng for TaskRng {
|
||||||
|
|
|
@ -210,28 +210,4 @@ mod test {
|
||||||
});
|
});
|
||||||
assert_eq!(rx.recv(), expected);
|
assert_eq!(rx.recv(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dropped_future_doesnt_panic() {
|
|
||||||
struct Bomb(Sender<bool>);
|
|
||||||
|
|
||||||
local_data_key!(LOCAL: Bomb)
|
|
||||||
|
|
||||||
impl Drop for Bomb {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let Bomb(ref tx) = *self;
|
|
||||||
tx.send(task::failing());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn a future, but drop it immediately. When we receive the result
|
|
||||||
// later on, we should never view the task as having panicked.
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
drop(Future::spawn(proc() {
|
|
||||||
LOCAL.replace(Some(Bomb(tx)));
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Make sure the future didn't panic the task.
|
|
||||||
assert!(!rx.recv());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use collections;
|
||||||
|
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod helper_thread;
|
pub mod helper_thread;
|
||||||
|
pub mod thread_local;
|
||||||
|
|
||||||
// common error constructors
|
// common error constructors
|
||||||
|
|
||||||
|
|
306
src/libstd/sys/common/thread_local.rs
Normal file
306
src/libstd/sys/common/thread_local.rs
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
//! OS-based thread local storage
|
||||||
|
//!
|
||||||
|
//! This module provides an implementation of OS-based thread local storage,
|
||||||
|
//! using the native OS-provided facilities (think `TlsAlloc` or
|
||||||
|
//! `pthread_setspecific`). The interface of this differs from the other types
|
||||||
|
//! of thread-local-storage provided in this crate in that OS-based TLS can only
|
||||||
|
//! get/set pointers,
|
||||||
|
//!
|
||||||
|
//! This module also provides two flavors of TLS. One is intended for static
|
||||||
|
//! initialization, and does not contain a `Drop` implementation to deallocate
|
||||||
|
//! the OS-TLS key. The other is a type which does implement `Drop` and hence
|
||||||
|
//! has a safe interface.
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! This module should likely not be used directly unless other primitives are
|
||||||
|
//! being built on. types such as `thread_local::scoped::Key` are likely much
|
||||||
|
//! more useful in practice than this OS-based version which likely requires
|
||||||
|
//! unsafe code to interoperate with.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! Using a dynamically allocated TLS key. Note that this key can be shared
|
||||||
|
//! among many threads via an `Arc`.
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! let key = Key::new(None);
|
||||||
|
//! assert!(key.get().is_null());
|
||||||
|
//! key.set(1 as *mut u8);
|
||||||
|
//! assert!(!key.get().is_null());
|
||||||
|
//!
|
||||||
|
//! drop(key); // deallocate this TLS slot.
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Sometimes a statically allocated key is either required or easier to work
|
||||||
|
//! with, however.
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! static KEY: StaticKey = INIT;
|
||||||
|
//!
|
||||||
|
//! unsafe {
|
||||||
|
//! assert!(KEY.get().is_null());
|
||||||
|
//! KEY.set(1 as *mut u8);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use kinds::marker;
|
||||||
|
use mem;
|
||||||
|
use rustrt::exclusive::Exclusive;
|
||||||
|
use rustrt;
|
||||||
|
use sync::atomic::{mod, AtomicUint};
|
||||||
|
use sync::{Once, ONCE_INIT};
|
||||||
|
|
||||||
|
use sys::thread_local as imp;
|
||||||
|
|
||||||
|
/// A type for TLS keys that are statically allocated.
|
||||||
|
///
|
||||||
|
/// This type is entirely `unsafe` to use as it does not protect against
|
||||||
|
/// use-after-deallocation or use-during-deallocation.
|
||||||
|
///
|
||||||
|
/// The actual OS-TLS key is lazily allocated when this is used for the first
|
||||||
|
/// time. The key is also deallocated when the Rust runtime exits or `destroy`
|
||||||
|
/// is called, whichever comes first.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use tls::os::{StaticKey, INIT};
|
||||||
|
///
|
||||||
|
/// static KEY: StaticKey = INIT;
|
||||||
|
///
|
||||||
|
/// unsafe {
|
||||||
|
/// assert!(KEY.get().is_null());
|
||||||
|
/// KEY.set(1 as *mut u8);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct StaticKey {
|
||||||
|
/// Inner static TLS key (internals), created with by `INIT_INNER` in this
|
||||||
|
/// module.
|
||||||
|
pub inner: StaticKeyInner,
|
||||||
|
/// Destructor for the TLS value.
|
||||||
|
///
|
||||||
|
/// See `Key::new` for information about when the destructor runs and how
|
||||||
|
/// it runs.
|
||||||
|
pub dtor: Option<unsafe extern fn(*mut u8)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner contents of `StaticKey`, created by the `INIT_INNER` constant.
|
||||||
|
pub struct StaticKeyInner {
|
||||||
|
key: AtomicUint,
|
||||||
|
nc: marker::NoCopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type for a safely managed OS-based TLS slot.
|
||||||
|
///
|
||||||
|
/// This type allocates an OS TLS key when it is initialized and will deallocate
|
||||||
|
/// the key when it falls out of scope. When compared with `StaticKey`, this
|
||||||
|
/// type is entirely safe to use.
|
||||||
|
///
|
||||||
|
/// Implementations will likely, however, contain unsafe code as this type only
|
||||||
|
/// operates on `*mut u8`, an unsafe pointer.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// use tls::os::Key;
|
||||||
|
///
|
||||||
|
/// let key = Key::new(None);
|
||||||
|
/// assert!(key.get().is_null());
|
||||||
|
/// key.set(1 as *mut u8);
|
||||||
|
/// assert!(!key.get().is_null());
|
||||||
|
///
|
||||||
|
/// drop(key); // deallocate this TLS slot.
|
||||||
|
/// ```
|
||||||
|
pub struct Key {
|
||||||
|
key: imp::Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constant initialization value for static TLS keys.
|
||||||
|
///
|
||||||
|
/// This value specifies no destructor by default.
|
||||||
|
pub const INIT: StaticKey = StaticKey {
|
||||||
|
inner: INIT_INNER,
|
||||||
|
dtor: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Constant initialization value for the inner part of static TLS keys.
|
||||||
|
///
|
||||||
|
/// This value allows specific configuration of the destructor for a TLS key.
|
||||||
|
pub const INIT_INNER: StaticKeyInner = StaticKeyInner {
|
||||||
|
key: atomic::INIT_ATOMIC_UINT,
|
||||||
|
nc: marker::NoCopy,
|
||||||
|
};
|
||||||
|
|
||||||
|
static INIT_KEYS: Once = ONCE_INIT;
|
||||||
|
static mut KEYS: *mut Exclusive<Vec<imp::Key>> = 0 as *mut _;
|
||||||
|
|
||||||
|
impl StaticKey {
|
||||||
|
/// Gets the value associated with this TLS key
|
||||||
|
///
|
||||||
|
/// This will lazily allocate a TLS key from the OS if one has not already
|
||||||
|
/// been allocated.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn get(&self) -> *mut u8 { imp::get(self.key()) }
|
||||||
|
|
||||||
|
/// Sets this TLS key to a new value.
|
||||||
|
///
|
||||||
|
/// This will lazily allocate a TLS key from the OS if one has not already
|
||||||
|
/// been allocated.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn set(&self, val: *mut u8) { imp::set(self.key(), val) }
|
||||||
|
|
||||||
|
/// Deallocates this OS TLS key.
|
||||||
|
///
|
||||||
|
/// This function is unsafe as there is no guarantee that the key is not
|
||||||
|
/// currently in use by other threads or will not ever be used again.
|
||||||
|
///
|
||||||
|
/// Note that this does *not* run the user-provided destructor if one was
|
||||||
|
/// specified at definition time. Doing so must be done manually.
|
||||||
|
pub unsafe fn destroy(&self) {
|
||||||
|
match self.inner.key.swap(0, atomic::SeqCst) {
|
||||||
|
0 => {}
|
||||||
|
n => { unregister_key(n as imp::Key); imp::destroy(n as imp::Key) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn key(&self) -> imp::Key {
|
||||||
|
match self.inner.key.load(atomic::Relaxed) {
|
||||||
|
0 => self.lazy_init() as imp::Key,
|
||||||
|
n => n as imp::Key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn lazy_init(&self) -> uint {
|
||||||
|
let key = imp::create(self.dtor);
|
||||||
|
assert!(key != 0);
|
||||||
|
match self.inner.key.compare_and_swap(0, key as uint, atomic::SeqCst) {
|
||||||
|
// The CAS succeeded, so we've created the actual key
|
||||||
|
0 => {
|
||||||
|
register_key(key);
|
||||||
|
key as uint
|
||||||
|
}
|
||||||
|
// If someone beat us to the punch, use their key instead
|
||||||
|
n => { imp::destroy(key); n }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
/// Create a new managed OS TLS key.
|
||||||
|
///
|
||||||
|
/// This key will be deallocated when the key falls out of scope.
|
||||||
|
///
|
||||||
|
/// The argument provided is an optionally-specified destructor for the
|
||||||
|
/// value of this TLS key. When a thread exits and the value for this key
|
||||||
|
/// is non-null the destructor will be invoked. The TLS value will be reset
|
||||||
|
/// to null before the destructor is invoked.
|
||||||
|
///
|
||||||
|
/// Note that the destructor will not be run when the `Key` goes out of
|
||||||
|
/// scope.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
|
||||||
|
Key { key: unsafe { imp::create(dtor) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See StaticKey::get
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self) -> *mut u8 {
|
||||||
|
unsafe { imp::get(self.key) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See StaticKey::set
|
||||||
|
#[inline]
|
||||||
|
pub fn set(&self, val: *mut u8) {
|
||||||
|
unsafe { imp::set(self.key, val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Key {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { imp::destroy(self.key) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_keys() {
|
||||||
|
let keys = box Exclusive::new(Vec::<imp::Key>::new());
|
||||||
|
unsafe {
|
||||||
|
KEYS = mem::transmute(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
rustrt::at_exit(proc() unsafe {
|
||||||
|
let keys: Box<Exclusive<Vec<imp::Key>>> = mem::transmute(KEYS);
|
||||||
|
KEYS = 0 as *mut _;
|
||||||
|
let keys = keys.lock();
|
||||||
|
for key in keys.iter() {
|
||||||
|
imp::destroy(*key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_key(key: imp::Key) {
|
||||||
|
INIT_KEYS.doit(init_keys);
|
||||||
|
let mut keys = unsafe { (*KEYS).lock() };
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unregister_key(key: imp::Key) {
|
||||||
|
INIT_KEYS.doit(init_keys);
|
||||||
|
let mut keys = unsafe { (*KEYS).lock() };
|
||||||
|
keys.retain(|k| *k != key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use prelude::*;
|
||||||
|
use super::{Key, StaticKey, INIT_INNER};
|
||||||
|
|
||||||
|
fn assert_sync<T: Sync>() {}
|
||||||
|
fn assert_send<T: Send>() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke() {
|
||||||
|
assert_sync::<Key>();
|
||||||
|
assert_send::<Key>();
|
||||||
|
|
||||||
|
let k1 = Key::new(None);
|
||||||
|
let k2 = Key::new(None);
|
||||||
|
assert!(k1.get().is_null());
|
||||||
|
assert!(k2.get().is_null());
|
||||||
|
k1.set(1 as *mut _);
|
||||||
|
k2.set(2 as *mut _);
|
||||||
|
assert_eq!(k1.get() as uint, 1);
|
||||||
|
assert_eq!(k2.get() as uint, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn statik() {
|
||||||
|
static K1: StaticKey = StaticKey { inner: INIT_INNER, dtor: None };
|
||||||
|
static K2: StaticKey = StaticKey { inner: INIT_INNER, dtor: None };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
assert!(K1.get().is_null());
|
||||||
|
assert!(K2.get().is_null());
|
||||||
|
K1.set(1 as *mut _);
|
||||||
|
K2.set(2 as *mut _);
|
||||||
|
assert_eq!(K1.get() as uint, 1);
|
||||||
|
assert_eq!(K2.get() as uint, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,14 +34,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
|
||||||
|
|
||||||
pub mod c;
|
pub mod c;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod os;
|
|
||||||
pub mod tcp;
|
|
||||||
pub mod udp;
|
|
||||||
pub mod pipe;
|
|
||||||
pub mod helper_signal;
|
pub mod helper_signal;
|
||||||
|
pub mod os;
|
||||||
|
pub mod pipe;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
pub mod tcp;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
|
pub mod thread_local;
|
||||||
pub mod tty;
|
pub mod tty;
|
||||||
|
pub mod udp;
|
||||||
|
|
||||||
pub mod addrinfo {
|
pub mod addrinfo {
|
||||||
pub use sys_common::net::get_host_addresses;
|
pub use sys_common::net::get_host_addresses;
|
||||||
|
|
52
src/libstd/sys/unix/thread_local.rs
Normal file
52
src/libstd/sys/unix/thread_local.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
use libc::c_int;
|
||||||
|
|
||||||
|
pub type Key = pthread_key_t;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
|
||||||
|
let mut key = 0;
|
||||||
|
assert_eq!(pthread_key_create(&mut key, dtor), 0);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn set(key: Key, value: *mut u8) {
|
||||||
|
let r = pthread_setspecific(key, value);
|
||||||
|
debug_assert_eq!(r, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn get(key: Key) -> *mut u8 {
|
||||||
|
pthread_getspecific(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn destroy(key: Key) {
|
||||||
|
let r = pthread_key_delete(key);
|
||||||
|
debug_assert_eq!(r, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
type pthread_key_t = ::libc::c_ulong;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
type pthread_key_t = ::libc::c_uint;
|
||||||
|
|
||||||
|
extern {
|
||||||
|
fn pthread_key_create(key: *mut pthread_key_t,
|
||||||
|
dtor: Option<unsafe extern fn(*mut u8)>) -> c_int;
|
||||||
|
fn pthread_key_delete(key: pthread_key_t) -> c_int;
|
||||||
|
fn pthread_getspecific(key: pthread_key_t) -> *mut u8;
|
||||||
|
fn pthread_setspecific(key: pthread_key_t, value: *mut u8) -> c_int;
|
||||||
|
}
|
|
@ -35,14 +35,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
|
||||||
|
|
||||||
pub mod c;
|
pub mod c;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod os;
|
|
||||||
pub mod tcp;
|
|
||||||
pub mod udp;
|
|
||||||
pub mod pipe;
|
|
||||||
pub mod helper_signal;
|
pub mod helper_signal;
|
||||||
|
pub mod os;
|
||||||
|
pub mod pipe;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
pub mod tcp;
|
||||||
|
pub mod thread_local;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod tty;
|
pub mod tty;
|
||||||
|
pub mod udp;
|
||||||
|
|
||||||
pub mod addrinfo {
|
pub mod addrinfo {
|
||||||
pub use sys_common::net::get_host_addresses;
|
pub use sys_common::net::get_host_addresses;
|
||||||
|
|
238
src/libstd/sys/windows/thread_local.rs
Normal file
238
src/libstd/sys/windows/thread_local.rs
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use libc::types::os::arch::extra::{DWORD, LPVOID, BOOL};
|
||||||
|
|
||||||
|
use mem;
|
||||||
|
use rustrt;
|
||||||
|
use rustrt::exclusive::Exclusive;
|
||||||
|
use sync::{ONCE_INIT, Once};
|
||||||
|
|
||||||
|
pub type Key = DWORD;
|
||||||
|
pub type Dtor = unsafe extern fn(*mut u8);
|
||||||
|
|
||||||
|
// Turns out, like pretty much everything, Windows is pretty close the
|
||||||
|
// functionality that Unix provides, but slightly different! In the case of
|
||||||
|
// TLS, Windows does not provide an API to provide a destructor for a TLS
|
||||||
|
// variable. This ends up being pretty crucial to this implementation, so we
|
||||||
|
// need a way around this.
|
||||||
|
//
|
||||||
|
// The solution here ended up being a little obscure, but fear not, the
|
||||||
|
// internet has informed me [1][2] that this solution is not unique (no way
|
||||||
|
// I could have thought of it as well!). The key idea is to insert some hook
|
||||||
|
// somewhere to run arbitrary code on thread termination. With this in place
|
||||||
|
// we'll be able to run anything we like, including all TLS destructors!
|
||||||
|
//
|
||||||
|
// To accomplish this feat, we perform a number of tasks, all contained
|
||||||
|
// within this module:
|
||||||
|
//
|
||||||
|
// * All TLS destructors are tracked by *us*, not the windows runtime. This
|
||||||
|
// means that we have a global list of destructors for each TLS key that
|
||||||
|
// we know about.
|
||||||
|
// * When a TLS key is destroyed, we're sure to remove it from the dtor list
|
||||||
|
// if it's in there.
|
||||||
|
// * When a thread exits, we run over the entire list and run dtors for all
|
||||||
|
// non-null keys. This attempts to match Unix semantics in this regard.
|
||||||
|
//
|
||||||
|
// This ends up having the overhead of using a global list, having some
|
||||||
|
// locks here and there, and in general just adding some more code bloat. We
|
||||||
|
// attempt to optimize runtime by forgetting keys that don't have
|
||||||
|
// destructors, but this only gets us so far.
|
||||||
|
//
|
||||||
|
// For more details and nitty-gritty, see the code sections below!
|
||||||
|
//
|
||||||
|
// [1]: http://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
|
||||||
|
// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
|
||||||
|
// /threading/thread_local_storage_win.cc#L42
|
||||||
|
|
||||||
|
static INIT_DTORS: Once = ONCE_INIT;
|
||||||
|
static mut DTORS: *mut Exclusive<Vec<(Key, Dtor)>> = 0 as *mut _;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Native bindings
|
||||||
|
//
|
||||||
|
// This section is just raw bindings to the native functions that Windows
|
||||||
|
// provides, There's a few extra calls to deal with destructors.
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn create(dtor: Option<Dtor>) -> Key {
|
||||||
|
const TLS_OUT_OF_INDEXES: DWORD = 0xFFFFFFFF;
|
||||||
|
let key = TlsAlloc();
|
||||||
|
assert!(key != TLS_OUT_OF_INDEXES);
|
||||||
|
match dtor {
|
||||||
|
Some(f) => register_dtor(key, f),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn set(key: Key, value: *mut u8) {
|
||||||
|
let r = TlsSetValue(key, value as LPVOID);
|
||||||
|
debug_assert!(r != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn get(key: Key) -> *mut u8 {
|
||||||
|
TlsGetValue(key) as *mut u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn destroy(key: Key) {
|
||||||
|
if unregister_dtor(key) {
|
||||||
|
// FIXME: Currently if a key has a destructor associated with it we
|
||||||
|
// can't actually ever unregister it. If we were to
|
||||||
|
// unregister it, then any key destruction would have to be
|
||||||
|
// serialized with respect to actually running destructors.
|
||||||
|
//
|
||||||
|
// We want to avoid a race where right before run_dtors runs
|
||||||
|
// some destructors TlsFree is called. Allowing the call to
|
||||||
|
// TlsFree would imply that the caller understands that *all
|
||||||
|
// known threads* are not exiting, which is quite a difficult
|
||||||
|
// thing to know!
|
||||||
|
//
|
||||||
|
// For now we just leak all keys with dtors to "fix" this.
|
||||||
|
// Note that source [2] above shows precedent for this sort
|
||||||
|
// of strategy.
|
||||||
|
} else {
|
||||||
|
let r = TlsFree(key);
|
||||||
|
debug_assert!(r != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "system" {
|
||||||
|
fn TlsAlloc() -> DWORD;
|
||||||
|
fn TlsFree(dwTlsIndex: DWORD) -> BOOL;
|
||||||
|
fn TlsGetValue(dwTlsIndex: DWORD) -> LPVOID;
|
||||||
|
fn TlsSetValue(dwTlsIndex: DWORD, lpTlsvalue: LPVOID) -> BOOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Dtor registration
|
||||||
|
//
|
||||||
|
// These functions are associated with registering and unregistering
|
||||||
|
// destructors. They're pretty simple, they just push onto a vector and scan
|
||||||
|
// a vector currently.
|
||||||
|
//
|
||||||
|
// FIXME: This could probably be at least a little faster with a BTree.
|
||||||
|
|
||||||
|
fn init_dtors() {
|
||||||
|
let dtors = box Exclusive::new(Vec::<(Key, Dtor)>::new());
|
||||||
|
unsafe {
|
||||||
|
DTORS = mem::transmute(dtors);
|
||||||
|
}
|
||||||
|
|
||||||
|
rustrt::at_exit(proc() unsafe {
|
||||||
|
mem::transmute::<_, Box<Exclusive<Vec<(Key, Dtor)>>>>(DTORS);
|
||||||
|
DTORS = 0 as *mut _;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn register_dtor(key: Key, dtor: Dtor) {
|
||||||
|
INIT_DTORS.doit(init_dtors);
|
||||||
|
let mut dtors = (*DTORS).lock();
|
||||||
|
dtors.push((key, dtor));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn unregister_dtor(key: Key) -> bool {
|
||||||
|
if DTORS.is_null() { return false }
|
||||||
|
let mut dtors = (*DTORS).lock();
|
||||||
|
let before = dtors.len();
|
||||||
|
dtors.retain(|&(k, _)| k != key);
|
||||||
|
dtors.len() != before
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Where the Magic (TM) Happens
|
||||||
|
//
|
||||||
|
// If you're looking at this code, and wondering "what is this doing?",
|
||||||
|
// you're not alone! I'll try to break this down step by step:
|
||||||
|
//
|
||||||
|
// # What's up with CRT$XLB?
|
||||||
|
//
|
||||||
|
// For anything about TLS destructors to work on Windows, we have to be able
|
||||||
|
// to run *something* when a thread exits. To do so, we place a very special
|
||||||
|
// static in a very special location. If this is encoded in just the right
|
||||||
|
// way, the kernel's loader is apparently nice enough to run some function
|
||||||
|
// of ours whenever a thread exits! How nice of the kernel!
|
||||||
|
//
|
||||||
|
// Lots of detailed information can be found in source [1] above, but the
|
||||||
|
// gist of it is that this is leveraging a feature of Microsoft's PE format
|
||||||
|
// (executable format) which is not actually used by any compilers today.
|
||||||
|
// This apparently translates to any callbacks in the ".CRT$XLB" section
|
||||||
|
// being run on certain events.
|
||||||
|
//
|
||||||
|
// So after all that, we use the compiler's #[link_section] feature to place
|
||||||
|
// a callback pointer into the magic section so it ends up being called.
|
||||||
|
//
|
||||||
|
// # What's up with this callback?
|
||||||
|
//
|
||||||
|
// The callback specified receives a number of parameters from... someone!
|
||||||
|
// (the kernel? the runtime? I'm not qute sure!) There are a few events that
|
||||||
|
// this gets invoked for, but we're currentl only interested on when a
|
||||||
|
// thread or a process "detaches" (exits). The process part happens for the
|
||||||
|
// last thread and the thread part happens for any normal thread.
|
||||||
|
//
|
||||||
|
// # Ok, what's up with running all these destructors?
|
||||||
|
//
|
||||||
|
// This will likely need to be improved over time, but this function
|
||||||
|
// attempts a "poor man's" destructor callback system. To do this we clone a
|
||||||
|
// local copy of the dtor list to start out with. This is our fudgy attempt
|
||||||
|
// to not hold the lock while destructors run and not worry about the list
|
||||||
|
// changing while we're looking at it.
|
||||||
|
//
|
||||||
|
// Once we've got a list of what to run, we iterate over all keys, check
|
||||||
|
// their values, and then run destructors if the values turn out to be non
|
||||||
|
// null (setting them to null just beforehand). We do this a few times in a
|
||||||
|
// loop to basically match Unix semantics. If we don't reach a fixed point
|
||||||
|
// after a short while then we just inevitably leak something most likely.
|
||||||
|
//
|
||||||
|
// # The article mentions crazy stuff about "/INCLUDE"?
|
||||||
|
//
|
||||||
|
// It sure does! This seems to work for now, so maybe we'll just run into
|
||||||
|
// that if we start linking with msvc?
|
||||||
|
|
||||||
|
#[link_section = ".CRT$XLB"]
|
||||||
|
#[linkage = "external"]
|
||||||
|
#[allow(warnings)]
|
||||||
|
pub static p_thread_callback: unsafe extern "system" fn(LPVOID, DWORD,
|
||||||
|
LPVOID) =
|
||||||
|
on_tls_callback;
|
||||||
|
|
||||||
|
#[allow(warnings)]
|
||||||
|
unsafe extern "system" fn on_tls_callback(h: LPVOID,
|
||||||
|
dwReason: DWORD,
|
||||||
|
pv: LPVOID) {
|
||||||
|
const DLL_THREAD_DETACH: DWORD = 3;
|
||||||
|
const DLL_PROCESS_DETACH: DWORD = 0;
|
||||||
|
if dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH {
|
||||||
|
run_dtors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn run_dtors() {
|
||||||
|
if DTORS.is_null() { return }
|
||||||
|
let mut any_run = true;
|
||||||
|
for _ in range(0, 5i) {
|
||||||
|
if !any_run { break }
|
||||||
|
any_run = false;
|
||||||
|
let dtors = (*DTORS).lock().iter().map(|p| *p).collect::<Vec<_>>();
|
||||||
|
for &(key, dtor) in dtors.iter() {
|
||||||
|
let ptr = TlsGetValue(key);
|
||||||
|
if !ptr.is_null() {
|
||||||
|
TlsSetValue(key, 0 as *mut _);
|
||||||
|
dtor(ptr as *mut _);
|
||||||
|
any_run = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
634
src/libstd/thread_local/mod.rs
Normal file
634
src/libstd/thread_local/mod.rs
Normal file
|
@ -0,0 +1,634 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
//! Thread local storage
|
||||||
|
//!
|
||||||
|
//! This module provides an implementation of thread local storage for Rust
|
||||||
|
//! programs. Thread local storage is a method of storing data into a global
|
||||||
|
//! variable which each thread in the program will have its own copy of.
|
||||||
|
//! Threads do not share this data, so accesses do not need to be synchronized.
|
||||||
|
//!
|
||||||
|
//! At a high level, this module provides two variants of storage:
|
||||||
|
//!
|
||||||
|
//! * Owning thread local storage. This is a type of thread local key which
|
||||||
|
//! owns the value that it contains, and will destroy the value when the
|
||||||
|
//! thread exits. This variant is created with the `thread_local!` macro and
|
||||||
|
//! can contain any value which is `'static` (no borrowed pointers.
|
||||||
|
//!
|
||||||
|
//! * Scoped thread local storage. This type of key is used to store a reference
|
||||||
|
//! to a value into local storage temporarily for the scope of a function
|
||||||
|
//! call. There are no restrictions on what types of values can be placed
|
||||||
|
//! into this key.
|
||||||
|
//!
|
||||||
|
//! Both forms of thread local storage provide an accessor function, `with`,
|
||||||
|
//! which will yield a shared reference to the value to the specified
|
||||||
|
//! closure. Thread local keys only allow shared access to values as there is no
|
||||||
|
//! way to guarantee uniqueness if a mutable borrow was allowed. Most values
|
||||||
|
//! will want to make use of some form of **interior mutability** through the
|
||||||
|
//! `Cell` or `RefCell` types.
|
||||||
|
|
||||||
|
#![macro_escape]
|
||||||
|
#![experimental]
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use cell::UnsafeCell;
|
||||||
|
|
||||||
|
// Sure wish we had macro hygiene, no?
|
||||||
|
#[doc(hidden)] pub use self::imp::Key as KeyInner;
|
||||||
|
#[doc(hidden)] pub use self::imp::destroy_value;
|
||||||
|
#[doc(hidden)] pub use sys_common::thread_local::INIT_INNER as OS_INIT_INNER;
|
||||||
|
#[doc(hidden)] pub use sys_common::thread_local::StaticKey as OsStaticKey;
|
||||||
|
|
||||||
|
pub mod scoped;
|
||||||
|
|
||||||
|
/// A thread local storage key which owns its contents.
|
||||||
|
///
|
||||||
|
/// This key uses the fastest possible implementation available to it for the
|
||||||
|
/// target platform. It is instantiated with the `thread_local!` macro and the
|
||||||
|
/// primary method is the `with` method.
|
||||||
|
///
|
||||||
|
/// The `with` method yields a reference to the contained value which cannot be
|
||||||
|
/// sent across tasks or escape the given closure.
|
||||||
|
///
|
||||||
|
/// # Initialization and Destruction
|
||||||
|
///
|
||||||
|
/// Initialization is dynamically performed on the first call to `with()`
|
||||||
|
/// within a thread, and values support destructors which will be run when a
|
||||||
|
/// thread exits.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::cell::RefCell;
|
||||||
|
///
|
||||||
|
/// thread_local!(static FOO: RefCell<uint> = RefCell::new(1));
|
||||||
|
///
|
||||||
|
/// FOO.with(|f| {
|
||||||
|
/// assert_eq!(*f.borrow(), 1);
|
||||||
|
/// *f.borrow_mut() = 2;
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // each thread starts out with the initial value of 1
|
||||||
|
/// spawn(proc() {
|
||||||
|
/// FOO.with(|f| {
|
||||||
|
/// assert_eq!(*f.borrow(), 1);
|
||||||
|
/// *f.borrow_mut() = 3;
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // we retain our original value of 2 despite the child thread
|
||||||
|
/// FOO.with(|f| {
|
||||||
|
/// assert_eq!(*f.borrow(), 2);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub struct Key<T> {
|
||||||
|
// The key itself may be tagged with #[thread_local], and this `Key` is
|
||||||
|
// stored as a `static`, and it's not valid for a static to reference the
|
||||||
|
// address of another thread_local static. For this reason we kinda wonkily
|
||||||
|
// work around this by generating a shim function which will give us the
|
||||||
|
// address of the inner TLS key at runtime.
|
||||||
|
//
|
||||||
|
// This is trivially devirtualizable by LLVM because we never store anything
|
||||||
|
// to this field and rustc can declare the `static` as constant as well.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub inner: fn() -> &'static KeyInner<UnsafeCell<Option<T>>>,
|
||||||
|
|
||||||
|
// initialization routine to invoke to create a value
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub init: fn() -> T,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declare a new thread local storage key of type `std::thread_local::Key`.
|
||||||
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
|
macro_rules! thread_local(
|
||||||
|
(static $name:ident: $t:ty = $init:expr) => (
|
||||||
|
static $name: ::std::thread_local::Key<$t> = {
|
||||||
|
use std::cell::UnsafeCell as __UnsafeCell;
|
||||||
|
use std::thread_local::KeyInner as __KeyInner;
|
||||||
|
use std::option::Option as __Option;
|
||||||
|
use std::option::None as __None;
|
||||||
|
|
||||||
|
__thread_local_inner!(static __KEY: __UnsafeCell<__Option<$t>> = {
|
||||||
|
__UnsafeCell { value: __None }
|
||||||
|
})
|
||||||
|
fn __init() -> $t { $init }
|
||||||
|
fn __getit() -> &'static __KeyInner<__UnsafeCell<__Option<$t>>> {
|
||||||
|
&__KEY
|
||||||
|
}
|
||||||
|
::std::thread_local::Key { inner: __getit, init: __init }
|
||||||
|
};
|
||||||
|
);
|
||||||
|
(pub static $name:ident: $t:ty = $init:expr) => (
|
||||||
|
pub static $name: ::std::thread_local::Key<$t> = {
|
||||||
|
use std::cell::UnsafeCell as __UnsafeCell;
|
||||||
|
use std::thread_local::KeyInner as __KeyInner;
|
||||||
|
use std::option::Option as __Option;
|
||||||
|
use std::option::None as __None;
|
||||||
|
|
||||||
|
__thread_local_inner!(static __KEY: __UnsafeCell<__Option<$t>> = {
|
||||||
|
__UnsafeCell { value: __None }
|
||||||
|
})
|
||||||
|
fn __init() -> $t { $init }
|
||||||
|
fn __getit() -> &'static __KeyInner<__UnsafeCell<__Option<$t>>> {
|
||||||
|
&__KEY
|
||||||
|
}
|
||||||
|
::std::thread_local::Key { inner: __getit, init: __init }
|
||||||
|
};
|
||||||
|
);
|
||||||
|
)
|
||||||
|
|
||||||
|
// Macro pain #4586:
|
||||||
|
//
|
||||||
|
// When cross compiling, rustc will load plugins and macros from the *host*
|
||||||
|
// platform before search for macros from the target platform. This is primarily
|
||||||
|
// done to detect, for example, plugins. Ideally the macro below would be
|
||||||
|
// defined once per module below, but unfortunately this means we have the
|
||||||
|
// following situation:
|
||||||
|
//
|
||||||
|
// 1. We compile libstd for x86_64-unknown-linux-gnu, this thread_local!() macro
|
||||||
|
// will inject #[thread_local] statics.
|
||||||
|
// 2. We then try to compile a program for arm-linux-androideabi
|
||||||
|
// 3. The compiler has a host of linux and a target of android, so it loads
|
||||||
|
// macros from the *linux* libstd.
|
||||||
|
// 4. The macro generates a #[thread_local] field, but the android libstd does
|
||||||
|
// not use #[thread_local]
|
||||||
|
// 5. Compile error about structs with wrong fields.
|
||||||
|
//
|
||||||
|
// To get around this, we're forced to inject the #[cfg] logic into the macro
|
||||||
|
// itself. Woohoo.
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! __thread_local_inner(
|
||||||
|
(static $name:ident: $t:ty = $init:expr) => (
|
||||||
|
#[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
|
||||||
|
static $name: ::std::thread_local::KeyInner<$t> =
|
||||||
|
__thread_local_inner!($init, $t);
|
||||||
|
);
|
||||||
|
(pub static $name:ident: $t:ty = $init:expr) => (
|
||||||
|
#[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
|
||||||
|
pub static $name: ::std::thread_local::KeyInner<$t> =
|
||||||
|
__thread_local_inner!($init, $t);
|
||||||
|
);
|
||||||
|
($init:expr, $t:ty) => ({
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
const INIT: ::std::thread_local::KeyInner<$t> = {
|
||||||
|
::std::thread_local::KeyInner {
|
||||||
|
inner: ::std::cell::UnsafeCell { value: $init },
|
||||||
|
dtor_registered: ::std::cell::UnsafeCell { value: false },
|
||||||
|
dtor_running: ::std::cell::UnsafeCell { value: false },
|
||||||
|
marker: ::std::kinds::marker::NoCopy,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
|
const INIT: ::std::thread_local::KeyInner<$t> = {
|
||||||
|
unsafe extern fn __destroy(ptr: *mut u8) {
|
||||||
|
::std::thread_local::destroy_value::<$t>(ptr);
|
||||||
|
}
|
||||||
|
::std::thread_local::KeyInner {
|
||||||
|
inner: ::std::cell::UnsafeCell { value: $init },
|
||||||
|
os: ::std::thread_local::OsStaticKey {
|
||||||
|
inner: ::std::thread_local::OS_INIT_INNER,
|
||||||
|
dtor: ::std::option::Some(__destroy),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
INIT
|
||||||
|
});
|
||||||
|
)
|
||||||
|
|
||||||
|
impl<T: 'static> Key<T> {
|
||||||
|
/// Acquire a reference to the value in this TLS key.
|
||||||
|
///
|
||||||
|
/// This will lazily initialize the value if this thread has not referenced
|
||||||
|
/// this key yet.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will `panic!()` if the key currently has its
|
||||||
|
/// destructor running, and it **may** panic if the destructor has
|
||||||
|
/// previously been run for this thread.
|
||||||
|
pub fn with<R>(&'static self, f: |&T| -> R) -> R {
|
||||||
|
let slot = (self.inner)();
|
||||||
|
unsafe {
|
||||||
|
let slot = slot.get().expect("cannot access a TLS value during or \
|
||||||
|
after it is destroyed");
|
||||||
|
if (*slot.get()).is_none() {
|
||||||
|
*slot.get() = Some((self.init)());
|
||||||
|
}
|
||||||
|
f((*slot.get()).as_ref().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test this TLS key to determine whether its value has been destroyed for
|
||||||
|
/// the current thread or not.
|
||||||
|
///
|
||||||
|
/// This will not initialize the key if it is not already initialized.
|
||||||
|
pub fn destroyed(&'static self) -> bool {
|
||||||
|
unsafe { (self.inner)().get().is_none() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
mod imp {
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use cell::UnsafeCell;
|
||||||
|
use intrinsics;
|
||||||
|
use kinds::marker;
|
||||||
|
use ptr;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Key<T> {
|
||||||
|
// Place the inner bits in an `UnsafeCell` to currently get around the
|
||||||
|
// "only Sync statics" restriction. This allows any type to be placed in
|
||||||
|
// the cell.
|
||||||
|
//
|
||||||
|
// Note that all access requires `T: 'static` so it can't be a type with
|
||||||
|
// any borrowed pointers still.
|
||||||
|
pub inner: UnsafeCell<T>,
|
||||||
|
|
||||||
|
// Metadata to keep track of the state of the destructor. Remember that
|
||||||
|
// these variables are thread-local, not global.
|
||||||
|
pub dtor_registered: UnsafeCell<bool>, // should be Cell
|
||||||
|
pub dtor_running: UnsafeCell<bool>, // should be Cell
|
||||||
|
|
||||||
|
// These shouldn't be copied around.
|
||||||
|
pub marker: marker::NoCopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<T> Key<T> {
|
||||||
|
pub unsafe fn get(&'static self) -> Option<&'static T> {
|
||||||
|
if intrinsics::needs_drop::<T>() && *self.dtor_running.get() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
self.register_dtor();
|
||||||
|
Some(&*self.inner.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn register_dtor(&self) {
|
||||||
|
if !intrinsics::needs_drop::<T>() || *self.dtor_registered.get() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
register_dtor(self as *const _ as *mut u8,
|
||||||
|
destroy_value::<T>);
|
||||||
|
*self.dtor_registered.get() = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since what appears to be glibc 2.18 this symbol has been shipped which
|
||||||
|
// GCC and clang both use to invoke destructors in thread_local globals, so
|
||||||
|
// let's do the same!
|
||||||
|
//
|
||||||
|
// Note, however, that we run on lots older linuxes, as well as cross
|
||||||
|
// compiling from a newer linux to an older linux, so we also have a
|
||||||
|
// fallback implementation to use as well.
|
||||||
|
//
|
||||||
|
// Due to rust-lang/rust#18804, make sure this is not generic!
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
|
||||||
|
use mem;
|
||||||
|
use libc;
|
||||||
|
use sys_common::thread_local as os;
|
||||||
|
|
||||||
|
extern {
|
||||||
|
static __dso_handle: *mut u8;
|
||||||
|
#[linkage = "extern_weak"]
|
||||||
|
static __cxa_thread_atexit_impl: *const ();
|
||||||
|
}
|
||||||
|
if !__cxa_thread_atexit_impl.is_null() {
|
||||||
|
type F = unsafe extern fn(dtor: unsafe extern fn(*mut u8),
|
||||||
|
arg: *mut u8,
|
||||||
|
dso_handle: *mut u8) -> libc::c_int;
|
||||||
|
mem::transmute::<*const (), F>(__cxa_thread_atexit_impl)
|
||||||
|
(dtor, t, __dso_handle);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fallback implementation uses a vanilla OS-based TLS key to track
|
||||||
|
// the list of destructors that need to be run for this thread. The key
|
||||||
|
// then has its own destructor which runs all the other destructors.
|
||||||
|
//
|
||||||
|
// The destructor for DTORS is a little special in that it has a `while`
|
||||||
|
// loop to continuously drain the list of registered destructors. It
|
||||||
|
// *should* be the case that this loop always terminates because we
|
||||||
|
// provide the guarantee that a TLS key cannot be set after it is
|
||||||
|
// flagged for destruction.
|
||||||
|
static DTORS: os::StaticKey = os::StaticKey {
|
||||||
|
inner: os::INIT_INNER,
|
||||||
|
dtor: Some(run_dtors),
|
||||||
|
};
|
||||||
|
type List = Vec<(*mut u8, unsafe extern fn(*mut u8))>;
|
||||||
|
if DTORS.get().is_null() {
|
||||||
|
let v: Box<List> = box Vec::new();
|
||||||
|
DTORS.set(mem::transmute(v));
|
||||||
|
}
|
||||||
|
let list: &mut List = &mut *(DTORS.get() as *mut List);
|
||||||
|
list.push((t, dtor));
|
||||||
|
|
||||||
|
unsafe extern fn run_dtors(mut ptr: *mut u8) {
|
||||||
|
while !ptr.is_null() {
|
||||||
|
let list: Box<List> = mem::transmute(ptr);
|
||||||
|
for &(ptr, dtor) in list.iter() {
|
||||||
|
dtor(ptr);
|
||||||
|
}
|
||||||
|
ptr = DTORS.get();
|
||||||
|
DTORS.set(0 as *mut _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSX's analog of the above linux function is this _tlv_atexit function.
|
||||||
|
// The disassembly of thread_local globals in C++ (at least produced by
|
||||||
|
// clang) will have this show up in the output.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
|
||||||
|
extern {
|
||||||
|
fn _tlv_atexit(dtor: unsafe extern fn(*mut u8),
|
||||||
|
arg: *mut u8);
|
||||||
|
}
|
||||||
|
_tlv_atexit(dtor, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe extern fn destroy_value<T>(ptr: *mut u8) {
|
||||||
|
let ptr = ptr as *mut Key<T>;
|
||||||
|
// Right before we run the user destructor be sure to flag the
|
||||||
|
// destructor as running for this thread so calls to `get` will return
|
||||||
|
// `None`.
|
||||||
|
*(*ptr).dtor_running.get() = true;
|
||||||
|
ptr::read((*ptr).inner.get() as *const T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
|
mod imp {
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use cell::UnsafeCell;
|
||||||
|
use mem;
|
||||||
|
use sys_common::thread_local::StaticKey as OsStaticKey;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Key<T> {
|
||||||
|
// Statically allocated initialization expression, using an `UnsafeCell`
|
||||||
|
// for the same reasons as above.
|
||||||
|
pub inner: UnsafeCell<T>,
|
||||||
|
|
||||||
|
// OS-TLS key that we'll use to key off.
|
||||||
|
pub os: OsStaticKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Value<T: 'static> {
|
||||||
|
key: &'static Key<T>,
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<T> Key<T> {
|
||||||
|
pub unsafe fn get(&'static self) -> Option<&'static T> {
|
||||||
|
self.ptr().map(|p| &*p)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn ptr(&'static self) -> Option<*mut T> {
|
||||||
|
let ptr = self.os.get() as *mut Value<T>;
|
||||||
|
if !ptr.is_null() {
|
||||||
|
if ptr as uint == 1 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
return Some(&mut (*ptr).value as *mut T);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the lookup returned null, we haven't initialized our own local
|
||||||
|
// copy, so do that now.
|
||||||
|
//
|
||||||
|
// Also note that this transmute_copy should be ok because the value
|
||||||
|
// `inner` is already validated to be a valid `static` value, so we
|
||||||
|
// should be able to freely copy the bits.
|
||||||
|
let ptr: Box<Value<T>> = box Value {
|
||||||
|
key: self,
|
||||||
|
value: mem::transmute_copy(&self.inner),
|
||||||
|
};
|
||||||
|
let ptr: *mut Value<T> = mem::transmute(ptr);
|
||||||
|
self.os.set(ptr as *mut u8);
|
||||||
|
Some(&mut (*ptr).value as *mut T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe extern fn destroy_value<T: 'static>(ptr: *mut u8) {
|
||||||
|
// The OS TLS ensures that this key contains a NULL value when this
|
||||||
|
// destructor starts to run. We set it back to a sentinel value of 1 to
|
||||||
|
// ensure that any future calls to `get` for this thread will return
|
||||||
|
// `None`.
|
||||||
|
//
|
||||||
|
// Note that to prevent an infinite loop we reset it back to null right
|
||||||
|
// before we return from the destructor ourselves.
|
||||||
|
let ptr: Box<Value<T>> = mem::transmute(ptr);
|
||||||
|
let key = ptr.key;
|
||||||
|
key.os.set(1 as *mut u8);
|
||||||
|
drop(ptr);
|
||||||
|
key.os.set(0 as *mut u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use cell::UnsafeCell;
|
||||||
|
use rustrt::thread::Thread;
|
||||||
|
|
||||||
|
struct Foo(Sender<()>);
|
||||||
|
|
||||||
|
impl Drop for Foo {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let Foo(ref s) = *self;
|
||||||
|
s.send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke_no_dtor() {
|
||||||
|
thread_local!(static FOO: UnsafeCell<int> = UnsafeCell { value: 1 })
|
||||||
|
|
||||||
|
FOO.with(|f| unsafe {
|
||||||
|
assert_eq!(*f.get(), 1);
|
||||||
|
*f.get() = 2;
|
||||||
|
});
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
spawn(proc() {
|
||||||
|
FOO.with(|f| unsafe {
|
||||||
|
assert_eq!(*f.get(), 1);
|
||||||
|
});
|
||||||
|
tx.send(());
|
||||||
|
});
|
||||||
|
rx.recv();
|
||||||
|
|
||||||
|
FOO.with(|f| unsafe {
|
||||||
|
assert_eq!(*f.get(), 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke_dtor() {
|
||||||
|
thread_local!(static FOO: UnsafeCell<Option<Foo>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
spawn(proc() unsafe {
|
||||||
|
let mut tx = Some(tx);
|
||||||
|
FOO.with(|f| {
|
||||||
|
*f.get() = Some(Foo(tx.take().unwrap()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
rx.recv();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn circular() {
|
||||||
|
struct S1;
|
||||||
|
struct S2;
|
||||||
|
thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
thread_local!(static K2: UnsafeCell<Option<S2>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
static mut HITS: uint = 0;
|
||||||
|
|
||||||
|
impl Drop for S1 {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
HITS += 1;
|
||||||
|
if K2.destroyed() {
|
||||||
|
assert_eq!(HITS, 3);
|
||||||
|
} else {
|
||||||
|
if HITS == 1 {
|
||||||
|
K2.with(|s| *s.get() = Some(S2));
|
||||||
|
} else {
|
||||||
|
assert_eq!(HITS, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for S2 {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
HITS += 1;
|
||||||
|
assert!(!K1.destroyed());
|
||||||
|
assert_eq!(HITS, 2);
|
||||||
|
K1.with(|s| *s.get() = Some(S1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread::start(proc() {
|
||||||
|
drop(S1);
|
||||||
|
}).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn self_referential() {
|
||||||
|
struct S1;
|
||||||
|
thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
|
||||||
|
impl Drop for S1 {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
assert!(K1.destroyed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread::start(proc() unsafe {
|
||||||
|
K1.with(|s| *s.get() = Some(S1));
|
||||||
|
}).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dtors_in_dtors_in_dtors() {
|
||||||
|
struct S1(Sender<()>);
|
||||||
|
thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
thread_local!(static K2: UnsafeCell<Option<Foo>> = UnsafeCell {
|
||||||
|
value: None
|
||||||
|
})
|
||||||
|
|
||||||
|
impl Drop for S1 {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let S1(ref tx) = *self;
|
||||||
|
unsafe {
|
||||||
|
if !K2.destroyed() {
|
||||||
|
K2.with(|s| *s.get() = Some(Foo(tx.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
spawn(proc() unsafe {
|
||||||
|
let mut tx = Some(tx);
|
||||||
|
K1.with(|s| *s.get() = Some(S1(tx.take().unwrap())));
|
||||||
|
});
|
||||||
|
rx.recv();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod dynamic_tests {
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use cell::RefCell;
|
||||||
|
use collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke() {
|
||||||
|
fn square(i: int) -> int { i * i }
|
||||||
|
thread_local!(static FOO: int = square(3))
|
||||||
|
|
||||||
|
FOO.with(|f| {
|
||||||
|
assert_eq!(*f, 9);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hashmap() {
|
||||||
|
fn map() -> RefCell<HashMap<int, int>> {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert(1, 2);
|
||||||
|
RefCell::new(m)
|
||||||
|
}
|
||||||
|
thread_local!(static FOO: RefCell<HashMap<int, int>> = map())
|
||||||
|
|
||||||
|
FOO.with(|map| {
|
||||||
|
assert_eq!(map.borrow()[1], 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refcell_vec() {
|
||||||
|
thread_local!(static FOO: RefCell<Vec<uint>> = RefCell::new(vec![1, 2, 3]))
|
||||||
|
|
||||||
|
FOO.with(|vec| {
|
||||||
|
assert_eq!(vec.borrow().len(), 3);
|
||||||
|
vec.borrow_mut().push(4);
|
||||||
|
assert_eq!(vec.borrow()[3], 4);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
261
src/libstd/thread_local/scoped.rs
Normal file
261
src/libstd/thread_local/scoped.rs
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
//! Scoped thread-local storage
|
||||||
|
//!
|
||||||
|
//! This module provides the ability to generate *scoped* thread-local
|
||||||
|
//! variables. In this sense, scoped indicates that thread local storage
|
||||||
|
//! actually stores a reference to a value, and this reference is only placed
|
||||||
|
//! in storage for a scoped amount of time.
|
||||||
|
//!
|
||||||
|
//! There are no restrictions on what types can be placed into a scoped
|
||||||
|
//! variable, but all scoped variables are initialized to the equivalent of
|
||||||
|
//! null. Scoped thread local stor is useful when a value is present for a known
|
||||||
|
//! period of time and it is not required to relinquish ownership of the
|
||||||
|
//! contents.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! scoped_thread_local!(static FOO: uint)
|
||||||
|
//!
|
||||||
|
//! // Initially each scoped slot is empty.
|
||||||
|
//! assert!(!FOO.is_set());
|
||||||
|
//!
|
||||||
|
//! // When inserting a value, the value is only in place for the duration
|
||||||
|
//! // of the closure specified.
|
||||||
|
//! FOO.set(&1, || {
|
||||||
|
//! FOO.with(|slot| {
|
||||||
|
//! assert_eq!(*slot, 1);
|
||||||
|
//! });
|
||||||
|
//! });
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![macro_escape]
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
// macro hygiene sure would be nice, wouldn't it?
|
||||||
|
#[doc(hidden)] pub use self::imp::KeyInner;
|
||||||
|
#[doc(hidden)] pub use sys_common::thread_local::INIT as OS_INIT;
|
||||||
|
|
||||||
|
/// Type representing a thread local storage key corresponding to a reference
|
||||||
|
/// to the type parameter `T`.
|
||||||
|
///
|
||||||
|
/// Keys are statically allocated and can contain a reference to an instance of
|
||||||
|
/// type `T` scoped to a particular lifetime. Keys provides two methods, `set`
|
||||||
|
/// and `with`, both of which currently use closures to control the scope of
|
||||||
|
/// their contents.
|
||||||
|
pub struct Key<T> { #[doc(hidden)] pub inner: KeyInner<T> }
|
||||||
|
|
||||||
|
/// Declare a new scoped thread local storage key.
|
||||||
|
///
|
||||||
|
/// This macro declares a `static` item on which methods are used to get and
|
||||||
|
/// set the value stored within.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! scoped_thread_local(
|
||||||
|
(static $name:ident: $t:ty) => (
|
||||||
|
__scoped_thread_local_inner!(static $name: $t)
|
||||||
|
);
|
||||||
|
(pub static $name:ident: $t:ty) => (
|
||||||
|
__scoped_thread_local_inner!(pub static $name: $t)
|
||||||
|
);
|
||||||
|
)
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
|
macro_rules! __scoped_thread_local_inner(
|
||||||
|
(static $name:ident: $t:ty) => (
|
||||||
|
#[cfg_attr(not(any(windows, target_os = "android", target_os = "ios")),
|
||||||
|
thread_local)]
|
||||||
|
static $name: ::std::thread_local::scoped::Key<$t> =
|
||||||
|
__scoped_thread_local_inner!($t);
|
||||||
|
);
|
||||||
|
(pub static $name:ident: $t:ty) => (
|
||||||
|
#[cfg_attr(not(any(windows, target_os = "android", target_os = "ios")),
|
||||||
|
thread_local)]
|
||||||
|
pub static $name: ::std::thread_local::scoped::Key<$t> =
|
||||||
|
__scoped_thread_local_inner!($t);
|
||||||
|
);
|
||||||
|
($t:ty) => ({
|
||||||
|
use std::thread_local::scoped::Key as __Key;
|
||||||
|
|
||||||
|
#[cfg(not(any(windows, target_os = "android", target_os = "ios")))]
|
||||||
|
const INIT: __Key<$t> = __Key {
|
||||||
|
inner: ::std::thread_local::scoped::KeyInner {
|
||||||
|
inner: ::std::cell::UnsafeCell { value: 0 as *mut _ },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(any(windows, target_os = "android", target_os = "ios"))]
|
||||||
|
const INIT: __Key<$t> = __Key {
|
||||||
|
inner: ::std::thread_local::scoped::KeyInner {
|
||||||
|
inner: ::std::thread_local::scoped::OS_INIT,
|
||||||
|
marker: ::std::kinds::marker::InvariantType,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
INIT
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
impl<T> Key<T> {
|
||||||
|
/// Insert a value into this scoped thread local storage slot for a
|
||||||
|
/// duration of a closure.
|
||||||
|
///
|
||||||
|
/// While `cb` is running, the value `t` will be returned by `get` unless
|
||||||
|
/// this function is called recursively inside of `cb`.
|
||||||
|
///
|
||||||
|
/// Upon return, this function will restore the previous value, if any
|
||||||
|
/// was available.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// scoped_thread_local!(static FOO: uint)
|
||||||
|
///
|
||||||
|
/// FOO.set(&100, || {
|
||||||
|
/// let val = FOO.with(|v| *v);
|
||||||
|
/// assert_eq!(val, 100);
|
||||||
|
///
|
||||||
|
/// // set can be called recursively
|
||||||
|
/// FOO.set(&101, || {
|
||||||
|
/// // ...
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Recursive calls restore the previous value.
|
||||||
|
/// let val = FOO.with(|v| *v);
|
||||||
|
/// assert_eq!(val, 100);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn set<R>(&'static self, t: &T, cb: || -> R) -> R {
|
||||||
|
struct Reset<'a, T: 'a> {
|
||||||
|
key: &'a KeyInner<T>,
|
||||||
|
val: *mut T,
|
||||||
|
}
|
||||||
|
#[unsafe_destructor]
|
||||||
|
impl<'a, T> Drop for Reset<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.key.set(self.val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev = unsafe {
|
||||||
|
let prev = self.inner.get();
|
||||||
|
self.inner.set(t as *const T as *mut T);
|
||||||
|
prev
|
||||||
|
};
|
||||||
|
|
||||||
|
let _reset = Reset { key: &self.inner, val: prev };
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a value out of this scoped variable.
|
||||||
|
///
|
||||||
|
/// This function takes a closure which receives the value of this
|
||||||
|
/// variable.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if `set` has not previously been called.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// scoped_thread_local!(static FOO: uint)
|
||||||
|
///
|
||||||
|
/// FOO.with(|slot| {
|
||||||
|
/// // work with `slot`
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn with<R>(&'static self, cb: |&T| -> R) -> R {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.inner.get();
|
||||||
|
assert!(!ptr.is_null(), "cannot access a scoped thread local \
|
||||||
|
variable without calling `set` first");
|
||||||
|
cb(&*ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test whether this TLS key has been `set` for the current thread.
|
||||||
|
pub fn is_set(&'static self) -> bool {
|
||||||
|
unsafe { !self.inner.get().is_null() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(windows, target_os = "android", target_os = "ios")))]
|
||||||
|
mod imp {
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
|
||||||
|
// FIXME: Should be a `Cell`, but that's not `Sync`
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct KeyInner<T> { pub inner: UnsafeCell<*mut T> }
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<T> KeyInner<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe fn set(&self, ptr: *mut T) { *self.inner.get() = ptr; }
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe fn get(&self) -> *mut T { *self.inner.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(windows, target_os = "android", target_os = "ios"))]
|
||||||
|
mod imp {
|
||||||
|
use kinds::marker;
|
||||||
|
use sys_common::thread_local::StaticKey as OsStaticKey;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct KeyInner<T> {
|
||||||
|
pub inner: OsStaticKey,
|
||||||
|
pub marker: marker::InvariantType<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<T> KeyInner<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe fn set(&self, ptr: *mut T) { self.inner.set(ptr as *mut _) }
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub unsafe fn get(&self) -> *mut T { self.inner.get() as *mut _ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use cell::Cell;
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke() {
|
||||||
|
scoped_thread_local!(static BAR: uint)
|
||||||
|
|
||||||
|
assert!(!BAR.is_set());
|
||||||
|
BAR.set(&1, || {
|
||||||
|
assert!(BAR.is_set());
|
||||||
|
BAR.with(|slot| {
|
||||||
|
assert_eq!(*slot, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
assert!(!BAR.is_set());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cell_allowed() {
|
||||||
|
scoped_thread_local!(static BAR: Cell<uint>)
|
||||||
|
|
||||||
|
BAR.set(&Cell::new(1), || {
|
||||||
|
BAR.with(|slot| {
|
||||||
|
assert_eq!(slot.get(), 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,21 +25,20 @@ use parse::token::InternedString;
|
||||||
use parse::token;
|
use parse::token;
|
||||||
use ptr::P;
|
use ptr::P;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::cell::{RefCell, Cell};
|
||||||
use std::collections::BitvSet;
|
use std::collections::BitvSet;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
local_data_key!(used_attrs: BitvSet)
|
thread_local!(static USED_ATTRS: RefCell<BitvSet> = RefCell::new(BitvSet::new()))
|
||||||
|
|
||||||
pub fn mark_used(attr: &Attribute) {
|
pub fn mark_used(attr: &Attribute) {
|
||||||
let mut used = used_attrs.replace(None).unwrap_or_else(|| BitvSet::new());
|
|
||||||
let AttrId(id) = attr.node.id;
|
let AttrId(id) = attr.node.id;
|
||||||
used.insert(id);
|
USED_ATTRS.with(|slot| slot.borrow_mut().insert(id));
|
||||||
used_attrs.replace(Some(used));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_used(attr: &Attribute) -> bool {
|
pub fn is_used(attr: &Attribute) -> bool {
|
||||||
let AttrId(id) = attr.node.id;
|
let AttrId(id) = attr.node.id;
|
||||||
used_attrs.get().map_or(false, |used| used.contains(&id))
|
USED_ATTRS.with(|slot| slot.borrow().contains(&id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AttrMetaMethods {
|
pub trait AttrMetaMethods {
|
||||||
|
@ -167,11 +166,14 @@ pub fn mk_word_item(name: InternedString) -> P<MetaItem> {
|
||||||
P(dummy_spanned(MetaWord(name)))
|
P(dummy_spanned(MetaWord(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
local_data_key!(next_attr_id: uint)
|
thread_local!(static NEXT_ATTR_ID: Cell<uint> = Cell::new(0))
|
||||||
|
|
||||||
pub fn mk_attr_id() -> AttrId {
|
pub fn mk_attr_id() -> AttrId {
|
||||||
let id = next_attr_id.replace(None).unwrap_or(0);
|
let id = NEXT_ATTR_ID.with(|slot| {
|
||||||
next_attr_id.replace(Some(id + 1));
|
let r = slot.get();
|
||||||
|
slot.set(r + 1);
|
||||||
|
r
|
||||||
|
});
|
||||||
AttrId(id)
|
AttrId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,31 +18,23 @@ use ext::build::AstBuilder;
|
||||||
use parse::token;
|
use parse::token;
|
||||||
use ptr::P;
|
use ptr::P;
|
||||||
|
|
||||||
local_data_key!(registered_diagnostics: RefCell<HashMap<Name, Option<Name>>>)
|
thread_local!(static REGISTERED_DIAGNOSTICS: RefCell<HashMap<Name, Option<Name>>> = {
|
||||||
local_data_key!(used_diagnostics: RefCell<HashMap<Name, Span>>)
|
RefCell::new(HashMap::new())
|
||||||
|
})
|
||||||
|
thread_local!(static USED_DIAGNOSTICS: RefCell<HashMap<Name, Span>> = {
|
||||||
|
RefCell::new(HashMap::new())
|
||||||
|
})
|
||||||
|
|
||||||
fn with_registered_diagnostics<T>(f: |&mut HashMap<Name, Option<Name>>| -> T) -> T {
|
fn with_registered_diagnostics<T>(f: |&mut HashMap<Name, Option<Name>>| -> T) -> T {
|
||||||
match registered_diagnostics.get() {
|
REGISTERED_DIAGNOSTICS.with(|slot| {
|
||||||
Some(cell) => f(cell.borrow_mut().deref_mut()),
|
f(&mut *slot.borrow_mut())
|
||||||
None => {
|
})
|
||||||
let mut map = HashMap::new();
|
|
||||||
let value = f(&mut map);
|
|
||||||
registered_diagnostics.replace(Some(RefCell::new(map)));
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_used_diagnostics<T>(f: |&mut HashMap<Name, Span>| -> T) -> T {
|
fn with_used_diagnostics<T>(f: |&mut HashMap<Name, Span>| -> T) -> T {
|
||||||
match used_diagnostics.get() {
|
USED_DIAGNOSTICS.with(|slot| {
|
||||||
Some(cell) => f(cell.borrow_mut().deref_mut()),
|
f(&mut *slot.borrow_mut())
|
||||||
None => {
|
})
|
||||||
let mut map = HashMap::new();
|
|
||||||
let value = f(&mut map);
|
|
||||||
used_diagnostics.replace(Some(RefCell::new(map)));
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
|
pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
|
||||||
|
|
|
@ -20,7 +20,6 @@ pub use self::SyntaxContext_::*;
|
||||||
use ast::{Ident, Mrk, Name, SyntaxContext};
|
use ast::{Ident, Mrk, Name, SyntaxContext};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::{Occupied, Vacant};
|
use std::collections::hash_map::{Occupied, Vacant};
|
||||||
|
|
||||||
|
@ -105,16 +104,8 @@ pub fn apply_renames(renames: &RenameList, ctxt: SyntaxContext) -> SyntaxContext
|
||||||
|
|
||||||
/// Fetch the SCTable from TLS, create one if it doesn't yet exist.
|
/// Fetch the SCTable from TLS, create one if it doesn't yet exist.
|
||||||
pub fn with_sctable<T>(op: |&SCTable| -> T) -> T {
|
pub fn with_sctable<T>(op: |&SCTable| -> T) -> T {
|
||||||
local_data_key!(sctable_key: Rc<SCTable>)
|
thread_local!(static SCTABLE_KEY: SCTable = new_sctable_internal())
|
||||||
|
SCTABLE_KEY.with(|slot| op(slot))
|
||||||
match sctable_key.get() {
|
|
||||||
Some(ts) => op(&**ts),
|
|
||||||
None => {
|
|
||||||
let ts = Rc::new(new_sctable_internal());
|
|
||||||
sctable_key.replace(Some(ts.clone()));
|
|
||||||
op(&*ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a fresh syntax context table with EmptyCtxt in slot zero
|
// Make a fresh syntax context table with EmptyCtxt in slot zero
|
||||||
|
@ -165,16 +156,11 @@ type ResolveTable = HashMap<(Name,SyntaxContext),Name>;
|
||||||
// okay, I admit, putting this in TLS is not so nice:
|
// okay, I admit, putting this in TLS is not so nice:
|
||||||
// fetch the SCTable from TLS, create one if it doesn't yet exist.
|
// fetch the SCTable from TLS, create one if it doesn't yet exist.
|
||||||
fn with_resolve_table_mut<T>(op: |&mut ResolveTable| -> T) -> T {
|
fn with_resolve_table_mut<T>(op: |&mut ResolveTable| -> T) -> T {
|
||||||
local_data_key!(resolve_table_key: Rc<RefCell<ResolveTable>>)
|
thread_local!(static RESOLVE_TABLE_KEY: RefCell<ResolveTable> = {
|
||||||
|
RefCell::new(HashMap::new())
|
||||||
|
})
|
||||||
|
|
||||||
match resolve_table_key.get() {
|
RESOLVE_TABLE_KEY.with(|slot| op(&mut *slot.borrow_mut()))
|
||||||
Some(ts) => op(&mut *ts.borrow_mut()),
|
|
||||||
None => {
|
|
||||||
let ts = Rc::new(RefCell::new(HashMap::new()));
|
|
||||||
resolve_table_key.replace(Some(ts.clone()));
|
|
||||||
op(&mut *ts.borrow_mut())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a syntax object to a name, per MTWT.
|
/// Resolve a syntax object to a name, per MTWT.
|
||||||
|
|
|
@ -560,15 +560,10 @@ pub type IdentInterner = StrInterner;
|
||||||
// fresh one.
|
// fresh one.
|
||||||
// FIXME(eddyb) #8726 This should probably use a task-local reference.
|
// FIXME(eddyb) #8726 This should probably use a task-local reference.
|
||||||
pub fn get_ident_interner() -> Rc<IdentInterner> {
|
pub fn get_ident_interner() -> Rc<IdentInterner> {
|
||||||
local_data_key!(key: Rc<::parse::token::IdentInterner>)
|
thread_local!(static KEY: Rc<::parse::token::IdentInterner> = {
|
||||||
match key.get() {
|
Rc::new(mk_fresh_ident_interner())
|
||||||
Some(interner) => interner.clone(),
|
})
|
||||||
None => {
|
KEY.with(|k| k.clone())
|
||||||
let interner = Rc::new(mk_fresh_ident_interner());
|
|
||||||
key.replace(Some(interner.clone()));
|
|
||||||
interner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a string stored in the task-local interner. Because the
|
/// Represents a string stored in the task-local interner. Because the
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
extern crate rustc;
|
extern crate rustc;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::cell::RefCell;
|
||||||
use rustc::plugin::Registry;
|
use rustc::plugin::Registry;
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
|
@ -27,7 +28,7 @@ impl Drop for Foo {
|
||||||
|
|
||||||
#[plugin_registrar]
|
#[plugin_registrar]
|
||||||
pub fn registrar(_: &mut Registry) {
|
pub fn registrar(_: &mut Registry) {
|
||||||
local_data_key!(foo: Box<Any+Send>);
|
thread_local!(static FOO: RefCell<Option<Box<Any+Send>>> = RefCell::new(None));
|
||||||
foo.replace(Some(box Foo { foo: 10 } as Box<Any+Send>));
|
FOO.with(|s| *s.borrow_mut() = Some(box Foo { foo: 10 } as Box<Any+Send>));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright 2012 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.
|
|
||||||
|
|
||||||
// Testing that we can't store a reference in task-local storage
|
|
||||||
|
|
||||||
local_data_key!(key: Box<&int>)
|
|
||||||
//~^ ERROR missing lifetime specifier
|
|
||||||
|
|
||||||
fn main() {}
|
|
|
@ -11,10 +11,10 @@
|
||||||
// check that the local data keys are private by default.
|
// check that the local data keys are private by default.
|
||||||
|
|
||||||
mod bar {
|
mod bar {
|
||||||
local_data_key!(baz: f64)
|
thread_local!(static baz: f64 = 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
bar::baz.replace(Some(-10.0));
|
bar::baz.with(|_| ());
|
||||||
//~^ ERROR static `baz` is private
|
//~^ ERROR static `baz` is private
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright 2013 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.
|
|
||||||
|
|
||||||
local_data_key!(foo: int)
|
|
||||||
|
|
||||||
mod bar {
|
|
||||||
local_data_key!(pub baz: f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
assert!(foo.get().is_none());
|
|
||||||
assert!(bar::baz.get().is_none());
|
|
||||||
|
|
||||||
foo.replace(Some(3));
|
|
||||||
bar::baz.replace(Some(-10.0));
|
|
||||||
|
|
||||||
assert_eq!(*foo.get().unwrap(), 3);
|
|
||||||
assert_eq!(*bar::baz.get().unwrap(), -10.0);
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
use std::task;
|
|
||||||
|
|
||||||
static mut DROPS: uint = 0;
|
|
||||||
|
|
||||||
struct Foo;
|
|
||||||
impl Drop for Foo {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { DROPS += 1; }
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let _ = task::try(proc() {
|
|
||||||
local_data_key!(foo: Foo);
|
|
||||||
foo.replace(Some(Foo));
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
assert_eq!(DROPS, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ use std::rt;
|
||||||
|
|
||||||
use rustrt::unwind::try;
|
use rustrt::unwind::try;
|
||||||
|
|
||||||
local_data_key!(foo: int)
|
|
||||||
|
|
||||||
#[start]
|
#[start]
|
||||||
fn start(argc: int, argv: *const *const u8) -> int {
|
fn start(argc: int, argv: *const *const u8) -> int {
|
||||||
if argc > 1 {
|
if argc > 1 {
|
||||||
|
@ -30,8 +28,6 @@ fn start(argc: int, argv: *const *const u8) -> int {
|
||||||
4 => assert!(try(|| panic!()).is_err()),
|
4 => assert!(try(|| panic!()).is_err()),
|
||||||
5 => assert!(try(|| spawn(proc() {})).is_err()),
|
5 => assert!(try(|| spawn(proc() {})).is_err()),
|
||||||
6 => assert!(Command::new("test").spawn().is_err()),
|
6 => assert!(Command::new("test").spawn().is_err()),
|
||||||
7 => assert!(foo.get().is_none()),
|
|
||||||
8 => assert!(try(|| { foo.replace(Some(3)); }).is_err()),
|
|
||||||
_ => panic!()
|
_ => panic!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,10 +53,6 @@ fn main() {
|
||||||
pass(Command::new(me).arg(x).output().unwrap());
|
pass(Command::new(me).arg(x).output().unwrap());
|
||||||
let x: &[u8] = &[6u8];
|
let x: &[u8] = &[6u8];
|
||||||
pass(Command::new(me).arg(x).output().unwrap());
|
pass(Command::new(me).arg(x).output().unwrap());
|
||||||
let x: &[u8] = &[7u8];
|
|
||||||
pass(Command::new(me).arg(x).output().unwrap());
|
|
||||||
let x: &[u8] = &[8u8];
|
|
||||||
pass(Command::new(me).arg(x).output().unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pass(output: ProcessOutput) {
|
fn pass(output: ProcessOutput) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue