From 438532ad48be7ac542f798b548145024919aaaae Mon Sep 17 00:00:00 2001 From: Jeehoon Kang Date: Sun, 27 Aug 2017 12:57:55 +0900 Subject: [PATCH] Initial commit --- .gitignore | 3 + .travis.yml | 6 + Cargo.toml | 12 ++ LICENSE-APACHE | 201 ++++++++++++++++++++++++ LICENSE-MIT | 25 +++ README.md | 29 ++++ src/atomic_option.rs | 55 +++++++ src/cache_padded.rs | 154 ++++++++++++++++++ src/lib.rs | 6 + src/scoped.rs | 364 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 855 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/atomic_option.rs create mode 100644 src/cache_padded.rs create mode 100644 src/lib.rs create mode 100644 src/scoped.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6aa1064 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..66eed7b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: rust + +rust: + - stable + - beta + - nightly diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d99fc6d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "crossbeam-utils" +version = "0.1.0" +authors = ["The Crossbeam Project Developers"] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/crossbeam-rs/crossbeam-utils" +homepage = "https://github.com/crossbeam-rs/crossbeam-utils" +documentation = "https://docs.rs/crossbeam-utils" +description = "Utilities for concurrent programming" + +[dependencies] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..25597d5 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2010 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..01f445f --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Utilities for concurrent programming + +[![Build Status](https://travis-ci.org/crossbeam-rs/crossbeam-utils.svg?branch=master)](https://travis-ci.org/crossbeam-rs/crossbeam-utils) +[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/crossbeam-rs/crossbeam-utils) +[![Cargo](https://img.shields.io/crates/v/crossbeam-utils.svg)](https://crates.io/crates/crossbeam-utils) +[![Documentation](https://docs.rs/crossbeam-utils/badge.svg)](https://docs.rs/crossbeam-utils) + +This crate provides utilities for concurrent programming. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +crossbeam-utils = "0.1" +``` + +Next, add this to your crate: + +```rust +extern crate crossbeam_utils; +``` + +## License + +Licensed under the terms of MIT license and the Apache License (Version 2.0). + +See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE) for details. diff --git a/src/atomic_option.rs b/src/atomic_option.rs new file mode 100644 index 0000000..3b53629 --- /dev/null +++ b/src/atomic_option.rs @@ -0,0 +1,55 @@ +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::ptr; + +unsafe impl Send for AtomicOption {} +unsafe impl Sync for AtomicOption {} + +#[derive(Debug)] +pub struct AtomicOption { + inner: AtomicPtr, +} + +impl Drop for AtomicOption { + fn drop(&mut self) { + let inner = self.inner.load(Ordering::Relaxed); + if !inner.is_null() { + unsafe { + drop(Box::from_raw(inner)); + } + } + } +} + +impl AtomicOption { + pub fn new() -> Self { + AtomicOption { inner: AtomicPtr::new(ptr::null_mut()) } + } + + fn swap_inner(&self, ptr: *mut T, order: Ordering) -> Option> { + let old = self.inner.swap(ptr, order); + if old.is_null() { + None + } else { + Some(unsafe { Box::from_raw(old) }) + } + } + + // allows re-use of allocation + pub fn swap_box(&self, t: Box, order: Ordering) -> Option> { + self.swap_inner(Box::into_raw(t), order) + } + + pub fn swap(&self, t: T, order: Ordering) -> Option { + self.swap_box(Box::new(t), order).map(|old| *old) + } + + pub fn take(&self, order: Ordering) -> Option { + self.swap_inner(ptr::null_mut(), order).map(|old| *old) + } +} + +impl Default for AtomicOption { + fn default() -> Self { + Self::new() + } +} diff --git a/src/cache_padded.rs b/src/cache_padded.rs new file mode 100644 index 0000000..3c62a95 --- /dev/null +++ b/src/cache_padded.rs @@ -0,0 +1,154 @@ +use std::marker; +use std::cell::UnsafeCell; +use std::fmt; +use std::mem; +use std::ptr; +use std::ops::{Deref, DerefMut}; + +// For now, treat this as an arch-independent constant. +const CACHE_LINE: usize = 32; + +#[cfg_attr(feature = "nightly", repr(simd))] +#[derive(Debug)] +struct Padding(u64, u64, u64, u64); + +/// Pad `T` to the length of a cacheline. +/// +/// Sometimes concurrent programming requires a piece of data to be padded out +/// to the size of a cacheline to avoid "false sharing": cachelines being +/// invalidated due to unrelated concurrent activity. Use the `CachePadded` type +/// when you want to *avoid* cache locality. +/// +/// At the moment, cache lines are assumed to be 32 * sizeof(usize) on all +/// architectures. +/// +/// **Warning**: the wrapped data is never dropped; move out using `ptr::read` +/// if you need to run dtors. +pub struct CachePadded { + data: UnsafeCell<[usize; CACHE_LINE]>, + _marker: ([Padding; 0], marker::PhantomData), +} + +impl fmt::Debug for CachePadded { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CachePadded {{ ... }}") + } +} + +unsafe impl Send for CachePadded {} +unsafe impl Sync for CachePadded {} + +/// Types for which `mem::zeroed()` is safe. +/// +/// If a type `T: ZerosValid`, then a sequence of zeros the size of `T` must be +/// a valid member of the type `T`. +pub unsafe trait ZerosValid {} + +#[cfg(feature = "nightly")] +unsafe impl ZerosValid for .. {} + +macro_rules! zeros_valid { ($( $T:ty )*) => ($( + unsafe impl ZerosValid for $T {} +)*)} + +zeros_valid!(u8 u16 u32 u64 usize); +zeros_valid!(i8 i16 i32 i64 isize); + +unsafe impl ZerosValid for ::std::sync::atomic::AtomicUsize {} +unsafe impl ZerosValid for ::std::sync::atomic::AtomicPtr {} + +impl CachePadded { + /// A const fn equivalent to mem::zeroed(). + #[cfg(not(feature = "nightly"))] + pub fn zeroed() -> CachePadded { + CachePadded { + data: UnsafeCell::new([0; CACHE_LINE]), + _marker: ([], marker::PhantomData), + } + } + + /// A const fn equivalent to mem::zeroed(). + #[cfg(feature = "nightly")] + pub const fn zeroed() -> CachePadded { + CachePadded { + data: UnsafeCell::new([0; CACHE_LINE]), + _marker: ([], marker::PhantomData), + } + } +} + +#[inline] +/// Assert that the size and alignment of `T` are consistent with `CachePadded`. +fn assert_valid() { + assert!(mem::size_of::() <= mem::size_of::>()); + assert!(mem::align_of::() <= mem::align_of::>()); +} + +impl CachePadded { + /// Wrap `t` with cacheline padding. + /// + /// **Warning**: the wrapped data is never dropped; move out using + /// `ptr:read` if you need to run dtors. + pub fn new(t: T) -> CachePadded { + assert_valid::(); + let ret = CachePadded { + data: UnsafeCell::new([0; CACHE_LINE]), + _marker: ([], marker::PhantomData), + }; + unsafe { + let p: *mut T = &ret.data as *const UnsafeCell<[usize; CACHE_LINE]> as *mut T; + ptr::write(p, t); + } + ret + } +} + +impl Deref for CachePadded { + type Target = T; + fn deref(&self) -> &T { + assert_valid::(); + unsafe { mem::transmute(&self.data) } + } +} + +impl DerefMut for CachePadded { + fn deref_mut(&mut self) -> &mut T { + assert_valid::(); + unsafe { mem::transmute(&mut self.data) } + } +} + +impl Default for CachePadded { + fn default() -> Self { + Self::new(Default::default()) + } +} + +// FIXME: support Drop by pulling out a version usable for statics +/* +impl Drop for CachePadded { + fn drop(&mut self) { + assert_valid::(); + let p: *mut T = mem::transmute(&self.data); + mem::drop(ptr::read(p)); + } +} +*/ + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cache_padded_store_u64() { + let x: CachePadded = CachePadded::new(17); + assert_eq!(*x, 17); + } + + #[test] + fn cache_padded_store_pair() { + let x: CachePadded<(u64, u64)> = CachePadded::new((17, 37)); + assert_eq!(x.0, 17); + assert_eq!(x.1, 37); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..29c4fac --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(const_fn))] + +pub mod atomic_option; +#[macro_use] +pub mod cache_padded; +pub mod scoped; diff --git a/src/scoped.rs b/src/scoped.rs new file mode 100644 index 0000000..a571a90 --- /dev/null +++ b/src/scoped.rs @@ -0,0 +1,364 @@ +/// Scoped thread. +/// +/// # Examples +/// +/// A basic scoped thread: +/// +/// ``` +/// crossbeam_utils::scoped::scope(|scope| { +/// scope.spawn(|| { +/// println!("Hello from a scoped thread!"); +/// }); +/// }); +/// ``` +/// +/// When writing concurrent Rust programs, you'll sometimes see a pattern like this, using +/// [`std::thread::spawn`][spawn]: +/// +/// ```ignore +/// let array = [1, 2, 3]; +/// let mut guards = vec![]; +/// +/// for i in &array { +/// let guard = std::thread::spawn(move || { +/// println!("element: {}", i); +/// }); +/// +/// guards.push(guard); +/// } +/// +/// for guard in guards { +/// guard.join().unwrap(); +/// } +/// ``` +/// +/// The basic pattern is: +/// +/// 1. Iterate over some collection. +/// 2. Spin up a thread to operate on each part of the collection. +/// 3. Join all the threads. +/// +/// However, this code actually gives an error: +/// +/// ```text +/// error: `array` does not live long enough +/// for i in &array { +/// ^~~~~ +/// in expansion of for loop expansion +/// note: expansion site +/// note: reference must be valid for the static lifetime... +/// note: ...but borrowed value is only valid for the block suffix following statement 0 at ... +/// let array = [1, 2, 3]; +/// let mut guards = vec![]; +/// +/// for i in &array { +/// let guard = std::thread::spawn(move || { +/// println!("element: {}", i); +/// ... +/// error: aborting due to previous error +/// ``` +/// +/// Because [`std::thread::spawn`][spawn] doesn't know about this scope, it requires a +/// `'static` lifetime. One way of giving it a proper lifetime is to use an [`Arc`][arc]: +/// +/// [arc]: http://doc.rust-lang.org/stable/std/sync/struct.Arc.html +/// +/// ``` +/// use std::sync::Arc; +/// +/// let array = Arc::new([1, 2, 3]); +/// let mut guards = vec![]; +/// +/// for i in 0..array.len() { +/// let a = array.clone(); +/// +/// let guard = std::thread::spawn(move || { +/// println!("element: {}", a[i]); +/// }); +/// +/// guards.push(guard); +/// } +/// +/// for guard in guards { +/// guard.join().unwrap(); +/// } +/// ``` +/// +/// But this introduces unnecessary allocation, as `Arc` puts its data on the heap, and we +/// also end up dealing with reference counts. We know that we're joining the threads before +/// our function returns, so just taking a reference _should_ be safe. Rust can't know that, +/// though. +/// +/// Enter scoped threads. Here's our original example, using `spawn` from crossbeam rather +/// than from `std::thread`: +/// +/// ``` +/// let array = [1, 2, 3]; +/// +/// crossbeam_utils::scoped::scope(|scope| { +/// for i in &array { +/// scope.spawn(move || { +/// println!("element: {}", i); +/// }); +/// } +/// }); +/// ``` +/// +/// Much more straightforward. +// FIXME(jeehoonkang): maybe we should create a new crate for scoped threads. + +use std::cell::RefCell; +use std::fmt; +use std::mem; +use std::rc::Rc; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; +use std::io; + +use atomic_option::AtomicOption; + +#[doc(hidden)] +trait FnBox { + fn call_box(self: Box); +} + +impl FnBox for F { + fn call_box(self: Box) { + (*self)() + } +} + +/// Like `std::thread::spawn`, but without the closure bounds. +pub unsafe fn spawn_unsafe<'a, F>(f: F) -> thread::JoinHandle<()> +where + F: FnOnce() + Send + 'a, +{ + let builder = thread::Builder::new(); + builder_spawn_unsafe(builder, f).unwrap() +} + +/// Like `std::thread::Builder::spawn`, but without the closure bounds. +pub unsafe fn builder_spawn_unsafe<'a, F>( + builder: thread::Builder, + f: F, +) -> io::Result> +where + F: FnOnce() + Send + 'a, +{ + use std::mem; + + let closure: Box = Box::new(f); + let closure: Box = mem::transmute(closure); + builder.spawn(move || closure.call_box()) +} + + +pub struct Scope<'a> { + dtors: RefCell>>, +} + +struct DtorChain<'a> { + dtor: Box, + next: Option>>, +} + +enum JoinState { + Running(thread::JoinHandle<()>), + Joined, +} + +impl JoinState { + fn join(&mut self) { + let mut state = JoinState::Joined; + mem::swap(self, &mut state); + if let JoinState::Running(handle) = state { + let res = handle.join(); + + if !thread::panicking() { + res.unwrap(); + } + } + } +} + +/// A handle to a scoped thread +pub struct ScopedJoinHandle { + inner: Rc>, + packet: Arc>, + thread: thread::Thread, +} + +/// Create a new `scope`, for deferred destructors. +/// +/// Scopes, in particular, support [*scoped thread spawning*](struct.Scope.html#method.spawn). +/// +/// # Examples +/// +/// Creating and using a scope: +/// +/// ``` +/// crossbeam_utils::scoped::scope(|scope| { +/// scope.defer(|| println!("Exiting scope")); +/// scope.spawn(|| println!("Running child thread in scope")) +/// }); +/// // Prints messages in the reverse order written +/// ``` +pub fn scope<'a, F, R>(f: F) -> R +where + F: FnOnce(&Scope<'a>) -> R, +{ + let mut scope = Scope { dtors: RefCell::new(None) }; + let ret = f(&scope); + scope.drop_all(); + ret +} + +impl<'a> fmt::Debug for Scope<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Scope {{ ... }}") + } +} + +impl fmt::Debug for ScopedJoinHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ScopedJoinHandle {{ ... }}") + } +} + +impl<'a> Scope<'a> { + // This method is carefully written in a transactional style, so + // that it can be called directly and, if any dtor panics, can be + // resumed in the unwinding this causes. By initially running the + // method outside of any destructor, we avoid any leakage problems + // due to @rust-lang/rust#14875. + fn drop_all(&mut self) { + loop { + // use a separate scope to ensure that the RefCell borrow + // is relinquished before running `dtor` + let dtor = { + let mut dtors = self.dtors.borrow_mut(); + if let Some(mut node) = dtors.take() { + *dtors = node.next.take().map(|b| *b); + node.dtor + } else { + return; + } + }; + dtor.call_box() + } + } + + /// Schedule code to be executed when exiting the scope. + /// + /// This is akin to having a destructor on the stack, except that it is + /// *guaranteed* to be run. + pub fn defer(&self, f: F) + where + F: FnOnce() + 'a, + { + let mut dtors = self.dtors.borrow_mut(); + *dtors = Some(DtorChain { + dtor: Box::new(f), + next: dtors.take().map(Box::new), + }); + } + + /// Create a scoped thread. + /// + /// `spawn` is similar to the [`spawn`][spawn] function in Rust's standard library. The + /// difference is that this thread is scoped, meaning that it's guaranteed to terminate + /// before the current stack frame goes away, allowing you to reference the parent stack frame + /// directly. This is ensured by having the parent thread join on the child thread before the + /// scope exits. + /// + /// [spawn]: http://doc.rust-lang.org/std/thread/fn.spawn.html + pub fn spawn(&self, f: F) -> ScopedJoinHandle + where + F: FnOnce() -> T + Send + 'a, + T: Send + 'a, + { + self.builder().spawn(f).unwrap() + } + + /// Generates the base configuration for spawning a scoped thread, from which configuration + /// methods can be chained. + pub fn builder<'s>(&'s self) -> ScopedThreadBuilder<'s, 'a> { + ScopedThreadBuilder { + scope: self, + builder: thread::Builder::new(), + } + } +} + +/// Scoped thread configuration. Provides detailed control over the properties and behavior of new +/// scoped threads. +pub struct ScopedThreadBuilder<'s, 'a: 's> { + scope: &'s Scope<'a>, + builder: thread::Builder, +} + +impl<'s, 'a: 's> ScopedThreadBuilder<'s, 'a> { + /// Names the thread-to-be. Currently the name is used for identification only in panic + /// messages. + pub fn name(mut self, name: String) -> ScopedThreadBuilder<'s, 'a> { + self.builder = self.builder.name(name); + self + } + + /// Sets the size of the stack for the new thread. + pub fn stack_size(mut self, size: usize) -> ScopedThreadBuilder<'s, 'a> { + self.builder = self.builder.stack_size(size); + self + } + + /// Spawns a new thread, and returns a join handle for it. + pub fn spawn(self, f: F) -> io::Result> + where + F: FnOnce() -> T + Send + 'a, + T: Send + 'a, + { + let their_packet = Arc::new(AtomicOption::new()); + let my_packet = their_packet.clone(); + + let join_handle = try!(unsafe { + builder_spawn_unsafe(self.builder, move || { + their_packet.swap(f(), Ordering::Relaxed); + }) + }); + + let thread = join_handle.thread().clone(); + let deferred_handle = Rc::new(RefCell::new(JoinState::Running(join_handle))); + let my_handle = deferred_handle.clone(); + + self.scope.defer(move || { + let mut state = deferred_handle.borrow_mut(); + state.join(); + }); + + Ok(ScopedJoinHandle { + inner: my_handle, + packet: my_packet, + thread: thread, + }) + } +} + +impl ScopedJoinHandle { + /// Join the scoped thread, returning the result it produced. + pub fn join(self) -> T { + self.inner.borrow_mut().join(); + self.packet.take(Ordering::Relaxed).unwrap() + } + + /// Get the underlying thread handle. + pub fn thread(&self) -> &thread::Thread { + &self.thread + } +} + +impl<'a> Drop for Scope<'a> { + fn drop(&mut self) { + self.drop_all() + } +}