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::{Semaphore, 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, 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 fn raise_open_file_limit() {
231 static ONCE: std::sync::Once = std::sync::Once::new();
232 ONCE.call_once(raise_open_file_limit_inner);
233}
234
235#[cfg(unix)]
236fn raise_open_file_limit_inner() {
237 unsafe {
240 let mut lim = libc::rlimit {
241 rlim_cur: 0,
242 rlim_max: 0,
243 };
244 if libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim) != 0 {
245 eprintln!(
246 "UmaDB: could not read open-file limit (RLIMIT_NOFILE): {}",
247 std::io::Error::last_os_error()
248 );
249 return;
250 }
251 let previous = lim.rlim_cur;
252 if lim.rlim_max != libc::RLIM_INFINITY && previous >= lim.rlim_max {
253 println!(
254 "UmaDB open-file limit (RLIMIT_NOFILE): soft {previous} already at hard limit {}",
255 lim.rlim_max
256 );
257 return;
258 }
259 let candidates: &[libc::rlim_t] = if lim.rlim_max == libc::RLIM_INFINITY {
263 &[1_048_576, 65_536]
264 } else {
265 &[lim.rlim_max]
266 };
267 let mut attempted = false;
268 for &target in candidates.iter() {
269 if target <= previous {
270 continue;
271 }
272 attempted = true;
273 let new = libc::rlimit {
274 rlim_cur: target,
275 rlim_max: lim.rlim_max,
276 };
277 if libc::setrlimit(libc::RLIMIT_NOFILE, &new) == 0 {
278 println!(
279 "UmaDB raised open-file limit (RLIMIT_NOFILE): soft {previous} -> {target}"
280 );
281 return;
282 }
283 }
284 if !attempted {
285 println!(
287 "UmaDB open-file limit (RLIMIT_NOFILE): soft {previous} is already sufficient"
288 );
289 return;
290 }
291 eprintln!(
292 "UmaDB: could not raise open-file limit (RLIMIT_NOFILE) from soft {previous}: {}. \
293 Consider raising it manually (e.g. `ulimit -n 262144`).",
294 std::io::Error::last_os_error()
295 );
296 }
297}
298
299#[cfg(not(unix))]
300fn raise_open_file_limit_inner() {}
301
302pub async fn start_server_with_options(
303 options: ServerOptions,
304 shutdown_rx: oneshot::Receiver<()>,
305) -> Result<(), Box<dyn std::error::Error>> {
306 raise_open_file_limit();
308
309 let addr = options.listen_addr.parse()?;
310 let incoming = match TcpIncoming::bind(addr) {
312 Ok(incoming) => incoming,
313 Err(err) => {
314 return Err(Box::new(DcbError::InitializationError(format!(
315 "failed to bind to address {}: {}",
316 addr, err
317 ))));
318 }
319 }
320 .with_nodelay(Some(true))
321 .with_keepalive(Some(std::time::Duration::from_secs(60)));
322
323 let (srv_shutdown_tx, srv_shutdown_rx) = watch::channel(false);
325 let dcb_server = match DcbServer::new(srv_shutdown_rx, options.api_key.clone(), options.storage)
326 {
327 Ok(server) => server,
328 Err(err) => {
329 return Err(Box::new(err));
330 }
331 };
332
333 println!(
334 "UmaDB has {:?} events",
335 dcb_server
336 .request_handler
337 .head()
338 .unwrap_or(Some(0))
339 .unwrap_or(0)
340 );
341 let tls_mode_display_str = if options.tls.is_some() {
342 "with TLS"
343 } else {
344 "without TLS"
345 };
346
347 let api_key_display_str = if options.api_key.is_some() {
348 "with API key"
349 } else {
350 "without API key"
351 };
352
353 use tonic_health::ServingStatus; let (health_reporter, health_service) = tonic_health::server::health_reporter();
356 health_reporter
358 .set_service_status("", ServingStatus::Serving)
359 .await;
360 health_reporter
361 .set_service_status("umadb.v1.DCB", ServingStatus::Serving)
362 .await;
363 let health_reporter_for_shutdown = health_reporter.clone();
364
365 let mut builder = build_server_builder_with_options(options.tls)
367 .layer(PathRewriterLayer)
368 .add_service(health_service);
369
370 builder = builder.add_service(dcb_server.into_service());
372 let router = builder;
373
374 println!("UmaDB is listening on {addr} ({tls_mode_display_str}, {api_key_display_str})");
375 println!("UmaDB started in {:?}", uptime());
376 router
378 .serve_with_incoming_shutdown(incoming, async move {
379 let _ = shutdown_rx.await;
381 let _ = health_reporter_for_shutdown
383 .set_service_status("", ServingStatus::NotServing)
384 .await;
385 let _ = health_reporter_for_shutdown
386 .set_service_status("umadb.v1.DCB", ServingStatus::NotServing)
387 .await;
388 let _ = srv_shutdown_tx.send(true);
390 println!("\nUmaDB server shutdown complete");
391 })
392 .await?;
393
394 Ok(())
395}
396
397fn read_scan_concurrency_limit() -> usize {
409 const PER_CORE: usize = 4;
410 if let Some(n) = std::env::var("UMADB_READ_SCAN_CONCURRENCY")
411 .ok()
412 .and_then(|s| s.parse::<usize>().ok())
413 .filter(|n| *n > 0)
414 {
415 return n;
416 }
417 std::thread::available_parallelism()
418 .map(|n| n.get())
419 .unwrap_or(4)
420 .saturating_mul(PER_CORE)
421 .max(PER_CORE)
422}
423
424pub struct DcbServer {
426 pub(crate) request_handler: RequestHandler,
427 shutdown_watch_rx: watch::Receiver<bool>,
428 api_key: Option<String>,
429 read_scan_semaphore: Arc<Semaphore>,
431}
432
433impl DcbServer {
434 pub fn new(
435 shutdown_rx: watch::Receiver<bool>,
436 api_key: Option<String>,
437 storage_options: StorageOptions,
438 ) -> DcbResult<Self> {
439 let command_handler = RequestHandler::new(storage_options)?;
440 Ok(Self {
441 request_handler: command_handler,
442 shutdown_watch_rx: shutdown_rx,
443 api_key,
444 read_scan_semaphore: Arc::new(Semaphore::new(read_scan_concurrency_limit())),
445 })
446 }
447
448 pub fn into_service(self) -> umadb_proto::v1::dcb_server::DcbServer<Self> {
449 umadb_proto::v1::dcb_server::DcbServer::new(self)
450 }
451
452 fn enforce_api_key(&self, metadata: &tonic::metadata::MetadataMap) -> Result<(), Status> {
453 if let Some(expected) = &self.api_key {
454 let auth = metadata.get("authorization");
455 let expected_val = format!("Bearer {}", expected);
456 let ok = auth
457 .and_then(|m| m.to_str().ok())
458 .map(|s| s == expected_val)
459 .unwrap_or(false);
460 if !ok {
461 return Err(status_from_dcb_error(DcbError::AuthenticationError(
462 "missing or invalid API key".to_string(),
463 )));
464 }
465 }
466 Ok(())
467 }
468}
469
470#[tonic::async_trait]
471impl umadb_proto::v1::dcb_server::Dcb for DcbServer {
472 type ReadStream =
473 Pin<Box<dyn Stream<Item = Result<umadb_proto::v1::ReadResponse, Status>> + Send + 'static>>;
474 type SubscribeStream = Pin<
475 Box<dyn Stream<Item = Result<umadb_proto::v1::SubscribeResponse, Status>> + Send + 'static>,
476 >;
477
478 async fn read(
479 &self,
480 request: Request<umadb_proto::v1::ReadRequest>,
481 ) -> Result<Response<Self::ReadStream>, Status> {
482 self.enforce_api_key(request.metadata())?;
484 let read_request = request.into_inner();
485
486 #[allow(deprecated)]
488 if read_request.subscribe.unwrap_or(false) {
489 return Err(status_from_dcb_error(DcbError::InvalidArgument(
490 "The `subscribe` argument of `read()` has been deprecated. \
491 Please call the `subscribe()` method instead."
492 .to_string(),
493 )));
494 }
495
496 let query: Option<DcbQuery> = read_request.query.map(|q| q.into());
498 let start = read_request.start;
499 let backwards = read_request.backwards.unwrap_or(false);
500 let limit = read_request.limit;
501 let batch_size = read_request
503 .batch_size
504 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
505 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
506
507 let (tx, rx) = mpsc::channel(2048);
509 let request_handler = self.request_handler.clone();
511 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
513
514 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
515 let cancel_signal_for_task = cancel_signal.clone();
516 let read_scan_semaphore = self.read_scan_semaphore.clone();
517
518 tokio::spawn(async move {
520 let query_clone = query;
522 let mut next_start = start;
523 let mut sent_any = false;
524 let mut remaining_limit = limit.unwrap_or(u32::MAX);
525 let mut captured_db_head: Option<u64> = None;
526 let mut have_captured_db_head: bool = false;
527 loop {
528 if tx.is_closed() || *shutdown_watch_rx.borrow() {
532 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
533 break;
534 }
535 let read_limit = remaining_limit.min(batch_size);
537 if limit.is_some() && remaining_limit == 0 {
539 break;
540 }
541 let permit = match read_scan_semaphore.clone().acquire_owned().await {
545 Ok(permit) => permit,
546 Err(_) => break,
547 };
548 let handler = request_handler.clone();
549 let query_val = query_clone.clone();
550 let limit_val = Some(read_limit);
551 let cancel_for_blocking = cancel_signal_for_task.clone();
552 let mut blocking_handle = tokio::task::spawn_blocking(move || {
553 let _permit = permit;
554 handler.read(
555 query_val,
556 next_start,
557 backwards,
558 limit_val,
559 Some(cancel_for_blocking),
560 )
561 });
562
563 let res = tokio::select! {
564 res = &mut blocking_handle => {
565 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
566 }
567 _ = tx.closed() => {
568 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
569 let _ = blocking_handle.await;
571 break;
572 }
573 _ = shutdown_watch_rx.changed() => {
574 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
575 let _ = blocking_handle.await;
576 break;
577 }
578 };
579
580 match res {
581 Ok((dcb_sequenced_events, db_head)) => {
582 if !have_captured_db_head {
584 captured_db_head = db_head;
585 have_captured_db_head = true;
586 }
587
588 let original_len = dcb_sequenced_events.len();
590 let read_less_than_read_limit = (original_len as u32) < read_limit;
591
592 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
594 dcb_sequenced_events
595 .into_iter()
596 .filter(|e| {
597 if let Some(h) = captured_db_head {
598 e.position <= h
599 } else {
600 true
601 }
602 })
603 .map(umadb_proto::v1::SequencedEvent::from)
604 .collect();
605
606 let reached_captured_head = captured_db_head.is_some()
608 && sequenced_event_protos.len() < original_len;
609
610 if sequenced_event_protos.is_empty() {
611 if !sent_any {
612 let response = umadb_proto::v1::ReadResponse {
614 events: vec![],
615 head: if limit.is_some() {
616 None
617 } else {
618 captured_db_head
619 },
620 };
621 let _ = tx.send(Ok(response)).await;
622 }
623 break;
625 }
626
627 let sent_count = sequenced_event_protos.len() as u32;
629
630 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
631
632 let response = umadb_proto::v1::ReadResponse {
633 events: sequenced_event_protos,
634 head: if limit.is_some() {
635 last_event_position
636 } else {
637 captured_db_head
638 },
639 };
640
641 if tx.send(Ok(response)).await.is_err() {
642 break;
643 }
644 sent_any = true;
645
646 next_start = last_event_position.map(|p| {
648 if backwards {
649 p.saturating_sub(1)
650 } else {
651 p.saturating_add(1)
652 }
653 });
654
655 if read_less_than_read_limit || reached_captured_head {
658 break;
659 }
660
661 if limit.is_some() {
663 remaining_limit = remaining_limit.saturating_sub(sent_count);
664 if remaining_limit == 0 {
665 break;
666 }
667 }
668
669 tokio::task::yield_now().await;
671 }
672 Err(e) => {
673 if matches!(e, DcbError::CancelledByUser()) {
674 } else {
676 let _ = tx.send(Err(status_from_dcb_error(e))).await;
677 }
678 break;
679 }
680 }
681 }
682 });
683
684 Ok(Response::new(
686 Box::pin(ReceiverStream::new(rx)) as Self::ReadStream
687 ))
688 }
689
690 async fn subscribe(
691 &self,
692 request: Request<umadb_proto::v1::SubscribeRequest>,
693 ) -> Result<Response<Self::SubscribeStream>, Status> {
694 self.enforce_api_key(request.metadata())?;
696 let subscribe_request = request.into_inner();
697
698 let query: Option<DcbQuery> = subscribe_request.query.map(|q| q.into());
700 let after = subscribe_request.after;
701 let batch_size = subscribe_request
703 .batch_size
704 .unwrap_or(READ_RESPONSE_BATCH_SIZE_DEFAULT)
705 .clamp(1, READ_RESPONSE_BATCH_SIZE_MAX);
706
707 let (tx, rx) = mpsc::channel(2048);
709 let request_handler = self.request_handler.clone();
711 let mut shutdown_watch_rx = self.shutdown_watch_rx.clone();
713
714 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
715 let cancel_signal_for_task = cancel_signal.clone();
716 let read_scan_semaphore = self.read_scan_semaphore.clone();
717
718 tokio::spawn(async move {
720 let query_clone = query;
722 let mut next_after = after.map(|a| a.saturating_add(1));
724 let mut head_rx = request_handler.watch_head();
726
727 loop {
728 if tx.is_closed() || *shutdown_watch_rx.borrow() {
732 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
733 break;
734 }
735
736 let permit = match read_scan_semaphore.clone().acquire_owned().await {
741 Ok(permit) => permit,
742 Err(_) => break,
743 };
744 let handler = request_handler.clone();
745 let query_val = query_clone.clone();
746 let batch_size_val = Some(batch_size);
747 let cancel_for_blocking = cancel_signal_for_task.clone();
748 let mut blocking_handle = tokio::task::spawn_blocking(move || {
749 let _permit = permit;
750 handler.read(
751 query_val,
752 next_after,
753 false,
754 batch_size_val,
755 Some(cancel_for_blocking),
756 )
757 });
758
759 let res = tokio::select! {
760 res = &mut blocking_handle => {
761 res.map_err(|e| DcbError::InternalError(e.to_string())).and_then(|res| res)
762 }
763 _ = tx.closed() => {
764 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
765 let _ = blocking_handle.await;
766 break;
767 }
768 _ = shutdown_watch_rx.changed() => {
769 cancel_signal_for_task.store(true, std::sync::atomic::Ordering::SeqCst);
770 let _ = blocking_handle.await;
771 break;
772 }
773 };
774
775 match res {
776 Ok((dcb_sequenced_events, _unused_db_head)) => {
777 let sequenced_event_protos: Vec<umadb_proto::v1::SequencedEvent> =
779 dcb_sequenced_events
780 .into_iter()
781 .map(umadb_proto::v1::SequencedEvent::from)
782 .collect();
783
784 if sequenced_event_protos.is_empty() {
785 tokio::select! {
787 _ = head_rx.changed() => {},
788 _ = shutdown_watch_rx.changed() => {},
789 _ = tx.closed() => {},
790 }
791 continue;
792 }
793
794 let last_event_position = sequenced_event_protos.last().map(|e| e.position);
795
796 let response = umadb_proto::v1::SubscribeResponse {
797 events: sequenced_event_protos,
798 };
799
800 if tx.send(Ok(response)).await.is_err() {
801 break;
802 }
803
804 next_after = last_event_position.map(|p| p.saturating_add(1));
807
808 tokio::task::yield_now().await;
810 }
811 Err(e) => {
812 if matches!(e, DcbError::CancelledByUser()) {
813 } else {
815 let _ = tx.send(Err(status_from_dcb_error(e))).await;
816 }
817 break;
818 }
819 }
820 }
821 });
822
823 Ok(Response::new(
825 Box::pin(ReceiverStream::new(rx)) as Self::SubscribeStream
826 ))
827 }
828
829 async fn append(
830 &self,
831 request: Request<umadb_proto::v1::AppendRequest>,
832 ) -> Result<Response<umadb_proto::v1::AppendResponse>, Status> {
833 self.enforce_api_key(request.metadata())?;
835 let req = request.into_inner();
836
837 let events: Vec<DcbEvent> = match req.events.into_iter().map(|e| e.try_into()).collect() {
839 Ok(events) => events,
840 Err(e) => {
841 return Err(status_from_dcb_error(e));
842 }
843 };
844 let condition = req.condition.map(|c| c.into());
845
846 let cancel_signal = Arc::new(std::sync::atomic::AtomicBool::new(false));
847 let cancel_signal_for_task = cancel_signal.clone();
848
849 let (cancel_tx, cancel_rx) = oneshot::channel();
851 let _guard = CancellationGuard(Some(cancel_tx));
852
853 let cancel_signal_for_monitoring = cancel_signal.clone();
855 tokio::spawn(async move {
856 let _ = cancel_rx.await;
859 cancel_signal_for_monitoring.store(true, std::sync::atomic::Ordering::SeqCst);
860 });
861
862 let res = self
864 .request_handler
865 .append(
866 events,
867 condition,
868 req.tracking_info.map(|t| TrackingInfo {
869 source: t.source,
870 position: t.position,
871 }),
872 Some(cancel_signal_for_task.clone()),
873 )
874 .await;
875
876 match res {
877 Ok(position) => Ok(Response::new(umadb_proto::v1::AppendResponse { position })),
878 Err(e) => Err(status_from_dcb_error(e)),
879 }
880 }
881
882 async fn head(
883 &self,
884 request: Request<umadb_proto::v1::HeadRequest>,
885 ) -> Result<Response<umadb_proto::v1::HeadResponse>, Status> {
886 self.enforce_api_key(request.metadata())?;
888 let request_handler = self.request_handler.clone();
892 let res = tokio::task::spawn_blocking(move || request_handler.head())
893 .await
894 .map_err(|e| status_from_dcb_error(DcbError::InternalError(e.to_string())))?;
895 match res {
896 Ok(position) => {
897 Ok(Response::new(umadb_proto::v1::HeadResponse { position }))
899 }
900 Err(e) => Err(status_from_dcb_error(e)),
901 }
902 }
903
904 async fn get_tracking_info(
905 &self,
906 request: Request<umadb_proto::v1::TrackingRequest>,
907 ) -> Result<Response<umadb_proto::v1::TrackingResponse>, Status> {
908 self.enforce_api_key(request.metadata())?;
910 let req = request.into_inner();
911 let permit = match self.read_scan_semaphore.clone().acquire_owned().await {
915 Ok(permit) => permit,
916 Err(_) => {
917 return Err(status_from_dcb_error(DcbError::InternalError(
918 "read-scan semaphore closed".to_string(),
919 )));
920 }
921 };
922 let request_handler = self.request_handler.clone();
923 let res = tokio::task::spawn_blocking(move || {
924 let _permit = permit;
925 request_handler.get_tracking_info(req.source)
926 })
927 .await
928 .map_err(|e| status_from_dcb_error(DcbError::InternalError(e.to_string())))?;
929 match res {
930 Ok(position) => Ok(Response::new(umadb_proto::v1::TrackingResponse {
931 position,
932 })),
933 Err(e) => Err(status_from_dcb_error(e)),
934 }
935 }
936}
937
938enum WriterRequest {
940 Append {
941 events: Vec<DcbEvent>,
942 condition: Option<DcbAppendCondition>,
943 tracking_info: Option<TrackingInfo>,
944 response_tx: oneshot::Sender<DcbResult<u64>>,
945 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
946 },
947 Shutdown,
948}
949
950struct RequestHandler {
952 mvcc: Arc<Mvcc>,
953 head_watch_tx: watch::Sender<Option<u64>>,
954 writer_request_tx: mpsc::Sender<WriterRequest>,
955}
956
957impl RequestHandler {
958 fn new(storage_options: StorageOptions) -> DcbResult<Self> {
959 let (request_tx, mut request_rx) = mpsc::channel::<WriterRequest>(1024);
961
962 let mvcc = Arc::new(Mvcc::new(false, storage_options)?);
964
965 let init_head = {
967 let header_page = mvcc.get_latest_header_page()?;
968 let header = header_page.as_header_node()?;
969 let last = header.next_position.0.saturating_sub(1);
970 if last == 0 { None } else { Some(last) }
971 };
972 let (head_tx, _head_rx) = watch::channel::<Option<u64>>(init_head);
973
974 let mvcc_for_writer = mvcc.clone();
976 let head_tx_writer = head_tx.clone();
977 thread::spawn(move || {
978 let db = UmaDb::from_arc(mvcc_for_writer);
979
980 let rt = Runtime::new().unwrap();
982
983 rt.block_on(async {
985 while let Some(request) = request_rx.recv().await {
986 match request {
987 WriterRequest::Append {
988 events,
989 condition,
990 tracking_info,
991 response_tx,
992 cancel,
993 } => {
994 let mut total_events = 0;
999 total_events += events.len();
1000 let mvcc = &db.mvcc;
1003 let mut writer = match mvcc.writer() {
1004 Ok(writer) => writer,
1005 Err(err) => {
1006 let _ = response_tx.send(Err(err));
1007 continue;
1008 }
1009 };
1010
1011 let mut responders: Vec<oneshot::Sender<DcbResult<u64>>> = Vec::new();
1012 let mut results: Vec<DcbResult<u64>> = Vec::new();
1013
1014 let mut abort_idx: Option<usize> = None;
1016 let mut abort_err: Option<DcbError> = None;
1017
1018 responders.push(response_tx);
1019 let result = UmaDb::process_append_request(
1020 events,
1021 condition,
1022 tracking_info,
1023 mvcc,
1024 &mut writer,
1025 cancel,
1026 );
1027 match &result {
1029 Ok(_) => results.push(result),
1030 Err(e) if is_integrity_error(e) => {
1031 results.push(Err(clone_dcb_error(e)))
1032 }
1033 Err(e) if is_invalid_argument_error(e) => {
1034 results.push(Err(clone_dcb_error(e)))
1035 }
1036 Err(e) => {
1037 abort_idx = Some(0);
1038 abort_err = Some(clone_dcb_error(e));
1039 results.push(Err(clone_dcb_error(e)));
1040 }
1041 }
1042
1043 loop {
1047 if total_events >= APPEND_BATCH_MAX_EVENTS {
1048 break;
1049 }
1050 if abort_idx.is_some() {
1052 break;
1053 }
1054 match request_rx.try_recv() {
1055 Ok(WriterRequest::Append {
1056 events,
1057 condition,
1058 tracking_info,
1059 response_tx,
1060 cancel,
1061 }) => {
1062 let ev_len = events.len();
1063 let idx_in_batch = responders.len();
1064 responders.push(response_tx);
1065 let res_next = UmaDb::process_append_request(
1066 events,
1067 condition,
1068 tracking_info,
1069 mvcc,
1070 &mut writer,
1071 cancel,
1072 );
1073 match &res_next {
1074 Ok(_) => results.push(res_next),
1075 Err(e) if is_integrity_error(e) => {
1076 results.push(Err(clone_dcb_error(e)))
1077 }
1078 Err(e) if is_invalid_argument_error(e) => {
1079 results.push(Err(clone_dcb_error(e)))
1080 }
1081 Err(e) => {
1082 abort_idx = Some(idx_in_batch);
1083 abort_err = Some(clone_dcb_error(e));
1084 results.push(Err(clone_dcb_error(e)));
1085 }
1087 }
1088 total_events += ev_len;
1089 }
1090 Ok(WriterRequest::Shutdown) => {
1091 break;
1096 }
1097 Err(mpsc::error::TryRecvError::Empty) => {
1098 break;
1099 }
1100 Err(mpsc::error::TryRecvError::Disconnected) => break,
1101 }
1102 }
1103 if let (Some(failed_at), Some(orig_err)) = (abort_idx, abort_err) {
1106 let shadow = shadow_for_batch_abort(&orig_err);
1108 for (i, tx) in responders.into_iter().enumerate() {
1109 if i == failed_at {
1110 let _ = tx.send(Err(clone_dcb_error(&orig_err)));
1111 } else {
1112 let _ = tx.send(Err(clone_dcb_error(&shadow)));
1113 }
1114 }
1115 continue;
1117 }
1118
1119 let batch_result = match mvcc.commit(&mut writer) {
1121 Ok(_) => Ok(results),
1122 Err(err) => Err(err),
1123 };
1124
1125 match batch_result {
1126 Ok(results) => {
1127 for (res, tx) in results.into_iter().zip(responders.into_iter())
1129 {
1130 let _ = tx.send(res);
1131 }
1132 let last_committed = writer.next_position.0.saturating_sub(1);
1134 let new_head = if last_committed == 0 {
1135 None
1136 } else {
1137 Some(last_committed)
1138 };
1139 let _ = head_tx_writer.send(new_head);
1140 }
1141 Err(e) => {
1142 let total = responders.len();
1146 let mut iter = responders.into_iter();
1147 for _ in 0..total {
1148 if let Some(tx) = iter.next() {
1149 let _ = tx.send(Err(clone_dcb_error(&e)));
1150 }
1151 }
1152 }
1153 }
1154 }
1155 WriterRequest::Shutdown => {
1156 break;
1157 }
1158 }
1159 }
1160 });
1161 });
1162
1163 Ok(Self {
1164 mvcc,
1165 head_watch_tx: head_tx,
1166 writer_request_tx: request_tx,
1167 })
1168 }
1169
1170 fn read(
1171 &self,
1172 query: Option<DcbQuery>,
1173 start: Option<u64>,
1174 backwards: bool,
1175 limit: Option<u32>,
1176 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1177 ) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
1178 let reader = self.mvcc.reader()?;
1179 let db_head = if reader.next_position > Position(1) {
1180 Some(reader.next_position.0.saturating_sub(1))
1181 } else {
1182 None
1183 };
1184
1185 let q = query.unwrap_or(DcbQuery { items: vec![] });
1186 let start_position = start.map(Position);
1187
1188 let events = read_conditional(
1189 &self.mvcc,
1190 &std::collections::HashMap::new(),
1191 reader.events_tree_root_id,
1192 reader.tags_tree_root_id,
1193 q,
1194 start_position,
1195 backwards,
1196 limit,
1197 false,
1198 cancel,
1199 )
1200 .map_err(|e| match e {
1201 DcbError::CancelledByUser() => DcbError::CancelledByUser(),
1202 _ => DcbError::Corruption(format!("{e}")),
1203 })?;
1204
1205 Ok((events, db_head))
1206 }
1207
1208 fn head(&self) -> DcbResult<Option<u64>> {
1209 let header_page = self
1210 .mvcc
1211 .get_latest_header_page()
1212 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1213 let header = header_page
1214 .as_header_node()
1215 .map_err(|e| DcbError::Corruption(format!("{e}")))?;
1216 let last = header.next_position.0.saturating_sub(1);
1217 if last == 0 { Ok(None) } else { Ok(Some(last)) }
1218 }
1219
1220 fn get_tracking_info(&self, source: String) -> DcbResult<Option<u64>> {
1221 let db = UmaDb::from_arc(self.mvcc.clone());
1222 db.get_tracking_info(&source)
1223 }
1224
1225 pub async fn append(
1226 &self,
1227 events: Vec<DcbEvent>,
1228 condition: Option<DcbAppendCondition>,
1229 tracking_info: Option<TrackingInfo>,
1230 cancel: Option<Arc<std::sync::atomic::AtomicBool>>,
1231 ) -> DcbResult<u64> {
1232 let (response_tx, response_rx) = oneshot::channel();
1244
1245 self.writer_request_tx
1246 .send(WriterRequest::Append {
1247 events,
1248 condition,
1249 tracking_info,
1250 response_tx,
1251 cancel,
1252 })
1253 .await
1254 .map_err(|_| {
1255 DcbError::Io(std::io::Error::other(
1256 "failed to send append request to EventStore thread",
1257 ))
1258 })?;
1259
1260 response_rx.await.map_err(|_| {
1261 DcbError::Io(std::io::Error::other(
1262 "failed to receive append response from EventStore thread",
1263 ))
1264 })?
1265 }
1266
1267 fn watch_head(&self) -> watch::Receiver<Option<u64>> {
1268 self.head_watch_tx.subscribe()
1269 }
1270
1271 #[allow(dead_code)]
1272 async fn shutdown(&self) {
1273 let _ = self.writer_request_tx.send(WriterRequest::Shutdown).await;
1274 }
1275}
1276
1277impl Clone for RequestHandler {
1279 fn clone(&self) -> Self {
1280 Self {
1281 mvcc: self.mvcc.clone(),
1282 head_watch_tx: self.head_watch_tx.clone(),
1283 writer_request_tx: self.writer_request_tx.clone(),
1284 }
1285 }
1286}