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}