Skip to main content

yash_env/waker/
set.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2026 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Items for managing collections of wakers in the virtual system
18
19use super::WakerEntry;
20use std::cell::Cell;
21use std::collections::HashSet;
22use std::rc::Weak;
23use std::task::Waker;
24
25/// Set of wakers to be awakened when a certain event occurs
26///
27/// A `WakerSet` stores wakers that can later be awakened. It is used in the
28/// virtual system to manage wakers for events such as file readiness and signal
29/// delivery.
30///
31/// Wakers are stored as `Weak<Cell<Option<Waker>>>`. The asynchronous task that
32/// wants to be awakened creates a `Cell<Option<Waker>>` to hold its waker, and
33/// keeps a `Rc` to it until it is awakened as expected or it wants to cancel
34/// the wake-up. If the strong reference is dropped and the cell is deallocated
35/// before the waker is awakened, the `WakerSet` will not attempt to wake it, as
36/// the `Weak` reference will fail to upgrade. This design allows for automatic
37/// cleanup of wakers that are no longer valid without requiring explicit
38/// removal from the set.
39///
40/// The cell of the optional waker allows taking the waker out of the cell when
41/// waking, which is necessary to call `Waker::wake` without the need to clone
42/// the waker. After waking, the cell is set to `None` to indicate that the
43/// waker has been consumed and should not be woken again. This design also
44/// allows multiple sets for different events to share the same waker cell, as
45/// waking from one set will consume the waker and prevent it from being woken
46/// again from other sets, which is suitable when a task is waiting for multiple
47/// events and should be awakened when any of them occurs.
48///
49/// Wakers are considered **dead** when their weak reference cannot be upgraded
50/// or their cell contains `None`. Dead wakers are not awakened and may be
51/// automatically removed from the set as a side effect of other set operations.
52///
53/// A `WakerSet` internally uses a `HashSet` to store wakers. Wakers are
54/// compared by their pointer addresses, so adding the same waker multiple times
55/// will not create duplicates in the set.
56#[derive(Clone, Debug, Default, Eq, PartialEq)]
57pub struct WakerSet {
58    wakers: HashSet<WakerEntry>,
59}
60
61impl WakerSet {
62    /// Creates a new empty `WakerSet`.
63    #[inline(always)]
64    #[must_use]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Returns the capacity of the set.
70    #[inline(always)]
71    #[must_use]
72    pub fn capacity(&self) -> usize {
73        self.wakers.capacity()
74    }
75
76    /// Reserves capacity for at least `additional` more wakers to be inserted
77    /// without reallocating.
78    ///
79    /// After calling this method, the capacity will be greater than or equal to
80    /// the current item count plus `additional`. Does nothing if the capacity
81    /// is already sufficient.
82    #[inline(always)]
83    pub fn reserve(&mut self, additional: usize) {
84        self.wakers.reserve(additional)
85    }
86
87    /// Shrinks the capacity of the set as much as possible.
88    #[inline(always)]
89    pub fn shrink_to_fit(&mut self) {
90        self.wakers.shrink_to_fit()
91    }
92
93    /// Shrinks the capacity of the set to the specified minimum.
94    ///
95    /// After this operation, the capacity will be at least `min_capacity`, but
96    /// the set may have more capacity than that.
97    #[inline(always)]
98    pub fn shrink_to(&mut self, min_capacity: usize) {
99        self.wakers.shrink_to(min_capacity)
100    }
101
102    /// Returns the number of wakers in the set.
103    ///
104    /// Wakers may become dead over time, so the actual number of valid wakers
105    /// may be less than this count.
106    #[inline(always)]
107    #[must_use]
108    pub fn len(&self) -> usize {
109        self.wakers.len()
110    }
111
112    /// Returns `true` if the set contains no wakers.
113    ///
114    /// Wakers may become dead over time, so there may be no valid wakers even
115    /// if this method returns `false`.
116    #[inline(always)]
117    #[must_use]
118    pub fn is_empty(&self) -> bool {
119        self.wakers.is_empty()
120    }
121
122    /// Inserts a waker into the set.
123    ///
124    /// Returns `true` if the waker was not already present in the set, and
125    /// `false` if it was already present, in which case the set is not modified
126    /// and the passed weak reference is dropped.
127    ///
128    /// The amortized time complexity of this method is O(1). If the set is
129    /// full, it will first clean up dead wakers and possibly reallocate to
130    /// optimize the capacity for future insertions, which will cost O(n) time.
131    #[inline]
132    pub fn insert(&mut self, waker_cell: Weak<Cell<Option<Waker>>>) -> bool {
133        if self.len() == self.capacity() {
134            // If the set is full, the inner `HashSet` will need to reallocate
135            // to insert a new waker, which will cost O(n) time. This is a good
136            // opportunity to clean up dead wakers, which will also cost O(n)
137            // time.
138            self.wakers.retain(WakerEntry::is_alive);
139
140            // If we have removed substantially many wakers, we can also shrink
141            // the capacity to save memory. This is not strictly necessary, but
142            // it can help prevent the set from growing too large if many wakers
143            // are added and removed over time.
144            self.shrink_to(std::cmp::max(8, self.wakers.len() * 2));
145
146            // For amortized O(1) time complexity, we make sure the next cleanup
147            // will not occur until the number of wakers doubles again.
148            self.reserve(self.wakers.len());
149        }
150
151        let entry = WakerEntry(waker_cell);
152        entry.is_alive() && self.wakers.insert(entry)
153    }
154
155    /// Wakes all wakers in the set and clears the set.
156    ///
157    /// If a waker has been consumed or its strong reference has been dropped,
158    /// it is not awakened and simply removed from the set.
159    pub fn wake_all(&mut self) {
160        self.wakers
161            .drain()
162            .filter_map(|entry| entry.0.upgrade().and_then(|cell| cell.take()))
163            .for_each(Waker::wake);
164    }
165
166    /// Clears the set without waking any wakers.
167    #[inline(always)]
168    pub fn clear(&mut self) {
169        self.wakers.clear()
170    }
171}
172
173impl FromIterator<Weak<Cell<Option<Waker>>>> for WakerSet {
174    fn from_iter<I>(iter: I) -> Self
175    where
176        I: IntoIterator<Item = Weak<Cell<Option<Waker>>>>,
177    {
178        let wakers = HashSet::from_iter(iter.into_iter().map(WakerEntry));
179        Self { wakers }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::test_helper::WakeFlag;
187    use std::rc::Rc;
188    use std::sync::Arc;
189
190    #[test]
191    fn waking_all_inserted_wakers() {
192        let mut set = WakerSet::new();
193        let wake_flag_1 = Arc::new(WakeFlag::new());
194        let wake_flag_2 = Arc::new(WakeFlag::new());
195        let waker_1 = Rc::new(Cell::new(Some(Waker::from(wake_flag_1.clone()))));
196        let waker_2 = Rc::new(Cell::new(Some(Waker::from(wake_flag_2.clone()))));
197        assert!(set.insert(Rc::downgrade(&waker_1)));
198        assert!(set.insert(Rc::downgrade(&waker_2)));
199
200        set.wake_all();
201        assert!(wake_flag_1.is_woken());
202        assert!(wake_flag_2.is_woken());
203        assert!(set.is_empty());
204    }
205
206    #[test]
207    fn duplicate_wakers_are_not_inserted() {
208        let mut set = WakerSet::new();
209        let waker_1 = Rc::new(Cell::new(Some(Waker::noop().clone())));
210        let waker_1_clone = Rc::clone(&waker_1);
211        let waker_2 = Rc::new(Cell::new(Some(Waker::noop().clone())));
212        assert!(set.insert(Rc::downgrade(&waker_1)));
213        assert!(set.insert(Rc::downgrade(&waker_2)));
214        assert_eq!(set.len(), 2);
215
216        assert!(!set.insert(Rc::downgrade(&waker_1)));
217        assert!(!set.insert(Rc::downgrade(&waker_1_clone)));
218        assert_eq!(set.len(), 2);
219    }
220
221    #[test]
222    fn clearing_waker_set() {
223        let mut set = WakerSet::new();
224        let waker_1 = Rc::new(Cell::new(Some(Waker::noop().clone())));
225        let waker_2 = Rc::new(Cell::new(Some(Waker::noop().clone())));
226        assert!(set.insert(Rc::downgrade(&waker_1)));
227        assert!(set.insert(Rc::downgrade(&waker_2)));
228        assert_eq!(set.len(), 2);
229
230        set.clear();
231        assert!(set.is_empty());
232        assert_eq!(Rc::weak_count(&waker_1), 0);
233        assert_eq!(Rc::weak_count(&waker_2), 0);
234    }
235
236    #[test]
237    fn dead_wakers_are_removed_before_insertion_if_full() {
238        let mut set = WakerSet::new();
239        let waker_1 = Rc::new(Cell::new(Some(Waker::noop().clone())));
240        let waker_2 = Rc::new(Cell::new(Some(Waker::noop().clone())));
241        let waker_3 = Rc::new(Cell::new(Some(Waker::noop().clone())));
242        let waker_4 = Rc::new(Cell::new(Some(Waker::noop().clone())));
243        let waker_5 = Rc::new(Cell::new(Some(Waker::noop().clone())));
244
245        set.reserve(10);
246        assert!(set.insert(Rc::downgrade(&waker_1)));
247        assert!(set.insert(Rc::downgrade(&waker_2)));
248        waker_2.take(); // Consume waker_2 to make it dead
249        assert!(set.insert(Rc::downgrade(&waker_3)));
250        while set.len() < set.capacity() - 1 {
251            let waker = Rc::new(Cell::new(Some(Waker::noop().clone())));
252            assert!(set.insert(Rc::downgrade(&waker)));
253        }
254        assert!(set.insert(Rc::downgrade(&waker_4)));
255        assert_eq!(set.len(), set.capacity());
256
257        // Now the set is full. Inserting waker_5 should trigger cleanup of dead wakers.
258        assert!(set.insert(Rc::downgrade(&waker_5)));
259        assert_eq!(Rc::weak_count(&waker_1), 1);
260        assert_eq!(Rc::weak_count(&waker_2), 0);
261        assert_eq!(Rc::weak_count(&waker_3), 1);
262        assert_eq!(Rc::weak_count(&waker_4), 1);
263        assert_eq!(Rc::weak_count(&waker_5), 1);
264        assert_eq!(set.len(), 4);
265    }
266}