Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions src/cortex-storage/src/sessions/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,67 @@ impl SessionStorage {
}

/// Save a session to disk.
///
/// This function ensures data durability by calling sync_all() (fsync)
/// after writing to prevent data loss on crash or forceful termination.
pub async fn save_session(&self, session: &StoredSession) -> Result<()> {
let path = self.paths.session_path(&session.id);
let content = serde_json::to_string_pretty(session)?;
fs::write(&path, content).await?;

// Write content to file
let file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&path)
.await?;

use tokio::io::AsyncWriteExt;
let mut file = file;
file.write_all(content.as_bytes()).await?;
file.flush().await?;

// Ensure data is durably written to disk (fsync) to prevent data loss on crash
file.sync_all().await?;

// Sync parent directory on Unix for crash safety (ensures directory entry is persisted)
#[cfg(unix)]
{
if let Some(parent) = path.parent() {
if let Ok(dir) = fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}
}

debug!(session_id = %session.id, "Session saved");
Ok(())
}

/// Save a session synchronously.
///
/// This function ensures data durability by calling sync_all() (fsync)
/// after writing to prevent data loss on crash or forceful termination.
pub fn save_session_sync(&self, session: &StoredSession) -> Result<()> {
let path = self.paths.session_path(&session.id);
let file = std::fs::File::create(&path)?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, session)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer_pretty(&mut writer, session)?;
writer.flush()?;

// Ensure data is durably written to disk (fsync) to prevent data loss on crash
writer.get_ref().sync_all()?;

// Sync parent directory on Unix for crash safety (ensures directory entry is persisted)
#[cfg(unix)]
{
if let Some(parent) = path.parent() {
if let Ok(dir) = std::fs::File::open(parent) {
let _ = dir.sync_all();
}
}
}

debug!(session_id = %session.id, "Session saved");
Ok(())
}
Expand Down
41 changes: 39 additions & 2 deletions src/cortex-tui/src/session/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,47 @@ impl SessionStorage {
// ========================================================================

/// Saves session metadata.
///
/// Uses atomic write (temp file + rename) with fsync for durability.
/// This prevents data loss on crash or forceful termination.
pub fn save_meta(&self, meta: &SessionMeta) -> Result<()> {
self.ensure_session_dir(&meta.id)?;

let path = self.meta_path(&meta.id);
let content =
serde_json::to_string_pretty(meta).context("Failed to serialize session metadata")?;

// Atomic write: write to temp file then rename
// Atomic write: write to temp file, fsync, then rename
let temp_path = path.with_extension("json.tmp");
fs::write(&temp_path, &content)

// Write and sync temp file
let file = File::create(&temp_path)
.with_context(|| format!("Failed to create temp metadata file: {:?}", temp_path))?;
let mut writer = BufWriter::new(file);
writer
.write_all(content.as_bytes())
.with_context(|| format!("Failed to write temp metadata file: {:?}", temp_path))?;
writer.flush()?;

// Ensure data is durably written to disk (fsync) before rename
writer.get_ref().sync_all().with_context(|| {
format!("Failed to sync temp metadata file to disk: {:?}", temp_path)
})?;

// Rename temp file to final path
fs::rename(&temp_path, &path)
.with_context(|| format!("Failed to rename metadata file: {:?}", path))?;

// Sync parent directory on Unix for crash safety (ensures directory entry is persisted)
#[cfg(unix)]
{
if let Some(parent) = path.parent() {
if let Ok(dir) = File::open(parent) {
let _ = dir.sync_all();
}
}
}

Ok(())
}

Expand Down Expand Up @@ -212,6 +239,16 @@ impl SessionStorage {
fs::rename(&temp_path, &path)
.with_context(|| format!("Failed to rename history file: {:?}", path))?;

// Sync parent directory on Unix for crash safety (ensures directory entry is persisted)
#[cfg(unix)]
{
if let Some(parent) = path.parent() {
if let Ok(dir) = File::open(parent) {
let _ = dir.sync_all();
}
}
}

Ok(())
}

Expand Down
Loading