1use crate::{
2 config::streaming::StreamingLogLevel,
3 logging::ServerLogger,
4 memory_management::{ManagedMemoryId, SharedMemoryBindings},
5 server::{Binding, ServerError},
6 stream::{StreamFactory, StreamPool},
7};
8use core::any::Any;
9use cubecl_common::{backtrace::BackTrace, stream_id::StreamId};
10use hashbrown::HashMap;
11use std::{
12 boxed::Box,
13 format,
14 sync::{Arc, mpsc::SyncSender},
15 vec::Vec,
16};
17
18pub trait EventStreamBackend: 'static {
23 type Stream: core::fmt::Debug;
25 type Event: Send + 'static;
27
28 fn create_stream(&self) -> Self::Stream;
30 fn handle_cursor(stream: &Self::Stream, handle: &Binding) -> u64;
32 fn is_healthy(stream: &Self::Stream) -> bool;
34
35 fn flush(stream: &mut Self::Stream) -> Self::Event;
38 fn wait_event(stream: &mut Self::Stream, event: Self::Event);
40 fn wait_event_sync(event: Self::Event) -> Result<(), ServerError>;
42}
43
44#[derive(Debug)]
49pub struct MultiStream<B: EventStreamBackend> {
50 streams: StreamPool<EventStreamBackendWrapper<B>>,
52 pub logger: Arc<ServerLogger>,
54 max_streams: usize,
55 gc: GcThread<B>,
56 shared_bindings_pool: Vec<(ManagedMemoryId, StreamId, u64)>,
57}
58
59pub(crate) struct StreamWrapper<B: EventStreamBackend> {
64 stream: B::Stream,
66 cursor: u64,
68 last_synced: HashMap<usize, u64>,
70}
71
72pub struct ResolvedStreams<'a, B: EventStreamBackend> {
74 pub cursor: u64,
78 streams: &'a mut StreamPool<EventStreamBackendWrapper<B>>,
79 analysis: SharedBindingAnalysis,
80 gc: &'a GcThread<B>,
81 pub current: StreamId,
83}
84
85#[derive(Debug)]
86pub struct GcTask<B: EventStreamBackend> {
88 to_drop: Box<dyn Any + Send + 'static>,
89 event: B::Event,
91}
92
93impl<B: EventStreamBackend> GcTask<B> {
94 pub fn new<T: Send + 'static>(to_drop: T, event: B::Event) -> Self {
96 Self {
97 to_drop: Box::new(to_drop),
98 event,
99 }
100 }
101}
102
103#[derive(Debug)]
104struct EventStreamBackendWrapper<B: EventStreamBackend> {
105 backend: B,
106}
107
108impl<B: EventStreamBackend> StreamFactory for EventStreamBackendWrapper<B> {
109 type Stream = StreamWrapper<B>;
110
111 fn create(&mut self) -> Self::Stream {
112 StreamWrapper {
113 stream: self.backend.create_stream(),
114 cursor: 0,
115 last_synced: Default::default(),
116 }
117 }
118}
119
120#[derive(Debug)]
121struct GcThread<B: EventStreamBackend> {
122 sender: SyncSender<GcTask<B>>,
123}
124
125impl<B: EventStreamBackend> GcThread<B> {
126 fn new() -> GcThread<B> {
127 let (sender, recv) = std::sync::mpsc::sync_channel::<GcTask<B>>(8);
128
129 std::thread::spawn(move || {
130 while let Ok(event) = recv.recv() {
131 B::wait_event_sync(event.event).unwrap();
132 core::mem::drop(event.to_drop);
133 }
134 });
135
136 GcThread { sender }
137 }
138 fn register(&self, task: GcTask<B>) {
139 self.sender.send(task).unwrap()
140 }
141}
142
143fn stream_index(stream_id: &StreamId, max_streams: usize) -> usize {
144 stream_id.value as usize % max_streams
145}
146
147impl<'a, B: EventStreamBackend> ResolvedStreams<'a, B> {
148 pub fn get(&mut self, stream_id: &StreamId) -> &mut B::Stream {
150 let stream = self.streams.get_mut(stream_id);
151 &mut stream.stream
152 }
153
154 pub fn current(&mut self) -> &mut B::Stream {
156 let stream = self.streams.get_mut(&self.current);
157 &mut stream.stream
158 }
159
160 pub fn gc(&mut self, gc: GcTask<B>) {
162 self.gc.sender.send(gc).unwrap();
163 }
164}
165
166impl<'a, B: EventStreamBackend> Drop for ResolvedStreams<'a, B> {
167 fn drop(&mut self) {
168 if self.analysis.pinned.is_empty() {
169 return;
170 }
171
172 let stream = self.streams.get_mut(&self.current);
173 let event_origin = B::flush(&mut stream.stream);
174
175 let stream_gc = &mut unsafe { self.streams.get_special(0) }.stream;
176 B::wait_event(stream_gc, event_origin);
177 let event = B::flush(stream_gc);
178
179 let pinned = core::mem::take(&mut self.analysis.pinned);
180 self.gc.register(GcTask::new(pinned, event));
181 }
182}
183
184impl<B: EventStreamBackend> MultiStream<B> {
185 pub fn backend_mut(&mut self) -> &mut B {
189 &mut self.streams.factory_mut().backend
190 }
191
192 pub fn new(logger: Arc<ServerLogger>, backend: B, max_streams: u8) -> Self {
194 let wrapper = EventStreamBackendWrapper { backend };
195 Self {
196 streams: StreamPool::new(wrapper, max_streams, 1),
197 logger,
198 max_streams: max_streams as usize,
199 gc: GcThread::new(),
200 shared_bindings_pool: Vec::new(),
201 }
202 }
203
204 pub fn stream_ids(&self) -> impl Iterator<Item = StreamId> + '_ {
206 self.streams.stream_ids()
207 }
208
209 pub fn gc(&mut self, gc: GcTask<B>) {
211 self.gc.sender.send(gc).unwrap();
212 }
213
214 pub fn resolve<'a>(
220 &mut self,
221 stream_id: StreamId,
222 handles: impl Iterator<Item = &'a Binding>,
223 enforce_healthy: bool,
224 ) -> Result<ResolvedStreams<'_, B>, ServerError> {
225 let analysis = self.align_streams(stream_id, handles);
226
227 let stream = self.streams.get_mut(&stream_id);
228 stream.cursor += 1;
229
230 if enforce_healthy && !B::is_healthy(&stream.stream) {
231 return Err(ServerError::Generic {
232 reason: "Can't resolve the stream since it is currently in an error state".into(),
233 backtrace: BackTrace::capture(),
234 });
235 }
236
237 Ok(ResolvedStreams {
238 cursor: stream.cursor,
239 streams: &mut self.streams,
240 current: stream_id,
241 analysis,
242 gc: &self.gc,
243 })
244 }
245
246 fn align_streams<'a>(
251 &mut self,
252 stream_id: StreamId,
253 handles: impl Iterator<Item = &'a Binding>,
254 ) -> SharedBindingAnalysis {
255 let analysis = self.update_shared_bindings(stream_id, handles);
256
257 self.apply_analysis(stream_id, analysis)
258 }
259
260 pub(crate) fn update_shared_bindings<'a>(
265 &mut self,
266 stream_id: StreamId,
267 handles: impl Iterator<Item = &'a Binding>,
268 ) -> SharedBindingAnalysis {
269 self.shared_bindings_pool.clear();
271
272 let mut analysis = SharedBindingAnalysis::default();
273
274 for handle in handles.filter(|handle| handle.stream != stream_id) {
276 let index = stream_index(&handle.stream, self.max_streams);
277 let stream = unsafe { self.streams.get_mut_index(index) };
278 let cursor_handle = B::handle_cursor(&stream.stream, handle);
279
280 self.shared_bindings_pool.push((
281 handle.memory.descriptor().id,
282 handle.stream,
283 cursor_handle,
284 ));
285 analysis.pinned.push(handle.memory.clone());
290 }
291
292 let current = self.streams.get_mut(&stream_id);
293
294 for (handle_id, stream, cursor) in self.shared_bindings_pool.iter() {
295 let index = stream_index(stream, self.max_streams);
296
297 if let Some(last_synced) = current.last_synced.get(&index) {
298 if last_synced < cursor {
299 self.logger.log_streaming(
300 |level| matches!(level, StreamingLogLevel::Full),
301 || {
302 format!(
303 "Binding on {} is shared on {} since it's not sync {} < {}",
304 stream, stream_id, last_synced, cursor
305 )
306 },
307 );
308 analysis.shared(*handle_id, index);
309 }
310 } else {
311 self.logger.log_streaming(
312 |level| matches!(level, StreamingLogLevel::Full),
313 || {
314 format!(
315 "Binding on {} is shared on {} since it was never synced.",
316 stream, stream_id,
317 )
318 },
319 );
320 analysis.shared(*handle_id, index);
321 }
322 }
323
324 analysis
325 }
326
327 pub(crate) fn apply_analysis(
328 &mut self,
329 stream_id: StreamId,
330 analysis: SharedBindingAnalysis,
331 ) -> SharedBindingAnalysis {
332 if analysis.slices.is_empty() {
333 return analysis;
334 }
335
336 let mut events = Vec::with_capacity(analysis.slices.len());
337
338 unsafe {
339 for origin in analysis.slices.keys() {
340 let stream = self.streams.get_mut_index(*origin);
341 let event = B::flush(&mut stream.stream);
342
343 events.push(((origin, stream.cursor), event));
344 }
345 }
346
347 let stream = self.streams.get_mut(&stream_id);
348
349 for ((stream_origin, cursor_origin), event) in events {
350 stream.last_synced.insert(*stream_origin, cursor_origin);
351
352 self.logger.log_streaming(
353 |level| !matches!(level, StreamingLogLevel::Disabled),
354 || format!("Waiting on {stream_origin} from {stream_id}",),
355 );
356
357 B::wait_event(&mut stream.stream, event);
358 }
359
360 analysis
361 }
362}
363
364impl<B: EventStreamBackend> core::fmt::Debug for StreamWrapper<B> {
365 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366 f.debug_struct("StreamWrapper")
367 .field("stream", &self.stream)
368 .field("cursor", &self.cursor)
369 .field("last_synced", &self.last_synced)
370 .finish()
371 }
372}
373
374#[derive(Default, Debug)]
375pub(crate) struct SharedBindingAnalysis {
376 slices: HashMap<usize, Vec<ManagedMemoryId>>,
377 pinned: SharedMemoryBindings,
387}
388
389impl PartialEq for SharedBindingAnalysis {
392 fn eq(&self, other: &Self) -> bool {
393 self.slices == other.slices
394 }
395}
396
397impl Eq for SharedBindingAnalysis {}
398
399impl SharedBindingAnalysis {
400 fn shared(&mut self, id: ManagedMemoryId, index: usize) {
401 match self.slices.get_mut(&index) {
402 Some(bindings) => bindings.push(id),
403 None => {
404 self.slices.insert(index, alloc::vec![id]);
405 }
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use crate::server::Handle;
413 use core::sync::atomic::{AtomicBool, Ordering};
414
415 use super::*;
416
417 const MAX_STREAMS: u8 = 4;
418
419 #[test_log::test]
420 fn test_analysis_shared_bindings_1() {
421 let logger = Arc::new(ServerLogger::default());
422 let stream_1 = StreamId { value: 1 };
423 let stream_2 = StreamId { value: 2 };
424
425 let binding_1 = handle(stream_1);
426 let binding_2 = handle(stream_2);
427
428 let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
429 ms.resolve(stream_1, [].into_iter(), false).unwrap();
430 ms.resolve(stream_2, [].into_iter(), false).unwrap();
431
432 let analysis = ms.update_shared_bindings(stream_1, [&binding_1, &binding_2].into_iter());
433
434 let mut expected = SharedBindingAnalysis::default();
435 expected.shared(
436 binding_2.memory.descriptor().id,
437 ms.streams.stream_index(&binding_2.stream),
438 );
439
440 assert_eq!(analysis, expected);
441 }
442
443 #[test_log::test]
444 fn test_analysis_shared_bindings_2() {
445 let logger = Arc::new(ServerLogger::default());
446 let stream_1 = StreamId { value: 1 };
447 let stream_2 = StreamId { value: 2 };
448
449 let binding_1 = handle(stream_1);
450 let binding_2 = handle(stream_2);
451 let binding_3 = handle(stream_1);
452
453 let mut ms = MultiStream::new(logger, TestBackend, 4);
454 ms.resolve(stream_1, [].into_iter(), false).unwrap();
455 ms.resolve(stream_2, [].into_iter(), false).unwrap();
456
457 let analysis =
458 ms.update_shared_bindings(stream_1, [&binding_1, &binding_2, &binding_3].into_iter());
459
460 let mut expected = SharedBindingAnalysis::default();
461 expected.shared(
462 binding_2.memory.descriptor().id,
463 ms.streams.stream_index(&binding_2.stream),
464 );
465
466 assert_eq!(analysis, expected);
467 }
468
469 #[test_log::test]
470 fn test_analysis_no_shared() {
471 let logger = Arc::new(ServerLogger::default());
472 let stream_1 = StreamId { value: 1 };
473 let stream_2 = StreamId { value: 2 };
474
475 let binding_1 = handle(stream_1);
476 let binding_2 = handle(stream_1);
477 let binding_3 = handle(stream_1);
478
479 let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
480 ms.resolve(stream_1, [].into_iter(), false).unwrap();
481 ms.resolve(stream_2, [].into_iter(), false).unwrap();
482
483 let analysis =
484 ms.update_shared_bindings(stream_1, [&binding_1, &binding_2, &binding_3].into_iter());
485
486 let expected = SharedBindingAnalysis::default();
487
488 assert_eq!(analysis, expected);
489 }
490
491 #[test_log::test]
492 fn test_state() {
493 let logger = Arc::new(ServerLogger::default());
494 let stream_1 = StreamId { value: 1 };
495 let stream_2 = StreamId { value: 2 };
496
497 let binding_1 = handle(stream_1);
498 let binding_2 = handle(stream_2);
499 let binding_3 = handle(stream_1);
500
501 let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
502 ms.resolve(stream_1, [].into_iter(), false).unwrap();
503 ms.resolve(stream_2, [].into_iter(), false).unwrap();
504
505 ms.resolve(
506 stream_1,
507 [&binding_1, &binding_2, &binding_3].into_iter(),
508 false,
509 )
510 .unwrap();
511
512 let stream1 = ms.streams.get_mut(&stream_1);
513 let index_2 = stream_index(&stream_2, MAX_STREAMS as usize);
514 assert_eq!(stream1.last_synced.get(&index_2), Some(&1));
515 assert_eq!(stream1.cursor, 2);
516
517 let stream2 = ms.streams.get_mut(&stream_2);
518 assert!(stream2.last_synced.is_empty());
519 assert_eq!(stream2.cursor, 1);
520 }
521
522 #[test_log::test]
523 fn test_cross_stream_binding_pinned_until_gc_event() {
524 let logger = Arc::new(ServerLogger::default());
525 let stream_1 = StreamId { value: 1 };
526 let stream_2 = StreamId { value: 2 };
527
528 let gate = Arc::new(AtomicBool::new(false));
529 let mut ms = MultiStream::new(logger, GatedBackend { gate: gate.clone() }, MAX_STREAMS);
530 ms.resolve(stream_1, [].into_iter(), false).unwrap();
531 ms.resolve(stream_2, [].into_iter(), false).unwrap();
532
533 let handle = Handle::new(stream_1, 10);
534 let observer = handle.memory.clone();
535 let binding = handle.binding();
536
537 drop(ms.resolve(stream_2, [&binding].into_iter(), false).unwrap());
538 drop(binding);
539
540 assert!(
544 !observer.is_free(),
545 "cross-stream binding must stay pinned while the consumer event is pending"
546 );
547
548 gate.store(true, Ordering::Release);
549 wait_until_free(&observer);
550 }
551
552 #[test_log::test]
553 fn test_already_synced_cross_stream_binding_still_pinned() {
554 let logger = Arc::new(ServerLogger::default());
555 let stream_1 = StreamId { value: 1 };
556 let stream_2 = StreamId { value: 2 };
557
558 let gate = Arc::new(AtomicBool::new(true));
559 let mut ms = MultiStream::new(logger, GatedBackend { gate: gate.clone() }, MAX_STREAMS);
560 ms.resolve(stream_1, [].into_iter(), false).unwrap();
561 ms.resolve(stream_2, [].into_iter(), false).unwrap();
562
563 let handle_1 = Handle::new(stream_1, 10);
565 let binding_1 = handle_1.binding();
566 drop(
567 ms.resolve(stream_2, [&binding_1].into_iter(), false)
568 .unwrap(),
569 );
570 drop(binding_1);
571
572 gate.store(false, Ordering::Release);
574
575 let handle_2 = Handle::new(stream_1, 10);
576 let observer = handle_2.memory.clone();
577 let binding_2 = handle_2.binding();
578
579 let resolved = ms
584 .resolve(stream_2, [&binding_2].into_iter(), false)
585 .unwrap();
586 assert!(resolved.analysis.slices.is_empty());
587 drop(resolved);
588 drop(binding_2);
589
590 assert!(
591 !observer.is_free(),
592 "already-synced cross-stream binding must still be pinned"
593 );
594
595 gate.store(true, Ordering::Release);
596 wait_until_free(&observer);
597 }
598
599 fn wait_until_free(observer: &crate::memory_management::ManagedMemoryHandle) {
600 let start = std::time::Instant::now();
601 while !observer.is_free() {
602 assert!(
603 start.elapsed() < std::time::Duration::from_secs(10),
604 "pinned binding was never released"
605 );
606 std::thread::yield_now();
607 }
608 }
609
610 fn handle(stream: StreamId) -> Binding {
611 Handle::new(stream, 10).binding()
612 }
613
614 struct TestBackend;
615
616 #[derive(Debug)]
617 struct TestStream {}
618
619 #[derive(Debug)]
620 struct TestEvent {}
621
622 struct GatedBackend {
625 gate: Arc<AtomicBool>,
626 }
627
628 #[derive(Debug)]
629 struct GatedStream {
630 gate: Arc<AtomicBool>,
631 }
632
633 #[derive(Debug)]
634 struct GatedEvent {
635 gate: Arc<AtomicBool>,
636 }
637
638 impl EventStreamBackend for GatedBackend {
639 type Stream = GatedStream;
640 type Event = GatedEvent;
641
642 fn create_stream(&self) -> Self::Stream {
643 GatedStream {
644 gate: self.gate.clone(),
645 }
646 }
647
648 fn flush(stream: &mut Self::Stream) -> Self::Event {
649 GatedEvent {
650 gate: stream.gate.clone(),
651 }
652 }
653
654 fn wait_event(_stream: &mut Self::Stream, _event: Self::Event) {}
655
656 fn wait_event_sync(event: Self::Event) -> Result<(), ServerError> {
657 while !event.gate.load(Ordering::Acquire) {
658 std::thread::yield_now();
659 }
660 Ok(())
661 }
662
663 fn handle_cursor(_stream: &Self::Stream, _handle: &Binding) -> u64 {
664 0
665 }
666
667 fn is_healthy(_stream: &Self::Stream) -> bool {
668 true
669 }
670 }
671
672 impl EventStreamBackend for TestBackend {
673 type Stream = TestStream;
674 type Event = TestEvent;
675
676 fn create_stream(&self) -> Self::Stream {
677 TestStream {}
678 }
679
680 fn flush(_stream: &mut Self::Stream) -> Self::Event {
681 TestEvent {}
682 }
683
684 fn wait_event(_stream: &mut Self::Stream, _event: Self::Event) {}
685
686 fn wait_event_sync(_event: Self::Event) -> Result<(), ServerError> {
687 Ok(())
688 }
689
690 fn handle_cursor(_stream: &Self::Stream, _handle: &Binding) -> u64 {
691 0
692 }
693
694 fn is_healthy(_stream: &Self::Stream) -> bool {
695 true
696 }
697 }
698}