1use std::sync::Arc;
2use std::time::Instant;
3
4use anyhow::{Result, anyhow};
5use async_trait::async_trait;
6use rivet_envoy_protocol as protocol;
7use tokio::runtime::Handle;
8use tokio::sync::watch;
9
10use crate::{
11 query::{BindParam, ExecResult, ExecuteResult, QueryResult},
12 vfs::{
13 InitialPages, NativeVfsHandle, SqliteOpenPhase, SqliteTransportHandle, SqliteVfs,
14 SqliteVfsMetrics, SqliteVfsMetricsSnapshot, VfsConfig,
15 fetch_initial_pages_for_registration,
16 },
17 worker::{
18 SqlitePool, SqliteWorkerCloseTimeoutError, SqliteWorkerFatalError, SqliteWorkerHandle,
19 SqliteWorkerResult,
20 },
21};
22
23#[derive(Clone)]
24pub struct NativeDatabaseHandle {
25 vfs: NativeVfsHandle,
26 worker: SqliteWorkerHandle,
27}
28
29pub fn vfs_name_for_actor_database(actor_id: &str, generation: u64) -> String {
30 format!("envoy-sqlite-{actor_id}-g{generation}")
31}
32
33struct GenerationFencedTransport {
34 inner: SqliteTransportHandle,
35 generation: u64,
36}
37
38#[async_trait]
39impl crate::vfs::SqliteTransport for GenerationFencedTransport {
40 async fn get_pages(
41 &self,
42 mut request: protocol::SqliteGetPagesRequest,
43 ) -> Result<protocol::SqliteGetPagesResponse> {
44 request.expected_generation.get_or_insert(self.generation);
45 self.inner.get_pages(request).await
46 }
47
48 async fn commit(
49 &self,
50 mut request: protocol::SqliteCommitRequest,
51 ) -> Result<protocol::SqliteCommitResponse> {
52 request.expected_generation.get_or_insert(self.generation);
53 self.inner.commit(request).await
54 }
55
56 async fn commit_stage_begin(
57 &self,
58 mut request: protocol::SqliteCommitStageBeginRequest,
59 ) -> Result<protocol::SqliteCommitStageBeginResponse> {
60 request.expected_generation.get_or_insert(self.generation);
61 self.inner.commit_stage_begin(request).await
62 }
63
64 async fn commit_stage_segment(
65 &self,
66 mut request: protocol::SqliteCommitStageSegmentRequest,
67 ) -> Result<protocol::SqliteCommitStageSegmentResponse> {
68 request.expected_generation.get_or_insert(self.generation);
69 self.inner.commit_stage_segment(request).await
70 }
71
72 async fn commit_finalize(
73 &self,
74 mut request: protocol::SqliteCommitFinalizeRequest,
75 ) -> Result<protocol::SqliteCommitFinalizeResponse> {
76 request.expected_generation.get_or_insert(self.generation);
77 self.inner.commit_finalize(request).await
78 }
79}
80
81pub async fn open_database_from_transport(
82 transport: SqliteTransportHandle,
83 actor_id: String,
84 generation: u64,
85 rt_handle: Handle,
86 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
87) -> Result<NativeDatabaseHandle> {
88 open_database_from_transport_on_pool(None, transport, actor_id, generation, rt_handle, metrics)
89 .await
90}
91
92pub async fn open_database_from_transport_on_pool(
95 pool: Option<&SqlitePool>,
96 transport: SqliteTransportHandle,
97 actor_id: String,
98 generation: u64,
99 rt_handle: Handle,
100 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
101) -> Result<NativeDatabaseHandle> {
102 let vfs_name = vfs_name_for_actor_database(&actor_id, generation);
103 open_database_from_transport_with_guard(
104 pool, vfs_name, transport, actor_id, generation, rt_handle, metrics, None,
105 )
106 .await
107}
108
109pub async fn open_new_database_from_transport_on_pool(
116 pool: Option<&SqlitePool>,
117 transport: SqliteTransportHandle,
118 actor_id: String,
119 generation: u64,
120 rt_handle: Handle,
121 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
122) -> Result<NativeDatabaseHandle> {
123 let vfs_name = vfs_name_for_actor_database(&actor_id, generation);
124 open_database(
125 pool,
126 vfs_name,
127 transport,
128 actor_id,
129 generation,
130 rt_handle,
131 metrics,
132 None,
133 Preload::Empty,
134 )
135 .await
136}
137
138#[allow(clippy::too_many_arguments)]
142pub async fn open_database_from_transport_with_guard(
143 pool: Option<&SqlitePool>,
144 vfs_name: String,
145 transport: SqliteTransportHandle,
146 actor_id: String,
147 generation: u64,
148 rt_handle: Handle,
149 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
150 guard: Option<crate::guard::ConnectionGuard>,
151) -> Result<NativeDatabaseHandle> {
152 open_database(
153 pool,
154 vfs_name,
155 transport,
156 actor_id,
157 generation,
158 rt_handle,
159 metrics,
160 guard,
161 Preload::Fetch,
162 )
163 .await
164}
165
166enum Preload {
168 Fetch,
170 Empty,
172}
173
174#[allow(clippy::too_many_arguments)]
175async fn open_database(
176 pool: Option<&SqlitePool>,
177 vfs_name: String,
178 transport: SqliteTransportHandle,
179 actor_id: String,
180 generation: u64,
181 rt_handle: Handle,
182 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
183 guard: Option<crate::guard::ConnectionGuard>,
184 preload: Preload,
185) -> Result<NativeDatabaseHandle> {
186 let open_timer = SqliteOpenTimer::new(&metrics);
187 let owned_pool;
190 let pool = match pool {
191 Some(pool) => pool,
192 None => {
193 owned_pool = SqlitePool::new(1, None);
194 &owned_pool
195 }
196 };
197 let mut config = VfsConfig::default();
198 config.page_budget = pool.page_budget().clone();
199 let transport: SqliteTransportHandle = Arc::new(GenerationFencedTransport {
200 inner: transport,
201 generation,
202 });
203 let initial_pages = match preload {
204 Preload::Fetch => {
205 let preload_start = Instant::now();
206 let preload_result = fetch_initial_pages_for_registration(
207 transport.clone(),
208 &actor_id,
209 generation,
210 &config,
211 )
212 .await
213 .map_err(|err| anyhow!("failed to preload sqlite pages: {err}"));
214 let initial_pages = observe_open_phase_result(
215 &metrics,
216 SqliteOpenPhase::InitialPreload,
217 preload_start,
218 preload_result,
219 )?;
220 record_startup_preload_pages(
221 &metrics,
222 u64::from(initial_pages.requested_page_count),
223 initial_pages.pages.len() as u64,
224 );
225 initial_pages
226 }
227 Preload::Empty => InitialPages::default(),
228 };
229
230 let vfs_register_start = Instant::now();
231 let vfs_result = SqliteVfs::register_with_transport_and_initial_pages(
232 &vfs_name,
233 transport,
234 actor_id.clone(),
235 rt_handle,
236 config,
237 initial_pages,
238 metrics.clone(),
239 )
240 .map(Arc::new)
241 .map_err(|err| anyhow!("failed to register sqlite VFS: {err}"));
242 let vfs = observe_open_phase_result(
243 &metrics,
244 SqliteOpenPhase::VfsRegister,
245 vfs_register_start,
246 vfs_result,
247 )?;
248
249 let worker_ready_start = Instant::now();
250 let worker_ready_result: Result<NativeDatabaseHandle> = async {
251 let native_db = NativeDatabaseHandle {
252 worker: SqliteWorkerHandle::start_on_with_guard(
253 pool,
254 vfs.clone(),
255 actor_id,
256 metrics.clone(),
257 guard,
258 )?,
259 vfs,
260 };
261 native_db.initialize().await?;
262 Ok(native_db)
263 }
264 .await;
265 let native_db = observe_open_phase_result(
266 &metrics,
267 SqliteOpenPhase::WorkerReady,
268 worker_ready_start,
269 worker_ready_result,
270 )?;
271 open_timer.finish_success();
272 Ok(native_db)
273}
274
275struct SqliteOpenTimer {
280 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
281 started_at: Instant,
282 finished: bool,
283}
284
285impl SqliteOpenTimer {
286 fn new(metrics: &Option<Arc<dyn SqliteVfsMetrics>>) -> Self {
287 Self {
288 metrics: metrics.clone(),
289 started_at: Instant::now(),
290 finished: false,
291 }
292 }
293
294 fn finish_success(mut self) {
295 observe_open_phase(
296 &self.metrics,
297 SqliteOpenPhase::Total,
298 "success",
299 self.started_at,
300 );
301 self.finished = true;
302 }
303}
304
305impl Drop for SqliteOpenTimer {
306 fn drop(&mut self) {
307 if self.finished {
308 return;
309 }
310
311 observe_open_phase(
312 &self.metrics,
313 SqliteOpenPhase::Total,
314 "error",
315 self.started_at,
316 );
317 }
318}
319
320fn observe_open_phase_result<T, E>(
321 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
322 phase: SqliteOpenPhase,
323 start: Instant,
324 result: std::result::Result<T, E>,
325) -> std::result::Result<T, E> {
326 let outcome = if result.is_ok() { "success" } else { "error" };
327 observe_open_phase(metrics, phase, outcome, start);
328 result
329}
330
331fn observe_open_phase(
332 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
333 phase: SqliteOpenPhase,
334 outcome: &'static str,
335 start: Instant,
336) {
337 if let Some(metrics) = metrics {
338 metrics.observe_open_phase(phase, outcome, start.elapsed().as_nanos() as u64);
339 }
340}
341
342fn record_startup_preload_pages(
343 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
344 requested_pages: u64,
345 loaded_pages: u64,
346) {
347 if let Some(metrics) = metrics {
348 if requested_pages > 0 {
349 metrics.record_startup_preload_pages("requested", requested_pages);
350 }
351 if loaded_pages > 0 {
352 metrics.record_startup_preload_pages("loaded", loaded_pages);
353 }
354 }
355}
356
357impl NativeDatabaseHandle {
358 pub fn new(vfs: NativeVfsHandle, file_name: String) -> Result<Self> {
359 Self::new_with_metrics(vfs, file_name, None)
360 }
361
362 pub fn new_with_metrics(
364 vfs: NativeVfsHandle,
365 file_name: String,
366 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
367 ) -> Result<Self> {
368 Ok(Self {
369 worker: SqliteWorkerHandle::start(vfs.clone(), file_name, metrics)?,
370 vfs,
371 })
372 }
373
374 pub fn new_on_pool(
376 pool: &SqlitePool,
377 vfs: NativeVfsHandle,
378 file_name: String,
379 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
380 ) -> Result<Self> {
381 Ok(Self {
382 worker: SqliteWorkerHandle::start_on(pool, vfs.clone(), file_name, metrics)?,
383 vfs,
384 })
385 }
386
387 pub async fn exec(&self, sql: String) -> Result<QueryResult> {
388 self.check_fatal_error()?;
389 self.map_worker_result(self.worker.exec(sql).await)
390 }
391
392 pub async fn exec_profiled(&self, sql: String) -> Result<SqliteWorkerResult<QueryResult>> {
393 self.check_fatal_error()?;
394 self.map_worker_result(self.worker.exec_profiled(sql).await)
395 }
396
397 pub async fn cork_commits(&self) -> Result<()> {
400 self.check_fatal_error()?;
401 self.map_worker_result(self.worker.cork_commits().await)
402 }
403
404 pub async fn uncork_commits(&self) -> Result<()> {
407 self.check_fatal_error()?;
408 self.map_worker_result(self.worker.uncork_commits().await)
409 }
410
411 pub async fn query(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<QueryResult> {
412 self.execute(sql, params).await.map(|result| QueryResult {
413 columns: result.columns,
414 rows: result.rows,
415 })
416 }
417
418 pub async fn run(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
419 self.execute(sql, params).await.map(|result| ExecResult {
420 changes: result.changes,
421 })
422 }
423
424 pub async fn execute(
425 &self,
426 sql: String,
427 params: Option<Vec<BindParam>>,
428 ) -> Result<ExecuteResult> {
429 self.check_fatal_error()?;
430 self.map_worker_result(self.worker.execute(sql, params).await)
431 }
432
433 pub async fn execute_profiled(
434 &self,
435 sql: String,
436 params: Option<Vec<BindParam>>,
437 ) -> Result<SqliteWorkerResult<ExecuteResult>> {
438 self.check_fatal_error()?;
439 self.map_worker_result(self.worker.execute_profiled(sql, params).await)
440 }
441
442 pub fn set_background(&self, background: bool) {
445 self.worker.set_background(background);
446 }
447
448 pub async fn execute_batch(
451 &self,
452 statements: Vec<(String, Option<Vec<BindParam>>)>,
453 ) -> Result<Vec<ExecuteResult>> {
454 self.check_fatal_error()?;
455 self.map_worker_result(self.worker.execute_batch(statements).await)
456 }
457
458 pub async fn close(&self) -> Result<()> {
459 self.map_worker_close_result(self.worker.close().await)
460 }
461
462 pub async fn close_and_wait(&self) -> Result<()> {
465 self.map_worker_close_result(self.worker.close_and_wait().await)
466 }
467
468 fn map_worker_close_result(&self, result: Result<()>) -> Result<()> {
469 match result {
470 Ok(()) => Ok(()),
471 Err(error)
475 if error
476 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
477 .is_some() =>
478 {
479 Err(error)
480 }
481 Err(error) => Err(self.fatal_error().unwrap_or(error)),
482 }
483 }
484
485 pub async fn wait_for_worker_failure(&self) -> bool {
486 self.worker.wait_for_failure().await
487 }
488
489 pub fn explicit_transaction_open(&self) -> bool {
492 self.worker.explicit_transaction_open()
493 }
494
495 pub fn subscribe_explicit_transaction(&self) -> watch::Receiver<bool> {
498 self.worker.subscribe_explicit_transaction()
499 }
500
501 pub fn take_last_kv_error(&self) -> Option<String> {
502 self.vfs.take_last_error()
503 }
504
505 pub fn clone_fatal_error(&self) -> Option<String> {
506 self.vfs.clone_fatal_error()
507 }
508
509 pub fn read_vfs_file(&self, path: &str) -> Option<Vec<u8>> {
511 self.vfs.read_aux_file(path)
512 }
513
514 pub fn delete_vfs_file(&self, path: &str) {
516 self.vfs.delete_aux_file(path);
517 }
518
519 pub fn sqlite_vfs_metrics(&self) -> SqliteVfsMetricsSnapshot {
520 self.vfs.sqlite_vfs_metrics()
521 }
522
523
524
525
526
527
528
529
530
531 async fn initialize(&self) -> Result<()> {
532 self.map_worker_result(self.worker.wait_ready().await)
533 }
534
535 fn check_fatal_error(&self) -> Result<()> {
536 if let Some(error) = self.fatal_error() {
537 return Err(error);
538 }
539
540 Ok(())
541 }
542
543 fn map_worker_result<T>(&self, result: Result<T>) -> Result<T> {
544 match result {
545 Ok(value) => {
546 self.check_fatal_error()?;
547 Ok(value)
548 }
549 Err(error) if error.is::<crate::vfs::SqliteCommitIndeterminateError>() => Err(error),
550 Err(error)
551 if error.is::<crate::vfs::SqliteTransactionTooLargeError>()
552 && self.fatal_error().is_none() =>
553 {
554 Err(error)
555 }
556 Err(error) => Err(self.fatal_error().unwrap_or(error)),
557 }
558 }
559
560 fn fatal_error(&self) -> Option<anyhow::Error> {
561 self.clone_fatal_error()
562 .map(|message| SqliteWorkerFatalError::new(message).into())
563 }
564}
565
566