├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── src └── lib.rs └── tests └── threads-living-too-long-demo.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - beta 5 | - stable 6 | before_script: 7 | - | 8 | pip install 'travis-cargo<0.2' --user && 9 | export PATH=$HOME/.local/bin:$PATH 10 | script: 11 | - | 12 | travis-cargo build && 13 | travis-cargo test && 14 | rustdoc --test README.md -L dependency=./target/debug/deps --extern scoped_threadpool=./target/debug/libscoped_threadpool.rlib && 15 | travis-cargo bench && 16 | travis-cargo --only stable doc 17 | after_success: 18 | - travis-cargo --only stable doc-upload 19 | env: 20 | global: 21 | - TRAVIS_CARGO_NIGHTLY_FEATURE=nightly 22 | - secure: WdOoICzaAJej1J9AbDWGu+xN58NWbzICGkgE1Lxe7gW8CdJbFeWEDL/M+kSKOVrRjoHRdHrpVP7zkTQzpwKzKhnXMGn2uzlRaC0MYvKPfO8OK3hZiiUDaP6kdwFTFrPbhTxZfLQBysuMWAWEy1CbJ+kjVFexoVMgG/KAPLc+26n6UkV7dMirW3anNY3gkgkHijnwdGinazzZB1fQjy8dHxGVnnAYhMAAoinpcrrhLPYy3k/6nB12Njc3dzXGYe33jepznjnsQejTS/Mk1HLL1Iov+Lj/lM6TQJA+wl7x62FE+uPBYEK2aTTybN79rajFXLAZH+ZBO2Kep7iVKqa1R/5bk93X6X6aBc7jcUA7B8dbOS1o2s1cU+T+xL0mk99UXeLPZDUkEHiHppoJGy3wdyUkZvQCdybVfGDvhf9FlmJ6AvSH8E4MICvlxoNitPE6LlGdO2si2Jc5O99/GvHczjfx6oYWqPIhGo8Zkg91t1JWbwGD3Umzzl2BYT9D6v5o2zMHWzPmso3NlvVeP2YvbteWqPCrtZO7AobaENJSiRTfbv/rj4aVzsRQUt6DFMqzZnNPvwDMXNf8JY+pAlxjDj3dHreDKjgKC3C2SO73ObwpqoFlsFB/iz18lIz3ylscr9PRcXXXbJL2uV7zZAant3bbqB62eS2Zfk4IsEzByNk= 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scoped_threadpool" 3 | version = "0.1.9" 4 | authors = ["Marvin Löbel "] 5 | license = "MIT" 6 | 7 | description = "A library for scoped and cached threadpools." 8 | readme = "README.md" 9 | documentation = "http://kimundi.github.io/scoped-threadpool-rs/scoped_threadpool/index.html" 10 | 11 | repository = "https://github.com/Kimundi/scoped-threadpool-rs" 12 | keywords = ["thread", "scoped", "pool", "cached", "threadpool"] 13 | 14 | [dev-dependencies] 15 | lazy_static = "1.0" 16 | 17 | [features] 18 | nightly = [] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marvin Löbel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | scoped-threadpool-rs 2 | ============== 3 | 4 | [![Travis-CI Status](https://travis-ci.org/Kimundi/scoped-threadpool-rs.png?branch=master)](https://travis-ci.org/Kimundi/scoped-threadpool-rs) 5 | 6 | A library for scoped and cached threadpools. 7 | 8 | For more details, see the [docs](http://kimundi.github.io/scoped-threadpool-rs/scoped_threadpool/index.html). 9 | 10 | # Getting Started 11 | 12 | [scoped-threadpool-rs is available on crates.io](https://crates.io/crates/scoped_threadpool). 13 | Add the following dependency to your Cargo manifest to get the latest version of the 0.1 branch: 14 | ```toml 15 | [dependencies] 16 | 17 | scoped_threadpool = "0.1.*" 18 | ``` 19 | 20 | To always get the latest version, add this git repository to your 21 | Cargo manifest: 22 | 23 | ```toml 24 | [dependencies.scoped_threadpool] 25 | git = "https://github.com/Kimundi/scoped-threadpool-rs" 26 | ``` 27 | # Example 28 | 29 | ```rust 30 | extern crate scoped_threadpool; 31 | use scoped_threadpool::Pool; 32 | 33 | fn main() { 34 | // Create a threadpool holding 4 threads 35 | let mut pool = Pool::new(4); 36 | 37 | let mut vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; 38 | 39 | // Use the threads as scoped threads that can 40 | // reference anything outside this closure 41 | pool.scoped(|scoped| { 42 | // Create references to each element in the vector ... 43 | for e in &mut vec { 44 | // ... and add 1 to it in a seperate thread 45 | scoped.execute(move || { 46 | *e += 1; 47 | }); 48 | } 49 | }); 50 | 51 | assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8]); 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a stable, safe and scoped threadpool. 2 | //! 3 | //! It can be used to execute a number of short-lived jobs in parallel 4 | //! without the need to respawn the underlying threads. 5 | //! 6 | //! Jobs are runnable by borrowing the pool for a given scope, during which 7 | //! an arbitrary number of them can be executed. These jobs can access data of 8 | //! any lifetime outside of the pools scope, which allows working on 9 | //! non-`'static` references in parallel. 10 | //! 11 | //! For safety reasons, a panic inside a worker thread will not be isolated, 12 | //! but rather propagate to the outside of the pool. 13 | //! 14 | //! # Examples: 15 | //! 16 | //! ```rust 17 | //! extern crate scoped_threadpool; 18 | //! use scoped_threadpool::Pool; 19 | //! 20 | //! fn main() { 21 | //! // Create a threadpool holding 4 threads 22 | //! let mut pool = Pool::new(4); 23 | //! 24 | //! let mut vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; 25 | //! 26 | //! // Use the threads as scoped threads that can 27 | //! // reference anything outside this closure 28 | //! pool.scoped(|scope| { 29 | //! // Create references to each element in the vector ... 30 | //! for e in &mut vec { 31 | //! // ... and add 1 to it in a seperate thread 32 | //! scope.execute(move || { 33 | //! *e += 1; 34 | //! }); 35 | //! } 36 | //! }); 37 | //! 38 | //! assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8]); 39 | //! } 40 | //! ``` 41 | 42 | #![cfg_attr(all(feature="nightly", test), feature(test))] 43 | #![cfg_attr(feature="nightly", feature(drop_types_in_const))] 44 | #![cfg_attr(all(feature="nightly", test), feature(core_intrinsics))] 45 | #![cfg_attr(feature="nightly", feature(const_fn))] 46 | #![cfg_attr(feature="nightly", feature(const_unsafe_cell_new))] 47 | 48 | #![warn(missing_docs)] 49 | 50 | #[macro_use] 51 | #[cfg(test)] 52 | extern crate lazy_static; 53 | 54 | use std::thread::{self, JoinHandle}; 55 | use std::sync::mpsc::{channel, Sender, Receiver, SyncSender, sync_channel, RecvError}; 56 | use std::sync::{Arc, Mutex}; 57 | use std::marker::PhantomData; 58 | use std::mem; 59 | 60 | enum Message { 61 | NewJob(Thunk<'static>), 62 | Join, 63 | } 64 | 65 | trait FnBox { 66 | fn call_box(self: Box); 67 | } 68 | 69 | impl FnBox for F { 70 | fn call_box(self: Box) { 71 | (*self)() 72 | } 73 | } 74 | 75 | type Thunk<'a> = Box; 76 | 77 | impl Drop for Pool { 78 | fn drop(&mut self) { 79 | self.job_sender = None; 80 | } 81 | } 82 | 83 | /// A threadpool that acts as a handle to a number 84 | /// of threads spawned at construction. 85 | pub struct Pool { 86 | threads: Vec, 87 | job_sender: Option> 88 | } 89 | 90 | struct ThreadData { 91 | _thread_join_handle: JoinHandle<()>, 92 | pool_sync_rx: Receiver<()>, 93 | thread_sync_tx: SyncSender<()>, 94 | } 95 | 96 | impl Pool { 97 | /// Construct a threadpool with the given number of threads. 98 | /// Minimum value is `1`. 99 | pub fn new(n: u32) -> Pool { 100 | assert!(n >= 1); 101 | 102 | let (job_sender, job_receiver) = channel(); 103 | let job_receiver = Arc::new(Mutex::new(job_receiver)); 104 | 105 | let mut threads = Vec::with_capacity(n as usize); 106 | 107 | // spawn n threads, put them in waiting mode 108 | for _ in 0..n { 109 | let job_receiver = job_receiver.clone(); 110 | 111 | let (pool_sync_tx, pool_sync_rx) = 112 | sync_channel::<()>(0); 113 | let (thread_sync_tx, thread_sync_rx) = 114 | sync_channel::<()>(0); 115 | 116 | let thread = thread::spawn(move || { 117 | loop { 118 | let message = { 119 | // Only lock jobs for the time it takes 120 | // to get a job, not run it. 121 | let lock = job_receiver.lock().unwrap(); 122 | lock.recv() 123 | }; 124 | 125 | match message { 126 | Ok(Message::NewJob(job)) => { 127 | job.call_box(); 128 | } 129 | Ok(Message::Join) => { 130 | // Syncronize/Join with pool. 131 | // This has to be a two step 132 | // process to ensure that all threads 133 | // finished their work before the pool 134 | // can continue 135 | 136 | // Wait until the pool started syncing with threads 137 | if pool_sync_tx.send(()).is_err() { 138 | // The pool was dropped. 139 | break; 140 | } 141 | 142 | // Wait until the pool finished syncing with threads 143 | if thread_sync_rx.recv().is_err() { 144 | // The pool was dropped. 145 | break; 146 | } 147 | } 148 | Err(..) => { 149 | // The pool was dropped. 150 | break 151 | } 152 | } 153 | } 154 | }); 155 | 156 | threads.push(ThreadData { 157 | _thread_join_handle: thread, 158 | pool_sync_rx: pool_sync_rx, 159 | thread_sync_tx: thread_sync_tx, 160 | }); 161 | } 162 | 163 | Pool { 164 | threads: threads, 165 | job_sender: Some(job_sender), 166 | } 167 | } 168 | 169 | /// Borrows the pool and allows executing jobs on other 170 | /// threads during that scope via the argument of the closure. 171 | /// 172 | /// This method will block until the closure and all its jobs have 173 | /// run to completion. 174 | pub fn scoped<'pool, 'scope, F, R>(&'pool mut self, f: F) -> R 175 | where F: FnOnce(&Scope<'pool, 'scope>) -> R 176 | { 177 | let scope = Scope { 178 | pool: self, 179 | _marker: PhantomData, 180 | }; 181 | f(&scope) 182 | } 183 | 184 | /// Returns the number of threads inside this pool. 185 | pub fn thread_count(&self) -> u32 { 186 | self.threads.len() as u32 187 | } 188 | } 189 | 190 | ///////////////////////////////////////////////////////////////////////////// 191 | 192 | /// Handle to the scope during which the threadpool is borrowed. 193 | pub struct Scope<'pool, 'scope> { 194 | pool: &'pool mut Pool, 195 | // The 'scope needs to be invariant... it seems? 196 | _marker: PhantomData<::std::cell::Cell<&'scope mut ()>>, 197 | } 198 | 199 | impl<'pool, 'scope> Scope<'pool, 'scope> { 200 | /// Execute a job on the threadpool. 201 | /// 202 | /// The body of the closure will be send to one of the 203 | /// internal threads, and this method itself will not wait 204 | /// for its completion. 205 | pub fn execute(&self, f: F) where F: FnOnce() + Send + 'scope { 206 | self.execute_(f) 207 | } 208 | 209 | fn execute_(&self, f: F) where F: FnOnce() + Send + 'scope { 210 | let b = unsafe { 211 | mem::transmute::, Thunk<'static>>(Box::new(f)) 212 | }; 213 | self.pool.job_sender.as_ref().unwrap().send(Message::NewJob(b)).unwrap(); 214 | } 215 | 216 | /// Blocks until all currently queued jobs have run to completion. 217 | pub fn join_all(&self) { 218 | for _ in 0..self.pool.threads.len() { 219 | self.pool.job_sender.as_ref().unwrap().send(Message::Join).unwrap(); 220 | } 221 | 222 | // Synchronize/Join with threads 223 | // This has to be a two step process 224 | // to make sure _all_ threads received _one_ Join message each. 225 | 226 | // This loop will block on every thread until it 227 | // received and reacted to its Join message. 228 | let mut worker_panic = false; 229 | for thread_data in &self.pool.threads { 230 | if let Err(RecvError) = thread_data.pool_sync_rx.recv() { 231 | worker_panic = true; 232 | } 233 | } 234 | if worker_panic { 235 | // Now that all the threads are paused, we can safely panic 236 | panic!("Thread pool worker panicked"); 237 | } 238 | 239 | // Once all threads joined the jobs, send them a continue message 240 | for thread_data in &self.pool.threads { 241 | thread_data.thread_sync_tx.send(()).unwrap(); 242 | } 243 | } 244 | } 245 | 246 | impl<'pool, 'scope> Drop for Scope<'pool, 'scope> { 247 | fn drop(&mut self) { 248 | self.join_all(); 249 | } 250 | } 251 | 252 | ///////////////////////////////////////////////////////////////////////////// 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | #![cfg_attr(feature="nightly", allow(unused_unsafe))] 257 | 258 | use super::Pool; 259 | use std::thread; 260 | use std::sync; 261 | use std::time; 262 | 263 | fn sleep_ms(ms: u64) { 264 | thread::sleep(time::Duration::from_millis(ms)); 265 | } 266 | 267 | #[test] 268 | fn smoketest() { 269 | let mut pool = Pool::new(4); 270 | 271 | for i in 1..7 { 272 | let mut vec = vec![0, 1, 2, 3, 4]; 273 | pool.scoped(|s| { 274 | for e in vec.iter_mut() { 275 | s.execute(move || { 276 | *e += i; 277 | }); 278 | } 279 | }); 280 | 281 | let mut vec2 = vec![0, 1, 2, 3, 4]; 282 | for e in vec2.iter_mut() { 283 | *e += i; 284 | } 285 | 286 | assert_eq!(vec, vec2); 287 | } 288 | } 289 | 290 | #[test] 291 | #[should_panic] 292 | fn thread_panic() { 293 | let mut pool = Pool::new(4); 294 | pool.scoped(|scoped| { 295 | scoped.execute(move || { 296 | panic!() 297 | }); 298 | }); 299 | } 300 | 301 | #[test] 302 | #[should_panic] 303 | fn scope_panic() { 304 | let mut pool = Pool::new(4); 305 | pool.scoped(|_scoped| { 306 | panic!() 307 | }); 308 | } 309 | 310 | #[test] 311 | #[should_panic] 312 | fn pool_panic() { 313 | let _pool = Pool::new(4); 314 | panic!() 315 | } 316 | 317 | #[test] 318 | fn join_all() { 319 | let mut pool = Pool::new(4); 320 | 321 | let (tx_, rx) = sync::mpsc::channel(); 322 | 323 | pool.scoped(|scoped| { 324 | let tx = tx_.clone(); 325 | scoped.execute(move || { 326 | sleep_ms(1000); 327 | tx.send(2).unwrap(); 328 | }); 329 | 330 | let tx = tx_.clone(); 331 | scoped.execute(move || { 332 | tx.send(1).unwrap(); 333 | }); 334 | 335 | scoped.join_all(); 336 | 337 | let tx = tx_.clone(); 338 | scoped.execute(move || { 339 | tx.send(3).unwrap(); 340 | }); 341 | }); 342 | 343 | assert_eq!(rx.iter().take(3).collect::>(), vec![1, 2, 3]); 344 | } 345 | 346 | #[test] 347 | fn join_all_with_thread_panic() { 348 | use std::sync::mpsc::Sender; 349 | struct OnScopeEnd(Sender); 350 | impl Drop for OnScopeEnd { 351 | fn drop(&mut self) { 352 | self.0.send(1).unwrap(); 353 | sleep_ms(200); 354 | } 355 | } 356 | let (tx_, rx) = sync::mpsc::channel(); 357 | // Use a thread here to handle the expected panic from the pool. Should 358 | // be switched to use panic::recover instead when it becomes stable. 359 | let handle = thread::spawn(move || { 360 | let mut pool = Pool::new(8); 361 | let _on_scope_end = OnScopeEnd(tx_.clone()); 362 | pool.scoped(|scoped| { 363 | scoped.execute(move || { 364 | sleep_ms(100); 365 | panic!(); 366 | }); 367 | for _ in 1..8 { 368 | let tx = tx_.clone(); 369 | scoped.execute(move || { 370 | sleep_ms(200); 371 | tx.send(0).unwrap(); 372 | }); 373 | } 374 | }); 375 | }); 376 | if let Ok(..) = handle.join() { 377 | panic!("Pool didn't panic as expected"); 378 | } 379 | // If the `1` that OnScopeEnd sent occurs anywhere else than at the 380 | // end, that means that a worker thread was still running even 381 | // after the `scoped` call finished, which is unsound. 382 | let values: Vec = rx.into_iter().collect(); 383 | assert_eq!(&values[..], &[0, 0, 0, 0, 0, 0, 0, 1]); 384 | } 385 | 386 | #[test] 387 | fn safe_execute() { 388 | let mut pool = Pool::new(4); 389 | pool.scoped(|scoped| { 390 | scoped.execute(move || { 391 | }); 392 | }); 393 | } 394 | } 395 | 396 | #[cfg(all(test, feature="nightly"))] 397 | mod benches { 398 | extern crate test; 399 | 400 | use self::test::{Bencher, black_box}; 401 | use super::Pool; 402 | use std::sync::Mutex; 403 | 404 | // const MS_SLEEP_PER_OP: u32 = 1; 405 | 406 | lazy_static! { 407 | static ref POOL_1: Mutex = Mutex::new(Pool::new(1)); 408 | static ref POOL_2: Mutex = Mutex::new(Pool::new(2)); 409 | static ref POOL_3: Mutex = Mutex::new(Pool::new(3)); 410 | static ref POOL_4: Mutex = Mutex::new(Pool::new(4)); 411 | static ref POOL_5: Mutex = Mutex::new(Pool::new(5)); 412 | static ref POOL_8: Mutex = Mutex::new(Pool::new(8)); 413 | } 414 | 415 | fn fib(n: u64) -> u64 { 416 | let mut prev_prev: u64 = 1; 417 | let mut prev = 1; 418 | let mut current = 1; 419 | for _ in 2..(n+1) { 420 | current = prev_prev.wrapping_add(prev); 421 | prev_prev = prev; 422 | prev = current; 423 | } 424 | current 425 | } 426 | 427 | fn threads_interleaved_n(pool: &mut Pool) { 428 | let size = 1024; // 1kiB 429 | 430 | let mut data = vec![1u8; size]; 431 | pool.scoped(|s| { 432 | for e in data.iter_mut() { 433 | s.execute(move || { 434 | *e += fib(black_box(1000 * (*e as u64))) as u8; 435 | for i in 0..10000 { black_box(i); } 436 | //sleep_ms(MS_SLEEP_PER_OP); 437 | }); 438 | } 439 | }); 440 | } 441 | 442 | #[bench] 443 | fn threads_interleaved_1(b: &mut Bencher) { 444 | b.iter(|| threads_interleaved_n(&mut POOL_1.lock().unwrap())) 445 | } 446 | 447 | #[bench] 448 | fn threads_interleaved_2(b: &mut Bencher) { 449 | b.iter(|| threads_interleaved_n(&mut POOL_2.lock().unwrap())) 450 | } 451 | 452 | #[bench] 453 | fn threads_interleaved_4(b: &mut Bencher) { 454 | b.iter(|| threads_interleaved_n(&mut POOL_4.lock().unwrap())) 455 | } 456 | 457 | #[bench] 458 | fn threads_interleaved_8(b: &mut Bencher) { 459 | b.iter(|| threads_interleaved_n(&mut POOL_8.lock().unwrap())) 460 | } 461 | 462 | fn threads_chunked_n(pool: &mut Pool) { 463 | // Set this to 1GB and 40 to get good but slooow results 464 | let size = 1024 * 1024 * 10 / 4; // 10MiB 465 | let bb_repeat = 50; 466 | 467 | let n = pool.thread_count(); 468 | let mut data = vec![0u32; size]; 469 | pool.scoped(|s| { 470 | let l = (data.len() - 1) / n as usize + 1; 471 | for es in data.chunks_mut(l) { 472 | s.execute(move || { 473 | if es.len() > 1 { 474 | es[0] = 1; 475 | es[1] = 1; 476 | for i in 2..es.len() { 477 | // Fibonnaci gets big fast, 478 | // so just wrap around all the time 479 | es[i] = black_box(es[i-1].wrapping_add(es[i-2])); 480 | for i in 0..bb_repeat { black_box(i); } 481 | } 482 | } 483 | //sleep_ms(MS_SLEEP_PER_OP); 484 | }); 485 | } 486 | }); 487 | } 488 | 489 | #[bench] 490 | fn threads_chunked_1(b: &mut Bencher) { 491 | b.iter(|| threads_chunked_n(&mut POOL_1.lock().unwrap())) 492 | } 493 | 494 | #[bench] 495 | fn threads_chunked_2(b: &mut Bencher) { 496 | b.iter(|| threads_chunked_n(&mut POOL_2.lock().unwrap())) 497 | } 498 | 499 | #[bench] 500 | fn threads_chunked_3(b: &mut Bencher) { 501 | b.iter(|| threads_chunked_n(&mut POOL_3.lock().unwrap())) 502 | } 503 | 504 | #[bench] 505 | fn threads_chunked_4(b: &mut Bencher) { 506 | b.iter(|| threads_chunked_n(&mut POOL_4.lock().unwrap())) 507 | } 508 | 509 | #[bench] 510 | fn threads_chunked_5(b: &mut Bencher) { 511 | b.iter(|| threads_chunked_n(&mut POOL_5.lock().unwrap())) 512 | } 513 | 514 | #[bench] 515 | fn threads_chunked_8(b: &mut Bencher) { 516 | b.iter(|| threads_chunked_n(&mut POOL_8.lock().unwrap())) 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /tests/threads-living-too-long-demo.rs: -------------------------------------------------------------------------------- 1 | extern crate scoped_threadpool; 2 | 3 | use scoped_threadpool::Pool; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; 6 | use std::panic::AssertUnwindSafe; 7 | 8 | // The representation invariant for PositivelyAtomic is that it is 9 | // always a positive integer. When we drop it, we store zero in 10 | // its value; but one should never observe that. 11 | pub struct PositivelyAtomic(AtomicUsize); 12 | impl Drop for PositivelyAtomic { 13 | fn drop(&mut self) { 14 | // Since we are being dropped, we will now break the 15 | // representation invariant. (And then clients will 16 | // subsequently observe that breakage.) 17 | self.0.store(0, Ordering::Relaxed); 18 | } 19 | } 20 | impl PositivelyAtomic { 21 | pub fn new(x: usize) -> PositivelyAtomic { 22 | assert!(x > 0); 23 | PositivelyAtomic(AtomicUsize::new(x)) 24 | } 25 | // Assuming the representation invariant holds, this should 26 | // always return a positive value. 27 | pub fn load(&self) -> usize { 28 | self.0.load(Ordering::Relaxed) 29 | } 30 | pub fn cas(&self, old: usize, new: usize) -> usize { 31 | assert!(new > 0); 32 | self.0.compare_and_swap(old, new, Ordering::Relaxed) 33 | } 34 | } 35 | 36 | #[test] 37 | fn demo_stack_allocated() { 38 | static SAW_ZERO: AtomicBool = ATOMIC_BOOL_INIT; 39 | for _i in 0..100 { 40 | let saw_zero = &AssertUnwindSafe(&SAW_ZERO); 41 | let _p = ::std::panic::catch_unwind(move || { 42 | let p = PositivelyAtomic::new(1); 43 | kernel(&p, saw_zero); 44 | }); 45 | 46 | if saw_zero.load(Ordering::Relaxed) { 47 | panic!("demo_stack_allocated saw zero!"); 48 | } 49 | } 50 | } 51 | 52 | #[test] 53 | fn demo_heap_allocated() { 54 | static SAW_ZERO: AtomicBool = ATOMIC_BOOL_INIT; 55 | for i in 0..100 { 56 | let saw_zero = &AssertUnwindSafe(&SAW_ZERO); 57 | let _p = ::std::panic::catch_unwind(move || { 58 | let mut v = Vec::with_capacity((i % 5)*1024 + i); 59 | v.push(PositivelyAtomic::new(1)); 60 | kernel(&v[0], saw_zero); 61 | }); 62 | 63 | if saw_zero.load(Ordering::Relaxed) { 64 | panic!("demo_heap_allocated saw zero!"); 65 | } 66 | } 67 | } 68 | 69 | pub fn kernel(r: &PositivelyAtomic, saw_zero: &AtomicBool) { 70 | // Create a threadpool holding 4 threads 71 | let mut pool = Pool::new(4); 72 | 73 | // Use the threads as scoped threads that can 74 | // reference anything outside this closure 75 | pool.scoped(|scope| { 76 | // Create references to each element in the vector ... 77 | for _ in 0..4 { 78 | scope.execute(move || { 79 | for _ in 0..100000 { 80 | let v = r.load(); 81 | if v == 0 { 82 | saw_zero.store(true, Ordering::Relaxed); 83 | panic!("SAW ZERO"); 84 | } 85 | let v_new = (v % 100) + 1; 86 | if v != r.cas(v, v_new) { 87 | 88 | // this is not a true panic condition 89 | // in the original scenario. 90 | // 91 | // it rather is a rare event, and I want to 92 | // emulate a rare panic occurring from one 93 | // thread (and then see how the overall 94 | // computation proceeds from there). 95 | panic!("interference"); 96 | } else { 97 | // incremented successfully 98 | } 99 | } 100 | }); 101 | } 102 | }); 103 | } 104 | --------------------------------------------------------------------------------