From 5bd834bdb4e2c1fe6666be8c8fad41526d72e3d1 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Wed, 7 Sep 2016 23:48:07 -0500 Subject: [PATCH 1/5] Add ThreadId for comparing threads --- src/libstd/thread/mod.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/libstd/thread/mod.rs b/src/libstd/thread/mod.rs index d8e021bb04f..97a277e7bb4 100644 --- a/src/libstd/thread/mod.rs +++ b/src/libstd/thread/mod.rs @@ -165,6 +165,7 @@ use panic; use panicking; use str; use sync::{Mutex, Condvar, Arc}; +use sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use sys::thread as imp; use sys_common::thread_info; use sys_common::util; @@ -524,6 +525,35 @@ pub fn park_timeout(dur: Duration) { *guard = false; } +//////////////////////////////////////////////////////////////////////////////// +// ThreadId +//////////////////////////////////////////////////////////////////////////////// + +/// A unique identifier for a running thread. +/// +/// A `ThreadId` is an opaque object that has a unique value for each thread +/// that creates one. `ThreadId`s do not correspond to a thread's system- +/// designated identifier. +#[unstable(feature = "thread_id", issue = "21507")] +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct ThreadId(usize); + +impl ThreadId { + /// Returns an identifier unique to the current calling thread. + #[unstable(feature = "thread_id", issue = "21507")] + pub fn current() -> ThreadId { + static THREAD_ID_COUNT: AtomicUsize = ATOMIC_USIZE_INIT; + #[thread_local] static mut THREAD_ID: ThreadId = ThreadId(0); + + unsafe { + if THREAD_ID.0 == 0 { + THREAD_ID.0 = 1 + THREAD_ID_COUNT.fetch_add(1, Ordering::SeqCst); + } + THREAD_ID + } + } +} + //////////////////////////////////////////////////////////////////////////////// // Thread //////////////////////////////////////////////////////////////////////////////// @@ -977,6 +1007,16 @@ mod tests { thread::sleep(Duration::from_millis(2)); } + #[test] + fn test_thread_id_equal() { + assert_eq!(ThreadId::current(), ThreadId::current()); + } + + #[test] + fn test_thread_id_not_equal() { + assert!(ThreadId::current() != spawn(|| ThreadId::current()).join()); + } + // NOTE: the corresponding test for stderr is in run-pass/thread-stderr, due // to the test harness apparently interfering with stderr configuration. } From 6e10e29a9702ef2fb5b1cedced4e2a72a1a81b69 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Thu, 8 Sep 2016 00:08:15 -0500 Subject: [PATCH 2/5] Fix tests --- src/libstd/thread/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstd/thread/mod.rs b/src/libstd/thread/mod.rs index 97a277e7bb4..ea20c93fc26 100644 --- a/src/libstd/thread/mod.rs +++ b/src/libstd/thread/mod.rs @@ -1009,12 +1009,12 @@ mod tests { #[test] fn test_thread_id_equal() { - assert_eq!(ThreadId::current(), ThreadId::current()); + assert!(thread::ThreadId::current() == thread::ThreadId::current()); } #[test] fn test_thread_id_not_equal() { - assert!(ThreadId::current() != spawn(|| ThreadId::current()).join()); + assert!(thread::ThreadId::current() != thread::spawn(|| thread::ThreadId::current()).join().unwrap()); } // NOTE: the corresponding test for stderr is in run-pass/thread-stderr, due From 894ef966c631facd13cd0d021acca43e37c8510e Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Wed, 5 Oct 2016 11:34:25 -0500 Subject: [PATCH 3/5] Generate ID using u64 + atomic spinlock --- src/libstd/thread/mod.rs | 68 +++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/src/libstd/thread/mod.rs b/src/libstd/thread/mod.rs index ea20c93fc26..a20c2c5002b 100644 --- a/src/libstd/thread/mod.rs +++ b/src/libstd/thread/mod.rs @@ -165,7 +165,7 @@ use panic; use panicking; use str; use sync::{Mutex, Condvar, Arc}; -use sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +use sync::atomic::{AtomicBool, Ordering}; use sys::thread as imp; use sys_common::thread_info; use sys_common::util; @@ -536,21 +536,52 @@ pub fn park_timeout(dur: Duration) { /// designated identifier. #[unstable(feature = "thread_id", issue = "21507")] #[derive(Eq, PartialEq, Copy, Clone)] -pub struct ThreadId(usize); +pub struct ThreadId(u64); impl ThreadId { - /// Returns an identifier unique to the current calling thread. - #[unstable(feature = "thread_id", issue = "21507")] - pub fn current() -> ThreadId { - static THREAD_ID_COUNT: AtomicUsize = ATOMIC_USIZE_INIT; - #[thread_local] static mut THREAD_ID: ThreadId = ThreadId(0); + // Generate a new unique thread ID. Since this function is called every + // time a thread is created, this is optimized to generate unique values + // as quickly as possible. + fn new() -> ThreadId { + // 64-bit operations are not atomic on all systems, so use an atomic + // flag as a guard around a 64-bit global counter. The window for + // contention on the counter is rather narrow since the general case + // should be compiled down to three instructions between locking and + // unlocking the guard. Since contention on the guard is low, use a + // spinlock that optimizes for the fast path of the guard being + // unlocked. + static GUARD: AtomicBool = AtomicBool::new(false); + static mut COUNTER: u64 = 0; - unsafe { - if THREAD_ID.0 == 0 { - THREAD_ID.0 = 1 + THREAD_ID_COUNT.fetch_add(1, Ordering::SeqCst); - } - THREAD_ID + // Get exclusive access to the counter. + while GUARD.compare_exchange_weak( + false, + true, + Ordering::Acquire, + Ordering::Relaxed + ).is_err() { + // Give up the rest of our thread quantum if another thread is + // using the counter. This is the slow_er_ path. + yield_now(); } + + // We have exclusive access to the counter, so use it fast and get out. + let id = unsafe { + // If we somehow use up all our bits, panic so that we're not + // covering up subtle bugs of IDs being reused. + if COUNTER == ::u64::MAX { + panic!("failed to generate unique thread ID: bitspace exhausted"); + } + + let id = COUNTER; + COUNTER += 1; + id + }; + + // Unlock the guard. + GUARD.store(false, Ordering::Release); + + ThreadId(id) } } @@ -561,6 +592,7 @@ impl ThreadId { /// The internal representation of a `Thread` handle struct Inner { name: Option, // Guaranteed to be UTF-8 + id: ThreadId, lock: Mutex, // true when there is a buffered unpark cvar: Condvar, } @@ -581,6 +613,7 @@ impl Thread { Thread { inner: Arc::new(Inner { name: cname, + id: ThreadId::new(), lock: Mutex::new(false), cvar: Condvar::new(), }) @@ -599,6 +632,12 @@ impl Thread { } } + /// Gets the thread's unique identifier. + #[unstable(feature = "thread_id", issue = "21507")] + pub fn id(&self) -> ThreadId { + self.inner.id + } + /// Gets the thread's name. /// /// # Examples @@ -1009,12 +1048,13 @@ mod tests { #[test] fn test_thread_id_equal() { - assert!(thread::ThreadId::current() == thread::ThreadId::current()); + assert!(thread::current().id() == thread::current().id()); } #[test] fn test_thread_id_not_equal() { - assert!(thread::ThreadId::current() != thread::spawn(|| thread::ThreadId::current()).join().unwrap()); + let spawned_id = thread::spawn(|| thread::current().id()).join().unwrap(); + assert!(thread::current().id() != spawned_id); } // NOTE: the corresponding test for stderr is in run-pass/thread-stderr, due From e80fd2531bbb8d2ca30e4036d7be5bcfcaefb6c0 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Wed, 5 Oct 2016 18:11:28 -0500 Subject: [PATCH 4/5] Use mutex to guard thread ID counter --- src/libstd/thread/mod.rs | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/libstd/thread/mod.rs b/src/libstd/thread/mod.rs index a20c2c5002b..c8b6046bb8d 100644 --- a/src/libstd/thread/mod.rs +++ b/src/libstd/thread/mod.rs @@ -165,8 +165,8 @@ use panic; use panicking; use str; use sync::{Mutex, Condvar, Arc}; -use sync::atomic::{AtomicBool, Ordering}; use sys::thread as imp; +use sys_common::mutex; use sys_common::thread_info; use sys_common::util; use sys_common::{AsInner, IntoInner}; @@ -539,34 +539,14 @@ pub fn park_timeout(dur: Duration) { pub struct ThreadId(u64); impl ThreadId { - // Generate a new unique thread ID. Since this function is called every - // time a thread is created, this is optimized to generate unique values - // as quickly as possible. + // Generate a new unique thread ID. fn new() -> ThreadId { - // 64-bit operations are not atomic on all systems, so use an atomic - // flag as a guard around a 64-bit global counter. The window for - // contention on the counter is rather narrow since the general case - // should be compiled down to three instructions between locking and - // unlocking the guard. Since contention on the guard is low, use a - // spinlock that optimizes for the fast path of the guard being - // unlocked. - static GUARD: AtomicBool = AtomicBool::new(false); + static GUARD: mutex::Mutex = mutex::Mutex::new(); static mut COUNTER: u64 = 0; - // Get exclusive access to the counter. - while GUARD.compare_exchange_weak( - false, - true, - Ordering::Acquire, - Ordering::Relaxed - ).is_err() { - // Give up the rest of our thread quantum if another thread is - // using the counter. This is the slow_er_ path. - yield_now(); - } + unsafe { + GUARD.lock(); - // We have exclusive access to the counter, so use it fast and get out. - let id = unsafe { // If we somehow use up all our bits, panic so that we're not // covering up subtle bugs of IDs being reused. if COUNTER == ::u64::MAX { @@ -575,13 +555,11 @@ impl ThreadId { let id = COUNTER; COUNTER += 1; - id - }; - // Unlock the guard. - GUARD.store(false, Ordering::Release); + GUARD.unlock(); - ThreadId(id) + ThreadId(id) + } } } From 032bffa5b8ae6c3977884c4e10fd6ab6a5dc5ef6 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Fri, 7 Oct 2016 17:45:04 -0500 Subject: [PATCH 5/5] Unlock guard before overflow panic --- src/libstd/thread/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstd/thread/mod.rs b/src/libstd/thread/mod.rs index c8b6046bb8d..98feea96f8c 100644 --- a/src/libstd/thread/mod.rs +++ b/src/libstd/thread/mod.rs @@ -550,6 +550,7 @@ impl ThreadId { // If we somehow use up all our bits, panic so that we're not // covering up subtle bugs of IDs being reused. if COUNTER == ::u64::MAX { + GUARD.unlock(); panic!("failed to generate unique thread ID: bitspace exhausted"); }