restate_sdk_shared_core/vm/
errors.rs1use crate::error::NotificationMetadata;
2use crate::fmt::{display_closed_error, DiffFormatter};
3use crate::service_protocol::messages::{CommandMessageHeaderDiff, ErrorBehavior, RestateMessage};
4use crate::service_protocol::{
5 CompletionId, ContentTypeError, DecodingError, MessageType, NotificationId,
6};
7use crate::{Error, Version};
8use std::borrow::Cow;
9use std::collections::{HashMap, HashSet};
10use std::fmt;
11#[derive(Copy, Clone, PartialEq, Eq)]
14pub struct InvocationErrorCode(u16);
15
16impl InvocationErrorCode {
17 pub const fn new(code: u16) -> Self {
18 InvocationErrorCode(code)
19 }
20
21 pub const fn code(self) -> u16 {
22 self.0
23 }
24}
25
26impl fmt::Debug for InvocationErrorCode {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}", self.0)
29 }
30}
31
32impl fmt::Display for InvocationErrorCode {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 fmt::Debug::fmt(self, f)
35 }
36}
37
38impl From<u16> for InvocationErrorCode {
39 fn from(value: u16) -> Self {
40 InvocationErrorCode(value)
41 }
42}
43
44impl From<u32> for InvocationErrorCode {
45 fn from(value: u32) -> Self {
46 value
47 .try_into()
48 .map(InvocationErrorCode)
49 .unwrap_or(codes::INTERNAL)
50 }
51}
52
53impl From<InvocationErrorCode> for u16 {
54 fn from(value: InvocationErrorCode) -> Self {
55 value.0
56 }
57}
58
59impl From<InvocationErrorCode> for u32 {
60 fn from(value: InvocationErrorCode) -> Self {
61 value.0 as u32
62 }
63}
64
65pub mod codes {
66 use super::InvocationErrorCode;
67
68 pub const BAD_REQUEST: InvocationErrorCode = InvocationErrorCode(400);
69 pub const INTERNAL: InvocationErrorCode = InvocationErrorCode(500);
70 pub const UNSUPPORTED_MEDIA_TYPE: InvocationErrorCode = InvocationErrorCode(415);
71 pub const JOURNAL_MISMATCH: InvocationErrorCode = InvocationErrorCode(570);
72 pub const PROTOCOL_VIOLATION: InvocationErrorCode = InvocationErrorCode(571);
73 pub const AWAITING_TWO_ASYNC_RESULTS: InvocationErrorCode = InvocationErrorCode(572);
74 pub const UNSUPPORTED_FEATURE: InvocationErrorCode = InvocationErrorCode(573);
75 pub const CLOSED: InvocationErrorCode = InvocationErrorCode(598);
76 pub const SUSPENDED: InvocationErrorCode = InvocationErrorCode(599);
77}
78
79impl Error {
82 const fn new_const(code: InvocationErrorCode, message: &'static str) -> Self {
83 Error {
84 code: code.0,
85 message: Cow::Borrowed(message),
86 stacktrace: String::new(),
87 related_command: None,
88 next_retry_delay: None,
89 behavior: ErrorBehavior::Retry,
90 }
91 }
92}
93
94pub const MISSING_CONTENT_TYPE: Error = Error::new_const(
95 codes::UNSUPPORTED_MEDIA_TYPE,
96 "Missing content type when invoking the service deployment",
97);
98
99pub const UNEXPECTED_INPUT_MESSAGE: Error = Error::new_const(
100 codes::PROTOCOL_VIOLATION,
101 "Expected incoming message to be an entry",
102);
103
104pub const KNOWN_ENTRIES_IS_ZERO: Error =
105 Error::new_const(codes::INTERNAL, "Known entries is zero, expected >= 1");
106
107pub const UNEXPECTED_ENTRY_MESSAGE: Error = Error::new_const(
108 codes::PROTOCOL_VIOLATION,
109 "Expected entry messages only when waiting replay entries",
110);
111
112pub const INPUT_CLOSED_WHILE_WAITING_ENTRIES: Error = Error::new_const(
113 codes::PROTOCOL_VIOLATION,
114 "The input was closed while still waiting to receive all journal to replay",
115);
116
117pub const EMPTY_IDEMPOTENCY_KEY: Error = Error::new_const(
118 codes::INTERNAL,
119 "Trying to execute an idempotent request with an empty idempotency key. The idempotency key must be non-empty.",
120);
121
122pub const EMPTY_LIMIT_KEY: Error = Error::new_const(
123 codes::INTERNAL,
124 "Trying to execute a request with an empty limit key. The limit key must be non-empty.",
125);
126
127pub const EMPTY_SCOPE: Error = Error::new_const(
128 codes::INTERNAL,
129 "Trying to execute a request with an empty scope. The scope must be non-empty.",
130);
131
132pub const SUSPENDED: Error = Error::new_const(codes::SUSPENDED, "Suspended invocation");
133
134#[derive(Debug, Clone, thiserror::Error)]
137#[error("The execution replay ended unexpectedly. Expecting to read '{expected}' from the recorded journal, but the buffered messages were already drained.")]
138pub struct UnavailableEntryError {
139 expected: MessageType,
140}
141
142impl UnavailableEntryError {
143 pub fn new(expected: MessageType) -> Self {
144 Self { expected }
145 }
146}
147
148#[derive(Debug, thiserror::Error)]
149#[error("Unexpected state '{state:?}' when invoking '{event:?}'")]
150pub struct UnexpectedStateError {
151 state: &'static str,
152 event: String,
153}
154
155impl UnexpectedStateError {
156 pub fn new(state: &'static str, event: String) -> Self {
157 Self { state, event }
158 }
159}
160
161#[derive(Debug)]
162pub struct ClosedError {
163 event: String,
164}
165
166impl ClosedError {
167 pub fn new(event: String) -> Self {
168 Self { event }
169 }
170}
171
172impl fmt::Display for ClosedError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 display_closed_error(f, &self.event)
175 }
176}
177
178impl std::error::Error for ClosedError {}
179
180#[derive(Debug)]
181pub struct CommandTypeMismatchError {
182 actual: MessageType,
183 command_index: i64,
184 expected: MessageType,
185}
186
187impl CommandTypeMismatchError {
188 pub fn new(
189 command_index: i64,
190 actual: MessageType,
191 expected: MessageType,
192 ) -> CommandTypeMismatchError {
193 Self {
194 command_index,
195 actual,
196 expected,
197 }
198 }
199}
200
201impl fmt::Display for CommandTypeMismatchError {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f,
204 "Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
205This typically happens when some parts of the code are non-deterministic.
206 - The previous execution ran and recorded the following: '{}' (index '{}')
207 - The current execution attempts to perform the following: '{}'",
208 self.expected,
209 self.command_index,
210 self.actual,
211 )
212 }
213}
214
215impl std::error::Error for CommandTypeMismatchError {}
216
217#[derive(Debug)]
218pub struct CommandMismatchError<M> {
219 command_index: i64,
220 actual: M,
221 expected: M,
222}
223
224impl<M> CommandMismatchError<M> {
225 pub fn new(command_index: i64, actual: M, expected: M) -> CommandMismatchError<M> {
226 Self {
227 command_index,
228 actual,
229 expected,
230 }
231 }
232}
233
234impl<M: RestateMessage + CommandMessageHeaderDiff> fmt::Display for CommandMismatchError<M> {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 write!(f,
237"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
238This typically happens when some parts of the code are non-deterministic.
239- The mismatch happened while executing '{}' (index '{}')
240- Difference:",
241 M::ty(), self.command_index,
242 )?;
243 self.actual
244 .write_diff(&self.expected, DiffFormatter::new(f, " "))
245 }
246}
247
248impl<M: RestateMessage + CommandMessageHeaderDiff + std::fmt::Debug> std::error::Error
249 for CommandMismatchError<M>
250{
251}
252
253#[derive(Debug)]
254pub struct UncompletedDoProgressDuringReplay {
255 notification_ids: Vec<NotificationId>,
256 additional_known_metadata: HashMap<NotificationId, NotificationMetadata>,
257}
258
259impl UncompletedDoProgressDuringReplay {
260 pub(crate) fn new(
261 notification_ids: HashSet<NotificationId>,
262 additional_known_metadata: HashMap<NotificationId, NotificationMetadata>,
263 ) -> Self {
264 let mut ordered_notification_ids = Vec::from_iter(notification_ids);
266 ordered_notification_ids.sort_by(|a, b| match (a, b) {
267 (NotificationId::CompletionId(a_id), NotificationId::CompletionId(b_id)) => {
268 a_id.cmp(b_id)
269 }
270 (NotificationId::CompletionId(_), _) => std::cmp::Ordering::Less,
271 (_, NotificationId::CompletionId(_)) => std::cmp::Ordering::Greater,
272
273 (NotificationId::SignalName(a_name), NotificationId::SignalName(b_name)) => {
274 a_name.cmp(b_name)
275 }
276 (NotificationId::SignalName(_), NotificationId::SignalId(_)) => {
277 std::cmp::Ordering::Less
278 }
279 (NotificationId::SignalId(_), NotificationId::SignalName(_)) => {
280 std::cmp::Ordering::Greater
281 }
282
283 (NotificationId::SignalId(a_id), NotificationId::SignalId(b_id)) => {
284 let a_is_cancel = *a_id == crate::service_protocol::CANCEL_SIGNAL_ID;
285 let b_is_cancel = *b_id == crate::service_protocol::CANCEL_SIGNAL_ID;
286 match (a_is_cancel, b_is_cancel) {
287 (true, false) => std::cmp::Ordering::Greater,
288 (false, true) => std::cmp::Ordering::Less,
289 _ => a_id.cmp(b_id),
290 }
291 }
292 });
293 Self {
294 notification_ids: ordered_notification_ids,
295 additional_known_metadata,
296 }
297 }
298}
299
300impl fmt::Display for UncompletedDoProgressDuringReplay {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f,
303"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
304'await' could not be replayed. This usually means the code was mutated adding an 'await' without registering a new service revision.
305Notifications awaited on this await point:",
306 )?;
307
308 for notification_id in &self.notification_ids {
309 write!(f, "\n - ")?;
310 if let Some(metadata) = self.additional_known_metadata.get(notification_id) {
311 write!(f, "{}", metadata)?;
312 } else {
313 match notification_id {
314 NotificationId::CompletionId(completion_id) => {
315 write!(f, "completion id {}", completion_id)?;
316 }
317 NotificationId::SignalId(signal_id) => {
318 write!(f, "signal [{}]", signal_id)?;
319 }
320 NotificationId::SignalName(signal_name) => {
321 write!(f, "Named signal: {}", signal_name)?;
322 }
323 }
324 }
325 }
326
327 Ok(())
328 }
329}
330
331impl std::error::Error for UncompletedDoProgressDuringReplay {}
332
333#[derive(Debug, Clone, thiserror::Error)]
334#[error("Cannot convert a eager state key into UTF-8 String: {0:?}")]
335pub struct BadEagerStateKeyError(#[from] pub(crate) std::string::FromUtf8Error);
336
337pub const EMPTY_GET_EAGER_STATE: Error = Error::new_const(
338 codes::PROTOCOL_VIOLATION,
339 "Unexpected empty value variant for get eager state.",
340);
341
342pub const EMPTY_GET_EAGER_STATE_KEYS: Error = Error::new_const(
343 codes::PROTOCOL_VIOLATION,
344 "Unexpected empty value variant for state keys.",
345);
346
347#[derive(Debug, thiserror::Error)]
348#[error("Feature '{feature}' is not supported by the negotiated protocol version '{current_version}', the minimum required version is '{minimum_required_version}'")]
349pub struct UnsupportedFeatureForNegotiatedVersion {
350 feature: &'static str,
351 current_version: Version,
352 minimum_required_version: Version,
353}
354
355impl UnsupportedFeatureForNegotiatedVersion {
356 pub fn new(
357 feature: &'static str,
358 current_version: Version,
359 minimum_required_version: Version,
360 ) -> Self {
361 Self {
362 feature,
363 current_version,
364 minimum_required_version,
365 }
366 }
367}
368
369#[derive(Debug, thiserror::Error)]
370#[error("Received a run completion ack for completion id {completion_id}, but the related run was not proposed during this attempt.")]
371pub struct BadProposeRunCompletionAck {
372 completion_id: CompletionId,
373}
374
375impl BadProposeRunCompletionAck {
376 pub fn new(completion_id: CompletionId) -> Self {
377 Self { completion_id }
378 }
379}
380
381#[derive(Debug, Clone, thiserror::Error)]
382#[error("The provided duration for '{0}' is out of bounds: {1:?}")]
383pub struct OutOfBoundsDuration(
384 pub(crate) &'static str,
385 pub(crate) std::num::TryFromIntError,
386);
387
388trait WithInvocationErrorCode {
391 fn code(&self) -> InvocationErrorCode;
392}
393
394impl<T: WithInvocationErrorCode + fmt::Display> From<T> for Error {
395 fn from(value: T) -> Self {
396 Error::new(value.code().0, value.to_string())
397 }
398}
399
400macro_rules! impl_error_code {
401 ($error_type:ident, $code:ident) => {
402 impl WithInvocationErrorCode for $error_type {
403 fn code(&self) -> InvocationErrorCode {
404 codes::$code
405 }
406 }
407 };
408}
409
410impl_error_code!(ContentTypeError, UNSUPPORTED_MEDIA_TYPE);
411impl WithInvocationErrorCode for DecodingError {
412 fn code(&self) -> InvocationErrorCode {
413 match self {
414 DecodingError::UnexpectedMessageType { .. } => codes::JOURNAL_MISMATCH,
415 _ => codes::INTERNAL,
416 }
417 }
418}
419impl_error_code!(UnavailableEntryError, PROTOCOL_VIOLATION);
420impl_error_code!(UnexpectedStateError, PROTOCOL_VIOLATION);
421impl_error_code!(ClosedError, CLOSED);
422impl_error_code!(CommandTypeMismatchError, JOURNAL_MISMATCH);
423impl_error_code!(UncompletedDoProgressDuringReplay, JOURNAL_MISMATCH);
424impl<M: RestateMessage + CommandMessageHeaderDiff> WithInvocationErrorCode
425 for CommandMismatchError<M>
426{
427 fn code(&self) -> InvocationErrorCode {
428 codes::JOURNAL_MISMATCH
429 }
430}
431impl_error_code!(BadEagerStateKeyError, INTERNAL);
432impl_error_code!(UnsupportedFeatureForNegotiatedVersion, UNSUPPORTED_FEATURE);
433impl_error_code!(BadProposeRunCompletionAck, PROTOCOL_VIOLATION);
434impl_error_code!(OutOfBoundsDuration, INTERNAL);