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