Skip to main content

ruda_runtime/runtime/stream/
event.rs

1use crate::runtime::{
2    config::streaming::StreamingLogLevel,
3    logging::ServerLogger,
4    memory_management::ManagedMemoryId,
5    server::{Binding, ServerError},
6    stream::{StreamFactory, StreamPool},
7};
8use core::any::Any;
9use ruda_core::{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
18/// Trait defining the backend operations for managing streams and events.
19///
20/// This trait provides the necessary methods for initializing streams, flushing them to create events,
21/// and waiting on events for synchronization purposes.
22pub trait EventStreamBackend: 'static {
23    /// The type representing a stream in this backend.
24    type Stream: core::fmt::Debug;
25    /// The type representing an event in this backend.
26    type Event: Send + 'static;
27
28    /// Initializes and returns a new stream associated with the given stream ID.
29    fn create_stream(&self) -> Self::Stream;
30    /// Returns the cursor of the given handle on the given stream.
31    fn handle_cursor(stream: &Self::Stream, handle: &Binding) -> u64;
32    /// Returns whether the stream can access new tasks.
33    fn is_healthy(stream: &Self::Stream) -> bool;
34
35    /// Flushes the given stream, ensuring all pending operations are submitted, and returns an event
36    /// that can be used for synchronization.
37    fn flush(stream: &mut Self::Stream) -> Self::Event;
38    /// Makes the stream wait for the specified event to complete before proceeding with further operations.
39    fn wait_event(stream: &mut Self::Stream, event: Self::Event);
40    /// Wait for the given event synching the CPU.
41    fn wait_event_sync(event: Self::Event) -> Result<(), ServerError>;
42}
43
44/// Manages multiple streams with synchronization logic based on shared bindings.
45///
46/// This struct handles the creation and alignment of streams to ensure proper synchronization
47/// when bindings (e.g., buffers) are shared across different streams.
48#[derive(Debug)]
49pub struct MultiStream<B: EventStreamBackend> {
50    /// The map of stream IDs to their corresponding stream wrappers.
51    streams: StreamPool<EventStreamBackendWrapper<B>>,
52    /// The logger used by the server.
53    pub logger: Arc<ServerLogger>,
54    max_streams: usize,
55    gc: GcThread<B>,
56    shared_bindings_pool: Vec<(ManagedMemoryId, StreamId, u64)>,
57}
58
59/// A wrapper around a backend stream that includes synchronization metadata.
60///
61/// This includes the stream itself, a map of last synchronized cursors from other streams,
62/// and the current cursor position for this stream.
63pub(crate) struct StreamWrapper<B: EventStreamBackend> {
64    /// The underlying backend stream.
65    stream: B::Stream,
66    /// The current cursor position, representing the logical progress or version of operations on this stream.
67    cursor: u64,
68    /// A map tracking the last synchronized cursor positions from other streams.
69    last_synced: HashMap<usize, u64>,
70}
71
72/// Streams that are synchronized correctly after a [`MultiStream::resolve`] is called.
73pub struct ResolvedStreams<'a, B: EventStreamBackend> {
74    /// The cursor on the current stream.
75    ///
76    /// This cursor should be use for new allocations happening on the current stream.
77    pub cursor: u64,
78    streams: &'a mut StreamPool<EventStreamBackendWrapper<B>>,
79    analysis: SharedBindingAnalysis,
80    gc: &'a GcThread<B>,
81    /// The current stream where new tasks can be sent safely.
82    pub current: StreamId,
83}
84
85#[derive(Debug)]
86/// A task to be enqueue on the gc stream that will be clearned after an event is reached.
87pub struct GcTask<B: EventStreamBackend> {
88    to_drop: Box<dyn Any + Send + 'static>,
89    /// The event to sync making sure the bindings in the batch are ready to be reused by other streams.
90    event: B::Event,
91}
92
93impl<B: EventStreamBackend> GcTask<B> {
94    /// Creates a new task that will be clearned when the event is reached.
95    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    /// Get the stream associated to the given [`stream_id`](StreamId).
149    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    /// Get the stream associated to the [current `stream_id`](StreamId).
155    pub fn current(&mut self) -> &mut B::Stream {
156        let stream = self.streams.get_mut(&self.current);
157        &mut stream.stream
158    }
159
160    /// Enqueue a task to be cleaned.
161    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.slices.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 mut ids = Vec::new();
180        self.analysis
181            .slices
182            .drain()
183            .for_each(|item| ids.extend(item.1));
184
185        self.gc.register(GcTask::new(ids, event));
186    }
187}
188
189impl<B: EventStreamBackend> MultiStream<B> {
190    /// Initialize an unused physical stream without discarding synchronization
191    /// state or replacing a stream used by existing allocations.
192    pub fn try_initialize_with<E>(
193        &mut self, stream_id: &StreamId,
194        initialize: impl FnOnce(&B) -> Result<B::Stream, E>,
195    ) -> Result<bool, E> {
196        self.streams.try_initialize_with(stream_id, |factory| {
197            Ok(StreamWrapper { stream: initialize(&factory.backend)?,
198                cursor: 0, last_synced: Default::default() })
199        })
200    }
201
202    /// Creates an empty multi-stream.
203    pub fn new(logger: Arc<ServerLogger>, backend: B, max_streams: u8) -> Self {
204        let wrapper = EventStreamBackendWrapper { backend };
205        Self {
206            streams: StreamPool::new(wrapper, max_streams, 1),
207            logger,
208            max_streams: max_streams as usize,
209            gc: GcThread::new(),
210            shared_bindings_pool: Vec::new(),
211        }
212    }
213
214    /// Enqueue a task to be cleaned.
215    pub fn gc(&mut self, gc: GcTask<B>) {
216        self.gc.sender.send(gc).unwrap();
217    }
218
219    /// Resolves and returns a mutable reference to the stream for the given ID, performing any necessary
220    /// alignment based on the provided bindings.
221    ///
222    /// This method ensures that the stream is synchronized with any shared bindings from other streams
223    /// before returning the stream reference.
224    pub fn resolve<'a>(
225        &mut self,
226        stream_id: StreamId,
227        handles: impl Iterator<Item = &'a Binding>,
228        enforce_healthy: bool,
229    ) -> Result<ResolvedStreams<'_, B>, ServerError> {
230        let analysis = self.align_streams(stream_id, handles);
231
232        let stream = self.streams.get_mut(&stream_id);
233        stream.cursor += 1;
234
235        if enforce_healthy && !B::is_healthy(&stream.stream) {
236            return Err(ServerError::Generic {
237                reason: "Can't resolve the stream since it is currently in an error state".into(),
238                backtrace: BackTrace::capture(),
239            });
240        }
241
242        Ok(ResolvedStreams {
243            cursor: stream.cursor,
244            streams: &mut self.streams,
245            current: stream_id,
246            analysis,
247            gc: &self.gc,
248        })
249    }
250
251    /// Aligns the target stream with other streams based on shared bindings.
252    ///
253    /// This initializes the stream if it doesn't exist, analyzes which originating streams need flushing
254    /// for synchronization, flushes them, and waits on the events in the target stream.
255    fn align_streams<'a>(
256        &mut self,
257        stream_id: StreamId,
258        handles: impl Iterator<Item = &'a Binding>,
259    ) -> SharedBindingAnalysis {
260        let analysis = self.update_shared_bindings(stream_id, handles);
261
262        self.apply_analysis(stream_id, analysis)
263    }
264
265    /// Update and analyzes the bindings to determine which streams need alignment (flushing and waiting).
266    ///
267    /// This checks for shared bindings from other streams and determines if synchronization is needed
268    /// based on cursor positions.
269    pub(crate) fn update_shared_bindings<'a>(
270        &mut self,
271        stream_id: StreamId,
272        handles: impl Iterator<Item = &'a Binding>,
273    ) -> SharedBindingAnalysis {
274        // We reset the memory pool for the info.
275        self.shared_bindings_pool.clear();
276
277        for handle in handles {
278            let index = stream_index(&handle.stream, self.max_streams);
279            let stream = unsafe { self.streams.get_mut_index(index) };
280            let cursor_handle = B::handle_cursor(&stream.stream, handle);
281
282            // We only add the info to be consider if the handle stream is different from the current
283            // stream.
284            if handle.stream != stream_id {
285                self.shared_bindings_pool.push((
286                    handle.memory.descriptor().id,
287                    handle.stream,
288                    cursor_handle,
289                ));
290            }
291        }
292
293        let mut analysis = SharedBindingAnalysis::default();
294        let current = self.streams.get_mut(&stream_id);
295
296        for (handle_id, stream, cursor) in self.shared_bindings_pool.iter() {
297            let index = stream_index(stream, self.max_streams);
298
299            if let Some(last_synced) = current.last_synced.get(&index) {
300                if last_synced < cursor {
301                    self.logger.log_streaming(
302                        |level| matches!(level, StreamingLogLevel::Full),
303                        || {
304                            format!(
305                                "Binding on {} is shared on {} since it's not sync {} < {}",
306                                stream, stream_id, last_synced, cursor
307                            )
308                        },
309                    );
310                    analysis.shared(*handle_id, index);
311                }
312            } else {
313                self.logger.log_streaming(
314                    |level| matches!(level, StreamingLogLevel::Full),
315                    || {
316                        format!(
317                            "Binding on {} is shared on {} since it was never synced.",
318                            stream, stream_id,
319                        )
320                    },
321                );
322                analysis.shared(*handle_id, index);
323            }
324        }
325
326        analysis
327    }
328
329    pub(crate) fn apply_analysis(
330        &mut self,
331        stream_id: StreamId,
332        analysis: SharedBindingAnalysis,
333    ) -> SharedBindingAnalysis {
334        if analysis.slices.is_empty() {
335            return analysis;
336        }
337
338        let mut events = Vec::with_capacity(analysis.slices.len());
339
340        unsafe {
341            for origin in analysis.slices.keys() {
342                let stream = self.streams.get_mut_index(*origin);
343                let event = B::flush(&mut stream.stream);
344
345                events.push(((origin, stream.cursor), event));
346            }
347        }
348
349        let stream = self.streams.get_mut(&stream_id);
350
351        for ((stream_origin, cursor_origin), event) in events {
352            stream.last_synced.insert(*stream_origin, cursor_origin);
353
354            self.logger.log_streaming(
355                |level| !matches!(level, StreamingLogLevel::Disabled),
356                || format!("Waiting on {stream_origin} from {stream_id}",),
357            );
358
359            B::wait_event(&mut stream.stream, event);
360        }
361
362        analysis
363    }
364}
365
366impl<B: EventStreamBackend> core::fmt::Debug for StreamWrapper<B> {
367    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
368        f.debug_struct("StreamWrapper")
369            .field("stream", &self.stream)
370            .field("cursor", &self.cursor)
371            .field("last_synced", &self.last_synced)
372            .finish()
373    }
374}
375
376#[derive(Default, Debug, PartialEq, Eq)]
377pub(crate) struct SharedBindingAnalysis {
378    slices: HashMap<usize, Vec<ManagedMemoryId>>,
379}
380
381impl SharedBindingAnalysis {
382    fn shared(&mut self, id: ManagedMemoryId, index: usize) {
383        match self.slices.get_mut(&index) {
384            Some(bindings) => bindings.push(id),
385            None => {
386                self.slices.insert(index, alloc::vec![id]);
387            }
388        }
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use crate::runtime::server::Handle;
395
396    use super::*;
397
398    const MAX_STREAMS: u8 = 4;
399
400    #[test_log::test]
401    fn test_analysis_shared_bindings_1() {
402        let logger = Arc::new(ServerLogger::default());
403        let stream_1 = StreamId { value: 1 };
404        let stream_2 = StreamId { value: 2 };
405
406        let binding_1 = handle(stream_1);
407        let binding_2 = handle(stream_2);
408
409        let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
410        ms.resolve(stream_1, [].into_iter(), false).unwrap();
411        ms.resolve(stream_2, [].into_iter(), false).unwrap();
412
413        let analysis = ms.update_shared_bindings(stream_1, [&binding_1, &binding_2].into_iter());
414
415        let mut expected = SharedBindingAnalysis::default();
416        expected.shared(
417            binding_2.memory.descriptor().id,
418            ms.streams.stream_index(&binding_2.stream),
419        );
420
421        assert_eq!(analysis, expected);
422    }
423
424    #[test_log::test]
425    fn test_analysis_shared_bindings_2() {
426        let logger = Arc::new(ServerLogger::default());
427        let stream_1 = StreamId { value: 1 };
428        let stream_2 = StreamId { value: 2 };
429
430        let binding_1 = handle(stream_1);
431        let binding_2 = handle(stream_2);
432        let binding_3 = handle(stream_1);
433
434        let mut ms = MultiStream::new(logger, TestBackend, 4);
435        ms.resolve(stream_1, [].into_iter(), false).unwrap();
436        ms.resolve(stream_2, [].into_iter(), false).unwrap();
437
438        let analysis =
439            ms.update_shared_bindings(stream_1, [&binding_1, &binding_2, &binding_3].into_iter());
440
441        let mut expected = SharedBindingAnalysis::default();
442        expected.shared(
443            binding_2.memory.descriptor().id,
444            ms.streams.stream_index(&binding_2.stream),
445        );
446
447        assert_eq!(analysis, expected);
448    }
449
450    #[test_log::test]
451    fn test_analysis_no_shared() {
452        let logger = Arc::new(ServerLogger::default());
453        let stream_1 = StreamId { value: 1 };
454        let stream_2 = StreamId { value: 2 };
455
456        let binding_1 = handle(stream_1);
457        let binding_2 = handle(stream_1);
458        let binding_3 = handle(stream_1);
459
460        let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
461        ms.resolve(stream_1, [].into_iter(), false).unwrap();
462        ms.resolve(stream_2, [].into_iter(), false).unwrap();
463
464        let analysis =
465            ms.update_shared_bindings(stream_1, [&binding_1, &binding_2, &binding_3].into_iter());
466
467        let expected = SharedBindingAnalysis::default();
468
469        assert_eq!(analysis, expected);
470    }
471
472    #[test_log::test]
473    fn test_state() {
474        let logger = Arc::new(ServerLogger::default());
475        let stream_1 = StreamId { value: 1 };
476        let stream_2 = StreamId { value: 2 };
477
478        let binding_1 = handle(stream_1);
479        let binding_2 = handle(stream_2);
480        let binding_3 = handle(stream_1);
481
482        let mut ms = MultiStream::new(logger, TestBackend, MAX_STREAMS);
483        ms.resolve(stream_1, [].into_iter(), false).unwrap();
484        ms.resolve(stream_2, [].into_iter(), false).unwrap();
485
486        ms.resolve(
487            stream_1,
488            [&binding_1, &binding_2, &binding_3].into_iter(),
489            false,
490        )
491        .unwrap();
492
493        let stream1 = ms.streams.get_mut(&stream_1);
494        let index_2 = stream_index(&stream_2, MAX_STREAMS as usize);
495        assert_eq!(stream1.last_synced.get(&index_2), Some(&1));
496        assert_eq!(stream1.cursor, 2);
497
498        let stream2 = ms.streams.get_mut(&stream_2);
499        assert!(stream2.last_synced.is_empty());
500        assert_eq!(stream2.cursor, 1);
501    }
502
503    fn handle(stream: StreamId) -> Binding {
504        Handle::new(stream, 10).binding()
505    }
506
507    struct TestBackend;
508
509    #[derive(Debug)]
510    struct TestStream {}
511
512    #[derive(Debug)]
513    struct TestEvent {}
514
515    impl EventStreamBackend for TestBackend {
516        type Stream = TestStream;
517        type Event = TestEvent;
518
519        fn create_stream(&self) -> Self::Stream {
520            TestStream {}
521        }
522
523        fn flush(_stream: &mut Self::Stream) -> Self::Event {
524            TestEvent {}
525        }
526
527        fn wait_event(_stream: &mut Self::Stream, _event: Self::Event) {}
528
529        fn wait_event_sync(_event: Self::Event) -> Result<(), ServerError> {
530            Ok(())
531        }
532
533        fn handle_cursor(_stream: &Self::Stream, _handle: &Binding) -> u64 {
534            0
535        }
536
537        fn is_healthy(_stream: &Self::Stream) -> bool {
538            true
539        }
540    }
541}