agave_scheduling_utils/handshake/
server.rs1use {
2 crate::handshake::{
3 AgaveHandshakeError, AgaveTpuToPackSession, AgaveWorkerSession, ClientLogon,
4 shared::{
5 AgaveSession, GLOBAL_ALLOCATORS, LOGON_FAILURE, LOGON_SUCCESS, MAX_ALLOCATOR_HANDLES,
6 MAX_WORKERS, VERSION,
7 },
8 },
9 agave_scheduler_bindings::PackToWorkerMessage,
10 nix::sys::socket::{self, ControlMessage, MsgFlags, UnixAddr},
11 rts_alloc::Allocator,
12 std::{
13 ffi::CStr,
14 fs::File,
15 io::{IoSlice, Read, Write},
16 os::{
17 fd::{AsRawFd, FromRawFd},
18 unix::net::{UnixListener, UnixStream},
19 },
20 path::Path,
21 time::{Duration, Instant},
22 },
23};
24
25type ShaqError = shaq::error::Error;
26type RtsAllocError = rts_alloc::error::Error;
27
28const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(1);
29const SHMEM_NAME: &CStr = c"/agave-scheduler-bindings";
30
31pub struct Server {
33 listener: UnixListener,
34
35 buffer: [u8; 1024],
36}
37
38impl Server {
39 pub fn new(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
40 let listener = UnixListener::bind(path)?;
41
42 Ok(Self {
43 listener,
44 buffer: [0; 1024],
45 })
46 }
47
48 pub fn accept(&mut self) -> Result<AgaveSession, AgaveHandshakeError> {
49 let (mut stream, _) = self.listener.accept()?;
51 stream.set_read_timeout(Some(HANDSHAKE_TIMEOUT))?;
52
53 match self.handle_logon(&mut stream) {
54 Ok(session) => Ok(session),
55 Err(err) => {
56 let reason = err.to_string();
57 let reason_len = u8::try_from(reason.len()).unwrap_or(u8::MAX);
58
59 let buffer_len = 2usize.checked_add(usize::from(reason_len)).unwrap();
60 self.buffer[0] = LOGON_FAILURE;
61 self.buffer[1] = reason_len;
62 self.buffer[2..buffer_len]
63 .copy_from_slice(&reason.as_bytes()[..usize::from(reason_len)]);
64
65 stream.set_nonblocking(true)?;
66 let _ = stream.write(&self.buffer[..buffer_len])?;
69
70 Err(err)
71 }
72 }
73 }
74
75 fn handle_logon(
76 &mut self,
77 stream: &mut UnixStream,
78 ) -> Result<AgaveSession, AgaveHandshakeError> {
79 let logon = self.recv_logon(stream)?;
81
82 let (session, files) = Self::setup_session(logon)?;
84
85 let fds_raw: Vec<_> = files.iter().map(|file| file.as_raw_fd()).collect();
87 let iov = [IoSlice::new(&[LOGON_SUCCESS])];
88 let cmsgs = [ControlMessage::ScmRights(&fds_raw)];
89 let sent =
90 socket::sendmsg::<UnixAddr>(stream.as_raw_fd(), &iov, &cmsgs, MsgFlags::empty(), None)
91 .map_err(std::io::Error::from)?;
92 debug_assert_eq!(sent, 1);
93
94 Ok(session)
95 }
96
97 fn recv_logon(&mut self, stream: &mut UnixStream) -> Result<ClientLogon, AgaveHandshakeError> {
98 let handshake_start = Instant::now();
100 let mut buffer_len = 0;
101 while buffer_len < self.buffer.len() {
102 let read = stream.read(&mut self.buffer[buffer_len..])?;
103 if read == 0 {
104 return Err(AgaveHandshakeError::EofDuringHandshake);
105 }
106
107 buffer_len = buffer_len.checked_add(read).unwrap();
109
110 if handshake_start.elapsed() > HANDSHAKE_TIMEOUT {
111 return Err(AgaveHandshakeError::Timeout);
112 }
113 }
114
115 let version = u64::from_le_bytes(self.buffer[..8].try_into().unwrap());
118 if version != VERSION {
119 return Err(AgaveHandshakeError::Version {
120 server: VERSION,
121 client: version,
122 });
123 }
124
125 const LOGON_END: usize = 8 + core::mem::size_of::<ClientLogon>();
128 let logon = ClientLogon::try_from_bytes(&self.buffer[8..LOGON_END]).unwrap();
129
130 if !(1..=MAX_WORKERS).contains(&logon.worker_count) {
132 return Err(AgaveHandshakeError::WorkerCount(logon.worker_count));
133 }
134
135 if !(1..=MAX_ALLOCATOR_HANDLES).contains(&logon.allocator_handles) {
137 return Err(AgaveHandshakeError::AllocatorHandles(
138 logon.allocator_handles,
139 ));
140 }
141
142 Ok(logon)
143 }
144
145 pub fn setup_session(
146 logon: ClientLogon,
147 ) -> Result<(AgaveSession, Vec<File>), AgaveHandshakeError> {
148 let (allocator_file, tpu_to_pack_allocator) = Self::create_allocator(&logon)?;
151
152 let (tpu_to_pack_file, tpu_to_pack_queue) =
154 Self::create_producer(logon.tpu_to_pack_capacity, true)?;
155 let (progress_tracker_file, progress_tracker) =
156 Self::create_producer(logon.progress_tracker_capacity, false)?;
157
158 let (worker_files, workers) = (0..logon.worker_count).try_fold(
160 (Vec::default(), Vec::default()),
161 |(mut fds, mut workers), _| {
162 let allocator = Allocator::join(&allocator_file)?;
163
164 let (pack_to_worker_file, pack_to_worker) =
165 Self::create_consumer(logon.pack_to_worker_capacity)?;
166 let (worker_to_pack_file, worker_to_pack) =
167 Self::create_producer(logon.worker_to_pack_capacity, true)?;
168
169 fds.extend([pack_to_worker_file, worker_to_pack_file]);
170 workers.push(AgaveWorkerSession {
171 allocator,
172 pack_to_worker,
173 worker_to_pack,
174 });
175
176 Ok::<_, AgaveHandshakeError>((fds, workers))
177 },
178 )?;
179
180 Ok((
181 AgaveSession {
182 flags: logon.flags,
183 tpu_to_pack: AgaveTpuToPackSession {
184 allocator: tpu_to_pack_allocator,
185 producer: tpu_to_pack_queue,
186 },
187 progress_tracker,
188 workers,
189 },
190 [allocator_file, tpu_to_pack_file, progress_tracker_file]
191 .into_iter()
192 .chain(worker_files)
193 .collect(),
194 ))
195 }
196
197 fn create_allocator(logon: &ClientLogon) -> Result<(File, Allocator), RtsAllocError> {
198 let allocator_count = GLOBAL_ALLOCATORS
199 .checked_add(logon.worker_count)
200 .unwrap()
201 .checked_add(logon.allocator_handles)
202 .unwrap();
203
204 let create = |huge: bool| {
205 let allocator_file = Self::create_shmem(huge)?;
206 let allocator_file_size = Self::align_file_size(logon.allocator_size, huge);
207
208 unsafe {
210 Allocator::create(
211 &allocator_file,
212 allocator_file_size,
213 u32::try_from(allocator_count).unwrap(),
214 2 * 1024 * 1024,
215 )
216 }
217 .map(|allocator| (allocator_file, allocator))
218 };
219
220 create(true).or_else(|_| create(false))
222 }
223
224 fn create_producer<T>(
225 capacity: usize,
226 huge: bool,
227 ) -> Result<(File, shaq::spsc::Producer<T>), ShaqError> {
228 let create = |huge: bool| {
229 let file = Self::create_shmem(huge)?;
230 let minimum_file_size = shaq::spsc::minimum_file_size::<T>(capacity);
231 let file_size = Self::align_file_size(minimum_file_size, huge);
232
233 unsafe { shaq::spsc::Producer::create(&file, file_size) }
235 .map(|producer| (file, producer))
236 };
237
238 match huge {
240 true => create(true).or_else(|_| create(false)),
241 false => create(false),
242 }
243 }
244
245 fn create_consumer(
246 capacity: usize,
247 ) -> Result<(File, shaq::spsc::Consumer<PackToWorkerMessage>), ShaqError> {
248 let create = |huge: bool| {
249 let file = Self::create_shmem(huge)?;
250 let minimum_file_size = shaq::spsc::minimum_file_size::<PackToWorkerMessage>(capacity);
251 let file_size = Self::align_file_size(minimum_file_size, huge);
252
253 unsafe { shaq::spsc::Consumer::create(&file, file_size) }
255 .map(|producer| (file, producer))
256 };
257
258 create(true).or_else(|_| create(false))
260 }
261
262 #[cfg(any(
263 target_os = "linux",
264 target_os = "l4re",
265 target_os = "android",
266 target_os = "emscripten"
267 ))]
268 fn create_shmem(huge: bool) -> Result<File, std::io::Error> {
269 let flags = match huge {
270 true => libc::MFD_HUGETLB | libc::MFD_HUGE_2MB,
271 false => 0,
272 };
273
274 unsafe {
275 let ret = libc::memfd_create(SHMEM_NAME.as_ptr(), flags);
276 if ret == -1 {
277 return Err(std::io::Error::last_os_error());
278 }
279
280 Ok(File::from_raw_fd(ret))
281 }
282 }
283
284 #[cfg(not(any(
285 target_os = "linux",
286 target_os = "l4re",
287 target_os = "android",
288 target_os = "emscripten"
289 )))]
290 fn create_shmem(huge: bool) -> Result<File, std::io::Error> {
291 if huge {
292 return Err(std::io::ErrorKind::Unsupported.into());
293 }
294
295 unsafe {
296 let ret = libc::shm_unlink(SHMEM_NAME.as_ptr());
298 if ret == -1 {
299 let err = std::io::Error::last_os_error();
300 if err.kind() != std::io::ErrorKind::NotFound {
301 return Err(err);
302 }
303 }
304
305 let ret = libc::shm_open(
307 SHMEM_NAME.as_ptr(),
308 libc::O_CREAT | libc::O_EXCL | libc::O_RDWR,
309 #[cfg(not(target_os = "macos"))]
310 {
311 libc::S_IRUSR | libc::S_IWUSR
312 },
313 #[cfg(any(target_os = "macos", target_os = "ios"))]
314 {
315 (libc::S_IRUSR | libc::S_IWUSR) as libc::c_uint
316 },
317 );
318 if ret == -1 {
319 return Err(std::io::Error::last_os_error());
320 }
321 let file = File::from_raw_fd(ret);
322
323 let ret = libc::shm_unlink(SHMEM_NAME.as_ptr());
325 if ret == -1 {
326 return Err(std::io::Error::last_os_error());
327 }
328
329 Ok(file)
330 }
331 }
332
333 fn align_file_size(size: usize, huge: bool) -> usize {
334 match huge {
335 true => size.next_multiple_of(2 * 1024 * 1024),
336 false => size.next_multiple_of(4096),
337 }
338 }
339}