a2a_protocol_server/streaming/event_queue/
in_memory.rs1use std::future::Future;
19use std::pin::Pin;
20
21use a2a_protocol_types::error::{A2aError, A2aResult};
22use a2a_protocol_types::events::StreamResponse;
23use tokio::sync::{broadcast, mpsc};
24
25use super::{EventQueueReader, EventQueueWriter};
26
27struct CountingWriter(usize);
33
34impl std::io::Write for CountingWriter {
35 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
36 self.0 += buf.len();
37 Ok(buf.len())
38 }
39
40 fn flush(&mut self) -> std::io::Result<()> {
41 Ok(())
42 }
43}
44
45#[derive(Debug, Clone)]
57pub struct InMemoryQueueWriter {
58 tx: broadcast::Sender<A2aResult<StreamResponse>>,
59 persistence_tx: Option<mpsc::Sender<A2aResult<StreamResponse>>>,
63 max_event_size: usize,
65 #[allow(dead_code)]
67 write_timeout: std::time::Duration,
68}
69
70impl InMemoryQueueWriter {
71 pub(super) const fn new(
73 tx: broadcast::Sender<A2aResult<StreamResponse>>,
74 max_event_size: usize,
75 write_timeout: std::time::Duration,
76 ) -> Self {
77 Self {
78 tx,
79 persistence_tx: None,
80 max_event_size,
81 write_timeout,
82 }
83 }
84
85 pub(super) const fn new_with_persistence(
87 tx: broadcast::Sender<A2aResult<StreamResponse>>,
88 persistence_tx: mpsc::Sender<A2aResult<StreamResponse>>,
89 max_event_size: usize,
90 write_timeout: std::time::Duration,
91 ) -> Self {
92 Self {
93 tx,
94 persistence_tx: Some(persistence_tx),
95 max_event_size,
96 write_timeout,
97 }
98 }
99
100 #[must_use]
105 pub fn subscribe(&self) -> InMemoryQueueReader {
106 InMemoryQueueReader::new(self.tx.subscribe())
107 }
108
109 pub(crate) fn raw_subscribe(&self) -> broadcast::Receiver<A2aResult<StreamResponse>> {
114 self.tx.subscribe()
115 }
116}
117
118#[allow(clippy::manual_async_fn)]
119impl EventQueueWriter for InMemoryQueueWriter {
120 fn write<'a>(
121 &'a self,
122 event: StreamResponse,
123 ) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
124 Box::pin(async move {
125 let serialized_size = {
130 let mut counter = CountingWriter(0);
131 serde_json::to_writer(&mut counter, &event)
132 .map_err(|e| A2aError::internal(format!("event serialization failed: {e}")))?;
133 counter.0
134 };
135 if serialized_size > self.max_event_size {
136 return Err(A2aError::internal(format!(
137 "event size {serialized_size} bytes exceeds maximum {} bytes",
138 self.max_event_size
139 )));
140 }
141 if let Some(ref persistence_tx) = self.persistence_tx {
144 if let Err(_e) = persistence_tx.send(Ok(event.clone())).await {
145 trace_warn!("persistence channel closed, event not persisted");
146 }
147 }
148 match self.tx.send(Ok(event)) {
157 Ok(_) => Ok(()),
158 Err(_) if self.persistence_tx.is_some() => {
159 trace_warn!("no live event subscribers; event persisted only");
160 Ok(())
161 }
162 Err(_) => Err(A2aError::internal("event queue: no active receivers")),
163 }
164 })
165 }
166
167 fn close<'a>(&'a self) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
168 Box::pin(async move {
169 Ok(())
172 })
173 }
174}
175
176#[derive(Debug)]
187pub struct InMemoryQueueReader {
188 rx: broadcast::Receiver<A2aResult<StreamResponse>>,
189 pending_first: Option<A2aResult<StreamResponse>>,
190}
191
192impl InMemoryQueueReader {
193 pub(crate) const fn new(rx: broadcast::Receiver<A2aResult<StreamResponse>>) -> Self {
195 Self {
196 rx,
197 pending_first: None,
198 }
199 }
200
201 pub fn set_first_event(&mut self, event: StreamResponse) {
203 self.pending_first = Some(Ok(event));
204 }
205
206 pub(crate) const fn with_first_event(
208 rx: broadcast::Receiver<A2aResult<StreamResponse>>,
209 first: StreamResponse,
210 ) -> Self {
211 Self {
212 rx,
213 pending_first: Some(Ok(first)),
214 }
215 }
216
217 pub(crate) fn snapshot_then_end(first: StreamResponse) -> Self {
224 let (tx, rx) = broadcast::channel(1);
227 drop(tx);
228 Self {
229 rx,
230 pending_first: Some(Ok(first)),
231 }
232 }
233}
234
235const LAG_ERROR_MARKER: &str = "streamLagged";
243
244fn lag_error(dropped: u64) -> a2a_protocol_types::error::A2aError {
246 let mut err = a2a_protocol_types::error::A2aError::internal(format!(
247 "event stream lagged: {dropped} events were dropped because this consumer read too \
248 slowly; resubscribe to resynchronize from a fresh task snapshot"
249 ));
250 err.data = Some(serde_json::json!({ LAG_ERROR_MARKER: dropped }));
251 err
252}
253
254#[allow(clippy::redundant_pub_crate)] pub(crate) fn is_lag_error(err: &a2a_protocol_types::error::A2aError) -> bool {
258 err.data
259 .as_ref()
260 .is_some_and(|d| d.get(LAG_ERROR_MARKER).is_some())
261}
262
263impl EventQueueReader for InMemoryQueueReader {
264 fn read(
265 &mut self,
266 ) -> Pin<Box<dyn Future<Output = Option<A2aResult<StreamResponse>>> + Send + '_>> {
267 Box::pin(async move {
268 if let Some(first) = self.pending_first.take() {
271 return Some(first);
272 }
273 match self.rx.recv().await {
274 Ok(event) => Some(event),
275 Err(broadcast::error::RecvError::Lagged(n)) => {
276 trace_warn!(
282 dropped_events = n,
283 "event queue reader lagged, {n} events dropped"
284 );
285 Some(Err(lag_error(n)))
286 }
287 Err(broadcast::error::RecvError::Closed) => None,
288 }
289 })
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::streaming::event_queue::{
297 new_in_memory_queue, new_in_memory_queue_with_options, DEFAULT_MAX_EVENT_SIZE,
298 DEFAULT_WRITE_TIMEOUT,
299 };
300 use a2a_protocol_types::events::{StreamResponse, TaskStatusUpdateEvent};
301 use a2a_protocol_types::task::{ContextId, TaskId, TaskState, TaskStatus};
302
303 fn make_status_event(task_id: &str, state: TaskState) -> StreamResponse {
305 StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
306 task_id: TaskId::new(task_id),
307 context_id: ContextId::new("ctx-test"),
308 status: TaskStatus {
309 state,
310 message: None,
311 timestamp: None,
312 },
313 metadata: None,
314 })
315 }
316
317 #[tokio::test]
325 async fn write_with_no_subscribers_succeeds_when_persistence_attached() {
326 let (writer, reader, mut persistence_rx) =
327 crate::streaming::event_queue::new_in_memory_queue_with_persistence(
328 8,
329 1024 * 1024,
330 std::time::Duration::from_secs(1),
331 );
332 drop(reader); writer
335 .write(make_status_event("t1", TaskState::Working))
336 .await
337 .expect("write must succeed with persistence attached");
338
339 let persisted = persistence_rx
340 .recv()
341 .await
342 .expect("persistence channel should have the event")
343 .expect("event should be Ok");
344 match persisted {
345 StreamResponse::StatusUpdate(evt) => {
346 assert_eq!(evt.status.state, TaskState::Working);
347 }
348 other => panic!("expected StatusUpdate, got: {other:?}"),
349 }
350 }
351
352 #[tokio::test]
356 async fn write_with_no_subscribers_fails_without_persistence() {
357 let (writer, reader) = new_in_memory_queue();
358 drop(reader);
359
360 let result = writer
361 .write(make_status_event("t1", TaskState::Working))
362 .await;
363 assert!(
364 result.is_err(),
365 "sync-mode write with no receivers must fail"
366 );
367 }
368
369 #[tokio::test]
370 async fn write_then_read_single_event() {
371 let (writer, mut reader) = new_in_memory_queue();
372 let event = make_status_event("t1", TaskState::Working);
373
374 writer.write(event).await.expect("write should succeed");
375 drop(writer);
376
377 let received = reader.read().await;
378 assert!(received.is_some(), "reader should return the written event");
379 let result = received.unwrap();
380 let event = result.expect("event should be Ok");
381 match &event {
382 StreamResponse::StatusUpdate(evt) => {
383 assert_eq!(
384 evt.status.state,
385 TaskState::Working,
386 "should be Working event"
387 );
388 }
389 other => panic!("expected StatusUpdate, got: {other:?}"),
390 }
391
392 let eof = reader.read().await;
394 assert!(
395 eof.is_none(),
396 "reader should return None after writer is dropped"
397 );
398 }
399
400 #[tokio::test]
401 async fn write_multiple_events_read_in_order() {
402 let (writer, mut reader) = new_in_memory_queue();
403
404 let e1 = make_status_event("t1", TaskState::Working);
405 let e2 = make_status_event("t1", TaskState::Completed);
406
407 writer.write(e1).await.expect("first write should succeed");
408 writer.write(e2).await.expect("second write should succeed");
409 drop(writer);
410
411 let r1 = reader.read().await.expect("should read first event");
413 let sr1 = r1.expect("first event should be Ok");
414 match &sr1 {
415 StreamResponse::StatusUpdate(evt) => {
416 assert_eq!(
417 evt.status.state,
418 TaskState::Working,
419 "first event should be Working"
420 );
421 }
422 other => panic!("expected StatusUpdate, got: {other:?}"),
423 }
424
425 let r2 = reader.read().await.expect("should read second event");
427 let sr2 = r2.expect("second event should be Ok");
428 match &sr2 {
429 StreamResponse::StatusUpdate(evt) => {
430 assert_eq!(
431 evt.status.state,
432 TaskState::Completed,
433 "second event should be Completed"
434 );
435 }
436 other => panic!("expected StatusUpdate, got: {other:?}"),
437 }
438
439 assert!(
441 reader.read().await.is_none(),
442 "should be EOF after all events"
443 );
444 }
445
446 #[tokio::test]
449 async fn read_returns_none_on_empty_closed_queue() {
450 let (writer, mut reader) = new_in_memory_queue();
451 drop(writer); let result = reader.read().await;
454 assert!(
455 result.is_none(),
456 "reading from an empty closed queue should return None"
457 );
458 }
459
460 #[tokio::test]
461 async fn write_after_all_readers_dropped_returns_error() {
462 let (writer, reader) = new_in_memory_queue();
463 drop(reader);
464
465 let result = writer
466 .write(make_status_event("t1", TaskState::Working))
467 .await;
468 assert!(
469 result.is_err(),
470 "writing with no active receivers should return an error"
471 );
472 }
473
474 #[tokio::test]
475 async fn close_is_no_op_and_succeeds() {
476 let (writer, _reader) = new_in_memory_queue();
477 let result = writer.close().await;
478 assert!(result.is_ok(), "close() should succeed");
479 }
480
481 #[tokio::test]
484 async fn subscribe_creates_independent_reader() {
485 let (writer, mut reader1) = new_in_memory_queue();
486 let mut reader2 = writer.subscribe();
487
488 let event = make_status_event("t1", TaskState::Working);
489 writer.write(event).await.expect("write should succeed");
490 drop(writer);
491
492 let r1 = reader1.read().await;
494 assert!(r1.is_some(), "reader1 should receive the event");
495
496 let r2 = reader2.read().await;
497 assert!(r2.is_some(), "reader2 should receive the event");
498
499 assert!(reader1.read().await.is_none(), "reader1 should see EOF");
501 assert!(reader2.read().await.is_none(), "reader2 should see EOF");
502 }
503
504 #[tokio::test]
505 async fn subscriber_only_sees_events_after_subscribe() {
506 let (writer, mut reader1) = new_in_memory_queue();
507
508 writer
510 .write(make_status_event("t1", TaskState::Submitted))
511 .await
512 .expect("write should succeed");
513
514 let mut reader2 = writer.subscribe();
516
517 writer
519 .write(make_status_event("t1", TaskState::Working))
520 .await
521 .expect("write should succeed");
522 drop(writer);
523
524 let r1a = reader1
526 .read()
527 .await
528 .expect("reader1 should see first event");
529 let evt1a = r1a.expect("first event should be Ok");
530 assert!(
531 matches!(&evt1a, StreamResponse::StatusUpdate(e) if e.status.state == TaskState::Submitted),
532 "reader1 first event should be Submitted"
533 );
534 let r1b = reader1
535 .read()
536 .await
537 .expect("reader1 should see second event");
538 let evt_1b = r1b.expect("second event should be Ok");
539 assert!(
540 matches!(&evt_1b, StreamResponse::StatusUpdate(e) if e.status.state == TaskState::Working),
541 "reader1 second event should be Working"
542 );
543 assert!(reader1.read().await.is_none());
544
545 let r2a = reader2
547 .read()
548 .await
549 .expect("reader2 should see second event");
550 let evt2a = r2a.expect("event should be Ok");
551 assert!(
552 matches!(&evt2a, StreamResponse::StatusUpdate(e) if e.status.state == TaskState::Working),
553 "reader2 should see Working event"
554 );
555 assert!(
556 reader2.read().await.is_none(),
557 "reader2 should see EOF after the one event it received"
558 );
559 }
560
561 #[tokio::test]
564 async fn oversized_event_is_rejected() {
565 let (writer, _reader) = new_in_memory_queue_with_options(
567 16,
568 10, DEFAULT_WRITE_TIMEOUT,
570 );
571
572 let event = make_status_event("t1", TaskState::Working);
573 let result = writer.write(event).await;
574 assert!(
575 result.is_err(),
576 "event exceeding max_event_size should be rejected"
577 );
578 let err = result.unwrap_err();
579 let msg = format!("{err}");
580 assert!(
581 msg.contains("exceeds maximum"),
582 "error message should mention size limit, got: {msg}"
583 );
584 }
585
586 #[test]
588 fn counting_writer_flush_is_noop() {
589 use std::io::Write;
590 let mut cw = super::CountingWriter(0);
591 cw.write_all(b"hello").unwrap();
592 assert_eq!(cw.0, 5);
593 cw.flush().unwrap();
595 assert_eq!(cw.0, 5, "flush should not change the count");
596 }
597
598 #[tokio::test]
599 async fn event_within_size_limit_is_accepted() {
600 let (writer, mut reader) =
602 new_in_memory_queue_with_options(16, DEFAULT_MAX_EVENT_SIZE, DEFAULT_WRITE_TIMEOUT);
603
604 let event = make_status_event("t1", TaskState::Working);
605 writer
606 .write(event)
607 .await
608 .expect("event within size limit should be accepted");
609 drop(writer);
610
611 let r = reader.read().await;
612 assert!(r.is_some(), "reader should receive the event");
613 }
614}