Skip to content
Merged
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
59 changes: 49 additions & 10 deletions src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,22 @@ fn is_missing_working_dir_error(error: &AutterError) -> bool {
}
}

/// Returns true when the error comes from a full disk on the host (ENOSPC on
/// Unix, ERROR_DISK_FULL on Windows). That is an environment condition on the
/// user's machine, not a daemon fault, so side-effect failures with this cause
/// log at warn level and do not become error-tracking exceptions.
fn is_storage_full_error(error: &AutterError) -> bool {
matches!(error, AutterError::IoError(io_error) if io_error.kind() == std::io::ErrorKind::StorageFull)
}

fn log_side_effect_error(error: &AutterError, family: &str, seq: u64, message: &str) {
if is_storage_full_error(error) {
tracing::warn!(%error, %family, seq, "{message}: host disk is full");
} else {
tracing::error!(%error, %family, seq, "{message}");
}
}

/// Returns true when a side effect failed because its repository was deleted
/// mid-operation. git reports this race in more shapes than
/// `is_missing_working_dir_error` knows (for example `git diff` exits 1 with
Expand Down Expand Up @@ -6100,11 +6116,11 @@ impl ActorDaemonCoordinator {
Ok(Ok((applied, side_effect_result))) => {
if let Err(error) = &side_effect_result {
let _ = self.record_side_effect_error(family, order, error);
tracing::error!(
%error,
%family,
seq = applied.seq,
"command side effect failed"
log_side_effect_error(
error,
family,
applied.seq,
"command side effect failed",
);
}
if let Err(error) = self.append_command_completion_log(
Expand Down Expand Up @@ -7864,11 +7880,11 @@ impl ActorDaemonCoordinator {
let _ = self.end_family_effect(&family);
if let Err(error) = result {
let _ = self.record_side_effect_error(&family, applied.seq, &error);
tracing::error!(
%error,
%family,
seq = applied.seq,
"async side-effect error"
log_side_effect_error(
&error,
&family,
applied.seq,
"async side-effect error",
);
} else if let Err(error) =
self.append_command_completion_log(&family, &applied, &Ok(()), applied.seq)
Expand Down Expand Up @@ -9448,6 +9464,29 @@ mod tests {
)));
}

#[test]
fn storage_full_error_detects_disk_full_io_errors() {
let disk_full = AutterError::IoError(std::io::Error::new(
std::io::ErrorKind::StorageFull,
"No space left on device (os error 28)",
));
assert!(is_storage_full_error(&disk_full));

#[cfg(unix)]
assert!(is_storage_full_error(&AutterError::IoError(
std::io::Error::from_raw_os_error(28)
)));

let permission_denied = AutterError::IoError(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"access denied",
));
assert!(!is_storage_full_error(&permission_denied));
assert!(!is_storage_full_error(&AutterError::Generic(
"No space left on device".to_string()
)));
}

fn init_fake_repo(worktree: &Path) {
fs::create_dir_all(worktree.join(".git").join("objects")).unwrap();
fs::write(worktree.join(".git").join("HEAD"), "ref: refs/heads/main\n").unwrap();
Expand Down
Loading