picodata_plugin/error_code.rs
1use tarantool::error::TarantoolErrorCode;
2use tarantool::static_assert;
3
4tarantool::define_enum_with_introspection! {
5 /// Error codes used with [`BoxError`] which are only generated by picodata.
6 ///
7 /// [`BoxError`]: tarantool::error::BoxError
8 pub enum ErrorCode {
9 // NOTE: At the moment of writing this the maximum tarantool error code
10 // on master branch is 286, but we still leave a huge window between
11 // that and our first error code just to be absolutely safe.
12 //
13 // We also don't want to go too big because of the msgpack encoding size
14 // considerations (65535 is the largest number which serializes into 3 bytes).
15
16 /// This is the first picodata error code. Use this for errors which
17 /// are hard to categorize and for which there's no specific way of handling.
18 ///
19 /// Also please use a **unique error message** for these kinds of errors,
20 /// so it's easy to find where it was generated in code.
21 ///
22 /// If your error instead should be handled in a specific way please
23 /// add a new one if the existing ones don't satisfy your needs.
24 Other = 10000,
25
26 /// Requested instance is not a leader.
27 NotALeader = 10001,
28
29 /// Contents of a builtin table has invalid format.
30 StorageCorrupted = 10002,
31
32 /// Operation request from different term.
33 TermMismatch = 10003,
34
35 /// Raft log is temporarily unavailable.
36 RaftLogUnavailable = 10004,
37
38 /// Can't check the predicate because raft log is compacted.
39 RaftLogCompacted = 10005,
40
41 /// Nearly impossible error indicating invalid request.
42 CasNoSuchRaftIndex = 10006,
43
44 /// Checking the predicate revealed a collision.
45 CasConflictFound = 10007,
46
47 /// Request expected raft entry to have a different term.
48 CasEntryTermMismatch = 10008,
49
50 /// SpaceNotAllowed: space {space} is prohibited for use in a predicate
51 CasTableNotAllowed = 10009,
52
53 /// Unexpected traft operation kind.
54 CasInvalidOpKind = 10010,
55
56 NoSuchService = 10011,
57 ServiceNotStarted = 10012,
58 ServicePoisoned = 10013,
59 ServiceNotAvailable = 10014,
60 WrongPluginVersion = 10015,
61
62 NoSuchInstance = 10016,
63 NoSuchReplicaset = 10017,
64
65 LeaderUnknown = 10018,
66
67 // Error in plugin system.
68 PluginError = 10019,
69
70 // Instance in question was expelled from the cluster.
71 InstanceExpelled = 10020,
72
73 // Replicaset in question was expelled from the cluster.
74 ReplicasetExpelled = 10021,
75
76 // Instance unavailiable due to it's target state is Offline
77 InstanceUnavaliable = 10022,
78
79 /// TableNotOperable: table {table} is prohibited for use in a predicate
80 CasTableNotOperable = 10023,
81
82 /// Picodata machinery is not yet initialized on the instance.
83 Uninitialized = 10024,
84
85 /// Instance is not allowed to be expelled in the given situation for
86 /// some reason. The most often solution is to use `picodata expel --force`.
87 ExpelNotAllowed = 10025,
88
89 /// TableNotOperable: table {table} is prohibited for use in a predicate
90 CasConfigNotAllowed = 10026,
91
92 /// Raft proposal was dropped by the leader.
93 RaftProposalDropped = 10027,
94
95 /// Generic sbroad error
96 SbroadError = 10028,
97
98 /// A raft snapshot read view is not available.
99 RaftSnapshotReadViewNotAvailable = 10029,
100
101 /// Can't apply the snapshot because the local schema is not up to date yet.
102 /// The schema should be propagated via tarantool replication.
103 LocalSchemaNotUpToDate = 10030,
104
105 /// Not an actual error code, just designates the start of the range.
106 UserDefinedErrorCodesStart = 20000,
107 // Plugin writers should use error codes in this range
108 }
109}
110
111impl ErrorCode {
112 /// These types of errors signify different kinds of conflicts which can
113 /// occur during a [`compare_and_swap`] RPC request. If such an error
114 /// happens it's safe to retry the request under the following conditions:
115 /// - The raft read index operation is performed before each retry
116 /// - The preconditions of the request are checked before each retry
117 /// - The request is generated before each retry with an up to date raft index
118 ///
119 /// [`compare_and_swap`]: crate::internal::cas::compare_and_swap
120 #[inline]
121 pub fn is_retriable_for_cas(&self) -> bool {
122 match *self {
123 // Raft leader is in the middle of being changed.
124 // The client should synchronize and retry the request.
125 ErrorCode::LeaderUnknown
126 // Raft leader has changed since the CaS request was generated.
127 // The client should synchronize and retry the request.
128 | ErrorCode::NotALeader
129 // Raft term has changed since the CaS request was generated.
130 // The client should synchronize and retry the request.
131 | ErrorCode::TermMismatch
132 // Raft log was compacted on the leader, so the predicate cannot be checked.
133 // The client should synchronize and retry the request.
134 | ErrorCode::RaftLogCompacted
135 // Some raft log entries have disappeared on the leader, so the predicate cannot be checked.
136 // XXX Not sure how this would happen.
137 // The client should synchronize and retry the request.
138 | ErrorCode::RaftLogUnavailable
139 // Entry at requested index has a mismatched term in the leader's log.
140 // The client should synchronize ( raft log will likely be truncated) and retry the request.
141 | ErrorCode::CasEntryTermMismatch
142 // Leader checked the predicate and found a conflict.
143 // The client should synchronize and check the preconditions.
144 | ErrorCode::CasConflictFound
145 // Raft proposal was dropped by the leader.
146 // The client should synchronize and retry the request.
147 | ErrorCode::RaftProposalDropped
148 => true,
149 _ => false,
150 }
151 }
152}
153
154#[inline]
155pub fn error_code_is_retriable_for_cas(code: u32) -> bool {
156 let Ok(error_code) = ErrorCode::try_from(code) else {
157 return false;
158 };
159
160 error_code.is_retriable_for_cas()
161}
162
163impl From<ErrorCode> for u32 {
164 #[inline(always)]
165 fn from(code: ErrorCode) -> u32 {
166 code as _
167 }
168}
169
170static_assert!(
171 ErrorCode::MIN as u32 > TarantoolErrorCode::MAX as u32,
172 "picodata error code range must not intersect tarantool's ones"
173);
174static_assert!(
175 ErrorCode::MAX as u32 <= ErrorCode::UserDefinedErrorCodesStart as u32,
176 "picodata builtin error code range must not intersect with user defined ones"
177);