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")]
21mod eventfd;
22#[cfg(target_os = "linux")]
23use eventfd as platform;
24
25#[cfg(windows)]
26mod iocp;
27#[cfg(windows)]
28use iocp as platform;
29
30#[cfg(not(any(target_os = "linux", windows)))]
31mod pipe;
32#[cfg(not(any(target_os = "linux", windows)))]
33use 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.
40pub 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.
50pub 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`.
56pub type PingSource = platform::PingSource;
57
58/// An error arising from processing events for a ping.
59#[derive(thiserror::Error, Debug)]
60#[error(transparent)]
61pub struct PingError(Box<dyn std::error::Error + Sync + Send>);
62
63#[cfg(test)]
64mod 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