Skip to main content

sabi/
async_group.rs

1// Copyright (C) 2024-2026 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5use crate::{AsyncGroup, ErrEntry};
6
7use std::sync::Arc;
8use std::{mem, thread};
9
10/// The enum type representing the reasons for errors that can occur within an [`AsyncGroup`].
11#[derive(Debug)]
12pub enum AsyncGroupError {
13    /// Indicates that a spawned thread by [`AsyncGroup`] has panicked.
14    /// Contains the panic message if available.
15    ThreadPanicked(String),
16}
17
18impl AsyncGroup {
19    pub(crate) fn new() -> Self {
20        Self {
21            handlers: Vec::new(),
22            _index: 0,
23            _name: Default::default(),
24        }
25    }
26
27    /// Adds a task (a closure) to the group to be executed concurrently.
28    ///
29    /// This provided closure is executed in a new `std::thread` concurrently
30    /// with other added tasks.
31    ///
32    /// # Type Parameters
33    ///
34    /// * `F`: The type of the closure, which must be
35    ///   `FnOnce() -> errs::Result<()> + Send + 'static`.
36    ///
37    /// # Parameters
38    ///
39    /// * `f`: The closure to be executed in a separate thread.
40    pub fn add<F>(&mut self, f: F)
41    where
42        F: FnOnce() -> errs::Result<()> + Send + 'static,
43    {
44        self.handlers
45            .push((self._index, self._name.clone(), thread::spawn(f)));
46    }
47
48    pub(crate) fn join_and_collect_errors(mut self, errors: &mut Vec<ErrEntry>) {
49        if self.handlers.is_empty() {
50            return;
51        }
52
53        let vec = mem::take(&mut self.handlers);
54
55        for h in vec.into_iter() {
56            match h.2.join() {
57                Ok(r) => {
58                    if let Err(e) = r {
59                        errors.push(ErrEntry {
60                            index: h.0,
61                            name: h.1,
62                            err: e,
63                        });
64                    }
65                }
66                Err(e) => {
67                    let s = if let Some(s) = e.downcast_ref::<&'static str>() {
68                        s.to_string()
69                    } else if let Some(s) = e.downcast_ref::<String>() {
70                        s.clone()
71                    } else if let Some(s) = e.downcast_ref::<Arc<&str>>() {
72                        s.to_string()
73                    } else if let Some(s) = e.downcast_ref::<Arc<str>>() {
74                        s.to_string()
75                    } else {
76                        "Unknown panic payload".to_string()
77                    };
78                    let e = errs::Err::new(AsyncGroupError::ThreadPanicked(s));
79                    errors.push(ErrEntry {
80                        index: h.0,
81                        name: h.1,
82                        err: e,
83                    });
84                }
85            }
86        }
87    }
88
89    pub(crate) fn join_and_ignore_errors(mut self) {
90        if self.handlers.is_empty() {
91            return;
92        }
93
94        let vec = mem::take(&mut self.handlers);
95
96        for h in vec.into_iter() {
97            let _ = h.2.join();
98        }
99    }
100}
101
102#[cfg_attr(coverage_nightly, coverage(off))]
103#[cfg(test)]
104mod tests_of_async_group {
105    use super::*;
106    use std::{sync, time};
107
108    const BASE_LINE: u32 = line!();
109
110    #[derive(Debug, PartialEq)]
111    enum Reasons {
112        BadString(String),
113    }
114
115    struct MyStruct {
116        string: sync::Arc<sync::Mutex<String>>,
117        fail: bool,
118    }
119
120    impl MyStruct {
121        fn new(s: String, fail: bool) -> Self {
122            Self {
123                string: sync::Arc::new(sync::Mutex::new(s)),
124                fail,
125            }
126        }
127
128        fn process(&self, ag: &mut AsyncGroup) {
129            let s_mutex = self.string.clone();
130            let fail = self.fail;
131            ag.add(move || {
132                let _ = thread::sleep(time::Duration::from_millis(100));
133                {
134                    let mut s = s_mutex.lock().unwrap();
135                    if fail {
136                        return Err(errs::Err::new(Reasons::BadString(s.to_string())));
137                    }
138                    *s = s.to_uppercase();
139                }
140                Ok(())
141            });
142        }
143
144        fn process_multiple(&self, ag: &mut AsyncGroup) {
145            let s_mutex = self.string.clone();
146            let fail = self.fail;
147            ag.add(move || {
148                let _ = thread::sleep(time::Duration::from_millis(100));
149                {
150                    let mut s = s_mutex.lock().unwrap();
151                    if fail {
152                        return Err(errs::Err::new(Reasons::BadString(s.to_string())));
153                    }
154                    *s = s.to_uppercase();
155                }
156                Ok(())
157            });
158
159            let s_mutex = self.string.clone();
160            let fail = self.fail;
161            ag.add(move || {
162                let _ = thread::sleep(time::Duration::from_millis(100));
163                {
164                    let mut s = s_mutex.lock().unwrap();
165                    if fail {
166                        return Err(errs::Err::new(Reasons::BadString(s.to_string())));
167                    }
168                    *s = s.to_uppercase();
169                }
170                Ok(())
171            });
172        }
173    }
174
175    mod tests_of_join_and_collect_errors {
176        use super::*;
177        use std::panic::panic_any;
178
179        #[test]
180        fn zero() {
181            let ag = AsyncGroup::new();
182
183            let mut err_vec = Vec::new();
184            ag.join_and_collect_errors(&mut err_vec);
185
186            assert!(err_vec.is_empty());
187        }
188
189        #[test]
190        fn single_ok() {
191            let mut ag = AsyncGroup::new();
192
193            let struct_a = MyStruct::new("a".to_string(), false);
194            assert_eq!(*struct_a.string.lock().unwrap(), "a");
195
196            ag._index = 12;
197            struct_a.process(&mut ag);
198
199            let mut errors = Vec::new();
200            ag.join_and_collect_errors(&mut errors);
201
202            assert!(errors.is_empty());
203            assert_eq!(*struct_a.string.lock().unwrap(), "A");
204        }
205
206        #[test]
207        fn single_fail() {
208            let mut ag = AsyncGroup::new();
209
210            let struct_a = MyStruct::new("a".to_string(), true);
211            assert_eq!(*struct_a.string.lock().unwrap(), "a");
212
213            ag._index = 12;
214            ag._name = "foo".into();
215            struct_a.process(&mut ag);
216
217            let mut errors = Vec::new();
218            ag.join_and_collect_errors(&mut errors);
219
220            assert_eq!(errors.len(), 1);
221            assert_eq!(*struct_a.string.lock().unwrap(), "a");
222
223            assert_eq!(errors[0].index, 12);
224            assert_eq!(errors[0].name, "foo".into());
225            #[cfg(unix)]
226            assert_eq!(
227                format!("{:?}", errors[0].err),
228                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"a\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }"
229            );
230            #[cfg(windows)]
231            assert_eq!(
232                format!("{:?}", errors[0].err),
233                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"a\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }"
234            );
235        }
236
237        #[test]
238        fn multiple_ok() {
239            let mut ag = AsyncGroup::new();
240
241            let struct_a = MyStruct::new("a".to_string(), false);
242            assert_eq!(*struct_a.string.lock().unwrap(), "a".to_string());
243
244            let struct_b = MyStruct::new("b".to_string(), false);
245            assert_eq!(*struct_b.string.lock().unwrap(), "b".to_string());
246
247            let struct_c = MyStruct::new("c".to_string(), false);
248            assert_eq!(*struct_c.string.lock().unwrap(), "c".to_string());
249
250            ag._index = 12;
251            ag._name = "foo".into();
252            struct_a.process(&mut ag);
253
254            ag._index = 34;
255            ag._name = "bar".into();
256            struct_b.process(&mut ag);
257
258            ag._index = 56;
259            ag._name = "baz".into();
260            struct_c.process(&mut ag);
261
262            let mut err_vec = Vec::new();
263            ag.join_and_collect_errors(&mut err_vec);
264
265            assert_eq!(err_vec.len(), 0);
266
267            assert_eq!(*struct_a.string.lock().unwrap(), "A");
268            assert_eq!(*struct_b.string.lock().unwrap(), "B");
269            assert_eq!(*struct_c.string.lock().unwrap(), "C");
270        }
271
272        #[test]
273        fn multiple_processes_and_single_fail() {
274            let mut ag = AsyncGroup::new();
275
276            let struct_a = MyStruct::new("a".to_string(), false);
277            assert_eq!(*struct_a.string.lock().unwrap(), "a");
278
279            let struct_b = MyStruct::new("b".to_string(), true);
280            assert_eq!(*struct_b.string.lock().unwrap(), "b");
281
282            let struct_c = MyStruct::new("c".to_string(), false);
283            assert_eq!(*struct_c.string.lock().unwrap(), "c");
284
285            ag._index = 12;
286            ag._name = "foo".into();
287            struct_a.process(&mut ag);
288
289            ag._index = 34;
290            ag._name = "bar".into();
291            struct_b.process(&mut ag);
292
293            ag._index = 56;
294            ag._name = "baz".into();
295            struct_c.process(&mut ag);
296
297            let mut err_vec = Vec::new();
298            ag.join_and_collect_errors(&mut err_vec);
299
300            assert_eq!(err_vec.len(), 1);
301            assert_eq!(err_vec[0].index, 34);
302            assert_eq!(err_vec[0].name, "bar".into());
303            #[cfg(unix)]
304            assert_eq!(
305                format!("{:?}", err_vec[0].err),
306                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"b\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
307            );
308            #[cfg(windows)]
309            assert_eq!(
310                format!("{:?}", err_vec[0].err),
311                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"b\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
312            );
313
314            assert_eq!(*struct_a.string.lock().unwrap(), "A");
315            assert_eq!(*struct_b.string.lock().unwrap(), "b");
316            assert_eq!(*struct_c.string.lock().unwrap(), "C");
317        }
318
319        #[test]
320        fn multiple_fail() {
321            let mut ag = AsyncGroup::new();
322
323            let struct_a = MyStruct::new("a".to_string(), true);
324            assert_eq!(*struct_a.string.lock().unwrap(), "a");
325
326            let struct_b = MyStruct::new("b".to_string(), true);
327            assert_eq!(*struct_b.string.lock().unwrap(), "b");
328
329            let struct_c = MyStruct::new("c".to_string(), true);
330            assert_eq!(*struct_c.string.lock().unwrap(), "c");
331
332            ag._index = 12;
333            ag._name = "foo".into();
334            struct_a.process(&mut ag);
335
336            ag._index = 34;
337            ag._name = "bar".into();
338            struct_b.process(&mut ag);
339
340            ag._index = 56;
341            ag._name = "baz".into();
342            struct_c.process(&mut ag);
343
344            let mut err_vec = Vec::new();
345            ag.join_and_collect_errors(&mut err_vec);
346
347            assert_eq!(err_vec.len(), 3);
348
349            assert_eq!(err_vec[0].index, 12);
350            assert_eq!(err_vec[1].index, 34);
351            assert_eq!(err_vec[2].index, 56);
352
353            assert_eq!(err_vec[0].name, "foo".into());
354            assert_eq!(err_vec[1].name, "bar".into());
355            assert_eq!(err_vec[2].name, "baz".into());
356
357            #[cfg(unix)]
358            assert_eq!(
359                format!("{:?}", err_vec[0].err),
360                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"a\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
361            );
362            #[cfg(windows)]
363            assert_eq!(
364                format!("{:?}", err_vec[0].err),
365                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"a\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
366            );
367            #[cfg(unix)]
368            assert_eq!(
369                format!("{:?}", err_vec[1].err),
370                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"b\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
371            );
372            #[cfg(windows)]
373            assert_eq!(
374                format!("{:?}", err_vec[1].err),
375                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"b\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
376            );
377            #[cfg(unix)]
378            assert_eq!(
379                format!("{:?}", err_vec[2].err),
380                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"c\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
381            );
382            #[cfg(windows)]
383            assert_eq!(
384                format!("{:?}", err_vec[2].err),
385                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"c\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 28).to_string() + " }",
386            );
387
388            assert_eq!(*struct_a.string.lock().unwrap(), "a");
389            assert_eq!(*struct_b.string.lock().unwrap(), "b");
390            assert_eq!(*struct_c.string.lock().unwrap(), "c");
391        }
392
393        #[test]
394        fn data_src_execute_multiple_setup_handles() {
395            let mut ag = AsyncGroup::new();
396
397            let struct_d = MyStruct::new("d".to_string(), false);
398            assert_eq!(*struct_d.string.lock().unwrap(), "d");
399
400            ag._index = 123;
401            ag._name = "foo".into();
402            struct_d.process(&mut ag);
403
404            let mut err_vec = Vec::new();
405            ag.join_and_collect_errors(&mut err_vec);
406
407            assert_eq!(err_vec.len(), 0);
408
409            assert_eq!(*struct_d.string.lock().unwrap(), "D");
410        }
411
412        #[test]
413        fn collect_all_errors_if_data_src_executes_multiple_setup_handles() {
414            let mut ag = AsyncGroup::new();
415
416            let struct_d = MyStruct::new("d".to_string(), true);
417            assert_eq!(*struct_d.string.lock().unwrap(), "d");
418
419            ag._index = 123;
420            ag._name = "foo".into();
421            struct_d.process_multiple(&mut ag);
422
423            let mut err_vec = Vec::new();
424            ag.join_and_collect_errors(&mut err_vec);
425
426            assert_eq!(err_vec.len(), 2);
427
428            assert_eq!(err_vec[0].index, 123);
429            assert_eq!(err_vec[1].index, 123);
430
431            assert_eq!(err_vec[0].name, "foo".into());
432            assert_eq!(err_vec[1].name, "foo".into());
433
434            #[cfg(unix)]
435            assert_eq!(
436                format!("{:?}", err_vec[0].err),
437                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"d\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 44).to_string() + " }",
438            );
439            #[cfg(windows)]
440            assert_eq!(
441                format!("{:?}", err_vec[0].err),
442                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"d\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 44).to_string() + " }"
443            );
444
445            #[cfg(unix)]
446            assert_eq!(
447                format!("{:?}", err_vec[1].err),
448                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"d\"), file = src/async_group.rs, line = ".to_string() + &(BASE_LINE + 58).to_string() + " }",
449            );
450            #[cfg(windows)]
451            assert_eq!(
452                format!("{:?}", err_vec[1].err),
453                "errs::Err { reason = sabi::async_group::tests_of_async_group::Reasons BadString(\"d\"), file = src\\async_group.rs, line = ".to_string() + &(BASE_LINE + 58).to_string() + " }"
454            );
455
456            assert_eq!(*struct_d.string.lock().unwrap(), "d");
457        }
458
459        #[test]
460        fn async_code_calls_thread_panic_with_a_str() {
461            let mut ag = AsyncGroup::new();
462
463            ag._index = 123;
464            ag._name = "foo".into();
465
466            ag.add(|| {
467                panic!("panic");
468            });
469
470            let mut errors = Vec::<ErrEntry>::new();
471            ag.join_and_collect_errors(&mut errors);
472
473            assert_eq!(errors.len(), 1);
474            assert_eq!(errors[0].index, 123);
475            assert_eq!(errors[0].name, "foo".into());
476            #[cfg(unix)]
477            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src/async_group.rs, line = 78 }");
478            #[cfg(windows)]
479            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src\\async_group.rs, line = 78 }");
480        }
481
482        #[test]
483        fn async_code_calls_thread_panic_with_a_string() {
484            let mut ag = AsyncGroup::new();
485
486            ag._index = 123;
487            ag._name = "foo".into();
488
489            ag.add(|| {
490                panic_any("panic".to_string());
491            });
492
493            let mut errors = Vec::<ErrEntry>::new();
494            ag.join_and_collect_errors(&mut errors);
495
496            assert_eq!(errors.len(), 1);
497            assert_eq!(errors[0].index, 123);
498            assert_eq!(errors[0].name, "foo".into());
499            #[cfg(unix)]
500            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src/async_group.rs, line = 78 }");
501            #[cfg(windows)]
502            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src\\async_group.rs, line = 78 }");
503        }
504
505        #[test]
506        fn async_code_calls_thread_panic_with_an_arc_str_ref() {
507            let mut ag = AsyncGroup::new();
508
509            ag._index = 123;
510            ag._name = "foo".into();
511
512            ag.add(|| {
513                panic_any(Arc::new("panic"));
514            });
515
516            let mut errors = Vec::<ErrEntry>::new();
517            ag.join_and_collect_errors(&mut errors);
518
519            assert_eq!(errors.len(), 1);
520            assert_eq!(errors[0].index, 123);
521            assert_eq!(errors[0].name, "foo".into());
522            #[cfg(unix)]
523            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src/async_group.rs, line = 78 }");
524            #[cfg(windows)]
525            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src\\async_group.rs, line = 78 }");
526        }
527
528        #[test]
529        fn async_code_calls_thread_panic_with_an_arc_str() {
530            let mut ag = AsyncGroup::new();
531
532            ag._index = 123;
533            ag._name = "foo".into();
534
535            ag.add(|| {
536                panic_any(Arc::<str>::from("panic"));
537            });
538
539            let mut errors = Vec::<ErrEntry>::new();
540            ag.join_and_collect_errors(&mut errors);
541
542            assert_eq!(errors.len(), 1);
543            assert_eq!(errors[0].index, 123);
544            assert_eq!(errors[0].name, "foo".into());
545            #[cfg(unix)]
546            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src/async_group.rs, line = 78 }");
547            #[cfg(windows)]
548            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"panic\"), file = src\\async_group.rs, line = 78 }");
549        }
550
551        #[test]
552        fn async_code_calls_thread_panic_with_an_unknown_value() {
553            let mut ag = AsyncGroup::new();
554
555            ag._index = 123;
556            ag._name = "foo".into();
557
558            ag.add(|| {
559                panic_any(987);
560            });
561
562            let mut errors = Vec::<ErrEntry>::new();
563            ag.join_and_collect_errors(&mut errors);
564
565            assert_eq!(errors.len(), 1);
566            assert_eq!(errors[0].index, 123);
567            assert_eq!(errors[0].name, "foo".into());
568            #[cfg(unix)]
569            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"Unknown panic payload\"), file = src/async_group.rs, line = 78 }");
570            #[cfg(windows)]
571            assert_eq!(format!("{:?}", errors[0].err), "errs::Err { reason = sabi::async_group::AsyncGroupError ThreadPanicked(\"Unknown panic payload\"), file = src\\async_group.rs, line = 78 }");
572        }
573    }
574
575    mod tests_of_join_and_ignore_errors {
576        use super::*;
577
578        #[test]
579        fn zero() {
580            let ag = AsyncGroup::new();
581
582            ag.join_and_ignore_errors();
583        }
584
585        #[test]
586        fn single_ok() {
587            let mut ag = AsyncGroup::new();
588
589            let struct_a = MyStruct::new("a".to_string(), false);
590            assert_eq!(*struct_a.string.lock().unwrap(), "a");
591
592            ag._index = 123;
593            ag._name = "foo".into();
594            struct_a.process(&mut ag);
595
596            ag.join_and_ignore_errors();
597            assert_eq!(*struct_a.string.lock().unwrap(), "A");
598        }
599
600        #[test]
601        fn single_fail() {
602            let mut ag = AsyncGroup::new();
603
604            let struct_a = MyStruct::new("a".to_string(), true);
605            assert_eq!(*struct_a.string.lock().unwrap(), "a");
606
607            ag._index = 123;
608            ag._name = "foo".into();
609            struct_a.process(&mut ag);
610
611            ag.join_and_ignore_errors();
612            assert_eq!(*struct_a.string.lock().unwrap(), "a");
613        }
614
615        #[test]
616        fn multiple_ok() {
617            let mut ag = AsyncGroup::new();
618
619            let struct_a = MyStruct::new("a".to_string(), false);
620            assert_eq!(*struct_a.string.lock().unwrap(), "a");
621
622            let struct_b = MyStruct::new("b".to_string(), false);
623            assert_eq!(*struct_b.string.lock().unwrap(), "b");
624
625            let struct_c = MyStruct::new("c".to_string(), false);
626            assert_eq!(*struct_c.string.lock().unwrap(), "c");
627
628            ag._index = 123;
629            ag._name = "foo".into();
630            struct_a.process(&mut ag);
631
632            ag._index = 456;
633            ag._name = "bar".into();
634            struct_b.process(&mut ag);
635
636            ag._index = 789;
637            ag._name = "baz".into();
638            struct_c.process(&mut ag);
639
640            ag.join_and_ignore_errors();
641
642            assert_eq!(*struct_a.string.lock().unwrap(), "A");
643            assert_eq!(*struct_b.string.lock().unwrap(), "B");
644            assert_eq!(*struct_c.string.lock().unwrap(), "C");
645        }
646
647        #[test]
648        fn multiple_processes_and_single_fail() {
649            let mut ag = AsyncGroup::new();
650
651            let struct_a = MyStruct::new("a".to_string(), false);
652            assert_eq!(*struct_a.string.lock().unwrap(), "a");
653
654            let struct_b = MyStruct::new("b".to_string(), true);
655            assert_eq!(*struct_b.string.lock().unwrap(), "b");
656
657            let struct_c = MyStruct::new("c".to_string(), false);
658            assert_eq!(*struct_c.string.lock().unwrap(), "c");
659
660            ag._index = 123;
661            ag._name = "foo".into();
662            struct_a.process(&mut ag);
663
664            ag._index = 456;
665            ag._name = "bar".into();
666            struct_b.process(&mut ag);
667
668            ag._index = 789;
669            ag._name = "baz".into();
670            struct_c.process(&mut ag);
671
672            ag.join_and_ignore_errors();
673
674            assert_eq!(*struct_a.string.lock().unwrap(), "A");
675            assert_eq!(*struct_b.string.lock().unwrap(), "b");
676            assert_eq!(*struct_c.string.lock().unwrap(), "C");
677        }
678
679        #[test]
680        fn multiple_fail() {
681            let mut ag = AsyncGroup::new();
682
683            let struct_a = MyStruct::new("a".to_string(), true);
684            assert_eq!(*struct_a.string.lock().unwrap(), "a");
685
686            let struct_b = MyStruct::new("b".to_string(), true);
687            assert_eq!(*struct_b.string.lock().unwrap(), "b");
688
689            let struct_c = MyStruct::new("c".to_string(), true);
690            assert_eq!(*struct_c.string.lock().unwrap(), "c");
691
692            ag._index = 123;
693            ag._name = "foo".into();
694            struct_a.process(&mut ag);
695
696            ag._index = 456;
697            ag._name = "foo".into();
698            struct_b.process(&mut ag);
699
700            ag._index = 789;
701            ag._name = "foo".into();
702            struct_c.process(&mut ag);
703
704            ag.join_and_ignore_errors();
705
706            assert_eq!(*struct_a.string.lock().unwrap(), "a");
707            assert_eq!(*struct_b.string.lock().unwrap(), "b");
708            assert_eq!(*struct_c.string.lock().unwrap(), "c");
709        }
710    }
711}