-
Notifications
You must be signed in to change notification settings - Fork 13
/
exceptions_internal.hxx
405 lines (369 loc) · 11.4 KB
/
exceptions_internal.hxx
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/*
* Copyright 2021-Present Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "core/transactions/error_class.hxx"
#include "core/transactions/exceptions.hxx"
#include "core/transactions/result.hxx"
#include "transaction_context.hxx"
#include <couchbase/transaction_op_error_context.hxx>
#include <algorithm>
#include <list>
namespace couchbase::core::transactions
{
// only used in ambiguity resolution during atr_commit
class retry_atr_commit : public std::runtime_error
{
public:
retry_atr_commit(const std::string& what)
: std::runtime_error(what)
{
}
};
class retry_operation : public std::runtime_error
{
public:
retry_operation(const std::string& what)
: std::runtime_error(what)
{
}
};
class retry_operation_timeout : public std::runtime_error
{
public:
retry_operation_timeout(const std::string& what)
: std::runtime_error(what)
{
}
};
class retry_operation_retries_exhausted : public std::runtime_error
{
public:
retry_operation_retries_exhausted(const std::string& what)
: std::runtime_error(what)
{
}
};
external_exception
external_exception_from_error_class(error_class ec);
enum final_error { FAILED, EXPIRED, FAILED_POST_COMMIT, AMBIGUOUS };
error_class
error_class_from_result(const result& res);
class client_error : public std::runtime_error
{
private:
error_class ec_;
std::optional<result> res_;
public:
explicit client_error(const result& res)
: runtime_error(res.strerror())
, ec_(error_class_from_result(res))
, res_(res)
{
}
explicit client_error(error_class ec, const std::string& what)
: runtime_error(what)
, ec_(ec)
{
}
error_class ec() const
{
return ec_;
}
std::optional<result> res() const
{
return res_;
}
};
// Prefer this as it reads better than throw client_error(FAIL_EXPIRY, ...)
class attempt_expired : public client_error
{
public:
attempt_expired(const std::string& what)
: client_error(FAIL_EXPIRY, what)
{
}
};
/**
* All exceptions within a transaction are, or are converted to, an exception
* derived from this. The transaction logic then consumes them to decide to
* retry, or rollback the transaction.
*/
class transaction_operation_failed : public std::runtime_error
{
public:
explicit transaction_operation_failed(error_class ec, const std::string& what)
: std::runtime_error(what)
, ec_(ec)
, retry_(false)
, rollback_(true)
, to_raise_(FAILED)
, cause_(external_exception_from_error_class(ec))
{
}
explicit transaction_operation_failed(const client_error& client_err)
: std::runtime_error(client_err.what())
, ec_(client_err.ec())
, retry_(false)
, rollback_(true)
, to_raise_(FAILED)
, cause_(UNKNOWN)
{
}
static transaction_operation_failed merge_errors(std::list<transaction_operation_failed> errors,
std::optional<external_exception> cause = {},
bool do_throw = true)
{
// default would be to set retry false, rollback true. If
// _all_ errors set retry to true, we set retry to true. If _any_ errors
// set rollback to false, we set rollback to false.
// For now lets retain the ec, to_raise, and cause for the first of the
// retries (if they all are true), or the first of the rollbacks, if it
// is false. Not rolling back takes precedence over retry. Otherwise, we
// just retain the first.
assert(errors.size() > 0);
// start with first error that isn't previous_operation_failed
auto error_to_throw = *(std::find_if(
errors.begin(), errors.end(), [](auto& err) { return err.cause() != external_exception::PREVIOUS_OPERATION_FAILED; }));
for (auto& ex : errors) {
if (ex.cause() == external_exception::PREVIOUS_OPERATION_FAILED) {
continue;
}
if (!ex.retry_) {
error_to_throw = ex;
}
if (!ex.rollback_) {
// this takes precedence, (no_rollback means no_retry as well), so just throw this
error_to_throw = ex;
break;
}
}
if (cause) {
error_to_throw.cause(*cause);
}
if (do_throw) {
throw error_to_throw;
}
return error_to_throw;
}
// Retry is false by default, this makes it true
transaction_operation_failed& retry()
{
retry_ = true;
return *this;
}
// Rollback defaults to true, this sets it to false
transaction_operation_failed& no_rollback()
{
rollback_ = false;
return *this;
}
// Defaults to FAILED, this sets it to EXPIRED
transaction_operation_failed& expired()
{
to_raise_ = EXPIRED;
return *this;
}
// Defaults to FAILED, sets to FAILED_POST_COMMIT
transaction_operation_failed& failed_post_commit()
{
to_raise_ = FAILED_POST_COMMIT;
return *this;
}
// Defaults to FAILED, sets AMBIGUOUS
transaction_operation_failed& ambiguous()
{
to_raise_ = AMBIGUOUS;
return *this;
}
transaction_operation_failed& cause(external_exception cause)
{
cause_ = cause;
return *this;
}
bool should_rollback() const
{
return rollback_;
}
bool should_retry() const
{
return retry_;
}
external_exception cause() const
{
return cause_;
}
final_error to_raise() const
{
return to_raise_;
}
void do_throw(const transaction_context& context) const
{
if (to_raise_ == FAILED_POST_COMMIT) {
return;
}
switch (to_raise_) {
case EXPIRED:
throw transaction_exception(*this, context, failure_type::EXPIRY);
case AMBIGUOUS:
throw transaction_exception(*this, context, failure_type::COMMIT_AMBIGUOUS);
default:
throw transaction_exception(*this, context, failure_type::FAIL);
}
}
std::optional<transaction_exception> get_final_exception(const transaction_context& context) const
{
{
switch (to_raise_) {
case EXPIRED:
return transaction_exception(*this, context, failure_type::EXPIRY);
case AMBIGUOUS:
return transaction_exception(*this, context, failure_type::COMMIT_AMBIGUOUS);
case FAILED_POST_COMMIT:
return std::nullopt;
default:
return transaction_exception(*this, context, failure_type::FAIL);
}
}
}
transaction_op_error_context get_error_ctx() const
{
errc::transaction_op ec = transaction_op_errc_from_external_exception(cause_);
return { ec };
}
private:
error_class ec_;
bool retry_;
bool rollback_;
final_error to_raise_;
external_exception cause_;
};
namespace internal
{
/**
* Used only in testing: injects an error that will be handled as FAIL_HARD.
*
* This is not an error class the transaction library would ever raise
* voluntarily. It is designed to simulate an application crash or similar. The
* transaction will not rollback and will stop abruptly.
*
* However, for testing purposes, a TransactionFailed will still be raised,
* correct in all respects including the attempts field.
*/
class test_fail_hard : public client_error
{
public:
explicit test_fail_hard()
: client_error(FAIL_HARD, "Injecting a FAIL_HARD error")
{
}
};
/**
* Used only in testing: injects an error that will be handled as
* FAIL_AMBIGUOUS.
*
* E.g. either the server or SDK raised an error indicating the operation was
* ambiguously successful.
*/
class test_fail_ambiguous : public client_error
{
public:
explicit test_fail_ambiguous()
: client_error(FAIL_AMBIGUOUS, "Injecting a FAIL_AMBIGUOUS error")
{
}
};
/**
* Used only in testing: injects an error that will be handled as
* FAIL_TRANSIENT.
*
* E.g. a transient server error that could be recovered with a retry of either
* the operation or the transaction.
*/
class test_fail_transient : public client_error
{
public:
explicit test_fail_transient()
: client_error(FAIL_TRANSIENT, "Injecting a FAIL_TRANSIENT error")
{
}
};
/**
* Used only in testing: injects an error that will be handled as FAIL_OTHER.
*
* E.g. an error which is not retryable.
*/
class test_fail_other : public client_error
{
public:
explicit test_fail_other()
: client_error(FAIL_OTHER, "Injecting a FAIL_OTHER error")
{
}
};
} // namespace internal
} // namespace couchbase::core::transactions
template<>
struct fmt::formatter<couchbase::core::transactions::error_class> {
template<typename ParseContext>
constexpr auto parse(ParseContext& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
auto format(couchbase::core::transactions::error_class ec, FormatContext& ctx) const
{
string_view name = "UNKNOWN ERROR CLASS";
switch (ec) {
case couchbase::core::transactions::FAIL_HARD:
name = "FAIL_HARD";
break;
case couchbase::core::transactions::FAIL_OTHER:
name = "FAIL_OTHER";
break;
case couchbase::core::transactions::FAIL_TRANSIENT:
name = "FAIL_TRANSIENT";
break;
case couchbase::core::transactions::FAIL_AMBIGUOUS:
name = "FAIL_AMBIGUOUS";
break;
case couchbase::core::transactions::FAIL_DOC_ALREADY_EXISTS:
name = "FAIL_DOC_ALREADY_EXISTS";
break;
case couchbase::core::transactions::FAIL_DOC_NOT_FOUND:
name = "FAIL_DOC_NOT_FOUND";
break;
case couchbase::core::transactions::FAIL_PATH_NOT_FOUND:
name = "FAIL_PATH_NOT_FOUND";
break;
case couchbase::core::transactions::FAIL_CAS_MISMATCH:
name = "FAIL_CAS_MISMATCH";
break;
case couchbase::core::transactions::FAIL_WRITE_WRITE_CONFLICT:
name = "FAIL_WRITE_WRITE_CONFLICT";
break;
case couchbase::core::transactions::FAIL_ATR_FULL:
name = "FAIL_ATR_FULL";
break;
case couchbase::core::transactions::FAIL_PATH_ALREADY_EXISTS:
name = "FAIL_PATH_ALREADY_EXISTS";
break;
case couchbase::core::transactions::FAIL_EXPIRY:
name = "FAIL_EXPIRY";
break;
}
return format_to(ctx.out(), "{}", name);
}
};