agave_scheduling_utils/handshake/
client.rs1use {
2 crate::handshake::{
3 ClientHandshakeError, ClientLogon, ClientSession, ClientWorkerSession,
4 shared::{LOGON_FAILURE, MAX_WORKERS, VERSION},
5 },
6 libc::CMSG_LEN,
7 nix::sys::socket::{self, ControlMessageOwned, MsgFlags, UnixAddr},
8 rts_alloc::Allocator,
9 std::{
10 fs::File,
11 io::{IoSliceMut, Write},
12 os::{
13 fd::{AsRawFd, FromRawFd},
14 unix::net::UnixStream,
15 },
16 path::Path,
17 time::Duration,
18 },
19};
20
21const GLOBAL_SHMEM: usize = 3;
23
24const CMSG_MAX_SIZE: usize = (GLOBAL_SHMEM + MAX_WORKERS * 2) * 4;
30
31pub fn connect(
42 path: impl AsRef<Path>,
43 logon: ClientLogon,
44 timeout: Duration,
45) -> Result<ClientSession, ClientHandshakeError> {
46 connect_path(path.as_ref(), logon, timeout)
47}
48
49fn connect_path(
50 path: &Path,
51 logon: ClientLogon,
52 timeout: Duration,
53) -> Result<ClientSession, ClientHandshakeError> {
54 let mut stream = UnixStream::connect(path)?;
62 stream.set_read_timeout(Some(timeout))?;
63 stream.set_write_timeout(Some(timeout))?;
64
65 send_logon(&mut stream, logon)?;
67
68 let files = recv_response(&mut stream)?;
70
71 let session = setup_session(&logon, files)?;
73
74 Ok(session)
75}
76
77fn send_logon(stream: &mut UnixStream, logon: ClientLogon) -> Result<(), ClientHandshakeError> {
78 let mut buf = [0; 1024];
80 buf[..8].copy_from_slice(&VERSION.to_le_bytes());
81 const LOGON_END: usize = 8 + core::mem::size_of::<ClientLogon>();
82 let ptr = buf[8..LOGON_END].as_mut_ptr().cast::<ClientLogon>();
83 unsafe {
87 core::ptr::write_unaligned(ptr, logon);
88 }
89 stream.write_all(&buf)?;
90
91 Ok(())
92}
93
94fn recv_response(stream: &mut UnixStream) -> Result<Vec<File>, ClientHandshakeError> {
95 let mut buf = [0; 1024];
97 let mut iov = [IoSliceMut::new(&mut buf)];
98 let mut cmsgs = [0u8; unsafe { CMSG_LEN(CMSG_MAX_SIZE as u32) as usize }];
100 let msg = socket::recvmsg::<UnixAddr>(
101 stream.as_raw_fd(),
102 &mut iov,
103 Some(&mut cmsgs),
104 MsgFlags::empty(),
105 )?;
106
107 let buf = msg.iovs().next().unwrap();
109 if buf[0] == LOGON_FAILURE {
110 let reason_len = usize::from(buf[1]);
111 #[allow(clippy::arithmetic_side_effects)]
112 let reason = std::str::from_utf8(&buf[2..2 + reason_len]).unwrap();
113
114 return Err(ClientHandshakeError::Rejected(reason.to_string()));
115 }
116
117 let mut cmsgs = msg.cmsgs().unwrap();
119 let fds = match cmsgs.next() {
120 Some(ControlMessageOwned::ScmRights(fds)) => fds,
121 Some(msg) => panic!("Unexpected; msg={msg:?}"),
122 None => panic!(),
123 };
124 let files = fds
126 .into_iter()
127 .map(|fd| unsafe { File::from_raw_fd(fd) })
128 .collect();
129
130 Ok(files)
131}
132
133pub fn setup_session(
134 logon: &ClientLogon,
135 files: Vec<File>,
136) -> Result<ClientSession, ClientHandshakeError> {
137 if files.len() < GLOBAL_SHMEM {
138 return Err(ClientHandshakeError::ProtocolViolation);
139 }
140 let (global_files, worker_files) = files.split_at(GLOBAL_SHMEM);
141 let [allocator_file, tpu_to_pack_file, progress_tracker_file] = global_files else {
142 unreachable!();
143 };
144
145 let allocators = (0..logon.allocator_handles)
147 .map(|_| Allocator::join(allocator_file))
148 .collect::<Result<Vec<_>, _>>()?;
149
150 if worker_files.is_empty()
152 || !worker_files.len().is_multiple_of(2)
153 || worker_files.len() / 2 != logon.worker_count
154 {
155 return Err(ClientHandshakeError::ProtocolViolation);
156 }
157
158 let session = ClientSession {
161 allocators,
162 tpu_to_pack: unsafe { shaq::spsc::Consumer::join(tpu_to_pack_file)? },
163 progress_tracker: unsafe { shaq::spsc::Consumer::join(progress_tracker_file)? },
164 workers: worker_files
165 .chunks(2)
166 .map(|window| {
167 let [pack_to_worker, worker_to_pack] = window else {
168 panic!();
169 };
170
171 Ok(ClientWorkerSession {
172 pack_to_worker: unsafe { shaq::spsc::Producer::join(pack_to_worker)? },
173 worker_to_pack: unsafe { shaq::spsc::Consumer::join(worker_to_pack)? },
174 })
175 })
176 .collect::<Result<_, ClientHandshakeError>>()?,
177 };
178
179 drop(files);
181
182 Ok(session)
183}
184
185impl From<nix::Error> for ClientHandshakeError {
186 fn from(value: nix::Error) -> Self {
187 Self::Io(value.into())
188 }
189}