mctx_core/
tokio_adapter.rs1use crate::{MctxError, Publication, SendReport};
2use std::io;
3#[cfg(not(unix))]
4use std::time::Duration;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum TokioSendError {
10 #[error("MCTX: tokio readiness failed: {0}")]
12 Readiness(io::Error),
13
14 #[error(transparent)]
16 Send(#[from] MctxError),
17}
18
19#[derive(Debug)]
25pub struct TokioPublication {
26 #[cfg(unix)]
27 inner: tokio::io::unix::AsyncFd<Publication>,
28 #[cfg(not(unix))]
29 inner: Publication,
30 #[cfg(not(unix))]
31 readiness: tokio::net::UdpSocket,
32}
33
34impl TokioPublication {
35 pub fn new(publication: Publication) -> io::Result<Self> {
37 #[cfg(unix)]
38 {
39 Ok(Self {
40 inner: tokio::io::unix::AsyncFd::new(publication)?,
41 })
42 }
43
44 #[cfg(not(unix))]
45 {
46 let readiness_socket = publication.socket().try_clone()?;
47 let readiness_socket: std::net::UdpSocket = readiness_socket.into();
48 readiness_socket.set_nonblocking(true)?;
49 Ok(Self {
50 inner: publication,
51 readiness: tokio::net::UdpSocket::from_std(readiness_socket)?,
52 })
53 }
54 }
55
56 pub fn publication(&self) -> &Publication {
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_publication(self) -> Publication {
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))]
85 pub fn with_poll_interval(self, _poll_interval: Duration) -> Self {
86 self
87 }
88
89 pub async fn send(&self, payload: &[u8]) -> Result<SendReport, TokioSendError> {
91 #[cfg(unix)]
92 {
93 loop {
94 let mut readiness = self
95 .inner
96 .writable()
97 .await
98 .map_err(TokioSendError::Readiness)?;
99
100 match self.inner.get_ref().send(payload) {
101 Ok(report) => return Ok(report),
102 Err(error) if error.is_would_block() => readiness.clear_ready(),
103 Err(error) => return Err(TokioSendError::Send(error)),
104 }
105 }
106 }
107
108 #[cfg(not(unix))]
109 {
110 loop {
111 match self.readiness.try_send(payload) {
112 Ok(bytes_sent) => {
113 return self
114 .inner
115 .finish_send(Ok(bytes_sent))
116 .map_err(TokioSendError::Send);
117 }
118 Err(error) if error.kind() == io::ErrorKind::WouldBlock => self
119 .readiness
120 .writable()
121 .await
122 .map_err(TokioSendError::Readiness)?,
123 Err(error) => {
124 return self
125 .inner
126 .finish_send(Err(error))
127 .map_err(TokioSendError::Send);
128 }
129 }
130 }
131 }
132 }
133}
134
135#[cfg(all(test, feature = "tokio"))]
136mod tests {
137 use super::*;
138 use crate::test_support::{
139 TEST_GROUP, is_multicast_test_network_unavailable, recv_payload, test_multicast_receiver,
140 };
141 use crate::{Context, PublicationConfig};
142
143 #[tokio::test]
144 async fn tokio_publication_sends_a_packet() {
145 let (receiver, port) = test_multicast_receiver();
146 let mut context = Context::new();
147 let id = context
148 .add_publication(PublicationConfig::new(TEST_GROUP, port))
149 .unwrap();
150
151 let publication = context.take_publication(id).unwrap();
152 let publication = TokioPublication::new(publication).unwrap();
153
154 match publication.send(b"tokio hello").await {
155 Ok(_) => {}
156 Err(TokioSendError::Send(error)) if is_multicast_test_network_unavailable(&error) => {
157 eprintln!("skipping multicast integration test: {error}");
158 return;
159 }
160 Err(error) => panic!("tokio multicast integration test failed: {error}"),
161 }
162 let payload = recv_payload(&receiver);
163
164 assert_eq!(payload, b"tokio hello");
165 }
166}