Skip to main content

maelstrom_util/
net.rs

1//! Functions that are useful for reading/writing messages from/to sockets.
2
3use anyhow::{bail, Context as _, Result};
4use maelstrom_base::proto;
5use maelstrom_github::{GitHubReadQueue, GitHubWriteQueue};
6use maelstrom_linux::{self as linux, Fd};
7use serde::{de::DeserializeOwned, Serialize};
8use slog::{debug, Logger};
9use std::time::Duration;
10use std::{
11    fmt::Debug,
12    io::{Read, Write},
13    os::fd::AsRawFd,
14};
15use tokio::{
16    io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _},
17    sync::mpsc::{UnboundedReceiver, UnboundedSender},
18    time::Instant,
19};
20
21fn encode_message_to_vec(msg: &impl Serialize) -> Result<Vec<u8>> {
22    let msg_len = proto::serialized_size(msg)? as u32;
23    let mut buf = Vec::<u8>::with_capacity(msg_len as usize + 4);
24    Write::write_all(&mut buf, &msg_len.to_be_bytes())?;
25    proto::serialize_into(&mut buf, msg)?;
26    Ok(buf)
27}
28
29/// Write a message to a normal (threaded) writer. Each message is framed by sending a leading
30/// 4-byte, little-endian message size. The message is logged at the debug log level, as well as
31/// any error encountered sending it.
32pub fn write_message_to_socket(
33    stream: &mut impl Write,
34    msg: &(impl Debug + Serialize),
35    log: &Logger,
36) -> Result<()> {
37    debug!(log, "sending message"; "message" => #?msg);
38    (|| Ok(stream.write_all(&encode_message_to_vec(msg)?)?))()
39        .inspect_err(|err| debug!(log, "error sending message"; "error" => %err))
40}
41
42/// Write a message to a Tokio output stream. Each message is framed by sending a leading 4-byte,
43/// little-endian message size. The message is logged at the debug log level, as well as any error
44/// encountered sending it.
45pub async fn write_message_to_async_socket(
46    stream: &mut (impl AsyncWrite + Unpin),
47    msg: &(impl Debug + Serialize),
48    log: &Logger,
49) -> Result<()> {
50    debug!(log, "sending message"; "message" => #?msg);
51    async { Ok(stream.write_all(&encode_message_to_vec(msg)?).await?) }
52        .await
53        .inspect_err(|err| debug!(log, "error sending message"; "error" => %err))
54}
55
56/// Read a message from a normal (threaded) reader. The framing must match that of
57/// [`write_message_to_socket`] and [`write_message_to_async_socket`]. The received message will be
58/// logged at the debug log level.
59pub fn read_message_from_socket<MessageT>(stream: &mut impl Read, log: &Logger) -> Result<MessageT>
60where
61    MessageT: Debug + DeserializeOwned,
62{
63    (|| {
64        let mut msg_len: [u8; 4] = [0; 4];
65        stream.read_exact(&mut msg_len)?;
66        let mut buf = vec![0; u32::from_be_bytes(msg_len) as usize];
67        stream.read_exact(&mut buf)?;
68        Result::Ok(proto::deserialize(&buf)?)
69    })()
70    .inspect(|msg| debug!(log, "received message"; "message" => #?msg))
71    .inspect_err(|err| debug!(log, "error receiving message"; "error" => %err))
72}
73
74/// Read a message from a Tokio input stream. The framing must match that of
75/// [`write_message_to_socket`] and [`write_message_to_async_socket`].
76pub async fn read_message_from_async_socket<MessageT>(
77    stream: &mut (impl AsyncRead + Unpin),
78    log: &Logger,
79) -> Result<MessageT>
80where
81    MessageT: Debug + DeserializeOwned,
82{
83    async {
84        let mut msg_len: [u8; 4] = [0; 4];
85        stream.read_exact(&mut msg_len).await?;
86        let mut buf = vec![0; u32::from_be_bytes(msg_len) as usize];
87        stream.read_exact(&mut buf).await?;
88        Result::Ok(proto::deserialize(&buf)?)
89    }
90    .await
91    .inspect(|msg| debug!(log, "received message"; "message" => #?msg))
92    .inspect_err(|err| debug!(log, "error receiving message"; "error" => %err))
93}
94
95/// Loop, reading messages from a channel and writing them to a socket. The `log` parameter is used
96/// to insert debug logging. Each message is logged. Also, if there is a failure reading a message,
97/// that is logged as well.
98pub async fn async_socket_writer<MessageT>(
99    mut channel: UnboundedReceiver<MessageT>,
100    mut socket: (impl AsyncWrite + Unpin),
101    log: Logger,
102    context: &'static str,
103) -> Result<()>
104where
105    MessageT: Debug + Serialize,
106{
107    while let Some(msg) = channel.recv().await {
108        write_message_to_async_socket(&mut socket, &msg, &log)
109            .await
110            .context(context)?;
111    }
112    Ok(())
113}
114
115/// Loop, reading messages from a socket and writing them to an mpsc channel. The `transform`
116/// parameter is used to log the messages and wrap them in any necessary structure for internal use
117/// by the program.
118pub async fn async_socket_reader<MessageT, TransformedT>(
119    mut socket: (impl AsyncRead + Unpin),
120    channel: UnboundedSender<TransformedT>,
121    transform: impl Fn(MessageT) -> TransformedT,
122    log: Logger,
123    context: &'static str,
124) -> Result<()>
125where
126    MessageT: Debug + DeserializeOwned,
127{
128    loop {
129        let msg = read_message_from_async_socket(&mut socket, &log)
130            .await
131            .context(context)?;
132        if channel.send(transform(msg)).is_err() {
133            return Ok(());
134        }
135    }
136}
137
138pub trait AsRawFdExt: Sized {
139    fn set_socket_options(self) -> Result<Self>;
140}
141
142impl<T: AsRawFd> AsRawFdExt for T {
143    fn set_socket_options(self) -> Result<Self> {
144        let fd = Fd::from_raw(self.as_raw_fd());
145        linux::setsockopt_tcp_nodelay(&fd, true)?;
146        linux::setsockopt_so_keepalive(&fd, true)?;
147        linux::setsockopt_tcp_keepcnt(&fd, 3)?;
148        linux::setsockopt_tcp_keepidle(&fd, 300)?;
149        linux::setsockopt_tcp_keepintvl(&fd, 300)?;
150        Ok(self)
151    }
152}
153
154pub async fn read_message_from_github_queue<MessageT>(
155    queue: &mut GitHubReadQueue,
156    log: &Logger,
157) -> Result<MessageT>
158where
159    MessageT: Debug + DeserializeOwned,
160{
161    async {
162        let Some(buf) = queue.read_msg().await? else {
163            bail!("unexpected eof from github queue");
164        };
165        Result::Ok(proto::deserialize(&buf)?)
166    }
167    .await
168    .inspect(|msg| debug!(log, "received message"; "message" => #?msg))
169    .inspect_err(|err| debug!(log, "error receiving message"; "error" => %err))
170}
171
172pub async fn github_queue_reader<MessageT, TransformedT>(
173    queue: &mut GitHubReadQueue,
174    channel: UnboundedSender<TransformedT>,
175    transform: impl Fn(MessageT) -> TransformedT,
176    log: Logger,
177    context: &'static str,
178) -> Result<()>
179where
180    MessageT: Debug + DeserializeOwned,
181{
182    loop {
183        let msg = read_message_from_github_queue(queue, &log)
184            .await
185            .context(context)?;
186        if channel.send(transform(msg)).is_err() {
187            return Ok(());
188        }
189    }
190}
191
192pub async fn write_message_to_github_queue(
193    queue: &mut GitHubWriteQueue,
194    msg: &(impl Debug + Serialize),
195    log: &Logger,
196) -> Result<()> {
197    debug!(log, "sending message"; "message" => #?msg);
198    queue
199        .write_msg(&proto::serialize(msg).unwrap())
200        .await
201        .inspect_err(|err| debug!(log, "error sending message"; "error" => %err))?;
202    Ok(())
203}
204
205pub async fn write_many_messages_to_github_queue<MessageT>(
206    queue: &mut GitHubWriteQueue,
207    msgs: &[MessageT],
208    log: &Logger,
209) -> Result<()>
210where
211    MessageT: Debug + Serialize,
212{
213    queue
214        .write_many_msgs(&Vec::from_iter(
215            msgs.iter().map(|msg| proto::serialize(msg).unwrap()),
216        ))
217        .await
218        .inspect_err(|err| debug!(log, "error sending message"; "error" => %err))?;
219    Ok(())
220}
221
222pub async fn github_queue_writer<MessageT>(
223    mut channel: UnboundedReceiver<MessageT>,
224    queue: &mut GitHubWriteQueue,
225    log: Logger,
226    context: &'static str,
227) -> Result<()>
228where
229    MessageT: Debug + Serialize,
230{
231    let mut to_send = vec![];
232    let mut next = Instant::now() + Duration::from_millis(10);
233    loop {
234        match tokio::time::timeout_at(next, channel.recv()).await {
235            Err(_) => {
236                if !to_send.is_empty() {
237                    write_many_messages_to_github_queue(queue, &to_send, &log)
238                        .await
239                        .context("writing messages to github queue")
240                        .context(context)?;
241                    to_send.clear();
242                }
243                next = Instant::now() + Duration::from_millis(10);
244            }
245            Ok(Some(msg)) => {
246                to_send.push(msg);
247            }
248            Ok(None) => break,
249        }
250    }
251    queue
252        .shut_down()
253        .await
254        .context("shutting down github queue")
255        .context(context)?;
256
257    Ok(())
258}