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;
8
9use crate::{
10 query::{BindParam, ExecResult, ExecuteResult, QueryResult},
11 vfs::{
12 NativeVfsHandle, SqliteOpenPhase, SqliteTransportHandle, SqliteVfs, SqliteVfsMetrics,
13 SqliteVfsMetricsSnapshot, VfsConfig, VfsPreloadHintSnapshot,
14 fetch_initial_pages_for_registration,
15 },
16 worker::{
17 SqliteWorkerCloseTimeoutError, SqliteWorkerFatalError, SqliteWorkerHandle,
18 SqliteWorkerResult,
19 },
20};
21
22#[derive(Clone)]
23pub struct NativeDatabaseHandle {
24 vfs: NativeVfsHandle,
25 worker: SqliteWorkerHandle,
26}
27
28pub fn vfs_name_for_actor_database(actor_id: &str, generation: u64) -> String {
29 format!("envoy-sqlite-{actor_id}-g{generation}")
30}
31
32struct GenerationFencedTransport {
33 inner: SqliteTransportHandle,
34 generation: u64,
35}
36
37#[async_trait]
38impl crate::vfs::SqliteTransport for GenerationFencedTransport {
39 async fn get_pages(
40 &self,
41 mut request: protocol::SqliteGetPagesRequest,
42 ) -> Result<protocol::SqliteGetPagesResponse> {
43 request.expected_generation.get_or_insert(self.generation);
44 self.inner.get_pages(request).await
45 }
46
47 async fn commit(
48 &self,
49 mut request: protocol::SqliteCommitRequest,
50 ) -> Result<protocol::SqliteCommitResponse> {
51 request.expected_generation.get_or_insert(self.generation);
52 self.inner.commit(request).await
53 }
54
55 async fn commit_stage_begin(
56 &self,
57 mut request: protocol::SqliteCommitStageBeginRequest,
58 ) -> Result<protocol::SqliteCommitStageBeginResponse> {
59 request.expected_generation.get_or_insert(self.generation);
60 self.inner.commit_stage_begin(request).await
61 }
62
63 async fn commit_stage_segment(
64 &self,
65 mut request: protocol::SqliteCommitStageSegmentRequest,
66 ) -> Result<protocol::SqliteCommitStageSegmentResponse> {
67 request.expected_generation.get_or_insert(self.generation);
68 self.inner.commit_stage_segment(request).await
69 }
70
71 async fn commit_finalize(
72 &self,
73 mut request: protocol::SqliteCommitFinalizeRequest,
74 ) -> Result<protocol::SqliteCommitFinalizeResponse> {
75 request.expected_generation.get_or_insert(self.generation);
76 self.inner.commit_finalize(request).await
77 }
78}
79
80pub async fn open_database_from_transport(
81 transport: SqliteTransportHandle,
82 actor_id: String,
83 generation: u64,
84 rt_handle: Handle,
85 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
86) -> Result<NativeDatabaseHandle> {
87 let open_timer = SqliteOpenTimer::new(&metrics);
88 let vfs_name = vfs_name_for_actor_database(&actor_id, generation);
89 let config = VfsConfig::default();
90 let transport: SqliteTransportHandle = Arc::new(GenerationFencedTransport {
91 inner: transport,
92 generation,
93 });
94 let preload_start = Instant::now();
95 let preload_result =
96 fetch_initial_pages_for_registration(transport.clone(), &actor_id, generation, &config)
97 .await
98 .map_err(|err| anyhow!("failed to preload sqlite pages: {err}"));
99 let initial_pages = observe_open_phase_result(
100 &metrics,
101 SqliteOpenPhase::InitialPreload,
102 preload_start,
103 preload_result,
104 )?;
105 record_startup_preload_pages(
106 &metrics,
107 u64::from(initial_pages.requested_page_count),
108 initial_pages.pages.len() as u64,
109 );
110
111 let vfs_register_start = Instant::now();
112 let vfs_result = SqliteVfs::register_with_transport_and_initial_pages(
113 &vfs_name,
114 transport,
115 actor_id.clone(),
116 rt_handle,
117 config,
118 initial_pages,
119 metrics.clone(),
120 )
121 .map(Arc::new)
122 .map_err(|err| anyhow!("failed to register sqlite VFS: {err}"));
123 let vfs = observe_open_phase_result(
124 &metrics,
125 SqliteOpenPhase::VfsRegister,
126 vfs_register_start,
127 vfs_result,
128 )?;
129
130 let worker_ready_start = Instant::now();
131 let worker_ready_result: Result<NativeDatabaseHandle> = async {
132 let native_db = NativeDatabaseHandle::new_with_metrics(vfs, actor_id, metrics.clone())?;
133 native_db.initialize().await?;
134 Ok(native_db)
135 }
136 .await;
137 let native_db = observe_open_phase_result(
138 &metrics,
139 SqliteOpenPhase::WorkerReady,
140 worker_ready_start,
141 worker_ready_result,
142 )?;
143 open_timer.finish_success();
144 Ok(native_db)
145}
146
147struct SqliteOpenTimer {
152 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
153 started_at: Instant,
154 finished: bool,
155}
156
157impl SqliteOpenTimer {
158 fn new(metrics: &Option<Arc<dyn SqliteVfsMetrics>>) -> Self {
159 Self {
160 metrics: metrics.clone(),
161 started_at: Instant::now(),
162 finished: false,
163 }
164 }
165
166 fn finish_success(mut self) {
167 observe_open_phase(
168 &self.metrics,
169 SqliteOpenPhase::Total,
170 "success",
171 self.started_at,
172 );
173 self.finished = true;
174 }
175}
176
177impl Drop for SqliteOpenTimer {
178 fn drop(&mut self) {
179 if self.finished {
180 return;
181 }
182
183 observe_open_phase(
184 &self.metrics,
185 SqliteOpenPhase::Total,
186 "error",
187 self.started_at,
188 );
189 }
190}
191
192fn observe_open_phase_result<T, E>(
193 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
194 phase: SqliteOpenPhase,
195 start: Instant,
196 result: std::result::Result<T, E>,
197) -> std::result::Result<T, E> {
198 let outcome = if result.is_ok() { "success" } else { "error" };
199 observe_open_phase(metrics, phase, outcome, start);
200 result
201}
202
203fn observe_open_phase(
204 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
205 phase: SqliteOpenPhase,
206 outcome: &'static str,
207 start: Instant,
208) {
209 if let Some(metrics) = metrics {
210 metrics.observe_open_phase(phase, outcome, start.elapsed().as_nanos() as u64);
211 }
212}
213
214fn record_startup_preload_pages(
215 metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
216 requested_pages: u64,
217 loaded_pages: u64,
218) {
219 if let Some(metrics) = metrics {
220 if requested_pages > 0 {
221 metrics.record_startup_preload_pages("requested", requested_pages);
222 }
223 if loaded_pages > 0 {
224 metrics.record_startup_preload_pages("loaded", loaded_pages);
225 }
226 }
227}
228
229impl NativeDatabaseHandle {
230 pub fn new(vfs: NativeVfsHandle, file_name: String) -> Result<Self> {
231 Self::new_with_metrics(vfs, file_name, None)
232 }
233
234 pub fn new_with_metrics(
235 vfs: NativeVfsHandle,
236 file_name: String,
237 metrics: Option<Arc<dyn SqliteVfsMetrics>>,
238 ) -> Result<Self> {
239 Ok(Self {
240 worker: SqliteWorkerHandle::start(vfs.clone(), file_name, metrics)?,
241 vfs,
242 })
243 }
244
245 pub async fn exec(&self, sql: String) -> Result<QueryResult> {
246 self.check_fatal_error()?;
247 self.map_worker_result(self.worker.exec(sql).await)
248 }
249
250 pub async fn exec_profiled(&self, sql: String) -> Result<SqliteWorkerResult<QueryResult>> {
251 self.check_fatal_error()?;
252 self.map_worker_result(self.worker.exec_profiled(sql).await)
253 }
254
255 pub async fn query(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<QueryResult> {
256 self.execute(sql, params).await.map(|result| QueryResult {
257 columns: result.columns,
258 rows: result.rows,
259 })
260 }
261
262 pub async fn run(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
263 self.execute(sql, params).await.map(|result| ExecResult {
264 changes: result.changes,
265 })
266 }
267
268 pub async fn execute(
269 &self,
270 sql: String,
271 params: Option<Vec<BindParam>>,
272 ) -> Result<ExecuteResult> {
273 self.check_fatal_error()?;
274 self.map_worker_result(self.worker.execute(sql, params).await)
275 }
276
277 pub async fn execute_profiled(
278 &self,
279 sql: String,
280 params: Option<Vec<BindParam>>,
281 ) -> Result<SqliteWorkerResult<ExecuteResult>> {
282 self.check_fatal_error()?;
283 self.map_worker_result(self.worker.execute_profiled(sql, params).await)
284 }
285
286 pub async fn close(&self) -> Result<()> {
287 self.map_worker_close_result(self.worker.close().await)
288 }
289
290 pub async fn close_and_wait(&self) -> Result<()> {
293 self.map_worker_close_result(self.worker.close_and_wait().await)
294 }
295
296 fn map_worker_close_result(&self, result: Result<()>) -> Result<()> {
297 match result {
298 Ok(()) => Ok(()),
299 Err(error)
303 if error
304 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
305 .is_some() =>
306 {
307 Err(error)
308 }
309 Err(error) => Err(self.fatal_error().unwrap_or(error)),
310 }
311 }
312
313 pub async fn wait_for_worker_failure(&self) -> bool {
314 self.worker.wait_for_failure().await
315 }
316
317 pub fn take_last_kv_error(&self) -> Option<String> {
318 self.vfs.take_last_error()
319 }
320
321 pub fn clone_fatal_error(&self) -> Option<String> {
322 self.vfs.clone_fatal_error()
323 }
324
325 pub fn snapshot_preload_hints(&self) -> VfsPreloadHintSnapshot {
326 self.vfs.snapshot_preload_hints()
327 }
328
329 pub fn read_vfs_file(&self, path: &str) -> Option<Vec<u8>> {
331 self.vfs.read_aux_file(path)
332 }
333
334 pub fn delete_vfs_file(&self, path: &str) {
336 self.vfs.delete_aux_file(path);
337 }
338
339 pub fn sqlite_vfs_metrics(&self) -> SqliteVfsMetricsSnapshot {
340 self.vfs.sqlite_vfs_metrics()
341 }
342
343 #[cfg(test)]
344 pub(crate) async fn pause_for_test(&self) -> tokio::sync::oneshot::Sender<()> {
345 self.worker.pause_for_test().await
346 }
347
348 #[cfg(test)]
349 pub(crate) fn is_closing_for_test(&self) -> bool {
350 self.worker.is_closing_for_test()
351 }
352
353 #[cfg(test)]
354 pub(crate) async fn panic_worker_for_test(&self) {
355 self.worker.panic_for_test().await
356 }
357
358 async fn initialize(&self) -> Result<()> {
359 self.map_worker_result(self.worker.wait_ready().await)
360 }
361
362 fn check_fatal_error(&self) -> Result<()> {
363 if let Some(error) = self.fatal_error() {
364 return Err(error);
365 }
366
367 Ok(())
368 }
369
370 fn map_worker_result<T>(&self, result: Result<T>) -> Result<T> {
371 match result {
372 Ok(value) => {
373 self.check_fatal_error()?;
374 Ok(value)
375 }
376 Err(error) => Err(self.fatal_error().unwrap_or(error)),
377 }
378 }
379
380 fn fatal_error(&self) -> Option<anyhow::Error> {
381 self.clone_fatal_error()
382 .map(|message| SqliteWorkerFatalError::new(message).into())
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::vfs_name_for_actor_database;
389
390 #[test]
391 fn vfs_name_includes_actor_and_generation() {
392 assert_eq!(
393 vfs_name_for_actor_database("actor-123", 42),
394 "envoy-sqlite-actor-123-g42"
395 );
396 }
397}