From: Alex Crichton Date: Fri, 14 Nov 2014 22:20:57 +0000 (-0800) Subject: std: Add a new top-level thread_local module X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=a9c1152c4bf72132806cb76045b3464d59db07da;p=rust.git 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>> = Rc::new(RefCell::new(None))) The old `local_data` module inherently contained the `Rc>>` as an implementation detail which must now be explicitly stated by users. [rfc]: https://github.com/rust-lang/rfcs/pull/461 [breaking-change] --- diff --git a/src/liblog/lib.rs b/src/liblog/lib.rs index fd2d97d4deb..dab033e0972 100644 --- a/src/liblog/lib.rs +++ b/src/liblog/lib.rs @@ -171,7 +171,7 @@ extern crate regex; -use regex::Regex; +use std::cell::RefCell; use std::fmt; use std::io::LineBufferedWriter; use std::io; @@ -181,6 +181,8 @@ use std::slice; use std::sync::{Once, ONCE_INIT}; +use regex::Regex; + use directive::LOG_LEVEL_NAMES; pub mod macros; @@ -213,7 +215,9 @@ /// Error log level pub const ERROR: u32 = 1; -local_data_key!(local_logger: Box) +thread_local!(static LOCAL_LOGGER: RefCell>> = { + RefCell::new(None) +}) /// 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 @@ -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 // frob the slot while we're doing the logging. This will destroy any logger // 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.log(&LogRecord { @@ -293,7 +299,7 @@ pub fn log(level: u32, loc: &'static LogLocation, args: &fmt::Arguments) { module_path: loc.module_path, 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 @@ -305,7 +311,10 @@ pub fn log_level() -> u32 { unsafe { LOG_LEVEL } } /// Replaces the task-local logger with the specified logger, returning the old /// logger. pub fn set_logger(logger: Box) -> Option> { - 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 diff --git a/src/librustc/util/common.rs b/src/librustc/util/common.rs index e2fa02584f4..7973004d515 100644 --- a/src/librustc/util/common.rs +++ b/src/librustc/util/common.rs @@ -10,7 +10,7 @@ #![allow(non_camel_case_types)] -use std::cell::RefCell; +use std::cell::{RefCell, Cell}; use std::collections::HashMap; use std::fmt::Show; use std::hash::{Hash, Hasher}; @@ -26,11 +26,14 @@ pub struct ErrorReported; pub fn time(do_it: bool, what: &str, u: U, f: |U| -> T) -> T { - local_data_key!(depth: uint); + thread_local!(static DEPTH: Cell = Cell::new(0)); if !do_it { return f(u); } - let old = depth.get().map(|d| *d).unwrap_or(0); - depth.replace(Some(old + 1)); + let old = DEPTH.with(|slot| { + let r = slot.get(); + slot.set(r + 1); + r + }); let mut u = Some(u); let mut rv = None; @@ -41,7 +44,7 @@ pub fn time(do_it: bool, what: &str, u: U, f: |U| -> T) -> T { println!("{}time: {}.{:03} \t{}", " ".repeat(old), dur.num_seconds(), dur.num_milliseconds() % 1000, what); - depth.replace(Some(old)); + DEPTH.with(|slot| slot.set(old)); rv } diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index 85085f46731..bdf2eca21d6 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -100,17 +100,20 @@ use syntax::visit; use syntax::{ast, ast_util, ast_map}; -local_data_key!(task_local_insn_key: RefCell>) +thread_local!(static TASK_LOCAL_INSN_KEY: RefCell>> = { + RefCell::new(None) +}) pub fn with_insn_ctxt(blk: |&[&'static str]|) { - match task_local_insn_key.get() { - Some(ctx) => blk(ctx.borrow().as_slice()), - None => () - } + TASK_LOCAL_INSN_KEY.with(|slot| { + slot.borrow().as_ref().map(|s| blk(s.as_slice())); + }) } 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 { @@ -120,19 +123,23 @@ pub struct _InsnCtxt { #[unsafe_destructor] impl Drop for _InsnCtxt { fn drop(&mut self) { - match task_local_insn_key.get() { - Some(ctx) => { ctx.borrow_mut().pop(); } - None => {} - } + TASK_LOCAL_INSN_KEY.with(|slot| { + match slot.borrow_mut().as_mut() { + Some(ctx) => { ctx.pop(); } + None => {} + } + }) } } pub fn push_ctxt(s: &'static str) -> _InsnCtxt { debug!("new InsnCtxt: {}", s); - match task_local_insn_key.get() { - Some(ctx) => ctx.borrow_mut().push(s), - None => {} - } + TASK_LOCAL_INSN_KEY.with(|slot| { + match slot.borrow_mut().as_mut() { + Some(ctx) => ctx.push(s), + None => {} + } + }); _InsnCtxt { _cannot_construct_outside_of_this_module: () } } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index a7f33151547..2b521d1da06 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -26,7 +26,7 @@ use html::item_type; use html::item_type::ItemType; 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 /// visibility is preset) @@ -236,9 +236,9 @@ fn path(w: &mut fmt::Formatter, path: &clean::Path, print_all: bool, generics.push_str(">"); } - let loc = current_location_key.get().unwrap(); - let cache = cache_key.get().unwrap(); - let abs_root = root(&**cache, loc.as_slice()); + let loc = CURRENT_LOCATION_KEY.with(|l| l.borrow().clone()); + let cache = cache(); + let abs_root = root(&*cache, loc.as_slice()); let rel_root = match path.segments[0].name.as_slice() { "self" => Some("./".to_string()), _ => 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! Some((ref fqp, shortty)) if abs_root.is_some() => { 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, prim: clean::PrimitiveType, name: &str) -> fmt::Result { - let m = cache_key.get().unwrap(); + let m = cache(); let mut needs_termination = false; match m.primitive_locations.get(&prim) { Some(&ast::LOCAL_CRATE) => { - let loc = current_location_key.get().unwrap(); - let len = if loc.len() == 0 {0} else {loc.len() - 1}; + let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len()); + let len = if len == 0 {0} else {len - 1}; try!(write!(f, "", "../".repeat(len), prim.to_url_str())); @@ -327,8 +327,8 @@ fn primitive_link(f: &mut fmt::Formatter, let loc = match m.extern_locations[cnum] { render::Remote(ref s) => Some(s.to_string()), render::Local => { - let loc = current_location_key.get().unwrap(); - Some("../".repeat(loc.len())) + let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len()); + Some("../".repeat(len)) } render::Unknown => None, }; @@ -371,12 +371,10 @@ impl fmt::Show for clean::Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { clean::TyParamBinder(id) => { - let m = cache_key.get().unwrap(); - f.write(m.typarams[ast_util::local_def(id)].as_bytes()) + f.write(cache().typarams[ast_util::local_def(id)].as_bytes()) } clean::Generic(did) => { - let m = cache_key.get().unwrap(); - f.write(m.typarams[did].as_bytes()) + f.write(cache().typarams[did].as_bytes()) } clean::ResolvedPath{ did, ref typarams, ref path } => { try!(resolved_path(f, did, path, false)); diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 07b58e1b66c..11dc8f4f660 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -147,10 +147,14 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> { } } -local_data_key!(used_header_map: RefCell>) -local_data_key!(test_idx: Cell) -// None == render an example, but there's no crate name -local_data_key!(pub playground_krate: Option) +thread_local!(static USED_HEADER_MAP: RefCell> = { + RefCell::new(HashMap::new()) +}) +thread_local!(static TEST_IDX: Cell = Cell::new(0)) + +thread_local!(pub static PLAYGROUND_KRATE: RefCell>> = { + RefCell::new(None) +}) 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, @@ -183,12 +187,15 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result { stripped_filtered_line(*l).is_none() }); let text = lines.collect::>().connect("\n"); - if !rendered { + if rendered { return } + PLAYGROUND_KRATE.with(|krate| { let mut s = String::new(); - let id = playground_krate.get().map(|krate| { - let idx = test_idx.get().unwrap(); - let i = idx.get(); - idx.set(i + 1); + let id = krate.borrow().as_ref().map(|krate| { + let idx = TEST_IDX.with(|slot| { + let i = slot.get(); + slot.set(i + 1); + i + }); let test = origtext.lines().map(|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); s.push_str(format!("{}", - i, Escape(test.as_slice())).as_slice()); - format!("rust-example-rendered-{}", i) + idx, Escape(test.as_slice())).as_slice()); + format!("rust-example-rendered-{}", idx) }); let id = id.as_ref().map(|a| a.as_slice()); s.push_str(highlight::highlight(text.as_slice(), None, id) .as_slice()); let output = s.to_c_str(); 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 // html for text rather than the raw text. - let id = id.replace("", "").replace("", "").to_string(); let opaque = opaque as *mut hoedown_html_renderer_state; let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; // Make sure our hyphenated ID is unique for this page - let map = used_header_map.get().unwrap(); - let id = match map.borrow_mut().get_mut(&id) { - None => id, - Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) } - }; - map.borrow_mut().insert(id.clone(), 1); + let id = USED_HEADER_MAP.with(|map| { + let id = id.replace("", "").replace("", "").to_string(); + let id = match map.borrow_mut().get_mut(&id) { + None => id, + Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) } + }; + map.borrow_mut().insert(id.clone(), 1); + id + }); let sec = match opaque.toc_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) }); } - if used_header_map.get().is_none() { - reset_headers(); - } + reset_headers(); unsafe { let ob = hoedown_buffer_new(DEF_OUNIT); @@ -418,8 +425,8 @@ fn parse(string: &str) -> LangString { /// used at the beginning of rendering an entire HTML page to reset from the /// previous state (if any). pub fn reset_headers() { - used_header_map.replace(Some(RefCell::new(HashMap::new()))); - test_idx.replace(Some(Cell::new(0))); + USED_HEADER_MAP.with(|s| s.borrow_mut().clear()); + TEST_IDX.with(|s| s.set(0)); } impl<'a> fmt::Show for Markdown<'a> { diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 9e3c336a7a0..466af36898e 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -34,8 +34,10 @@ //! both occur before the crate is rendered. pub use self::ExternalLocation::*; -use std::collections::{HashMap, HashSet}; +use std::cell::RefCell; use std::collections::hash_map::{Occupied, Vacant}; +use std::collections::{HashMap, HashSet}; +use std::default::Default; use std::fmt; use std::io::fs::PathExtensions; 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 `Send` so it may be stored in a `Arc` instance and shared among the various /// rendering tasks. +#[deriving(Default)] pub struct Cache { /// 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 @@ -235,8 +238,9 @@ struct IndexItem { // TLS keys used to carry information around during rendering. -local_data_key!(pub cache_key: Arc) -local_data_key!(pub current_location_key: Vec ) +thread_local!(static CACHE_KEY: RefCell> = Default::default()) +thread_local!(pub static CURRENT_LOCATION_KEY: RefCell> = + RefCell::new(Vec::new())) /// Generates the documentation for `crate` into the directory `dst` pub fn run(mut krate: clean::Crate, @@ -280,10 +284,12 @@ pub fn run(mut krate: clean::Crate, clean::NameValue(ref x, ref s) if "html_playground_url" == x.as_slice() => { cx.layout.playground_url = s.to_string(); - let name = krate.name.clone(); - if markdown::playground_krate.get().is_none() { - markdown::playground_krate.replace(Some(Some(name))); - } + markdown::PLAYGROUND_KRATE.with(|slot| { + if slot.borrow().is_none() { + let name = krate.name.clone(); + *slot.borrow_mut() = Some(Some(name)); + } + }); } clean::Word(ref x) 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 - 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 = public_items.unwrap_or(NodeSet::new()); let paths: HashMap, 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 // for future parallelization opportunities let cache = Arc::new(cache); - cache_key.replace(Some(cache.clone())); - current_location_key.replace(Some(Vec::new())); + CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); + CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear()); try!(write_shared(&cx, &krate, &*cache, index)); let krate = try!(render_sources(&mut cx, krate)); @@ -1134,7 +1141,9 @@ fn render(w: io::File, cx: &Context, it: &clean::Item, info!("Rendering an item to {}", w.path().display()); // A little unfortunate that this is done like this, but it sure // 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("::"); if pushname { @@ -1177,7 +1186,7 @@ fn render(w: io::File, cx: &Context, it: &clean::Item, &Item{ cx: cx, item: it })); } else { 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, _)) => { for name in names[..names.len() - 1].iter() { url.push_str(name.as_slice()); @@ -1324,7 +1333,7 @@ fn href(&self) -> Option { // If we don't know where the external documentation for this crate is // located, then we return `None`. } else { - let cache = cache_key.get().unwrap(); + let cache = cache(); let path = &cache.external_paths[self.item.def_id]; let root = match cache.extern_locations[self.item.def_id.krate] { Remote(ref s) => s.to_string(), @@ -1751,7 +1760,7 @@ fn trait_item(w: &mut fmt::Formatter, m: &clean::TraitMethod) try!(write!(w, "")); } - let cache = cache_key.get().unwrap(); + let cache = cache(); try!(write!(w, "

Implementors

    @@ -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 { - match cache_key.get().unwrap().impls.get(&it.def_id) { + match cache().impls.get(&it.def_id) { Some(v) => { let (non_trait, traits) = v.partitioned(|i| i.impl_.trait_.is_none()); if non_trait.len() > 0 { @@ -2101,7 +2110,7 @@ fn render_default_methods(w: &mut fmt::Formatter, match i.impl_.trait_ { Some(clean::ResolvedPath { did, .. }) => { try!({ - match cache_key.get().unwrap().traits.get(&did) { + match cache().traits.get(&did) { Some(t) => try!(render_default_methods(w, t, &i.impl_)), None => {} } @@ -2220,3 +2229,7 @@ fn get_basic_keywords() -> &'static str { fn make_item_keywords(it: &clean::Item) -> String { format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap()) } + +pub fn cache() -> Arc { + CACHE_KEY.with(|c| c.borrow().clone()) +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 8770e473dea..204f866e827 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -28,12 +28,14 @@ extern crate "test" as testing; #[phase(plugin, link)] extern crate log; -use std::io; -use std::io::File; +use std::cell::RefCell; use std::collections::HashMap; 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 serialize::{json, Decodable, Encodable}; // reexported from `clean` so it can be easily updated with the mod itself pub use clean::SCHEMA_VERSION; @@ -84,7 +86,9 @@ pub mod html { "unindent-comments", ]; -local_data_key!(pub analysiskey: core::CrateAnalysis) +thread_local!(pub static ANALYSISKEY: Rc>> = { + Rc::new(RefCell::new(None)) +}) struct Output { 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) }).map_err(|_| "rustc failed").unwrap(); 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") { Some(name) => krate.name = name, diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 7ee58d99c27..881c7a91d81 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -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 playground = matches.opt_str("markdown-playground-url"); 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()); diff --git a/src/librustdoc/passes.rs b/src/librustdoc/passes.rs index 7c4d28d5adb..f55d447f569 100644 --- a/src/librustdoc/passes.rs +++ b/src/librustdoc/passes.rs @@ -101,7 +101,9 @@ fn fold_item(&mut self, i: Item) -> Option { pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult { // This stripper collects all *retained* nodes. 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(); // strip all private items diff --git a/src/librustdoc/stability_summary.rs b/src/librustdoc/stability_summary.rs index 7ec59f0139d..42f4c2a0ca6 100644 --- a/src/librustdoc/stability_summary.rs +++ b/src/librustdoc/stability_summary.rs @@ -23,7 +23,7 @@ use clean::{ImplItem, Impl, Trait, TraitItem, TraitMethod, ProvidedMethod, RequiredMethod}; use clean::{TypeTraitItem, ViewItemItem, PrimitiveItem, Stability}; -use html::render::cache_key; +use html::render::cache; #[deriving(Zero, Encodable, Decodable, PartialEq, Eq)] /// The counts for each stability level. @@ -116,7 +116,7 @@ fn count_stability(stab: Option<&Stability>) -> 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) => { v.iter().map(|i| { let count = count_stability(i.stability.as_ref()); diff --git a/src/librustrt/lib.rs b/src/librustrt/lib.rs index 65e6bdb70f8..3d172d62350 100644 --- a/src/librustrt/lib.rs +++ b/src/librustrt/lib.rs @@ -52,7 +52,6 @@ pub mod c_str; pub mod exclusive; pub mod local; -pub mod local_data; pub mod mutex; pub mod stack; pub mod task; diff --git a/src/librustrt/local_data.rs b/src/librustrt/local_data.rs deleted file mode 100644 index ca0f694676f..00000000000 --- a/src/librustrt/local_data.rs +++ /dev/null @@ -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 or the MIT license -// , 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) - -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 = &'static KeyValue; - -#[allow(missing_docs)] -pub enum KeyValue { 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; -#[unsafe_no_drop_flag] -struct TLDValue { - // box_ptr is a pointer to TLDValueBox. 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 { - // 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 -} - -// 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::) { 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 { - // FIXME #12808: strange names to try to avoid interfering with - // field accesses of the contained type via Deref - _inner: &'static TLDValueBox, - _marker: marker::NoSend -} - -fn key_to_key_value(key: Key) -> uint { - key as *const _ as uint -} - -impl KeyValue { - /// 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) -> Option { - 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; - 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> { - 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; - 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 Deref for Ref { - #[inline(always)] - fn deref<'a>(&'a self) -> &'a T { - &self._inner.value - } -} - -impl fmt::Show for Ref { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - (**self).fmt(f) - } -} - -impl cmp::PartialEq for Ref { - fn eq(&self, other: &Ref) -> bool { - (**self).eq(&**other) - } - fn ne(&self, other: &Ref) -> bool { - (**self).ne(&**other) - } -} - -impl cmp::Eq for Ref {} - -impl cmp::PartialOrd for Ref { - fn partial_cmp(&self, other: &Ref) -> Option { - (**self).partial_cmp(&**other) - } - fn lt(&self, other: &Ref) -> bool { (**self).lt(&**other) } - fn le(&self, other: &Ref) -> bool { (**self).le(&**other) } - fn gt(&self, other: &Ref) -> bool { (**self).gt(&**other) } - fn ge(&self, other: &Ref) -> bool { (**self).ge(&**other) } -} - -impl cmp::Ord for Ref { - fn cmp(&self, other: &Ref) -> cmp::Ordering { - (**self).cmp(&**other) - } -} - -#[unsafe_destructor] -impl Drop for Ref { - fn drop(&mut self) { - unsafe { - *self._inner.refcount.get() -= 1; - } - } -} - -impl TLDValue { - fn new(value: T) -> TLDValue { - let box_ptr = unsafe { - let allocation = heap::allocate(mem::size_of::>(), - mem::min_align_of::>()); - if allocation.is_null() { ::alloc::oom() } - let value_box = allocation as *mut TLDValueBox; - 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(p: *mut ()) { - let value_box = p as *mut TLDValueBox; - 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 { - p: *mut TLDValueBox - } - #[unsafe_destructor] - impl Drop for Guard { - fn drop(&mut self) { - let size = mem::size_of::>(); - let align = mem::align_of::>(); - unsafe { heap::deallocate(self.p as *mut u8, size, align); } - } - } - let _guard = Guard:: { 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:: - } - } -} - - -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 = &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 = &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 = &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 = &KeyValueKey; - task::spawn(proc() { - MY_KEY.replace(Some("hax".to_string())); - }); - } - - #[test] - fn test_tls_multiple_types() { - static STR_KEY: Key = &KeyValueKey; - static BOX_KEY: Key> = &KeyValueKey; - static INT_KEY: Key = &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 = &KeyValueKey; - static BOX_KEY: Key> = &KeyValueKey; - static INT_KEY: Key = &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 = &KeyValueKey; - static BOX_KEY: Key> = &KeyValueKey; - static INT_KEY: Key = &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 = &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> = &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 = &KeyValueKey; - static KEY2: Key = &KeyValueKey; - static KEY3: Key = &KeyValueKey; - static KEY4: Key = &KeyValueKey; - static KEY5: Key = &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 = &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(Key); - #[unsafe_destructor] - impl Drop for ClearKey { - fn drop(&mut self) { - let ClearKey(ref key) = *self; - key.clear(); - } - } - - #[bench] - fn bench_replace_none(b: &mut test::Bencher) { - static KEY: Key = &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 = &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 = &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, ..100] = [KeyValueKey, ..100]; - let _clear = KEYS.iter().map(ClearKey).collect::>>(); - for (i, key) in KEYS.iter().enumerate() { - key.replace(Some(i)); - } - b.iter(|| { - let key: Key = &KEYS[99]; - key.replace(Some(42)) - }); - } - - #[bench] - fn bench_1000_keys_replace_last(b: &mut test::Bencher) { - static KEYS: [KeyValue, ..1000] = [KeyValueKey, ..1000]; - let _clear = KEYS.iter().map(ClearKey).collect::>>(); - for (i, key) in KEYS.iter().enumerate() { - key.replace(Some(i)); - } - b.iter(|| { - let key: Key = &KEYS[999]; - key.replace(Some(42)) - }); - for key in KEYS.iter() { key.clear(); } - } - - #[bench] - fn bench_get(b: &mut test::Bencher) { - static KEY: Key = &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, ..100] = [KeyValueKey, ..100]; - let _clear = KEYS.iter().map(ClearKey).collect::>>(); - for (i, key) in KEYS.iter().enumerate() { - key.replace(Some(i)); - } - b.iter(|| { - let key: Key = &KEYS[99]; - key.get() - }); - } - - #[bench] - fn bench_1000_keys_get_last(b: &mut test::Bencher) { - static KEYS: [KeyValue, ..1000] = [KeyValueKey, ..1000]; - let _clear = KEYS.iter().map(ClearKey).collect::>>(); - for (i, key) in KEYS.iter().enumerate() { - key.replace(Some(i)); - } - b.iter(|| { - let key: Key = &KEYS[999]; - key.get() - }); - } -} diff --git a/src/librustrt/task.rs b/src/librustrt/task.rs index 64c402bfbbc..63fa3938fc8 100644 --- a/src/librustrt/task.rs +++ b/src/librustrt/task.rs @@ -27,7 +27,6 @@ use bookkeeping; use mutex::NativeMutex; -use local_data; use local::Local; use thread::{mod, Thread}; use stack; @@ -40,7 +39,6 @@ /// This structure is currently undergoing major changes, and is /// likely to be move/be merged with a `Thread` structure. pub struct Task { - pub storage: LocalStorage, pub unwinder: Unwinder, pub death: Death, pub name: Option, @@ -83,8 +81,6 @@ pub struct TaskOpts { /// children tasks complete, recommend using a result future. pub type Result = ::core::result::Result<(), Box>; -pub struct LocalStorage(pub Option); - /// A handle to a blocked task. Usually this means having the Box /// pointer by ownership, but if the task is killable, a killer can steal it /// at any time. @@ -107,7 +103,6 @@ impl Task { /// Creates a new uninitialized task. pub fn new(stack_bounds: Option<(uint, uint)>, stack_guard: Option) -> Task { Task { - storage: LocalStorage(None), unwinder: Unwinder::new(), death: Death::new(), state: New, @@ -230,54 +225,11 @@ pub fn destroy(self: Box) -> Box { /// 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 /// already been destroyed and/or annihilated. - fn cleanup(self: Box, result: Result) -> Box { - // 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::); - 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 - } - + fn cleanup(mut self: Box, result: Result) -> Box { // After taking care of the data above, we need to transmit the result // of this task. - let what_to_do = task.death.on_exit.take(); - Local::put(task); + let what_to_do = self.death.on_exit.take(); + Local::put(self); // FIXME: this is running in a seriously constrained context. If this // allocates TLD then it will likely abort the runtime. Similarly, @@ -549,16 +501,6 @@ mod test { use std::task; 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] fn unwind() { let result = task::try(proc()()); diff --git a/src/libstd/collections/hash/map.rs b/src/libstd/collections/hash/map.rs index 69375e8d4f8..662ae913764 100644 --- a/src/libstd/collections/hash/map.rs +++ b/src/libstd/collections/hash/map.rs @@ -1471,7 +1471,7 @@ fn test_insert() { assert_eq!(*m.get(&2).unwrap(), 4); } - local_data_key!(drop_vector: RefCell>) + thread_local!(static DROP_VECTOR: RefCell> = RefCell::new(Vec::new())) #[deriving(Hash, PartialEq, Eq)] struct Dropable { @@ -1480,8 +1480,9 @@ struct Dropable { impl Dropable { fn new(k: uint) -> Dropable { - let v = drop_vector.get().unwrap(); - v.borrow_mut().as_mut_slice()[k] += 1; + DROP_VECTOR.with(|slot| { + slot.borrow_mut()[k] += 1; + }); Dropable { k: k } } @@ -1489,8 +1490,9 @@ fn new(k: uint) -> Dropable { impl Drop for Dropable { fn drop(&mut self) { - let v = drop_vector.get().unwrap(); - v.borrow_mut().as_mut_slice()[self.k] -= 1; + DROP_VECTOR.with(|slot| { + slot.borrow_mut()[self.k] -= 1; + }); } } @@ -1502,16 +1504,18 @@ fn clone(&self) -> Dropable { #[test] 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 v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 0); - } - drop(v); + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 0); + } + }); for i in range(0u, 100) { let d1 = Dropable::new(i); @@ -1519,11 +1523,11 @@ fn test_drops() { m.insert(d1, d2); } - let v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 1); - } - drop(v); + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 1); + } + }); for i in range(0u, 50) { let k = Dropable::new(i); @@ -1531,41 +1535,46 @@ fn test_drops() { assert!(v.is_some()); - let v = drop_vector.get().unwrap(); - assert_eq!(v.borrow().as_slice()[i], 1); - assert_eq!(v.borrow().as_slice()[i+100], 1); + DROP_VECTOR.with(|v| { + 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, 50) { - assert_eq!(v.borrow().as_slice()[i], 0); - assert_eq!(v.borrow().as_slice()[i+100], 0); - } + DROP_VECTOR.with(|v| { + 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); - } + 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); - } + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 0); + } + }); } #[test] 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 mut hm = HashMap::new(); - let v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 0); - } - drop(v); + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 0); + } + }); for i in range(0u, 100) { let d1 = Dropable::new(i); @@ -1573,11 +1582,11 @@ fn test_move_iter_drops() { hm.insert(d1, d2); } - let v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 1); - } - drop(v); + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 1); + } + }); hm }; @@ -1588,31 +1597,33 @@ fn test_move_iter_drops() { { let mut half = hm.into_iter().take(50); - let v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 1); - } - drop(v); + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 1); + } + }); for _ in half {} - let v = drop_vector.get().unwrap(); - let nk = range(0u, 100).filter(|&i| { - v.borrow().as_slice()[i] == 1 - }).count(); + DROP_VECTOR.with(|v| { + let nk = range(0u, 100).filter(|&i| { + v.borrow().as_slice()[i] == 1 + }).count(); - let nv = range(0u, 100).filter(|&i| { - v.borrow().as_slice()[i+100] == 1 - }).count(); + let nv = range(0u, 100).filter(|&i| { + v.borrow().as_slice()[i+100] == 1 + }).count(); - assert_eq!(nk, 50); - assert_eq!(nv, 50); + assert_eq!(nk, 50); + assert_eq!(nv, 50); + }); }; - let v = drop_vector.get().unwrap(); - for i in range(0u, 200) { - assert_eq!(v.borrow().as_slice()[i], 0); - } + DROP_VECTOR.with(|v| { + for i in range(0u, 200) { + assert_eq!(v.borrow().as_slice()[i], 0); + } + }); } #[test] diff --git a/src/libstd/failure.rs b/src/libstd/failure.rs index c23e043c174..32a8be22902 100644 --- a/src/libstd/failure.rs +++ b/src/libstd/failure.rs @@ -12,10 +12,11 @@ use alloc::boxed::Box; use any::{Any, AnyRefExt}; +use cell::RefCell; use fmt; use io::{Writer, IoResult}; use kinds::Send; -use option::{Some, None}; +use option::{Some, None, Option}; use result::Ok; use rt::backtrace; use rustrt::{Stderr, Stdio}; @@ -25,7 +26,9 @@ use string::String; // Defined in this module instead of io::stdio so that the unwinding -local_data_key!(pub local_stderr: Box) +thread_local!(pub static LOCAL_STDERR: RefCell>> = { + RefCell::new(None) +}) impl Writer for Stdio { 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(""); - match local_stderr.replace(None) { + let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take()); + match prev { Some(mut stderr) => { // FIXME: what to do when the task printing panics? let _ = writeln!(stderr, @@ -83,7 +87,10 @@ pub fn on_fail(obj: &Any + Send, file: &'static str, line: uint) { if backtrace::log_enabled() { 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 => { let _ = writeln!(&mut err, "task '{}' panicked at '{}', {}:{}", diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 7374668a69d..d450e9f1dce 100644 --- a/src/libstd/io/stdio.rs +++ b/src/libstd/io/stdio.rs @@ -29,22 +29,24 @@ use self::StdSource::*; -use failure::local_stderr; +use boxed::Box; +use cell::RefCell; +use failure::LOCAL_STDERR; use fmt; use io::{Reader, Writer, IoResult, IoError, OtherIoError, standard_error, EndOfFile, LineBufferedWriter, BufferedReader}; use iter::Iterator; use kinds::Send; use libc; +use mem; use option::{Option, Some, None}; -use boxed::Box; -use sys::{fs, tty}; use result::{Ok, Err}; use rustrt; use rustrt::local::Local; use rustrt::task::Task; use slice::SlicePrelude; use str::StrPrelude; +use sys::{fs, tty}; use uint; // And so begins the tale of acquiring a uv handle to a stdio stream on all @@ -87,7 +89,9 @@ fn src(fd: libc::c_int, _readable: bool, f: |StdSource| -> T) -> T { } } -local_data_key!(local_stdout: Box) +thread_local!(static LOCAL_STDOUT: RefCell>> = { + RefCell::new(None) +}) /// 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 /// output handle is to the process's stdout stream. pub fn set_stdout(stdout: Box) -> Option> { - 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(); Some(s) }) @@ -182,7 +189,10 @@ pub fn set_stdout(stdout: Box) -> Option> { /// Note that this does not need to be called for all new tasks; the default /// output handle is to the process's stderr stream. pub fn set_stderr(stderr: Box) -> Option> { - 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(); Some(s) }) @@ -200,11 +210,16 @@ pub fn set_stderr(stderr: Box) -> Option> { // }) fn with_task_stdout(f: |&mut Writer| -> IoResult<()>) { let result = if Local::exists(None::) { - 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 }); 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 } else { let mut io = rustrt::Stdout; diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index b35c49efdd8..77da727e94d 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -170,7 +170,6 @@ pub use core_collections::vec; pub use rustrt::c_str; -pub use rustrt::local_data; pub use unicode::char; @@ -209,17 +208,25 @@ #[path = "num/f32.rs"] pub mod f32; #[path = "num/f64.rs"] pub mod f64; -pub mod rand; - pub mod ascii; -pub mod time; - /* Common traits */ pub mod error; 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 */ pub mod collections; @@ -230,15 +237,6 @@ pub mod task; 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)] #[path = "sys/unix/mod.rs"] mod sys; #[cfg(windows)] @@ -263,10 +261,12 @@ mod std { pub use error; // used for try!() pub use fmt; // used for any formatting strings pub use io; // used for println!() - pub use local_data; // used for local_data_key!() pub use option; // used for bitflags!{} pub use rt; // used for panic!() 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 #[cfg(test)] pub use realstd::os as os; @@ -276,4 +276,5 @@ mod std { pub use slice; pub use boxed; // used for vec![] + } diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index 18c109d92a0..c3260225d0a 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -304,28 +304,6 @@ macro_rules! println( ($($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 /// error if the value of the expression is `Err`. For more information, see /// `std::io`. diff --git a/src/libstd/rand/mod.rs b/src/libstd/rand/mod.rs index 08eb7350bcf..f9f9147b107 100644 --- a/src/libstd/rand/mod.rs +++ b/src/libstd/rand/mod.rs @@ -226,7 +226,6 @@ use io::IoResult; use iter::Iterator; use mem; -use option::{Some, None}; use rc::Rc; use result::{Ok, Err}; use vec::Vec; @@ -337,24 +336,18 @@ pub struct TaskRng { /// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`. pub fn task_rng() -> TaskRng { // used to make space in TLS for a random number generator - local_data_key!(TASK_RNG_KEY: Rc>) - - match TASK_RNG_KEY.get() { - 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() } - } + thread_local!(static TASK_RNG_KEY: Rc> = { + 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)) + }) + + TaskRng { rng: TASK_RNG_KEY.with(|t| t.clone()) } } impl Rng for TaskRng { diff --git a/src/libstd/sync/future.rs b/src/libstd/sync/future.rs index e37d1f83877..3e1ba8cebf8 100644 --- a/src/libstd/sync/future.rs +++ b/src/libstd/sync/future.rs @@ -210,28 +210,4 @@ fn test_sendable_future() { }); assert_eq!(rx.recv(), expected); } - - #[test] - fn test_dropped_future_doesnt_panic() { - struct Bomb(Sender); - - 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()); - } } diff --git a/src/libstd/sys/common/mod.rs b/src/libstd/sys/common/mod.rs index cacb128faa5..77edd7c5c4e 100644 --- a/src/libstd/sys/common/mod.rs +++ b/src/libstd/sys/common/mod.rs @@ -21,6 +21,7 @@ pub mod net; pub mod helper_thread; +pub mod thread_local; // common error constructors diff --git a/src/libstd/sys/common/thread_local.rs b/src/libstd/sys/common/thread_local.rs new file mode 100644 index 00000000000..9ad38cbaf65 --- /dev/null +++ b/src/libstd/sys/common/thread_local.rs @@ -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 or the MIT license +// , 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, +} + +/// 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> = 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) -> 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::::new()); + unsafe { + KEYS = mem::transmute(keys); + } + + rustrt::at_exit(proc() unsafe { + let keys: Box>> = 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() {} + fn assert_send() {} + + #[test] + fn smoke() { + assert_sync::(); + assert_send::(); + + 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); + } + } +} + diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 664a6a1e70c..7a5317b578d 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -34,14 +34,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( pub mod c; pub mod fs; +pub mod helper_signal; pub mod os; -pub mod tcp; -pub mod udp; pub mod pipe; -pub mod helper_signal; pub mod process; +pub mod tcp; pub mod timer; +pub mod thread_local; pub mod tty; +pub mod udp; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/unix/thread_local.rs b/src/libstd/sys/unix/thread_local.rs new file mode 100644 index 00000000000..b300e93eeb6 --- /dev/null +++ b/src/libstd/sys/unix/thread_local.rs @@ -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 or the MIT license +// , 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) -> 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) -> 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; +} diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 815ace21f87..a785ccfe804 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -35,14 +35,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( pub mod c; pub mod fs; +pub mod helper_signal; pub mod os; -pub mod tcp; -pub mod udp; pub mod pipe; -pub mod helper_signal; pub mod process; +pub mod tcp; +pub mod thread_local; pub mod timer; pub mod tty; +pub mod udp; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/windows/thread_local.rs b/src/libstd/sys/windows/thread_local.rs new file mode 100644 index 00000000000..b841f6d3a2b --- /dev/null +++ b/src/libstd/sys/windows/thread_local.rs @@ -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 or the MIT license +// , 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> = 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) -> 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>>>(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::>(); + 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; + } + } + } +} diff --git a/src/libstd/thread_local/mod.rs b/src/libstd/thread_local/mod.rs new file mode 100644 index 00000000000..e2e8461ea54 --- /dev/null +++ b/src/libstd/thread_local/mod.rs @@ -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 or the MIT license +// , 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 = 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 { + // 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>>, + + // 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 Key { + /// 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(&'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 { + // 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, + + // Metadata to keep track of the state of the destructor. Remember that + // these variables are thread-local, not global. + pub dtor_registered: UnsafeCell, // should be Cell + pub dtor_running: UnsafeCell, // should be Cell + + // These shouldn't be copied around. + pub marker: marker::NoCopy, + } + + #[doc(hidden)] + impl Key { + pub unsafe fn get(&'static self) -> Option<&'static T> { + if intrinsics::needs_drop::() && *self.dtor_running.get() { + return None + } + self.register_dtor(); + Some(&*self.inner.get()) + } + + unsafe fn register_dtor(&self) { + if !intrinsics::needs_drop::() || *self.dtor_registered.get() { + return + } + + register_dtor(self as *const _ as *mut u8, + destroy_value::); + *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 = 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 = 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(ptr: *mut u8) { + let ptr = ptr as *mut Key; + // 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 { + // Statically allocated initialization expression, using an `UnsafeCell` + // for the same reasons as above. + pub inner: UnsafeCell, + + // OS-TLS key that we'll use to key off. + pub os: OsStaticKey, + } + + struct Value { + key: &'static Key, + value: T, + } + + #[doc(hidden)] + impl Key { + 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; + 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> = box Value { + key: self, + value: mem::transmute_copy(&self.inner), + }; + let ptr: *mut Value = 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(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> = 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 = 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> = 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> = UnsafeCell { + value: None + }) + thread_local!(static K2: UnsafeCell> = 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> = 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> = UnsafeCell { + value: None + }) + thread_local!(static K2: UnsafeCell> = 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> { + let mut m = HashMap::new(); + m.insert(1, 2); + RefCell::new(m) + } + thread_local!(static FOO: RefCell> = map()) + + FOO.with(|map| { + assert_eq!(map.borrow()[1], 2); + }); + } + + #[test] + fn refcell_vec() { + thread_local!(static FOO: RefCell> = 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); + }); + } +} diff --git a/src/libstd/thread_local/scoped.rs b/src/libstd/thread_local/scoped.rs new file mode 100644 index 00000000000..11d539c4f9f --- /dev/null +++ b/src/libstd/thread_local/scoped.rs @@ -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 or the MIT license +// , 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 { #[doc(hidden)] pub inner: KeyInner } + +/// 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 Key { + /// 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(&'static self, t: &T, cb: || -> R) -> R { + struct Reset<'a, T: 'a> { + key: &'a KeyInner, + 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(&'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 { pub inner: UnsafeCell<*mut T> } + + #[doc(hidden)] + impl KeyInner { + #[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 { + pub inner: OsStaticKey, + pub marker: marker::InvariantType, + } + + #[doc(hidden)] + impl KeyInner { + #[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) + + BAR.set(&Cell::new(1), || { + BAR.with(|slot| { + assert_eq!(slot.get(), 1); + }); + }); + } +} + diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index 42fdb50f87a..fdfa275549a 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -25,21 +25,20 @@ use parse::token; use ptr::P; -use std::collections::HashSet; +use std::cell::{RefCell, Cell}; use std::collections::BitvSet; +use std::collections::HashSet; -local_data_key!(used_attrs: BitvSet) +thread_local!(static USED_ATTRS: RefCell = RefCell::new(BitvSet::new())) pub fn mark_used(attr: &Attribute) { - let mut used = used_attrs.replace(None).unwrap_or_else(|| BitvSet::new()); let AttrId(id) = attr.node.id; - used.insert(id); - used_attrs.replace(Some(used)); + USED_ATTRS.with(|slot| slot.borrow_mut().insert(id)); } pub fn is_used(attr: &Attribute) -> bool { 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 { @@ -167,11 +166,14 @@ pub fn mk_word_item(name: InternedString) -> P { P(dummy_spanned(MetaWord(name))) } -local_data_key!(next_attr_id: uint) +thread_local!(static NEXT_ATTR_ID: Cell = Cell::new(0)) pub fn mk_attr_id() -> AttrId { - let id = next_attr_id.replace(None).unwrap_or(0); - next_attr_id.replace(Some(id + 1)); + let id = NEXT_ATTR_ID.with(|slot| { + let r = slot.get(); + slot.set(r + 1); + r + }); AttrId(id) } diff --git a/src/libsyntax/diagnostics/plugin.rs b/src/libsyntax/diagnostics/plugin.rs index 281bde3129a..5f4e675aad5 100644 --- a/src/libsyntax/diagnostics/plugin.rs +++ b/src/libsyntax/diagnostics/plugin.rs @@ -18,31 +18,23 @@ use parse::token; use ptr::P; -local_data_key!(registered_diagnostics: RefCell>>) -local_data_key!(used_diagnostics: RefCell>) +thread_local!(static REGISTERED_DIAGNOSTICS: RefCell>> = { + RefCell::new(HashMap::new()) +}) +thread_local!(static USED_DIAGNOSTICS: RefCell> = { + RefCell::new(HashMap::new()) +}) fn with_registered_diagnostics(f: |&mut HashMap>| -> T) -> T { - match registered_diagnostics.get() { - Some(cell) => f(cell.borrow_mut().deref_mut()), - None => { - let mut map = HashMap::new(); - let value = f(&mut map); - registered_diagnostics.replace(Some(RefCell::new(map))); - value - } - } + REGISTERED_DIAGNOSTICS.with(|slot| { + f(&mut *slot.borrow_mut()) + }) } fn with_used_diagnostics(f: |&mut HashMap| -> T) -> T { - match used_diagnostics.get() { - Some(cell) => f(cell.borrow_mut().deref_mut()), - None => { - let mut map = HashMap::new(); - let value = f(&mut map); - used_diagnostics.replace(Some(RefCell::new(map))); - value - } - } + USED_DIAGNOSTICS.with(|slot| { + f(&mut *slot.borrow_mut()) + }) } pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt, diff --git a/src/libsyntax/ext/mtwt.rs b/src/libsyntax/ext/mtwt.rs index 2ddcab10cda..e6d886e28ba 100644 --- a/src/libsyntax/ext/mtwt.rs +++ b/src/libsyntax/ext/mtwt.rs @@ -20,7 +20,6 @@ use ast::{Ident, Mrk, Name, SyntaxContext}; use std::cell::RefCell; -use std::rc::Rc; use std::collections::HashMap; 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. pub fn with_sctable(op: |&SCTable| -> T) -> T { - local_data_key!(sctable_key: Rc) - - match sctable_key.get() { - Some(ts) => op(&**ts), - None => { - let ts = Rc::new(new_sctable_internal()); - sctable_key.replace(Some(ts.clone())); - op(&*ts) - } - } + thread_local!(static SCTABLE_KEY: SCTable = new_sctable_internal()) + SCTABLE_KEY.with(|slot| op(slot)) } // Make a fresh syntax context table with EmptyCtxt in slot zero @@ -165,16 +156,11 @@ pub fn resolve(id: Ident) -> Name { // okay, I admit, putting this in TLS is not so nice: // fetch the SCTable from TLS, create one if it doesn't yet exist. fn with_resolve_table_mut(op: |&mut ResolveTable| -> T) -> T { - local_data_key!(resolve_table_key: Rc>) - - match resolve_table_key.get() { - 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()) - } - } + thread_local!(static RESOLVE_TABLE_KEY: RefCell = { + RefCell::new(HashMap::new()) + }) + + RESOLVE_TABLE_KEY.with(|slot| op(&mut *slot.borrow_mut())) } /// Resolve a syntax object to a name, per MTWT. diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs index 4272b57a4dc..3a3407aedba 100644 --- a/src/libsyntax/parse/token.rs +++ b/src/libsyntax/parse/token.rs @@ -560,15 +560,10 @@ pub mod keywords { // fresh one. // FIXME(eddyb) #8726 This should probably use a task-local reference. pub fn get_ident_interner() -> Rc { - local_data_key!(key: Rc<::parse::token::IdentInterner>) - match key.get() { - Some(interner) => interner.clone(), - None => { - let interner = Rc::new(mk_fresh_ident_interner()); - key.replace(Some(interner.clone())); - interner - } - } + thread_local!(static KEY: Rc<::parse::token::IdentInterner> = { + Rc::new(mk_fresh_ident_interner()) + }) + KEY.with(|k| k.clone()) } /// Represents a string stored in the task-local interner. Because the diff --git a/src/test/auxiliary/plugin_crate_outlive_expansion_phase.rs b/src/test/auxiliary/plugin_crate_outlive_expansion_phase.rs index 1592ffb6c67..021eae90cf8 100644 --- a/src/test/auxiliary/plugin_crate_outlive_expansion_phase.rs +++ b/src/test/auxiliary/plugin_crate_outlive_expansion_phase.rs @@ -15,6 +15,7 @@ extern crate rustc; use std::any::Any; +use std::cell::RefCell; use rustc::plugin::Registry; struct Foo { @@ -27,7 +28,7 @@ fn drop(&mut self) {} #[plugin_registrar] pub fn registrar(_: &mut Registry) { - local_data_key!(foo: Box); - foo.replace(Some(box Foo { foo: 10 } as Box)); + thread_local!(static FOO: RefCell>> = RefCell::new(None)); + FOO.with(|s| *s.borrow_mut() = Some(box Foo { foo: 10 } as Box)); } diff --git a/src/test/compile-fail/core-tls-store-pointer.rs b/src/test/compile-fail/core-tls-store-pointer.rs deleted file mode 100644 index d77c552be03..00000000000 --- a/src/test/compile-fail/core-tls-store-pointer.rs +++ /dev/null @@ -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 or the MIT license -// , 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() {} diff --git a/src/test/compile-fail/macro-local-data-key-priv.rs b/src/test/compile-fail/macro-local-data-key-priv.rs index ec0e656cc29..3f2ecd86abe 100644 --- a/src/test/compile-fail/macro-local-data-key-priv.rs +++ b/src/test/compile-fail/macro-local-data-key-priv.rs @@ -11,10 +11,10 @@ // check that the local data keys are private by default. mod bar { - local_data_key!(baz: f64) + thread_local!(static baz: f64 = 0.0) } fn main() { - bar::baz.replace(Some(-10.0)); + bar::baz.with(|_| ()); //~^ ERROR static `baz` is private } diff --git a/src/test/run-pass/macro-local-data-key.rs b/src/test/run-pass/macro-local-data-key.rs deleted file mode 100644 index 730b0b08d45..00000000000 --- a/src/test/run-pass/macro-local-data-key.rs +++ /dev/null @@ -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 or the MIT license -// , 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); -} diff --git a/src/test/run-pass/panic-during-tld-destroy.rs b/src/test/run-pass/panic-during-tld-destroy.rs deleted file mode 100644 index 2f0d6cf90aa..00000000000 --- a/src/test/run-pass/panic-during-tld-destroy.rs +++ /dev/null @@ -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 or the MIT license -// , 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); - } -} - diff --git a/src/test/run-pass/running-with-no-runtime.rs b/src/test/run-pass/running-with-no-runtime.rs index ed4c20c8094..683f7038ead 100644 --- a/src/test/run-pass/running-with-no-runtime.rs +++ b/src/test/run-pass/running-with-no-runtime.rs @@ -17,8 +17,6 @@ use rustrt::unwind::try; -local_data_key!(foo: int) - #[start] fn start(argc: int, argv: *const *const u8) -> int { if argc > 1 { @@ -30,8 +28,6 @@ fn start(argc: int, argv: *const *const u8) -> int { 4 => assert!(try(|| panic!()).is_err()), 5 => assert!(try(|| spawn(proc() {})).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!() } } @@ -57,10 +53,6 @@ fn main() { pass(Command::new(me).arg(x).output().unwrap()); let x: &[u8] = &[6u8]; 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) {