1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
// spsc.rs
use std::ptr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use crate::buffer::ffi::{self, StreamContext};
use crate::buffer::{pinned::Pinned, Header, Slot};
#[cfg(feature = "cuda")]
use crate::buffer::GpuTextArena;
/// Producer handle for writing to the ring buffer.
pub struct Producer<'a> {
// We can use a reference for simpler borrows...
pinned_ref: Option<&'a Pinned>,
// ...or an Arc for owned, thread-safe handles.
pinned_arc: Option<Arc<Pinned>>,
// Direct pointers for performance in the hot path
header: *const Header,
slots: *mut Slot,
wrap_mask: u64,
}
impl<'a> Producer<'a> {
/// Creates a new producer from a shared Pinned buffer (for moving across threads).
pub fn new(arc_pinned: Arc<Pinned>) -> Self {
let header_ptr = arc_pinned.header() as *const Header;
let slots_ptr = arc_pinned.slots_ptr();
let wrap_mask = unsafe { (*header_ptr).config.wrap_mask };
Self {
pinned_ref: None,
pinned_arc: Some(arc_pinned),
header: header_ptr,
slots: slots_ptr,
wrap_mask,
}
}
/// Creates a new producer from a borrowed Pinned buffer.
pub fn new_from_ref(pinned: &'a Pinned) -> Self {
let header_ptr = pinned.header() as *const Header;
let slots_ptr = pinned.slots_ptr();
let wrap_mask = unsafe { (*header_ptr).config.wrap_mask };
Self {
pinned_ref: Some(pinned),
pinned_arc: None,
header: header_ptr,
slots: slots_ptr,
wrap_mask,
}
}
/// Simple test function
pub fn run_test(&self, n_messages: u32) -> Result<(), String> {
let result = unsafe {
ffi::launch_simple_test(
self.slots,
self.header as *mut _, // Cast from const to mut
n_messages as std::os::raw::c_int,
)
};
if result != 0 {
return Err(format!("Simple test kernel failed with code: {}", result));
}
Ok(())
}
/// Process a batch of agent responses using the unified CUDA kernel.
pub fn process_agent_responses(
&self,
contexts: &[StreamContext],
enable_metrics: bool,
stream: u64, // Pass the stream explicitly
) -> Result<(), String> {
if contexts.is_empty() {
return Ok(());
}
let result = unsafe {
ffi::launch_unified_kernel(
self.slots,
self.header as *mut _,
contexts.as_ptr(),
contexts.len() as u32,
enable_metrics as i32,
stream as *mut std::ffi::c_void, // Convert u64 to pointer for cudaStream_t
)
};
if result != 0 {
return Err(format!("Unified kernel failed with code: {}", result));
}
Ok(())
}
/// Launch async GPU kernel using pre-uploaded arena
#[cfg(feature = "cuda")]
pub fn launch_async_gpu_kernel(
&mut self,
arena: &GpuTextArena,
n_messages: u32,
) -> Result<(), String> {
// Get device pointers from arena
let (d_contexts, d_text, stream) = arena.get_device_pointers();
// Launch kernel asynchronously - NO SYNC!
let result = unsafe {
ffi::launch_unified_kernel_async(
self.slots,
self.header as *mut _,
d_contexts,
d_text,
n_messages,
0, // disable metrics for now
stream as *mut std::ffi::c_void,
)
};
if result != 0 {
return Err(format!("Async kernel launch failed: {}", result));
}
// Return immediately - kernel runs in background!
Ok(())
}
/// CPU based produce with proper backpressure
pub fn try_produce(&mut self, data: &[u8]) -> Result<u64, &'static str> {
if data.len() > 240 {
return Err("Payload too large");
}
unsafe {
let header = &*self.header;
// CRITICAL: Check if we're about to lap the consumer
let write_idx = header.producer.write_idx.load(Ordering::Acquire);
let read_idx = header.consumer.read_idx.load(Ordering::Acquire);
let n_slots = header.config.slot_count as u64;
// Prevent producer from overrunning consumer
// Leave 64 slots as safety margin
if (write_idx - read_idx) >= (n_slots - 64) {
return Err("Buffer full - backpressure");
}
// Check for explicit backpressure signal
if header.control.backpressure.load(Ordering::Acquire) != 0 {
return Err("Backpressure active");
}
// Reserve sequence number
let seq = header.producer.write_idx.fetch_add(1, Ordering::AcqRel);
// Get slot
let idx = (seq & self.wrap_mask) as usize;
let slot = &mut *self.slots.add(idx);
// Write payload
ptr::copy_nonoverlapping(data.as_ptr(), slot.payload.as_mut_ptr(), data.len());
// Use volatile writes for unified memory
ptr::write_volatile(&mut slot.len, data.len() as u32);
ptr::write_volatile(&mut slot.flags, 0);
// Memory fence for cross-device visibility
std::sync::atomic::fence(Ordering::Release);
// Publish sequence
ptr::write_volatile(&mut slot.seq, seq);
std::sync::atomic::fence(Ordering::Release);
// Update stats
header
.producer
.messages_produced
.fetch_add(1, Ordering::Relaxed);
Ok(seq)
}
}
}
// Mark Producer as Send + Sync - safe because we maintain ownership/borrowing
unsafe impl<'a> Send for Producer<'a> {}
unsafe impl<'a> Sync for Producer<'a> {}
/// Consumer handle for reading from the ring buffer.
pub struct Consumer<'a> {
pinned_ref: Option<&'a Pinned>,
pinned_arc: Option<Arc<Pinned>>,
// Direct pointers for performance
header: *const Header,
slots: *const Slot,
wrap_mask: u64,
// Consumer state
read_seq: u64,
}
impl<'a> Consumer<'a> {
/// Creates a new consumer from a shared Pinned buffer.
pub fn new(arc_pinned: Arc<Pinned>) -> Self {
let header_ptr = arc_pinned.header() as *const Header;
let slots_ptr = arc_pinned.slots_ptr() as *const Slot;
let wrap_mask = unsafe { (*header_ptr).config.wrap_mask };
Self {
pinned_ref: None,
pinned_arc: Some(arc_pinned),
header: header_ptr,
slots: slots_ptr,
wrap_mask,
read_seq: 0,
}
}
/// Creates a new consumer from a borrowed Pinned buffer.
pub fn new_from_ref(pinned: &'a Pinned) -> Self {
let header_ptr = pinned.header() as *const Header;
let slots_ptr = pinned.slots_ptr() as *const Slot;
let wrap_mask = unsafe { (*header_ptr).config.wrap_mask };
Self {
pinned_ref: Some(pinned),
pinned_arc: None,
header: header_ptr,
slots: slots_ptr,
wrap_mask,
read_seq: 0,
}
}
/// Try to consume one message from the ring buffer.
pub fn try_consume(&mut self) -> Option<Message> {
unsafe {
let idx = (self.read_seq & self.wrap_mask) as usize;
let slot = &*self.slots.add(idx);
// Memory fence before reading for cross-device visibility
std::sync::atomic::fence(Ordering::Acquire);
// Use volatile read for unified memory
let slot_seq = ptr::read_volatile(&slot.seq);
// Check if slot is ready
if slot_seq != self.read_seq {
// Debug: print mismatch for first few attempts
if self.read_seq < 25 {
println!("[Consumer] Seq mismatch at idx {}: expected {}, got {} (slot addr={:p})",
idx, self.read_seq, slot_seq, slot as *const _);
}
return None;
}
// Memory fence after sequence check
std::sync::atomic::fence(Ordering::Acquire);
// Read slot data
let len = ptr::read_volatile(&slot.len) as usize;
let flags = ptr::read_volatile(&slot.flags);
// Bounds check to prevent allocation errors
if len > 240 {
// Slot appears corrupted - skip it
println!("[Consumer] WARNING: Corrupted slot at seq={}, len={}", slot_seq, len);
return None;
}
// Copy payload
let mut payload = vec![0u8; len];
ptr::copy_nonoverlapping(slot.payload.as_ptr(), payload.as_mut_ptr(), len);
// Update read index
self.read_seq += 1;
(*self.header)
.consumer
.read_idx
.store(self.read_seq, Ordering::Release);
(*self.header)
.consumer
.messages_consumed
.fetch_add(1, Ordering::Relaxed);
Some(Message {
seq: slot_seq,
payload,
flags,
})
}
}
/// Consume up to max_messages from the buffer.
pub fn consume_batch(&mut self, max_messages: usize) -> Vec<Message> {
let mut messages = Vec::with_capacity(max_messages);
for _ in 0..max_messages {
if let Some(msg) = self.try_consume() {
messages.push(msg);
} else {
break;
}
}
messages
}
/// Blocking consume with timeout.
pub fn consume_blocking(&mut self, timeout_ms: u64) -> Option<Message> {
let start = std::time::Instant::now();
let timeout = std::time::Duration::from_millis(timeout_ms);
while start.elapsed() < timeout {
if let Some(msg) = self.try_consume() {
return Some(msg);
}
std::thread::yield_now();
}
None
}
/// Consume all available messages.
pub fn drain(&mut self) -> Vec<Message> {
let mut messages = Vec::new();
while let Some(msg) = self.try_consume() {
messages.push(msg);
}
messages
}
/// Check how many messages are available.
pub fn available(&self) -> usize {
unsafe {
let header = &*self.header;
let write_idx = header.producer.write_idx.load(Ordering::Acquire);
let read_idx = header.consumer.read_idx.load(Ordering::Acquire);
(write_idx - read_idx) as usize
}
}
/// A more performant batching consume with careful calculation
pub fn consume_available(&mut self, limit: Option<usize>) -> Vec<Message> {
unsafe {
let header = &*self.header;
let write_idx = header.producer.write_idx.load(Ordering::Acquire);
// Calculate how many we can consume
let available = (write_idx - self.read_seq) as usize;
let max_to_check = limit.unwrap_or(available).min(available);
let mut messages = Vec::with_capacity(max_to_check);
for _ in 0..max_to_check {
if let Some(msg) = self.try_consume() {
messages.push(msg);
} else {
// Slot not ready yet, stop here
break;
}
}
messages
}
}
}
// Mark Consumer as Send + Sync
unsafe impl<'a> Send for Consumer<'a> {}
unsafe impl<'a> Sync for Consumer<'a> {}
/// A message consumed from the ring buffer.
pub struct Message {
pub seq: u64,
pub payload: Vec<u8>,
pub flags: u32,
}
impl Message {
/// Get the message as a string (assumes UTF-8).
pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(&self.payload)
}
}