1use crate::RuntimeCapability;
16use std::fmt::{Display, Formatter};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum FileDescriptorError {
23 InvalidBudget {
24 limit: usize,
25 },
26 ConfigurationConflict {
27 configured: usize,
28 requested: usize,
29 },
30 BudgetExceeded {
31 requested: usize,
32 available: usize,
33 limit: usize,
34 },
35 OsLimit {
36 requested: usize,
37 os_code: Option<i32>,
38 soft: Option<u64>,
39 hard: Option<u64>,
40 },
41}
42
43impl Display for FileDescriptorError {
44 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::InvalidBudget { limit } => write!(formatter, "invalid file descriptor budget: {limit}"),
47 Self::ConfigurationConflict { configured, requested } => write!(formatter,
48 "project file descriptor budget conflict: configured {configured}, requested {requested}"),
49 Self::BudgetExceeded { requested, available, limit } => write!(formatter,
50 "project file descriptor budget exceeded: requested {requested}, available {available}, limit {limit}"),
51 Self::OsLimit { requested, os_code, soft, hard } => write!(formatter,
52 "operating system file descriptor limit: requested {requested}, soft {soft:?}, hard {hard:?}, OS code {os_code:?}"),
53 }
54 }
55}
56
57impl std::error::Error for FileDescriptorError {}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum HawDBError {
61 Parse(String),
62 Semantic(String),
63 Storage(String),
64 FileDescriptors(FileDescriptorError),
65 StorageIntegrity(String),
66 Execution(String),
67 TransactionConflict {
68 read_epoch: u64,
69 committed_epoch: u64,
70 key: String,
71 },
72 AppendSequenceExhausted {
73 table: String,
74 watermark: i64,
75 requested: usize,
76 },
77 CapabilityUnavailable {
78 capability: RuntimeCapability,
79 },
80 BranchCommandUnsupported {
81 command: &'static str,
82 context: &'static str,
83 },
84 BranchBusy {
85 resource: &'static str,
86 },
87}
88
89impl Display for HawDBError {
90 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91 match self {
92 HawDBError::Parse(message) => write!(f, "parse error: {message}"),
93 HawDBError::Semantic(message) => write!(f, "semantic error: {message}"),
94 HawDBError::Storage(message) => write!(f, "storage error: {message}"),
95 HawDBError::FileDescriptors(error) => Display::fmt(error, f),
96 HawDBError::StorageIntegrity(message) => {
97 write!(f, "storage integrity error: {message}")
98 }
99 HawDBError::Execution(message) => write!(f, "execution error: {message}"),
100 HawDBError::TransactionConflict {
101 read_epoch,
102 committed_epoch,
103 key,
104 } => write!(
105 f,
106 "transaction conflict on {key}: transaction read epoch {read_epoch}, conflicting commit epoch {committed_epoch}"
107 ),
108 HawDBError::AppendSequenceExhausted {
109 table,
110 watermark,
111 requested,
112 } => write!(
113 f,
114 "append commit sequence exhausted for table {table}: watermark={watermark}, requested={requested}"
115 ),
116 HawDBError::CapabilityUnavailable { capability } => {
117 write!(f, "capability unavailable: {}", capability.as_str())
118 }
119 HawDBError::BranchCommandUnsupported { command, context } => {
120 write!(f, "{command} is unavailable in {context}")
121 }
122 HawDBError::BranchBusy { resource } => write!(f, "branch is busy: {resource}"),
123 }
124 }
125}
126
127impl std::error::Error for HawDBError {
128 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129 match self {
130 Self::FileDescriptors(error) => Some(error),
131 _ => None,
132 }
133 }
134}
135
136impl HawDBError {
137 pub fn from_storage_error(error: impl std::error::Error + 'static) -> Self {
139 if let Some(error) = (&error as &dyn std::error::Error).downcast_ref::<Self>() {
140 return error.clone();
141 }
142 if let Some(resource) = file_descriptor_error(&error) {
143 Self::FileDescriptors(resource)
144 } else {
145 Self::Storage(error.to_string())
146 }
147 }
148
149 pub fn from_storage_error_with_context(
153 error: impl std::error::Error + 'static,
154 context: &str,
155 ) -> Self {
156 match Self::from_storage_error(error) {
157 Self::Storage(message) => Self::Storage(format!("{context}: {message}")),
158 error => error,
159 }
160 }
161
162 pub const fn is_retryable_transaction_conflict(&self) -> bool {
163 matches!(self, Self::TransactionConflict { .. })
164 }
165}
166
167pub type Result<T> = std::result::Result<T, HawDBError>;
168
169impl From<std::io::Error> for HawDBError {
170 fn from(error: std::io::Error) -> Self {
171 Self::from_storage_error(error)
172 }
173}
174
175pub fn file_descriptor_error(
176 error: &(dyn std::error::Error + 'static),
177) -> Option<FileDescriptorError> {
178 let mut current = Some(error);
179 while let Some(error) = current {
180 if let Some(resource) = error.downcast_ref::<FileDescriptorError>() {
181 return Some(resource.clone());
182 }
183 if let Some(HawDBError::FileDescriptors(resource)) = error.downcast_ref::<HawDBError>() {
184 return Some(resource.clone());
185 }
186 if let Some(error) = error.downcast_ref::<std::io::Error>() {
187 if let Some(code) = error.raw_os_error()
188 && descriptor_os_limit(code)
189 {
190 return Some(FileDescriptorError::OsLimit {
191 requested: 1,
192 os_code: Some(code),
193 soft: None,
194 hard: None,
195 });
196 }
197 if let Some(inner) = error.get_ref()
200 && let Some(resource) = file_descriptor_error(inner)
201 {
202 return Some(resource);
203 }
204 }
205 current = error.source();
206 }
207 None
208}
209
210fn descriptor_os_limit(code: i32) -> bool {
211 #[cfg(unix)]
212 {
213 matches!(code, 23 | 24)
214 }
215 #[cfg(windows)]
216 {
217 code == 4
218 }
219 #[cfg(not(any(unix, windows)))]
220 {
221 let _ = code;
222 false
223 }
224}