mcrx_core/
tokio_adapter.rs1use crate::{McrxError, Packet, PacketWithMetadata, Subscription};
2use std::io;
3#[cfg(not(unix))]
4use std::time::Duration;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum TokioReceiveError {
10 #[error("MCRX: tokio readiness failed: {0}")]
12 Readiness(io::Error),
13
14 #[error(transparent)]
16 Receive(#[from] McrxError),
17}
18
19#[derive(Debug)]
28pub struct TokioSubscription {
29 #[cfg(unix)]
30 inner: tokio::io::unix::AsyncFd<Subscription>,
31 #[cfg(not(unix))]
32 inner: Subscription,
33 #[cfg(not(unix))]
34 poll_interval: Duration,
35}
36
37impl TokioSubscription {
38 pub fn new(subscription: Subscription) -> io::Result<Self> {
40 #[cfg(unix)]
41 {
42 Ok(Self {
43 inner: tokio::io::unix::AsyncFd::new(subscription)?,
44 })
45 }
46
47 #[cfg(not(unix))]
48 {
49 Ok(Self {
50 inner: subscription,
51 poll_interval: Duration::from_millis(10),
52 })
53 }
54 }
55
56 pub fn subscription(&self) -> &Subscription {
58 #[cfg(unix)]
59 {
60 self.inner.get_ref()
61 }
62
63 #[cfg(not(unix))]
64 {
65 &self.inner
66 }
67 }
68
69 pub fn into_subscription(self) -> Subscription {
71 #[cfg(unix)]
72 {
73 self.inner.into_inner()
74 }
75
76 #[cfg(not(unix))]
77 {
78 self.inner
79 }
80 }
81
82 #[cfg(not(unix))]
84 pub fn with_poll_interval(mut self, poll_interval: Duration) -> Self {
85 self.poll_interval = poll_interval;
86 self
87 }
88
89 pub async fn recv(&mut self) -> Result<Packet, TokioReceiveError> {
91 #[cfg(unix)]
92 {
93 loop {
94 let mut readiness = self
95 .inner
96 .readable_mut()
97 .await
98 .map_err(TokioReceiveError::Readiness)?;
99
100 match readiness.get_inner_mut().try_recv()? {
101 Some(packet) => return Ok(packet),
102 None => readiness.clear_ready(),
103 }
104 }
105 }
106
107 #[cfg(not(unix))]
108 {
109 loop {
110 match self.inner.try_recv()? {
111 Some(packet) => return Ok(packet),
112 None => tokio::time::sleep(self.poll_interval).await,
113 }
114 }
115 }
116 }
117
118 pub async fn recv_with_metadata(&mut self) -> Result<PacketWithMetadata, TokioReceiveError> {
120 #[cfg(unix)]
121 {
122 loop {
123 let mut readiness = self
124 .inner
125 .readable_mut()
126 .await
127 .map_err(TokioReceiveError::Readiness)?;
128
129 match readiness.get_inner_mut().try_recv_with_metadata()? {
130 Some(packet) => return Ok(packet),
131 None => readiness.clear_ready(),
132 }
133 }
134 }
135
136 #[cfg(not(unix))]
137 {
138 loop {
139 match self.inner.try_recv_with_metadata()? {
140 Some(packet) => return Ok(packet),
141 None => tokio::time::sleep(self.poll_interval).await,
142 }
143 }
144 }
145 }
146}
147
148#[cfg(all(test, feature = "tokio"))]
149mod tests {
150 use super::*;
151 use crate::test_support::{make_multicast_sender, sample_config_on_unused_port};
152 use crate::{Context, SubscriptionConfig};
153 use std::net::{Ipv4Addr, SocketAddrV4};
154 use tokio::time::{Duration, timeout};
155
156 fn ipv4_group(config: &SubscriptionConfig) -> Ipv4Addr {
157 config.ipv4_membership().unwrap().group
158 }
159
160 #[tokio::test]
161 async fn tokio_subscription_receives_metadata_packet() {
162 let mut context = Context::new();
163 let config = sample_config_on_unused_port();
164 let id = context.add_subscription(config.clone()).unwrap();
165 context.join_subscription(id).unwrap();
166
167 let subscription = context.take_subscription(id).unwrap();
168 let mut subscription = TokioSubscription::new(subscription).unwrap();
169
170 let sender = make_multicast_sender();
171 let payload = b"tokio adapter packet";
172 sender
173 .send_to(
174 payload,
175 SocketAddrV4::new(ipv4_group(&config), config.dst_port),
176 )
177 .unwrap();
178
179 let packet = timeout(Duration::from_secs(1), subscription.recv_with_metadata())
180 .await
181 .expect("timed out waiting for tokio packet")
182 .unwrap();
183
184 assert_eq!(packet.packet.subscription_id, id);
185 assert_eq!(&packet.packet.payload[..], payload);
186 }
187
188 #[cfg(feature = "metrics")]
189 #[tokio::test]
190 async fn tokio_subscription_with_metrics_is_spawn_safe() {
191 let mut context = Context::new();
192 let config = sample_config_on_unused_port();
193 let id = context.add_subscription(config.clone()).unwrap();
194 context.join_subscription(id).unwrap();
195
196 let subscription = context.take_subscription(id).unwrap();
197 let sender = make_multicast_sender();
198 let payload = b"tokio metrics packet";
199
200 let handle = tokio::spawn(async move {
201 let mut subscription = TokioSubscription::new(subscription).unwrap();
202 let packet = timeout(Duration::from_secs(1), subscription.recv_with_metadata())
203 .await
204 .expect("timed out waiting for spawned tokio packet")
205 .unwrap();
206 let metrics = subscription.subscription().metrics_snapshot();
207 (packet, metrics)
208 });
209
210 sender
211 .send_to(
212 payload,
213 SocketAddrV4::new(ipv4_group(&config), config.dst_port),
214 )
215 .unwrap();
216
217 let (packet, metrics) = handle.await.unwrap();
218
219 assert_eq!(packet.packet.subscription_id, id);
220 assert_eq!(&packet.packet.payload[..], payload);
221 assert_eq!(metrics.packets_received, 1);
222 assert_eq!(metrics.bytes_received, payload.len() as u64);
223 assert_eq!(metrics.last_payload_len, Some(payload.len()));
224 }
225}