1
//! `tor_memtrack::tracker::test`
2

            
3
// @@ begin test lint list maintained by maint/add_warning @@
4
#![allow(clippy::bool_assert_comparison)]
5
#![allow(clippy::clone_on_copy)]
6
#![allow(clippy::dbg_macro)]
7
#![allow(clippy::mixed_attributes_style)]
8
#![allow(clippy::print_stderr)]
9
#![allow(clippy::print_stdout)]
10
#![allow(clippy::single_char_pattern)]
11
#![allow(clippy::unwrap_used)]
12
#![allow(clippy::unchecked_duration_subtraction)]
13
#![allow(clippy::useless_vec)]
14
#![allow(clippy::needless_pass_by_value)]
15
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
16
#![allow(clippy::let_and_return)] // TODO this lint is annoying and we should disable it
17
#![allow(clippy::arithmetic_side_effects)] // don't mind potential panicking ops in tests
18

            
19
use super::*;
20

            
21
use std::collections::BTreeMap;
22
use std::fmt::{Display, Write as _};
23
use std::time::Duration;
24

            
25
use itertools::Itertools;
26
use rand::Rng;
27
use slotmap_careful::Key;
28
use tracing_test::traced_test;
29

            
30
use tor_basic_utils::RngExt as _;
31
use tor_rtcompat::{CoarseDuration, Runtime};
32
use tor_rtmock::MockRuntime;
33

            
34
//---------- useful utilities ----------
35

            
36
pub(crate) const TEST_DEFAULT_LIMIT: usize = mbytes(20);
37
pub(crate) const TEST_DEFAULT_LOWWATER: usize = mbytes(15);
38

            
39
144
fn secs(s: u64) -> CoarseDuration {
40
144
    Duration::from_secs(s).into()
41
144
}
42

            
43
424
pub(crate) const fn mbytes(mib: usize) -> usize {
44
424
    mib * 1024 * 1024
45
424
}
46

            
47
44
fn mk_config() -> Config {
48
44
    Config::builder()
49
44
        .max(TEST_DEFAULT_LIMIT)
50
44
        .low_water(TEST_DEFAULT_LOWWATER)
51
44
        .build()
52
44
        .unwrap()
53
44
}
54

            
55
44
pub(crate) fn mk_tracker(rt: &impl Runtime) -> Arc<MemoryQuotaTracker> {
56
44
    MemoryQuotaTracker::new(&rt, mk_config()).unwrap()
57
44
}
58

            
59
12
fn test_with_various_mocks<F, Fut>(f: F)
60
12
where
61
12
    F: Fn(tor_rtmock::MockRuntime) -> Fut,
62
12
    Fut: Future<Output = ()>,
63
12
{
64
24
    MockRuntime::test_with_various(|rt| async {
65
24
        // Make sure we can talk about times at least 1000s in the past
66
24
        // TODO maybe this should be a feature of MockRuntime but what value to pick?
67
24
        rt.advance_by(Duration::from_secs(1000)).await;
68
24
        f(rt).await;
69
48
    });
70
12
}
71

            
72
//---------- consistency check (test invariants against outside view) ----------
73

            
74
use consistency::*;
75
mod consistency {
76
    use super::*;
77

            
78
    #[derive(Default)]
79
    pub(super) struct CallerInfoCollector {
80
        g: usize,
81
        acs: BTreeMap<AId, refcount::RawCount>,
82
        pcs: BTreeMap<(AId, PId), (refcount::RawCount, usize)>,
83
        debug_dump: String,
84
    }
85

            
86
    pub(super) trait HasCallerInfo {
87
        fn note_consistency_caller_info(&self, collector: &mut CallerInfoCollector);
88
    }
89

            
90
    impl CallerInfoCollector {
91
41768
        pub(super) fn note_account(&mut self, acct: &Account, reclaimed: ReclaimedOrOk) {
92
41768
            let acct = acct.0.as_enabled().unwrap();
93
41768
            writeln!(self.debug_dump, "acct {acct:?} {reclaimed:?}").unwrap();
94
41768
            if acct.aid.is_null() || reclaimed.is_err() {
95
32
                return;
96
41736
            }
97
41736
            let ac = self.acs.entry(*acct.aid).or_default();
98
41736
            *ac += 1;
99
41768
        }
100
41768
        pub(super) fn note_particip(
101
41768
            &mut self,
102
41768
            p: &Participation,
103
41768
            reclaimed: ReclaimedOrOk,
104
41768
            used: usize,
105
41768
        ) {
106
41768
            let p = p.0.as_enabled().unwrap();
107
41768
            writeln!(self.debug_dump, "particip {p:?} {reclaimed:?} {used:?}").unwrap();
108
41768
            if p.pid.is_null() || p.aid.is_null() || reclaimed.is_err() {
109
36
                return;
110
41732
            }
111
41732
            self.note_partn_core(p, used);
112
41768
        }
113
40000
        pub(super) fn note_partn_clone(&mut self, p: &Participation) {
114
40000
            let p = p.0.as_enabled().unwrap();
115
40000
            writeln!(self.debug_dump, "partn {p:?}").unwrap();
116
40000
            if p.pid.is_null() {
117
                return;
118
40000
            }
119
40000
            self.note_partn_core(p, 0);
120
40000
        }
121
81732
        fn note_partn_core(&mut self, p: &ParticipationInner, x_used: usize) {
122
81732
            let pc = self.pcs.entry((p.aid, *p.pid)).or_default();
123
81732
            let used = *p.cache.as_raw() + x_used;
124
81732
            pc.0 += 1;
125
81732
            pc.1 += used;
126
81732
            self.g += used;
127
81732
        }
128
    }
129

            
130
40168
    pub(super) fn check_consistency_general(
131
40168
        trk: &Arc<MemoryQuotaTracker>,
132
40168
        collect_caller_info: impl FnOnce(&mut CallerInfoCollector),
133
40168
    ) {
134
40168
        let state = trk.lock().unwrap().into_enabled().unwrap();
135
40168

            
136
40168
        let (expected, debug_dump) = {
137
40168
            let mut c = CallerInfoCollector::default();
138
40168
            collect_caller_info(&mut c);
139
40168
            ((c.g, c.acs, c.pcs), c.debug_dump)
140
40168
        };
141

            
142
40168
        let got = {
143
40168
            let mut gc = 0;
144
40168
            let mut acs = BTreeMap::new();
145
40168
            let mut pcs = BTreeMap::new();
146
41736
            for (aid, arecord) in state.accounts.iter() {
147
41736
                acs.insert(aid, *arecord.refcount);
148
41736
                for (pid, precord) in arecord.ps.iter() {
149
41732
                    let used = *precord.used.as_raw();
150
41732
                    gc += used;
151
41732
                    pcs.insert((aid, pid), (*precord.refcount, used));
152
41732
                }
153
            }
154

            
155
40168
            (gc, acs, pcs)
156
40168
        };
157
40168

            
158
40168
        assert_eq!(
159
            expected, got,
160
            "\n----- dump (start) -----\n{debug_dump}----- dump (end) -----",
161
        );
162
40168
    }
163
}
164

            
165
//---------- common test participant (state) ----------
166

            
167
#[derive(Debug)]
168
struct PartnState {
169
    partn: Participation,
170
    age: Option<CoarseInstant>,
171
    used: usize,
172
    reclaimed: ReclaimedOrOk,
173
    show: String,
174
}
175

            
176
#[derive(Debug)]
177
struct TestPartn {
178
    state: Mutex<PartnState>,
179
}
180

            
181
impl TestPartn {
182
82440
    fn lock(&self) -> MutexGuard<PartnState> {
183
82440
        self.state.lock().unwrap()
184
82440
    }
185
}
186

            
187
impl From<PartnState> for TestPartn {
188
144
    fn from(state: PartnState) -> TestPartn {
189
144
        TestPartn {
190
144
            state: Mutex::new(state),
191
144
        }
192
144
    }
193
}
194

            
195
impl TestPartn {
196
124
    fn get_oldest(&self) -> Option<CoarseInstant> {
197
124
        self.lock().age
198
124
    }
199
52
    fn reclaim(&self) -> ReclaimFuture {
200
52
        let () = mem::replace(&mut self.lock().reclaimed, Err(())).expect("reclaimed twice!");
201
78
        Box::pin(async { Reclaimed::Collapsing })
202
52
    }
203
240
    fn is_reclaimed(&self) -> Result<(), ()> {
204
240
        self.lock().reclaimed
205
240
    }
206
}
207

            
208
impl IsParticipant for TestPartn {
209
12
    fn get_oldest(&self, _: EnabledToken) -> Option<CoarseInstant> {
210
12
        self.get_oldest()
211
12
    }
212
12
    fn reclaim(self: Arc<Self>, _: EnabledToken) -> ReclaimFuture {
213
12
        (*self.clone()).reclaim()
214
12
    }
215
}
216

            
217
impl PartnState {
218
184
    fn claim(&mut self, qty: usize) -> Result<(), crate::Error> {
219
184
        claim_via(&mut self.partn, &self.show, &mut self.used, qty)
220
184
    }
221

            
222
36
    fn release(&mut self, qty: usize) {
223
36
        release_via(&mut self.partn, &self.show, &mut self.used, qty);
224
36
    }
225
}
226

            
227
20232
fn claim_via(
228
20232
    via: &mut Participation,
229
20232
    show: impl Display,
230
20232
    used: &mut usize,
231
20232
    qty: usize,
232
20232
) -> Result<(), crate::Error> {
233
20232
    eprintln!("{show} claim {qty} {qty:#x}");
234
20232
    via.claim(qty)?;
235
20212
    *used += qty;
236
20212
    Ok(())
237
20232
}
238

            
239
19988
fn release_via(via: &mut Participation, show: impl Display, used: &mut usize, qty: usize) {
240
19988
    eprintln!("{show} release {qty} {qty:#x}");
241
19988
    via.release(qty);
242
19988
    *used -= qty;
243
19988
}
244

            
245
impl HasCallerInfo for PartnState {
246
41768
    fn note_consistency_caller_info(&self, collector: &mut CallerInfoCollector) {
247
41768
        collector.note_particip(&self.partn, self.reclaimed, self.used);
248
41768
    }
249
}
250

            
251
//---------- test participant which is directly the accountholder ----------
252

            
253
#[derive(Debug, Deref)]
254
struct UnifiedP {
255
    acct: Account,
256
    #[deref]
257
    state: TestPartn,
258
}
259

            
260
type ReclaimedOrOk = Result<(), ()>;
261

            
262
impl IsParticipant for UnifiedP {
263
112
    fn get_oldest(&self, _: EnabledToken) -> Option<CoarseInstant> {
264
112
        self.state.get_oldest()
265
112
    }
266
40
    fn reclaim(self: Arc<Self>, _: EnabledToken) -> ReclaimFuture {
267
40
        self.state.reclaim()
268
40
    }
269
}
270

            
271
impl UnifiedP {
272
120
    fn new(
273
120
        rt: &impl Runtime,
274
120
        trk: &Arc<MemoryQuotaTracker>,
275
120
        parent: Option<&Account>,
276
120
        age: CoarseDuration,
277
120
        show: impl Display,
278
120
    ) -> Arc<Self> {
279
120
        let acct = trk.new_account(parent).unwrap();
280
120

            
281
120
        let now = rt.now_coarse();
282
120

            
283
120
        acct.register_participant_with(now, |partn| {
284
120
            Ok::<_, Void>((
285
120
                Arc::new(UnifiedP {
286
120
                    acct: acct.clone(),
287
120
                    state: PartnState {
288
120
                        partn,
289
120
                        age: Some(now - age),
290
120
                        show: show.to_string(),
291
120
                        used: 0,
292
120
                        reclaimed: Ok(()),
293
120
                    }
294
120
                    .into(),
295
120
                }),
296
120
                (),
297
120
            ))
298
120
        })
299
120
        .unwrap()
300
120
        .void_unwrap()
301
120
        .0
302
120
    }
303

            
304
144
    async fn settle_check_consistency<'i>(
305
144
        rt: &'i MockRuntime,
306
144
        trk: &'i Arc<MemoryQuotaTracker>,
307
144
        ups: impl IntoIterator<Item = &'i Arc<Self>> + 'i,
308
144
    ) {
309
144
        rt.advance_until_stalled().await;
310

            
311
144
        check_consistency_general(trk, |collector| {
312
1888
            for up in ups {
313
1744
                up.note_consistency_caller_info(collector);
314
1744
            }
315
144
        });
316
144
    }
317
}
318

            
319
impl HasCallerInfo for UnifiedP {
320
41752
    fn note_consistency_caller_info(&self, collector: &mut CallerInfoCollector) {
321
41752
        let state = self.lock();
322
41752
        collector.note_account(&self.acct, state.reclaimed);
323
41752
        state.note_consistency_caller_info(collector);
324
41752
    }
325
}
326

            
327
//---------- test cases with unified accountholder/participant ----------
328

            
329
#[traced_test]
330
#[test]
331
2
fn basic() {
332
5
    test_with_various_mocks(|rt| async move {
333
4
        let trk = mk_tracker(&rt);
334
4

            
335
4
        let ps: Vec<Arc<UnifiedP>> = (0..21)
336
84
            .map(|i| UnifiedP::new(&rt, &trk, None, secs(i), i))
337
4
            .collect();
338

            
339
76
        for p in &ps[0..19] {
340
76
            p.lock().claim(mbytes(1)).unwrap();
341
76
            UnifiedP::settle_check_consistency(&rt, &trk, &ps).await;
342
        }
343

            
344
168
        let count_uncollapsed = || ps.iter().filter(|p| p.is_reclaimed().is_ok()).count();
345

            
346
4
        assert_eq!(count_uncollapsed(), 21);
347

            
348
4
        for p in &ps[20..] {
349
            // check that we are exercising a situation with nonzero cached
350
            // (this is set up by register_participant
351
4
            assert_ne!(p.lock().partn.0.as_enabled().unwrap().cache, Qty(0));
352

            
353
4
            p.lock()
354
4
                .claim(mbytes(1))
355
4
                .expect("allocation rejected, during collapse, but collapse is async");
356
        }
357

            
358
4
        UnifiedP::settle_check_consistency(&rt, &trk, &ps).await;
359

            
360
4
        assert_eq!(count_uncollapsed(), 14);
361

            
362
        // Now we drop everything.  This exercises much of the teardown!
363
9
    });
364
2
}
365

            
366
#[traced_test]
367
#[test]
368
2
fn parent() {
369
5
    test_with_various_mocks(|rt| async move {
370
8
        for ages in [[10, 20], [20, 10]] {
371
8
            eprintln!("ages: {ages:?}");
372
8
            let [parent_age, child_age] = ages.map(secs);
373
8

            
374
8
            let trk = mk_tracker(&rt);
375
8

            
376
24
            let mk_p = |parent, age, show| UnifiedP::new(&rt, &trk, parent, age, show);
377

            
378
8
            let parent = mk_p(None, parent_age, "parent");
379
8
            parent.lock().claim(mbytes(7)).unwrap();
380
8
            rt.advance_until_stalled().await;
381
8
            assert!(parent.is_reclaimed().is_ok());
382

            
383
8
            let child = mk_p(Some(&parent.acct), child_age, "child");
384
8
            child.lock().claim(mbytes(7)).unwrap();
385
8
            assert!(parent.is_reclaimed().is_ok());
386
8
            assert!(child.is_reclaimed().is_ok());
387

            
388
8
            let trigger = mk_p(None, secs(0), "trigger");
389
8
            trigger.lock().claim(mbytes(7)).unwrap();
390
8
            assert!(trigger.is_reclaimed().is_ok());
391

            
392
8
            rt.advance_until_stalled().await;
393

            
394
8
            if parent_age > child_age {
395
                // parent is older than child, we're supposed to have reclaimed
396
                // from the parent, causing reclamation of the child.
397
4
                assert!(parent.is_reclaimed().is_err());
398
4
                assert!(child.is_reclaimed().is_err());
399
            } else {
400
                // supposed to have reclaimed from child only
401
4
                assert!(parent.is_reclaimed().is_ok());
402
4
                assert!(child.is_reclaimed().is_err());
403
            }
404
        }
405
9
    });
406
2
}
407

            
408
#[traced_test]
409
#[test]
410
2
fn cache() {
411
5
    test_with_various_mocks(|rt| async move {
412
4
        let seq = [
413
4
            1,
414
4
            1000,
415
4
            *MAX_CACHE - 2000,
416
4
            3000,
417
4
            *MAX_CACHE,
418
4
            *MAX_CACHE - 1,
419
4
            *MAX_CACHE + 1,
420
4
        ];
421
4

            
422
4
        let trk = mk_tracker(&rt);
423
4
        let p = UnifiedP::new(&rt, &trk, None, secs(0), "p");
424

            
425
32
        for qty in seq {
426
28
            p.lock().claim(qty).unwrap();
427
28
            UnifiedP::settle_check_consistency(&rt, &trk, [&p]).await;
428
        }
429

            
430
32
        for qty in seq {
431
28
            p.lock().release(qty);
432
28
            UnifiedP::settle_check_consistency(&rt, &trk, [&p]).await;
433
        }
434

            
435
4
        let mut p2 = p.lock().partn.clone();
436
4

            
437
4
        let mut rng = tor_basic_utils::test_rng::Config::Deterministic.into_rng();
438
40004
        for _iter in 0..10_000 {
439
40000
            let qty = rng.gen_range_checked(0..=*MAX_CACHE).unwrap();
440
40000
            let p_use_i = rng.gen_range_checked(1..=3).unwrap();
441
40000
            {
442
40000
                let mut state = p.lock();
443
40000
                let state = &mut *state;
444

            
445
                let mut p_use_buf;
446
40000
                let p_use = match p_use_i {
447
13556
                    1 => &mut state.partn,
448
13060
                    2 => &mut p2,
449
                    3 => {
450
13384
                        p_use_buf = p2.clone();
451
13384
                        &mut p_use_buf
452
                    }
453
                    x => panic!("{}", x),
454
                };
455

            
456
40000
                if rng.gen() || qty > state.used {
457
20048
                    claim_via(p_use, p_use_i, &mut state.used, qty).unwrap();
458
20048
                } else {
459
19952
                    release_via(p_use, p_use_i, &mut state.used, qty);
460
19952
                }
461
            }
462

            
463
40000
            rt.advance_until_stalled().await;
464
40000
            check_consistency_general(&trk, |collector| {
465
40000
                p.note_consistency_caller_info(collector);
466
40000
                collector.note_partn_clone(&p2);
467
40000
            });
468
40000
        }
469
9
    });
470
2
}
471

            
472
#[traced_test]
473
#[test]
474
2
fn explicit_destroy() {
475
5
    test_with_various_mocks(|rt| async move {
476
4
        let trk = mk_tracker(&rt);
477
4

            
478
4
        let p0 = UnifiedP::new(&rt, &trk, None, secs(0), "0");
479
4
        let p1 = p0.clone();
480
4

            
481
4
        p0.lock().claim(mbytes(1)).unwrap();
482
4
        UnifiedP::settle_check_consistency(&rt, &trk, [&p0]).await;
483

            
484
4
        p1.lock().claim(mbytes(2)).unwrap();
485
4
        UnifiedP::settle_check_consistency(&rt, &trk, [&p0]).await;
486

            
487
4
        p1.lock().partn.clone().destroy_participant();
488
4

            
489
4
        rt.advance_until_stalled().await;
490
4
        check_consistency_general(&trk, |collector| {
491
4
            collector.note_account(&p0.acct, Ok(()));
492
4
            // We don't note the participation, since it's dead.
493
4
        });
494
4

            
495
4
        assert!(p1.lock().claim(mbytes(3)).is_err());
496

            
497
        // Now we drop everything.  This exercises much of the teardown!
498
9
    });
499
2
}
500

            
501
//---------- test client with multiple participants per account ----------
502

            
503
#[derive(Debug)]
504
struct ComplexAH {
505
    acct: Account,
506
    ps: Vec<Arc<TestPartn>>,
507
}
508

            
509
impl HasCallerInfo for ComplexAH {
510
8
    fn note_consistency_caller_info(&self, collector: &mut CallerInfoCollector) {
511
8
        let reclaimed = self
512
8
            .ps
513
8
            .iter()
514
20
            .map(|p| p.lock().reclaimed)
515
8
            .dedup()
516
8
            .exactly_one()
517
8
            .unwrap();
518
8

            
519
8
        collector.note_account(&self.acct, reclaimed);
520
24
        for p in &self.ps {
521
16
            p.lock().note_consistency_caller_info(collector);
522
16
        }
523
8
    }
524
}
525

            
526
impl ComplexAH {
527
20
    fn new(trk: &Arc<MemoryQuotaTracker>) -> Self {
528
20
        ComplexAH {
529
20
            acct: trk.new_account(None).unwrap(),
530
20
            ps: vec![],
531
20
        }
532
20
    }
533

            
534
24
    fn add_p(&mut self, now: CoarseInstant, age: CoarseDuration, show: impl Display) -> usize {
535
24
        let (cp, x) = self
536
24
            .acct
537
24
            .register_participant_with(now, |partn| {
538
24
                Ok::<_, Void>((
539
24
                    Arc::new(TestPartn::from(PartnState {
540
24
                        partn,
541
24
                        age: Some(now - age),
542
24
                        show: show.to_string(),
543
24
                        used: 0,
544
24
                        reclaimed: Ok(()),
545
24
                    })),
546
24
                    42,
547
24
                ))
548
24
            })
549
24
            .unwrap()
550
24
            .void_unwrap();
551
24

            
552
24
        assert_eq!(x, 42);
553

            
554
24
        let i = self.ps.len();
555
24
        self.ps.push(cp);
556
24
        i
557
24
    }
558
}
559

            
560
#[traced_test]
561
#[test]
562
2
fn complex() {
563
5
    test_with_various_mocks(|rt| async move {
564
4
        let trk = mk_tracker(&rt);
565
4

            
566
4
        let up = UnifiedP::new(&rt, &trk, None, secs(0), "U");
567
4
        let mut ah = ComplexAH::new(&trk);
568
4
        let now = rt.now_coarse();
569

            
570
12
        for age in [5, 9] {
571
8
            ah.add_p(now, secs(age), age);
572
8
        }
573

            
574
8
        let settle_check_consistency = || async {
575
8
            rt.advance_until_stalled().await;
576

            
577
8
            check_consistency_general(&trk, |collector| {
578
8
                up.note_consistency_caller_info(collector);
579
8
                ah.note_consistency_caller_info(collector);
580
8
            });
581
8
        };
582

            
583
4
        up.lock().claim(mbytes(1)).unwrap();
584
4
        ah.ps[0].lock().claim(mbytes(11)).unwrap();
585
4

            
586
4
        settle_check_consistency().await;
587

            
588
4
        assert!(up.is_reclaimed().is_ok());
589
12
        for p in &ah.ps {
590
8
            assert!(p.is_reclaimed().is_ok());
591
        }
592

            
593
4
        ah.ps[1].lock().claim(mbytes(11)).unwrap();
594
4

            
595
4
        settle_check_consistency().await;
596
4
        assert!(up.is_reclaimed().is_ok());
597
12
        for p in &ah.ps {
598
8
            assert!(p.is_reclaimed().is_err());
599
        }
600
9
    });
601
2
}
602

            
603
//---------- various error cases ----------
604

            
605
#[derive(Debug)]
606
struct DummyParticipant;
607

            
608
impl IsParticipant for DummyParticipant {
609
    fn get_oldest(&self, _: EnabledToken) -> Option<CoarseInstant> {
610
        None
611
    }
612
    fn reclaim(self: Arc<Self>, _: EnabledToken) -> ReclaimFuture {
613
        Box::pin(async { Reclaimed::Collapsing })
614
    }
615
}
616

            
617
#[traced_test]
618
#[test]
619
2
fn errors() {
620
5
    test_with_various_mocks(|rt| async move {
621
4
        let trk = mk_tracker(&rt);
622
4
        let now = rt.now_coarse();
623
4

            
624
16
        let mk_ah = || {
625
16
            let mut ah = ComplexAH::new(&trk);
626
16
            ah.add_p(now, secs(5), "p");
627
16
            ah
628
16
        };
629

            
630
        const CLAIM: usize = MAX_CACHE.as_usize() + 1;
631

            
632
8
        let dummy_dangling = || {
633
8
            let p = Arc::new(DummyParticipant);
634
8
            Arc::downgrade(&p)
635
8
            // p dropped here
636
8
        };
637
4
        assert!(dummy_dangling().upgrade().is_none());
638

            
639
        macro_rules! assert_error { { $error:ident, $r:expr } => {
640
            let r = $r;
641
            assert!(matches!(r, Err(Error::$error)), "unexpected: {:?} => {:?}", stringify!($r), &r);
642
        } }
643

            
644
        // Dropped account
645
        {
646
4
            let mut ah = mk_ah();
647
4
            let wa1: WeakAccount = ah.acct.downgrade();
648
4
            let p = ah.ps.pop().unwrap();
649
4
            assert!(p.lock().claim(1).is_ok());
650
4
            let wa2: WeakAccount = p.lock().partn.account();
651
4
            drop(ah.acct);
652
4

            
653
4
            rt.advance_until_stalled().await;
654
4
            check_consistency_general(&trk, |_collector| ());
655
4

            
656
4
            // account should be dead now
657
4
            assert_error!(AccountClosed, p.lock().claim(CLAIM));
658
4
            assert_error!(AccountClosed, wa1.upgrade());
659
4
            assert_error!(AccountClosed, wa2.upgrade());
660

            
661
            // but we can still release
662
4
            p.lock().release(1);
663
4
        }
664
4

            
665
4
        // Dropped IsParticipant
666
4
        {
667
4
            let mut ah = mk_ah();
668
4
            let p = ah.ps.pop().unwrap();
669
4
            let mut state = Arc::into_inner(p).unwrap().state.into_inner().unwrap();
670
4

            
671
4
            state.claim(mbytes(30)).unwrap(); // will trigger reclaim, which discovers the loss
672
4

            
673
4
            rt.advance_until_stalled().await;
674
4
            check_consistency_general(&trk, |collector| {
675
4
                let reclaimed = Ok(()); // didn't manage to make the callback!
676
4
                collector.note_account(&ah.acct, reclaimed);
677
4
            });
678
4

            
679
4
            assert_error!(ParticipantShutdown, state.claim(CLAIM));
680
        }
681

            
682
        // Reclaimed account
683
        {
684
4
            let ah = mk_ah();
685
4
            ah.ps[0].lock().claim(mbytes(30)).unwrap();
686
4

            
687
4
            rt.advance_until_stalled().await;
688
4
            check_consistency_general(&trk, |_collector| ());
689
4

            
690
4
            let p = &ah.ps[0];
691
4

            
692
4
            assert!(p.lock().reclaimed.is_err());
693
4
            assert_error!(AccountClosed, p.lock().claim(CLAIM));
694

            
695
4
            let cloned = ah.acct.clone();
696
4
            assert!(cloned.0.as_enabled().unwrap().aid.is_null());
697
4
            assert_error!(
698
4
                AccountClosed,
699
4
                ah.acct.register_participant(dummy_dangling())
700
4
            );
701

            
702
4
            let mut cloned = p.lock().partn.clone();
703
4
            assert!(cloned.0.as_enabled().unwrap().pid.is_null());
704
4
            assert_error!(AccountClosed, cloned.claim(CLAIM));
705

            
706
            // but we can still release
707
4
            p.lock().release(1);
708
4
        }
709
4

            
710
4
        // Dropped tracker
711
4
        {
712
4
            let mut ah = mk_ah();
713
4
            let p = ah.ps.pop().unwrap();
714
4
            let wa = ah.acct.downgrade();
715
4
            drop(ah.acct);
716
4
            let _: MemoryQuotaTracker = Arc::into_inner(trk).unwrap();
717
4

            
718
4
            assert_error!(TrackerShutdown, wa.upgrade());
719
4
            assert_error!(TrackerShutdown, p.lock().partn.account().upgrade());
720
4
            assert_error!(TrackerShutdown, p.lock().claim(CLAIM));
721
        }
722
9
    });
723
2
}