calloop/sources/
ping.rs

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
35use 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.
42pub 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.
52pub 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`.
58pub type PingSource = platform::PingSource;
59
60/// An error arising from processing events for a ping.
61#[derive(Debug)]
62pub struct PingError(Box<dyn std::error::Error + Sync + Send>);
63
64impl 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
71impl 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)]
79mod 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}