1use rivet_envoy_protocol as protocol;
2use tokio::sync::oneshot;
3
4use crate::connection::{WsSendResult, ws_send, ws_send_for_session};
5use crate::envoy::EnvoyContext;
6use crate::kv::KV_EXPIRE_MS;
7use crate::metrics::METRICS;
8use crate::utils::{
9 EnvoyShutdownError, RemoteSqliteConnectionSessionLostError,
10 RemoteSqliteIndeterminateResultError,
11};
12
13#[derive(Clone)]
14pub enum SqliteRequest {
15 GetPages(protocol::SqliteGetPagesRequest),
16 Commit(protocol::SqliteCommitRequest),
17 CommitStageBegin(protocol::SqliteCommitStageBeginRequest),
18 CommitStageSegment(protocol::SqliteCommitStageSegmentRequest),
19 CommitFinalize(protocol::SqliteCommitFinalizeRequest),
20}
21
22impl SqliteRequest {
23 pub fn kind(&self) -> &'static str {
24 match self {
25 SqliteRequest::GetPages(_) => "get_pages",
26 SqliteRequest::Commit(_) => "commit",
27 SqliteRequest::CommitStageBegin(_) => "commit_stage_begin",
28 SqliteRequest::CommitStageSegment(_) => "commit_stage_segment",
29 SqliteRequest::CommitFinalize(_) => "commit_finalize",
30 }
31 }
32}
33
34pub enum SqliteResponse {
35 GetPages(protocol::SqliteGetPagesResponse),
36 Commit(protocol::SqliteCommitResponse),
37 CommitStageBegin(protocol::SqliteCommitStageBeginResponse),
38 CommitStageSegment(protocol::SqliteCommitStageSegmentResponse),
39 CommitFinalize(protocol::SqliteCommitFinalizeResponse),
40}
41
42#[derive(Clone, Debug)]
43pub enum RemoteSqliteRequest {
44 Exec(protocol::SqliteExecRequest),
45 Execute(protocol::SqliteExecuteRequest),
46 ExecuteBatch(protocol::SqliteExecuteBatchRequest),
47}
48
49#[derive(Debug)]
50pub enum RemoteSqliteResponse {
51 Exec(protocol::SqliteExecResponse),
52 Execute(protocol::SqliteExecuteResponse),
53 ExecuteBatch(protocol::SqliteExecuteBatchResponse),
54}
55
56#[derive(Debug)]
57pub struct RemoteSqliteResponseEnvelope {
58 pub response: RemoteSqliteResponse,
59 pub session: u64,
60}
61
62impl RemoteSqliteRequest {
63 fn operation(&self) -> &'static str {
64 match self {
65 RemoteSqliteRequest::Exec(_) => "exec",
66 RemoteSqliteRequest::Execute(_) => "execute",
67 RemoteSqliteRequest::ExecuteBatch(_) => "execute_batch",
68 }
69 }
70
71 pub fn kind(&self) -> &'static str {
72 match self {
73 RemoteSqliteRequest::Exec(_) => "remote_exec",
74 RemoteSqliteRequest::Execute(_) => "remote_execute",
75 RemoteSqliteRequest::ExecuteBatch(_) => "remote_execute_batch",
76 }
77 }
78}
79
80pub struct SqliteRequestEntry {
81 pub request: SqliteRequest,
82 pub response_tx: oneshot::Sender<anyhow::Result<SqliteResponse>>,
83 pub sent: bool,
84 pub timestamp: crate::time::Instant,
85}
86
87pub struct RemoteSqliteRequestEntry {
88 pub request: RemoteSqliteRequest,
89 pub response_tx: oneshot::Sender<anyhow::Result<RemoteSqliteResponseEnvelope>>,
90 pub expected_session: Option<u64>,
91 pub sent_session: Option<u64>,
92 pub sent: bool,
93 pub timestamp: crate::time::Instant,
94}
95
96pub async fn handle_sqlite_request(
97 ctx: &mut EnvoyContext,
98 request: SqliteRequest,
99 response_tx: oneshot::Sender<anyhow::Result<SqliteResponse>>,
100) {
101 let request_id = ctx.next_sqlite_request_id;
102 ctx.next_sqlite_request_id += 1;
103
104 let entry = SqliteRequestEntry {
105 request,
106 response_tx,
107 sent: false,
108 timestamp: crate::time::Instant::now(),
109 };
110
111 ctx.sqlite_requests.insert(request_id, entry);
112 METRICS.sqlite_requests_inflight.inc();
113
114 let ws_available = {
115 let guard = ctx.shared.ws_tx.lock().await;
116 guard.is_some()
117 };
118
119 if ws_available {
120 send_single_sqlite_request(ctx, request_id).await;
121 }
122}
123
124pub async fn handle_remote_sqlite_request(
125 ctx: &mut EnvoyContext,
126 request: RemoteSqliteRequest,
127 expected_session: Option<u64>,
128 response_tx: oneshot::Sender<anyhow::Result<RemoteSqliteResponseEnvelope>>,
129) {
130 let request_id = ctx.next_remote_sqlite_request_id;
131 ctx.next_remote_sqlite_request_id += 1;
132
133 let entry = RemoteSqliteRequestEntry {
134 request,
135 response_tx,
136 expected_session,
137 sent_session: None,
138 sent: false,
139 timestamp: crate::time::Instant::now(),
140 };
141
142 ctx.remote_sqlite_requests.insert(request_id, entry);
143 METRICS.remote_sqlite_requests_inflight.inc();
144
145 send_single_remote_sqlite_request(ctx, request_id).await;
146}
147
148pub async fn handle_sqlite_get_pages_response(
149 ctx: &mut EnvoyContext,
150 response: protocol::ToEnvoySqliteGetPagesResponse,
151) {
152 handle_sqlite_response(
153 ctx,
154 response.request_id,
155 SqliteResponse::GetPages(response.data),
156 "sqlite_get_pages",
157 );
158}
159
160pub async fn handle_sqlite_commit_response(
161 ctx: &mut EnvoyContext,
162 response: protocol::ToEnvoySqliteCommitResponse,
163) {
164 handle_sqlite_response(
165 ctx,
166 response.request_id,
167 SqliteResponse::Commit(response.data),
168 "sqlite_commit",
169 );
170}
171
172pub async fn handle_sqlite_commit_stage_begin_response(
173 ctx: &mut EnvoyContext,
174 response: protocol::ToEnvoySqliteCommitStageBeginResponse,
175) {
176 handle_sqlite_response(
177 ctx,
178 response.request_id,
179 SqliteResponse::CommitStageBegin(response.data),
180 "sqlite_commit_stage_begin",
181 );
182}
183
184pub async fn handle_sqlite_commit_stage_segment_response(
185 ctx: &mut EnvoyContext,
186 response: protocol::ToEnvoySqliteCommitStageSegmentResponse,
187) {
188 handle_sqlite_response(
189 ctx,
190 response.request_id,
191 SqliteResponse::CommitStageSegment(response.data),
192 "sqlite_commit_stage_segment",
193 );
194}
195
196pub async fn handle_sqlite_commit_finalize_response(
197 ctx: &mut EnvoyContext,
198 response: protocol::ToEnvoySqliteCommitFinalizeResponse,
199) {
200 handle_sqlite_response(
201 ctx,
202 response.request_id,
203 SqliteResponse::CommitFinalize(response.data),
204 "sqlite_commit_finalize",
205 );
206}
207
208pub async fn handle_remote_sqlite_exec_response(
209 ctx: &mut EnvoyContext,
210 response: protocol::ToEnvoySqliteExecResponse,
211) {
212 handle_remote_sqlite_response(
213 ctx,
214 response.request_id,
215 RemoteSqliteResponse::Exec(response.data),
216 "remote_sqlite_exec",
217 );
218}
219
220pub async fn handle_remote_sqlite_execute_response(
221 ctx: &mut EnvoyContext,
222 response: protocol::ToEnvoySqliteExecuteResponse,
223) {
224 handle_remote_sqlite_response(
225 ctx,
226 response.request_id,
227 RemoteSqliteResponse::Execute(response.data),
228 "remote_sqlite_execute",
229 );
230}
231
232pub async fn handle_remote_sqlite_execute_batch_response(
233 ctx: &mut EnvoyContext,
234 response: protocol::ToEnvoySqliteExecuteBatchResponse,
235) {
236 handle_remote_sqlite_response(
237 ctx,
238 response.request_id,
239 RemoteSqliteResponse::ExecuteBatch(response.data),
240 "remote_sqlite_execute_batch",
241 );
242}
243
244fn handle_sqlite_response(
245 ctx: &mut EnvoyContext,
246 request_id: u32,
247 response: SqliteResponse,
248 op: &str,
249) {
250 let request = ctx.sqlite_requests.remove(&request_id);
251
252 if let Some(request) = request {
253 METRICS.sqlite_requests_inflight.dec();
254 let _ = request.response_tx.send(Ok(response));
255 } else {
256 tracing::error!(
257 request_id,
258 op,
259 "received sqlite response for unknown request id"
260 );
261 }
262}
263
264fn handle_remote_sqlite_response(
265 ctx: &mut EnvoyContext,
266 request_id: u32,
267 response: RemoteSqliteResponse,
268 op: &str,
269) {
270 let request = ctx.remote_sqlite_requests.remove(&request_id);
271
272 if let Some(request) = request {
273 METRICS.remote_sqlite_requests_inflight.dec();
274 let session = request
275 .sent_session
276 .expect("a remote sqlite response must belong to a sent request");
277 let _ = request
278 .response_tx
279 .send(Ok(RemoteSqliteResponseEnvelope { response, session }));
280 } else {
281 tracing::error!(
282 request_id,
283 op,
284 "received remote sqlite response for unknown request id"
285 );
286 }
287}
288
289pub async fn send_single_sqlite_request(ctx: &mut EnvoyContext, request_id: u32) {
290 let request = ctx.sqlite_requests.get_mut(&request_id);
291 let Some(request) = request else { return };
292 if request.sent {
293 return;
294 }
295
296 let message =
297 match request.request.clone() {
298 SqliteRequest::GetPages(data) => protocol::ToRivet::ToRivetSqliteGetPagesRequest(
299 protocol::ToRivetSqliteGetPagesRequest { request_id, data },
300 ),
301 SqliteRequest::Commit(data) => protocol::ToRivet::ToRivetSqliteCommitRequest(
302 protocol::ToRivetSqliteCommitRequest { request_id, data },
303 ),
304 SqliteRequest::CommitStageBegin(data) => {
305 protocol::ToRivet::ToRivetSqliteCommitStageBeginRequest(
306 protocol::ToRivetSqliteCommitStageBeginRequest { request_id, data },
307 )
308 }
309 SqliteRequest::CommitStageSegment(data) => {
310 protocol::ToRivet::ToRivetSqliteCommitStageSegmentRequest(
311 protocol::ToRivetSqliteCommitStageSegmentRequest { request_id, data },
312 )
313 }
314 SqliteRequest::CommitFinalize(data) => {
315 protocol::ToRivet::ToRivetSqliteCommitFinalizeRequest(
316 protocol::ToRivetSqliteCommitFinalizeRequest { request_id, data },
317 )
318 }
319 };
320
321 ws_send(&ctx.shared, message).await;
322
323 if let Some(request) = ctx.sqlite_requests.get_mut(&request_id) {
324 request.sent = true;
325 request.timestamp = crate::time::Instant::now();
326 }
327}
328
329pub async fn send_single_remote_sqlite_request(ctx: &mut EnvoyContext, request_id: u32) {
330 let request = ctx.remote_sqlite_requests.get_mut(&request_id);
331 let Some(request) = request else { return };
332 if request.sent {
333 return;
334 }
335
336 let expected_session = request.expected_session;
337 let message = remote_sqlite_request_to_message(request_id, request.request.clone());
338
339 match ws_send_for_session(&ctx.shared, message, expected_session).await {
340 WsSendResult::Sent { session } => {
341 if let Some(request) = ctx.remote_sqlite_requests.get_mut(&request_id) {
342 request.sent = true;
343 request.sent_session = Some(session);
344 request.timestamp = crate::time::Instant::now();
345 }
346 }
347 WsSendResult::Unavailable if expected_session.is_none() => {
348 }
351 WsSendResult::Unavailable => {
352 fail_remote_sqlite_session_request(ctx, request_id, None);
353 }
354 WsSendResult::StaleSession { current } => {
355 fail_remote_sqlite_session_request(ctx, request_id, current);
356 }
357 }
358}
359
360fn fail_remote_sqlite_session_request(
361 ctx: &mut EnvoyContext,
362 request_id: u32,
363 current: Option<u64>,
364) {
365 let Some(request) = ctx.remote_sqlite_requests.remove(&request_id) else {
366 return;
367 };
368 METRICS.remote_sqlite_requests_inflight.dec();
369 let expected = request
370 .expected_session
371 .expect("only session-affine requests fail session admission");
372 let _ = request.response_tx.send(Err(anyhow::anyhow!(
373 RemoteSqliteConnectionSessionLostError { expected, current }
374 )));
375}
376
377pub fn remote_sqlite_request_to_message(
378 request_id: u32,
379 request: RemoteSqliteRequest,
380) -> protocol::ToRivet {
381 match request {
382 RemoteSqliteRequest::Exec(data) => {
383 protocol::ToRivet::ToRivetSqliteExecRequest(protocol::ToRivetSqliteExecRequest {
384 request_id,
385 data,
386 })
387 }
388 RemoteSqliteRequest::Execute(data) => {
389 protocol::ToRivet::ToRivetSqliteExecuteRequest(protocol::ToRivetSqliteExecuteRequest {
390 request_id,
391 data,
392 })
393 }
394 RemoteSqliteRequest::ExecuteBatch(data) => {
395 protocol::ToRivet::ToRivetSqliteExecuteBatchRequest(
396 protocol::ToRivetSqliteExecuteBatchRequest { request_id, data },
397 )
398 }
399 }
400}
401
402pub async fn process_unsent_sqlite_requests(ctx: &mut EnvoyContext) {
403 let ws_available = {
404 let guard = ctx.shared.ws_tx.lock().await;
405 guard.is_some()
406 };
407
408 if !ws_available {
409 return;
410 }
411
412 let unsent: Vec<u32> = ctx
413 .sqlite_requests
414 .iter()
415 .filter(|(_, req)| !req.sent)
416 .map(|(id, _)| *id)
417 .collect();
418
419 for request_id in unsent {
420 send_single_sqlite_request(ctx, request_id).await;
421 }
422}
423
424pub async fn process_unsent_remote_sqlite_requests(ctx: &mut EnvoyContext) {
425 let ws_available = {
426 let guard = ctx.shared.ws_tx.lock().await;
427 guard.is_some()
428 };
429
430 if !ws_available {
431 return;
432 }
433
434 let unsent: Vec<u32> = ctx
435 .remote_sqlite_requests
436 .iter()
437 .filter(|(_, req)| !req.sent)
438 .map(|(id, _)| *id)
439 .collect();
440
441 for request_id in unsent {
442 send_single_remote_sqlite_request(ctx, request_id).await;
443 }
444}
445
446pub fn cleanup_old_sqlite_requests(ctx: &mut EnvoyContext) {
447 let now = crate::time::Instant::now();
448 let mut to_delete = Vec::new();
449
450 for (request_id, request) in &ctx.sqlite_requests {
451 if now.duration_since(request.timestamp).as_millis() > KV_EXPIRE_MS as u128 {
452 to_delete.push(*request_id);
453 }
454 }
455
456 for request_id in to_delete {
457 if let Some(request) = ctx.sqlite_requests.remove(&request_id) {
458 let kind = request.request.kind();
459 let was_sent = if request.sent { "true" } else { "false" };
460 METRICS
461 .sqlite_request_expired_total
462 .with_label_values(&[kind, was_sent])
463 .inc();
464 METRICS.sqlite_requests_inflight.dec();
465 tracing::warn!(
466 request_id,
467 kind,
468 was_sent = request.sent,
469 age_ms = now.duration_since(request.timestamp).as_millis() as u64,
470 "sqlite request expired by cleanup; if was_sent=true this indicates an abandoned in-flight request"
471 );
472 let _ = request
473 .response_tx
474 .send(Err(anyhow::anyhow!("sqlite request timed out")));
475 }
476 }
477}
478
479pub fn cleanup_old_remote_sqlite_requests(ctx: &mut EnvoyContext) {
480 let now = crate::time::Instant::now();
481 let mut to_delete = Vec::new();
482
483 for (request_id, request) in &ctx.remote_sqlite_requests {
484 if now.duration_since(request.timestamp).as_millis() > KV_EXPIRE_MS as u128 {
485 to_delete.push(*request_id);
486 }
487 }
488
489 for request_id in to_delete {
490 if let Some(request) = ctx.remote_sqlite_requests.remove(&request_id) {
491 let kind = request.request.kind();
492 let was_sent = if request.sent { "true" } else { "false" };
493 METRICS
494 .sqlite_request_expired_total
495 .with_label_values(&[kind, was_sent])
496 .inc();
497 METRICS.remote_sqlite_requests_inflight.dec();
498 tracing::warn!(
499 request_id,
500 kind,
501 was_sent = request.sent,
502 age_ms = now.duration_since(request.timestamp).as_millis() as u64,
503 "remote sqlite request expired by cleanup; if was_sent=true this indicates an abandoned in-flight request"
504 );
505 let _ = request
506 .response_tx
507 .send(Err(anyhow::anyhow!("remote sqlite request timed out")));
508 }
509 }
510}
511
512pub fn fail_sqlite_requests_with_shutdown(ctx: &mut EnvoyContext) {
513 for (_id, request) in ctx.sqlite_requests.drain() {
514 METRICS.sqlite_requests_inflight.dec();
515 let _ = request
516 .response_tx
517 .send(Err(anyhow::anyhow!(EnvoyShutdownError)));
518 }
519}
520
521pub fn fail_sent_sqlite_requests_with_indeterminate_result(ctx: &mut EnvoyContext) {
529 let request_ids: Vec<u32> = ctx
530 .sqlite_requests
531 .iter()
532 .filter(|(_, request)| request.sent)
533 .map(|(request_id, _)| *request_id)
534 .collect();
535
536 for request_id in request_ids {
537 if let Some(request) = ctx.sqlite_requests.remove(&request_id) {
538 METRICS.sqlite_requests_inflight.dec();
539 let operation = request.request.kind();
540 tracing::warn!(
541 request_id,
542 operation,
543 "sqlite response lost after websocket disconnect"
544 );
545 let _ = request.response_tx.send(Err(anyhow::anyhow!(
546 RemoteSqliteIndeterminateResultError { operation }
547 )));
548 }
549 }
550}
551
552pub fn fail_remote_sqlite_requests_with_shutdown(ctx: &mut EnvoyContext) {
553 for (_id, request) in ctx.remote_sqlite_requests.drain() {
554 METRICS.remote_sqlite_requests_inflight.dec();
555 let _ = request
556 .response_tx
557 .send(Err(anyhow::anyhow!(EnvoyShutdownError)));
558 }
559}
560
561pub fn fail_sent_remote_sqlite_requests_with_indeterminate_result(ctx: &mut EnvoyContext) {
562 let request_ids: Vec<u32> = ctx
563 .remote_sqlite_requests
564 .iter()
565 .filter(|(_, request)| request.sent)
566 .map(|(request_id, _)| *request_id)
567 .collect();
568
569 for request_id in request_ids {
570 if let Some(request) = ctx.remote_sqlite_requests.remove(&request_id) {
571 METRICS.remote_sqlite_requests_inflight.dec();
572 let operation = request.request.operation();
573 tracing::warn!(
574 request_id,
575 operation,
576 "remote sqlite response lost after websocket disconnect"
577 );
578 let _ = request.response_tx.send(Err(anyhow::anyhow!(
579 RemoteSqliteIndeterminateResultError { operation }
580 )));
581 }
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use std::collections::HashMap;
588 use std::sync::Arc;
589
590 use vbare::OwnedVersionedData;
591
592 use super::*;
593 use crate::config::{
594 BoxFuture, EnvoyCallbacks, EnvoyConfig, HttpRequest, HttpResponse, WebSocketHandler,
595 WebSocketSender,
596 };
597 use crate::context::{SharedContext, WsTxMessage};
598 use crate::handle::EnvoyHandle;
599 use crate::utils::{BufferMap, RemoteSqliteIndeterminateResultError};
600
601 struct IdleCallbacks;
602
603 impl EnvoyCallbacks for IdleCallbacks {
604 fn on_actor_start(
605 &self,
606 _handle: EnvoyHandle,
607 _actor_id: String,
608 _generation: u32,
609 _config: protocol::ActorConfig,
610 _preloaded_kv: Option<protocol::PreloadedKv>,
611 ) -> BoxFuture<anyhow::Result<()>> {
612 Box::pin(async { Ok(()) })
613 }
614
615 fn on_shutdown(&self) {}
616
617 fn fetch(
618 &self,
619 _handle: EnvoyHandle,
620 _actor_id: String,
621 _gateway_id: protocol::GatewayId,
622 _request_id: protocol::RequestId,
623 _request: HttpRequest,
624 ) -> BoxFuture<anyhow::Result<HttpResponse>> {
625 Box::pin(async { anyhow::bail!("fetch should not be called in sqlite tests") })
626 }
627
628 fn websocket(
629 &self,
630 _handle: EnvoyHandle,
631 _actor_id: String,
632 _gateway_id: protocol::GatewayId,
633 _request_id: protocol::RequestId,
634 _request: HttpRequest,
635 _path: String,
636 _headers: HashMap<String, String>,
637 _is_hibernatable: bool,
638 _is_restoring_hibernatable: bool,
639 _sender: WebSocketSender,
640 ) -> BoxFuture<anyhow::Result<WebSocketHandler>> {
641 Box::pin(async { anyhow::bail!("websocket should not be called in sqlite tests") })
642 }
643
644 fn can_hibernate(
645 &self,
646 _actor_id: &str,
647 _gateway_id: &protocol::GatewayId,
648 _request_id: &protocol::RequestId,
649 _request: &HttpRequest,
650 ) -> BoxFuture<anyhow::Result<bool>> {
651 Box::pin(async { Ok(false) })
652 }
653 }
654
655 fn new_envoy_context() -> EnvoyContext {
656 let (envoy_tx, _envoy_rx) = tokio::sync::mpsc::unbounded_channel();
657 let shared = Arc::new(SharedContext {
658 config: EnvoyConfig {
659 version: 1,
660 endpoint: "http://127.0.0.1:1".to_string(),
661 token: None,
662 namespace: "test".to_string(),
663 pool_name: "test".to_string(),
664 prepopulate_actor_names: HashMap::new(),
665 metadata: None,
666 not_global: true,
667 debug_latency_ms: None,
668 callbacks: Arc::new(IdleCallbacks),
669 },
670 envoy_key: "test-envoy".to_string(),
671 envoy_tx,
672 actors: Arc::new(std::sync::Mutex::new(HashMap::new())),
673 actors_notify: Arc::new(tokio::sync::Notify::new()),
674 live_tunnel_requests: Arc::new(std::sync::Mutex::new(HashMap::new())),
675 pending_hibernation_restores: Arc::new(std::sync::Mutex::new(HashMap::new())),
676 ws_tx: Arc::new(tokio::sync::Mutex::new(
677 None::<tokio::sync::mpsc::UnboundedSender<WsTxMessage>>,
678 )),
679 http_ws_tx: Arc::new(tokio::sync::Mutex::new(None)),
680 connection_session: std::sync::atomic::AtomicU64::new(0),
681 next_connection_session: std::sync::atomic::AtomicU64::new(0),
682 connection_session_tx: tokio::sync::watch::channel(0).0,
683 protocol_metadata: Arc::new(tokio::sync::Mutex::new(None)),
684 shutting_down: std::sync::atomic::AtomicBool::new(false),
685 last_ping_ts: std::sync::atomic::AtomicI64::new(0),
686 stopped_tx: tokio::sync::watch::channel(true).0,
687 });
688
689 EnvoyContext {
690 shared,
691 shutting_down: false,
692 actors: HashMap::new(),
693 pending_events: HashMap::new(),
694 buffered_actor_messages: HashMap::new(),
695 kv_requests: HashMap::new(),
696 next_kv_request_id: 0,
697 sqlite_requests: HashMap::new(),
698 next_sqlite_request_id: 0,
699 remote_sqlite_requests: HashMap::new(),
700 next_remote_sqlite_request_id: 0,
701 request_to_actor: BufferMap::new(),
702 http_request_routes: BufferMap::new(),
703 http_message_indices: BufferMap::new(),
704 http_request_cancellations: HashMap::new(),
705 buffered_messages: Vec::new(),
706 processed_command_idx: HashMap::new(),
707 }
708 }
709
710 fn exec_request() -> protocol::SqliteExecRequest {
711 protocol::SqliteExecRequest {
712 namespace_id: "ns".to_string(),
713 actor_id: "actor".to_string(),
714 generation: 1,
715 sql: "select 1".to_string(),
716 }
717 }
718
719 fn execute_request() -> protocol::SqliteExecuteRequest {
720 protocol::SqliteExecuteRequest {
721 namespace_id: "ns".to_string(),
722 actor_id: "actor".to_string(),
723 generation: 1,
724 sql: "select ?".to_string(),
725 params: Some(vec![protocol::SqliteBindParam::SqliteValueInteger(
726 protocol::SqliteValueInteger { value: 1 },
727 )]),
728 }
729 }
730
731 fn execute_batch_request() -> protocol::SqliteExecuteBatchRequest {
732 protocol::SqliteExecuteBatchRequest {
733 namespace_id: "ns".to_string(),
734 actor_id: "actor".to_string(),
735 generation: 1,
736 statements: vec![
737 protocol::SqliteBatchStatement {
738 sql: "insert into t values (?)".to_string(),
739 params: Some(vec![protocol::SqliteBindParam::SqliteValueInteger(
740 protocol::SqliteValueInteger { value: 1 },
741 )]),
742 },
743 protocol::SqliteBatchStatement {
744 sql: "insert into t values (?)".to_string(),
745 params: Some(vec![protocol::SqliteBindParam::SqliteValueInteger(
746 protocol::SqliteValueInteger { value: 2 },
747 )]),
748 },
749 ],
750 }
751 }
752
753 #[tokio::test]
754 async fn remote_sqlite_exec_response_matches_pending_request() {
755 let mut ctx = new_envoy_context();
756 let (ws_tx, mut ws_rx) = tokio::sync::mpsc::unbounded_channel();
757 let session = crate::connection::install_connection(&ctx.shared, ws_tx).await;
758 let (tx, rx) = oneshot::channel();
759
760 handle_remote_sqlite_request(
761 &mut ctx,
762 RemoteSqliteRequest::Exec(exec_request()),
763 None,
764 tx,
765 )
766 .await;
767 assert!(matches!(ws_rx.recv().await, Some(WsTxMessage::Send(_))));
768 assert!(ctx.remote_sqlite_requests.contains_key(&0));
769
770 handle_remote_sqlite_exec_response(
771 &mut ctx,
772 protocol::ToEnvoySqliteExecResponse {
773 request_id: 0,
774 data: protocol::SqliteExecResponse::SqliteExecOk(protocol::SqliteExecOk {
775 result: protocol::SqliteQueryResult {
776 columns: vec!["one".to_string()],
777 rows: vec![vec![protocol::SqliteColumnValue::SqliteValueInteger(
778 protocol::SqliteValueInteger { value: 1 },
779 )]],
780 },
781 }),
782 },
783 )
784 .await;
785
786 let response = rx
787 .await
788 .expect("response sender should complete")
789 .expect("response should succeed");
790 assert_eq!(response.session, session);
791 match response.response {
792 RemoteSqliteResponse::Exec(protocol::SqliteExecResponse::SqliteExecOk(ok)) => {
793 assert_eq!(ok.result.columns, vec!["one"]);
794 assert_eq!(ok.result.rows.len(), 1);
795 }
796 _ => panic!("unexpected response"),
797 }
798 assert!(ctx.remote_sqlite_requests.is_empty());
799 }
800
801 #[tokio::test]
802 async fn remote_sqlite_batch_uses_one_websocket_request() {
803 let mut ctx = new_envoy_context();
804 let (ws_tx, mut ws_rx) = tokio::sync::mpsc::unbounded_channel();
805 crate::connection::install_connection(&ctx.shared, ws_tx).await;
806 let (tx, _rx) = oneshot::channel();
807
808 handle_remote_sqlite_request(
809 &mut ctx,
810 RemoteSqliteRequest::ExecuteBatch(execute_batch_request()),
811 None,
812 tx,
813 )
814 .await;
815
816 assert!(matches!(ws_rx.recv().await, Some(WsTxMessage::Send(_))));
817 assert!(
818 ws_rx.try_recv().is_err(),
819 "a batch must serialize as one WebSocket message"
820 );
821 assert_eq!(ctx.remote_sqlite_requests.len(), 1);
822 }
823
824 #[test]
825 fn remote_sqlite_requests_reject_protocol_v3_serialization() {
826 let requests = vec![
827 RemoteSqliteRequest::Exec(exec_request()),
828 RemoteSqliteRequest::Execute(execute_request()),
829 ];
830
831 for request in requests {
832 let message = remote_sqlite_request_to_message(7, request);
833 let err = protocol::versioned::ToRivet::wrap_latest(message)
834 .serialize(3)
835 .expect_err("remote sqlite requests should require protocol v4");
836 let compatibility = err
837 .downcast_ref::<protocol::versioned::ProtocolCompatibilityError>()
838 .expect("error should be a protocol compatibility error");
839 assert_eq!(
840 compatibility.feature,
841 protocol::versioned::ProtocolCompatibilityFeature::RemoteSqliteExecution
842 );
843 assert_eq!(compatibility.required_version, 4);
844 assert_eq!(compatibility.target_version, 3);
845 }
846 }
847
848 #[tokio::test]
849 async fn remote_sqlite_shutdown_cleanup_fails_pending_requests() {
850 let mut ctx = new_envoy_context();
851 let (tx, rx) = oneshot::channel();
852
853 handle_remote_sqlite_request(
854 &mut ctx,
855 RemoteSqliteRequest::Execute(execute_request()),
856 None,
857 tx,
858 )
859 .await;
860 fail_remote_sqlite_requests_with_shutdown(&mut ctx);
861
862 let err = rx
863 .await
864 .expect("response sender should complete")
865 .expect_err("pending request should fail during shutdown");
866 assert!(err.downcast_ref::<EnvoyShutdownError>().is_some());
867 assert!(ctx.remote_sqlite_requests.is_empty());
868 }
869
870 #[tokio::test]
871 async fn sent_remote_sqlite_request_fails_indeterminate_on_disconnect() {
872 let mut ctx = new_envoy_context();
873 let (ws_tx, mut ws_rx) = tokio::sync::mpsc::unbounded_channel();
874 crate::connection::install_connection(&ctx.shared, ws_tx).await;
875 let (tx, rx) = oneshot::channel();
876
877 handle_remote_sqlite_request(
878 &mut ctx,
879 RemoteSqliteRequest::Execute(execute_request()),
880 None,
881 tx,
882 )
883 .await;
884 assert!(matches!(ws_rx.recv().await, Some(WsTxMessage::Send(_))));
885 assert!(
886 ctx.remote_sqlite_requests
887 .get(&0)
888 .expect("request should be pending")
889 .sent
890 );
891
892 fail_sent_remote_sqlite_requests_with_indeterminate_result(&mut ctx);
893
894 let err = rx
895 .await
896 .expect("response sender should complete")
897 .expect_err("sent write should fail indeterminate on disconnect");
898 let indeterminate = err
899 .downcast_ref::<RemoteSqliteIndeterminateResultError>()
900 .expect("error should describe indeterminate remote sqlite result");
901 assert_eq!(indeterminate.operation, "execute");
902 assert!(ctx.remote_sqlite_requests.is_empty());
903 }
904
905 #[tokio::test]
906 async fn unsent_remote_sqlite_request_survives_disconnect_and_sends_on_reconnect() {
907 let mut ctx = new_envoy_context();
908 let (tx, mut rx) = oneshot::channel();
909
910 handle_remote_sqlite_request(
911 &mut ctx,
912 RemoteSqliteRequest::Execute(execute_request()),
913 None,
914 tx,
915 )
916 .await;
917 assert!(
918 !ctx.remote_sqlite_requests
919 .get(&0)
920 .expect("request should be pending")
921 .sent
922 );
923
924 fail_sent_remote_sqlite_requests_with_indeterminate_result(&mut ctx);
925 assert!(matches!(
926 rx.try_recv(),
927 Err(tokio::sync::oneshot::error::TryRecvError::Empty)
928 ));
929 assert!(ctx.remote_sqlite_requests.contains_key(&0));
930
931 let (ws_tx, mut ws_rx) = tokio::sync::mpsc::unbounded_channel();
932 crate::connection::install_connection(&ctx.shared, ws_tx).await;
933 process_unsent_remote_sqlite_requests(&mut ctx).await;
934
935 assert!(matches!(ws_rx.recv().await, Some(WsTxMessage::Send(_))));
936 assert!(
937 ctx.remote_sqlite_requests
938 .get(&0)
939 .expect("request should still be pending")
940 .sent
941 );
942 }
943
944 #[tokio::test]
945 async fn transaction_request_never_crosses_connection_sessions() {
946 let mut ctx = new_envoy_context();
947 let (first_tx, _first_rx) = tokio::sync::mpsc::unbounded_channel();
948 let first_session = crate::connection::install_connection(&ctx.shared, first_tx).await;
949 crate::connection::remove_connection(&ctx.shared).await;
950 let (second_tx, mut second_rx) = tokio::sync::mpsc::unbounded_channel();
951 let second_session = crate::connection::install_connection(&ctx.shared, second_tx).await;
952 assert_ne!(first_session, second_session);
953
954 let (response_tx, response_rx) = oneshot::channel();
955 handle_remote_sqlite_request(
956 &mut ctx,
957 RemoteSqliteRequest::Execute(execute_request()),
958 Some(first_session),
959 response_tx,
960 )
961 .await;
962
963 let error = response_rx
964 .await
965 .expect("response sender should complete")
966 .expect_err("stale transaction session must fail before send");
967 let lost = error
968 .downcast_ref::<RemoteSqliteConnectionSessionLostError>()
969 .expect("error should identify the stale session");
970 assert_eq!(lost.expected, first_session);
971 assert_eq!(lost.current, Some(second_session));
972 assert!(second_rx.try_recv().is_err(), "stale request was sent");
973 assert!(ctx.remote_sqlite_requests.is_empty());
974 }
975}