1use bytes::{Bytes, BytesMut};
9use futures_core::Stream;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::fmt;
13use std::pin::Pin;
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::sync::{Arc, Mutex};
16use std::task::{Context, Poll};
17
18#[derive(
20 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
21)]
22pub struct DataStreamId(pub u64);
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub enum FissionDataStreamErrorKind {
27 NotFound,
29 AlreadyConsumed,
31 Cancelled,
33 PermissionDenied,
35 Unsupported,
37 Io,
39 InvalidData,
41 Other,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct FissionDataStreamError {
48 pub kind: FissionDataStreamErrorKind,
50 pub message: String,
52}
53
54impl FissionDataStreamError {
55 pub fn new(kind: FissionDataStreamErrorKind, message: impl Into<String>) -> Self {
57 Self {
58 kind,
59 message: message.into(),
60 }
61 }
62
63 pub fn not_found(id: DataStreamId) -> Self {
65 Self::new(
66 FissionDataStreamErrorKind::NotFound,
67 format!("data stream {} was not found", id.0),
68 )
69 }
70
71 pub fn already_consumed(id: DataStreamId) -> Self {
73 Self::new(
74 FissionDataStreamErrorKind::AlreadyConsumed,
75 format!("data stream {} was already consumed", id.0),
76 )
77 }
78}
79
80impl std::error::Error for FissionDataStreamError {}
81
82impl fmt::Display for FissionDataStreamError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{:?}: {}", self.kind, self.message)
85 }
86}
87
88impl From<std::io::Error> for FissionDataStreamError {
89 fn from(error: std::io::Error) -> Self {
90 Self::new(FissionDataStreamErrorKind::Io, error.to_string())
91 }
92}
93
94pub trait FissionDataStream:
99 Stream<Item = Result<Bytes, FissionDataStreamError>> + Send + 'static
100{
101}
102
103impl<T> FissionDataStream for T where
104 T: Stream<Item = Result<Bytes, FissionDataStreamError>> + Send + 'static
105{
106}
107
108pub type BoxFissionDataStream = Pin<Box<dyn FissionDataStream>>;
110
111#[derive(Clone, Default)]
113pub struct DataStreamRegistry {
114 inner: Arc<DataStreamRegistryInner>,
115}
116
117#[derive(Default)]
118struct DataStreamRegistryInner {
119 next_id: AtomicU64,
120 streams: Mutex<HashMap<DataStreamId, Option<BoxFissionDataStream>>>,
121}
122
123impl fmt::Debug for DataStreamRegistry {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 let len = self
126 .inner
127 .streams
128 .lock()
129 .map(|streams| streams.len())
130 .unwrap_or_default();
131 f.debug_struct("DataStreamRegistry")
132 .field("streams", &len)
133 .finish()
134 }
135}
136
137impl DataStreamRegistry {
138 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn register(&self, stream: BoxFissionDataStream) -> DataStreamId {
145 let id = DataStreamId(self.inner.next_id.fetch_add(1, Ordering::Relaxed) + 1);
146 self.inner
147 .streams
148 .lock()
149 .expect("data stream registry lock poisoned")
150 .insert(id, Some(stream));
151 id
152 }
153
154 pub fn open(&self, id: DataStreamId) -> Result<BoxFissionDataStream, FissionDataStreamError> {
159 let mut streams = self.inner.streams.lock().map_err(|_| {
160 FissionDataStreamError::new(
161 FissionDataStreamErrorKind::Other,
162 "data stream registry lock was poisoned",
163 )
164 })?;
165 match streams.get_mut(&id) {
166 Some(slot) => slot
167 .take()
168 .ok_or_else(|| FissionDataStreamError::already_consumed(id)),
169 None => Err(FissionDataStreamError::not_found(id)),
170 }
171 }
172
173 pub fn release(&self, id: DataStreamId) -> bool {
175 self.inner
176 .streams
177 .lock()
178 .map(|mut streams| streams.remove(&id).is_some())
179 .unwrap_or(false)
180 }
181}
182
183struct SingleChunkDataStream {
184 chunk: Option<Result<Bytes, FissionDataStreamError>>,
185}
186
187impl Stream for SingleChunkDataStream {
188 type Item = Result<Bytes, FissionDataStreamError>;
189
190 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
191 Poll::Ready(self.chunk.take())
192 }
193}
194
195pub fn single_chunk_data_stream(bytes: impl Into<Bytes>) -> BoxFissionDataStream {
197 Box::pin(SingleChunkDataStream {
198 chunk: Some(Ok(bytes.into())),
199 })
200}
201
202pub fn empty_data_stream() -> BoxFissionDataStream {
204 Box::pin(SingleChunkDataStream { chunk: None })
205}
206
207pub async fn collect_data_stream(
213 mut stream: BoxFissionDataStream,
214) -> Result<Bytes, FissionDataStreamError> {
215 let mut out = BytesMut::new();
216 while let Some(chunk) = std::future::poll_fn(|cx| stream.as_mut().poll_next(cx)).await {
217 out.extend_from_slice(&chunk?);
218 }
219 Ok(out.freeze())
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use std::future::Future;
226 use std::task::{RawWaker, RawWakerVTable, Waker};
227
228 fn noop_waker() -> Waker {
229 unsafe fn clone(data: *const ()) -> RawWaker {
230 RawWaker::new(data, &VTABLE)
231 }
232 unsafe fn wake(_data: *const ()) {}
233 unsafe fn wake_by_ref(_data: *const ()) {}
234 unsafe fn drop(_data: *const ()) {}
235
236 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
237 unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
238 }
239
240 fn block_on_ready<F: Future>(future: F) -> F::Output {
241 let waker = noop_waker();
242 let mut cx = Context::from_waker(&waker);
243 let mut future = Box::pin(future);
244 match future.as_mut().poll(&mut cx) {
245 Poll::Ready(output) => output,
246 Poll::Pending => panic!("test future unexpectedly pending"),
247 }
248 }
249
250 #[test]
251 fn registry_opens_stream_once() {
252 let registry = DataStreamRegistry::new();
253 let id = registry.register(single_chunk_data_stream(Bytes::from_static(b"abc")));
254
255 let bytes = block_on_ready(collect_data_stream(registry.open(id).unwrap())).unwrap();
256 assert_eq!(bytes.as_ref(), b"abc");
257
258 match registry.open(id) {
259 Ok(_) => panic!("stream should be consumed"),
260 Err(error) => assert_eq!(error.kind, FissionDataStreamErrorKind::AlreadyConsumed),
261 }
262 }
263
264 #[test]
265 fn registry_release_removes_unopened_stream() {
266 let registry = DataStreamRegistry::new();
267 let id = registry.register(empty_data_stream());
268
269 assert!(registry.release(id));
270 match registry.open(id) {
271 Ok(_) => panic!("released stream should be removed"),
272 Err(error) => assert_eq!(error.kind, FissionDataStreamErrorKind::NotFound),
273 }
274 }
275
276 #[test]
277 fn cloned_registries_share_streams() {
278 let registry = DataStreamRegistry::new();
279 let clone = registry.clone();
280 let id = registry.register(single_chunk_data_stream(Bytes::from_static(b"shared")));
281
282 let bytes = block_on_ready(collect_data_stream(clone.open(id).unwrap())).unwrap();
283 assert_eq!(bytes.as_ref(), b"shared");
284 }
285}