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_request_idempotent, read_conditional,
16 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 let query: Option<DcbQuery> = read_request.query.map(|q| q.into());
374 let start = read_request.start;
375 let backwards = read_request.backwards.unwrap_or(false);
376 let limit = read_request.limit;
377 let batch_size = read_request
379 .batch_size
380 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
381 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
382 let subscribe = read_request.subscribe.unwrap_or(false);
383
384 let (tx, rx) = mpsc::channel(2048);
386 let request_handler = self.request_handler.clone();
388 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
390
391 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
392 let cancel_signal_for_task = cancel_signal.clone();
393
394 tokio::spawn(async move {
396 let query_clone = query;
398 let mut next_start = start;
399 let mut sent_any = false;
400 let mut remaining_limit = limit.unwrap_or(u32::MAX);
401 let mut head_rx = subscribe.then(|| request_handler.watch_head());
403 let mut captured_db_head: Option<u64> = None;
404 let mut have_captured_db_head: bool = false;
405 loop {
406 if tx.is_closed() || *shutdown_watch_rx.borrow() {
410 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
411 break;
412 }
413 let read_limit = remaining_limit.min(batch_size);
415 if limit.is_some() && remaining_limit == 0 {
417 break;
418 }
419 let handler = request_handler.clone();
420 let query_val = query_clone.clone();
421 let limit_val = Some(read_limit);
422 let cancel_for_blocking = cancel_signal_for_task.clone();
423 let mut blocking_handle = tokio::task::spawn_blocking(move || {
424 handler.read(
425 query_val,
426 next_start,
427 backwards,
428 limit_val,
429 Some(cancel_for_blocking),
430 )
431 });
432
433 let res = tokio::select! {
434 res = &mut blocking_handle => {
435 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
436 }
437 _ = tx.closed() => {
438 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
439 let _ = blocking_handle.await;
441 break;
442 }
443 _ = shutdown_watch_rx.changed() => {
444 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
445 let _ = blocking_handle.await;
446 break;
447 }
448 };
449
450 match res {
451 Ok((dcb_sequenced_events, db_head)) => {
452 if !have_captured_db_head && !subscribe {
454 captured_db_head = db_head;
455 have_captured_db_head = true;
456 }
457
458 let original_len = dcb_sequenced_events.len();
460 let read_less_than_read_limit = (original_len as u32) < read_limit;
461
462 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
464 dcb_sequenced_events
465 .into_iter()
466 .filter(|e| {
467 if let Some(h) = captured_db_head {
468 e.position <= h
469 } else {
470 true
471 }
472 })
473 .map(umadb_proto::v1::SequencedEvent::from)
474 .collect();
475
476 let reached_captured_head = captured_db_head.is_some()
478 && sequenced_event_protos.len() < original_len;
479
480 if sequenced_event_protos.is_empty() {
481 if let Some(head_rx) = head_rx.as_mut() {
482 tokio::select! {
484 _ = head_rx.changed() => {},
485 _ = shutdown_watch_rx.changed() => break,
486 _ = tx.closed() => break,
487 }
488 continue;
490 } else if !sent_any {
491 let response = umadb_proto::v1::ReadResponse {
493 events: vec![],
494 head: if limit.is_some() {
495 None
496 } else {
497 captured_db_head
498 },
499 };
500 let _ = tx.send(Ok(response)).await;
501 }
502 break;
504 }
505
506 let sent_count = sequenced_event_protos.len() as u32;
508
509 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
510
511 let response = umadb_proto::v1::ReadResponse {
512 events: sequenced_event_protos,
513 head: if subscribe || limit.is_some() {
514 last_event_position
515 } else {
516 captured_db_head
517 },
518 };
519
520 if tx.send(Ok(response)).await.is_err() {
521 break;
522 }
523 sent_any = true;
524
525 next_start = last_event_position.map(|p| {
527 if backwards {
528 p.saturating_sub(1)
529 } else {
530 p.saturating_add(1)
531 }
532 });
533
534 if !subscribe && (read_less_than_read_limit || reached_captured_head) {
537 break;
538 }
539
540 if limit.is_some() {
542 remaining_limit = remaining_limit.saturating_sub(sent_count);
543 if remaining_limit == 0 {
544 break;
545 }
546 }
547
548 tokio::task::yield_now().await;
550 }
551 Err(e) => {
552 if matches!(e, DcbError::CancelledByUser()) {
553 } else {
555 let _ = tx.send(Err(status_from_dcb_error(e))).await;
556 }
557 break;
558 }
559 }
560 }
561 });
562
563 Ok(Response::new(
565 Box::pin(ReceiverStream::new(rx)) as Self::ReadStream
566 ))
567 }
568
569 async fn subscribe(
570 &self,
571 request: Request<umadb_proto::v1::SubscribeRequest>,
572 ) -> Result<Response<Self::SubscribeStream>, Status> {
573 self.enforce_api_key(request.metadata())?;
575 let subscribe_request = request.into_inner();
576
577 let query: Option<DcbQuery> = subscribe_request.query.map(|q| q.into());
579 let after = subscribe_request.after;
580 let batch_size = subscribe_request
582 .batch_size
583 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
584 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
585
586 let (tx, rx) = mpsc::channel(2048);
588 let request_handler = self.request_handler.clone();
590 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
592
593 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
594 let cancel_signal_for_task = cancel_signal.clone();
595
596 tokio::spawn(async move {
598 let query_clone = query;
600 let mut next_after = after.map(|a| a.saturating_add(1));
602 let mut head_rx = request_handler.watch_head();
604
605 loop {
606 if tx.is_closed() || *shutdown_watch_rx.borrow() {
610 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
611 break;
612 }
613
614 let handler = request_handler.clone();
615 let query_val = query_clone.clone();
616 let batch_size_val = Some(batch_size);
617 let cancel_for_blocking = cancel_signal_for_task.clone();
618 let mut blocking_handle = tokio::task::spawn_blocking(move || {
619 handler.read(
620 query_val,
621 next_after,
622 false,
623 batch_size_val,
624 Some(cancel_for_blocking),
625 )
626 });
627
628 let res = tokio::select! {
629 res = &mut blocking_handle => {
630 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
631 }
632 _ = tx.closed() => {
633 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
634 let _ = blocking_handle.await;
635 break;
636 }
637 _ = shutdown_watch_rx.changed() => {
638 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
639 let _ = blocking_handle.await;
640 break;
641 }
642 };
643
644 match res {
645 Ok((dcb_sequenced_events, _unused_db_head)) => {
646 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
648 dcb_sequenced_events
649 .into_iter()
650 .map(umadb_proto::v1::SequencedEvent::from)
651 .collect();
652
653 if sequenced_event_protos.is_empty() {
654 tokio::select! {
656 _ = head_rx.changed() => {},
657 _ = shutdown_watch_rx.changed() => {},
658 _ = tx.closed() => {},
659 }
660 continue;
661 }
662
663 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
664
665 let response = umadb_proto::v1::SubscribeResponse {
666 events: sequenced_event_protos,
667 };
668
669 if tx.send(Ok(response)).await.is_err() {
670 break;
671 }
672
673 next_after = last_event_position.map(|p| p.saturating_add(1));
676
677 tokio::task::yield_now().await;
679 }
680 Err(e) => {
681 if matches!(e, DcbError::CancelledByUser()) {
682 } else {
684 let _ = tx.send(Err(status_from_dcb_error(e))).await;
685 }
686 break;
687 }
688 }
689 }
690 });
691
692 Ok(Response::new(
694 Box::pin(ReceiverStream::new(rx)) as Self::SubscribeStream
695 ))
696 }
697
698 async fn append(
699 &self,
700 request: Request<umadb_proto::v1::AppendRequest>,
701 ) -> Result<Response<umadb_proto::v1::AppendResponse>, Status> {
702 self.enforce_api_key(request.metadata())?;
704 let req = request.into_inner();
705
706 let events: Vec<DcbEvent> = match req.events.into_iter().map(|e| e.try_into()).collect() {
708 Ok(events) => events,
709 Err(e) => {
710 return Err(status_from_dcb_error(e));
711 }
712 };
713 let condition = req.condition.map(|c| c.into());
714
715 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
716 let cancel_signal_for_task = cancel_signal.clone();
717
718 let (cancel_tx, cancel_rx) = oneshot::channel();
720 let _guard = CancellationGuard(Some(cancel_tx));
721
722 let cancel_signal_for_monitoring = cancel_signal.clone();
724 tokio::spawn(async move {
725 let _ = cancel_rx.await;
728 cancel_signal_for_monitoring.store(true, std::sync::atomic::Ordering::SeqCst);
729 });
730
731 let res = self
733 .request_handler
734 .append(
735 events,
736 condition,
737 req.tracking_info.map(|t| TrackingInfo {
738 source: t.source,
739 position: t.position,
740 }),
741 Some(cancel_signal_for_task.clone()),
742 )
743 .await;
744
745 match res {
746 Ok(position) => Ok(Response::new(umadb_proto::v1::AppendResponse { position })),
747 Err(e) => Err(status_from_dcb_error(e)),
748 }
749 }
750
751 async fn head(
752 &self,
753 request: Request<umadb_proto::v1::HeadRequest>,
754 ) -> Result<Response<umadb_proto::v1::HeadResponse>, Status> {
755 self.enforce_api_key(request.metadata())?;
757 match self.request_handler.head() {
759 Ok(position) => {
760 Ok(Response::new(umadb_proto::v1::HeadResponse { position }))
762 }
763 Err(e) => Err(status_from_dcb_error(e)),
764 }
765 }
766
767 async fn get_tracking_info(
768 &self,
769 request: Request<umadb_proto::v1::TrackingRequest>,
770 ) -> Result<Response<umadb_proto::v1::TrackingResponse>, Status> {
771 self.enforce_api_key(request.metadata())?;
773 let req = request.into_inner();
774 match self.request_handler.get_tracking_info(req.source) {
775 Ok(position) => Ok(Response::new(umadb_proto::v1::TrackingResponse {
776 position,
777 })),
778 Err(e) => Err(status_from_dcb_error(e)),
779 }
780 }
781}
782
783enum WriterRequest {
785 Append {
786 events: Vec<DcbEvent>,
787 condition: Option<DcbAppendCondition>,
788 tracking_info: Option<TrackingInfo>,
789 response_tx: oneshot::Sender<DcbResult<u64>>,
790 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
791 },
792 Shutdown,
793}
794
795struct RequestHandler {
797 mvcc: Arc<Mvcc>,
798 head_watch_tx: watch::Sender<Option<u64>>,
799 writer_request_tx: mpsc::Sender<WriterRequest>,
800}
801
802impl RequestHandler {
803 fn new(storage_options: StorageOptions) -> DcbResult<Self> {
804 let (request_tx, mut request_rx) = mpsc::channel::<WriterRequest>(1024);
806
807 let mvcc = Arc::new(Mvcc::new(false, storage_options)?);
809
810 let init_head = {
812 let header_page = mvcc.get_latest_header_page()?;
813 let header = header_page.as_header_node()?;
814 let last = header.next_position.0.saturating_sub(1);
815 if last == 0 { None } else { Some(last) }
816 };
817 let (head_tx, _head_rx) = watch::channel::<Option<u64>>(init_head);
818
819 let mvcc_for_writer = mvcc.clone();
821 let head_tx_writer = head_tx.clone();
822 thread::spawn(move || {
823 let db = UmaDb::from_arc(mvcc_for_writer);
824
825 let rt = Runtime::new().unwrap();
827
828 rt.block_on(async {
830 while let Some(request) = request_rx.recv().await {
831 match request {
832 WriterRequest::Append {
833 events,
834 condition,
835 tracking_info,
836 response_tx,
837 cancel,
838 } => {
839 let mut total_events = 0;
844 total_events += events.len();
845 let mvcc = &db.mvcc;
848 let mut writer = match mvcc.writer() {
849 Ok(writer) => writer,
850 Err(err) => {
851 let _ = response_tx.send(Err(err));
852 continue;
853 }
854 };
855
856 let mut responders: Vec<oneshot::Sender<DcbResult<u64>>> = Vec::new();
857 let mut results: Vec<DcbResult<u64>> = Vec::new();
858
859 let mut abort_idx: Option<usize> = None;
861 let mut abort_err: Option<DcbError> = None;
862
863 responders.push(response_tx);
864 let result = UmaDb::process_append_request(
865 events,
866 condition,
867 tracking_info,
868 mvcc,
869 &mut writer,
870 cancel,
871 );
872 match &result {
874 Ok(_) => results.push(result),
875 Err(e) if is_integrity_error(e) => {
876 results.push(Err(clone_dcb_error(e)))
877 }
878 Err(e) => {
879 abort_idx = Some(0);
880 abort_err = Some(clone_dcb_error(e));
881 results.push(Err(clone_dcb_error(e)));
882 }
883 }
884
885 loop {
889 if total_events >= APPEND_BATCH_MAX_EVENTS {
890 break;
891 }
892 if abort_idx.is_some() {
894 break;
895 }
896 match request_rx.try_recv() {
897 Ok(WriterRequest::Append {
898 events,
899 condition,
900 tracking_info,
901 response_tx,
902 cancel,
903 }) => {
904 let ev_len = events.len();
905 let idx_in_batch = responders.len();
906 responders.push(response_tx);
907 let res_next = UmaDb::process_append_request(
908 events,
909 condition,
910 tracking_info,
911 mvcc,
912 &mut writer,
913 cancel,
914 );
915 match &res_next {
916 Ok(_) => results.push(res_next),
917 Err(e) if is_integrity_error(e) => {
918 results.push(Err(clone_dcb_error(e)))
919 }
920 Err(e) => {
921 abort_idx = Some(idx_in_batch);
922 abort_err = Some(clone_dcb_error(e));
923 results.push(Err(clone_dcb_error(e)));
924 }
926 }
927 total_events += ev_len;
928 }
929 Ok(WriterRequest::Shutdown) => {
930 break;
935 }
936 Err(mpsc::error::TryRecvError::Empty) => {
937 break;
938 }
939 Err(mpsc::error::TryRecvError::Disconnected) => break,
940 }
941 }
942 if let (Some(failed_at), Some(orig_err)) = (abort_idx, abort_err) {
945 let shadow = shadow_for_batch_abort(&orig_err);
947 for (i, tx) in responders.into_iter().enumerate() {
948 if i == failed_at {
949 let _ = tx.send(Err(clone_dcb_error(&orig_err)));
950 } else {
951 let _ = tx.send(Err(clone_dcb_error(&shadow)));
952 }
953 }
954 continue;
956 }
957
958 let batch_result = match mvcc.commit(&mut writer) {
960 Ok(_) => Ok(results),
961 Err(err) => Err(err),
962 };
963
964 match batch_result {
965 Ok(results) => {
966 for (res, tx) in results.into_iter().zip(responders.into_iter())
968 {
969 let _ = tx.send(res);
970 }
971 let last_committed = writer.next_position.0.saturating_sub(1);
973 let new_head = if last_committed == 0 {
974 None
975 } else {
976 Some(last_committed)
977 };
978 let _ = head_tx_writer.send(new_head);
979 }
980 Err(e) => {
981 let total = responders.len();
985 let mut iter = responders.into_iter();
986 for _ in 0..total {
987 if let Some(tx) = iter.next() {
988 let _ = tx.send(Err(clone_dcb_error(&e)));
989 }
990 }
991 }
992 }
993 }
994 WriterRequest::Shutdown => {
995 break;
996 }
997 }
998 }
999 });
1000 });
1001
1002 Ok(Self {
1003 mvcc,
1004 head_watch_tx: head_tx,
1005 writer_request_tx: request_tx,
1006 })
1007 }
1008
1009 fn read(
1010 &self,
1011 query: Option<DcbQuery>,
1012 start: Option<u64>,
1013 backwards: bool,
1014 limit: Option<u32>,
1015 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1016 ) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
1017 let reader = self.mvcc.reader()?;
1018 let db_head = if reader.next_position > Position(1) {
1019 Some(reader.next_position.0.saturating_sub(1))
1020 } else {
1021 None
1022 };
1023
1024 let q = query.unwrap_or(DcbQuery { items: vec![] });
1025 let start_position = start.map(Position);
1026
1027 let events = read_conditional(
1028 &self.mvcc,
1029 &std::collections::HashMap::new(),
1030 reader.events_tree_root_id,
1031 reader.tags_tree_root_id,
1032 q,
1033 start_position,
1034 backwards,
1035 limit,
1036 false,
1037 cancel,
1038 )
1039 .map_err(|e| match e {
1040 DcbError::CancelledByUser() => DcbError::CancelledByUser(),
1041 _ => DcbError::Corruption(format!("{e}")),
1042 })?;
1043
1044 Ok((events, db_head))
1045 }
1046
1047 fn head(&self) -> DcbResult<Option<u64>> {
1048 let header_page = self
1049 .mvcc
1050 .get_latest_header_page()
1051 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1052 let header = header_page
1053 .as_header_node()
1054 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1055 let last = header.next_position.0.saturating_sub(1);
1056 if last == 0 { Ok(None) } else { Ok(Some(last)) }
1057 }
1058
1059 fn get_tracking_info(&self, source: String) -> DcbResult<Option<u64>> {
1060 let db = UmaDb::from_arc(self.mvcc.clone());
1061 db.get_tracking_info(&source)
1062 }
1063
1064 pub async fn append(
1065 &self,
1066 events: Vec<DcbEvent>,
1067 condition: Option<DcbAppendCondition>,
1068 tracking_info: Option<TrackingInfo>,
1069 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1070 ) -> DcbResult<u64> {
1071 let pre_append_decision = if let Some(mut given_condition) = condition {
1073 let reader = self.mvcc.reader()?;
1074 let current_head = {
1075 let last = reader.next_position.0.saturating_sub(1);
1076 if last == 0 { None } else { Some(last) }
1077 };
1078
1079 let from = given_condition.after.map(|after| Position(after + 1));
1081 let empty_dirty = std::collections::HashMap::new();
1082 let found = read_conditional(
1083 &self.mvcc,
1084 &empty_dirty,
1085 reader.events_tree_root_id,
1086 reader.tags_tree_root_id,
1087 given_condition.fail_if_events_match.clone(),
1088 from,
1089 false,
1090 Some(1),
1091 false,
1092 None,
1093 )?;
1094
1095 if let Some(matched) = found.first() {
1096 match is_request_idempotent(
1098 &self.mvcc,
1099 &empty_dirty,
1100 reader.events_tree_root_id,
1101 reader.tags_tree_root_id,
1102 &events,
1103 given_condition.fail_if_events_match.clone(),
1104 from,
1105 cancel.clone(),
1106 ) {
1107 Ok(Some(last_recorded_position)) => {
1108 PreAppendDecision::AlreadyAppended(last_recorded_position)
1110 }
1111 Ok(None) => {
1112 let msg = format!(
1114 "condition: {:?} matched: {:?}",
1115 given_condition.clone(),
1116 matched,
1117 );
1118 return Err(DcbError::IntegrityError(msg));
1119 }
1120 Err(err) => {
1121 return Err(err);
1123 }
1124 }
1125 } else {
1126 let new_after = std::cmp::max(
1128 given_condition.after.unwrap_or(0),
1129 current_head.unwrap_or(0),
1130 );
1131 given_condition.after = Some(new_after);
1132
1133 PreAppendDecision::UseCondition(Some(given_condition))
1134 }
1135 } else {
1136 PreAppendDecision::UseCondition(None)
1138 };
1139
1140 match pre_append_decision {
1142 PreAppendDecision::AlreadyAppended(last_found_position) => {
1143 Ok(last_found_position)
1145 }
1146 PreAppendDecision::UseCondition(adjusted_condition) => {
1147 let (response_tx, response_rx) = oneshot::channel();
1149
1150 self.writer_request_tx
1151 .send(WriterRequest::Append {
1152 events,
1153 condition: adjusted_condition,
1154 tracking_info,
1155 response_tx,
1156 cancel,
1157 })
1158 .await
1159 .map_err(|_| {
1160 DcbError::Io(std::io::Error::other(
1161 "failed to send append request to EventStore thread",
1162 ))
1163 })?;
1164
1165 response_rx.await.map_err(|_| {
1166 DcbError::Io(std::io::Error::other(
1167 "failed to receive append response from EventStore thread",
1168 ))
1169 })?
1170 }
1171 }
1172 }
1173
1174 fn watch_head(&self) -> watch::Receiver<Option<u64>> {
1175 self.head_watch_tx.subscribe()
1176 }
1177
1178 #[allow(dead_code)]
1179 async fn shutdown(&self) {
1180 let _ = self.writer_request_tx.send(WriterRequest::Shutdown).await;
1181 }
1182}
1183
1184impl Clone for RequestHandler {
1186 fn clone(&self) -> Self {
1187 Self {
1188 mvcc: self.mvcc.clone(),
1189 head_watch_tx: self.head_watch_tx.clone(),
1190 writer_request_tx: self.writer_request_tx.clone(),
1191 }
1192 }
1193}
1194
1195#[derive(Debug)]
1196enum PreAppendDecision {
1197 UseCondition(Option<DcbAppendCondition>),
1199 AlreadyAppended(u64),
1201}