diff --git a/aw-datastore/src/lib.rs b/aw-datastore/src/lib.rs index 69c47618..af89d3a1 100644 --- a/aw-datastore/src/lib.rs +++ b/aw-datastore/src/lib.rs @@ -39,4 +39,6 @@ pub enum DatastoreError { // Errors specific to when migrate is disabled Uninitialized(String), OldDbVersion(String), + // Database commit failed (e.g., disk full) + CommitFailed(String), } diff --git a/aw-datastore/src/worker.rs b/aw-datastore/src/worker.rs index 18eaf665..08611747 100644 --- a/aw-datastore/src/worker.rs +++ b/aw-datastore/src/worker.rs @@ -128,22 +128,26 @@ impl DatastoreWorker { }; let mut ds = DatastoreInstance::new(&conn, true).unwrap(); - // Ensure legacy import + // Ensure legacy import (best-effort, don't fail if it doesn't work) if self.legacy_import { - let transaction = match conn.transaction_with_behavior(TransactionBehavior::Immediate) { - Ok(transaction) => transaction, + match conn.transaction_with_behavior(TransactionBehavior::Immediate) { + Ok(transaction) => { + match ds.ensure_legacy_import(&transaction) { + Ok(_) => (), + Err(err) => error!("Failed to do legacy import: {:?}", err), + } + match transaction.commit() { + Ok(_) => (), + Err(err) => { + error!("Failed to commit legacy import transaction: {err}"); + } + } + } Err(err) => { - panic!("Unable to start immediate transaction on SQLite database! {err}") + error!("Unable to start transaction for legacy import: {err}"); + info!("Skipping legacy import due to transaction error"); } }; - match ds.ensure_legacy_import(&transaction) { - Ok(_) => (), - Err(err) => error!("Failed to do legacy import: {:?}", err), - } - match transaction.commit() { - Ok(_) => (), - Err(err) => panic!("Failed to commit datastore transaction! {err}"), - } } // Start handling and respond to requests @@ -192,7 +196,16 @@ impl DatastoreWorker { ); match tx.commit() { Ok(_) => (), - Err(err) => panic!("Failed to commit datastore transaction! {err}"), + Err(err) => { + // Don't panic - that would poison locks and make the server unusable. + // Instead, log the error and exit the worker gracefully. + // This can happen when the disk is full or there are I/O errors. + error!( + "Failed to commit datastore transaction (disk full or I/O error?): {err}. \ + Worker will shut down gracefully." + ); + self.quit = true; + } } if self.quit { break; diff --git a/aw-server/src/endpoints/util.rs b/aw-server/src/endpoints/util.rs index fdf4a992..17951d97 100644 --- a/aw-server/src/endpoints/util.rs +++ b/aw-server/src/endpoints/util.rs @@ -98,6 +98,10 @@ impl From for HttpErrorJson { DatastoreError::OldDbVersion(msg) => { HttpErrorJson::new(Status::InternalServerError, msg) } + DatastoreError::CommitFailed(msg) => HttpErrorJson::new( + Status::ServiceUnavailable, + format!("Database commit failed (disk full?): {msg}"), + ), } } } @@ -116,3 +120,48 @@ macro_rules! endpoints_get_lock { } }; } + +#[cfg(test)] +mod tests { + use super::*; + use aw_datastore::DatastoreError; + use rocket::http::Status; + + #[test] + fn test_datastore_error_to_http_error() { + // Test NoSuchBucket -> 404 + let err: HttpErrorJson = DatastoreError::NoSuchBucket("test-bucket".into()).into(); + assert_eq!(err.status, Status::NotFound); + assert!(err.message.contains("test-bucket")); + + // Test BucketAlreadyExists -> 304 + let err: HttpErrorJson = DatastoreError::BucketAlreadyExists("test-bucket".into()).into(); + assert_eq!(err.status, Status::NotModified); + + // Test NoSuchKey -> 404 + let err: HttpErrorJson = DatastoreError::NoSuchKey("test-key".into()).into(); + assert_eq!(err.status, Status::NotFound); + + // Test MpscError -> 500 + let err: HttpErrorJson = DatastoreError::MpscError.into(); + assert_eq!(err.status, Status::InternalServerError); + + // Test InternalError -> 500 + let err: HttpErrorJson = DatastoreError::InternalError("internal".into()).into(); + assert_eq!(err.status, Status::InternalServerError); + + // Test Uninitialized -> 500 + let err: HttpErrorJson = DatastoreError::Uninitialized("uninitialized".into()).into(); + assert_eq!(err.status, Status::InternalServerError); + + // Test OldDbVersion -> 500 + let err: HttpErrorJson = DatastoreError::OldDbVersion("old version".into()).into(); + assert_eq!(err.status, Status::InternalServerError); + + // Test CommitFailed -> 503 (new test for disk-full handling) + let err: HttpErrorJson = + DatastoreError::CommitFailed("database or disk is full".into()).into(); + assert_eq!(err.status, Status::ServiceUnavailable); + assert!(err.message.contains("disk full")); + } +}