use crate::journal::JournalHandle;
use crate::Result;
use std::path::Path;
use std::time::Duration;
pub(crate) const DEFAULT_LOG_BUFFER_KIB: u32 = 64;
pub(crate) const MIN_LOG_BUFFER_BYTES: u32 = 4 * 1024;
pub(crate) const MAX_LOG_BUFFER_BYTES: u32 = 64 * 1024 * 1024;
pub(crate) const DEFAULT_GROUP_COMMIT_WINDOW: Option<Duration> = Some(Duration::from_micros(500));
pub(crate) const DEFAULT_GROUP_COMMIT_MAX_BATCH: u32 = 8;
pub(crate) const MIN_GROUP_COMMIT_MAX_BATCH: u32 = 1;
pub(crate) const MAX_GROUP_COMMIT_MAX_BATCH: u32 = 4096;
pub(crate) const MAX_GROUP_COMMIT_WINDOW: Duration = Duration::from_millis(100);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WriteLifetimeHint {
Short,
Medium,
Long,
Extreme,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum SyncMode {
#[default]
Full,
Barrier,
}
#[derive(Debug, Clone)]
#[must_use]
pub struct JournalOptions {
pub(crate) direct: bool,
pub(crate) log_buffer_kib: u32,
pub(crate) group_commit_window: Option<Duration>,
pub(crate) group_commit_max_batch: u32,
pub(crate) sync_mode: SyncMode,
pub(crate) write_lifetime_hint: Option<WriteLifetimeHint>,
}
impl Default for JournalOptions {
fn default() -> Self {
Self::new()
}
}
impl JournalOptions {
pub fn new() -> Self {
Self {
direct: false,
log_buffer_kib: DEFAULT_LOG_BUFFER_KIB,
group_commit_window: DEFAULT_GROUP_COMMIT_WINDOW,
group_commit_max_batch: DEFAULT_GROUP_COMMIT_MAX_BATCH,
sync_mode: SyncMode::Full,
write_lifetime_hint: None,
}
}
pub fn direct(mut self, yes: bool) -> Self {
self.direct = yes;
self
}
pub fn log_buffer_kib(mut self, kib: u32) -> Self {
self.log_buffer_kib = kib.clamp(MIN_LOG_BUFFER_BYTES / 1024, MAX_LOG_BUFFER_BYTES / 1024);
self
}
pub fn group_commit_window(mut self, window: Option<Duration>) -> Self {
self.group_commit_window = match window {
Some(d) if d.is_zero() => None,
Some(d) if d > MAX_GROUP_COMMIT_WINDOW => Some(MAX_GROUP_COMMIT_WINDOW),
other => other,
};
self
}
pub fn group_commit_max_batch(mut self, max_batch: u32) -> Self {
self.group_commit_max_batch =
max_batch.clamp(MIN_GROUP_COMMIT_MAX_BATCH, MAX_GROUP_COMMIT_MAX_BATCH);
self
}
pub fn sync_mode(mut self, mode: SyncMode) -> Self {
self.sync_mode = mode;
self
}
pub fn write_lifetime_hint(mut self, hint: Option<WriteLifetimeHint>) -> Self {
self.write_lifetime_hint = hint;
self
}
}
pub(crate) fn open_with_options(path: &Path, options: JournalOptions) -> Result<JournalHandle> {
JournalHandle::open_with_options(path, options)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_buffered_64k() {
let o = JournalOptions::default();
assert!(!o.direct);
assert_eq!(o.log_buffer_kib, DEFAULT_LOG_BUFFER_KIB);
}
#[test]
fn direct_toggle_round_trips() {
let o = JournalOptions::new().direct(true);
assert!(o.direct);
let o = o.direct(false);
assert!(!o.direct);
}
#[test]
fn log_buffer_kib_is_clamped() {
let o = JournalOptions::new().log_buffer_kib(0);
assert_eq!(o.log_buffer_kib, MIN_LOG_BUFFER_BYTES / 1024);
let o = JournalOptions::new().log_buffer_kib(u32::MAX);
assert_eq!(o.log_buffer_kib, MAX_LOG_BUFFER_BYTES / 1024);
let o = JournalOptions::new().log_buffer_kib(256);
assert_eq!(o.log_buffer_kib, 256);
}
#[test]
fn defaults_match_expected_group_commit() {
let o = JournalOptions::default();
assert_eq!(o.group_commit_window, DEFAULT_GROUP_COMMIT_WINDOW);
assert_eq!(o.group_commit_max_batch, DEFAULT_GROUP_COMMIT_MAX_BATCH);
}
#[test]
fn group_commit_window_zero_becomes_none() {
let o = JournalOptions::new().group_commit_window(Some(Duration::ZERO));
assert_eq!(o.group_commit_window, None);
}
#[test]
fn group_commit_window_above_cap_is_clamped() {
let huge = Duration::from_secs(60);
let o = JournalOptions::new().group_commit_window(Some(huge));
assert_eq!(o.group_commit_window, Some(MAX_GROUP_COMMIT_WINDOW));
}
#[test]
fn group_commit_window_in_range_passes_through() {
let d = Duration::from_micros(750);
let o = JournalOptions::new().group_commit_window(Some(d));
assert_eq!(o.group_commit_window, Some(d));
}
#[test]
fn group_commit_window_none_disables_batching() {
let o = JournalOptions::new().group_commit_window(None);
assert_eq!(o.group_commit_window, None);
}
#[test]
fn group_commit_max_batch_clamped() {
let o = JournalOptions::new().group_commit_max_batch(0);
assert_eq!(o.group_commit_max_batch, MIN_GROUP_COMMIT_MAX_BATCH);
let o = JournalOptions::new().group_commit_max_batch(u32::MAX);
assert_eq!(o.group_commit_max_batch, MAX_GROUP_COMMIT_MAX_BATCH);
let o = JournalOptions::new().group_commit_max_batch(64);
assert_eq!(o.group_commit_max_batch, 64);
}
#[test]
fn default_sync_mode_is_full() {
let o = JournalOptions::default();
assert_eq!(o.sync_mode, SyncMode::Full);
}
#[test]
fn sync_mode_round_trips() {
let o = JournalOptions::new().sync_mode(SyncMode::Barrier);
assert_eq!(o.sync_mode, SyncMode::Barrier);
let o = o.sync_mode(SyncMode::Full);
assert_eq!(o.sync_mode, SyncMode::Full);
}
#[test]
fn default_write_lifetime_hint_is_none() {
let o = JournalOptions::default();
assert_eq!(o.write_lifetime_hint, None);
}
#[test]
fn write_lifetime_hint_round_trips() {
for hint in [
WriteLifetimeHint::Short,
WriteLifetimeHint::Medium,
WriteLifetimeHint::Long,
WriteLifetimeHint::Extreme,
] {
let o = JournalOptions::new().write_lifetime_hint(Some(hint));
assert_eq!(o.write_lifetime_hint, Some(hint));
}
let o = JournalOptions::new().write_lifetime_hint(None);
assert_eq!(o.write_lifetime_hint, None);
}
#[test]
fn sync_mode_is_copy_and_eq() {
let a = SyncMode::Full;
let b = a;
assert_eq!(a, b);
assert_ne!(SyncMode::Full, SyncMode::Barrier);
}
#[test]
fn write_lifetime_hint_is_copy_and_eq() {
let a = WriteLifetimeHint::Long;
let b = a;
assert_eq!(a, b);
assert_ne!(WriteLifetimeHint::Short, WriteLifetimeHint::Long);
}
}