1 | //! Ping to the event loop |
2 | //! |
3 | //! This is an event source that just produces `()` events whevener the associated |
4 | //! [`Ping::ping`](Ping#method.ping) method is called. If the event source is pinged multiple times |
5 | //! between a single dispatching, it'll only generate one event. |
6 | //! |
7 | //! This event source is a simple way of waking up the event loop from an other part of your program |
8 | //! (and is what backs the [`LoopSignal`](crate::LoopSignal)). It can also be used as a building |
9 | //! block to construct event sources whose source of event is not file descriptor, but rather an |
10 | //! userspace source (like an other thread). |
11 | |
12 | // The ping source has platform-dependent implementations provided by modules |
13 | // under this one. These modules should expose: |
14 | // - a make_ping() function |
15 | // - a Ping type |
16 | // - a PingSource type |
17 | // |
18 | // See eg. the pipe implementation for these items' specific requirements. |
19 | |
20 | #[cfg (target_os = "linux" )] |
21 | mod eventfd; |
22 | #[cfg (target_os = "linux" )] |
23 | use eventfd as platform; |
24 | |
25 | #[cfg (windows)] |
26 | mod iocp; |
27 | #[cfg (windows)] |
28 | use iocp as platform; |
29 | |
30 | #[cfg (not(any(target_os = "linux" , windows)))] |
31 | mod pipe; |
32 | #[cfg (not(any(target_os = "linux" , windows)))] |
33 | use pipe as platform; |
34 | |
35 | use std::fmt; |
36 | |
37 | /// Create a new ping event source |
38 | /// |
39 | /// you are given a [`Ping`] instance, which can be cloned and used to ping the |
40 | /// event loop, and a [`PingSource`], which you can insert in your event loop to |
41 | /// receive the pings. |
42 | pub fn make_ping() -> std::io::Result<(Ping, PingSource)> { |
43 | platform::make_ping() |
44 | } |
45 | |
46 | /// The ping event source |
47 | /// |
48 | /// You can insert it in your event loop to receive pings. |
49 | /// |
50 | /// If you use it directly, it will automatically remove itself from the event loop |
51 | /// once all [`Ping`] instances are dropped. |
52 | pub type Ping = platform::Ping; |
53 | |
54 | /// The Ping handle |
55 | /// |
56 | /// This handle can be cloned and sent accross threads. It can be used to |
57 | /// send pings to the `PingSource`. |
58 | pub type PingSource = platform::PingSource; |
59 | |
60 | /// An error arising from processing events for a ping. |
61 | #[derive (Debug)] |
62 | pub struct PingError(Box<dyn std::error::Error + Sync + Send>); |
63 | |
64 | impl fmt::Display for PingError { |
65 | #[cfg_attr (feature = "nightly_coverage" , coverage(off))] |
66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
67 | fmt::Display::fmt(&self.0, f) |
68 | } |
69 | } |
70 | |
71 | impl std::error::Error for PingError { |
72 | #[cfg_attr (feature = "nightly_coverage" , coverage(off))] |
73 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
74 | Some(&*self.0) |
75 | } |
76 | } |
77 | |
78 | #[cfg (test)] |
79 | mod tests { |
80 | use crate::transient::TransientSource; |
81 | use std::time::Duration; |
82 | |
83 | use super::*; |
84 | |
85 | #[test ] |
86 | fn ping() { |
87 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
88 | |
89 | let (ping, source) = make_ping().unwrap(); |
90 | |
91 | event_loop |
92 | .handle() |
93 | .insert_source(source, |(), &mut (), dispatched| *dispatched = true) |
94 | .unwrap(); |
95 | |
96 | ping.ping(); |
97 | |
98 | let mut dispatched = false; |
99 | event_loop |
100 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
101 | .unwrap(); |
102 | assert!(dispatched); |
103 | |
104 | // Ping has been drained an no longer generates events |
105 | let mut dispatched = false; |
106 | event_loop |
107 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
108 | .unwrap(); |
109 | assert!(!dispatched); |
110 | } |
111 | |
112 | #[test ] |
113 | fn ping_closed() { |
114 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
115 | |
116 | let (_, source) = make_ping().unwrap(); |
117 | event_loop |
118 | .handle() |
119 | .insert_source(source, |(), &mut (), dispatched| *dispatched = true) |
120 | .unwrap(); |
121 | |
122 | let mut dispatched = false; |
123 | |
124 | // If the sender is closed from the start, the ping should first trigger |
125 | // once, disabling itself but not invoking the callback |
126 | event_loop |
127 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
128 | .unwrap(); |
129 | assert!(!dispatched); |
130 | |
131 | // Then it should not trigger any more, so this dispatch should wait the whole 100ms |
132 | let now = std::time::Instant::now(); |
133 | event_loop |
134 | .dispatch(std::time::Duration::from_millis(100), &mut dispatched) |
135 | .unwrap(); |
136 | assert!(now.elapsed() >= std::time::Duration::from_millis(100)); |
137 | } |
138 | |
139 | #[test ] |
140 | fn ping_removed() { |
141 | // This keeps track of whether the event fired. |
142 | let mut dispatched = false; |
143 | |
144 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
145 | |
146 | let (sender, source) = make_ping().unwrap(); |
147 | let wrapper = TransientSource::from(source); |
148 | |
149 | // Check that the source starts off in the wrapper. |
150 | assert!(!wrapper.is_none()); |
151 | |
152 | // Put the source in the loop. |
153 | |
154 | let dispatcher = |
155 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
156 | |
157 | let token = event_loop |
158 | .handle() |
159 | .register_dispatcher(dispatcher.clone()) |
160 | .unwrap(); |
161 | |
162 | // Drop the sender and check that it's actually removed. |
163 | drop(sender); |
164 | |
165 | // There should be no event, but the loop still needs to wake up to |
166 | // process the close event (just like in the ping_closed() test). |
167 | event_loop |
168 | .dispatch(Duration::ZERO, &mut dispatched) |
169 | .unwrap(); |
170 | assert!(!dispatched); |
171 | |
172 | // Pull the source wrapper out. |
173 | |
174 | event_loop.handle().remove(token); |
175 | let wrapper = dispatcher.into_source_inner(); |
176 | |
177 | // Check that the inner source is now gone. |
178 | assert!(wrapper.is_none()); |
179 | } |
180 | |
181 | #[test ] |
182 | fn ping_fired_and_removed() { |
183 | // This is like ping_removed() with the single difference that we fire a |
184 | // ping and drop it between two successive dispatches of the loop. |
185 | |
186 | // This keeps track of whether the event fired. |
187 | let mut dispatched = false; |
188 | |
189 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
190 | |
191 | let (sender, source) = make_ping().unwrap(); |
192 | let wrapper = TransientSource::from(source); |
193 | |
194 | // Check that the source starts off in the wrapper. |
195 | assert!(!wrapper.is_none()); |
196 | |
197 | // Put the source in the loop. |
198 | |
199 | let dispatcher = |
200 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
201 | |
202 | let token = event_loop |
203 | .handle() |
204 | .register_dispatcher(dispatcher.clone()) |
205 | .unwrap(); |
206 | |
207 | // Send a ping AND drop the sender and check that it's actually removed. |
208 | sender.ping(); |
209 | drop(sender); |
210 | |
211 | // There should be an event, but the source should be removed from the |
212 | // loop immediately after. |
213 | event_loop |
214 | .dispatch(Duration::ZERO, &mut dispatched) |
215 | .unwrap(); |
216 | assert!(dispatched); |
217 | |
218 | // Pull the source wrapper out. |
219 | |
220 | event_loop.handle().remove(token); |
221 | let wrapper = dispatcher.into_source_inner(); |
222 | |
223 | // Check that the inner source is now gone. |
224 | assert!(wrapper.is_none()); |
225 | } |
226 | |
227 | #[test ] |
228 | fn ping_multiple_senders() { |
229 | // This is like ping_removed() but for testing the behaviour of multiple |
230 | // senders. |
231 | |
232 | // This keeps track of whether the event fired. |
233 | let mut dispatched = false; |
234 | |
235 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
236 | |
237 | let (sender0, source) = make_ping().unwrap(); |
238 | let wrapper = TransientSource::from(source); |
239 | let sender1 = sender0.clone(); |
240 | let sender2 = sender1.clone(); |
241 | |
242 | // Check that the source starts off in the wrapper. |
243 | assert!(!wrapper.is_none()); |
244 | |
245 | // Put the source in the loop. |
246 | |
247 | let dispatcher = |
248 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
249 | |
250 | let token = event_loop |
251 | .handle() |
252 | .register_dispatcher(dispatcher.clone()) |
253 | .unwrap(); |
254 | |
255 | // Send a ping AND drop the sender and check that it's actually removed. |
256 | sender0.ping(); |
257 | drop(sender0); |
258 | |
259 | // There should be an event, and the source should remain in the loop. |
260 | event_loop |
261 | .dispatch(Duration::ZERO, &mut dispatched) |
262 | .unwrap(); |
263 | assert!(dispatched); |
264 | |
265 | // Now test that the clones still work. Drop after the dispatch loop |
266 | // instead of before, this time. |
267 | dispatched = false; |
268 | |
269 | sender1.ping(); |
270 | |
271 | event_loop |
272 | .dispatch(Duration::ZERO, &mut dispatched) |
273 | .unwrap(); |
274 | assert!(dispatched); |
275 | |
276 | // Finally, drop all of them without sending anything. |
277 | |
278 | dispatched = false; |
279 | |
280 | drop(sender1); |
281 | drop(sender2); |
282 | |
283 | event_loop |
284 | .dispatch(Duration::ZERO, &mut dispatched) |
285 | .unwrap(); |
286 | assert!(!dispatched); |
287 | |
288 | // Pull the source wrapper out. |
289 | |
290 | event_loop.handle().remove(token); |
291 | let wrapper = dispatcher.into_source_inner(); |
292 | |
293 | // Check that the inner source is now gone. |
294 | assert!(wrapper.is_none()); |
295 | } |
296 | } |
297 | |