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