1use std::{collections::HashMap, io::Write, sync::Arc, time::Duration};
2
3use antithesis_sdk::assert_sometimes;
4use axum::Extension;
5use bytes::{BufMut, Bytes, BytesMut};
6use compact_str::ToCompactString;
7use http_body_util::StreamBody;
8use klukai_types::{
9 agent::Agent,
10 api::NotifyEvent,
11 tripwire::Tripwire,
12 updates::{Handle, UpdateCreated, UpdateHandle, UpdatesManager},
13};
14use tokio::sync::{
15 RwLock as TokioRwLock,
16 broadcast::{self, error::RecvError},
17 mpsc,
18};
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{debug, info, warn};
21use uuid::Uuid;
22
23use crate::api::public::pubsub::MatcherUpsertError;
24
25pub type UpdateBroadcastCache = HashMap<Uuid, broadcast::Sender<Bytes>>;
26pub type SharedUpdateBroadcastCache = Arc<TokioRwLock<UpdateBroadcastCache>>;
27
28const RECEIVERS_CHECK_INTERVAL: Duration = Duration::from_secs(30);
30
31pub async fn api_v1_updates(
32 Extension(agent): Extension<Agent>,
33 Extension(bcast_cache): Extension<SharedUpdateBroadcastCache>,
34 Extension(tripwire): Extension<Tripwire>,
35 axum::extract::Path(table): axum::extract::Path<String>,
36) -> hyper::Response<
37 StreamBody<impl futures::Stream<Item = Result<hyper::body::Frame<Bytes>, std::io::Error>>>,
38> {
39 info!("Received update request for table: {table}");
40
41 assert_sometimes!(true, "Corrosion receives requests for table updates");
42
43 let mut bcast_write = bcast_cache.write().await;
44 let updates = agent.updates_manager();
45
46 let upsert_res = updates.get_or_insert(
47 &table,
48 &agent.schema().read(),
49 agent.pool(),
50 tripwire.clone(),
51 );
52
53 let (handle, maybe_created) = match upsert_res {
54 Ok(res) => res,
55 Err(e) => {
56 let error_msg = format!("Error: {}", MatcherUpsertError::from(e));
58 let error_json = serde_json::to_vec(&error_msg).expect("could not serialize error");
59 let (error_tx, error_rx) =
60 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(1);
61 let _ = error_tx
62 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
63 .await;
64 let error_stream = ReceiverStream::new(error_rx);
65 let error_body = StreamBody::new(error_stream);
66
67 return hyper::Response::builder()
68 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
69 .body(error_body)
70 .expect("could not build error response");
71 }
72 };
73
74 let (update_id, sub_rx) =
75 match upsert_update(handle.clone(), maybe_created, updates, &mut bcast_write).await {
76 Ok(id) => id,
77 Err(e) => {
78 let error_msg = format!("Error: {}", e);
80 let error_json = serde_json::to_vec(&error_msg).expect("could not serialize error");
81 let (error_tx, error_rx) = tokio::sync::mpsc::channel::<
82 Result<hyper::body::Frame<Bytes>, std::io::Error>,
83 >(1);
84 let _ = error_tx
85 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
86 .await;
87 let error_stream = ReceiverStream::new(error_rx);
88 let error_body = StreamBody::new(error_stream);
89
90 return hyper::Response::builder()
91 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
92 .body(error_body)
93 .expect("could not build error response");
94 }
95 };
96
97 let (stream_tx, stream_rx) =
99 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(10240);
100 let stream = ReceiverStream::new(stream_rx);
101 let body = StreamBody::new(stream);
102
103 tokio::spawn(forward_update_bytes_to_body_sender(
104 handle, sub_rx, stream_tx, tripwire,
105 ));
106
107 hyper::Response::builder()
108 .status(hyper::StatusCode::OK)
109 .header("corro-query-id", update_id.to_string())
110 .body(body)
111 .expect("could not generate ok http response for update request")
112}
113
114pub async fn upsert_update(
115 handle: UpdateHandle,
116 maybe_created: Option<UpdateCreated>,
117 updates: &UpdatesManager,
118 bcast_write: &mut UpdateBroadcastCache,
119) -> Result<(Uuid, broadcast::Receiver<Bytes>), MatcherUpsertError> {
120 let sub_rx = if let Some(created) = maybe_created {
121 let (sub_tx, sub_rx) = broadcast::channel(10240);
122 bcast_write.insert(handle.id(), sub_tx.clone());
123 tokio::spawn(process_update_channel(
124 updates.clone(),
125 handle.id(),
126 sub_tx,
127 created.evt_rx,
128 ));
129
130 sub_rx
131 } else {
132 let id = handle.id();
133 let sub_tx = bcast_write
134 .get(&id)
135 .cloned()
136 .ok_or(MatcherUpsertError::MissingBroadcaster)?;
137 debug!("found update handle");
138
139 sub_tx.subscribe()
140 };
141
142 Ok((handle.id(), sub_rx))
143}
144
145pub async fn process_update_channel(
146 updates: UpdatesManager,
147 id: Uuid,
148 tx: broadcast::Sender<Bytes>,
149 mut evt_rx: mpsc::Receiver<NotifyEvent>,
150) {
151 let mut buf = BytesMut::new();
152
153 let mut subs_check = tokio::time::interval(RECEIVERS_CHECK_INTERVAL);
156
157 loop {
158 tokio::select! {
159 biased;
160 Some(query_evt) = evt_rx.recv() => {
161 match make_query_event_bytes(&mut buf, &query_evt) {
162 Ok(b) => {
163 if tx.send(b).is_err() {
164 break;
165 }
166 },
167 Err(e) => {
168 match make_query_event_bytes(&mut buf, &NotifyEvent::Error(e.to_compact_string())) {
169 Ok(b) => {
170 let _ = tx.send(b);
171 }
172 Err(e) => {
173 warn!(update_id = %id, "failed to send error in update channel: {e}");
174 }
175 }
176 break;
177 }
178 };
179 },
180 _ = subs_check.tick() => {
181 if tx.receiver_count() == 0 {
182 break;
183 };
184 },
185 };
186 }
187
188 warn!(sub_id = %id, "updates channel done");
189
190 let handle = match updates.remove(&id) {
192 Some(h) => {
193 info!(update_id = %id, "Removed update handle from process_update_channel");
194 h
195 }
196 None => {
197 warn!(update_id = %id, "update handle was already gone. odd!");
198 return;
199 }
200 };
201
202 handle.cleanup().await;
204}
205
206fn make_query_event_bytes(
207 buf: &mut BytesMut,
208 query_evt: &NotifyEvent,
209) -> serde_json::Result<Bytes> {
210 {
211 let mut writer = buf.writer();
212 serde_json::to_writer(&mut writer, query_evt)?;
213
214 writer
216 .write_all(b"\n")
217 .expect("could not write new line to BytesMut Writer");
218 }
219
220 Ok(buf.split().freeze())
221}
222
223async fn forward_update_bytes_to_body_sender(
224 update: UpdateHandle,
225 mut rx: broadcast::Receiver<Bytes>,
226 tx: tokio::sync::mpsc::Sender<Result<hyper::body::Frame<Bytes>, std::io::Error>>,
227 mut tripwire: Tripwire,
228) {
229 let mut buf = BytesMut::new();
230
231 let send_deadline = tokio::time::sleep(Duration::from_millis(10));
232 tokio::pin!(send_deadline);
233
234 loop {
235 tokio::select! {
236 biased;
237 res = rx.recv() => {
238 match res {
239 Ok(event_buf) => {
240 buf.extend_from_slice(&event_buf);
241 if buf.len() >= 64 * 1024
242 && let Err(_) = tx.send(Ok(hyper::body::Frame::data(buf.split().freeze()))).await {
243 warn!(update_id = %update.id(), "could not forward update query event to receiver: stream closed");
244 return;
245 }
246 },
247 Err(RecvError::Lagged(skipped)) => {
248 warn!(update_id = %update.id(), "update skipped {} events, aborting", skipped);
249 return;
250 },
251 Err(RecvError::Closed) => {
252 info!(update_id = %update.id(), "events subcription ran out");
253 return;
254 },
255 }
256 },
257 _ = &mut send_deadline => {
258 if !buf.is_empty() {
259 if tx.send(Ok(hyper::body::Frame::data(buf.split().freeze()))).await.is_err() {
260 warn!(update_id = %update.id(), "could not forward subscription query event to receiver: stream closed");
261 return;
262 }
263 } else {
264 send_deadline.as_mut().reset(tokio::time::Instant::now() + Duration::from_millis(10));
265 continue;
266 }
267 },
268 _ = update.cancelled() => {
269 info!(update_id = %update.id(), "update cancelled, aborting forwarding bytes to subscriber");
270 return;
271 },
272 _ = &mut tripwire => {
273 break;
274 }
275 }
276 }
277
278 while let Ok(event_buf) = rx.try_recv() {
280 buf.extend_from_slice(&event_buf);
281 if tx
282 .send(Ok(hyper::body::Frame::data(buf.split().freeze())))
283 .await
284 .is_err()
285 {
286 warn!(update_id = %update.id(), "could not forward subscription query event to receiver: stream closed");
287 return;
288 }
289 }
290}