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 | /// Create a new ping event source |
36 | /// |
37 | /// you are given a [`Ping`] instance, which can be cloned and used to ping the |
38 | /// event loop, and a [`PingSource`], which you can insert in your event loop to |
39 | /// receive the pings. |
40 | pub fn make_ping() -> std::io::Result<(Ping, PingSource)> { |
41 | platform::make_ping() |
42 | } |
43 | |
44 | /// The ping event source |
45 | /// |
46 | /// You can insert it in your event loop to receive pings. |
47 | /// |
48 | /// If you use it directly, it will automatically remove itself from the event loop |
49 | /// once all [`Ping`] instances are dropped. |
50 | pub type Ping = platform::Ping; |
51 | |
52 | /// The Ping handle |
53 | /// |
54 | /// This handle can be cloned and sent accross threads. It can be used to |
55 | /// send pings to the `PingSource`. |
56 | pub type PingSource = platform::PingSource; |
57 | |
58 | /// An error arising from processing events for a ping. |
59 | #[derive (thiserror::Error, Debug)] |
60 | #[error(transparent)] |
61 | pub struct PingError(Box<dyn std::error::Error + Sync + Send>); |
62 | |
63 | #[cfg (test)] |
64 | mod tests { |
65 | use crate::transient::TransientSource; |
66 | use std::time::Duration; |
67 | |
68 | use super::*; |
69 | |
70 | #[test ] |
71 | fn ping() { |
72 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
73 | |
74 | let (ping, source) = make_ping().unwrap(); |
75 | |
76 | event_loop |
77 | .handle() |
78 | .insert_source(source, |(), &mut (), dispatched| *dispatched = true) |
79 | .unwrap(); |
80 | |
81 | ping.ping(); |
82 | |
83 | let mut dispatched = false; |
84 | event_loop |
85 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
86 | .unwrap(); |
87 | assert!(dispatched); |
88 | |
89 | // Ping has been drained an no longer generates events |
90 | let mut dispatched = false; |
91 | event_loop |
92 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
93 | .unwrap(); |
94 | assert!(!dispatched); |
95 | } |
96 | |
97 | #[test ] |
98 | fn ping_closed() { |
99 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
100 | |
101 | let (_, source) = make_ping().unwrap(); |
102 | event_loop |
103 | .handle() |
104 | .insert_source(source, |(), &mut (), dispatched| *dispatched = true) |
105 | .unwrap(); |
106 | |
107 | let mut dispatched = false; |
108 | |
109 | // If the sender is closed from the start, the ping should first trigger |
110 | // once, disabling itself but not invoking the callback |
111 | event_loop |
112 | .dispatch(std::time::Duration::ZERO, &mut dispatched) |
113 | .unwrap(); |
114 | assert!(!dispatched); |
115 | |
116 | // Then it should not trigger any more, so this dispatch should wait the whole 100ms |
117 | let now = std::time::Instant::now(); |
118 | event_loop |
119 | .dispatch(std::time::Duration::from_millis(100), &mut dispatched) |
120 | .unwrap(); |
121 | assert!(now.elapsed() >= std::time::Duration::from_millis(100)); |
122 | } |
123 | |
124 | #[test ] |
125 | fn ping_removed() { |
126 | // This keeps track of whether the event fired. |
127 | let mut dispatched = false; |
128 | |
129 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
130 | |
131 | let (sender, source) = make_ping().unwrap(); |
132 | let wrapper = TransientSource::from(source); |
133 | |
134 | // Check that the source starts off in the wrapper. |
135 | assert!(!wrapper.is_none()); |
136 | |
137 | // Put the source in the loop. |
138 | |
139 | let dispatcher = |
140 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
141 | |
142 | let token = event_loop |
143 | .handle() |
144 | .register_dispatcher(dispatcher.clone()) |
145 | .unwrap(); |
146 | |
147 | // Drop the sender and check that it's actually removed. |
148 | drop(sender); |
149 | |
150 | // There should be no event, but the loop still needs to wake up to |
151 | // process the close event (just like in the ping_closed() test). |
152 | event_loop |
153 | .dispatch(Duration::ZERO, &mut dispatched) |
154 | .unwrap(); |
155 | assert!(!dispatched); |
156 | |
157 | // Pull the source wrapper out. |
158 | |
159 | event_loop.handle().remove(token); |
160 | let wrapper = dispatcher.into_source_inner(); |
161 | |
162 | // Check that the inner source is now gone. |
163 | assert!(wrapper.is_none()); |
164 | } |
165 | |
166 | #[test ] |
167 | fn ping_fired_and_removed() { |
168 | // This is like ping_removed() with the single difference that we fire a |
169 | // ping and drop it between two successive dispatches of the loop. |
170 | |
171 | // This keeps track of whether the event fired. |
172 | let mut dispatched = false; |
173 | |
174 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
175 | |
176 | let (sender, source) = make_ping().unwrap(); |
177 | let wrapper = TransientSource::from(source); |
178 | |
179 | // Check that the source starts off in the wrapper. |
180 | assert!(!wrapper.is_none()); |
181 | |
182 | // Put the source in the loop. |
183 | |
184 | let dispatcher = |
185 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
186 | |
187 | let token = event_loop |
188 | .handle() |
189 | .register_dispatcher(dispatcher.clone()) |
190 | .unwrap(); |
191 | |
192 | // Send a ping AND drop the sender and check that it's actually removed. |
193 | sender.ping(); |
194 | drop(sender); |
195 | |
196 | // There should be an event, but the source should be removed from the |
197 | // loop immediately after. |
198 | event_loop |
199 | .dispatch(Duration::ZERO, &mut dispatched) |
200 | .unwrap(); |
201 | assert!(dispatched); |
202 | |
203 | // Pull the source wrapper out. |
204 | |
205 | event_loop.handle().remove(token); |
206 | let wrapper = dispatcher.into_source_inner(); |
207 | |
208 | // Check that the inner source is now gone. |
209 | assert!(wrapper.is_none()); |
210 | } |
211 | |
212 | #[test ] |
213 | fn ping_multiple_senders() { |
214 | // This is like ping_removed() but for testing the behaviour of multiple |
215 | // senders. |
216 | |
217 | // This keeps track of whether the event fired. |
218 | let mut dispatched = false; |
219 | |
220 | let mut event_loop = crate::EventLoop::<bool>::try_new().unwrap(); |
221 | |
222 | let (sender0, source) = make_ping().unwrap(); |
223 | let wrapper = TransientSource::from(source); |
224 | let sender1 = sender0.clone(); |
225 | let sender2 = sender1.clone(); |
226 | |
227 | // Check that the source starts off in the wrapper. |
228 | assert!(!wrapper.is_none()); |
229 | |
230 | // Put the source in the loop. |
231 | |
232 | let dispatcher = |
233 | crate::Dispatcher::new(wrapper, |(), &mut (), dispatched| *dispatched = true); |
234 | |
235 | let token = event_loop |
236 | .handle() |
237 | .register_dispatcher(dispatcher.clone()) |
238 | .unwrap(); |
239 | |
240 | // Send a ping AND drop the sender and check that it's actually removed. |
241 | sender0.ping(); |
242 | drop(sender0); |
243 | |
244 | // There should be an event, and the source should remain in the loop. |
245 | event_loop |
246 | .dispatch(Duration::ZERO, &mut dispatched) |
247 | .unwrap(); |
248 | assert!(dispatched); |
249 | |
250 | // Now test that the clones still work. Drop after the dispatch loop |
251 | // instead of before, this time. |
252 | dispatched = false; |
253 | |
254 | sender1.ping(); |
255 | |
256 | event_loop |
257 | .dispatch(Duration::ZERO, &mut dispatched) |
258 | .unwrap(); |
259 | assert!(dispatched); |
260 | |
261 | // Finally, drop all of them without sending anything. |
262 | |
263 | dispatched = false; |
264 | |
265 | drop(sender1); |
266 | drop(sender2); |
267 | |
268 | event_loop |
269 | .dispatch(Duration::ZERO, &mut dispatched) |
270 | .unwrap(); |
271 | assert!(!dispatched); |
272 | |
273 | // Pull the source wrapper out. |
274 | |
275 | event_loop.handle().remove(token); |
276 | let wrapper = dispatcher.into_source_inner(); |
277 | |
278 | // Check that the inner source is now gone. |
279 | assert!(wrapper.is_none()); |
280 | } |
281 | } |
282 | |