structfs_handles/
byte_stream.rs1use std::sync::Mutex;
12
13use structfs_core_store::Value;
14
15use crate::gate::{CancelToken, Cancelled, Gate};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ByteChunk {
20 pub bytes: Vec<u8>,
22 pub offset: u64,
24 pub eof: bool,
28}
29
30impl ByteChunk {
31 pub fn into_value(self) -> Value {
34 Value::Bytes(self.bytes)
35 }
36
37 pub fn next_offset(&self) -> u64 {
39 self.offset + self.bytes.len() as u64
40 }
41}
42
43struct StreamState {
44 data: Vec<u8>,
45 closed: bool,
46}
47
48pub struct ByteStream {
51 state: Mutex<StreamState>,
52 gate: Gate,
53}
54
55impl Default for ByteStream {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl ByteStream {
62 pub fn new() -> Self {
64 Self {
65 state: Mutex::new(StreamState {
66 data: Vec::new(),
67 closed: false,
68 }),
69 gate: Gate::new(),
70 }
71 }
72
73 fn lock(&self) -> std::sync::MutexGuard<'_, StreamState> {
74 self.state.lock().unwrap_or_else(|e| e.into_inner())
75 }
76
77 pub fn push(&self, bytes: &[u8]) -> bool {
81 {
82 let mut state = self.lock();
83 if state.closed {
84 return false;
85 }
86 state.data.extend_from_slice(bytes);
87 }
88 self.gate.notify();
89 true
90 }
91
92 pub fn close(&self) {
94 self.lock().closed = true;
95 self.gate.notify();
96 }
97
98 pub fn is_closed(&self) -> bool {
100 self.lock().closed
101 }
102
103 pub fn len(&self) -> u64 {
105 self.lock().data.len() as u64
106 }
107
108 pub fn is_empty(&self) -> bool {
110 self.len() == 0
111 }
112
113 fn chunk_at(state: &StreamState, offset: u64, max: usize) -> ByteChunk {
114 let start = (offset as usize).min(state.data.len());
117 let end = start.saturating_add(max).min(state.data.len());
118 ByteChunk {
119 bytes: state.data[start..end].to_vec(),
120 offset: start as u64,
121 eof: state.closed && end == state.data.len(),
122 }
123 }
124
125 pub fn snapshot_at(&self, offset: u64, max: usize) -> ByteChunk {
127 Self::chunk_at(&self.lock(), offset, max)
128 }
129
130 pub async fn read_at(&self, offset: u64, max: usize) -> ByteChunk {
134 self.gate
135 .wait_until(|| {
136 let state = self.lock();
137 if (state.data.len() as u64) > offset || state.closed {
138 Some(Self::chunk_at(&state, offset, max))
139 } else {
140 None
141 }
142 })
143 .await
144 }
145
146 pub async fn read_at_cancellable(
148 &self,
149 offset: u64,
150 max: usize,
151 token: &CancelToken,
152 ) -> Result<ByteChunk, Cancelled> {
153 self.gate
154 .wait_until_cancellable(token, || {
155 let state = self.lock();
156 if (state.data.len() as u64) > offset || state.closed {
157 Some(Self::chunk_at(&state, offset, max))
158 } else {
159 None
160 }
161 })
162 .await
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use std::sync::Arc;
170
171 #[tokio::test]
172 async fn ranged_reads_return_available_bytes() {
173 let stream = ByteStream::new();
174 stream.push(b"hello world");
175
176 let chunk = stream.read_at(0, 5).await;
177 assert_eq!(chunk.bytes, b"hello");
178 assert_eq!(chunk.next_offset(), 5);
179 assert!(!chunk.eof);
180
181 let chunk = stream.read_at(chunk.next_offset(), 100).await;
182 assert_eq!(chunk.bytes, b" world");
183 assert!(!chunk.eof);
184 }
185
186 #[tokio::test]
187 async fn read_parks_until_push() {
188 let stream = Arc::new(ByteStream::new());
189 let reader = {
190 let stream = stream.clone();
191 tokio::spawn(async move { stream.read_at(0, 16).await })
192 };
193 tokio::task::yield_now().await;
194 stream.push(b"data");
195
196 let chunk = reader.await.unwrap();
197 assert_eq!(chunk.bytes, b"data");
198 }
199
200 #[tokio::test]
201 async fn empty_read_means_eof() {
202 let stream = Arc::new(ByteStream::new());
203 stream.push(b"tail");
204
205 let reader = {
207 let stream = stream.clone();
208 tokio::spawn(async move {
209 let first = stream.read_at(0, 100).await;
210 let second = stream.read_at(first.next_offset(), 100).await;
211 (first, second)
212 })
213 };
214 tokio::task::yield_now().await;
215 stream.close();
216
217 let (first, second) = reader.await.unwrap();
218 assert_eq!(first.bytes, b"tail");
219 assert!(second.bytes.is_empty());
220 assert!(second.eof);
221 }
222
223 #[tokio::test]
224 async fn data_racing_close_arrives_before_eof() {
225 let stream = Arc::new(ByteStream::new());
228 let reader = {
229 let stream = stream.clone();
230 tokio::spawn(async move { stream.read_at(0, 100).await })
231 };
232 tokio::task::yield_now().await;
233 stream.push(b"last words");
234 stream.close();
235
236 let chunk = reader.await.unwrap();
237 assert_eq!(chunk.bytes, b"last words");
238 assert!(chunk.eof);
239 }
240
241 #[tokio::test]
242 async fn stale_offset_clamps() {
243 let stream = ByteStream::new();
244 stream.push(b"abc");
245 stream.close();
246
247 let chunk = stream.read_at(999, 10).await;
248 assert!(chunk.bytes.is_empty());
249 assert_eq!(chunk.offset, 3);
250 assert!(chunk.eof);
251 }
252
253 #[test]
254 fn push_after_close_is_dropped() {
255 let stream = ByteStream::new();
256 assert!(stream.push(b"a"));
257 stream.close();
258 assert!(!stream.push(b"b"));
259 assert_eq!(stream.len(), 1);
260 }
261
262 #[test]
263 fn snapshot_is_nonblocking() {
264 let stream = ByteStream::new();
265 let chunk = stream.snapshot_at(0, 10);
266 assert!(chunk.bytes.is_empty());
267 assert!(!chunk.eof); }
269
270 #[tokio::test]
271 async fn cancellation_fails_parked_read() {
272 let stream = Arc::new(ByteStream::new());
273 let token = CancelToken::new();
274 let reader = {
275 let stream = stream.clone();
276 let token = token.clone();
277 tokio::spawn(async move { stream.read_at_cancellable(0, 10, &token).await })
278 };
279 tokio::task::yield_now().await;
280 token.cancel();
281 assert!(reader.await.unwrap().is_err());
282 }
283
284 #[test]
285 fn value_encoding_is_bytes() {
286 let chunk = ByteChunk {
287 bytes: vec![1, 2, 3],
288 offset: 0,
289 eof: false,
290 };
291 assert_eq!(chunk.into_value(), Value::Bytes(vec![1, 2, 3]));
292 }
293}