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
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}