1use std::{
38 collections::HashMap,
39 io::{self, Read, Write},
40 net::{Shutdown, TcpListener, TcpStream, ToSocketAddrs},
41 sync::mpsc,
42 thread,
43 time::{Duration, Instant},
44};
45
46const HELLO_MAGIC: u32 = 0x5453_434d; #[derive(Debug, thiserror::Error)]
51pub enum Error {
52 #[error("collective I/O: {0}")]
54 Io(#[from] io::Error),
55 #[error("the mesh did not connect within {0:?}")]
57 Timeout(Duration),
58 #[error("bad mesh handshake: {0}")]
60 Handshake(String),
61 #[error("invalid group: {0}")]
63 InvalidGroup(String),
64 #[error("rank {0} left the mesh")]
66 PeerClosed(usize),
67 #[error("rank {rank} is out of step: expected {expected:?}, received {received:?}")]
69 Desync {
70 rank: usize,
72 expected: Tag,
74 received: Tag,
76 },
77 #[error("rank {rank} reduced {found} values, this rank {expected}")]
79 LengthMismatch {
80 rank: usize,
82 expected: usize,
84 found: usize,
86 },
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct Tag {
92 pub kind: u8,
94 pub group: u32,
96 pub sequence: u64,
98}
99
100const BROADCAST: u8 = 1;
101const ALL_GATHER: u8 = 2;
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum Reduce {
106 Max,
108 Min,
110 Sum,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct Group {
117 id: u32,
118 ranks: Vec<usize>,
119 index: usize,
120}
121
122impl Group {
123 pub fn new(id: u32, ranks: Vec<usize>, rank: usize) -> Result<Self, Error> {
127 let mut sorted = ranks.clone();
128 sorted.sort_unstable();
129 sorted.dedup();
130 if sorted.len() != ranks.len() {
131 return Err(Error::InvalidGroup(format!("{ranks:?} repeats a rank")));
132 }
133 let index = ranks
134 .iter()
135 .position(|member| *member == rank)
136 .ok_or_else(|| Error::InvalidGroup(format!("rank {rank} is not in {ranks:?}")))?;
137 Ok(Self { id, ranks, index })
138 }
139
140 pub fn ranks(&self) -> &[usize] {
142 &self.ranks
143 }
144
145 pub fn size(&self) -> usize {
147 self.ranks.len()
148 }
149
150 pub fn index(&self) -> usize {
152 self.index
153 }
154}
155
156pub struct Listener(TcpListener);
158
159impl Listener {
160 pub fn bind(host: &str) -> io::Result<Self> {
162 TcpListener::bind((host, 0)).map(Self)
163 }
164
165 pub fn port(&self) -> io::Result<u16> {
167 Ok(self.0.local_addr()?.port())
168 }
169}
170
171type Frame = (Tag, Vec<u8>);
172
173struct Peer {
174 stream: TcpStream,
175 frames: mpsc::Receiver<io::Result<Frame>>,
176 reader: Option<thread::JoinHandle<()>>,
177}
178
179pub struct Mesh {
181 rank: usize,
182 peers: Vec<Option<Peer>>,
183 sequences: HashMap<u32, u64>,
184}
185
186fn write_u32(out: &mut Vec<u8>, value: u32) {
187 out.extend_from_slice(&value.to_le_bytes());
188}
189
190fn read_u32(stream: &mut impl Read) -> io::Result<u32> {
191 let mut bytes = [0; 4];
192 stream.read_exact(&mut bytes)?;
193 Ok(u32::from_le_bytes(bytes))
194}
195
196fn read_u64(stream: &mut impl Read) -> io::Result<u64> {
197 let mut bytes = [0; 8];
198 stream.read_exact(&mut bytes)?;
199 Ok(u64::from_le_bytes(bytes))
200}
201
202fn read_frame(stream: &mut impl Read) -> io::Result<Frame> {
203 let mut kind = [0; 1];
204 stream.read_exact(&mut kind)?;
205 let group = read_u32(stream)?;
206 let sequence = read_u64(stream)?;
207 let len = usize::try_from(read_u64(stream)?)
208 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "frame too long"))?;
209 let mut payload = vec![0; len];
210 stream.read_exact(&mut payload)?;
211 let tag = Tag {
212 kind: kind[0],
213 group,
214 sequence,
215 };
216 Ok((tag, payload))
217}
218
219fn frame_header(tag: Tag, len: usize) -> Vec<u8> {
220 let mut header = Vec::with_capacity(21);
221 header.push(tag.kind);
222 write_u32(&mut header, tag.group);
223 header.extend_from_slice(&tag.sequence.to_le_bytes());
224 header.extend_from_slice(&(len as u64).to_le_bytes());
225 header
226}
227
228fn hello(rank: usize, world: usize) -> Vec<u8> {
229 let mut out = Vec::with_capacity(12);
230 write_u32(&mut out, HELLO_MAGIC);
231 write_u32(&mut out, rank as u32);
232 write_u32(&mut out, world as u32);
233 out
234}
235
236fn remaining(deadline: Instant, timeout: Duration) -> Result<Duration, Error> {
237 deadline
238 .checked_duration_since(Instant::now())
239 .filter(|left| !left.is_zero())
240 .ok_or(Error::Timeout(timeout))
241}
242
243impl Mesh {
244 pub fn connect(
248 rank: usize,
249 listener: Listener,
250 addresses: &[String],
251 timeout: Duration,
252 ) -> Result<Self, Error> {
253 let world = addresses.len();
254 if rank >= world {
255 return Err(Error::InvalidGroup(format!(
256 "rank {rank} of a {world}-rank mesh"
257 )));
258 }
259 let deadline = Instant::now() + timeout;
260 let mut streams: Vec<Option<TcpStream>> = (0..world).map(|_| None).collect();
261
262 for (peer, address) in addresses.iter().enumerate().take(rank) {
264 let stream = Self::dial(address, deadline, timeout)?;
265 (&stream).write_all(&hello(rank, world))?;
266 streams[peer] = Some(stream);
267 }
268 let listener = listener.0;
269 listener.set_nonblocking(true)?;
270 let mut expected = world - rank - 1;
271 while expected > 0 {
272 match listener.accept() {
273 Ok((stream, _)) => {
274 stream.set_nonblocking(false)?;
275 stream.set_read_timeout(Some(remaining(deadline, timeout)?))?;
276 let mut reader = &stream;
277 let (magic, peer, peer_world) = (
278 read_u32(&mut reader)?,
279 read_u32(&mut reader)? as usize,
280 read_u32(&mut reader)? as usize,
281 );
282 if magic != HELLO_MAGIC || peer_world != world || peer <= rank || peer >= world
283 {
284 return Err(Error::Handshake(format!(
285 "magic {magic:#x} rank {peer} of {peer_world} at rank {rank} of {world}"
286 )));
287 }
288 if streams[peer].is_some() {
289 return Err(Error::Handshake(format!("rank {peer} connected twice")));
290 }
291 stream.set_read_timeout(None)?;
292 streams[peer] = Some(stream);
293 expected -= 1;
294 }
295 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
296 remaining(deadline, timeout)?;
297 thread::sleep(Duration::from_millis(5));
298 }
299 Err(error) => return Err(error.into()),
300 }
301 }
302
303 let mut peers = Vec::with_capacity(world);
304 for (peer, stream) in streams.into_iter().enumerate() {
305 let Some(stream) = stream else {
306 peers.push(None);
307 continue;
308 };
309 stream.set_nodelay(true)?;
310 let mut reading = stream.try_clone()?;
311 let (sender, frames) = mpsc::channel();
312 let reader = thread::Builder::new()
313 .name(format!("ts-mesh-{peer}"))
314 .spawn(move || loop {
315 let frame = read_frame(&mut reading);
316 let failed = frame.is_err();
317 if sender.send(frame).is_err() || failed {
318 return;
319 }
320 })?;
321 peers.push(Some(Peer {
322 stream,
323 frames,
324 reader: Some(reader),
325 }));
326 }
327 tracing::info!(rank, world, "collective mesh connected");
328 Ok(Self {
329 rank,
330 peers,
331 sequences: HashMap::new(),
332 })
333 }
334
335 fn dial(address: &str, deadline: Instant, timeout: Duration) -> Result<TcpStream, Error> {
336 let resolved: Vec<_> = address.to_socket_addrs()?.collect();
337 let target = *resolved
338 .first()
339 .ok_or_else(|| Error::Handshake(format!("{address} resolves to nothing")))?;
340 loop {
341 match TcpStream::connect_timeout(&target, remaining(deadline, timeout)?) {
342 Ok(stream) => return Ok(stream),
343 Err(error) if error.kind() == io::ErrorKind::ConnectionRefused => {
346 thread::sleep(Duration::from_millis(20));
347 }
348 Err(error) => return Err(error.into()),
349 }
350 }
351 }
352
353 pub fn rank(&self) -> usize {
355 self.rank
356 }
357
358 fn next_tag(&mut self, kind: u8, group: &Group) -> Tag {
359 let sequence = self.sequences.entry(group.id).or_insert(0);
360 *sequence += 1;
361 Tag {
362 kind,
363 group: group.id,
364 sequence: *sequence,
365 }
366 }
367
368 fn peer(&mut self, rank: usize) -> &mut Peer {
369 self.peers[rank]
370 .as_mut()
371 .expect("a member other than this rank")
372 }
373
374 fn send(&mut self, rank: usize, tag: Tag, payload: &[u8]) -> Result<(), Error> {
375 let header = frame_header(tag, payload.len());
376 let stream = &mut self.peer(rank).stream;
377 stream
378 .write_all(&header)
379 .and_then(|()| stream.write_all(payload))
380 .map_err(|error| match error.kind() {
381 io::ErrorKind::BrokenPipe | io::ErrorKind::ConnectionReset => {
382 Error::PeerClosed(rank)
383 }
384 _ => Error::Io(error),
385 })
386 }
387
388 fn receive(&mut self, rank: usize, expected: Tag) -> Result<Vec<u8>, Error> {
389 let frame = self
390 .peer(rank)
391 .frames
392 .recv()
393 .map_err(|_| Error::PeerClosed(rank))?;
394 let (received, payload) = frame.map_err(|error| match error.kind() {
395 io::ErrorKind::UnexpectedEof | io::ErrorKind::ConnectionReset => {
396 Error::PeerClosed(rank)
397 }
398 _ => Error::Io(error),
399 })?;
400 if received != expected {
401 return Err(Error::Desync {
402 rank,
403 expected,
404 received,
405 });
406 }
407 Ok(payload)
408 }
409
410 pub fn broadcast(
414 &mut self,
415 group: &Group,
416 root: usize,
417 payload: Vec<u8>,
418 ) -> Result<Vec<u8>, Error> {
419 if group.size() == 1 {
420 return Ok(payload);
421 }
422 let tag = self.next_tag(BROADCAST, group);
423 if group.index() == root {
424 for (index, &member) in group.ranks().iter().enumerate() {
425 if index != root {
426 self.send(member, tag, &payload)?;
427 }
428 }
429 Ok(payload)
430 } else {
431 self.receive(group.ranks()[root], tag)
432 }
433 }
434
435 pub fn all_gather(&mut self, group: &Group, payload: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
437 if group.size() == 1 {
438 return Ok(vec![payload.to_vec()]);
439 }
440 let tag = self.next_tag(ALL_GATHER, group);
441 for (index, &member) in group.ranks().iter().enumerate() {
442 if index != group.index() {
443 self.send(member, tag, payload)?;
444 }
445 }
446 let mut gathered = Vec::with_capacity(group.size());
447 for (index, &member) in group.ranks().iter().enumerate() {
448 if index == group.index() {
449 gathered.push(payload.to_vec());
450 } else {
451 gathered.push(self.receive(member, tag)?);
452 }
453 }
454 Ok(gathered)
455 }
456
457 pub fn all_reduce(
459 &mut self,
460 group: &Group,
461 values: &[i64],
462 op: Reduce,
463 ) -> Result<Vec<i64>, Error> {
464 let mut rows = self.all_gather_i64(group, values)?.into_iter();
465 let mut reduced = rows.next().expect("a group has a member");
466 for row in rows {
467 for (out, value) in reduced.iter_mut().zip(row) {
468 *out = match op {
469 Reduce::Max => (*out).max(value),
470 Reduce::Min => (*out).min(value),
471 Reduce::Sum => *out + value,
472 };
473 }
474 }
475 Ok(reduced)
476 }
477
478 pub fn all_gather_i64(
480 &mut self,
481 group: &Group,
482 values: &[i64],
483 ) -> Result<Vec<Vec<i64>>, Error> {
484 let bytes: Vec<u8> = values
485 .iter()
486 .flat_map(|value| value.to_le_bytes())
487 .collect();
488 let gathered = self.all_gather(group, &bytes)?;
489 let mut rows = Vec::with_capacity(gathered.len());
490 for (index, row) in gathered.into_iter().enumerate() {
491 if row.len() != bytes.len() {
492 return Err(Error::LengthMismatch {
493 rank: group.ranks()[index],
494 expected: values.len(),
495 found: row.len() / 8,
496 });
497 }
498 rows.push(
499 row.chunks_exact(8)
500 .map(|chunk| i64::from_le_bytes(chunk.try_into().expect("eight bytes")))
501 .collect(),
502 );
503 }
504 Ok(rows)
505 }
506}
507
508impl Drop for Mesh {
509 fn drop(&mut self) {
510 for peer in self.peers.iter_mut().flatten() {
511 let _ = peer.stream.shutdown(Shutdown::Both);
512 if let Some(reader) = peer.reader.take() {
513 let _ = reader.join();
514 }
515 }
516 }
517}