Skip to main content

vstd_extra/
rcu_read_lease.rs

1//! Unbounded fractional read leases for delayed reclamation.
2//!
3//! An owner-side accumulator stores one linear resource in Verus' Leaf-style
4//! storage protocol. Each reader-held lease receives half of the accumulator's
5//! current rational fraction, so the number of outstanding leases has no fixed
6//! integer bound. Reclamation can recover the resource only after all leases
7//! have been returned and the accumulator fraction is whole.
8use vstd::{
9    prelude::*,
10    resource::{Loc, frac_opt::Frac},
11};
12
13verus! {
14
15/// Owner-side fractional accumulator for one delayed-reclamation resource.
16pub tracked struct RcuLeaseAccumulator<T> {
17    frac: Frac<T>,
18}
19
20/// Reader-held fractional permission split from an [`RcuLeaseAccumulator`].
21pub tracked struct RcuReadLease<T> {
22    frac: Frac<T>,
23}
24
25/// Reader-held lease registered under an allocation key and a unique lease ID.
26///
27/// The private `lease_id` names the matching [`RcuActiveReadLeaseRecord`] in
28/// the authoritative [`RcuReadLeaseRegistry`]. Returning this lease must
29/// consume that exact record, so it cannot be returned to another allocation
30/// that happens to store an equal resource.
31pub tracked struct RcuRegisteredReadLease<K, T> {
32    ghost lease_id: nat,
33    ghost key: K,
34    lease: RcuReadLease<T>,
35}
36
37/// Registry-held accounting record paired with one outstanding reader lease.
38///
39/// `W` is a client-provided linear witness. RCU uses it to retain enough of the
40/// reader's CPU-generation authority for a completed grace period to rule out
41/// this record before reclamation.
42pub tracked struct RcuActiveReadLeaseRecord<K, W> {
43    ghost key: K,
44    ghost accumulator_id: Loc,
45    ghost fraction: real,
46    witness: W,
47}
48
49/// Authoritative allocation-indexed registry for physical read permissions.
50///
51/// Each allocation keeps an owner-side [`RcuLeaseAccumulator`]. The registry
52/// also records every issued lease and removes its record only when the
53/// matching [`RcuRegisteredReadLease`] is returned. Its invariant says that
54/// the accumulator fraction plus all active reader fractions for an allocation
55/// is exactly one. Therefore proving that the allocation has no active record
56/// is sufficient to recover its stored resource.
57pub tracked struct RcuReadLeaseRegistry<K, T, W> {
58    accumulators: Map<K, RcuLeaseAccumulator<T>>,
59    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
60    ghost next_lease: nat,
61}
62
63impl<K, W> RcuActiveReadLeaseRecord<K, W> {
64    pub closed spec fn key(self) -> K {
65        self.key
66    }
67
68    pub closed spec fn accumulator_id(self) -> Loc {
69        self.accumulator_id
70    }
71
72    pub closed spec fn fraction(self) -> real {
73        self.fraction
74    }
75
76    pub closed spec fn witness(self) -> W {
77        self.witness
78    }
79}
80
81impl<K, T> RcuRegisteredReadLease<K, T> {
82    pub closed spec fn lease_id(self) -> nat {
83        self.lease_id
84    }
85
86    pub closed spec fn key(self) -> K {
87        self.key
88    }
89
90    pub closed spec fn accumulator_id(self) -> Loc {
91        self.lease.id()
92    }
93
94    pub closed spec fn resource(self) -> T {
95        self.lease.resource()
96    }
97
98    pub closed spec fn fraction(self) -> real {
99        self.lease.fraction()
100    }
101
102    /// Borrows the protected resource while this registered lease remains live.
103    pub proof fn tracked_borrow(tracked &self) -> (tracked resource: &T)
104        ensures
105            *resource == self.resource(),
106    {
107        self.lease.tracked_borrow()
108    }
109}
110
111/// Sum of active lease fractions for `key` among record IDs below `upto`.
112pub open spec fn active_lease_fraction<K, W>(
113    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
114    key: K,
115    upto: nat,
116) -> real
117    decreases upto,
118{
119    if upto == 0 {
120        0real
121    } else {
122        let id = (upto - 1) as nat;
123        active_lease_fraction(active, key, id) + if active.contains_key(id) && active[id].key()
124            == key {
125            active[id].fraction()
126        } else {
127            0real
128        }
129    }
130}
131
132proof fn lemma_active_fraction_insert_above<K, W>(
133    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
134    inserted: nat,
135    record: RcuActiveReadLeaseRecord<K, W>,
136    key: K,
137    upto: nat,
138)
139    requires
140        upto <= inserted,
141    ensures
142        active_lease_fraction(active.insert(inserted, record), key, upto) == active_lease_fraction(
143            active,
144            key,
145            upto,
146        ),
147    decreases upto,
148{
149    if upto > 0 {
150        let id = (upto - 1) as nat;
151        lemma_active_fraction_insert_above(active, inserted, record, key, id);
152        assert(id < inserted);
153        assert(active.insert(inserted, record).contains_key(id) == active.contains_key(id));
154        if active.contains_key(id) {
155            assert(active.insert(inserted, record)[id] == active[id]);
156        }
157    }
158}
159
160proof fn lemma_active_fraction_insert_next<K, W>(
161    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
162    next: nat,
163    record: RcuActiveReadLeaseRecord<K, W>,
164    key: K,
165)
166    ensures
167        active_lease_fraction(active.insert(next, record), key, next + 1) == active_lease_fraction(
168            active,
169            key,
170            next,
171        ) + if record.key() == key {
172            record.fraction()
173        } else {
174            0real
175        },
176{
177    lemma_active_fraction_insert_above(active, next, record, key, next);
178}
179
180proof fn lemma_active_fraction_remove<K, W>(
181    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
182    removed: nat,
183    key: K,
184    upto: nat,
185)
186    requires
187        removed < upto,
188        active.contains_key(removed),
189    ensures
190        active_lease_fraction(active.remove(removed), key, upto) == active_lease_fraction(
191            active,
192            key,
193            upto,
194        ) - if active[removed].key() == key {
195            active[removed].fraction()
196        } else {
197            0real
198        },
199    decreases upto,
200{
201    let id = (upto - 1) as nat;
202    if removed == id {
203        lemma_active_fraction_remove_above(active, removed, key, id);
204        assert(!active.remove(removed).contains_key(id));
205        assert(active_lease_fraction(active.remove(removed), key, upto) == active_lease_fraction(
206            active.remove(removed),
207            key,
208            id,
209        ));
210        assert(active_lease_fraction(active, key, upto) == active_lease_fraction(active, key, id)
211            + if active[removed].key() == key {
212            active[removed].fraction()
213        } else {
214            0real
215        });
216    } else {
217        assert(removed < id);
218        lemma_active_fraction_remove(active, removed, key, id);
219        assert(active.remove(removed).contains_key(id) == active.contains_key(id));
220        if active.contains_key(id) {
221            assert(active.remove(removed)[id] == active[id]);
222        }
223        assert(active_lease_fraction(active.remove(removed), key, upto) == active_lease_fraction(
224            active.remove(removed),
225            key,
226            id,
227        ) + if active.contains_key(id) && active[id].key() == key {
228            active[id].fraction()
229        } else {
230            0real
231        });
232        assert(active_lease_fraction(active, key, upto) == active_lease_fraction(active, key, id)
233            + if active.contains_key(id) && active[id].key() == key {
234            active[id].fraction()
235        } else {
236            0real
237        });
238    }
239}
240
241proof fn lemma_active_fraction_remove_above<K, W>(
242    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
243    removed: nat,
244    key: K,
245    upto: nat,
246)
247    requires
248        upto <= removed,
249    ensures
250        active_lease_fraction(active.remove(removed), key, upto) == active_lease_fraction(
251            active,
252            key,
253            upto,
254        ),
255    decreases upto,
256{
257    if upto > 0 {
258        let id = (upto - 1) as nat;
259        lemma_active_fraction_remove_above(active, removed, key, id);
260        assert(id < removed);
261        assert(active.remove(removed).contains_key(id) == active.contains_key(id));
262        if active.contains_key(id) {
263            assert(active.remove(removed)[id] == active[id]);
264        }
265    }
266}
267
268proof fn lemma_active_fraction_zero<K, W>(
269    active: Map<nat, RcuActiveReadLeaseRecord<K, W>>,
270    key: K,
271    upto: nat,
272)
273    requires
274        forall|id: nat| #![auto] id < upto && active.contains_key(id) ==> active[id].key() != key,
275    ensures
276        active_lease_fraction(active, key, upto) == 0real,
277    decreases upto,
278{
279    if upto > 0 {
280        let id = (upto - 1) as nat;
281        lemma_active_fraction_zero(active, key, id);
282    }
283}
284
285impl<T> RcuLeaseAccumulator<T> {
286    /// Stores `resource` and creates a whole read accumulator.
287    pub proof fn new(tracked resource: T) -> (tracked res: Self)
288        ensures
289            res.resource() == resource,
290            res.fraction() == 1real,
291    {
292        let tracked frac = Frac::new(resource);
293        RcuLeaseAccumulator { frac }
294    }
295
296    /// Storage-protocol identity shared by this accumulator and all of its leases.
297    pub closed spec fn id(self) -> Loc {
298        self.frac.id()
299    }
300
301    /// The resource retained in storage while read leases exist.
302    pub closed spec fn resource(self) -> T {
303        self.frac.resource()
304    }
305
306    /// Rational fraction currently accumulated by the owner.
307    pub closed spec fn fraction(self) -> real {
308        self.frac.frac()
309    }
310
311    /// Splits a fresh lease without imposing a fixed reader capacity.
312    pub proof fn split_lease(tracked &mut self) -> (tracked lease: RcuReadLease<T>)
313        ensures
314            final(self).id() == old(self).id(),
315            final(self).resource() == old(self).resource(),
316            lease.id() == old(self).id(),
317            lease.resource() == old(self).resource(),
318            final(self).fraction() == old(self).fraction() / 2real,
319            lease.fraction() == old(self).fraction() / 2real,
320    {
321        let tracked frac = self.frac.split();
322        RcuReadLease { frac }
323    }
324
325    /// Returns one lease to its originating accumulator.
326    pub proof fn return_lease(tracked &mut self, tracked lease: RcuReadLease<T>)
327        requires
328            old(self).id() == lease.id(),
329        ensures
330            final(self).id() == old(self).id(),
331            final(self).resource() == old(self).resource(),
332            final(self).resource() == lease.resource(),
333            final(self).fraction() == old(self).fraction() + lease.fraction(),
334    {
335        self.frac.combine(lease.frac);
336    }
337
338    /// Recovers the stored resource after every lease has returned.
339    pub proof fn reclaim(tracked self) -> (tracked resource: T)
340        requires
341            self.fraction() == 1real,
342        ensures
343            resource == self.resource(),
344    {
345        let tracked (resource, _empty) = self.frac.take_resource();
346        resource
347    }
348
349    /// Establishes the valid range of the accumulated rational fraction.
350    pub proof fn lemma_fraction_bounded(tracked &self)
351        ensures
352            0real < self.fraction() <= 1real,
353    {
354        self.frac.bounded();
355    }
356}
357
358impl<T> RcuReadLease<T> {
359    /// Storage-protocol identity of the originating accumulator.
360    pub closed spec fn id(self) -> Loc {
361        self.frac.id()
362    }
363
364    /// The resource protected by this lease.
365    pub closed spec fn resource(self) -> T {
366        self.frac.resource()
367    }
368
369    /// Rational fraction carried by this lease.
370    pub closed spec fn fraction(self) -> real {
371        self.frac.frac()
372    }
373
374    /// Borrows the protected resource for the lifetime of this lease borrow.
375    pub proof fn tracked_borrow(tracked &self) -> (tracked resource: &T)
376        ensures
377            *resource == self.resource(),
378    {
379        self.frac.borrow()
380    }
381
382    /// Establishes that every lease carries a positive rational fraction.
383    pub proof fn lemma_fraction_bounded(tracked &self)
384        ensures
385            0real < self.fraction() <= 1real,
386    {
387        self.frac.bounded();
388    }
389}
390
391impl<K, T, W> RcuReadLeaseRegistry<K, T, W> {
392    /// Creates an empty tracked registry.
393    pub proof fn empty() -> (tracked res: Self)
394        ensures
395            res.wf(),
396            res.keys() == Set::<K>::empty(),
397            res.active_ids() == Set::<nat>::empty(),
398            res.next_lease() == 0,
399    {
400        RcuReadLeaseRegistry {
401            accumulators: Map::tracked_empty(),
402            active: Map::tracked_empty(),
403            next_lease: 0,
404        }
405    }
406
407    pub closed spec fn keys(self) -> Set<K> {
408        self.accumulators.dom()
409    }
410
411    pub closed spec fn contains(self, key: K) -> bool {
412        self.accumulators.contains_key(key)
413    }
414
415    /// Relates keyed lookup to membership in the registry's key set.
416    pub proof fn lemma_contains_iff_key(tracked &self, key: K)
417        ensures
418            self.contains(key) <==> self.keys().contains(key),
419    {
420    }
421
422    /// Relates registry membership to the complete key set for all keys.
423    pub proof fn lemma_all_contains_iff_keys(tracked &self)
424        ensures
425            forall|key: K| #[trigger] self.contains(key) <==> self.keys().contains(key),
426    {
427    }
428
429    pub closed spec fn accumulator(self, key: K) -> RcuLeaseAccumulator<T>
430        recommends
431            self.contains(key),
432    {
433        self.accumulators[key]
434    }
435
436    pub closed spec fn active_ids(self) -> Set<nat> {
437        self.active.dom()
438    }
439
440    /// Ghost snapshot used to state the per-allocation accounting invariant.
441    pub closed spec fn active_records(self) -> Map<nat, RcuActiveReadLeaseRecord<K, W>> {
442        self.active
443    }
444
445    pub closed spec fn next_lease(self) -> nat {
446        self.next_lease
447    }
448
449    pub closed spec fn active_record(self, lease_id: nat) -> RcuActiveReadLeaseRecord<K, W>
450        recommends
451            self.active_ids().contains(lease_id),
452    {
453        self.active[lease_id]
454    }
455
456    /// Borrows the client witness associated with one active lease.
457    ///
458    /// The witness remains owned by the registry until the matching lease is
459    /// returned. Reclamation proofs use this borrow to show that an allegedly
460    /// active lease is incompatible with a completed grace period.
461    pub proof fn tracked_borrow_active_witness(tracked &self, lease_id: nat) -> (tracked witness:
462        &W)
463        requires
464            self.active_ids().contains(lease_id),
465        ensures
466            *witness == self.active_record(lease_id).witness(),
467    {
468        let tracked record = self.active.tracked_borrow(lease_id);
469        &record.witness
470    }
471
472    /// Mutably borrows an active witness while preserving the registry.
473    ///
474    /// Resource-algebra validation may require a mutable receiver even when
475    /// its postcondition leaves the witness unchanged.
476    pub proof fn tracked_borrow_active_witness_mut(
477        tracked &mut self,
478        lease_id: nat,
479    ) -> (tracked witness: &mut W)
480        requires
481            old(self).active_ids().contains(lease_id),
482        ensures
483            *witness == old(self).active_record(lease_id).witness(),
484            final(self).keys() == old(self).keys(),
485            final(self).active_ids() == old(self).active_ids(),
486            final(self).next_lease() == old(self).next_lease(),
487            final(self).active_record(lease_id).key() == old(self).active_record(lease_id).key(),
488            final(self).active_record(lease_id).accumulator_id() == old(self).active_record(
489                lease_id,
490            ).accumulator_id(),
491            final(self).active_record(lease_id).fraction() == old(self).active_record(
492                lease_id,
493            ).fraction(),
494            final(self).active_record(lease_id).witness() == *final(witness),
495            forall|other: nat|
496                #![auto]
497                other != lease_id && old(self).active_ids().contains(other)
498                    ==> final(self).active_record(other) == old(self).active_record(other),
499    {
500        let tracked record = self.active.tracked_borrow_mut(lease_id);
501        &mut record.witness
502    }
503
504    pub open spec fn has_active(self, key: K) -> bool {
505        exists|lease_id: nat|
506            #![auto]
507            self.active_ids().contains(lease_id) && self.active_record(lease_id).key() == key
508    }
509
510    pub open spec fn wf(self) -> bool {
511        &&& forall|lease_id: nat| #[trigger]
512            self.active_ids().contains(lease_id) ==> {
513                let record = self.active_record(lease_id);
514                &&& lease_id < self.next_lease()
515                &&& self.contains(record.key())
516                &&& record.accumulator_id() == self.accumulator(record.key()).id()
517                &&& record.fraction() > 0real
518            }
519        &&& forall|key: K| #[trigger]
520            self.contains(key) ==> self.accumulator(key).fraction() + active_lease_fraction(
521                self.active_records(),
522                key,
523                self.next_lease(),
524            ) == 1real
525    }
526
527    /// Registers one allocation and stores its complete ownership resource.
528    pub proof fn insert(tracked &mut self, key: K, tracked resource: T)
529        requires
530            old(self).wf(),
531            !old(self).contains(key),
532        ensures
533            final(self).wf(),
534            final(self).keys() == old(self).keys().insert(key),
535            final(self).active_ids() == old(self).active_ids(),
536            final(self).next_lease() == old(self).next_lease(),
537            forall|lease_id: nat|
538                #![auto]
539                old(self).active_ids().contains(lease_id) ==> final(self).active_record(lease_id)
540                    == old(self).active_record(lease_id),
541            final(self).contains(key),
542            final(self).accumulator(key).resource() == resource,
543            final(self).accumulator(key).fraction() == 1real,
544            forall|other: K|
545                old(self).contains(other) ==> final(self).accumulator(other) == old(
546                    self,
547                ).accumulator(other),
548    {
549        reveal(RcuReadLeaseRegistry::active_ids);
550        reveal(RcuReadLeaseRegistry::active_records);
551        reveal(RcuReadLeaseRegistry::active_record);
552        assert forall|lease_id: nat| #[trigger] old(self).active_ids().contains(lease_id) implies {
553            let record = old(self).active_record(lease_id);
554            &&& lease_id < old(self).next_lease()
555            &&& old(self).contains(record.key())
556            &&& record.accumulator_id() == old(self).accumulator(record.key()).id()
557            &&& record.fraction() > 0real
558        } by {};
559        assert forall|old_key: K| #[trigger] old(self).contains(old_key) implies old(
560            self,
561        ).accumulator(old_key).fraction() + active_lease_fraction(
562            old(self).active_records(),
563            old_key,
564            old(self).next_lease(),
565        ) == 1real by {};
566        let tracked accumulator = RcuLeaseAccumulator::new(resource);
567        self.accumulators.tracked_insert(key, accumulator);
568        assert forall|lease_id: nat| #![auto] self.active_ids().contains(lease_id) implies {
569            &&& lease_id < self.next_lease()
570            &&& self.contains(self.active_record(lease_id).key())
571            &&& self.active_record(lease_id).accumulator_id() == self.accumulator(
572                self.active_record(lease_id).key(),
573            ).id()
574            &&& self.active_record(lease_id).fraction() > 0real
575        } by {
576            assert(old(self).active_ids().contains(lease_id));
577            assert(old(self).active_record(lease_id).key() != key);
578        };
579        assert forall|lease_id: nat|
580            #![auto]
581            lease_id < self.next_lease() && self.active_records().contains_key(
582                lease_id,
583            ) implies self.active_records()[lease_id].key() != key by {
584            assert(old(self).active_ids().contains(lease_id));
585            assert(old(self).contains(old(self).active_record(lease_id).key()));
586        };
587        assert(active_lease_fraction(self.active_records(), key, self.next_lease()) == 0real) by {
588            lemma_active_fraction_zero(self.active_records(), key, self.next_lease());
589        };
590        assert forall|other: K| #![auto] self.contains(other) implies self.accumulator(
591            other,
592        ).fraction() + active_lease_fraction(self.active_records(), other, self.next_lease())
593            == 1real by {
594            if other == key {
595                assert(self.accumulator(key).fraction() == 1real);
596            } else {
597                assert(old(self).contains(other));
598                assert(self.accumulator(other) == old(self).accumulator(other));
599            }
600        };
601    }
602
603    /// Splits a lease and installs its client witness in the active registry.
604    pub proof fn split_lease(tracked &mut self, key: K, tracked witness: W) -> (tracked lease:
605        RcuRegisteredReadLease<K, T>)
606        requires
607            old(self).wf(),
608            old(self).contains(key),
609        ensures
610            final(self).wf(),
611            final(self).keys() == old(self).keys(),
612            forall|candidate: K| #[trigger]
613                final(self).contains(candidate) == old(self).contains(candidate),
614            final(self).next_lease() == old(self).next_lease() + 1,
615            lease.lease_id() == old(self).next_lease(),
616            lease.key() == key,
617            final(self).active_ids() == old(self).active_ids().insert(lease.lease_id()),
618            final(self).active_record(lease.lease_id()).key() == key,
619            final(self).active_record(lease.lease_id()).accumulator_id() == lease.accumulator_id(),
620            final(self).active_record(lease.lease_id()).fraction() == lease.fraction(),
621            final(self).active_record(lease.lease_id()).witness() == witness,
622            forall|lease_id: nat|
623                #![auto]
624                old(self).active_ids().contains(lease_id) ==> final(self).active_record(lease_id)
625                    == old(self).active_record(lease_id),
626            lease.accumulator_id() == old(self).accumulator(key).id(),
627            lease.resource() == old(self).accumulator(key).resource(),
628            lease.fraction() == old(self).accumulator(key).fraction() / 2real,
629            final(self).accumulator(key).id() == old(self).accumulator(key).id(),
630            final(self).accumulator(key).resource() == old(self).accumulator(key).resource(),
631            final(self).accumulator(key).fraction() == old(self).accumulator(key).fraction()
632                / 2real,
633            forall|other: K|
634                other != key && old(self).contains(other) ==> final(self).accumulator(other) == old(
635                    self,
636                ).accumulator(other),
637    {
638        reveal(RcuReadLeaseRegistry::active_ids);
639        reveal(RcuReadLeaseRegistry::active_records);
640        reveal(RcuReadLeaseRegistry::active_record);
641        assert forall|old_key: K| #[trigger] old(self).contains(old_key) implies old(
642            self,
643        ).accumulator(old_key).fraction() + active_lease_fraction(
644            old(self).active_records(),
645            old_key,
646            old(self).next_lease(),
647        ) == 1real by {};
648        let ghost lease_id = self.next_lease;
649        let tracked accumulator = self.accumulators.tracked_borrow_mut(key);
650        let tracked lease = accumulator.split_lease();
651        lease.lemma_fraction_bounded();
652        let ghost accumulator_id = lease.id();
653        let ghost fraction = lease.fraction();
654        let tracked record = RcuActiveReadLeaseRecord { key, accumulator_id, fraction, witness };
655        self.active.tracked_insert(lease_id, record);
656        self.next_lease = lease_id + 1;
657
658        assert forall|active_id: nat| #![auto] self.active_ids().contains(active_id) implies {
659            &&& active_id < self.next_lease()
660            &&& self.contains(self.active_record(active_id).key())
661            &&& self.active_record(active_id).accumulator_id() == self.accumulator(
662                self.active_record(active_id).key(),
663            ).id()
664            &&& self.active_record(active_id).fraction() > 0real
665        } by {
666            if active_id == lease_id {
667                assert(self.active_record(active_id).fraction() == fraction);
668            } else {
669                assert(old(self).active_ids().contains(active_id));
670                assert(self.active_record(active_id) == old(self).active_record(active_id));
671            }
672        };
673
674        assert forall|other: K| #![auto] self.contains(other) implies self.accumulator(
675            other,
676        ).fraction() + active_lease_fraction(self.active_records(), other, self.next_lease())
677            == 1real by {
678            lemma_active_fraction_insert_next(
679                old(self).active_records(),
680                lease_id,
681                self.active_record(lease_id),
682                other,
683            );
684            if other == key {
685                assert(old(self).accumulator(key).fraction() + active_lease_fraction(
686                    old(self).active_records(),
687                    key,
688                    lease_id,
689                ) == 1real);
690            } else {
691                assert(old(self).contains(other));
692                assert(self.accumulator(other) == old(self).accumulator(other));
693                assert(old(self).accumulator(other).fraction() + active_lease_fraction(
694                    old(self).active_records(),
695                    other,
696                    lease_id,
697                ) == 1real);
698            }
699        };
700        RcuRegisteredReadLease { lease_id, key, lease }
701    }
702
703    /// Returns one lease and removes exactly its matching active record.
704    pub proof fn return_lease(
705        tracked &mut self,
706        tracked lease: RcuRegisteredReadLease<K, T>,
707    ) -> (tracked witness: W)
708        requires
709            old(self).wf(),
710            old(self).active_ids().contains(lease.lease_id()),
711            old(self).active_record(lease.lease_id()).key() == lease.key(),
712            old(self).active_record(lease.lease_id()).accumulator_id() == lease.accumulator_id(),
713            old(self).active_record(lease.lease_id()).fraction() == lease.fraction(),
714        ensures
715            final(self).wf(),
716            final(self).keys() == old(self).keys(),
717            forall|candidate: K| #[trigger]
718                final(self).contains(candidate) == old(self).contains(candidate),
719            final(self).next_lease() == old(self).next_lease(),
720            final(self).active_ids() == old(self).active_ids().remove(lease.lease_id()),
721            witness == old(self).active_record(lease.lease_id()).witness(),
722            forall|lease_id: nat|
723                #![auto]
724                lease_id != lease.lease_id() && old(self).active_ids().contains(lease_id)
725                    ==> final(self).active_record(lease_id) == old(self).active_record(lease_id),
726            final(self).accumulator(lease.key()).id() == old(self).accumulator(lease.key()).id(),
727            final(self).accumulator(lease.key()).resource() == old(self).accumulator(
728                lease.key(),
729            ).resource(),
730            final(self).accumulator(lease.key()).fraction() == old(self).accumulator(
731                lease.key(),
732            ).fraction() + lease.fraction(),
733            forall|other: K|
734                other != lease.key() && old(self).contains(other) ==> final(self).accumulator(other)
735                    == old(self).accumulator(other),
736    {
737        reveal(RcuReadLeaseRegistry::active_ids);
738        reveal(RcuReadLeaseRegistry::active_records);
739        reveal(RcuReadLeaseRegistry::active_record);
740        assert forall|old_key: K| #[trigger] old(self).contains(old_key) implies old(
741            self,
742        ).accumulator(old_key).fraction() + active_lease_fraction(
743            old(self).active_records(),
744            old_key,
745            old(self).next_lease(),
746        ) == 1real by {};
747        let ghost lease_id = lease.lease_id;
748        let ghost key = lease.key;
749        let tracked record = self.active.tracked_remove(lease_id);
750        let tracked accumulator = self.accumulators.tracked_borrow_mut(key);
751        accumulator.return_lease(lease.lease);
752
753        assert forall|active_id: nat| #![auto] self.active_ids().contains(active_id) implies {
754            &&& active_id < self.next_lease()
755            &&& self.contains(self.active_record(active_id).key())
756            &&& self.active_record(active_id).accumulator_id() == self.accumulator(
757                self.active_record(active_id).key(),
758            ).id()
759            &&& self.active_record(active_id).fraction() > 0real
760        } by {
761            assert(old(self).active_ids().contains(active_id));
762            assert(active_id != lease_id);
763            assert(self.active_record(active_id) == old(self).active_record(active_id));
764        };
765
766        assert forall|other: K| #![auto] self.contains(other) implies self.accumulator(
767            other,
768        ).fraction() + active_lease_fraction(self.active_records(), other, self.next_lease())
769            == 1real by {
770            lemma_active_fraction_remove(
771                old(self).active_records(),
772                lease_id,
773                other,
774                self.next_lease(),
775            );
776            if other == key {
777                assert(old(self).accumulator(key).fraction() + active_lease_fraction(
778                    old(self).active_records(),
779                    key,
780                    self.next_lease(),
781                ) == 1real);
782            } else {
783                assert(old(self).contains(other));
784                assert(self.accumulator(other) == old(self).accumulator(other));
785                assert(old(self).accumulator(other).fraction() + active_lease_fraction(
786                    old(self).active_records(),
787                    other,
788                    self.next_lease(),
789                ) == 1real);
790            }
791        };
792        record.witness
793    }
794
795    /// Recovers one allocation after a client proof rules out all active leases.
796    pub proof fn reclaim(tracked &mut self, key: K) -> (tracked resource: T)
797        requires
798            old(self).wf(),
799            old(self).contains(key),
800            !old(self).has_active(key),
801        ensures
802            final(self).wf(),
803            final(self).keys() == old(self).keys().remove(key),
804            final(self).active_ids() == old(self).active_ids(),
805            final(self).active_records() == old(self).active_records(),
806            final(self).next_lease() == old(self).next_lease(),
807            forall|lease_id: nat|
808                #![auto]
809                old(self).active_ids().contains(lease_id) ==> final(self).active_record(lease_id)
810                    == old(self).active_record(lease_id),
811            !final(self).contains(key),
812            resource == old(self).accumulator(key).resource(),
813            forall|other: K|
814                other != key && old(self).contains(other) ==> final(self).accumulator(other) == old(
815                    self,
816                ).accumulator(other),
817    {
818        reveal(RcuReadLeaseRegistry::active_ids);
819        reveal(RcuReadLeaseRegistry::active_records);
820        reveal(RcuReadLeaseRegistry::active_record);
821        assert forall|old_key: K| #[trigger] old(self).contains(old_key) implies old(
822            self,
823        ).accumulator(old_key).fraction() + active_lease_fraction(
824            old(self).active_records(),
825            old_key,
826            old(self).next_lease(),
827        ) == 1real by {};
828        assert forall|lease_id: nat|
829            #![auto]
830            lease_id < self.next_lease() && self.active_records().contains_key(
831                lease_id,
832            ) implies self.active_records()[lease_id].key() != key by {
833            if self.active_records()[lease_id].key() == key {
834                assert(self.active_ids().contains(lease_id));
835                assert(exists|candidate: nat|
836                    #![auto]
837                    self.active_ids().contains(candidate) && self.active_record(candidate).key()
838                        == key) by {
839                    assert(self.active_record(lease_id).key() == key);
840                };
841                assert(self.has_active(key));
842            }
843        };
844        lemma_active_fraction_zero(self.active_records(), key, self.next_lease());
845        assert(self.accumulator(key).fraction() == 1real);
846        let tracked accumulator = self.accumulators.tracked_remove(key);
847        let tracked resource = accumulator.reclaim();
848        assert forall|lease_id: nat| #![auto] self.active_ids().contains(lease_id) implies {
849            &&& lease_id < self.next_lease()
850            &&& self.contains(self.active_record(lease_id).key())
851            &&& self.active_record(lease_id).accumulator_id() == self.accumulator(
852                self.active_record(lease_id).key(),
853            ).id()
854            &&& self.active_record(lease_id).fraction() > 0real
855        } by {
856            assert(old(self).active_ids().contains(lease_id));
857            assert(old(self).active_record(lease_id).key() != key);
858        };
859        assert forall|other: K| #![auto] self.contains(other) implies self.accumulator(
860            other,
861        ).fraction() + active_lease_fraction(self.active_records(), other, self.next_lease())
862            == 1real by {
863            assert(other != key);
864            assert(old(self).contains(other));
865            assert(self.accumulator(other) == old(self).accumulator(other));
866            assert(self.active_records() == old(self).active_records());
867            assert(old(self).accumulator(other).fraction() + active_lease_fraction(
868                old(self).active_records(),
869                other,
870                old(self).next_lease(),
871            ) == 1real);
872        };
873        resource
874    }
875}
876
877/// Regression proof for the complete indexed split/return/reclaim lifecycle.
878proof fn read_lease_registry_reclaims_after_returns<K, T, W>(
879    key: K,
880    tracked resource: T,
881    tracked first_witness: W,
882    tracked second_witness: W,
883) -> (tracked res: T)
884    ensures
885        res == resource,
886{
887    let tracked mut registry = RcuReadLeaseRegistry::empty();
888    registry.insert(key, resource);
889    let tracked first = registry.split_lease(key, first_witness);
890    let tracked second = registry.split_lease(key, second_witness);
891    let tracked _first_witness = registry.return_lease(first);
892    let tracked _second_witness = registry.return_lease(second);
893    assert(!registry.has_active(key));
894    assert(registry.accumulator(key).resource() == resource);
895    let tracked res = registry.reclaim(key);
896    assert(res == resource);
897    res
898}
899
900/// Regression proof: recursively splitting leases does not require a capacity
901/// assumption, and returning them restores the whole resource.
902pub proof fn lease_accumulator_reclaims_after_returns<T>(tracked resource: T) -> (tracked res: T)
903    ensures
904        res == resource,
905{
906    let tracked mut accumulator = RcuLeaseAccumulator::new(resource);
907    let tracked first = accumulator.split_lease();
908    let tracked second = accumulator.split_lease();
909    accumulator.return_lease(first);
910    accumulator.return_lease(second);
911    assert(accumulator.fraction() == 1real);
912    accumulator.reclaim()
913}
914
915} // verus!