Skip to content

Commit da7b7dc

Browse files
authored
Merge pull request #1615 from avinassh/ss-better-error
Improve error handling in Storage Server RPC calls
2 parents cd368ce + 7887f1d commit da7b7dc

File tree

6 files changed

+84
-9
lines changed

6 files changed

+84
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libsql-storage-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
2727
async-trait = "0.1.80"
2828
serde = "1.0.203"
2929
thiserror = "1.0.61"
30+
prost = "0.12.6"
3031

3132
[features]
3233
foundation-db = ["foundationdb"]
Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use tonic::Status;
1+
use libsql_storage::rpc;
2+
use prost::Message;
3+
use tonic::{Code, Status};
24

35
#[derive(Debug, thiserror::Error)]
46
pub enum Error {
@@ -8,11 +10,34 @@ pub enum Error {
810
Other(#[from] anyhow::Error),
911
}
1012

13+
impl Error {
14+
fn code(&self) -> rpc::ErrorCode {
15+
match self {
16+
Error::WriteConflict => rpc::ErrorCode::WriteConflict,
17+
Error::Other(_) => rpc::ErrorCode::InternalError,
18+
}
19+
}
20+
}
21+
1122
impl From<Error> for Status {
1223
fn from(error: Error) -> Self {
13-
match error {
14-
Error::WriteConflict => Status::aborted("write conflict"),
15-
Error::Other(err) => Status::internal(err.to_string()),
24+
let status_code = match error.code() {
25+
rpc::ErrorCode::InternalError => Code::Internal,
26+
rpc::ErrorCode::WriteConflict => Code::Aborted,
27+
};
28+
let details = rpc::ErrorDetails {
29+
message: error.to_string(),
30+
code: error.code() as i32,
31+
};
32+
33+
let mut details_buf = Vec::new();
34+
if let Err(e) = details.encode(&mut details_buf) {
35+
Status::new(
36+
Code::Internal,
37+
format!("failed to encode error details: {}", e),
38+
)
39+
} else {
40+
Status::with_details(status_code, error.to_string(), details_buf.into())
1641
}
1742
}
1843
}

libsql-storage/proto/storage.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ message DestroyRequest {
6767

6868
message DestroyResponse {}
6969

70+
enum ErrorCode {
71+
INTERNAL_ERROR = 0;
72+
WRITE_CONFLICT = 1;
73+
}
74+
75+
message ErrorDetails {
76+
ErrorCode code = 1;
77+
string message = 2;
78+
}
79+
7080
service Storage {
7181
rpc InsertFrames(InsertFramesRequest) returns (InsertFramesResponse) {}
7282
rpc FindFrame(FindFrameRequest) returns (FindFrameResponse) {}

libsql-storage/src/generated/storage.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,40 @@ pub struct DestroyRequest {
100100
#[allow(clippy::derive_partial_eq_without_eq)]
101101
#[derive(Clone, PartialEq, ::prost::Message)]
102102
pub struct DestroyResponse {}
103+
#[allow(clippy::derive_partial_eq_without_eq)]
104+
#[derive(Clone, PartialEq, ::prost::Message)]
105+
pub struct ErrorDetails {
106+
#[prost(enumeration = "ErrorCode", tag = "1")]
107+
pub code: i32,
108+
#[prost(string, tag = "2")]
109+
pub message: ::prost::alloc::string::String,
110+
}
111+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
112+
#[repr(i32)]
113+
pub enum ErrorCode {
114+
InternalError = 0,
115+
WriteConflict = 1,
116+
}
117+
impl ErrorCode {
118+
/// String value of the enum field names used in the ProtoBuf definition.
119+
///
120+
/// The values are not transformed in any way and thus are considered stable
121+
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
122+
pub fn as_str_name(&self) -> &'static str {
123+
match self {
124+
ErrorCode::InternalError => "INTERNAL_ERROR",
125+
ErrorCode::WriteConflict => "WRITE_CONFLICT",
126+
}
127+
}
128+
/// Creates an enum from field names used in the ProtoBuf definition.
129+
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
130+
match value {
131+
"INTERNAL_ERROR" => Some(Self::InternalError),
132+
"WRITE_CONFLICT" => Some(Self::WriteConflict),
133+
_ => None,
134+
}
135+
}
136+
}
103137
/// Generated client implementations.
104138
pub mod storage_client {
105139
#![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)]

libsql-storage/src/lib.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use std::path::Path;
66
use std::sync::{Arc, Mutex};
77

88
use crate::local_cache::LocalCache;
9-
use crate::rpc::Frame;
9+
use crate::rpc::{ErrorCode, Frame};
1010
use libsql_sys::ffi::{SQLITE_ABORT, SQLITE_BUSY};
1111
use libsql_sys::name::{NamespaceName, NamespaceResolver};
1212
use libsql_sys::rusqlite;
1313
use libsql_sys::wal::{Result, Vfs, Wal, WalManager};
14+
use prost::Message;
1415
use rpc::storage_client::StorageClient;
1516
use tonic::transport::Channel;
1617
use tracing::{error, trace, warn};
@@ -365,10 +366,13 @@ impl Wal for DurableWal {
365366
let resp = binding.insert_frames(req);
366367
let resp = tokio::task::block_in_place(|| rt.block_on(resp));
367368
if let Err(e) = resp {
368-
if e.code() == tonic::Code::Aborted {
369-
return Err(rusqlite::ffi::Error::new(SQLITE_BUSY));
370-
}
371-
return Err(rusqlite::ffi::Error::new(SQLITE_ABORT));
369+
let error_code = rpc::ErrorDetails::decode(e.details())
370+
.ok()
371+
.and_then(|details| rpc::ErrorCode::try_from(details.code).ok());
372+
return match error_code {
373+
Some(ErrorCode::WriteConflict) => Err(rusqlite::ffi::Error::new(SQLITE_BUSY)),
374+
_ => Err(rusqlite::ffi::Error::new(SQLITE_ABORT)),
375+
};
372376
}
373377
// TODO: fix parity with storage server frame num with local cache
374378
self.local_cache

0 commit comments

Comments
 (0)