1use std::collections::HashMap;
29use std::sync::Mutex;
30
31use super::GpuError;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub struct StreamId(pub u64);
40
41impl StreamId {
42 pub fn default_stream() -> Self {
44 StreamId(0)
45 }
46
47 pub fn new(id: u64) -> Self {
49 StreamId(id)
50 }
51}
52
53impl std::fmt::Display for StreamId {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "Stream({})", self.0)
56 }
57}
58
59struct StreamArena {
65 stream_id: StreamId,
66 allocated_bytes: usize,
67 max_bytes: usize,
68 allocations: Vec<Vec<u8>>,
70}
71
72impl StreamArena {
73 fn new(stream_id: StreamId, max_bytes: usize) -> Self {
74 Self {
75 stream_id,
76 allocated_bytes: 0,
77 max_bytes,
78 allocations: Vec::new(),
79 }
80 }
81
82 fn allocate(&mut self, bytes: usize) -> Result<*mut u8, GpuError> {
90 if bytes == 0 {
91 return Err(GpuError::InvalidParameter(
92 "allocation size must be > 0".to_string(),
93 ));
94 }
95 if self.allocated_bytes + bytes > self.max_bytes {
96 return Err(GpuError::OutOfMemory(format!(
97 "{}: {} bytes requested, {} bytes available",
98 self.stream_id,
99 bytes,
100 self.max_bytes.saturating_sub(self.allocated_bytes),
101 )));
102 }
103 let mut buf = vec![0u8; bytes];
104 let ptr = buf.as_mut_ptr();
105 self.allocations.push(buf);
106 self.allocated_bytes += bytes;
107 Ok(ptr)
108 }
109
110 fn reset(&mut self) {
112 self.allocations.clear();
113 self.allocated_bytes = 0;
114 }
115
116 fn allocated_bytes(&self) -> usize {
117 self.allocated_bytes
118 }
119}
120
121pub struct StreamAllocator {
130 arenas: Mutex<HashMap<StreamId, StreamArena>>,
131 per_stream_max_bytes: usize,
132 global_max_bytes: usize,
133}
134
135impl StreamAllocator {
136 pub fn new(per_stream_max_bytes: usize, global_max_bytes: usize) -> Self {
141 Self {
142 arenas: Mutex::new(HashMap::new()),
143 per_stream_max_bytes,
144 global_max_bytes,
145 }
146 }
147
148 pub fn register_stream(&self, stream_id: StreamId) -> Result<(), GpuError> {
152 let mut arenas = self.arenas.lock().map_err(|_| {
153 GpuError::Other("StreamAllocator mutex poisoned during register_stream".to_string())
154 })?;
155 if arenas.contains_key(&stream_id) {
156 return Err(GpuError::InvalidParameter(format!(
157 "stream {stream_id} is already registered",
158 )));
159 }
160 arenas.insert(
161 stream_id,
162 StreamArena::new(stream_id, self.per_stream_max_bytes),
163 );
164 Ok(())
165 }
166
167 pub fn unregister_stream(&self, stream_id: StreamId) {
169 if let Ok(mut arenas) = self.arenas.lock() {
170 arenas.remove(&stream_id);
171 }
172 }
173
174 pub fn allocate(&self, stream_id: StreamId, bytes: usize) -> Result<*mut u8, GpuError> {
180 let mut arenas = self.arenas.lock().map_err(|_| {
181 GpuError::Other("StreamAllocator mutex poisoned during allocate".to_string())
182 })?;
183
184 let total: usize = arenas.values().map(|a| a.allocated_bytes()).sum();
186 if total + bytes > self.global_max_bytes {
187 return Err(GpuError::OutOfMemory(format!(
188 "global limit: {} bytes requested, {} bytes available",
189 bytes,
190 self.global_max_bytes.saturating_sub(total),
191 )));
192 }
193
194 let arena = arenas.get_mut(&stream_id).ok_or_else(|| {
195 GpuError::InvalidParameter(format!(
196 "stream {stream_id} is not registered; call register_stream first",
197 ))
198 })?;
199
200 arena.allocate(bytes)
201 }
202
203 pub fn reset_stream(&self, stream_id: StreamId) {
207 if let Ok(mut arenas) = self.arenas.lock() {
208 if let Some(arena) = arenas.get_mut(&stream_id) {
209 arena.reset();
210 }
211 }
212 }
213
214 pub fn total_allocated_bytes(&self) -> usize {
216 self.arenas
217 .lock()
218 .map(|arenas| arenas.values().map(|a| a.allocated_bytes()).sum())
219 .unwrap_or(0)
220 }
221
222 pub fn registered_streams(&self) -> Vec<StreamId> {
224 self.arenas
225 .lock()
226 .map(|arenas| arenas.keys().copied().collect())
227 .unwrap_or_default()
228 }
229}
230
231#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_stream_allocator_register() {
241 let alloc = StreamAllocator::new(1024, 8192);
242 let s0 = StreamId::new(0);
243 let s1 = StreamId::new(1);
244
245 alloc.register_stream(s0).expect("register s0 failed");
246 alloc.register_stream(s1).expect("register s1 failed");
247
248 let streams = alloc.registered_streams();
249 assert!(streams.contains(&s0), "s0 should be registered");
250 assert!(streams.contains(&s1), "s1 should be registered");
251 assert_eq!(streams.len(), 2);
252 }
253
254 #[test]
255 fn test_stream_allocator_register_duplicate() {
256 let alloc = StreamAllocator::new(1024, 8192);
257 let s0 = StreamId::default_stream();
258 alloc.register_stream(s0).expect("first register failed");
259 let result = alloc.register_stream(s0);
260 assert!(result.is_err(), "duplicate registration should return Err");
261 }
262
263 #[test]
264 fn test_stream_allocator_allocate() {
265 let alloc = StreamAllocator::new(1024 * 1024, 8 * 1024 * 1024);
266 let s0 = StreamId::new(10);
267 let s1 = StreamId::new(20);
268 alloc.register_stream(s0).expect("register s0");
269 alloc.register_stream(s1).expect("register s1");
270
271 let p0 = alloc.allocate(s0, 512).expect("allocate on s0");
272 let p1 = alloc.allocate(s1, 512).expect("allocate on s1");
273
274 assert!(!p0.is_null(), "s0 pointer should not be null");
276 assert!(!p1.is_null(), "s1 pointer should not be null");
277 assert_ne!(p0, p1, "pointers from different streams should differ");
278
279 assert_eq!(alloc.total_allocated_bytes(), 1024);
280 }
281
282 #[test]
283 fn test_stream_allocator_overflow() {
284 let alloc = StreamAllocator::new(256, 8192);
285 let s = StreamId::new(5);
286 alloc.register_stream(s).expect("register");
287
288 alloc
290 .allocate(s, 200)
291 .expect("first allocation should succeed");
292
293 let result = alloc.allocate(s, 200);
295 assert!(
296 matches!(result, Err(GpuError::OutOfMemory(_))),
297 "expected OutOfMemory, got {result:?}"
298 );
299 }
300
301 #[test]
302 fn test_stream_allocator_global_overflow() {
303 let alloc = StreamAllocator::new(200, 300);
305 let s0 = StreamId::new(0);
306 let s1 = StreamId::new(1);
307 alloc.register_stream(s0).expect("register s0");
308 alloc.register_stream(s1).expect("register s1");
309
310 alloc.allocate(s0, 200).expect("first allocation");
311 let result = alloc.allocate(s1, 200);
313 assert!(
314 matches!(result, Err(GpuError::OutOfMemory(_))),
315 "expected global OutOfMemory"
316 );
317 }
318
319 #[test]
320 fn test_stream_allocator_reset() {
321 let alloc = StreamAllocator::new(1024, 8192);
322 let s = StreamId::new(99);
323 alloc.register_stream(s).expect("register");
324 alloc.allocate(s, 512).expect("allocate");
325 assert_eq!(alloc.total_allocated_bytes(), 512);
326
327 alloc.reset_stream(s);
328 assert_eq!(alloc.total_allocated_bytes(), 0, "reset should clear bytes");
329 }
330
331 #[test]
332 fn test_stream_allocator_unregister() {
333 let alloc = StreamAllocator::new(1024, 8192);
334 let s = StreamId::new(7);
335 alloc.register_stream(s).expect("register");
336 assert_eq!(alloc.registered_streams().len(), 1);
337
338 alloc.unregister_stream(s);
339 assert!(
340 alloc.registered_streams().is_empty(),
341 "stream should be gone after unregister"
342 );
343
344 let result = alloc.allocate(s, 64);
346 assert!(
347 result.is_err(),
348 "allocate on unregistered stream should fail"
349 );
350 }
351
352 #[test]
353 fn test_stream_id_default() {
354 assert_eq!(StreamId::default_stream(), StreamId(0));
355 }
356
357 #[test]
358 fn test_stream_allocator_zero_size_rejected() {
359 let alloc = StreamAllocator::new(1024, 8192);
360 let s = StreamId::new(3);
361 alloc.register_stream(s).expect("register");
362 let result = alloc.allocate(s, 0);
363 assert!(result.is_err(), "zero-size allocation should fail");
364 }
365}