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        /// Not an actual error code, just designates the start of the range.
102        UserDefinedErrorCodesStart = 20000,
103        // Plugin writers should use error codes in this range
104    }
105}
106
107impl ErrorCode {
108    /// These types of errors signify different kinds of conflicts which can
109    /// occur during a [`compare_and_swap`] RPC request. If such an error
110    /// happens it's safe to retry the request under the following conditions:
111    /// - The raft read index operation is performed before each retry
112    /// - The preconditions of the request are checked before each retry
113    /// - The request is generated before each retry with an up to date raft index
114    ///
115    /// [`compare_and_swap`]: crate::internal::cas::compare_and_swap
116    #[inline]
117    pub fn is_retriable_for_cas(&self) -> bool {
118        match *self {
119            // Raft leader is in the middle of being changed.
120            // The client should synchronize and retry the request.
121            ErrorCode::LeaderUnknown
122            // Raft leader has changed since the CaS request was generated.
123            // The client should synchronize and retry the request.
124            | ErrorCode::NotALeader
125            // Raft term has changed since the CaS request was generated.
126            // The client should synchronize and retry the request.
127            | ErrorCode::TermMismatch
128            // Raft log was compacted on the leader, so the predicate cannot be checked.
129            // The client should synchronize and retry the request.
130            | ErrorCode::RaftLogCompacted
131            // Some raft log entries have disappeared on the leader, so the predicate cannot be checked.
132            // XXX Not sure how this would happen.
133            // The client should synchronize and retry the request.
134            | ErrorCode::RaftLogUnavailable
135            // Entry at requested index has a mismatched term in the leader's log.
136            // The client should synchronize ( raft log will likely be truncated) and retry the request.
137            | ErrorCode::CasEntryTermMismatch
138            // Leader checked the predicate and found a conflict.
139            // The client should synchronize and check the preconditions.
140            | ErrorCode::CasConflictFound
141            // Raft proposal was dropped by the leader.
142            // The client should synchronize and retry the request.
143            | ErrorCode::RaftProposalDropped
144            => true,
145            _ => false,
146        }
147    }
148}
149
150#[inline]
151pub fn error_code_is_retriable_for_cas(code: u32) -> bool {
152    let Ok(error_code) = ErrorCode::try_from(code) else {
153        return false;
154    };
155
156    error_code.is_retriable_for_cas()
157}
158
159impl From<ErrorCode> for u32 {
160    #[inline(always)]
161    fn from(code: ErrorCode) -> u32 {
162        code as _
163    }
164}
165
166static_assert!(
167    ErrorCode::MIN as u32 > TarantoolErrorCode::MAX as u32,
168    "picodata error code range must not intersect tarantool's ones"
169);
170static_assert!(
171    ErrorCode::MAX as u32 <= ErrorCode::UserDefinedErrorCodesStart as u32,
172    "picodata builtin error code range must not intersect with user defined ones"
173);