about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-07-10 17:46:20 -0700
committerAlex Crichton <alex@alexcrichton.com>2015-07-12 15:08:40 -0700
commit987dc84b521a9be98099b504aa91b7baa9d22409 (patch)
treea0eaf5159ca98d9e1c38ed1eb411176c581c3335
parentfe0b5c0d38fc937ff6cf3623c4277b0463b17748 (diff)
downloadrust-987dc84b521a9be98099b504aa91b7baa9d22409.tar.gz
rust-987dc84b521a9be98099b504aa91b7baa9d22409.zip
std: Fix a TLS destructor bug on OSX
TLS tests have been deadlocking on the OSX bots for quite some time now and this
commit is the result of the investigation into what's going on. It turns out
that a value in TLS which is being destroyed (e.g. the destructor is run) can be
reset back to the initial state **while the destructor is running** if TLS is
re-accessed.

To fix this we stop calling drop_in_place on OSX and instead move the data to a
temporary location on the stack.
-rw-r--r--src/libstd/thread/local.rs21
-rw-r--r--src/test/run-pass/down-with-thread-dtors.rs38
2 files changed, 57 insertions, 2 deletions
diff --git a/src/libstd/thread/local.rs b/src/libstd/thread/local.rs
index 60563340d10..e2873601a7b 100644
--- a/src/libstd/thread/local.rs
+++ b/src/libstd/thread/local.rs
@@ -275,6 +275,7 @@ mod imp {
 
     use cell::{Cell, UnsafeCell};
     use intrinsics;
+    use ptr;
 
     pub struct Key<T> {
         inner: UnsafeCell<Option<T>>,
@@ -327,7 +328,6 @@ mod imp {
     #[cfg(target_os = "linux")]
     unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
         use mem;
-        use ptr;
         use libc;
         use sys_common::thread_local as os;
 
@@ -394,7 +394,24 @@ mod imp {
         // destructor as running for this thread so calls to `get` will return
         // `None`.
         (*ptr).dtor_running.set(true);
-        intrinsics::drop_in_place((*ptr).inner.get());
+
+        // The OSX implementation of TLS apparently had an odd aspect to it
+        // where the pointer we have may be overwritten while this destructor
+        // is running. Specifically if a TLS destructor re-accesses TLS it may
+        // trigger a re-initialization of all TLS variables, paving over at
+        // least some destroyed ones with initial values.
+        //
+        // This means that if we drop a TLS value in place on OSX that we could
+        // revert the value to its original state halfway through the
+        // destructor, which would be bad!
+        //
+        // Hence, we use `ptr::read` on OSX (to move to a "safe" location)
+        // instead of drop_in_place.
+        if cfg!(target_os = "macos") {
+            ptr::read((*ptr).inner.get());
+        } else {
+            intrinsics::drop_in_place((*ptr).inner.get());
+        }
     }
 }
 
diff --git a/src/test/run-pass/down-with-thread-dtors.rs b/src/test/run-pass/down-with-thread-dtors.rs
new file mode 100644
index 00000000000..ee835785cbe
--- /dev/null
+++ b/src/test/run-pass/down-with-thread-dtors.rs
@@ -0,0 +1,38 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+thread_local!(static FOO: Foo = Foo);
+thread_local!(static BAR: Bar = Bar(1));
+thread_local!(static BAZ: Baz = Baz);
+
+struct Foo;
+struct Bar(i32);
+struct Baz;
+
+impl Drop for Foo {
+    fn drop(&mut self) {
+        BAR.with(|_| {});
+    }
+}
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        assert_eq!(self.0, 1);
+        self.0 = 2;
+        BAZ.with(|_| {});
+        assert_eq!(self.0, 2);
+    }
+}
+
+fn main() {
+    std::thread::spawn(|| {
+        FOO.with(|_| {});
+    }).join().unwrap();
+}