├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── rust-toolchain └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "0.1.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 14 | 15 | [[package]] 16 | name = "crossbeam-channel" 17 | version = "0.4.2" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" 20 | dependencies = [ 21 | "crossbeam-utils", 22 | "maybe-uninit", 23 | ] 24 | 25 | [[package]] 26 | name = "crossbeam-deque" 27 | version = "0.7.3" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 30 | dependencies = [ 31 | "crossbeam-epoch", 32 | "crossbeam-utils", 33 | "maybe-uninit", 34 | ] 35 | 36 | [[package]] 37 | name = "crossbeam-epoch" 38 | version = "0.8.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 41 | dependencies = [ 42 | "autocfg", 43 | "cfg-if", 44 | "crossbeam-utils", 45 | "lazy_static", 46 | "maybe-uninit", 47 | "memoffset", 48 | "scopeguard", 49 | ] 50 | 51 | [[package]] 52 | name = "crossbeam-queue" 53 | version = "0.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" 56 | dependencies = [ 57 | "cfg-if", 58 | "crossbeam-utils", 59 | ] 60 | 61 | [[package]] 62 | name = "crossbeam-utils" 63 | version = "0.7.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 66 | dependencies = [ 67 | "autocfg", 68 | "cfg-if", 69 | "lazy_static", 70 | ] 71 | 72 | [[package]] 73 | name = "either" 74 | version = "1.5.3" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 77 | 78 | [[package]] 79 | name = "five_easy_pieces" 80 | version = "0.0.0" 81 | dependencies = [ 82 | "crossbeam-channel", 83 | "rayon", 84 | ] 85 | 86 | [[package]] 87 | name = "hermit-abi" 88 | version = "0.1.11" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" 91 | dependencies = [ 92 | "libc", 93 | ] 94 | 95 | [[package]] 96 | name = "lazy_static" 97 | version = "1.4.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 100 | 101 | [[package]] 102 | name = "libc" 103 | version = "0.2.69" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 106 | 107 | [[package]] 108 | name = "maybe-uninit" 109 | version = "2.0.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 112 | 113 | [[package]] 114 | name = "memoffset" 115 | version = "0.5.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 118 | dependencies = [ 119 | "autocfg", 120 | ] 121 | 122 | [[package]] 123 | name = "num_cpus" 124 | version = "1.13.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 127 | dependencies = [ 128 | "hermit-abi", 129 | "libc", 130 | ] 131 | 132 | [[package]] 133 | name = "rayon" 134 | version = "1.3.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" 137 | dependencies = [ 138 | "crossbeam-deque", 139 | "either", 140 | "rayon-core", 141 | ] 142 | 143 | [[package]] 144 | name = "rayon-core" 145 | version = "1.7.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" 148 | dependencies = [ 149 | "crossbeam-deque", 150 | "crossbeam-queue", 151 | "crossbeam-utils", 152 | "lazy_static", 153 | "num_cpus", 154 | ] 155 | 156 | [[package]] 157 | name = "scopeguard" 158 | version = "1.1.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 161 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "five_easy_pieces" 3 | description = "Five easy pieces on Rust concurrency" 4 | repository = "https://github.com/gterzian/rust_five_easy_pieces" 5 | version = "0.0.0" 6 | authors = ["Gregory Terzian"] 7 | license = "MPL-2.0" 8 | 9 | [dependencies] 10 | crossbeam-channel = "0.4" 11 | rayon = "1" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Rust concurrency: five easy pieces. 2 | 3 | Code examples for https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a 4 | 5 | For notes on how to improve the reliability of the threading, see https://github.com/gterzian/rust_five_easy_pieces/pull/1 6 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2020-04-08 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate crossbeam_channel; 3 | extern crate rayon; 4 | 5 | use crossbeam_channel::unbounded; 6 | use std::collections::HashMap; 7 | use std::sync::{Arc, Condvar, Mutex}; 8 | use std::thread; 9 | 10 | fn main() {} 11 | 12 | #[test] 13 | fn first() { 14 | /// The messages sent from the "main" component, 15 | /// to the other component running in parallel. 16 | enum WorkMsg { 17 | Work(u8), 18 | Exit, 19 | } 20 | 21 | /// The messages sent from the "parallel" component, 22 | /// back to the "main component". 23 | enum ResultMsg { 24 | Result(u8), 25 | Exited, 26 | } 27 | 28 | let (work_sender, work_receiver) = unbounded(); 29 | let (result_sender, result_receiver) = unbounded(); 30 | 31 | // Spawn another component in parallel. 32 | let _ = thread::spawn(move || loop { 33 | // Receive, and handle, messages, 34 | // until told to exit. 35 | match work_receiver.recv() { 36 | Ok(WorkMsg::Work(num)) => { 37 | // Perform some "work", sending back the result. 38 | let _ = result_sender.send(ResultMsg::Result(num)); 39 | } 40 | Ok(WorkMsg::Exit) => { 41 | // Send a confirmation of exit. 42 | let _ = result_sender.send(ResultMsg::Exited); 43 | break; 44 | } 45 | _ => panic!("Error receiving a WorkMsg."), 46 | } 47 | }); 48 | 49 | // Send two pieces of "work", 50 | // followed by a request to exit. 51 | let _ = work_sender.send(WorkMsg::Work(0)); 52 | let _ = work_sender.send(WorkMsg::Work(1)); 53 | let _ = work_sender.send(WorkMsg::Exit); 54 | 55 | // A counter of work performed. 56 | let mut counter = 0; 57 | 58 | loop { 59 | match result_receiver.recv() { 60 | Ok(ResultMsg::Result(num)) => { 61 | // Assert that we're receiving results 62 | // in the same order that the requests were sent. 63 | assert_eq!(num, counter); 64 | counter += 1; 65 | } 66 | Ok(ResultMsg::Exited) => { 67 | // Assert that we're exiting 68 | // after having received two work results. 69 | assert_eq!(2, counter); 70 | break; 71 | } 72 | _ => panic!("Error receiving a ResultMsg."), 73 | } 74 | } 75 | } 76 | 77 | #[test] 78 | fn second() { 79 | enum WorkMsg { 80 | Work(u8), 81 | Exit, 82 | } 83 | 84 | enum ResultMsg { 85 | Result(u8), 86 | Exited, 87 | } 88 | 89 | let (work_sender, work_receiver) = unbounded(); 90 | let (result_sender, result_receiver) = unbounded(); 91 | let pool = rayon::ThreadPoolBuilder::new() 92 | .num_threads(2) 93 | .build() 94 | .unwrap(); 95 | 96 | let _ = thread::spawn(move || loop { 97 | match work_receiver.recv() { 98 | Ok(WorkMsg::Work(num)) => { 99 | // Clone the result sender, and move the clone 100 | // into the spawned worker. 101 | let result_sender = result_sender.clone(); 102 | pool.spawn(move || { 103 | // From a worker thread, 104 | // do some "work", 105 | // and send the result back. 106 | let _ = result_sender.send(ResultMsg::Result(num)); 107 | }); 108 | } 109 | Ok(WorkMsg::Exit) => { 110 | let _ = result_sender.send(ResultMsg::Exited); 111 | break; 112 | } 113 | _ => panic!("Error receiving a WorkMsg."), 114 | } 115 | }); 116 | 117 | let _ = work_sender.send(WorkMsg::Work(0)); 118 | let _ = work_sender.send(WorkMsg::Work(1)); 119 | let _ = work_sender.send(WorkMsg::Exit); 120 | 121 | loop { 122 | match result_receiver.recv() { 123 | Ok(ResultMsg::Result(_)) => { 124 | // We cannot make assertions about ordering anymore. 125 | } 126 | Ok(ResultMsg::Exited) => { 127 | // And neither can we make assertions 128 | // that the results have been received 129 | // prior to the exited message. 130 | break; 131 | } 132 | _ => panic!("Error receiving a ResultMsg."), 133 | } 134 | } 135 | } 136 | 137 | #[test] 138 | fn third() { 139 | enum WorkMsg { 140 | Work(u8), 141 | Exit, 142 | } 143 | 144 | enum ResultMsg { 145 | Result(u8), 146 | Exited, 147 | } 148 | 149 | let (work_sender, work_receiver) = unbounded(); 150 | let (result_sender, result_receiver) = unbounded(); 151 | // Add a new channel, used by workers 152 | // to notity the "parallel" component of having completed a unit of work. 153 | let (pool_result_sender, pool_result_receiver) = unbounded(); 154 | let mut ongoing_work = 0; 155 | let mut exiting = false; 156 | let pool = rayon::ThreadPoolBuilder::new() 157 | .num_threads(2) 158 | .build() 159 | .unwrap(); 160 | 161 | let _ = thread::spawn(move || loop { 162 | select! { 163 | recv(work_receiver) -> msg => { 164 | match msg { 165 | Ok(WorkMsg::Work(num)) => { 166 | let result_sender = result_sender.clone(); 167 | let pool_result_sender = pool_result_sender.clone(); 168 | 169 | // Note that we're starting a new unit of work on the pool. 170 | ongoing_work += 1; 171 | 172 | pool.spawn(move || { 173 | // 1. Send the result to the main component. 174 | let _ = result_sender.send(ResultMsg::Result(num)); 175 | 176 | // 2. Let the parallel component know we've completed a unit of work. 177 | let _ = pool_result_sender.send(()); 178 | }); 179 | }, 180 | Ok(WorkMsg::Exit) => { 181 | // Note that we've received the request to exit. 182 | exiting = true; 183 | 184 | // If there is no ongoing work, 185 | // we can immediately exit. 186 | if ongoing_work == 0 { 187 | let _ = result_sender.send(ResultMsg::Exited); 188 | break; 189 | } 190 | }, 191 | _ => panic!("Error receiving a WorkMsg."), 192 | } 193 | }, 194 | recv(pool_result_receiver) -> _ => { 195 | if ongoing_work == 0 { 196 | panic!("Received an unexpected pool result."); 197 | } 198 | 199 | // Note that a unit of work has been completed. 200 | ongoing_work -=1; 201 | 202 | // If there is no more ongoing work, 203 | // and we've received the request to exit, 204 | // now is the time to exit. 205 | if ongoing_work == 0 && exiting { 206 | let _ = result_sender.send(ResultMsg::Exited); 207 | break; 208 | } 209 | }, 210 | } 211 | }); 212 | 213 | let _ = work_sender.send(WorkMsg::Work(0)); 214 | let _ = work_sender.send(WorkMsg::Work(1)); 215 | let _ = work_sender.send(WorkMsg::Exit); 216 | 217 | let mut counter = 0; 218 | 219 | loop { 220 | match result_receiver.recv() { 221 | Ok(ResultMsg::Result(_)) => { 222 | // Count the units of work that have been completed. 223 | counter += 1; 224 | } 225 | Ok(ResultMsg::Exited) => { 226 | // Assert that we're exiting after having received 227 | // all results. 228 | assert_eq!(2, counter); 229 | break; 230 | } 231 | _ => panic!("Error receiving a ResultMsg."), 232 | } 233 | } 234 | } 235 | 236 | #[test] 237 | fn fourth() { 238 | enum WorkMsg { 239 | Work(u8), 240 | Exit, 241 | } 242 | 243 | #[derive(Debug, Eq, PartialEq)] 244 | enum WorkPerformed { 245 | FromCache, 246 | New, 247 | } 248 | 249 | enum ResultMsg { 250 | Result(u8, WorkPerformed), 251 | Exited, 252 | } 253 | 254 | #[derive(Eq, Hash, PartialEq)] 255 | struct CacheKey(u8); 256 | 257 | let (work_sender, work_receiver) = unbounded(); 258 | let (result_sender, result_receiver) = unbounded(); 259 | let (pool_result_sender, pool_result_receiver) = unbounded(); 260 | let mut ongoing_work = 0; 261 | let mut exiting = false; 262 | let pool = rayon::ThreadPoolBuilder::new() 263 | .num_threads(2) 264 | .build() 265 | .unwrap(); 266 | 267 | // A cache of "work", shared by the workers on the pool. 268 | let cache: Arc>> = Arc::new(Mutex::new(HashMap::new())); 269 | 270 | let _ = thread::spawn(move || loop { 271 | select! { 272 | recv(work_receiver) -> msg => { 273 | match msg { 274 | Ok(WorkMsg::Work(num)) => { 275 | let result_sender = result_sender.clone(); 276 | let pool_result_sender = pool_result_sender.clone(); 277 | let cache = cache.clone(); 278 | ongoing_work += 1; 279 | 280 | pool.spawn(move || { 281 | let num = { 282 | // Start of critical section on the cache. 283 | let cache = cache.lock().unwrap(); 284 | let key = CacheKey(num); 285 | if let Some(result) = cache.get(&key) { 286 | // We're getting a result from the cache, 287 | // send it back, 288 | // along with a flag indicating we got it from the cache. 289 | let _ = result_sender.send(ResultMsg::Result(result.clone(), WorkPerformed::FromCache)); 290 | let _ = pool_result_sender.send(()); 291 | return; 292 | } 293 | key.0 294 | // End of critical section on the cache. 295 | }; 296 | 297 | // Perform "expensive work" outside of the critical section. 298 | // work work work work work work... 299 | 300 | // Send the result back, indicating we had to perform the work. 301 | let _ = result_sender.send(ResultMsg::Result(num.clone(), WorkPerformed::New)); 302 | 303 | // Store the result of the "expensive work" in the cache. 304 | let mut cache = cache.lock().unwrap(); 305 | let key = CacheKey(num.clone()); 306 | cache.insert(key, num); 307 | 308 | let _ = pool_result_sender.send(()); 309 | }); 310 | }, 311 | Ok(WorkMsg::Exit) => { 312 | exiting = true; 313 | if ongoing_work == 0 { 314 | let _ = result_sender.send(ResultMsg::Exited); 315 | break; 316 | } 317 | }, 318 | _ => panic!("Error receiving a WorkMsg."), 319 | } 320 | }, 321 | recv(pool_result_receiver) -> _ => { 322 | if ongoing_work == 0 { 323 | panic!("Received an unexpected pool result."); 324 | } 325 | ongoing_work -=1; 326 | if ongoing_work == 0 && exiting { 327 | let _ = result_sender.send(ResultMsg::Exited); 328 | break; 329 | } 330 | }, 331 | } 332 | }); 333 | 334 | let _ = work_sender.send(WorkMsg::Work(0)); 335 | // Send two requests for the same "work" 336 | let _ = work_sender.send(WorkMsg::Work(1)); 337 | let _ = work_sender.send(WorkMsg::Work(1)); 338 | let _ = work_sender.send(WorkMsg::Exit); 339 | 340 | let mut counter = 0; 341 | 342 | loop { 343 | match result_receiver.recv() { 344 | Ok(ResultMsg::Result(_num, _cached)) => { 345 | counter += 1; 346 | // We cannot make assertions about `cached`. 347 | } 348 | Ok(ResultMsg::Exited) => { 349 | assert_eq!(3, counter); 350 | break; 351 | } 352 | _ => panic!("Error receiving a ResultMsg."), 353 | } 354 | } 355 | } 356 | 357 | #[test] 358 | fn fifth() { 359 | enum WorkMsg { 360 | Work(u8), 361 | Exit, 362 | } 363 | 364 | #[derive(Debug, Eq, PartialEq)] 365 | enum WorkPerformed { 366 | FromCache, 367 | New, 368 | } 369 | 370 | #[derive(Debug, Eq, PartialEq)] 371 | enum CacheState { 372 | Ready, 373 | WorkInProgress, 374 | } 375 | 376 | enum ResultMsg { 377 | Result(u8, WorkPerformed), 378 | Exited, 379 | } 380 | 381 | #[derive(Eq, Hash, PartialEq)] 382 | struct CacheKey(u8); 383 | 384 | let (work_sender, work_receiver) = unbounded(); 385 | let (result_sender, result_receiver) = unbounded(); 386 | let (pool_result_sender, pool_result_receiver) = unbounded(); 387 | let mut ongoing_work = 0; 388 | let mut exiting = false; 389 | let pool = rayon::ThreadPoolBuilder::new() 390 | .num_threads(2) 391 | .build() 392 | .unwrap(); 393 | let cache: Arc>> = Arc::new(Mutex::new(HashMap::new())); 394 | 395 | // A new `cache_state` shared piece of data, indicating whether for a given key, 396 | // the cache is ready to be read from. 397 | let cache_state: Arc, Condvar)>>>> = 398 | Arc::new(Mutex::new(HashMap::new())); 399 | 400 | let _ = thread::spawn(move || loop { 401 | select! { 402 | recv(work_receiver) -> msg => { 403 | match msg { 404 | Ok(WorkMsg::Work(num)) => { 405 | let result_sender = result_sender.clone(); 406 | let pool_result_sender = pool_result_sender.clone(); 407 | let cache = cache.clone(); 408 | let cache_state = cache_state.clone(); 409 | ongoing_work += 1; 410 | pool.spawn(move || { 411 | let num = { 412 | let (lock, cvar) = { 413 | // Start of critical section on `cache_state`. 414 | let mut state_map = cache_state.lock().unwrap(); 415 | &*state_map 416 | .entry(CacheKey(num.clone())) 417 | .or_insert_with(|| { 418 | Arc::new(( 419 | Mutex::new(CacheState::Ready), 420 | Condvar::new(), 421 | )) 422 | }) 423 | .clone() 424 | // End of critical section on `cache_state`. 425 | }; 426 | 427 | // Start of critical section on `state`. 428 | let mut state = lock.lock().unwrap(); 429 | 430 | // Note: the `while` loop is necessary 431 | // for the logic to be robust to spurious wake-ups. 432 | while let CacheState::WorkInProgress = *state { 433 | // Block until the state is `CacheState::Ready`. 434 | // 435 | // Note: this will atomically release the lock, 436 | // and reacquire it on wake-up. 437 | let current_state = cvar 438 | .wait(state) 439 | .unwrap(); 440 | state = current_state; 441 | } 442 | 443 | // Here, since we're out of the loop, 444 | // we can be certain that the state is "ready". 445 | assert_eq!(*state, CacheState::Ready); 446 | 447 | let (num, result) = { 448 | // Start of critical section on the cache. 449 | let cache = cache.lock().unwrap(); 450 | let key = CacheKey(num); 451 | let result = match cache.get(&key) { 452 | Some(result) => Some(result.clone()), 453 | None => None, 454 | }; 455 | (key.0, result) 456 | // End of critical section on the cache. 457 | }; 458 | 459 | if let Some(result) = result { 460 | // We're getting a result from the cache, 461 | // send it back, 462 | // along with a flag indicating we got it from the cache. 463 | let _ = result_sender.send(ResultMsg::Result(result, WorkPerformed::FromCache)); 464 | let _ = pool_result_sender.send(()); 465 | 466 | // Don't forget to notify the waiting thread, 467 | // if any, that the state is ready. 468 | cvar.notify_one(); 469 | return; 470 | } else { 471 | // If we didn't find a result in the cache, 472 | // switch the state to in-progress. 473 | *state = CacheState::WorkInProgress; 474 | num 475 | } 476 | // End of critical section on `state`. 477 | }; 478 | 479 | // Do some "expensive work", outside of any critical section. 480 | 481 | let _ = result_sender.send(ResultMsg::Result(num.clone(), WorkPerformed::New)); 482 | 483 | { 484 | // Start of critical section on the cache. 485 | // Insert the result of the work into the cache. 486 | let mut cache = cache.lock().unwrap(); 487 | let key = CacheKey(num.clone()); 488 | cache.insert(key, num); 489 | // End of critical section on the cache. 490 | } 491 | 492 | let (lock, cvar) = { 493 | let mut state_map = cache_state.lock().unwrap(); 494 | &*state_map 495 | .get_mut(&CacheKey(num)) 496 | .expect("Entry in cache state to have been previously inserted") 497 | .clone() 498 | }; 499 | // Re-enter the critical section on `state`. 500 | let mut state = lock.lock().unwrap(); 501 | 502 | // Here, since we've set it earlier, 503 | // and any other worker would wait 504 | // on the state to switch back to ready, 505 | // we can be certain the state is "in-progress". 506 | assert_eq!(*state, CacheState::WorkInProgress); 507 | 508 | // Switch the state to ready. 509 | *state = CacheState::Ready; 510 | 511 | // Notify the waiting thread, if any, that the state has changed. 512 | // This can be done while still inside the critical section. 513 | cvar.notify_one(); 514 | 515 | let _ = pool_result_sender.send(()); 516 | }); 517 | }, 518 | Ok(WorkMsg::Exit) => { 519 | exiting = true; 520 | if ongoing_work == 0 { 521 | let _ = result_sender.send(ResultMsg::Exited); 522 | break; 523 | } 524 | }, 525 | _ => panic!("Error receiving a WorkMsg."), 526 | } 527 | }, 528 | recv(pool_result_receiver) -> _ => { 529 | if ongoing_work == 0 { 530 | panic!("Received an unexpected pool result."); 531 | } 532 | ongoing_work -=1; 533 | if ongoing_work == 0 && exiting { 534 | let _ = result_sender.send(ResultMsg::Exited); 535 | break; 536 | } 537 | }, 538 | } 539 | }); 540 | 541 | let _ = work_sender.send(WorkMsg::Work(0)); 542 | let _ = work_sender.send(WorkMsg::Work(1)); 543 | let _ = work_sender.send(WorkMsg::Work(1)); 544 | let _ = work_sender.send(WorkMsg::Exit); 545 | 546 | let mut counter = 0; 547 | 548 | // A new counter for work on 1. 549 | let mut work_one_counter = 0; 550 | 551 | loop { 552 | match result_receiver.recv() { 553 | Ok(ResultMsg::Result(num, cached)) => { 554 | counter += 1; 555 | 556 | if num == 1 { 557 | work_one_counter += 1; 558 | } 559 | 560 | // Now we can assert that by the time 561 | // the second result for 1 has been received, 562 | // it came from the cache. 563 | if num == 1 && work_one_counter == 2 { 564 | assert_eq!(cached, WorkPerformed::FromCache); 565 | } 566 | } 567 | Ok(ResultMsg::Exited) => { 568 | assert_eq!(3, counter); 569 | break; 570 | } 571 | _ => panic!("Error receiving a ResultMsg."), 572 | } 573 | } 574 | } 575 | --------------------------------------------------------------------------------