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