1#![warn(missing_docs)]
18
19use std::convert::AsRef;
20use std::ops::Deref;
21use std::path::{Path, PathBuf};
22use std::time::Instant;
23
24use itertools::Itertools;
25use mz_ore::cast::CastFrom;
26use mz_ore::error::ErrorExt;
27use mz_ore::metrics::{DeleteOnDropCounter, DeleteOnDropHistogram};
28use mz_ore::retry::{Retry, RetryResult};
29use prometheus::core::AtomicU64;
30use rocksdb::merge_operator::MergeOperandsIter;
31use rocksdb::{DB, Env, Error as RocksDBError, ErrorKind, Options as RocksDBOptions, WriteOptions};
32use serde::Serialize;
33use serde::de::DeserializeOwned;
34use tokio::sync::{mpsc, oneshot};
35
36pub mod config;
37pub use config::{RocksDBConfig, RocksDBTuningParameters, defaults};
38
39use crate::config::WriteBufferManagerHandle;
40
41type Diff = mz_ore::Overflowing<i64>;
42
43#[derive(Debug, thiserror::Error)]
45pub enum Error {
46 #[error(transparent)]
48 RocksDB(#[from] RocksDBError),
49
50 #[error("RocksDB thread has been shut down or errored")]
53 RocksDBThreadGoneAway,
54
55 #[error("failed to decode value")]
57 DecodeError(#[from] bincode::Error),
58
59 #[error("tokio thread panicked")]
61 TokioPanic(#[from] tokio::task::JoinError),
62
63 #[error("failed to cleanup in time")]
65 CleanupTimeout(#[from] tokio::time::error::Elapsed),
66
67 #[error("error with value: {0}")]
69 ValueError(String),
70}
71
72pub struct ValueIterator<'a, O, V>
76where
77 O: bincode::Options + Copy + Send + Sync + 'static,
78 V: DeserializeOwned + Serialize + Send + Sync + 'static,
79{
80 iter: std::iter::Chain<std::option::IntoIter<&'a [u8]>, MergeOperandsIter<'a>>,
81 bincode: &'a O,
82 v: std::marker::PhantomData<V>,
83}
84
85impl<O, V> Iterator for ValueIterator<'_, O, V>
86where
87 O: bincode::Options + Copy + Send + Sync + 'static,
88 V: DeserializeOwned + Serialize + Send + Sync + 'static,
89{
90 type Item = V;
91
92 fn next(&mut self) -> Option<Self::Item> {
93 self.iter
94 .next()
95 .map(|v| self.bincode.deserialize(v).unwrap())
96 }
97}
98
99pub type StubMergeOperator<V> =
102 fn(key: &[u8], operands: ValueIterator<bincode::DefaultOptions, V>) -> V;
103
104pub struct InstanceOptions<O, V, F> {
107 pub cleanup_on_new: bool,
110
111 pub cleanup_tries: usize,
113
114 pub use_wal: bool,
118
119 pub env: Env,
121
122 pub bincode: O,
124
125 pub merge_operator: Option<(String, F)>,
129
130 v: std::marker::PhantomData<V>,
131}
132
133impl<O, V, F> InstanceOptions<O, V, F>
134where
135 O: bincode::Options + Copy + Send + Sync + 'static,
136 V: DeserializeOwned + Serialize + Send + Sync + 'static,
137 F: for<'a> Fn(&'a [u8], ValueIterator<'a, O, V>) -> V + Copy + Send + Sync + 'static,
138{
139 pub fn new(
141 env: rocksdb::Env,
142 cleanup_tries: usize,
143 merge_operator: Option<(String, F)>,
144 bincode: O,
145 ) -> Self {
146 InstanceOptions {
147 cleanup_on_new: true,
148 cleanup_tries,
149 use_wal: false,
150 env,
151 merge_operator,
152 bincode,
153 v: std::marker::PhantomData,
154 }
155 }
156
157 fn as_rocksdb_options(
158 &self,
159 tuning_config: &RocksDBConfig,
160 ) -> (RocksDBOptions, Option<WriteBufferManagerHandle>) {
161 let mut options = rocksdb::Options::default();
163 options.create_if_missing(true);
164
165 options.set_env(&self.env);
167
168 if let Some((fn_name, merge_fn)) = &self.merge_operator {
169 let bincode = self.bincode.clone();
170 let merge_fn = merge_fn.clone();
171 options.set_merge_operator_associative(fn_name, move |key, existing, operands| {
176 let operands = ValueIterator {
177 iter: existing.into_iter().chain(operands.iter()),
178 bincode: &bincode,
179 v: std::marker::PhantomData::<V>,
180 };
181 let result = merge_fn(key, operands);
182 Some(bincode.serialize(&result).unwrap())
185 });
186 }
187
188 let write_buffer_handle = config::apply_to_options(tuning_config, &mut options);
189 (options, write_buffer_handle)
192 }
193
194 fn as_rocksdb_write_options(&self) -> WriteOptions {
195 let mut wo = rocksdb::WriteOptions::new();
196 wo.disable_wal(!self.use_wal);
197 wo
198 }
199}
200
201pub struct RocksDBSharedMetrics {
204 pub multi_get_latency: DeleteOnDropHistogram<Vec<String>>,
206 pub multi_put_latency: DeleteOnDropHistogram<Vec<String>>,
208}
209
210pub struct RocksDBInstanceMetrics {
213 pub multi_get_size: DeleteOnDropCounter<AtomicU64, Vec<String>>,
215 pub multi_get_result_count: DeleteOnDropCounter<AtomicU64, Vec<String>>,
217 pub multi_get_result_bytes: DeleteOnDropCounter<AtomicU64, Vec<String>>,
219 pub multi_get_count: DeleteOnDropCounter<AtomicU64, Vec<String>>,
221 pub multi_put_count: DeleteOnDropCounter<AtomicU64, Vec<String>>,
223 pub multi_put_size: DeleteOnDropCounter<AtomicU64, Vec<String>>,
225}
226
227#[derive(Default, Debug)]
229pub struct MultiGetResult {
230 pub processed_gets: u64,
232 pub processed_gets_size: u64,
234 pub returned_gets: u64,
236}
237
238#[derive(Debug, Default, Clone)]
240pub struct GetResult<V> {
241 pub value: V,
243 pub size: u64,
246}
247
248#[derive(Default, Debug)]
250pub struct MultiUpdateResult {
251 pub processed_updates: u64,
253 pub size_written: u64,
256 pub size_diff: Option<Diff>,
260}
261
262#[derive(Debug)]
264pub enum KeyUpdate<V> {
265 Put(V),
267 Merge(V),
270 Delete,
272}
273
274#[derive(Debug)]
275enum Command<K, V> {
276 MultiGet {
277 batch: Vec<K>,
278 results_scratch: Vec<Option<GetResult<V>>>,
280 response_sender: oneshot::Sender<
281 Result<
282 (
283 MultiGetResult,
284 Vec<K>,
286 Vec<Option<GetResult<V>>>,
287 ),
288 Error,
289 >,
290 >,
291 },
292 MultiUpdate {
293 batch: Vec<(K, KeyUpdate<V>, Option<Diff>)>,
297 response_sender: oneshot::Sender<
299 Result<(MultiUpdateResult, Vec<(K, KeyUpdate<V>, Option<Diff>)>), Error>,
300 >,
301 },
302 Shutdown {
303 done_sender: oneshot::Sender<()>,
304 },
305 ManualCompaction {
306 done_sender: oneshot::Sender<()>,
307 },
308}
309
310pub struct RocksDBInstance<K, V> {
312 tx: mpsc::Sender<Command<K, V>>,
313
314 multi_get_scratch: Vec<K>,
317
318 multi_get_results_scratch: Vec<Option<GetResult<V>>>,
321
322 multi_update_scratch: Vec<(K, KeyUpdate<V>, Option<Diff>)>,
325
326 dynamic_config: config::RocksDBDynamicConfig,
328
329 pub supports_merges: bool,
332
333 handle: Option<std::thread::JoinHandle<()>>,
335}
336
337impl<K, V> RocksDBInstance<K, V>
338where
339 K: AsRef<[u8]> + Send + Sync + 'static,
340 V: Serialize + DeserializeOwned + Send + Sync + 'static,
341{
342 pub fn new<M, O, IM, F>(
350 instance_path: &Path,
351 options: InstanceOptions<O, V, F>,
352 tuning_config: RocksDBConfig,
353 shared_metrics: M,
354 instance_metrics: IM,
355 ) -> Result<Self, Error>
356 where
357 O: bincode::Options + Copy + Send + Sync + 'static,
358 M: Deref<Target = RocksDBSharedMetrics> + Send + 'static,
359 IM: Deref<Target = RocksDBInstanceMetrics> + Send + 'static,
360 F: for<'a> Fn(&'a [u8], ValueIterator<'a, O, V>) -> V + Copy + Send + Sync + 'static,
361 {
362 let dynamic_config = tuning_config.dynamic.clone();
363 let supports_merges = options.merge_operator.is_some();
364
365 let (tx, rx): (mpsc::Sender<Command<K, V>>, _) = mpsc::channel(10);
367
368 let instance_path = instance_path.to_owned();
369 let handle = std::thread::spawn(move || {
375 rocksdb_core_loop(
376 options,
377 tuning_config,
378 instance_path,
379 rx,
380 shared_metrics,
381 instance_metrics,
382 )
383 });
384
385 Ok(Self {
386 tx,
387 multi_get_scratch: Vec::new(),
388 multi_get_results_scratch: Vec::new(),
389 multi_update_scratch: Vec::new(),
390 dynamic_config,
391 supports_merges,
392 handle: Some(handle),
393 })
394 }
395
396 pub fn take_core_loop_handle(&mut self) -> Option<std::thread::JoinHandle<()>> {
399 self.handle.take()
400 }
401
402 pub async fn multi_get<'r, G, R, Ret, Placement>(
406 &mut self,
407 gets: G,
408 results_out: R,
409 placement: Placement,
410 ) -> Result<MultiGetResult, Error>
411 where
412 G: IntoIterator<Item = K>,
413 R: IntoIterator<Item = &'r mut Ret>,
414 Ret: 'r,
415 Placement: Fn(Option<GetResult<V>>) -> Ret,
416 {
417 let batch_size = self.dynamic_config.batch_size();
418 let mut stats = MultiGetResult::default();
419
420 let mut gets = gets.into_iter().peekable();
421 if gets.peek().is_some() {
422 let gets = gets.chunks(batch_size);
423 let results_out = results_out.into_iter().chunks(batch_size);
424
425 for (gets, results_out) in gets.into_iter().zip_eq(&results_out) {
426 let ret = self.multi_get_inner(gets, results_out, &placement).await?;
427 stats.processed_gets += ret.processed_gets;
428 stats.processed_gets_size += ret.processed_gets_size;
429 stats.returned_gets += ret.returned_gets;
430 }
431 }
432
433 Ok(stats)
434 }
435
436 async fn multi_get_inner<'r, G, R, Ret, Placement>(
437 &mut self,
438 gets: G,
439 results_out: R,
440 placement: &Placement,
441 ) -> Result<MultiGetResult, Error>
442 where
443 G: IntoIterator<Item = K>,
444 R: IntoIterator<Item = &'r mut Ret>,
445 Ret: 'r,
446 Placement: Fn(Option<GetResult<V>>) -> Ret,
447 {
448 let mut multi_get_vec = std::mem::take(&mut self.multi_get_scratch);
449 let mut results_vec = std::mem::take(&mut self.multi_get_results_scratch);
450 multi_get_vec.clear();
451 results_vec.clear();
452
453 multi_get_vec.extend(gets);
454 if multi_get_vec.is_empty() {
455 self.multi_get_scratch = multi_get_vec;
456 self.multi_get_results_scratch = results_vec;
457 return Ok(MultiGetResult {
458 processed_gets: 0,
459 processed_gets_size: 0,
460 returned_gets: 0,
461 });
462 }
463
464 let (tx, rx) = oneshot::channel();
465 self.tx
466 .send(Command::MultiGet {
467 batch: multi_get_vec,
468 results_scratch: results_vec,
469 response_sender: tx,
470 })
471 .await
472 .map_err(|_| Error::RocksDBThreadGoneAway)?;
473
474 match rx.await.map_err(|_| Error::RocksDBThreadGoneAway)? {
476 Ok((ret, get_scratch, mut results_scratch)) => {
477 for (place, get) in results_out.into_iter().zip_eq(results_scratch.drain(..)) {
478 *place = placement(get);
479 }
480 self.multi_get_scratch = get_scratch;
481 self.multi_get_results_scratch = results_scratch;
482 Ok(ret)
483 }
484 Err(e) => {
485 Err(e)
487 }
488 }
489 }
490
491 pub async fn multi_update<P>(&mut self, puts: P) -> Result<MultiUpdateResult, Error>
498 where
499 P: IntoIterator<Item = (K, KeyUpdate<V>, Option<Diff>)>,
500 {
501 let batch_size = self.dynamic_config.batch_size();
502 let mut stats = MultiUpdateResult::default();
503
504 let mut puts = puts.into_iter().peekable();
505 if puts.peek().is_some() {
506 let puts = puts.chunks(batch_size);
507
508 for puts in puts.into_iter() {
509 let ret = self.multi_update_inner(puts).await?;
510 stats.processed_updates += ret.processed_updates;
511 stats.size_written += ret.size_written;
512 if let Some(diff) = ret.size_diff {
513 stats.size_diff = Some(stats.size_diff.unwrap_or(Diff::ZERO) + diff);
514 }
515 }
516 }
517
518 Ok(stats)
519 }
520
521 async fn multi_update_inner<P>(&mut self, updates: P) -> Result<MultiUpdateResult, Error>
522 where
523 P: IntoIterator<Item = (K, KeyUpdate<V>, Option<Diff>)>,
524 {
525 let mut multi_put_vec = std::mem::take(&mut self.multi_update_scratch);
526 multi_put_vec.clear();
527
528 multi_put_vec.extend(updates);
529 if multi_put_vec.is_empty() {
530 self.multi_update_scratch = multi_put_vec;
531 return Ok(MultiUpdateResult {
532 processed_updates: 0,
533 size_written: 0,
534 size_diff: None,
535 });
536 }
537
538 let (tx, rx) = oneshot::channel();
539 self.tx
540 .send(Command::MultiUpdate {
541 batch: multi_put_vec,
542 response_sender: tx,
543 })
544 .await
545 .map_err(|_| Error::RocksDBThreadGoneAway)?;
546
547 match rx.await.map_err(|_| Error::RocksDBThreadGoneAway)? {
549 Ok((ret, scratch)) => {
550 self.multi_update_scratch = scratch;
551 Ok(ret)
552 }
553 Err(e) => {
554 Err(e)
556 }
557 }
558 }
559
560 pub async fn manual_compaction(&self) -> Result<(), Error> {
562 let (tx, rx) = oneshot::channel();
563 self.tx
564 .send(Command::ManualCompaction { done_sender: tx })
565 .await
566 .map_err(|_| Error::RocksDBThreadGoneAway)?;
567
568 rx.await.map_err(|_| Error::RocksDBThreadGoneAway)
569 }
570
571 pub async fn close(self) -> Result<(), Error> {
574 let (tx, rx) = oneshot::channel();
575 self.tx
576 .send(Command::Shutdown { done_sender: tx })
577 .await
578 .map_err(|_| Error::RocksDBThreadGoneAway)?;
579
580 let _ = rx.await;
581
582 Ok(())
583 }
584}
585
586fn rocksdb_core_loop<K, V, M, O, IM, F>(
587 options: InstanceOptions<O, V, F>,
588 tuning_config: RocksDBConfig,
589 instance_path: PathBuf,
590 mut cmd_rx: mpsc::Receiver<Command<K, V>>,
591 shared_metrics: M,
592 instance_metrics: IM,
593) where
594 K: AsRef<[u8]> + Send + Sync + 'static,
595 V: Serialize + DeserializeOwned + Send + Sync + 'static,
596 M: Deref<Target = RocksDBSharedMetrics> + Send + 'static,
597 O: bincode::Options + Copy + Send + Sync + 'static,
598 F: for<'a> Fn(&'a [u8], ValueIterator<'a, O, V>) -> V + Send + Sync + Copy + 'static,
599 IM: Deref<Target = RocksDBInstanceMetrics> + Send + 'static,
600{
601 if options.cleanup_on_new {
602 let retry = mz_ore::retry::Retry::default()
606 .max_tries(options.cleanup_tries)
607 .initial_backoff(std::time::Duration::from_secs(1));
609
610 if let Err(e) = std::fs::create_dir_all(&instance_path) {
617 tracing::warn!(
618 "failed to create rocksdb dir on creation {}: {}",
619 instance_path.display(),
620 e.display_with_causes(),
621 );
622 }
623 let destroy_result = retry.retry(|_rs| {
624 if let Err(e) = DB::destroy(&destroy_options(&options.env), &*instance_path) {
625 tracing::warn!(
626 "failed to cleanup rocksdb dir on creation {}: {}",
627 instance_path.display(),
628 e.display_with_causes(),
629 );
630 RetryResult::RetryableErr(Error::from(e))
631 } else {
632 RetryResult::Ok(())
633 }
634 });
635 if let Err(e) = destroy_result {
636 tracing::error!(
637 "retries exhausted trying to cleanup rocksdb dir on creation {}: {}",
638 instance_path.display(),
639 e.display_with_causes(),
640 );
641 return;
642 }
643 }
644
645 let retry_max_duration = tuning_config.retry_max_duration;
646
647 let (rocksdb_options, write_buffer_handle) = options.as_rocksdb_options(&tuning_config);
652 tracing::info!(
653 "Starting rocksdb at {:?} with write_buffer_manager: {:?}",
654 instance_path,
655 write_buffer_handle
656 );
657
658 let retry_result = Retry::default()
659 .max_duration(retry_max_duration)
660 .retry(|_| match DB::open(&rocksdb_options, &instance_path) {
661 Ok(db) => RetryResult::Ok(db),
662 Err(e) => match e.kind() {
663 ErrorKind::TryAgain => RetryResult::RetryableErr(Error::RocksDB(e)),
664 _ => RetryResult::FatalErr(Error::RocksDB(e)),
665 },
666 });
667
668 let db: DB = match retry_result {
669 Ok(db) => db,
670 Err(e) => {
671 tracing::error!(
672 "failed to create rocksdb at {}: {}",
673 instance_path.display(),
674 e.display_with_causes(),
675 );
676 return;
677 }
678 };
679
680 let mut encoded_batch_buffers: Vec<Option<Vec<u8>>> = Vec::new();
681 let mut encoded_batch: Vec<(K, KeyUpdate<Vec<u8>>)> = Vec::new();
682
683 let wo = options.as_rocksdb_write_options();
684 while let Some(cmd) = cmd_rx.blocking_recv() {
685 match cmd {
686 Command::Shutdown { done_sender } => {
687 shutdown_and_cleanup(db, &instance_path, &options.env);
688 drop(write_buffer_handle);
689 let _ = done_sender.send(());
690 return;
691 }
692 Command::ManualCompaction { done_sender } => {
693 db.compact_range::<&[u8], &[u8]>(None, None);
695 let _ = done_sender.send(());
696 }
697 Command::MultiGet {
698 mut batch,
699 mut results_scratch,
700 response_sender,
701 } => {
702 let batch_size = batch.len();
703
704 let now = Instant::now();
706 let retry_result = Retry::default()
707 .max_duration(retry_max_duration)
708 .retry(|_| {
709 let gets = db.multi_get(batch.iter());
710 let latency = now.elapsed();
711
712 let gets: Result<Vec<_>, _> = gets.into_iter().collect();
713 match gets {
714 Ok(gets) => {
715 shared_metrics
716 .multi_get_latency
717 .observe(latency.as_secs_f64());
718 instance_metrics
719 .multi_get_size
720 .inc_by(batch_size.try_into().unwrap());
721 instance_metrics.multi_get_count.inc();
722
723 RetryResult::Ok(gets)
724 }
725 Err(e) => match e.kind() {
726 ErrorKind::TryAgain => RetryResult::RetryableErr(Error::RocksDB(e)),
727 _ => RetryResult::FatalErr(Error::RocksDB(e)),
728 },
729 }
730 });
731
732 let _ = match retry_result {
733 Ok(gets) => {
734 let processed_gets: u64 = gets.len().try_into().unwrap();
735 let mut processed_gets_size = 0;
736 let mut returned_gets: u64 = 0;
737 for previous_value in gets {
738 let get_result = match previous_value {
739 Some(previous_value) => {
740 match options.bincode.deserialize(&previous_value) {
741 Ok(value) => {
742 let size = u64::cast_from(previous_value.len());
743 processed_gets_size += size;
744 returned_gets += 1;
745 Some(GetResult { value, size })
746 }
747 Err(e) => {
748 let _ =
749 response_sender.send(Err(Error::DecodeError(e)));
750 return;
751 }
752 }
753 }
754 None => None,
755 };
756 results_scratch.push(get_result);
757 }
758
759 instance_metrics
760 .multi_get_result_count
761 .inc_by(returned_gets);
762 instance_metrics
763 .multi_get_result_bytes
764 .inc_by(processed_gets_size);
765 batch.clear();
766 response_sender.send(Ok((
767 MultiGetResult {
768 processed_gets,
769 processed_gets_size,
770 returned_gets,
771 },
772 batch,
773 results_scratch,
774 )))
775 }
776 Err(e) => response_sender.send(Err(e)),
777 };
778 }
779 Command::MultiUpdate {
780 mut batch,
781 response_sender,
782 } => {
783 let batch_size = batch.len();
784
785 let mut ret = MultiUpdateResult {
786 processed_updates: 0,
787 size_written: 0,
788 size_diff: None,
789 };
790
791 let buf_size = encoded_batch_buffers.len();
793 for _ in buf_size..batch_size {
794 encoded_batch_buffers.push(Some(Vec::new()));
795 }
796 if tuning_config.shrink_buffers_by_ratio > 0 {
799 let reduced_capacity =
800 encoded_batch_buffers.capacity() / tuning_config.shrink_buffers_by_ratio;
801 if reduced_capacity > batch_size {
802 encoded_batch_buffers.truncate(reduced_capacity);
803 encoded_batch_buffers.shrink_to(reduced_capacity);
804
805 encoded_batch.truncate(reduced_capacity);
806 encoded_batch.shrink_to(reduced_capacity);
807 }
808 }
809
810 let Some(encode_bufs) = encoded_batch_buffers.get_mut(0..batch_size) else {
811 panic!(
812 "Encoded buffers over-truncated. expected >= {batch_size} actual: {}",
813 encoded_batch_buffers.len()
814 );
815 };
816
817 for ((key, value, diff), encode_buf) in batch.drain(..).zip_eq(encode_bufs) {
819 ret.processed_updates += 1;
820
821 match &value {
822 update_type @ (KeyUpdate::Put(update) | KeyUpdate::Merge(update)) => {
823 let mut encode_buf =
824 encode_buf.take().expect("encode_buf should not be empty");
825 encode_buf.clear();
826 match options
827 .bincode
828 .serialize_into::<&mut Vec<u8>, _>(&mut encode_buf, update)
829 {
830 Ok(()) => {
831 ret.size_written += u64::cast_from(encode_buf.len());
832 if let Some(diff) = diff {
834 let encoded_len = Diff::try_from(encode_buf.len())
835 .expect("less than i64 size");
836 ret.size_diff = Some(
837 ret.size_diff.unwrap_or(Diff::ZERO)
838 + (diff * encoded_len),
839 );
840 }
841 }
842 Err(e) => {
843 let _ = response_sender.send(Err(Error::DecodeError(e)));
844 return;
845 }
846 };
847 if matches!(update_type, KeyUpdate::Put(_)) {
848 encoded_batch.push((key, KeyUpdate::Put(encode_buf)));
849 } else {
850 encoded_batch.push((key, KeyUpdate::Merge(encode_buf)));
851 }
852 }
853 KeyUpdate::Delete => encoded_batch.push((key, KeyUpdate::Delete)),
854 }
855 }
856 let now = Instant::now();
858 let retry_result = Retry::default()
859 .max_duration(retry_max_duration)
860 .retry(|_| {
861 let mut writes = rocksdb::WriteBatch::default();
862
863 for (key, value) in encoded_batch.iter() {
864 match value {
865 KeyUpdate::Put(update) => writes.put(key, update),
866 KeyUpdate::Merge(update) => writes.merge(key, update),
867 KeyUpdate::Delete => writes.delete(key),
868 }
869 }
870
871 match db.write_opt(writes, &wo) {
872 Ok(()) => {
873 let latency = now.elapsed();
874 shared_metrics
875 .multi_put_latency
876 .observe(latency.as_secs_f64());
877 instance_metrics
878 .multi_put_size
879 .inc_by(batch_size.try_into().unwrap());
880 instance_metrics.multi_put_count.inc();
881 RetryResult::Ok(())
882 }
883 Err(e) => match e.kind() {
884 ErrorKind::TryAgain => RetryResult::RetryableErr(Error::RocksDB(e)),
885 _ => RetryResult::FatalErr(Error::RocksDB(e)),
886 },
887 }
888 });
889
890 for (i, (_, encoded_buffer)) in encoded_batch.drain(..).enumerate() {
892 if let KeyUpdate::Put(encoded_buffer) | KeyUpdate::Merge(encoded_buffer) =
893 encoded_buffer
894 {
895 encoded_batch_buffers[i] = Some(encoded_buffer);
896 }
897 }
898
899 match retry_result {
900 Ok(()) => {
901 batch.clear();
902 let _ = response_sender.send(Ok((ret, batch)));
903 }
904 Err(e) => {
905 let db_err = match e {
906 Error::RocksDB(ref inner) => Some(inner.clone()),
907 _ => None,
908 };
909 let _ = response_sender.send(Err(e));
910 if let Some(db_err) = db_err {
911 if !matches!(db_err.kind(), ErrorKind::TryAgain) {
912 tracing::warn!(
913 "exiting on fatal rocksdb error at {}: {}",
914 instance_path.display(),
915 db_err.display_with_causes(),
916 );
917 break;
918 }
919 }
920 }
921 };
922 }
923 }
924 }
925 shutdown_and_cleanup(db, &instance_path, &options.env);
926}
927
928fn destroy_options(env: &Env) -> RocksDBOptions {
935 let mut options = RocksDBOptions::default();
936 options.set_env(env);
937 options
938}
939
940fn shutdown_and_cleanup(db: DB, instance_path: &PathBuf, env: &Env) {
941 db.cancel_all_background_work(true);
943 drop(db);
944 tracing::info!("dropped rocksdb at {}", instance_path.display());
945
946 if let Err(e) = DB::destroy(&destroy_options(env), &*instance_path) {
948 tracing::warn!(
949 "failed to cleanup rocksdb dir at {}: {}",
950 instance_path.display(),
951 e.display_with_causes(),
952 );
953 }
954}