Skip to main content

election/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Elections that arbitrate a setting across multiple voters.
6//!
7//! Each voter casts or withdraws its vote in its own `K` slot of an [`Election`], which elects the
8//! winning value (the result):
9//! - If an [`override_voter`](Election::with_override_voter) has an active vote, its vote wins.
10//! - Otherwise, active votes are combined using `cmp` ([`Election::min`], [`Election::max`], or a
11//!   custom comparator passed to [`Election::new`]).
12//!
13//! [`Election`] holds its internal lock only for short map reads and writes, never across `.await`
14//! points. Callers that need to update several [`Election`]s and apply the results as a single
15//! transaction must serialize those updates themselves.
16
17use fuchsia_sync::Mutex;
18use std::borrow::Borrow;
19use std::cmp::Ordering;
20use std::collections::BTreeMap;
21use std::future::Future;
22
23const INSPECTOR_PROP_RESULT: &str = "result";
24const INSPECTOR_PROP_OVERRIDDEN: &str = "overridden";
25const INSPECTOR_NODE_VOTES: &str = "votes";
26
27/// Records a vote or result onto an Inspect [`fuchsia_inspect::Node`] under `key`.
28///
29/// Value types that cannot implement this trait (for example, types from another crate) can be
30/// recorded with [`Election::record_inspect_with`] instead.
31pub trait InspectValue {
32    fn record_inspect(&self, node: &fuchsia_inspect::Node, key: &str);
33}
34
35impl InspectValue for u32 {
36    fn record_inspect(&self, node: &fuchsia_inspect::Node, key: &str) {
37        node.record_uint(key, u64::from(*self));
38    }
39}
40
41impl InspectValue for u64 {
42    fn record_inspect(&self, node: &fuchsia_inspect::Node, key: &str) {
43        node.record_uint(key, *self);
44    }
45}
46
47impl InspectValue for i64 {
48    fn record_inspect(&self, node: &fuchsia_inspect::Node, key: &str) {
49        node.record_int(key, *self);
50    }
51}
52
53impl InspectValue for bool {
54    fn record_inspect(&self, node: &fuchsia_inspect::Node, key: &str) {
55        node.record_bool(key, *self);
56    }
57}
58
59/// An election for one setting: tracks per-voter (`K`) votes for a value of type `V` and elects
60/// the winning value (the result).
61///
62/// This is what Linux charger drivers call a votable (Qualcomm `pmic-voter`) or an election
63/// (Google `gvotable`): a setting that several clients vote on, together with the rule that elects
64/// its value.
65#[derive(Debug)]
66pub struct Election<K, V> {
67    /// Comparator used to rank non-override votes (`Ordering::Greater` wins).
68    cmp: fn(&V, &V) -> Ordering,
69    /// Active votes keyed by voter.
70    votes: Mutex<BTreeMap<K, V>>,
71    /// Optional voter whose active vote overrides all other voters.
72    override_voter: Option<K>,
73}
74
75impl<K: Ord + Clone, V: Copy> Election<K, V> {
76    /// Creates an [`Election`] with no votes that elects the winning vote using `cmp`
77    /// (`Ordering::Greater` wins).
78    pub fn new(cmp: fn(&V, &V) -> Ordering) -> Self {
79        Self { cmp, votes: Mutex::new(BTreeMap::new()), override_voter: None }
80    }
81
82    /// Designates `override_voter` as taking precedence over all other voters when present.
83    pub fn with_override_voter(mut self, override_voter: K) -> Self {
84        self.override_voter = Some(override_voter);
85        self
86    }
87
88    fn is_overridden_locked(&self, votes: &BTreeMap<K, V>) -> bool {
89        self.override_voter.as_ref().is_some_and(|voter| votes.contains_key(voter))
90    }
91
92    fn result_locked(&self, votes: &BTreeMap<K, V>) -> Option<V> {
93        self.override_voter
94            .as_ref()
95            .and_then(|voter| votes.get(voter).copied())
96            .or_else(|| votes.values().copied().max_by(|a, b| (self.cmp)(a, b)))
97    }
98
99    /// Casts (`Some`) or withdraws (`None`) `voter`'s vote and returns
100    /// `(previous_vote, previous_result, new_result)`.
101    fn swap(&self, voter: K, vote: Option<V>) -> (Option<V>, Option<V>, Option<V>) {
102        let mut votes = self.votes.lock();
103        let previous_result = self.result_locked(&votes);
104        let previous_vote = match vote {
105            Some(vote) => votes.insert(voter, vote),
106            None => votes.remove(&voter),
107        };
108        (previous_vote, previous_result, self.result_locked(&votes))
109    }
110
111    /// Returns the result, whether the override voter has an active vote, and a copy of the votes,
112    /// read under one lock acquisition.
113    fn snapshot(&self) -> (Option<V>, bool, Vec<(K, V)>) {
114        let votes = self.votes.lock();
115        let copy = votes.iter().map(|(voter, vote)| (voter.clone(), *vote)).collect();
116        (self.result_locked(&votes), self.is_overridden_locked(&votes), copy)
117    }
118
119    /// Returns whether `override_voter` currently has an active vote.
120    pub fn is_overridden(&self) -> bool {
121        self.is_overridden_locked(&self.votes.lock())
122    }
123
124    /// Returns the winning vote across all active voters, or `None` if no votes are active.
125    pub fn result(&self) -> Option<V> {
126        self.result_locked(&self.votes.lock())
127    }
128
129    /// Returns `voter`'s current vote, if any.
130    pub fn vote<Q>(&self, voter: &Q) -> Option<V>
131    where
132        K: Borrow<Q>,
133        Q: Ord + ?Sized,
134    {
135        self.votes.lock().get(voter).copied()
136    }
137
138    /// Casts `voter`'s `vote`, replacing any previous vote, and returns the previous vote.
139    pub fn cast(&self, voter: K, vote: V) -> Option<V> {
140        self.votes.lock().insert(voter, vote)
141    }
142
143    /// Withdraws `voter`'s vote and returns it, or `None` if `voter` had no vote.
144    pub fn withdraw<Q>(&self, voter: &Q) -> Option<V>
145    where
146        K: Borrow<Q>,
147        Q: Ord + ?Sized,
148    {
149        self.votes.lock().remove(voter)
150    }
151
152    /// Casts (`Some`) or withdraws (`None`) `voter`'s vote and returns the previous vote, so that
153    /// callers can restore it if applying the new result fails.
154    pub fn set_vote(&self, voter: K, vote: Option<V>) -> Option<V> {
155        self.swap(voter, vote).0
156    }
157
158    /// Casts `voter`'s `vote` and, if the result changes, runs `apply(result)`. See
159    /// [`Election::try_set_vote`].
160    pub async fn try_cast<E, F, Fut>(&self, voter: K, vote: V, apply: F) -> Result<bool, E>
161    where
162        V: PartialEq,
163        F: FnOnce(Option<V>) -> Fut,
164        Fut: Future<Output = Result<(), E>>,
165    {
166        self.try_set_vote(voter, Some(vote), apply).await
167    }
168
169    /// Withdraws `voter`'s vote and, if the result changes, runs `apply(result)`. See
170    /// [`Election::try_set_vote`].
171    pub async fn try_withdraw<E, F, Fut>(&self, voter: K, apply: F) -> Result<bool, E>
172    where
173        V: PartialEq,
174        F: FnOnce(Option<V>) -> Fut,
175        Fut: Future<Output = Result<(), E>>,
176    {
177        self.try_set_vote(voter, None, apply).await
178    }
179
180    /// Casts (`Some`) or withdraws (`None`) `voter`'s vote and, if the result changes, runs
181    /// `apply(result)`.
182    ///
183    /// Restores `voter`'s previous vote if `apply` fails or if the returned future is dropped
184    /// before `apply` completes, so that the election matches what was last applied. A failed or
185    /// cancelled `apply` may still have partially taken effect.
186    ///
187    /// Returns `Ok(true)` if the result changed and `apply` succeeded, or `Ok(false)` if the result
188    /// was unchanged (and `apply` did not run).
189    pub async fn try_set_vote<E, F, Fut>(
190        &self,
191        voter: K,
192        vote: Option<V>,
193        apply: F,
194    ) -> Result<bool, E>
195    where
196        V: PartialEq,
197        F: FnOnce(Option<V>) -> Fut,
198        Fut: Future<Output = Result<(), E>>,
199    {
200        let (previous_vote, previous_result, result) = self.swap(voter.clone(), vote);
201        if result == previous_result {
202            return Ok(false);
203        }
204        let rollback = Rollback { election: self, voter: Some(voter), previous_vote };
205        apply(result).await?;
206        rollback.defuse();
207        Ok(true)
208    }
209}
210
211/// Restores a voter's previous vote when dropped, unless defused.
212struct Rollback<'a, K: Ord + Clone, V: Copy> {
213    election: &'a Election<K, V>,
214    /// Voter to restore, or `None` once defused.
215    voter: Option<K>,
216    previous_vote: Option<V>,
217}
218
219impl<K: Ord + Clone, V: Copy> Rollback<'_, K, V> {
220    /// Keeps the new vote.
221    fn defuse(mut self) {
222        self.voter = None;
223    }
224}
225
226impl<K: Ord + Clone, V: Copy> Drop for Rollback<'_, K, V> {
227    fn drop(&mut self) {
228        if let Some(voter) = self.voter.take() {
229            self.election.swap(voter, self.previous_vote);
230        }
231    }
232}
233
234impl<K: Ord + Clone + AsRef<str>, V: Copy> Election<K, V> {
235    /// Records `result`, `overridden`, and per-voter `votes` under `parent/<name>`, using `record`
236    /// to write each value.
237    pub fn record_inspect_with(
238        &self,
239        parent: &fuchsia_inspect::Node,
240        name: &str,
241        record: impl Fn(&V, &fuchsia_inspect::Node, &str),
242    ) {
243        // Record from a snapshot so that the lock is not held while `record` runs: `record` may
244        // call back into the election, and voters are not blocked while Inspect allocates.
245        let (result, overridden, votes) = self.snapshot();
246        parent.record_child(name, |node| {
247            if let Some(result) = result {
248                record(&result, node, INSPECTOR_PROP_RESULT);
249            }
250            node.record_bool(INSPECTOR_PROP_OVERRIDDEN, overridden);
251            node.record_child(INSPECTOR_NODE_VOTES, |votes_node| {
252                for (voter, vote) in &votes {
253                    record(vote, votes_node, voter.as_ref());
254                }
255            });
256        });
257    }
258}
259
260impl<K: Ord + Clone + AsRef<str>, V: Copy + InspectValue> Election<K, V> {
261    /// Records `result`, `overridden`, and per-voter `votes` under `parent/<name>`.
262    pub fn record_inspect(&self, parent: &fuchsia_inspect::Node, name: &str) {
263        self.record_inspect_with(parent, name, InspectValue::record_inspect);
264    }
265}
266
267impl<K: Ord + Clone, V: Copy + Ord> Election<K, V> {
268    /// Creates an [`Election`] where the lowest vote wins (`V::Ord`).
269    pub fn min() -> Self {
270        Self::new(|a, b| b.cmp(a))
271    }
272
273    /// Creates an [`Election`] where the highest vote wins (`V::Ord`).
274    pub fn max() -> Self {
275        Self::new(Ord::cmp)
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use diagnostics_assertions::assert_data_tree;
283    use std::pin::pin;
284    use std::task::{Context, Poll, Waker};
285
286    /// Verifies `Election::min` elects the minimum vote across numeric voter IDs, falls back as
287    /// votes are withdrawn, and honors `with_override_voter`.
288    #[fuchsia::test]
289    fn test_election_min_with_override_voter() {
290        const VOTER_A: u64 = 1;
291        const VOTER_B: u64 = 2;
292        const OVERRIDE_VOTER: u64 = 99;
293        const HIGH_LIMIT_UA: u32 = 1_500_000;
294        const MID_LIMIT_UA: u32 = 500_000;
295        const LOW_LIMIT_UA: u32 = 300_000;
296        const OVERRIDE_LIMIT_UA: u32 = 2_000_000;
297
298        let e = Election::min().with_override_voter(OVERRIDE_VOTER);
299        assert_eq!(e.result(), None);
300
301        assert_eq!(e.cast(VOTER_A, HIGH_LIMIT_UA), None);
302        assert_eq!(e.result(), Some(HIGH_LIMIT_UA));
303        assert_eq!(e.vote(&VOTER_A), Some(HIGH_LIMIT_UA));
304
305        e.cast(VOTER_B, MID_LIMIT_UA);
306        assert_eq!(e.result(), Some(MID_LIMIT_UA));
307
308        assert_eq!(e.cast(VOTER_A, LOW_LIMIT_UA), Some(HIGH_LIMIT_UA));
309        assert_eq!(e.result(), Some(LOW_LIMIT_UA));
310
311        // The override voter forces a higher value even under `Election::min`, and withdrawing
312        // restores the min across remaining voters.
313        e.cast(OVERRIDE_VOTER, OVERRIDE_LIMIT_UA);
314        assert!(e.is_overridden());
315        assert_eq!(e.result(), Some(OVERRIDE_LIMIT_UA));
316
317        assert_eq!(e.withdraw(&OVERRIDE_VOTER), Some(OVERRIDE_LIMIT_UA));
318        assert!(!e.is_overridden());
319        assert_eq!(e.vote(&OVERRIDE_VOTER), None);
320        assert_eq!(e.result(), Some(LOW_LIMIT_UA));
321
322        assert_eq!(e.set_vote(VOTER_A, None), Some(LOW_LIMIT_UA));
323        assert_eq!(e.result(), Some(MID_LIMIT_UA));
324        assert_eq!(e.set_vote(VOTER_A, Some(HIGH_LIMIT_UA)), None);
325        assert_eq!(e.result(), Some(MID_LIMIT_UA));
326
327        // Withdrawing a missing vote returns `None`.
328        assert_eq!(e.withdraw(&VOTER_A), Some(HIGH_LIMIT_UA));
329        assert_eq!(e.withdraw(&VOTER_A), None);
330
331        assert_eq!(e.withdraw(&VOTER_B), Some(MID_LIMIT_UA));
332        assert_eq!(e.result(), None);
333    }
334
335    /// Verifies `Election::max` elects the maximum vote across string voter keys, honors
336    /// `with_override_voter`, and records `result`, `overridden`, and per-voter `votes` in Inspect.
337    #[fuchsia::test]
338    async fn test_election_max_with_override_voter_and_record_inspect() {
339        let e = Election::max().with_override_voter("debug");
340        e.cast("vreg", false);
341        e.cast("controller", true);
342        assert_eq!(e.result(), Some(true));
343
344        // The override voter can force false even when another voter votes true.
345        e.cast("debug", false);
346        assert_eq!(e.result(), Some(false));
347
348        let inspector = fuchsia_inspect::Inspector::default();
349        e.record_inspect(inspector.root(), "otg_enabled");
350        assert_data_tree!(inspector, root: {
351            otg_enabled: {
352                result: false,
353                overridden: true,
354                votes: {
355                    vreg: false,
356                    controller: true,
357                    debug: false,
358                },
359            },
360        });
361
362        e.withdraw("debug");
363        assert!(!e.is_overridden());
364        assert_eq!(e.result(), Some(true));
365
366        e.cast("controller", false);
367        assert_eq!(e.result(), Some(false));
368    }
369
370    /// Verifies `Election::new` works on value types that do not implement `Ord` or
371    /// [`InspectValue`] using a custom precedence comparator, owned `String` voter keys, and
372    /// `record_inspect_with`.
373    #[fuchsia::test]
374    async fn test_election_custom_comparator_string_voters_and_record_inspect_with() {
375        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
376        enum Mode {
377            Charging,
378            Passthrough,
379            Discharging,
380        }
381
382        fn rank(m: &Mode) -> u8 {
383            match m {
384                Mode::Charging => 0,
385                Mode::Passthrough => 1,
386                Mode::Discharging => 2,
387            }
388        }
389
390        let debug = String::from("debug");
391        let msc = String::from("msc");
392        let thermal = String::from("thermal");
393        let defender = String::from("defender");
394
395        let e = Election::new(|a, b| rank(a).cmp(&rank(b))).with_override_voter(debug.clone());
396        e.cast(msc.clone(), Mode::Charging);
397        assert_eq!(e.vote("msc"), Some(Mode::Charging));
398        e.cast(thermal.clone(), Mode::Passthrough);
399        assert_eq!(e.result(), Some(Mode::Passthrough));
400
401        e.cast(defender.clone(), Mode::Discharging);
402        assert_eq!(e.result(), Some(Mode::Discharging));
403
404        let inspector = fuchsia_inspect::Inspector::default();
405        e.record_inspect_with(inspector.root(), "operating_mode", |mode, node, key| {
406            node.record_string(key, format!("{mode:?}"));
407        });
408        assert_data_tree!(inspector, root: {
409            operating_mode: {
410                result: "Discharging",
411                overridden: false,
412                votes: {
413                    defender: "Discharging",
414                    msc: "Charging",
415                    thermal: "Passthrough",
416                },
417            },
418        });
419
420        // The debug override voter can force Charging even when defender votes Discharging.
421        e.cast(debug.clone(), Mode::Charging);
422        assert_eq!(e.result(), Some(Mode::Charging));
423
424        e.withdraw(&debug);
425        assert_eq!(e.result(), Some(Mode::Discharging));
426
427        e.withdraw(&defender);
428        assert_eq!(e.result(), Some(Mode::Passthrough));
429    }
430
431    /// Verifies `try_cast` and `try_withdraw` commit on `Ok`, restore the previous vote on `Err`,
432    /// and skip `apply` when the result is unchanged.
433    #[fuchsia::test]
434    async fn test_election_try_cast_and_try_withdraw_rollback() {
435        const VOTER_A: u8 = 1;
436        const VOTER_B: u8 = 2;
437        const INITIAL_LIMIT_UA: u32 = 100;
438        const LOWER_LIMIT_UA: u32 = 50;
439
440        let e = Election::min();
441
442        // A failed first vote restores no vote.
443        let res = e
444            .try_cast(VOTER_A, INITIAL_LIMIT_UA, |result| async move {
445                assert_eq!(result, Some(INITIAL_LIMIT_UA));
446                Err("io")
447            })
448            .await;
449        assert_eq!(res, Err("io"));
450        assert_eq!(e.vote(&VOTER_A), None);
451        assert_eq!(e.result(), None);
452
453        // A successful vote commits and returns Ok(true); repeating the identical vote returns
454        // Ok(false) without running apply.
455        let res: Result<bool, &'static str> = e
456            .try_cast(VOTER_A, INITIAL_LIMIT_UA, |result| async move {
457                assert_eq!(result, Some(INITIAL_LIMIT_UA));
458                Ok(())
459            })
460            .await;
461        assert_eq!(res, Ok(true));
462        assert_eq!(e.vote(&VOTER_A), Some(INITIAL_LIMIT_UA));
463
464        let res: Result<bool, &'static str> = e
465            .try_cast(VOTER_A, INITIAL_LIMIT_UA, |_result| async {
466                panic!("apply should not run when the vote is unchanged");
467            })
468            .await;
469        assert_eq!(res, Ok(false));
470
471        // A failed vote restores the previous vote.
472        let res = e
473            .try_cast(VOTER_A, LOWER_LIMIT_UA, |result| async move {
474                assert_eq!(result, Some(LOWER_LIMIT_UA));
475                Err("io")
476            })
477            .await;
478        assert_eq!(res, Err("io"));
479        assert_eq!(e.vote(&VOTER_A), Some(INITIAL_LIMIT_UA));
480
481        // A failed withdrawal restores the previous vote.
482        let res = e
483            .try_withdraw(VOTER_A, |result| async move {
484                assert_eq!(result, None);
485                Err("io")
486            })
487            .await;
488        assert_eq!(res, Err("io"));
489        assert_eq!(e.vote(&VOTER_A), Some(INITIAL_LIMIT_UA));
490
491        // A winning lower vote from VOTER_B runs apply.
492        let res: Result<bool, &'static str> = e
493            .try_cast(VOTER_B, LOWER_LIMIT_UA, |result| async move {
494                assert_eq!(result, Some(LOWER_LIMIT_UA));
495                Ok(())
496            })
497            .await;
498        assert_eq!(res, Ok(true));
499
500        // Withdrawing VOTER_A's losing vote leaves the result unchanged, so apply does not run.
501        let res: Result<bool, &'static str> = e
502            .try_withdraw(VOTER_A, |_result| async {
503                panic!("apply should not run when the result is unchanged");
504            })
505            .await;
506        assert_eq!(res, Ok(false));
507        assert_eq!(e.vote(&VOTER_A), None);
508
509        // Withdrawing the last vote applies `None`; a second withdrawal returns Ok(false).
510        let res: Result<bool, &'static str> = e
511            .try_withdraw(VOTER_B, |result| async move {
512                assert_eq!(result, None);
513                Ok(())
514            })
515            .await;
516        assert_eq!(res, Ok(true));
517        let res: Result<bool, &'static str> = e
518            .try_withdraw(VOTER_B, |_result| async {
519                panic!("apply should not run when the voter has no vote");
520            })
521            .await;
522        assert_eq!(res, Ok(false));
523    }
524
525    /// Verifies that `record_inspect_with` does not hold the election's lock while running
526    /// `record`, so that `record` can call back into the election.
527    #[fuchsia::test]
528    async fn test_election_record_inspect_with_allows_reentrant_record() {
529        let e = Election::min();
530        e.cast("a", 2u32);
531        e.cast("b", 1u32);
532
533        let inspector = fuchsia_inspect::Inspector::default();
534        e.record_inspect_with(inspector.root(), "limit", |vote, node, key| {
535            // This would deadlock if the lock were held while recording.
536            assert_eq!(e.result(), Some(1));
537            node.record_uint(key, u64::from(*vote));
538        });
539        assert_data_tree!(inspector, root: {
540            limit: {
541                result: 1u64,
542                overridden: false,
543                votes: {
544                    a: 2u64,
545                    b: 1u64,
546                },
547            },
548        });
549    }
550
551    /// Verifies that dropping a `try_cast` future while `apply` is pending restores the previous
552    /// vote and result.
553    #[fuchsia::test]
554    fn test_election_try_cast_restores_vote_when_cancelled() {
555        const VOTER: u8 = 1;
556        const INITIAL_LIMIT_UA: u32 = 100;
557        const LOWER_LIMIT_UA: u32 = 50;
558
559        let e = Election::min();
560        e.cast(VOTER, INITIAL_LIMIT_UA);
561        {
562            let mut update = pin!(e.try_cast(VOTER, LOWER_LIMIT_UA, |_result| {
563                std::future::pending::<Result<(), ()>>()
564            }));
565            let mut cx = Context::from_waker(Waker::noop());
566            assert_eq!(update.as_mut().poll(&mut cx), Poll::Pending);
567            assert_eq!(e.result(), Some(LOWER_LIMIT_UA));
568        }
569        assert_eq!(e.vote(&VOTER), Some(INITIAL_LIMIT_UA));
570        assert_eq!(e.result(), Some(INITIAL_LIMIT_UA));
571    }
572}