1use futures::Stream;
2use std::fs;
3use std::path::Path;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::sync::LazyLock;
7use std::thread;
8use std::time::Instant;
9use tokio::sync::{mpsc, oneshot, watch};
10
11struct CancellationGuard(Option<oneshot::Sender<()>>);
13
14impl Drop for CancellationGuard {
15 fn drop(&mut self) {
16 if let Some(tx) = self.0.take() {
17 let _ = tx.send(());
18 }
19 }
20}
21
22use tokio_stream::wrappers::ReceiverStream;
23use tonic::transport::{Identity, ServerTlsConfig};
24use tonic::{Request, Response, Status, transport::Server};
25use umadb_core::db::{
26 DEFAULT_DB_FILENAME, DEFAULT_PAGE_SIZE, UmaDb, clone_dcb_error, is_integrity_error,
27 is_request_idempotent, read_conditional, shadow_for_batch_abort,
28};
29use umadb_core::mvcc::Mvcc;
30use umadb_dcb::{
31 DcbAppendCondition, DcbEvent, DcbQuery, DcbResult, DcbSequencedEvent, DcbError, TrackingInfo,
32};
33
34use tokio::runtime::Runtime;
35use tonic::codegen::http;
36use tonic::transport::server::TcpIncoming;
37use umadb_core::common::Position;
38
39use std::convert::Infallible;
40use std::future::Future;
41use std::task::{Context, Poll};
42use tonic::server::NamedService;
43use umadb_proto::status_from_dcb_error;
44
45#[derive(Clone, Debug)]
47pub struct PathRewriterService<S> {
48 inner: S,
49}
50
51impl<S> tower::Service<http::Request<tonic::body::Body>> for PathRewriterService<S>
52where
53 S: tower::Service<
54 http::Request<tonic::body::Body>,
55 Response = http::Response<tonic::body::Body>,
56 Error = Infallible,
57 > + Clone
58 + Send
59 + 'static,
60 S::Future: Send + 'static,
61{
62 type Response = S::Response;
63 type Error = S::Error;
64 type Future =
65 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
66
67 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
68 self.inner.poll_ready(cx)
69 }
70
71 fn call(&mut self, mut req: http::Request<tonic::body::Body>) -> Self::Future {
72 let uri = req.uri().clone();
73 let path = uri.path();
74
75 if path.starts_with("/umadb.UmaDBService/") {
77 let new_path_str = path.replace("/umadb.UmaDBService/", "/umadb.v1.DCB/");
78
79 let new_uri = if let (Some(scheme), Some(authority)) = (uri.scheme(), uri.authority()) {
82 http::Uri::builder()
84 .scheme(scheme.clone())
85 .authority(authority.clone())
86 .path_and_query(new_path_str.as_str())
87 .build()
88 .ok() } else {
90 new_path_str.parse::<http::Uri>().ok()
93 };
94
95 if let Some(final_uri) = new_uri {
96 *req.uri_mut() = final_uri;
97 } else {
98 eprintln!("failed to construct valid URI for path: {}", path);
99 }
100 }
101
102 let fut = self.inner.call(req);
103 Box::pin(fut)
104 }
105}
106
107impl<S: NamedService> NamedService for PathRewriterService<S> {
109 const NAME: &'static str = S::NAME;
110}
111
112#[derive(Clone, Debug)]
113pub struct PathRewriterLayer;
114
115impl<S> tower::Layer<S> for PathRewriterLayer
116where
117 S: tower::Service<
118 http::Request<tonic::body::Body>,
119 Response = http::Response<tonic::body::Body>,
120 Error = Infallible,
121 > + Clone
122 + Send
123 + 'static,
124 S::Future: Send + 'static,
125{
126 type Service = PathRewriterService<S>;
127
128 fn layer(&self, inner: S) -> Self::Service {
129 PathRewriterService { inner }
130 }
131}
132
133static START_TIME: LazyLock<Instant> = LazyLock::new(Instant::now);
134
135const APPEND_BATCH_MAX_EVENTS: usize = 2000;
136const READ_RESPONSE_BATCH_SIZE_DEFAULT: u32 = 100;
137const READ_RESPONSE_BATCH_SIZE_MAX: u32 = 5000;
138
139#[derive(Clone, Debug)]
141pub struct ServerTlsOptions {
142 pub cert_pem: Vec<u8>,
143 pub key_pem: Vec<u8>,
144}
145
146pub fn uptime() -> std::time::Duration {
147 START_TIME.elapsed()
148}
149
150fn build_server_builder_with_options(tls: Option<ServerTlsOptions>) -> Server {
151 use std::time::Duration;
152 let mut server_builder = Server::builder()
153 .http2_keepalive_interval(Some(Duration::from_secs(5)))
154 .http2_keepalive_timeout(Some(Duration::from_secs(10)))
155 .initial_stream_window_size(Some(4 * 1024 * 1024))
156 .initial_connection_window_size(Some(8 * 1024 * 1024))
157 .tcp_nodelay(true)
158 .concurrency_limit_per_connection(1024);
159
160 if let Some(opts) = tls {
161 let identity = Identity::from_pem(opts.cert_pem, opts.key_pem);
162 server_builder = server_builder
163 .tls_config(ServerTlsConfig::new().identity(identity))
164 .expect("failed to apply TLS config");
165 }
166
167 server_builder
168}
169
170pub async fn start_server<P: AsRef<Path> + Send + 'static>(
172 path: P,
173 addr: &str,
174 shutdown_rx: oneshot::Receiver<()>,
175) -> Result<(), Box<dyn std::error::Error>> {
176 start_server_internal(path, addr, shutdown_rx, None, None).await
177}
178
179pub async fn start_server_secure<P: AsRef<Path> + Send + 'static>(
181 path: P,
182 addr: &str,
183 shutdown_rx: oneshot::Receiver<()>,
184 cert_pem: Vec<u8>,
185 key_pem: Vec<u8>,
186) -> Result<(), Box<dyn std::error::Error>> {
187 let tls = ServerTlsOptions { cert_pem, key_pem };
188 start_server_internal(path, addr, shutdown_rx, Some(tls), None).await
189}
190
191pub async fn start_server_secure_from_files<
193 P: AsRef<Path> + Send + 'static,
194 CP: AsRef<Path>,
195 KP: AsRef<Path>,
196>(
197 path: P,
198 addr: &str,
199 shutdown_rx: oneshot::Receiver<()>,
200 cert_path: CP,
201 key_path: KP,
202) -> Result<(), Box<dyn std::error::Error>> {
203 let cert_path_ref = cert_path.as_ref();
204 let cert_pem = fs::read(cert_path_ref).map_err(|e| -> Box<dyn std::error::Error> {
205 format!(
206 "failed to open TLS certificate file '{}': {}",
207 cert_path_ref.display(),
208 e
209 )
210 .into()
211 })?;
212
213 let key_path_ref = key_path.as_ref();
214 let key_pem = fs::read(key_path_ref).map_err(|e| -> Box<dyn std::error::Error> {
215 format!(
216 "failed to open TLS key file '{}': {}",
217 key_path_ref.display(),
218 e
219 )
220 .into()
221 })?;
222 start_server_secure(path, addr, shutdown_rx, cert_pem, key_pem).await
223}
224
225pub async fn start_server_with_api_key<P: AsRef<Path> + Send + 'static>(
227 path: P,
228 addr: &str,
229 shutdown_rx: oneshot::Receiver<()>,
230 api_key: String,
231) -> Result<(), Box<dyn std::error::Error>> {
232 start_server_internal(path, addr, shutdown_rx, None, Some(api_key)).await
233}
234
235pub async fn start_server_secure_with_api_key<P: AsRef<Path> + Send + 'static>(
237 path: P,
238 addr: &str,
239 shutdown_rx: oneshot::Receiver<()>,
240 cert_pem: Vec<u8>,
241 key_pem: Vec<u8>,
242 api_key: String,
243) -> Result<(), Box<dyn std::error::Error>> {
244 let tls = ServerTlsOptions { cert_pem, key_pem };
245 start_server_internal(path, addr, shutdown_rx, Some(tls), Some(api_key)).await
246}
247
248pub async fn start_server_secure_from_files_with_api_key<
250 P: AsRef<Path> + Send + 'static,
251 CP: AsRef<Path>,
252 KP: AsRef<Path>,
253>(
254 path: P,
255 addr: &str,
256 shutdown_rx: oneshot::Receiver<()>,
257 cert_path: CP,
258 key_path: KP,
259 api_key: String,
260) -> Result<(), Box<dyn std::error::Error>> {
261 let cert_path_ref = cert_path.as_ref();
262 let cert_pem = fs::read(cert_path_ref).map_err(|e| -> Box<dyn std::error::Error> {
263 format!(
264 "failed to open TLS certificate file '{}': {}",
265 cert_path_ref.display(),
266 e
267 )
268 .into()
269 })?;
270
271 let key_path_ref = key_path.as_ref();
272 let key_pem = fs::read(key_path_ref).map_err(|e| -> Box<dyn std::error::Error> {
273 format!(
274 "failed to open TLS key file '{}': {}",
275 key_path_ref.display(),
276 e
277 )
278 .into()
279 })?;
280 start_server_secure_with_api_key(path, addr, shutdown_rx, cert_pem, key_pem, api_key).await
281}
282
283async fn start_server_internal<P: AsRef<Path> + Send + 'static>(
284 path: P,
285 addr: &str,
286 shutdown_rx: oneshot::Receiver<()>,
287 tls: Option<ServerTlsOptions>,
288 api_key: Option<String>,
289) -> Result<(), Box<dyn std::error::Error>> {
290 let addr = addr.parse()?;
291 let incoming = match TcpIncoming::bind(addr) {
293 Ok(incoming) => incoming,
294 Err(err) => {
295 return Err(Box::new(DcbError::InitializationError(format!(
296 "failed to bind to address {}: {}",
297 addr, err
298 ))));
299 }
300 }
301 .with_nodelay(Some(true))
302 .with_keepalive(Some(std::time::Duration::from_secs(60)));
303
304 let (srv_shutdown_tx, srv_shutdown_rx) = watch::channel(false);
306 let dcb_server =
307 match DcbServer::new(path.as_ref().to_owned(), srv_shutdown_rx, api_key.clone()) {
308 Ok(server) => server,
309 Err(err) => {
310 return Err(Box::new(err));
311 }
312 };
313
314 println!(
315 "UmaDB has {:?} events",
316 dcb_server.request_handler.head().unwrap_or(Some(0)).unwrap_or(0)
317 );
318 let tls_mode_display_str = if tls.is_some() {
319 "with TLS"
320 } else {
321 "without TLS"
322 };
323
324 let api_key_display_str = if api_key.is_some() {
325 "with API key"
326 } else {
327 "without API key"
328 };
329
330 use tonic_health::ServingStatus; let (health_reporter, health_service) = tonic_health::server::health_reporter();
333 health_reporter
335 .set_service_status("", ServingStatus::Serving)
336 .await;
337 health_reporter
338 .set_service_status("umadb.v1.DCB", ServingStatus::Serving)
339 .await;
340 let health_reporter_for_shutdown = health_reporter.clone();
341
342 let mut builder = build_server_builder_with_options(tls)
344 .layer(PathRewriterLayer)
345 .add_service(health_service);
346
347 builder = builder.add_service(dcb_server.into_service());
349 let router = builder;
350
351 println!("UmaDB is listening on {addr} ({tls_mode_display_str}, {api_key_display_str})");
352 println!("UmaDB started in {:?}", uptime());
353 router
355 .serve_with_incoming_shutdown(incoming, async move {
356 let _ = shutdown_rx.await;
358 let _ = health_reporter_for_shutdown
360 .set_service_status("", ServingStatus::NotServing)
361 .await;
362 let _ = health_reporter_for_shutdown
363 .set_service_status("umadb.v1.DCB", ServingStatus::NotServing)
364 .await;
365 let _ = srv_shutdown_tx.send(true);
367 println!("UmaDB server shutdown complete");
368 })
369 .await?;
370
371 Ok(())
372}
373
374pub struct DcbServer {
376 pub(crate) request_handler: RequestHandler,
377 shutdown_watch_rx: watch::Receiver<bool>,
378 api_key: Option<String>,
379}
380
381impl DcbServer {
382 pub fn new<P: AsRef<Path> + Send + 'static>(
383 path: P,
384 shutdown_rx: watch::Receiver<bool>,
385 api_key: Option<String>,
386 ) -> DcbResult<Self> {
387 let command_handler = RequestHandler::new(path)?;
388 Ok(Self {
389 request_handler: command_handler,
390 shutdown_watch_rx: shutdown_rx,
391 api_key,
392 })
393 }
394
395 pub fn into_service(self) -> umadb_proto::v1::dcb_server::DcbServer<Self> {
396 umadb_proto::v1::dcb_server::DcbServer::new(self)
397 }
398
399 fn enforce_api_key(&self, metadata: &tonic::metadata::MetadataMap) -> Result<(), Status> {
400 if let Some(expected) = &self.api_key {
401 let auth = metadata.get("authorization");
402 let expected_val = format!("Bearer {}", expected);
403 let ok = auth
404 .and_then(|m| m.to_str().ok())
405 .map(|s| s == expected_val)
406 .unwrap_or(false);
407 if !ok {
408 return Err(status_from_dcb_error(DcbError::AuthenticationError(
409 "missing or invalid API key".to_string(),
410 )));
411 }
412 }
413 Ok(())
414 }
415}
416
417#[tonic::async_trait]
418impl umadb_proto::v1::dcb_server::Dcb for DcbServer {
419 type ReadStream =
420 Pin<Box<dyn Stream<Item = Result<umadb_proto::v1::ReadResponse, Status>> + Send + 'static>>;
421 type SubscribeStream = Pin<
422 Box<dyn Stream<Item = Result<umadb_proto::v1::SubscribeResponse, Status>> + Send + 'static>,
423 >;
424
425 async fn read(
426 &self,
427 request: Request<umadb_proto::v1::ReadRequest>,
428 ) -> Result<Response<Self::ReadStream>, Status> {
429 self.enforce_api_key(request.metadata())?;
431 let read_request = request.into_inner();
432
433 let mut query: Option<DcbQuery> = read_request.query.map(|q| q.into());
435 let start = read_request.start;
436 let backwards = read_request.backwards.unwrap_or(false);
437 let limit = read_request.limit;
438 let batch_size = read_request
440 .batch_size
441 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
442 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
443 let subscribe = read_request.subscribe.unwrap_or(false);
444
445 let (tx, rx) = mpsc::channel(2048);
447 let request_handler = self.request_handler.clone();
449 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
451
452 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
453 let cancel_signal_for_task = cancel_signal.clone();
454
455 tokio::spawn(async move {
457 let query_clone = query.take();
459 let mut next_start = start;
460 let mut sent_any = false;
461 let mut remaining_limit = limit.unwrap_or(u32::MAX);
462 let mut head_rx = request_handler.watch_head();
465 let captured_head = if !subscribe {
467 let handler = request_handler.clone();
468 tokio::task::spawn_blocking(move || handler.head())
469 .await
470 .unwrap_or(Ok(None))
471 .unwrap_or(None)
472 } else {
473 None
474 };
475 loop {
476 if tx.is_closed() || *shutdown_watch_rx.borrow() {
480 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
481 break;
482 }
483 let read_limit = remaining_limit.min(batch_size);
485 if subscribe && limit.is_some() && remaining_limit == 0 {
487 break;
488 }
489 let handler = request_handler.clone();
490 let query_val = query_clone.clone();
491 let limit_val = Some(read_limit);
492 let cancel_for_blocking = cancel_signal_for_task.clone();
493 let mut blocking_handle = tokio::task::spawn_blocking(move || {
494 handler.read(query_val, next_start, backwards, limit_val, Some(cancel_for_blocking))
495 });
496
497 let res = tokio::select! {
498 res = &mut blocking_handle => {
499 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
500 }
501 _ = tx.closed() => {
502 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
503 let _ = blocking_handle.await;
505 break;
506 }
507 _ = shutdown_watch_rx.changed() => {
508 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
509 let _ = blocking_handle.await;
510 break;
511 }
512 };
513
514 match res {
515 Ok((dcb_sequenced_events, head)) => {
516 let original_len = dcb_sequenced_events.len();
518
519 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
521 dcb_sequenced_events
522 .into_iter()
523 .filter(|e| {
524 if let Some(h) = captured_head {
525 e.position <= h
526 } else {
527 true
528 }
529 })
530 .map(umadb_proto::v1::SequencedEvent::from)
531 .collect();
532
533 let reached_captured_head = if captured_head.is_some() {
534 sequenced_event_protos.len() < original_len
536 } else {
537 false
538 };
539
540 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
545 let head_to_send = if subscribe {
546 head
547 } else if limit.is_none() {
548 captured_head
549 } else {
550 last_event_position.or(head)
551 };
552
553 if sequenced_event_protos.is_empty() {
554 if !sent_any {
556 let response = umadb_proto::v1::ReadResponse {
557 events: vec![],
558 head: head_to_send,
559 };
560 let _ = tx.send(Ok(response)).await;
561 }
562 if subscribe {
564 tokio::select! {
566 _ = head_rx.changed() => {},
567 _ = shutdown_watch_rx.changed() => {},
568 _ = tx.closed() => {},
569 }
570 continue;
571 }
572 break;
573 }
574
575 let sent_count = sequenced_event_protos.len() as u32;
577
578 let response = umadb_proto::v1::ReadResponse {
579 events: sequenced_event_protos,
580 head: head_to_send,
581 };
582
583 if tx.send(Ok(response)).await.is_err() {
584 break;
585 }
586 sent_any = true;
587
588 next_start =
590 last_event_position.map(|p| if !backwards { p + 1 } else { p - 1 });
591
592 if reached_captured_head && !subscribe {
595 break;
596 }
597
598 if limit.is_some() {
600 if remaining_limit <= sent_count {
601 remaining_limit = 0;
602 } else {
603 remaining_limit -= sent_count;
604 }
605 if remaining_limit == 0 {
606 break;
607 }
608 }
609
610 tokio::task::yield_now().await;
612 }
613 Err(e) => {
614 if matches!(e, DcbError::CancelledByUser()) {
615 } else {
617 let _ = tx.send(Err(status_from_dcb_error(e))).await;
618 }
619 break;
620 }
621 }
622 }
623 });
624
625 Ok(Response::new(
627 Box::pin(ReceiverStream::new(rx)) as Self::ReadStream
628 ))
629 }
630
631 async fn subscribe(
632 &self,
633 request: Request<umadb_proto::v1::SubscribeRequest>,
634 ) -> Result<Response<Self::SubscribeStream>, Status> {
635 self.enforce_api_key(request.metadata())?;
637 let subscribe_request = request.into_inner();
638
639 let mut query: Option<DcbQuery> = subscribe_request.query.map(|q| q.into());
641 let after = subscribe_request.after;
642 let batch_size = subscribe_request
644 .batch_size
645 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
646 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
647
648 let (tx, rx) = mpsc::channel(2048);
650 let request_handler = self.request_handler.clone();
652 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
654
655 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
656 let cancel_signal_for_task = cancel_signal.clone();
657
658 tokio::spawn(async move {
660 let query_clone = query.take();
662 let mut next_after = after.map(|a| a.saturating_add(1));
664 let mut head_rx = request_handler.watch_head();
666
667 loop {
668 if tx.is_closed() || *shutdown_watch_rx.borrow() {
672 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
673 break;
674 }
675
676 let handler = request_handler.clone();
677 let query_val = query_clone.clone();
678 let batch_size_val = Some(batch_size);
679 let cancel_for_blocking = cancel_signal_for_task.clone();
680 let mut blocking_handle = tokio::task::spawn_blocking(move || {
681 handler.read(query_val, next_after, false, batch_size_val, Some(cancel_for_blocking))
682 });
683
684 let res = tokio::select! {
685 res = &mut blocking_handle => {
686 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
687 }
688 _ = tx.closed() => {
689 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
690 let _ = blocking_handle.await;
691 break;
692 }
693 _ = shutdown_watch_rx.changed() => {
694 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
695 let _ = blocking_handle.await;
696 break;
697 }
698 };
699
700 match res {
701 Ok((dcb_sequenced_events, _head)) => {
702 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
704 dcb_sequenced_events
705 .into_iter()
706 .map(umadb_proto::v1::SequencedEvent::from)
707 .collect();
708
709 if sequenced_event_protos.is_empty() {
710 tokio::select! {
712 _ = head_rx.changed() => {},
713 _ = shutdown_watch_rx.changed() => {},
714 _ = tx.closed() => {},
715 }
716 continue;
717 }
718
719 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
720
721 let response = umadb_proto::v1::SubscribeResponse {
722 events: sequenced_event_protos,
723 };
724
725 if tx.send(Ok(response)).await.is_err() {
726 break;
727 }
728
729 next_after = last_event_position.map(|p| p.saturating_add(1));
732
733 tokio::task::yield_now().await;
735 }
736 Err(e) => {
737 if matches!(e, DcbError::CancelledByUser()) {
738 } else {
740 let _ = tx.send(Err(status_from_dcb_error(e))).await;
741 }
742 break;
743 }
744 }
745 }
746 });
747
748 Ok(Response::new(
750 Box::pin(ReceiverStream::new(rx)) as Self::SubscribeStream
751 ))
752 }
753
754 async fn append(
755 &self,
756 request: Request<umadb_proto::v1::AppendRequest>,
757 ) -> Result<Response<umadb_proto::v1::AppendResponse>, Status> {
758 self.enforce_api_key(request.metadata())?;
760 let req = request.into_inner();
761
762 let events: Vec<DcbEvent> = match req.events.into_iter().map(|e| e.try_into()).collect() {
764 Ok(events) => events,
765 Err(e) => {
766 return Err(status_from_dcb_error(e));
767 }
768 };
769 let condition = req.condition.map(|c| c.into());
770
771 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
772 let cancel_signal_for_task = cancel_signal.clone();
773
774 let (cancel_tx, cancel_rx) = oneshot::channel();
776 let _guard = CancellationGuard(Some(cancel_tx));
777
778 let cancel_signal_for_monitoring = cancel_signal.clone();
780 tokio::spawn(async move {
781 let _ = cancel_rx.await;
784 cancel_signal_for_monitoring.store(true, std::sync::atomic::Ordering::SeqCst);
785 });
786
787 let res = self.request_handler.append(
789 events,
790 condition,
791 req.tracking_info.map(|t| TrackingInfo {
792 source: t.source,
793 position: t.position,
794 }),
795 Some(cancel_signal_for_task.clone()),
796 ).await;
797
798 match res {
799 Ok(position) => Ok(Response::new(umadb_proto::v1::AppendResponse { position })),
800 Err(e) => Err(status_from_dcb_error(e)),
801 }
802 }
803
804 async fn head(
805 &self,
806 request: Request<umadb_proto::v1::HeadRequest>,
807 ) -> Result<Response<umadb_proto::v1::HeadResponse>, Status> {
808 self.enforce_api_key(request.metadata())?;
810 match self.request_handler.head() {
812 Ok(position) => {
813 Ok(Response::new(umadb_proto::v1::HeadResponse { position }))
815 }
816 Err(e) => Err(status_from_dcb_error(e)),
817 }
818 }
819
820 async fn get_tracking_info(
821 &self,
822 request: Request<umadb_proto::v1::TrackingRequest>,
823 ) -> Result<Response<umadb_proto::v1::TrackingResponse>, Status> {
824 self.enforce_api_key(request.metadata())?;
826 let req = request.into_inner();
827 match self.request_handler.get_tracking_info(req.source) {
828 Ok(position) => Ok(Response::new(umadb_proto::v1::TrackingResponse {
829 position,
830 })),
831 Err(e) => Err(status_from_dcb_error(e)),
832 }
833 }
834}
835
836enum WriterRequest {
838 Append {
839 events: Vec<DcbEvent>,
840 condition: Option<DcbAppendCondition>,
841 tracking_info: Option<TrackingInfo>,
842 response_tx: oneshot::Sender<DcbResult<u64>>,
843 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
844 },
845 Shutdown,
846}
847
848struct RequestHandler {
850 mvcc: Arc<Mvcc>,
851 head_watch_tx: watch::Sender<Option<u64>>,
852 writer_request_tx: mpsc::Sender<WriterRequest>,
853}
854
855impl RequestHandler {
856 fn new<P: AsRef<Path> + Send + 'static>(path: P) -> DcbResult<Self> {
857 let (request_tx, mut request_rx) = mpsc::channel::<WriterRequest>(1024);
859
860 let p = path.as_ref();
862 let file_path = if p.is_dir() {
863 p.join(DEFAULT_DB_FILENAME)
864 } else {
865 p.to_path_buf()
866 };
867 let mvcc = Arc::new(Mvcc::new(&file_path, DEFAULT_PAGE_SIZE, false)?);
868
869 let init_head = {
871 let header_page = mvcc.get_latest_header_page()?;
872 let header = header_page.as_header_node()?;
873 let last = header.next_position.0.saturating_sub(1);
874 if last == 0 { None } else { Some(last) }
875 };
876 let (head_tx, _head_rx) = watch::channel::<Option<u64>>(init_head);
877
878 let mvcc_for_writer = mvcc.clone();
880 let head_tx_writer = head_tx.clone();
881 thread::spawn(move || {
882 let db = UmaDb::from_arc(mvcc_for_writer);
883
884 let rt = Runtime::new().unwrap();
886
887 rt.block_on(async {
889 while let Some(request) = request_rx.recv().await {
890 match request {
891 WriterRequest::Append {
892 events,
893 condition,
894 tracking_info,
895 response_tx,
896 cancel,
897 } => {
898 let mut total_events = 0;
903 total_events += events.len();
904 let mvcc = &db.mvcc;
907 let mut writer = match mvcc.writer() {
908 Ok(writer) => writer,
909 Err(err) => {
910 let _ = response_tx.send(Err(err));
911 continue;
912 }
913 };
914
915 let mut responders: Vec<oneshot::Sender<DcbResult<u64>>> = Vec::new();
916 let mut results: Vec<DcbResult<u64>> = Vec::new();
917
918 let mut abort_idx: Option<usize> = None;
920 let mut abort_err: Option<DcbError> = None;
921
922 responders.push(response_tx);
923 let result = UmaDb::process_append_request(
924 events,
925 condition,
926 tracking_info,
927 mvcc,
928 &mut writer,
929 cancel,
930 );
931 match &result {
933 Ok(_) => results.push(result),
934 Err(e) if is_integrity_error(e) => {
935 results.push(Err(clone_dcb_error(e)))
936 }
937 Err(e) => {
938 abort_idx = Some(0);
939 abort_err = Some(clone_dcb_error(e));
940 results.push(Err(clone_dcb_error(e)));
941 }
942 }
943
944 loop {
948 if total_events >= APPEND_BATCH_MAX_EVENTS {
949 break;
950 }
951 if abort_idx.is_some() {
953 break;
954 }
955 match request_rx.try_recv() {
956 Ok(WriterRequest::Append {
957 events,
958 condition,
959 tracking_info,
960 response_tx,
961 cancel,
962 }) => {
963 let ev_len = events.len();
964 let idx_in_batch = responders.len();
965 responders.push(response_tx);
966 let res_next = UmaDb::process_append_request(
967 events,
968 condition,
969 tracking_info,
970 mvcc,
971 &mut writer,
972 cancel,
973 );
974 match &res_next {
975 Ok(_) => results.push(res_next),
976 Err(e) if is_integrity_error(e) => {
977 results.push(Err(clone_dcb_error(e)))
978 }
979 Err(e) => {
980 abort_idx = Some(idx_in_batch);
981 abort_err = Some(clone_dcb_error(e));
982 results.push(Err(clone_dcb_error(e)));
983 }
985 }
986 total_events += ev_len;
987 }
988 Ok(WriterRequest::Shutdown) => {
989 break;
994 }
995 Err(mpsc::error::TryRecvError::Empty) => {
996 break;
997 }
998 Err(mpsc::error::TryRecvError::Disconnected) => break,
999 }
1000 }
1001 if let (Some(failed_at), Some(orig_err)) = (abort_idx, abort_err) {
1004 let shadow = shadow_for_batch_abort(&orig_err);
1006 for (i, tx) in responders.into_iter().enumerate() {
1007 if i == failed_at {
1008 let _ = tx.send(Err(clone_dcb_error(&orig_err)));
1009 } else {
1010 let _ = tx.send(Err(clone_dcb_error(&shadow)));
1011 }
1012 }
1013 continue;
1015 }
1016
1017 let batch_result = match mvcc.commit(&mut writer) {
1019 Ok(_) => Ok(results),
1020 Err(err) => Err(err),
1021 };
1022
1023 match batch_result {
1024 Ok(results) => {
1025 for (res, tx) in results.into_iter().zip(responders.into_iter())
1027 {
1028 let _ = tx.send(res);
1029 }
1030 let last_committed = writer.next_position.0.saturating_sub(1);
1032 let new_head = if last_committed == 0 {
1033 None
1034 } else {
1035 Some(last_committed)
1036 };
1037 let _ = head_tx_writer.send(new_head);
1038 }
1039 Err(e) => {
1040 let total = responders.len();
1044 let mut iter = responders.into_iter();
1045 for _ in 0..total {
1046 if let Some(tx) = iter.next() {
1047 let _ = tx.send(Err(clone_dcb_error(&e)));
1048 }
1049 }
1050 }
1051 }
1052 }
1053 WriterRequest::Shutdown => {
1054 break;
1055 }
1056 }
1057 }
1058 });
1059 });
1060
1061 Ok(Self {
1062 mvcc,
1063 head_watch_tx: head_tx,
1064 writer_request_tx: request_tx,
1065 })
1066 }
1067
1068 fn read(
1069 &self,
1070 query: Option<DcbQuery>,
1071 start: Option<u64>,
1072 backwards: bool,
1073 limit: Option<u32>,
1074 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1075 ) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
1076 let reader = self.mvcc.reader()?;
1077 let last_committed_position = reader.next_position.0.saturating_sub(1);
1078
1079 let q = query.unwrap_or(DcbQuery { items: vec![] });
1080 let start_position = start.map(Position);
1081
1082 let events = read_conditional(
1083 &self.mvcc,
1084 &std::collections::HashMap::new(),
1085 reader.events_tree_root_id,
1086 reader.tags_tree_root_id,
1087 q,
1088 start_position,
1089 backwards,
1090 limit,
1091 false,
1092 cancel,
1093 )
1094 .map_err(|e| match e {
1095 DcbError::CancelledByUser() => DcbError::CancelledByUser(),
1096 _ => DcbError::Corruption(format!("{e}")),
1097 })?;
1098
1099 let head = if limit.is_none() {
1100 if last_committed_position == 0 {
1101 None
1102 } else {
1103 Some(last_committed_position)
1104 }
1105 } else {
1106 events.last().map(|e| e.position)
1107 };
1108
1109 Ok((events, head))
1110 }
1111
1112 fn head(&self) -> DcbResult<Option<u64>> {
1113 let header_page = self
1114 .mvcc
1115 .get_latest_header_page()
1116 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1117 let header = header_page
1118 .as_header_node()
1119 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1120 let last = header.next_position.0.saturating_sub(1);
1121 if last == 0 { Ok(None) } else { Ok(Some(last)) }
1122 }
1123
1124 fn get_tracking_info(&self, source: String) -> DcbResult<Option<u64>> {
1125 let db = UmaDb::from_arc(self.mvcc.clone());
1126 db.get_tracking_info(&source)
1127 }
1128
1129 pub async fn append(
1130 &self,
1131 events: Vec<DcbEvent>,
1132 condition: Option<DcbAppendCondition>,
1133 tracking_info: Option<TrackingInfo>,
1134 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1135 ) -> DcbResult<u64> {
1136 let pre_append_decision = if let Some(mut given_condition) = condition {
1138 let reader = self.mvcc.reader()?;
1139 let current_head = {
1140 let last = reader.next_position.0.saturating_sub(1);
1141 if last == 0 { None } else { Some(last) }
1142 };
1143
1144 let from = given_condition.after.map(|after| Position(after + 1));
1146 let empty_dirty = std::collections::HashMap::new();
1147 let found = read_conditional(
1148 &self.mvcc,
1149 &empty_dirty,
1150 reader.events_tree_root_id,
1151 reader.tags_tree_root_id,
1152 given_condition.fail_if_events_match.clone(),
1153 from,
1154 false,
1155 Some(1),
1156 false,
1157 None,
1158 )?;
1159
1160 if let Some(matched) = found.first() {
1161 match is_request_idempotent(
1163 &self.mvcc,
1164 &empty_dirty,
1165 reader.events_tree_root_id,
1166 reader.tags_tree_root_id,
1167 &events,
1168 given_condition.fail_if_events_match.clone(),
1169 from,
1170 cancel.clone(),
1171 ) {
1172 Ok(Some(last_recorded_position)) => {
1173 PreAppendDecision::AlreadyAppended(last_recorded_position)
1175 }
1176 Ok(None) => {
1177 let msg = format!(
1179 "condition: {:?} matched: {:?}",
1180 given_condition.clone(),
1181 matched,
1182 );
1183 return Err(DcbError::IntegrityError(msg));
1184 }
1185 Err(err) => {
1186 return Err(err);
1188 }
1189 }
1190 } else {
1191 let new_after = std::cmp::max(
1193 given_condition.after.unwrap_or(0),
1194 current_head.unwrap_or(0),
1195 );
1196 given_condition.after = Some(new_after);
1197
1198 PreAppendDecision::UseCondition(Some(given_condition))
1199 }
1200 } else {
1201 PreAppendDecision::UseCondition(None)
1203 };
1204
1205 match pre_append_decision {
1207 PreAppendDecision::AlreadyAppended(last_found_position) => {
1208 Ok(last_found_position)
1210 }
1211 PreAppendDecision::UseCondition(adjusted_condition) => {
1212 let (response_tx, response_rx) = oneshot::channel();
1214
1215 self.writer_request_tx
1216 .send(WriterRequest::Append {
1217 events,
1218 condition: adjusted_condition,
1219 tracking_info,
1220 response_tx,
1221 cancel,
1222 })
1223 .await
1224 .map_err(|_| {
1225 DcbError::Io(std::io::Error::other(
1226 "failed to send append request to EventStore thread",
1227 ))
1228 })?;
1229
1230 response_rx.await.map_err(|_| {
1231 DcbError::Io(std::io::Error::other(
1232 "failed to receive append response from EventStore thread",
1233 ))
1234 })?
1235 }
1236 }
1237 }
1238
1239 fn watch_head(&self) -> watch::Receiver<Option<u64>> {
1240 self.head_watch_tx.subscribe()
1241 }
1242
1243 #[allow(dead_code)]
1244 async fn shutdown(&self) {
1245 let _ = self.writer_request_tx.send(WriterRequest::Shutdown).await;
1246 }
1247}
1248
1249impl Clone for RequestHandler {
1251 fn clone(&self) -> Self {
1252 Self {
1253 mvcc: self.mvcc.clone(),
1254 head_watch_tx: self.head_watch_tx.clone(),
1255 writer_request_tx: self.writer_request_tx.clone(),
1256 }
1257 }
1258}
1259
1260#[derive(Debug)]
1261enum PreAppendDecision {
1262 UseCondition(Option<DcbAppendCondition>),
1264 AlreadyAppended(u64),
1266}