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 task_runtime = Arc::clone(&runtime);
242 let result = match super::session::redwire_spawn_blocking(runtime.as_ref(), move || {
243 super::session::execute_with_redwire_context(&operation_context, role, || {
244 QueryRequestExecutor::new(&task_runtime, &PreparedRegistry::new())
245 .execute(QueryRequest::sql(request.sql, Vec::new()))
246 })
247 })
248 .await
249 {
250 Ok(result) => result,
251 Err(error) => {
252 if let Ok(frame) = build_stream_error_frame(
253 correlation_id,
254 stream_id,
255 error.code(),
256 &error.client_message(),
257 ) {
258 let _ = send.send_frame(frame).await;
259 }
260 return;
261 }
262 };
263
264 let mut seq: u64 = 0;
266 let mut row_count: u64 = 0;
267 let mut cancelled = false;
268 let mut had_error: Option<(String, String)> = None;
269
270 match result {
271 Ok(qr) => {
272 for record in &qr.result.records {
280 if let Ok(()) = cancel_rx.try_recv() {
282 cancelled = true;
283 break;
284 }
285 if lease.snapshot_expired(clock.now_ms()) {
286 had_error = Some((
287 "snapshot_expired".to_string(),
288 "stream snapshot pin TTL elapsed".to_string(),
289 ));
290 break;
291 }
292 let row_bytes = crate::presentation::query_result_json::unified_record_json(
295 record,
296 &qr.result.columns,
297 )
298 .to_string_compact()
299 .into_bytes();
300 let frame =
301 match reddb_wire::redwire::stream::build_stream_chunk_frame_from_json_bytes(
302 correlation_id,
303 stream_id,
304 seq,
305 vec![row_bytes],
306 false,
307 ) {
308 Ok(f) => f,
309 Err(error) => {
310 had_error = Some((
311 "stream_chunk_encoding_failed".to_string(),
312 error.to_string(),
313 ));
314 break;
315 }
316 };
317 if send.send_frame(frame).await.is_err() {
318 return;
319 }
320 seq += 1;
321 row_count += 1;
322 }
323 let _ = config;
327 }
328 Err(err) => {
329 had_error = Some(("query_failed".to_string(), err.to_string()));
330 }
331 }
332
333 if let Some((code, message)) = had_error {
334 if let Ok(frame) = reddb_wire::redwire::stream::build_stream_error_frame(
335 correlation_id,
336 stream_id,
337 Some(seq),
338 &code,
339 &message,
340 ) {
341 let _ = send.send_frame(frame).await;
342 }
343 }
344
345 if let Ok(frame) = reddb_wire::redwire::stream::build_stream_end_frame(
349 correlation_id,
350 stream_id,
351 row_count,
352 lease.id,
353 lease.snapshot_lsn,
354 cancelled,
355 ) {
356 let _ = send.send_frame(frame).await;
357 }
358}
359
360#[derive(Clone)]
366pub struct FrameTx {
367 tx: tokio::sync::mpsc::Sender<bytes::Bytes>,
368 runtime: Arc<RedDBRuntime>,
369 failure: tokio::sync::watch::Sender<bool>,
370}
371
372impl FrameTx {
373 pub fn new(tx: tokio::sync::mpsc::Sender<bytes::Bytes>, runtime: Arc<RedDBRuntime>) -> Self {
374 Self {
375 tx,
376 runtime,
377 failure: tokio::sync::watch::channel(false).0,
378 }
379 }
380
381 pub(crate) fn spawn_writer<W>(
382 &self,
383 mut receiver: tokio::sync::mpsc::Receiver<bytes::Bytes>,
384 mut writer: W,
385 ) -> tokio::task::JoinHandle<()>
386 where
387 W: tokio::io::AsyncWrite + Unpin + Send + 'static,
388 {
389 use tokio::io::AsyncWriteExt;
390 let failure = self.failure.clone();
391 tokio::spawn(async move {
392 let mut failed = failure.subscribe();
393 tokio::select! {
397 biased;
398 _ = failed.wait_for(|value| *value) => {},
399 _ = async {
400 while let Some(bytes) = receiver.recv().await {
401 if writer.write_all(&bytes).await.is_err() {
402 failure.send_replace(true);
403 break;
404 }
405 }
406 } => {},
407 }
408 })
409 }
410
411 pub(crate) async fn failed(&self) {
412 let _ = self.failure.subscribe().wait_for(|value| *value).await;
413 }
414
415 pub async fn send_frame(&self, frame: Frame) -> std::io::Result<()> {
419 let result = self.send_frame_inner(frame).await;
420 if result.is_err() {
421 self.failure.send_replace(true);
422 }
423 result
424 }
425
426 pub(crate) async fn send_legacy_response(
430 &self,
431 response: crate::wire::listener::LegacyQueryResponse,
432 correlation_id: u64,
433 ) -> std::io::Result<()> {
434 let result = self
435 .send_legacy_response_inner(response, correlation_id)
436 .await;
437 if result.is_err() {
438 self.failure.send_replace(true);
439 }
440 result
441 }
442
443 async fn send_legacy_response_inner(
444 &self,
445 response: crate::wire::listener::LegacyQueryResponse,
446 correlation_id: u64,
447 ) -> std::io::Result<()> {
448 use reddb_wire::redwire::builder::BorrowedLegacyReply;
449 if *self.failure.borrow() {
450 return Err(std::io::Error::other("redwire: write channel failed"));
451 }
452 let capacity = BorrowedLegacyReply::new(&response, correlation_id)
453 .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidInput, error))?
454 .encoded_len();
455 let bytes = self
456 .runtime
457 .serialize_query_buffer(capacity, response.unadmitted_capacity(), move |buffer| {
458 BorrowedLegacyReply::new(&response, correlation_id)
459 .expect("validated immutable legacy response")
460 .encode_into(buffer);
461 drop(response);
462 })
463 .map_err(|_| {
464 std::io::Error::new(
465 std::io::ErrorKind::OutOfMemory,
466 "redwire: response exceeds shared memory budget",
467 )
468 })?;
469 self.send_bytes(bytes).await
470 }
471
472 async fn send_frame_inner(&self, mut frame: Frame) -> std::io::Result<()> {
473 use reddb_wire::redwire::{Flags, FRAME_HEADER_SIZE, MAX_FRAME_SIZE};
474 if *self.failure.borrow() {
475 return Err(std::io::Error::other("redwire: write channel failed"));
476 }
477 frame.flags = Flags::from_bits(frame.flags.bits() & !Flags::COMPRESSED.bits());
482 let capacity = frame
483 .payload
484 .len()
485 .checked_add(FRAME_HEADER_SIZE)
486 .filter(|size| *size <= MAX_FRAME_SIZE as usize)
487 .ok_or_else(|| {
488 std::io::Error::new(
489 std::io::ErrorKind::InvalidInput,
490 "redwire: response exceeds frame limit",
491 )
492 })?;
493 let bytes = self
494 .runtime
495 .serialize_query_buffer(capacity, frame.payload.capacity(), move |buffer| {
496 reddb_wire::redwire::codec::encode_uncompressed_frame_into(&frame, buffer);
497 drop(frame);
498 })
499 .map_err(|_| {
500 std::io::Error::new(
501 std::io::ErrorKind::OutOfMemory,
502 "redwire: response exceeds shared memory budget",
503 )
504 })?;
505 self.send_bytes(bytes).await
506 }
507
508 async fn send_bytes(&self, bytes: bytes::Bytes) -> std::io::Result<()> {
509 tokio::select! {
510 biased;
511 _ = self.failed() => Err(std::io::Error::other("redwire: write channel failed")),
512 result = self.tx.send(bytes) => result.map_err(|_| std::io::Error::other("redwire: write channel closed")),
513 }
514 }
515}
516
517#[cfg(test)]
518mod memory_tests;
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523 use reddb_wire::redwire::MessageKind;
524
525 #[tokio::test]
526 async fn outbound_memory_survives_queue_receive_and_response_clones() {
527 use crate::storage::memory_pools::MemoryPool;
528 let runtime = RedDBRuntime::with_options(
529 crate::RedDBOptions::in_memory().with_memory_budget(1024 * 1024),
530 )
531 .expect("bounded runtime");
532 let (tx, mut rx) = tokio::sync::mpsc::channel(4);
533 let runtime = Arc::new(runtime);
534 let sender = FrameTx::new(tx, Arc::clone(&runtime));
535 sender
536 .send_frame(
537 reddb_wire::redwire::build_reply_frame(7, MessageKind::Result, vec![1; 128 * 1024])
538 .expect("reply fixture"),
539 )
540 .await
541 .expect("admitted response");
542 assert!(
543 runtime
544 .memory_accounting()
545 .used_bytes(MemoryPool::QueryBuffers)
546 >= 128 * 1024,
547 "queued encoded responses must remain charged to the runtime budget"
548 );
549 let bytes = rx.recv().await.expect("queued response");
550 let copy = bytes.clone();
551 drop(bytes);
552 assert!(
553 runtime
554 .memory_accounting()
555 .used_bytes(MemoryPool::QueryBuffers)
556 >= copy.len() as u64,
557 "socket ownership and clones must retain admission after dequeue"
558 );
559 let (frame, consumed) = reddb_wire::redwire::decode_frame(©).expect("wire bytes");
560 assert_eq!(consumed, copy.len());
561 assert_eq!(frame.correlation_id, 7);
562 assert_eq!(frame.payload, vec![1; 128 * 1024]);
563 drop(copy);
564 assert_eq!(
565 runtime
566 .memory_accounting()
567 .used_bytes(MemoryPool::QueryBuffers),
568 0
569 );
570 }
571
572 #[test]
573 fn parse_open_stream_accepts_minimal_payload() {
574 let req = parse_open_stream(br#"{"sql":"SELECT 1"}"#).unwrap();
575 assert_eq!(req.sql, "SELECT 1");
576 assert!(req.opts_raw.is_empty());
577 }
578
579 #[test]
580 fn parse_open_stream_captures_opts_opaque() {
581 let req =
582 parse_open_stream(br#"{"sql":"SELECT 1","opts":{"resume_after_rid":42}}"#).unwrap();
583 assert_eq!(req.sql, "SELECT 1");
584 assert!(!req.opts_raw.is_empty());
585 }
586
587 #[test]
588 fn parse_open_stream_rejects_non_object() {
589 assert!(matches!(
590 parse_open_stream(b"\"sql\""),
591 Err(OpenStreamParseError::NotObject)
592 ));
593 }
594
595 #[test]
596 fn parse_open_stream_rejects_missing_sql() {
597 assert!(matches!(
598 parse_open_stream(b"{}"),
599 Err(OpenStreamParseError::MissingSql)
600 ));
601 }
602
603 #[test]
604 fn parse_open_stream_rejects_empty_sql() {
605 assert!(matches!(
606 parse_open_stream(br#"{"sql":""}"#),
607 Err(OpenStreamParseError::EmptySql)
608 ));
609 }
610
611 #[test]
612 fn parse_open_stream_rejects_invalid_json() {
613 assert!(matches!(
614 parse_open_stream(b"{not json"),
615 Err(OpenStreamParseError::NotJson)
616 ));
617 }
618
619 #[test]
620 fn parse_stream_cancel_with_reason() {
621 let r = parse_stream_cancel(br#"{"reason":"client-abort"}"#);
622 assert_eq!(r.reason.as_deref(), Some("client-abort"));
623 }
624
625 #[test]
626 fn parse_stream_cancel_empty_payload_is_default() {
627 assert_eq!(parse_stream_cancel(b""), StreamCancelRequest::default());
628 assert_eq!(parse_stream_cancel(b"{}"), StreamCancelRequest::default());
629 }
630
631 #[tokio::test]
632 async fn registry_rejects_reserved_id_and_duplicates() {
633 let r = StreamRegistry::new();
634 assert!(matches!(
635 r.register(0).await,
636 Err(RegisterError::ReservedStreamId)
637 ));
638 let _rx = r.register(1).await.unwrap();
639 assert!(matches!(
640 r.register(1).await,
641 Err(RegisterError::StreamInUse)
642 ));
643 assert_eq!(r.active_count().await, 1);
644 }
645
646 #[tokio::test]
647 async fn registry_cancel_signals_named_stream_only() {
648 let r = StreamRegistry::new();
650 let rx1 = r.register(1).await.unwrap();
651 let mut rx2 = r.register(2).await.unwrap();
652 assert!(r.cancel(1).await);
653 assert!(rx1.await.is_ok());
655 match rx2.try_recv() {
657 Err(tokio::sync::oneshot::error::TryRecvError::Empty) => {}
658 other => panic!("stream 2 should not be cancelled: {other:?}"),
659 }
660 assert_eq!(r.active_count().await, 1);
661 }
662
663 #[tokio::test]
664 async fn registry_cancel_unknown_returns_false() {
665 let r = StreamRegistry::new();
666 assert!(!r.cancel(99).await);
667 }
668
669 #[tokio::test]
670 async fn registry_unregister_is_idempotent() {
671 let r = StreamRegistry::new();
672 let _rx = r.register(1).await.unwrap();
673 r.unregister(1).await;
674 r.unregister(1).await;
675 assert_eq!(r.active_count().await, 0);
676 }
677
678 #[test]
679 fn build_stream_error_frame_carries_stream_id_and_correlation() {
680 let frame = build_stream_error_frame(99, 7, "unknown_stream", "no such stream").unwrap();
681 assert_eq!(frame.kind, MessageKind::StreamError);
682 assert_eq!(frame.stream_id, 7);
683 assert_eq!(frame.correlation_id, 99);
684 }
685}