1use std::collections::HashMap;
27use std::sync::Arc;
28
29use tokio::sync::{oneshot, Mutex};
30
31use crate::application::OperationContext;
32use crate::auth::Role;
33use crate::runtime::query_request::{PreparedRegistry, QueryRequest, QueryRequestExecutor};
34use crate::runtime::RedDBRuntime;
35use crate::server::output_stream::{
36 self as outs, Clock, OpenStreamError, StreamConfig, SystemClock,
37};
38pub use reddb_wire::redwire::stream::{
39 OpenStreamParseError, OpenStreamRequest, StreamCancelRequest,
40};
41use reddb_wire::redwire::Frame;
42
43pub fn parse_open_stream(payload: &[u8]) -> Result<OpenStreamRequest, OpenStreamParseError> {
44 reddb_wire::redwire::stream::parse_open_stream(payload)
45}
46
47pub fn parse_stream_cancel(payload: &[u8]) -> StreamCancelRequest {
48 reddb_wire::redwire::stream::parse_stream_cancel(payload)
49}
50
51pub fn build_open_ack_frame(
52 correlation_id: u64,
53 stream_id: u16,
54 lease_id: u64,
55 snapshot_lsn: u64,
56 resumable: bool,
57) -> Result<Frame, reddb_wire::BuildError> {
58 reddb_wire::redwire::stream::build_open_ack_frame(
59 correlation_id,
60 stream_id,
61 lease_id,
62 snapshot_lsn,
63 resumable,
64 )
65}
66
67#[derive(Default)]
72pub struct StreamRegistry {
73 inner: Mutex<HashMap<u16, oneshot::Sender<()>>>,
74}
75
76impl StreamRegistry {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub async fn register(&self, stream_id: u16) -> Result<oneshot::Receiver<()>, RegisterError> {
85 if stream_id == 0 {
86 return Err(RegisterError::ReservedStreamId);
87 }
88 let mut guard = self.inner.lock().await;
89 if guard.contains_key(&stream_id) {
90 return Err(RegisterError::StreamInUse);
91 }
92 let (tx, rx) = oneshot::channel();
93 guard.insert(stream_id, tx);
94 Ok(rx)
95 }
96
97 pub async fn cancel(&self, stream_id: u16) -> bool {
101 let mut guard = self.inner.lock().await;
102 match guard.remove(&stream_id) {
103 Some(tx) => {
104 let _ = tx.send(());
105 true
106 }
107 None => false,
108 }
109 }
110
111 pub async fn unregister(&self, stream_id: u16) {
114 let mut guard = self.inner.lock().await;
115 guard.remove(&stream_id);
116 }
117
118 pub async fn active_count(&self) -> usize {
119 self.inner.lock().await.len()
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum RegisterError {
125 ReservedStreamId,
126 StreamInUse,
127}
128
129impl RegisterError {
130 pub fn code(&self) -> &'static str {
131 match self {
132 Self::ReservedStreamId => "open_stream_reserved_id",
133 Self::StreamInUse => "open_stream_id_in_use",
134 }
135 }
136 pub fn message(&self) -> &'static str {
137 match self {
138 Self::ReservedStreamId => {
139 "OpenStream cannot use stream_id 0 (reserved for unsolicited)"
140 }
141 Self::StreamInUse => "OpenStream stream_id already has an active stream",
142 }
143 }
144}
145
146pub fn build_stream_error_frame(
150 correlation_id: u64,
151 stream_id: u16,
152 code: &str,
153 message: &str,
154) -> std::io::Result<Frame> {
155 reddb_wire::redwire::stream::build_stream_error_frame(
156 correlation_id,
157 stream_id,
158 None,
159 code,
160 message,
161 )
162 .map_err(|e| std::io::Error::other(format!("build StreamError: {e}")))
163}
164
165pub async fn run_output_stream(
175 runtime: Arc<RedDBRuntime>,
176 correlation_id: u64,
177 stream_id: u16,
178 request: OpenStreamRequest,
179 in_transaction: bool,
180 mut cancel_rx: oneshot::Receiver<()>,
181 send: FrameTx,
182 operation_context: OperationContext,
183 role: Role,
184) {
185 let clock = SystemClock;
186 let config = match StreamConfig::load(&runtime) {
187 Ok(config) => config,
188 Err(error) => {
189 if let Ok(frame) = build_stream_error_frame(
190 correlation_id,
191 stream_id,
192 "stream_config_unavailable",
193 &error.to_string(),
194 ) {
195 if send.send_frame(frame).await.is_err() {
196 return;
197 }
198 }
199 return;
200 }
201 };
202 let snapshot_lsn = runtime.cdc_current_lsn();
203
204 let lease = match outs::open_stream(config, snapshot_lsn, in_transaction, &clock) {
205 Ok(l) => l,
206 Err(OpenStreamError::TransactionActive) => {
207 let err = OpenStreamError::TransactionActive;
208 let frame = match build_stream_error_frame(
209 correlation_id,
210 stream_id,
211 err.code(),
212 err.message(),
213 ) {
214 Ok(f) => f,
215 Err(_) => return,
216 };
217 let _ = send.send_frame(frame).await;
218 return;
219 }
220 };
221
222 let ack = match reddb_wire::redwire::stream::build_open_ack_frame(
224 correlation_id,
225 stream_id,
226 lease.id,
227 lease.snapshot_lsn,
228 false,
229 ) {
230 Ok(f) => f,
231 Err(_) => return,
232 };
233 if send.send_frame(ack).await.is_err() {
234 return;
235 }
236
237 let result = super::session::execute_with_redwire_context(&operation_context, role, || {
240 QueryRequestExecutor::new(&runtime, &PreparedRegistry::new())
241 .execute(QueryRequest::sql(request.sql, Vec::new()))
242 });
243
244 let mut seq: u64 = 0;
246 let mut row_count: u64 = 0;
247 let mut cancelled = false;
248 let mut had_error: Option<(String, String)> = None;
249
250 match result {
251 Ok(qr) => {
252 for record in &qr.result.records {
260 if let Ok(()) = cancel_rx.try_recv() {
262 cancelled = true;
263 break;
264 }
265 if lease.snapshot_expired(clock.now_ms()) {
266 had_error = Some((
267 "snapshot_expired".to_string(),
268 "stream snapshot pin TTL elapsed".to_string(),
269 ));
270 break;
271 }
272 let row_bytes = crate::presentation::query_result_json::unified_record_json(
275 record,
276 &qr.result.columns,
277 )
278 .to_string_compact()
279 .into_bytes();
280 let frame =
281 match reddb_wire::redwire::stream::build_stream_chunk_frame_from_json_bytes(
282 correlation_id,
283 stream_id,
284 seq,
285 vec![row_bytes],
286 false,
287 ) {
288 Ok(f) => f,
289 Err(error) => {
290 had_error = Some((
291 "stream_chunk_encoding_failed".to_string(),
292 error.to_string(),
293 ));
294 break;
295 }
296 };
297 if send.send_frame(frame).await.is_err() {
298 return;
299 }
300 seq += 1;
301 row_count += 1;
302 }
303 let _ = config;
307 }
308 Err(err) => {
309 had_error = Some(("query_failed".to_string(), err.to_string()));
310 }
311 }
312
313 if let Some((code, message)) = had_error {
314 if let Ok(frame) = reddb_wire::redwire::stream::build_stream_error_frame(
315 correlation_id,
316 stream_id,
317 Some(seq),
318 &code,
319 &message,
320 ) {
321 let _ = send.send_frame(frame).await;
322 }
323 }
324
325 if let Ok(frame) = reddb_wire::redwire::stream::build_stream_end_frame(
329 correlation_id,
330 stream_id,
331 row_count,
332 lease.id,
333 lease.snapshot_lsn,
334 cancelled,
335 ) {
336 let _ = send.send_frame(frame).await;
337 }
338}
339
340#[derive(Clone)]
346pub struct FrameTx {
347 tx: tokio::sync::mpsc::Sender<bytes::Bytes>,
348 runtime: Arc<RedDBRuntime>,
349 failure: tokio::sync::watch::Sender<bool>,
350}
351
352impl FrameTx {
353 pub fn new(tx: tokio::sync::mpsc::Sender<bytes::Bytes>, runtime: Arc<RedDBRuntime>) -> Self {
354 Self {
355 tx,
356 runtime,
357 failure: tokio::sync::watch::channel(false).0,
358 }
359 }
360
361 pub(crate) fn spawn_writer<W>(
362 &self,
363 mut receiver: tokio::sync::mpsc::Receiver<bytes::Bytes>,
364 mut writer: W,
365 ) -> tokio::task::JoinHandle<()>
366 where
367 W: tokio::io::AsyncWrite + Unpin + Send + 'static,
368 {
369 use tokio::io::AsyncWriteExt;
370 let failure = self.failure.clone();
371 tokio::spawn(async move {
372 let mut failed = failure.subscribe();
373 tokio::select! {
377 biased;
378 _ = failed.wait_for(|value| *value) => {},
379 _ = async {
380 while let Some(bytes) = receiver.recv().await {
381 if writer.write_all(&bytes).await.is_err() {
382 failure.send_replace(true);
383 break;
384 }
385 }
386 } => {},
387 }
388 })
389 }
390
391 pub(crate) async fn failed(&self) {
392 let _ = self.failure.subscribe().wait_for(|value| *value).await;
393 }
394
395 pub async fn send_frame(&self, frame: Frame) -> std::io::Result<()> {
399 let result = self.send_frame_inner(frame).await;
400 if result.is_err() {
401 self.failure.send_replace(true);
402 }
403 result
404 }
405
406 pub(crate) async fn send_legacy_response(
410 &self,
411 response: crate::wire::listener::LegacyQueryResponse,
412 correlation_id: u64,
413 ) -> std::io::Result<()> {
414 let result = self
415 .send_legacy_response_inner(response, correlation_id)
416 .await;
417 if result.is_err() {
418 self.failure.send_replace(true);
419 }
420 result
421 }
422
423 async fn send_legacy_response_inner(
424 &self,
425 response: crate::wire::listener::LegacyQueryResponse,
426 correlation_id: u64,
427 ) -> std::io::Result<()> {
428 use reddb_wire::redwire::builder::BorrowedLegacyReply;
429 if *self.failure.borrow() {
430 return Err(std::io::Error::other("redwire: write channel failed"));
431 }
432 let capacity = BorrowedLegacyReply::new(&response, correlation_id)
433 .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidInput, error))?
434 .encoded_len();
435 let bytes = self
436 .runtime
437 .serialize_query_buffer(capacity, response.unadmitted_capacity(), move |buffer| {
438 BorrowedLegacyReply::new(&response, correlation_id)
439 .expect("validated immutable legacy response")
440 .encode_into(buffer);
441 drop(response);
442 })
443 .map_err(|_| {
444 std::io::Error::new(
445 std::io::ErrorKind::OutOfMemory,
446 "redwire: response exceeds shared memory budget",
447 )
448 })?;
449 self.send_bytes(bytes).await
450 }
451
452 async fn send_frame_inner(&self, mut frame: Frame) -> std::io::Result<()> {
453 use reddb_wire::redwire::{Flags, FRAME_HEADER_SIZE, MAX_FRAME_SIZE};
454 if *self.failure.borrow() {
455 return Err(std::io::Error::other("redwire: write channel failed"));
456 }
457 frame.flags = Flags::from_bits(frame.flags.bits() & !Flags::COMPRESSED.bits());
462 let capacity = frame
463 .payload
464 .len()
465 .checked_add(FRAME_HEADER_SIZE)
466 .filter(|size| *size <= MAX_FRAME_SIZE as usize)
467 .ok_or_else(|| {
468 std::io::Error::new(
469 std::io::ErrorKind::InvalidInput,
470 "redwire: response exceeds frame limit",
471 )
472 })?;
473 let bytes = self
474 .runtime
475 .serialize_query_buffer(capacity, frame.payload.capacity(), move |buffer| {
476 reddb_wire::redwire::codec::encode_uncompressed_frame_into(&frame, buffer);
477 drop(frame);
478 })
479 .map_err(|_| {
480 std::io::Error::new(
481 std::io::ErrorKind::OutOfMemory,
482 "redwire: response exceeds shared memory budget",
483 )
484 })?;
485 self.send_bytes(bytes).await
486 }
487
488 async fn send_bytes(&self, bytes: bytes::Bytes) -> std::io::Result<()> {
489 tokio::select! {
490 biased;
491 _ = self.failed() => Err(std::io::Error::other("redwire: write channel failed")),
492 result = self.tx.send(bytes) => result.map_err(|_| std::io::Error::other("redwire: write channel closed")),
493 }
494 }
495}
496
497#[cfg(test)]
498mod memory_tests;
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503 use reddb_wire::redwire::MessageKind;
504
505 #[tokio::test]
506 async fn outbound_memory_survives_queue_receive_and_response_clones() {
507 use crate::storage::memory_pools::MemoryPool;
508 let runtime = RedDBRuntime::with_options(
509 crate::RedDBOptions::in_memory().with_memory_budget(1024 * 1024),
510 )
511 .expect("bounded runtime");
512 let (tx, mut rx) = tokio::sync::mpsc::channel(4);
513 let runtime = Arc::new(runtime);
514 let sender = FrameTx::new(tx, Arc::clone(&runtime));
515 sender
516 .send_frame(
517 reddb_wire::redwire::build_reply_frame(7, MessageKind::Result, vec![1; 128 * 1024])
518 .expect("reply fixture"),
519 )
520 .await
521 .expect("admitted response");
522 assert!(
523 runtime
524 .memory_accounting()
525 .used_bytes(MemoryPool::QueryBuffers)
526 >= 128 * 1024,
527 "queued encoded responses must remain charged to the runtime budget"
528 );
529 let bytes = rx.recv().await.expect("queued response");
530 let copy = bytes.clone();
531 drop(bytes);
532 assert!(
533 runtime
534 .memory_accounting()
535 .used_bytes(MemoryPool::QueryBuffers)
536 >= copy.len() as u64,
537 "socket ownership and clones must retain admission after dequeue"
538 );
539 let (frame, consumed) = reddb_wire::redwire::decode_frame(©).expect("wire bytes");
540 assert_eq!(consumed, copy.len());
541 assert_eq!(frame.correlation_id, 7);
542 assert_eq!(frame.payload, vec![1; 128 * 1024]);
543 drop(copy);
544 assert_eq!(
545 runtime
546 .memory_accounting()
547 .used_bytes(MemoryPool::QueryBuffers),
548 0
549 );
550 }
551
552 #[test]
553 fn parse_open_stream_accepts_minimal_payload() {
554 let req = parse_open_stream(br#"{"sql":"SELECT 1"}"#).unwrap();
555 assert_eq!(req.sql, "SELECT 1");
556 assert!(req.opts_raw.is_empty());
557 }
558
559 #[test]
560 fn parse_open_stream_captures_opts_opaque() {
561 let req =
562 parse_open_stream(br#"{"sql":"SELECT 1","opts":{"resume_after_rid":42}}"#).unwrap();
563 assert_eq!(req.sql, "SELECT 1");
564 assert!(!req.opts_raw.is_empty());
565 }
566
567 #[test]
568 fn parse_open_stream_rejects_non_object() {
569 assert!(matches!(
570 parse_open_stream(b"\"sql\""),
571 Err(OpenStreamParseError::NotObject)
572 ));
573 }
574
575 #[test]
576 fn parse_open_stream_rejects_missing_sql() {
577 assert!(matches!(
578 parse_open_stream(b"{}"),
579 Err(OpenStreamParseError::MissingSql)
580 ));
581 }
582
583 #[test]
584 fn parse_open_stream_rejects_empty_sql() {
585 assert!(matches!(
586 parse_open_stream(br#"{"sql":""}"#),
587 Err(OpenStreamParseError::EmptySql)
588 ));
589 }
590
591 #[test]
592 fn parse_open_stream_rejects_invalid_json() {
593 assert!(matches!(
594 parse_open_stream(b"{not json"),
595 Err(OpenStreamParseError::NotJson)
596 ));
597 }
598
599 #[test]
600 fn parse_stream_cancel_with_reason() {
601 let r = parse_stream_cancel(br#"{"reason":"client-abort"}"#);
602 assert_eq!(r.reason.as_deref(), Some("client-abort"));
603 }
604
605 #[test]
606 fn parse_stream_cancel_empty_payload_is_default() {
607 assert_eq!(parse_stream_cancel(b""), StreamCancelRequest::default());
608 assert_eq!(parse_stream_cancel(b"{}"), StreamCancelRequest::default());
609 }
610
611 #[tokio::test]
612 async fn registry_rejects_reserved_id_and_duplicates() {
613 let r = StreamRegistry::new();
614 assert!(matches!(
615 r.register(0).await,
616 Err(RegisterError::ReservedStreamId)
617 ));
618 let _rx = r.register(1).await.unwrap();
619 assert!(matches!(
620 r.register(1).await,
621 Err(RegisterError::StreamInUse)
622 ));
623 assert_eq!(r.active_count().await, 1);
624 }
625
626 #[tokio::test]
627 async fn registry_cancel_signals_named_stream_only() {
628 let r = StreamRegistry::new();
630 let rx1 = r.register(1).await.unwrap();
631 let mut rx2 = r.register(2).await.unwrap();
632 assert!(r.cancel(1).await);
633 assert!(rx1.await.is_ok());
635 match rx2.try_recv() {
637 Err(tokio::sync::oneshot::error::TryRecvError::Empty) => {}
638 other => panic!("stream 2 should not be cancelled: {other:?}"),
639 }
640 assert_eq!(r.active_count().await, 1);
641 }
642
643 #[tokio::test]
644 async fn registry_cancel_unknown_returns_false() {
645 let r = StreamRegistry::new();
646 assert!(!r.cancel(99).await);
647 }
648
649 #[tokio::test]
650 async fn registry_unregister_is_idempotent() {
651 let r = StreamRegistry::new();
652 let _rx = r.register(1).await.unwrap();
653 r.unregister(1).await;
654 r.unregister(1).await;
655 assert_eq!(r.active_count().await, 0);
656 }
657
658 #[test]
659 fn build_stream_error_frame_carries_stream_id_and_correlation() {
660 let frame = build_stream_error_frame(99, 7, "unknown_stream", "no such stream").unwrap();
661 assert_eq!(frame.kind, MessageKind::StreamError);
662 assert_eq!(frame.stream_id, 7);
663 assert_eq!(frame.correlation_id, 99);
664 }
665}