mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
1
src/fdb/CMakeLists.txt
Normal file
1
src/fdb/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
target_add_lib(fdb common fdb_c)
|
||||
283
src/fdb/FDB.cc
Normal file
283
src/fdb/FDB.cc
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "FDB.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <folly/CancellationToken.h>
|
||||
#include <folly/Likely.h>
|
||||
#include <folly/experimental/coro/Baton.h>
|
||||
#include <folly/experimental/coro/CurrentExecutor.h>
|
||||
#include <folly/experimental/coro/DetachOnCancel.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
|
||||
#include "foundationdb/fdb_c_types.h"
|
||||
|
||||
namespace hf3fs::kv::fdb {
|
||||
|
||||
#define TOU8(str) reinterpret_cast<const uint8_t *>(str.data()), str.length()
|
||||
#define VIEW(name) std::string_view(reinterpret_cast<const char *>(name), name##_length)
|
||||
#define SELECTOR(selector) TOU8(selector.key), selector.orEqual, selector.offset
|
||||
|
||||
template <>
|
||||
void Result<Int64Result, int64_t>::extractValue() {
|
||||
error_ = fdb_future_get_int64(future_.get(), &value_);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<KeyResult, String>::extractValue() {
|
||||
const uint8_t *data;
|
||||
int data_length;
|
||||
error_ = fdb_future_get_key(future_.get(), &data, &data_length);
|
||||
if (error_ == 0) {
|
||||
value_ = VIEW(data);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<ValueResult, std::optional<String>>::extractValue() {
|
||||
fdb_bool_t present = false;
|
||||
const uint8_t *data;
|
||||
int data_length;
|
||||
error_ = fdb_future_get_value(future_.get(), &present, &data, &data_length);
|
||||
if (error_ == 0 && present) {
|
||||
value_ = VIEW(data);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<KeyArrayResult, std::vector<String>>::extractValue() {
|
||||
const FDBKey *keys;
|
||||
int count = 0;
|
||||
error_ = fdb_future_get_key_array(future_.get(), &keys, &count);
|
||||
if (error_ == 0 && count) {
|
||||
value_.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
value_.emplace_back(VIEW(keys[i].key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<StringArrayResult, std::vector<String>>::extractValue() {
|
||||
const char **strings;
|
||||
int count = 0;
|
||||
error_ = fdb_future_get_string_array(future_.get(), &strings, &count);
|
||||
if (error_ == 0 && count) {
|
||||
value_.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
value_.emplace_back(strings[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<KeyValueArrayResult, std::pair<std::vector<KeyValue>, bool>>::extractValue() {
|
||||
const FDBKeyValue *kv;
|
||||
int count;
|
||||
fdb_bool_t more = false;
|
||||
error_ = fdb_future_get_keyvalue_array(future_.get(), &kv, &count, &more);
|
||||
if (error_ == 0 && count) {
|
||||
auto &vec = value_.first;
|
||||
vec.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
vec.emplace_back(VIEW(kv[i].key), VIEW(kv[i].value));
|
||||
}
|
||||
value_.second = more;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<KeyRangeArrayResult, std::vector<KeyRange>>::extractValue() {
|
||||
const FDBKeyRange *ranges;
|
||||
int count;
|
||||
error_ = fdb_future_get_keyrange_array(future_.get(), &ranges, &count);
|
||||
if (error_ == 0 && count) {
|
||||
value_.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
KeyRange range;
|
||||
range.beginKey = VIEW(ranges[i].begin_key);
|
||||
range.endKey = VIEW(ranges[i].end_key);
|
||||
value_.push_back(std::move(range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Result<EmptyResult, EmptyValue>::extractValue() {}
|
||||
|
||||
static void coroCallback(FDBFuture *, void *para) {
|
||||
auto baton = static_cast<folly::coro::Baton *>(para);
|
||||
baton->post();
|
||||
}
|
||||
|
||||
template <class T, class V>
|
||||
Task<T> Result<T, V>::toTask(FDBFuture *f) {
|
||||
T result;
|
||||
result.future_.reset(f);
|
||||
|
||||
folly::coro::Baton baton;
|
||||
result.error_ = fdb_future_set_callback(f, coroCallback, &baton);
|
||||
if (result.error()) {
|
||||
co_return result;
|
||||
}
|
||||
std::atomic_bool cancel = false;
|
||||
auto token = co_await folly::coro::co_current_cancellation_token;
|
||||
folly::CancellationCallback cb(token, [&]() {
|
||||
cancel = true;
|
||||
fdb_future_cancel(f);
|
||||
});
|
||||
co_await baton;
|
||||
if (cancel.load()) {
|
||||
throw folly::OperationCancelled();
|
||||
}
|
||||
|
||||
result.error_ = fdb_future_get_error(f);
|
||||
if (result.error()) {
|
||||
co_return result;
|
||||
}
|
||||
|
||||
result.extractValue();
|
||||
co_return result;
|
||||
}
|
||||
|
||||
// Global
|
||||
fdb_error_t DB::selectAPIVersion(int version) { return fdb_select_api_version(version); }
|
||||
std::string_view DB::errorMsg(fdb_error_t code) { return fdb_get_error(code); }
|
||||
bool DB::evaluatePredicate(int predicate_test, fdb_error_t code) { return fdb_error_predicate(predicate_test, code); }
|
||||
|
||||
// Network
|
||||
fdb_error_t DB::setNetworkOption(FDBNetworkOption option, std::string_view value /* = {} */) {
|
||||
return fdb_network_set_option(option, TOU8(value));
|
||||
}
|
||||
fdb_error_t DB::setupNetwork() { return fdb_setup_network(); }
|
||||
fdb_error_t DB::runNetwork() { return fdb_run_network(); }
|
||||
fdb_error_t DB::stopNetwork() { return fdb_stop_network(); }
|
||||
|
||||
// DB
|
||||
fdb_error_t DB::setOption(FDBDatabaseOption option, std::string_view value) {
|
||||
return fdb_database_set_option(db_.get(), option, TOU8(value));
|
||||
}
|
||||
|
||||
Task<Int64Result> DB::rebootWorker(std::string_view address, bool check /* = false */, int duration /* = 0 */) {
|
||||
co_return co_await Int64Result::toTask(fdb_database_reboot_worker(db_.get(), TOU8(address), check, duration));
|
||||
}
|
||||
|
||||
Task<EmptyResult> DB::forceRecoveryWithDataLoss(std::string_view dcid) {
|
||||
co_return co_await EmptyResult::toTask(fdb_database_force_recovery_with_data_loss(db_.get(), TOU8(dcid)));
|
||||
}
|
||||
|
||||
Task<EmptyResult> DB::createSnapshot(std::string_view uid, std::string_view snapCommand) {
|
||||
co_return co_await EmptyResult::toTask(fdb_database_create_snapshot(db_.get(), TOU8(uid), TOU8(snapCommand)));
|
||||
}
|
||||
|
||||
Task<KeyResult> DB::purgeBlobGranules(const KeyRangeView &range, int64_t purgeVersion, fdb_bool_t force) {
|
||||
co_return co_await KeyResult::toTask(
|
||||
fdb_database_purge_blob_granules(db_.get(), TOU8(range.beginKey), TOU8(range.endKey), purgeVersion, force));
|
||||
}
|
||||
|
||||
Task<EmptyResult> DB::waitPurgeGranulesComplete(std::string_view purgeKey) {
|
||||
co_return co_await EmptyResult::toTask(fdb_database_wait_purge_granules_complete(db_.get(), TOU8(purgeKey)));
|
||||
}
|
||||
|
||||
void Transaction::reset() { fdb_transaction_reset(tr_.get()); }
|
||||
|
||||
void Transaction::cancel() { fdb_transaction_cancel(tr_.get()); }
|
||||
|
||||
[[nodiscard]] fdb_error_t Transaction::setOption(FDBTransactionOption option, std::string_view value /* = {} */) {
|
||||
return fdb_transaction_set_option(tr_.get(), option, TOU8(value));
|
||||
}
|
||||
|
||||
void Transaction::setReadVersion(int64_t version) { fdb_transaction_set_read_version(tr_.get(), version); }
|
||||
|
||||
Task<Int64Result> Transaction::getReadVersion() {
|
||||
co_return co_await Int64Result::toTask(fdb_transaction_get_read_version(tr_.get()));
|
||||
}
|
||||
|
||||
Task<Int64Result> Transaction::getApproximateSize() {
|
||||
co_return co_await Int64Result::toTask(fdb_transaction_get_approximate_size(tr_.get()));
|
||||
}
|
||||
|
||||
Task<KeyResult> Transaction::getVersionstamp() {
|
||||
co_return co_await KeyResult::toTask(fdb_transaction_get_versionstamp(tr_.get()));
|
||||
}
|
||||
|
||||
Task<ValueResult> Transaction::get(std::string_view key, fdb_bool_t snapshot /* = false */) {
|
||||
co_return co_await ValueResult::toTask(fdb_transaction_get(tr_.get(), TOU8(key), snapshot));
|
||||
}
|
||||
|
||||
Task<KeyResult> Transaction::getKey(const KeySelector &selector, fdb_bool_t snapshot /* = false */) {
|
||||
co_return co_await KeyResult::toTask(fdb_transaction_get_key(tr_.get(), SELECTOR(selector), snapshot));
|
||||
}
|
||||
|
||||
Task<StringArrayResult> Transaction::getAddressesForKey(std::string_view key) {
|
||||
co_return co_await StringArrayResult::toTask(fdb_transaction_get_addresses_for_key(tr_.get(), TOU8(key)));
|
||||
}
|
||||
|
||||
Task<KeyValueArrayResult> Transaction::getRange(const KeySelector &begin,
|
||||
const KeySelector &end,
|
||||
GetRangeLimits limits /* = GetRangeLimits() */,
|
||||
int iteration /* = 0 */,
|
||||
bool snapshot /* = false */,
|
||||
bool reverse /* = false */,
|
||||
FDBStreamingMode streamingMode /* = FDB_STREAMING_MODE_SERIAL */) {
|
||||
co_return co_await KeyValueArrayResult::toTask(fdb_transaction_get_range(tr_.get(),
|
||||
SELECTOR(begin),
|
||||
SELECTOR(end),
|
||||
limits.rows.value_or(-1),
|
||||
limits.bytes.value_or(-1),
|
||||
streamingMode,
|
||||
iteration,
|
||||
snapshot,
|
||||
reverse));
|
||||
}
|
||||
|
||||
Task<Int64Result> Transaction::getEstimatedRangeSizeBytes(const KeyRangeView &range) {
|
||||
co_return co_await Int64Result::toTask(
|
||||
fdb_transaction_get_estimated_range_size_bytes(tr_.get(), TOU8(range.beginKey), TOU8(range.endKey)));
|
||||
}
|
||||
|
||||
Task<KeyArrayResult> Transaction::getRangeSplitPoints(const KeyRangeView &range, int64_t chunkSize) {
|
||||
co_return co_await KeyArrayResult::toTask(
|
||||
fdb_transaction_get_range_split_points(tr_.get(), TOU8(range.beginKey), TOU8(range.endKey), chunkSize));
|
||||
}
|
||||
|
||||
Task<EmptyResult> Transaction::watch(std::string_view key) {
|
||||
co_return co_await EmptyResult::toTask(fdb_transaction_watch(tr_.get(), TOU8(key)));
|
||||
}
|
||||
|
||||
Task<EmptyResult> Transaction::commit() {
|
||||
if (UNLIKELY(readonly_)) {
|
||||
// Prevent tools like admin_cli or fsck from mistakenly modifying data
|
||||
XLOGF(CRITICAL, "disallow call commit on a read-only FDBContext!!!");
|
||||
EmptyResult result;
|
||||
result.error_ = 1000; /* operation failed */
|
||||
co_return result;
|
||||
}
|
||||
co_return co_await EmptyResult::toTask(fdb_transaction_commit(tr_.get()));
|
||||
}
|
||||
|
||||
Task<EmptyResult> Transaction::onError(fdb_error_t err) {
|
||||
co_return co_await EmptyResult::toTask(fdb_transaction_on_error(tr_.get(), err));
|
||||
}
|
||||
|
||||
void Transaction::clear(std::string_view key) { return fdb_transaction_clear(tr_.get(), TOU8(key)); }
|
||||
|
||||
void Transaction::clearRange(const KeyRangeView &range) {
|
||||
fdb_transaction_clear_range(tr_.get(), TOU8(range.beginKey), TOU8(range.endKey));
|
||||
}
|
||||
|
||||
void Transaction::set(std::string_view key, std::string_view value) {
|
||||
fdb_transaction_set(tr_.get(), TOU8(key), TOU8(value));
|
||||
}
|
||||
|
||||
void Transaction::atomicOp(std::string_view key, std::string_view param, FDBMutationType operationType) {
|
||||
return fdb_transaction_atomic_op(tr_.get(), TOU8(key), TOU8(param), operationType);
|
||||
}
|
||||
|
||||
[[nodiscard]] fdb_error_t Transaction::getCommittedVersion(int64_t *outVersion) {
|
||||
return fdb_transaction_get_committed_version(tr_.get(), outVersion);
|
||||
}
|
||||
|
||||
fdb_error_t Transaction::addConflictRange(const KeyRangeView &range, FDBConflictRangeType type) {
|
||||
return fdb_transaction_add_conflict_range(tr_.get(), TOU8(range.beginKey), TOU8(range.endKey), type);
|
||||
}
|
||||
|
||||
} // namespace hf3fs::kv::fdb
|
||||
206
src/fdb/FDB.h
Normal file
206
src/fdb/FDB.h
Normal file
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include <folly/Utility.h>
|
||||
#include <folly/experimental/coro/FutureUtil.h>
|
||||
#include <folly/experimental/coro/Promise.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/String.h"
|
||||
#include "foundationdb/fdb_c_options.g.h"
|
||||
#include "foundationdb/fdb_c_types.h"
|
||||
|
||||
#define FDB_API_VERSION 710
|
||||
#include <foundationdb/fdb_c.h>
|
||||
|
||||
namespace hf3fs::kv {
|
||||
|
||||
class FDBKVEngine;
|
||||
|
||||
namespace fdb {
|
||||
|
||||
using folly::coro::Task;
|
||||
using KeyValue = IReadOnlyTransaction::KeyValue;
|
||||
|
||||
// Structs for returned values.
|
||||
struct KeyRange {
|
||||
String beginKey;
|
||||
String endKey;
|
||||
};
|
||||
|
||||
// Structs for parameters.
|
||||
struct KeyRangeView {
|
||||
std::string_view beginKey;
|
||||
std::string_view endKey;
|
||||
};
|
||||
|
||||
struct KeySelector {
|
||||
std::string_view key; // Find the last item less than key
|
||||
bool orEqual = true; // (or equal to key, if this is true)
|
||||
int offset = 0; // and then move forward this many items (or backward if negative)
|
||||
|
||||
KeySelector() = default;
|
||||
KeySelector(std::string_view key, bool orEqual, int offset)
|
||||
: key(std::move(key)),
|
||||
orEqual(orEqual),
|
||||
offset(offset) {}
|
||||
};
|
||||
|
||||
struct GetRangeLimits {
|
||||
std::optional<uint32_t> rows;
|
||||
std::optional<uint32_t> bytes;
|
||||
|
||||
explicit GetRangeLimits(std::optional<uint32_t> r = {}, std::optional<uint32_t> b = {})
|
||||
: rows(r),
|
||||
bytes(b) {}
|
||||
};
|
||||
|
||||
template <class T, class V>
|
||||
class Result {
|
||||
public:
|
||||
fdb_error_t error() const { return error_; };
|
||||
V &value() { return value_; }
|
||||
const V &value() const { return value_; }
|
||||
|
||||
protected:
|
||||
friend class DB;
|
||||
friend class Transaction;
|
||||
|
||||
static Task<T> toTask(FDBFuture *f);
|
||||
void extractValue();
|
||||
|
||||
protected:
|
||||
struct FDBFutureDeleter {
|
||||
constexpr FDBFutureDeleter() noexcept = default;
|
||||
void operator()(FDBFuture *future) const { future ? fdb_future_destroy(future) : void(); }
|
||||
};
|
||||
std::unique_ptr<FDBFuture, FDBFutureDeleter> future_;
|
||||
fdb_error_t error_ = 0;
|
||||
V value_{};
|
||||
};
|
||||
|
||||
class Int64Result : public Result<Int64Result, int64_t> {};
|
||||
class KeyResult : public Result<KeyResult, String> {};
|
||||
class ValueResult : public Result<ValueResult, std::optional<String>> {};
|
||||
class KeyArrayResult : public Result<KeyArrayResult, std::vector<String>> {};
|
||||
class StringArrayResult : public Result<StringArrayResult, std::vector<String>> {};
|
||||
class KeyValueArrayResult : public Result<KeyValueArrayResult, std::pair<std::vector<KeyValue>, bool>> {};
|
||||
class KeyRangeArrayResult : public Result<KeyRangeArrayResult, std::vector<KeyRange>> {};
|
||||
struct EmptyValue {};
|
||||
class EmptyResult : public Result<EmptyResult, EmptyValue> {};
|
||||
|
||||
class DB {
|
||||
public:
|
||||
static fdb_error_t selectAPIVersion(int version);
|
||||
static std::string_view errorMsg(fdb_error_t code);
|
||||
static bool evaluatePredicate(int predicate_test, fdb_error_t code);
|
||||
|
||||
// network
|
||||
static fdb_error_t setNetworkOption(FDBNetworkOption option, std::string_view value = {});
|
||||
static fdb_error_t setupNetwork();
|
||||
static fdb_error_t runNetwork();
|
||||
static fdb_error_t stopNetwork();
|
||||
|
||||
public:
|
||||
DB() = default;
|
||||
explicit DB(const String &clusterFilePath, bool readonly)
|
||||
: readonly_(readonly) {
|
||||
auto path = clusterFilePath.empty() ? nullptr : clusterFilePath.c_str();
|
||||
FDBDatabase *db;
|
||||
error_ = fdb_create_database(path, &db);
|
||||
if (error_ == 0) {
|
||||
db_.reset(db);
|
||||
}
|
||||
}
|
||||
|
||||
fdb_error_t error() const { return error_; }
|
||||
explicit operator bool() const { return error() == 0; }
|
||||
operator FDBDatabase *() const { return db_.get(); }
|
||||
|
||||
fdb_error_t setOption(FDBDatabaseOption option, std::string_view value = {});
|
||||
|
||||
Task<Int64Result> rebootWorker(std::string_view address, bool check = false, int duration = 0);
|
||||
Task<EmptyResult> forceRecoveryWithDataLoss(std::string_view dcid);
|
||||
Task<EmptyResult> createSnapshot(std::string_view uid, std::string_view snapCommand);
|
||||
Task<KeyResult> purgeBlobGranules(const KeyRangeView &range, int64_t purgeVersion, fdb_bool_t force);
|
||||
Task<EmptyResult> waitPurgeGranulesComplete(std::string_view purgeKey);
|
||||
|
||||
bool readonly() const { return readonly_; }
|
||||
|
||||
private:
|
||||
friend class hf3fs::kv::FDBKVEngine;
|
||||
|
||||
struct FDBDatabaseDeleter {
|
||||
constexpr FDBDatabaseDeleter() noexcept = default;
|
||||
void operator()(FDBDatabase *db) const { db ? fdb_database_destroy(db) : void(); }
|
||||
};
|
||||
std::unique_ptr<FDBDatabase, FDBDatabaseDeleter> db_;
|
||||
bool readonly_ = false;
|
||||
fdb_error_t error_ = 1;
|
||||
};
|
||||
|
||||
class Transaction final {
|
||||
public:
|
||||
Transaction(DB &db)
|
||||
: readonly_(db.readonly()) {
|
||||
FDBTransaction *tr;
|
||||
error_ = fdb_database_create_transaction(db, &tr);
|
||||
if (error_ == 0) {
|
||||
tr_.reset(tr);
|
||||
}
|
||||
}
|
||||
|
||||
fdb_error_t error() const { return error_; }
|
||||
explicit operator bool() const { return error_ == 0 && tr_; }
|
||||
|
||||
fdb_error_t setOption(FDBTransactionOption option, std::string_view value = {});
|
||||
|
||||
// Read transaction
|
||||
void setReadVersion(int64_t version);
|
||||
Task<Int64Result> getReadVersion();
|
||||
|
||||
Task<ValueResult> get(std::string_view key, fdb_bool_t snapshot = false);
|
||||
Task<KeyResult> getKey(const KeySelector &selector, fdb_bool_t snapshot = false);
|
||||
Task<EmptyResult> watch(std::string_view key);
|
||||
|
||||
Task<StringArrayResult> getAddressesForKey(std::string_view key);
|
||||
Task<KeyValueArrayResult> getRange(const KeySelector &begin,
|
||||
const KeySelector &end,
|
||||
GetRangeLimits limits = GetRangeLimits(),
|
||||
int iteration = 0,
|
||||
bool snapshot = false,
|
||||
bool reverse = false,
|
||||
FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL);
|
||||
|
||||
Task<Int64Result> getEstimatedRangeSizeBytes(const KeyRangeView &range);
|
||||
Task<KeyArrayResult> getRangeSplitPoints(const KeyRangeView &range, int64_t chunkSize);
|
||||
|
||||
Task<EmptyResult> onError(fdb_error_t err);
|
||||
|
||||
void cancel();
|
||||
void reset();
|
||||
|
||||
// Write transaction
|
||||
fdb_error_t addConflictRange(const KeyRangeView &range, FDBConflictRangeType type);
|
||||
|
||||
void atomicOp(std::string_view key, std::string_view param, FDBMutationType operationType);
|
||||
void set(std::string_view key, std::string_view value);
|
||||
void clear(std::string_view key);
|
||||
void clearRange(const KeyRangeView &range);
|
||||
|
||||
Task<EmptyResult> commit();
|
||||
fdb_error_t getCommittedVersion(int64_t *outVersion);
|
||||
Task<Int64Result> getApproximateSize();
|
||||
Task<KeyResult> getVersionstamp();
|
||||
|
||||
private:
|
||||
struct FDBTransactionDeleter {
|
||||
constexpr FDBTransactionDeleter() noexcept = default;
|
||||
void operator()(FDBTransaction *tr) const { tr ? fdb_transaction_destroy(tr) : void(); }
|
||||
};
|
||||
std::unique_ptr<FDBTransaction, FDBTransactionDeleter> tr_;
|
||||
bool readonly_ = false;
|
||||
fdb_error_t error_ = 1;
|
||||
};
|
||||
} // namespace fdb
|
||||
} // namespace hf3fs::kv
|
||||
22
src/fdb/FDBConfig.h
Normal file
22
src/fdb/FDBConfig.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "common/utils/ConfigBase.h"
|
||||
|
||||
namespace hf3fs::kv::fdb {
|
||||
struct FDBConfig : public ConfigBase<FDBConfig> {
|
||||
CONFIG_ITEM(clusterFile, "");
|
||||
CONFIG_ITEM(enableMultipleClient, false);
|
||||
CONFIG_ITEM(externalClientDir, "");
|
||||
CONFIG_ITEM(externalClientPath, "");
|
||||
CONFIG_ITEM(multipleClientThreadNum, 4L, ConfigCheckers::checkPositive);
|
||||
CONFIG_ITEM(trace_file, "");
|
||||
CONFIG_ITEM(trace_format, "json");
|
||||
CONFIG_ITEM(casual_read_risky, false);
|
||||
CONFIG_ITEM(default_backoff, 0);
|
||||
CONFIG_ITEM(readonly, false);
|
||||
};
|
||||
|
||||
} // namespace hf3fs::kv::fdb
|
||||
104
src/fdb/FDBContext.cc
Normal file
104
src/fdb/FDBContext.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "FDBContext.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <memory>
|
||||
|
||||
#include "common/utils/String.h"
|
||||
#include "foundationdb/fdb_c_options.g.h"
|
||||
|
||||
namespace hf3fs::kv::fdb {
|
||||
|
||||
#define CHECK_FDB_COND(condition, ...) XLOGF_IF(FATAL, !(condition), "FoundationDB error. " __VA_ARGS__)
|
||||
|
||||
#define CHECK_FDB_ERR(err, msg, ...) \
|
||||
XLOGF_IF(FATAL, (err) != 0, "FoundationDB error: {}. " msg, DB::errorMsg(err), #__VA_ARGS__)
|
||||
|
||||
#define CHECK_FDB_OP(cmd, ...) \
|
||||
do { \
|
||||
auto err = (cmd); \
|
||||
CHECK_FDB_ERR(err, #__VA_ARGS__); \
|
||||
} while (false)
|
||||
|
||||
std::shared_ptr<FDBContext> FDBContext::create(const FDBConfig &config) {
|
||||
auto context = std::shared_ptr<FDBContext>(new FDBContext(config));
|
||||
return context;
|
||||
}
|
||||
|
||||
FDBContext::FDBContext(const FDBConfig &config)
|
||||
: config_(config) {
|
||||
CHECK_FDB_OP(DB::selectAPIVersion(FDB_API_VERSION), "Failed to set API Version = {}.", FDB_API_VERSION);
|
||||
|
||||
if (config_.enableMultipleClient()) {
|
||||
XLOGF(INFO, "FoundationDB: enable externalClientDir");
|
||||
const String &externalDir = config_.externalClientDir();
|
||||
const String &externalPath = config_.externalClientPath();
|
||||
CHECK_FDB_COND((!externalDir.empty() || !externalPath.empty()),
|
||||
"'externalClientDir' or 'externalClientPath' should not be empty when 'enableMultipleClient'.");
|
||||
|
||||
// CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_DISABLE_LOCAL_CLIENT), "Failed to disable local client.");
|
||||
if (!externalPath.empty()) {
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY, externalPath),
|
||||
"Failed to set FDB_NET_OPTION_EXTERNAL_CLIENT_LIBRARY = {}.",
|
||||
externalPath);
|
||||
} else {
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY, externalDir),
|
||||
"Failed to set FDB_NET_OPTION_EXTERNAL_CLIENT_DIRECTORY = {}.",
|
||||
externalDir);
|
||||
}
|
||||
|
||||
auto threadCnt = config_.multipleClientThreadNum();
|
||||
auto threadCntOption = std::string_view{reinterpret_cast<const char *>(&threadCnt), sizeof(threadCnt)};
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION, threadCntOption),
|
||||
"Failed to set FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION = {}.",
|
||||
threadCnt);
|
||||
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS),
|
||||
"Failed to set FDB_NET_OPTION_CALLBACKS_ON_EXTERNAL_THREADS.");
|
||||
}
|
||||
|
||||
if (!config_.trace_file().empty()) {
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_TRACE_ENABLE, config_.trace_file()),
|
||||
"Failed to set FDB_NET_OPTION_TRACE_ENABLE to {}",
|
||||
config_.trace_file());
|
||||
CHECK_FDB_OP(DB::setNetworkOption(FDB_NET_OPTION_TRACE_FORMAT, config_.trace_format()),
|
||||
"Failed to set FDB_NET_OPTION_TRACE_FORMAT to {}",
|
||||
config_.trace_format());
|
||||
}
|
||||
|
||||
if (config_.default_backoff()) {
|
||||
CHECK_FDB_OP(
|
||||
DB::setNetworkOption(FDB_NET_OPTION_KNOB, fmt::format("DEFAULT_BACKOFF={}", config_.default_backoff())),
|
||||
"Failed to set DEFAULT_BACKOFF to {}",
|
||||
config_.default_backoff());
|
||||
}
|
||||
|
||||
CHECK_FDB_OP(DB::setupNetwork(), "Failed to setup network thread.");
|
||||
|
||||
networkThread = std::thread([] {
|
||||
pthread_setname_np(pthread_self(), "fdb_net");
|
||||
DB::runNetwork();
|
||||
});
|
||||
}
|
||||
|
||||
FDBContext::~FDBContext() {
|
||||
if (networkThread.joinable()) {
|
||||
CHECK_FDB_OP(DB::stopNetwork(), "Failed to stop network thread.");
|
||||
|
||||
networkThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
DB FDBContext::getDB() const {
|
||||
DB db(config_.clusterFile(), config_.readonly());
|
||||
CHECK_FDB_ERR(db.error(), "Failed to get fdb::DB instance.");
|
||||
if (config_.casual_read_risky()) {
|
||||
CHECK_FDB_ERR(db.setOption(FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY),
|
||||
"Failed to set FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY");
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
#undef CHECK_FDB_OP
|
||||
#undef CHECK_FDB_COND
|
||||
} // namespace hf3fs::kv::fdb
|
||||
27
src/fdb/FDBContext.h
Normal file
27
src/fdb/FDBContext.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "FDB.h"
|
||||
#include "FDBConfig.h"
|
||||
#include "common/utils/ConfigBase.h"
|
||||
|
||||
namespace hf3fs::kv::fdb {
|
||||
// FDBContext encapsulates the setup and cleanup of FoundationDB.
|
||||
// It is designed to be used in the main function.
|
||||
class FDBContext {
|
||||
public:
|
||||
static std::shared_ptr<FDBContext> create(const FDBConfig &config);
|
||||
|
||||
~FDBContext();
|
||||
|
||||
DB getDB() const;
|
||||
|
||||
int64_t maxDbCount() const { return config_.enableMultipleClient() ? config_.multipleClientThreadNum() : 1; }
|
||||
|
||||
private:
|
||||
explicit FDBContext(const FDBConfig &config);
|
||||
FDBConfig config_; // don't support hot update config
|
||||
std::thread networkThread;
|
||||
};
|
||||
} // namespace hf3fs::kv::fdb
|
||||
45
src/fdb/FDBKVEngine.h
Normal file
45
src/fdb/FDBKVEngine.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <folly/Likely.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
|
||||
#include "FDB.h"
|
||||
#include "FDBTransaction.h"
|
||||
#include "common/kv/IKVEngine.h"
|
||||
|
||||
namespace hf3fs {
|
||||
|
||||
namespace meta {
|
||||
template <typename KV>
|
||||
class MetaTestBase;
|
||||
}
|
||||
|
||||
namespace kv {
|
||||
class FDBKVEngine : public IKVEngine {
|
||||
public:
|
||||
FDBKVEngine(fdb::DB db)
|
||||
: db_(std::move(db)) {}
|
||||
|
||||
std::unique_ptr<IReadOnlyTransaction> createReadonlyTransaction() override { return createReadWriteTransaction(); }
|
||||
|
||||
std::unique_ptr<IReadWriteTransaction> createReadWriteTransaction() override {
|
||||
fdb::Transaction tr(db_);
|
||||
if (UNLIKELY(tr.error())) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<FDBTransaction>(std::move(tr));
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename KV>
|
||||
friend class hf3fs::meta::MetaTestBase;
|
||||
|
||||
FRIEND_TEST(TestFDBTransaction, Readonly);
|
||||
|
||||
void setReadonly(bool rdonly) { db_.readonly_ = rdonly; }
|
||||
|
||||
fdb::DB db_;
|
||||
};
|
||||
} // namespace kv
|
||||
|
||||
} // namespace hf3fs
|
||||
134
src/fdb/FDBRetryStrategy.h
Normal file
134
src/fdb/FDBRetryStrategy.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <folly/Likely.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/symbolizer/Symbolizer.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <foundationdb/fdb_c_types.h>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/Status.h"
|
||||
#include "fdb/FDBTransaction.h"
|
||||
|
||||
namespace hf3fs::kv {
|
||||
|
||||
class FDBRetryStrategy {
|
||||
public:
|
||||
static constexpr Duration kMinBackoff = 10_ms; // same with FDB, 0.01s
|
||||
|
||||
struct Config {
|
||||
Duration maxBackoff = 1_s;
|
||||
size_t maxRetryCount = 10;
|
||||
bool retryMaybeCommitted = true;
|
||||
};
|
||||
|
||||
FDBRetryStrategy()
|
||||
: FDBRetryStrategy(Config()) {}
|
||||
FDBRetryStrategy(Config config)
|
||||
: config_(config),
|
||||
backoff_(kMinBackoff),
|
||||
retry_(0) {}
|
||||
|
||||
template <typename Txn>
|
||||
Result<Void> init(Txn *txn) {
|
||||
retry_ = 0;
|
||||
backoff_ = kMinBackoff;
|
||||
|
||||
auto *fdbTransaction = dynamic_cast<FDBTransaction *>(txn);
|
||||
if (fdbTransaction) {
|
||||
uint64_t value = std::chrono::duration_cast<std::chrono::milliseconds>(config_.maxBackoff).count();
|
||||
auto result = fdbTransaction->setOption(FDBTransactionOption::FDB_TR_OPTION_MAX_RETRY_DELAY,
|
||||
std::string_view((char *)&value, sizeof(value)));
|
||||
if (result.hasError()) {
|
||||
XLOGF(ERR, "Failed to set option on FDBTransaction, {}", result.error().describe());
|
||||
RETURN_ERROR(result);
|
||||
}
|
||||
}
|
||||
|
||||
return Void{};
|
||||
}
|
||||
|
||||
template <typename Txn>
|
||||
CoTryTask<void> onError(Txn *txn, Status error) {
|
||||
if (retry_ >= config_.maxRetryCount) {
|
||||
XLOGF(ERR, "Transaction failed after retry {} times, error {}", retry_, error.describe());
|
||||
co_return makeError(std::move(error));
|
||||
}
|
||||
if (!TransactionHelper::isTransactionError(error)) {
|
||||
XLOGF(DBG, "Not transaction error {}", error);
|
||||
co_return makeError(std::move(error));
|
||||
}
|
||||
XLOGF(DBG, "Transaction error {}", error);
|
||||
|
||||
SCOPE_EXIT {
|
||||
// update retry and backoff before exit.
|
||||
backoff_ = std::min(config_.maxBackoff, Duration(backoff_ * 2));
|
||||
retry_++;
|
||||
};
|
||||
|
||||
auto *fdbTransaction = dynamic_cast<FDBTransaction *>(txn);
|
||||
if (fdbTransaction) {
|
||||
co_return co_await fdbBackoff(fdbTransaction, std::move(error));
|
||||
} else {
|
||||
co_return co_await defaultBackoff(txn, std::move(error));
|
||||
}
|
||||
}
|
||||
|
||||
CoTryTask<void> fdbBackoff(FDBTransaction *txn, Status error) {
|
||||
auto errcode = txn->errcode();
|
||||
if (UNLIKELY(!errcode)) {
|
||||
XLOGF_IF(CRITICAL,
|
||||
error.code() != TransactionCode::kTooOld,
|
||||
"Failed to get FDB errcode, error {}, stacktrace {}",
|
||||
error,
|
||||
folly::symbolizer::getStackTraceStr());
|
||||
co_return co_await defaultBackoff(txn, std::move(error));
|
||||
}
|
||||
|
||||
FDBErrorPredicate predict =
|
||||
config_.retryMaybeCommitted ? FDB_ERROR_PREDICATE_RETRYABLE : FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED;
|
||||
if (!fdb_error_predicate(predict, errcode)) {
|
||||
XLOGF(ERR, "Transaction error not retryable: {}, errcode {}", error, errcode);
|
||||
co_return makeError(std::move(error));
|
||||
}
|
||||
|
||||
XLOGF(DBG, "FDBRetryStrategy backoff by FoundationDB");
|
||||
auto ok = co_await txn->onError(errcode);
|
||||
if (!ok) {
|
||||
co_return makeError(std::move(error));
|
||||
}
|
||||
co_return Void{};
|
||||
}
|
||||
|
||||
template <typename Txn>
|
||||
CoTryTask<void> defaultBackoff(Txn *txn, Status error) {
|
||||
// fallback to our backoff implementation
|
||||
if (!TransactionHelper::isRetryable(error, config_.retryMaybeCommitted)) {
|
||||
XLOGF(ERR, "Transaction error not retryable: {}", error);
|
||||
co_return makeError(std::move(error));
|
||||
}
|
||||
XLOGF(WARN, "Transaction retryable error: {}", error);
|
||||
|
||||
XLOGF(DBG, "FDBRetryStrategy backoff transaction {}ms", backoff_.count());
|
||||
txn->reset();
|
||||
|
||||
auto duration = Duration(backoff_ / 100 * folly::Random::rand32(80, 120));
|
||||
co_await folly::coro::sleep(duration.asUs());
|
||||
co_return Void{};
|
||||
}
|
||||
|
||||
private:
|
||||
Config config_;
|
||||
Duration backoff_;
|
||||
size_t retry_;
|
||||
};
|
||||
|
||||
} // namespace hf3fs::kv
|
||||
438
src/fdb/FDBTransaction.cc
Normal file
438
src/fdb/FDBTransaction.cc
Normal file
@@ -0,0 +1,438 @@
|
||||
#include "FDBTransaction.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <folly/Likely.h>
|
||||
#include <folly/Portability.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/detail/Types.h>
|
||||
#include <folly/lang/Bits.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <foundationdb/fdb_c_types.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/monitor/Recorder.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/MagicEnum.hpp"
|
||||
#include "common/utils/RandomUtils.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/SerDeser.h"
|
||||
#include "common/utils/Status.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fdb/FDB.h"
|
||||
#include "foundationdb/fdb_c_options.g.h"
|
||||
|
||||
#define CHECK_FDB_ERRCODE(op, code) \
|
||||
do { \
|
||||
fdb_error_t errcode = code; \
|
||||
if (UNLIKELY(errcode != 0)) { \
|
||||
errcode_ = errcode; \
|
||||
if (UNLIKELY(errcode == error_code_not_committed)) { \
|
||||
XLOGF(WARN, \
|
||||
"FDBTransaction {} failed, err: {}, msg: {}.", \
|
||||
magic_enum::enum_name(op), \
|
||||
errcode, \
|
||||
fdb::DB::errorMsg(errcode)); \
|
||||
} else { \
|
||||
XLOGF(ERR, \
|
||||
"FDBTransaction {} failed, err: {}, msg: {}.", \
|
||||
magic_enum::enum_name(op), \
|
||||
errcode, \
|
||||
fdb::DB::errorMsg(errcode)); \
|
||||
} \
|
||||
co_return makeFDBError(errcode, op); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FAULT_INJECTION_ON_COMMIT(db_name, injectMaybeCommitted) \
|
||||
do { \
|
||||
if (FAULT_INJECTION()) { \
|
||||
auto delayMs = folly::Random::rand32(20, 100); \
|
||||
errcode_ = hf3fs::RandomUtils::randomSelect(std::vector<fdb_error_t>{error_code_not_committed, \
|
||||
error_code_commit_unknown_result, \
|
||||
error_code_transaction_too_old, \
|
||||
error_code_tag_throttled}); \
|
||||
XLOGF(WARN, "Inject fault on commit, errCode: {}, delayMs: {}.", errcode_.load(), delayMs); \
|
||||
if (errcode_ == error_code_commit_unknown_result && folly::Random::oneIn(2)) { \
|
||||
injectMaybeCommitted = true; \
|
||||
XLOGF(WARN, "Inject maybeCommitted on commit, commit transaction in " #db_name "."); \
|
||||
} else { \
|
||||
co_return makeError(convertError(errcode_, op), "Inject fault on commit."); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FAULT_INJECTION_ON_GET(op_name) \
|
||||
do { \
|
||||
if (FAULT_INJECTION()) { \
|
||||
auto delayMs = folly::Random::rand32(20, 100); \
|
||||
errcode_ = hf3fs::RandomUtils::randomSelect( \
|
||||
std::vector<fdb_error_t>{error_code_transaction_too_old, error_code_tag_throttled}); \
|
||||
XLOGF(WARN, "Inject fault on " #op_name ", errCode: {}, delayMs: {}.", errcode_.load(), delayMs); \
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(delayMs)); \
|
||||
co_return makeError(convertError(errcode_, op), "Inject fault on " #op_name); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace hf3fs::kv {
|
||||
namespace {
|
||||
|
||||
enum class Op {
|
||||
Get = 0,
|
||||
SnapshotGet,
|
||||
GetRange,
|
||||
SnapshotGetRange,
|
||||
AddReadConflict,
|
||||
Commit,
|
||||
Cancel,
|
||||
Set,
|
||||
SetVersionstampedKey,
|
||||
SetVersionstampedValue,
|
||||
Clear,
|
||||
ClearRange,
|
||||
GetReadVersion,
|
||||
};
|
||||
|
||||
template <Op op>
|
||||
struct OpRecorder {
|
||||
static constexpr bool recordFailed = false;
|
||||
static constexpr bool recordLatency = false;
|
||||
static monitor::CountRecorder totalRecorder;
|
||||
};
|
||||
|
||||
#define OP_RECORDER_BASIC(op, name) \
|
||||
template <> \
|
||||
struct OpRecorder<op> { \
|
||||
static constexpr bool recordFailed = false; \
|
||||
static constexpr bool recordLatency = false; \
|
||||
static inline monitor::CountRecorder totalRecorder{"fdb_total_count_" name}; \
|
||||
}
|
||||
|
||||
#define OP_RECORDER_FAILED(op, name) \
|
||||
template <> \
|
||||
struct OpRecorder<op> { \
|
||||
static constexpr bool recordFailed = true; \
|
||||
static constexpr bool recordLatency = false; \
|
||||
static inline monitor::CountRecorder totalRecorder{"fdb_total_count_" name}; \
|
||||
static inline monitor::CountRecorder failedRecorder{"fdb_failed_count_" name}; \
|
||||
}
|
||||
|
||||
#define OP_RECORDER_LATENCY(op, name) \
|
||||
template <> \
|
||||
struct OpRecorder<op> { \
|
||||
static constexpr bool recordFailed = true; \
|
||||
static constexpr bool recordLatency = true; \
|
||||
static inline monitor::CountRecorder totalRecorder{"fdb_total_count_" name}; \
|
||||
static inline monitor::CountRecorder failedRecorder{"fdb_failed_count_" name}; \
|
||||
static inline monitor::LatencyRecorder latencyRecorder{"fdb_latency_" name}; \
|
||||
}
|
||||
|
||||
OP_RECORDER_BASIC(Op::Set, "set");
|
||||
OP_RECORDER_BASIC(Op::SetVersionstampedKey, "set_versionstamped_key");
|
||||
OP_RECORDER_BASIC(Op::SetVersionstampedValue, "set_versionstamped_value");
|
||||
OP_RECORDER_BASIC(Op::Cancel, "cancel");
|
||||
OP_RECORDER_BASIC(Op::Clear, "clear");
|
||||
OP_RECORDER_BASIC(Op::ClearRange, "clear_range");
|
||||
OP_RECORDER_FAILED(Op::AddReadConflict, "add_read_conflict");
|
||||
OP_RECORDER_LATENCY(Op::Get, "get");
|
||||
OP_RECORDER_LATENCY(Op::SnapshotGet, "snapshot_get");
|
||||
OP_RECORDER_LATENCY(Op::GetRange, "get_range");
|
||||
OP_RECORDER_LATENCY(Op::SnapshotGetRange, "snapshot_get_range");
|
||||
OP_RECORDER_LATENCY(Op::Commit, "commit");
|
||||
OP_RECORDER_LATENCY(Op::GetReadVersion, "get_read_version");
|
||||
|
||||
monitor::CountRecorder retryConflict("fdb.retry_conflict");
|
||||
monitor::CountRecorder retryOther("fdb.retry_other");
|
||||
monitor::LatencyRecorder retryBackoff("fdb.retry_backoff");
|
||||
|
||||
#define ERROR(name, code, desp) constexpr int error_code_##name = code;
|
||||
FOLLY_PUSH_WARNING
|
||||
FOLLY_GNU_DISABLE_WARNING("-Wpedantic")
|
||||
#include "error_definitions.h"
|
||||
FOLLY_POP_WARNING
|
||||
#undef ERROR
|
||||
|
||||
uint32_t convertError(fdb_error_t code, Op op) {
|
||||
// see: Transaction::onError (foundationdb/fdbclient/NativeAPI.actor.cpp:7036)
|
||||
// see: fdb_error_predicate (foundationdb/bindings/c/fdb_c.cpp:71)
|
||||
assert(code != 0);
|
||||
switch (code) {
|
||||
case error_code_not_committed:
|
||||
return TransactionCode::kConflict;
|
||||
|
||||
case error_code_commit_unknown_result:
|
||||
return TransactionCode::kMaybeCommitted;
|
||||
|
||||
case error_code_proxy_memory_limit_exceeded:
|
||||
return TransactionCode::kResourceConstrained;
|
||||
|
||||
case error_code_process_behind:
|
||||
return TransactionCode::kProcessBehind;
|
||||
|
||||
case error_code_batch_transaction_throttled:
|
||||
case error_code_tag_throttled:
|
||||
return TransactionCode::kThrottled;
|
||||
|
||||
case error_code_transaction_too_old:
|
||||
return TransactionCode::kTooOld;
|
||||
|
||||
case error_code_future_version:
|
||||
return TransactionCode::kFutureVersion;
|
||||
|
||||
case error_code_connection_failed:
|
||||
case error_code_blocked_from_network_thread:
|
||||
return TransactionCode::kNetworkError;
|
||||
|
||||
case error_code_transaction_cancelled:
|
||||
return TransactionCode::kCanceled;
|
||||
|
||||
case error_code_database_locked:
|
||||
case error_code_unknown_tenant:
|
||||
return TransactionCode::kRetryable;
|
||||
|
||||
case error_code_cluster_version_changed:
|
||||
return op == Op::Commit ? TransactionCode::kMaybeCommitted : TransactionCode::kRetryable;
|
||||
}
|
||||
return TransactionCode::kFailed;
|
||||
}
|
||||
|
||||
inline folly::Unexpected<Status> makeFDBError(fdb_error_t code, Op op) {
|
||||
auto msg = fmt::format("FDB error: {}, msg: {}", code, fdb::DB::errorMsg(code));
|
||||
return makeError(convertError(code, op), std::move(msg));
|
||||
}
|
||||
|
||||
template <Op op>
|
||||
struct OpWrapper {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
template <typename F>
|
||||
static std::invoke_result_t<F, Op> run(F &&f) {
|
||||
OpRecorder<op>::totalRecorder.addSample(1);
|
||||
auto start = Clock::now();
|
||||
auto result = co_await f(op);
|
||||
auto duration = Clock::now() - start;
|
||||
if (UNLIKELY(result.hasError())) {
|
||||
if constexpr (OpRecorder<op>::recordFailed) {
|
||||
auto tagset = monitor::TagSet::create("statusCode", String(StatusCode::toString(result.error().code())));
|
||||
OpRecorder<op>::failedRecorder.addSample(1, tagset);
|
||||
}
|
||||
}
|
||||
if constexpr (OpRecorder<op>::recordLatency) {
|
||||
OpRecorder<op>::latencyRecorder.addSample(duration);
|
||||
}
|
||||
co_return result;
|
||||
}
|
||||
};
|
||||
|
||||
std::string appendOffset(std::string_view str, uint32_t offset) {
|
||||
String buf;
|
||||
buf.reserve(str.size() + sizeof(offset));
|
||||
Serializer ser{buf};
|
||||
ser.putRaw(str.data(), str.size());
|
||||
ser.put(folly::Endian::little32(offset));
|
||||
assert(buf.size() == str.size() + 4);
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CoTryTask<std::optional<String>> FDBTransaction::get(std::string_view key) {
|
||||
co_return co_await OpWrapper<Op::Get>::run([&](Op op) -> CoTryTask<std::optional<String>> {
|
||||
FAULT_INJECTION_ON_GET(get);
|
||||
|
||||
auto result = co_await tr_.get(key);
|
||||
CHECK_FDB_ERRCODE(op, result.error());
|
||||
co_return std::move(result.value());
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<FDBTransaction::GetRangeResult> FDBTransaction::getRange(const KeySelector &begin,
|
||||
const KeySelector &end,
|
||||
int32_t limit) {
|
||||
co_return co_await OpWrapper<Op::GetRange>::run([&](Op op) -> CoTryTask<GetRangeResult> {
|
||||
FAULT_INJECTION_ON_GET(getRange);
|
||||
|
||||
fdb::KeySelector innerBegin(begin.key, !begin.inclusive, 1);
|
||||
fdb::KeySelector innerEnd(end.key, end.inclusive, 1);
|
||||
fdb::GetRangeLimits limits(limit);
|
||||
auto result = co_await tr_.getRange(innerBegin, innerEnd, limits, /* iteration */ 0, /* snapshot */ false);
|
||||
CHECK_FDB_ERRCODE(op, result.error());
|
||||
co_return GetRangeResult(std::move(result.value().first), result.value().second);
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<FDBTransaction::GetRangeResult> FDBTransaction::snapshotGetRange(const KeySelector &begin,
|
||||
const KeySelector &end,
|
||||
int32_t limit) {
|
||||
co_return co_await OpWrapper<Op::SnapshotGetRange>::run([&](Op op) -> CoTryTask<GetRangeResult> {
|
||||
FAULT_INJECTION_ON_GET(snapshotGetRange);
|
||||
|
||||
fdb::KeySelector innerBegin(begin.key, !begin.inclusive, 1);
|
||||
fdb::KeySelector innerEnd(end.key, end.inclusive, 1);
|
||||
fdb::GetRangeLimits limits(limit);
|
||||
auto result = co_await tr_.getRange(innerBegin, innerEnd, limits, /* iteration */ 0, /* snapshot */ true);
|
||||
CHECK_FDB_ERRCODE(op, result.error());
|
||||
co_return GetRangeResult(std::move(result.value().first), result.value().second);
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::cancel() {
|
||||
co_return co_await OpWrapper<Op::Cancel>::run([&](Op) -> CoTryTask<void> {
|
||||
tr_.cancel();
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<std::optional<String>> FDBTransaction::snapshotGet(std::string_view key) {
|
||||
co_return co_await OpWrapper<Op::SnapshotGet>::run([&](Op op) -> CoTryTask<std::optional<String>> {
|
||||
FAULT_INJECTION_ON_GET(snapshotGet);
|
||||
|
||||
auto result = co_await tr_.get(key, /* snapshot = */ true);
|
||||
CHECK_FDB_ERRCODE(op, result.error());
|
||||
co_return std::move(result.value());
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::addReadConflict(std::string_view key) {
|
||||
co_return co_await OpWrapper<Op::AddReadConflict>::run([&](Op op) -> CoTryTask<void> {
|
||||
String endKey = TransactionHelper::keyAfter(key);
|
||||
fdb::KeyRangeView range;
|
||||
range.beginKey = key;
|
||||
range.endKey = endKey;
|
||||
auto result = tr_.addConflictRange(range, FDBConflictRangeType::FDB_CONFLICT_RANGE_TYPE_READ);
|
||||
CHECK_FDB_ERRCODE(op, result);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::addReadConflictRange(std::string_view begin, std::string_view end) {
|
||||
co_return co_await OpWrapper<Op::AddReadConflict>::run([&](Op op) -> CoTryTask<void> {
|
||||
fdb::KeyRangeView range;
|
||||
range.beginKey = begin;
|
||||
range.endKey = end;
|
||||
auto result = tr_.addConflictRange(range, FDBConflictRangeType::FDB_CONFLICT_RANGE_TYPE_READ);
|
||||
CHECK_FDB_ERRCODE(op, result);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::set(std::string_view key, std::string_view value) {
|
||||
co_return co_await OpWrapper<Op::Set>::run([&](Op) -> CoTryTask<void> {
|
||||
tr_.set(key, value);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::setVersionstampedKey(std::string_view key, uint32_t offset, std::string_view value) {
|
||||
if (offset + 10 > key.size()) {
|
||||
co_return makeError(StatusCode::kInvalidArg,
|
||||
fmt::format("setVersionstampedKey: {} + 10 > key.size {}", offset, key.size()));
|
||||
}
|
||||
co_return co_await OpWrapper<Op::SetVersionstampedKey>::run([&](Op) -> CoTryTask<void> {
|
||||
tr_.atomicOp(appendOffset(key, offset), value, FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::setVersionstampedValue(std::string_view key, std::string_view value, uint32_t offset) {
|
||||
if (offset + 10 > value.size()) {
|
||||
co_return makeError(StatusCode::kInvalidArg,
|
||||
fmt::format("setVersionstampedValue: {} + 10 > value.size {}", offset, value.size()));
|
||||
}
|
||||
co_return co_await OpWrapper<Op::SetVersionstampedValue>::run([&](Op) -> CoTryTask<void> {
|
||||
tr_.atomicOp(key, appendOffset(value, offset), FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::clear(std::string_view key) {
|
||||
co_return co_await OpWrapper<Op::Clear>::run([&](Op) -> CoTryTask<void> {
|
||||
tr_.clear(key);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::clearRange(std::string_view begin, std::string_view end) {
|
||||
co_return co_await OpWrapper<Op::ClearRange>::run([&](Op) -> CoTryTask<void> {
|
||||
fdb::KeyRangeView range;
|
||||
range.beginKey = begin;
|
||||
range.endKey = end;
|
||||
tr_.clearRange(range);
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
CoTryTask<void> FDBTransaction::commit() {
|
||||
co_return co_await OpWrapper<Op::Commit>::run([&](Op op) -> CoTryTask<void> {
|
||||
bool injectMaybeCommitted = false;
|
||||
FAULT_INJECTION_ON_COMMIT(FoundationDB, injectMaybeCommitted);
|
||||
|
||||
auto result = co_await tr_.commit();
|
||||
if (UNLIKELY(injectMaybeCommitted)) {
|
||||
errcode_ = error_code_commit_unknown_result;
|
||||
XLOGF(WARN, "Inject maybeCommitted error after commit, FoundationDB commit result is {}.", result.error());
|
||||
co_return makeError(convertError(errcode_, op), "Fault injection commit unknown result.");
|
||||
}
|
||||
CHECK_FDB_ERRCODE(op, result.error());
|
||||
co_return Void{};
|
||||
});
|
||||
}
|
||||
|
||||
void FDBTransaction::reset() {
|
||||
errcode_ = 0;
|
||||
tr_.reset();
|
||||
}
|
||||
|
||||
Result<Void> FDBTransaction::setOption(FDBTransactionOption option, std::string_view value) {
|
||||
fdb_error_t errcode = tr_.setOption(option, value);
|
||||
if (errcode != 0) {
|
||||
XLOGF(ERR, "FDBTransaction failed to set option {}, errcode {}", magic_enum::enum_name(option), errcode);
|
||||
return makeError(StatusCode::kInvalidArg);
|
||||
}
|
||||
return Void{};
|
||||
}
|
||||
|
||||
CoTask<bool> FDBTransaction::onError(fdb_error_t errcode) {
|
||||
if (errcode == error_code_not_committed) {
|
||||
retryConflict.addSample(1);
|
||||
} else {
|
||||
retryOther.addSample(1);
|
||||
}
|
||||
auto begin = SteadyClock::now();
|
||||
auto ret = co_await tr_.onError(errcode);
|
||||
retryBackoff.addSample(SteadyClock::now() - begin);
|
||||
co_return ret.error() == 0;
|
||||
}
|
||||
|
||||
Status FDBTransaction::testFDBError(fdb_error_t errCode, bool commit) {
|
||||
return makeFDBError(errCode, commit ? Op::Commit : Op::Get).error();
|
||||
}
|
||||
|
||||
CoTryTask<int64_t> FDBTransaction::getReadVersion() {
|
||||
co_return co_await OpWrapper<Op::GetReadVersion>::run([&](Op op) -> CoTryTask<int64_t> {
|
||||
auto r = co_await tr_.getReadVersion();
|
||||
CHECK_FDB_ERRCODE(Op::GetReadVersion, r.error());
|
||||
co_return r.value();
|
||||
});
|
||||
}
|
||||
|
||||
void FDBTransaction::setReadVersion(int64_t version) {
|
||||
if (version >= 0) {
|
||||
tr_.setReadVersion(version);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t FDBTransaction::getCommittedVersion() {
|
||||
int64_t version;
|
||||
auto errcode = tr_.getCommittedVersion(&version);
|
||||
return errcode == 0 ? version : -1;
|
||||
}
|
||||
|
||||
} // namespace hf3fs::kv
|
||||
60
src/fdb/FDBTransaction.h
Normal file
60
src/fdb/FDBTransaction.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Status.h"
|
||||
#include "fdb/FDB.h"
|
||||
|
||||
namespace hf3fs::kv {
|
||||
|
||||
class FDBTransaction : public IReadWriteTransaction {
|
||||
public:
|
||||
FDBTransaction(fdb::Transaction &&tr)
|
||||
: tr_(std::move(tr)),
|
||||
errcode_(0) {}
|
||||
|
||||
// Read operations
|
||||
CoTryTask<std::optional<String>> snapshotGet(std::string_view key) override;
|
||||
CoTryTask<GetRangeResult> snapshotGetRange(const KeySelector &begin, const KeySelector &end, int32_t limit) override;
|
||||
CoTryTask<std::optional<String>> get(std::string_view key) override;
|
||||
CoTryTask<GetRangeResult> getRange(const KeySelector &begin, const KeySelector &end, int32_t limit) override;
|
||||
|
||||
CoTryTask<void> cancel() override;
|
||||
|
||||
CoTryTask<void> addReadConflict(std::string_view key) override;
|
||||
CoTryTask<void> addReadConflictRange(std::string_view begin, std::string_view end) override;
|
||||
|
||||
// Write operations
|
||||
CoTryTask<void> set(std::string_view key, std::string_view value) override;
|
||||
CoTryTask<void> clear(std::string_view key) override;
|
||||
|
||||
CoTryTask<void> setVersionstampedKey(std::string_view key, uint32_t offset, std::string_view value) override;
|
||||
CoTryTask<void> setVersionstampedValue(std::string_view key, std::string_view value, uint32_t offset) override;
|
||||
|
||||
CoTryTask<void> clearRange(std::string_view begin, std::string_view end); // only used in test.
|
||||
|
||||
CoTryTask<void> commit() override;
|
||||
void reset() override;
|
||||
|
||||
Result<Void> setOption(FDBTransactionOption option, std::string_view value = {});
|
||||
CoTask<bool> onError(fdb_error_t errcode);
|
||||
|
||||
CoTryTask<int64_t> getReadVersion();
|
||||
int64_t getCommittedVersion() override;
|
||||
void setReadVersion(int64_t version) override;
|
||||
|
||||
fdb_error_t errcode() const { return errcode_; }
|
||||
|
||||
private:
|
||||
friend class TestFDBTransaction;
|
||||
static Status testFDBError(int errCode, bool commit);
|
||||
|
||||
fdb::Transaction tr_;
|
||||
std::atomic<fdb_error_t> errcode_;
|
||||
};
|
||||
|
||||
} // namespace hf3fs::kv
|
||||
63
src/fdb/HybridKvEngine.cc
Normal file
63
src/fdb/HybridKvEngine.cc
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "HybridKvEngine.h"
|
||||
|
||||
#include <folly/Random.h>
|
||||
|
||||
#include "FDBContext.h"
|
||||
#include "FDBKVEngine.h"
|
||||
#include "HybridKvEngineConfig.h"
|
||||
#include "common/kv/mem/MemKVEngine.h"
|
||||
|
||||
namespace hf3fs::kv {
|
||||
HybridKvEngine::HybridKvEngine() = default;
|
||||
HybridKvEngine::~HybridKvEngine() = default;
|
||||
|
||||
std::shared_ptr<HybridKvEngine> HybridKvEngine::fromMem() {
|
||||
std::shared_ptr<HybridKvEngine> p(new HybridKvEngine);
|
||||
p->kvEngines_.push_back(std::make_unique<kv::MemKVEngine>());
|
||||
return p;
|
||||
}
|
||||
|
||||
std::shared_ptr<HybridKvEngine> HybridKvEngine::fromFdb(const kv::fdb::FDBConfig &config) {
|
||||
std::shared_ptr<HybridKvEngine> p(new HybridKvEngine);
|
||||
p->fdbContext_ = kv::fdb::FDBContext::create(config);
|
||||
p->kvEngines_.reserve(p->fdbContext_->maxDbCount());
|
||||
for (auto i = 0; i < p->fdbContext_->maxDbCount(); ++i) {
|
||||
p->kvEngines_.push_back(std::make_unique<kv::FDBKVEngine>(p->fdbContext_->getDB()));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
std::shared_ptr<HybridKvEngine> HybridKvEngine::from(bool useMemKV, const kv::fdb::FDBConfig &config) {
|
||||
if (useMemKV) return fromMem();
|
||||
return fromFdb(config);
|
||||
}
|
||||
|
||||
std::shared_ptr<HybridKvEngine> HybridKvEngine::from(const HybridKvEngineConfig &config) {
|
||||
return from(config.use_memkv(), config.fdb());
|
||||
}
|
||||
|
||||
std::shared_ptr<HybridKvEngine> HybridKvEngine::from(const HybridKvEngineConfig &config,
|
||||
bool useMemKV,
|
||||
const kv::fdb::FDBConfig &fdbConfig) {
|
||||
static const HybridKvEngineConfig defaultHybridConfig;
|
||||
if (config == defaultHybridConfig) {
|
||||
return from(useMemKV, fdbConfig);
|
||||
} else {
|
||||
return from(config);
|
||||
}
|
||||
}
|
||||
|
||||
kv::IKVEngine &HybridKvEngine::pick() const {
|
||||
assert(!kvEngines_.empty());
|
||||
auto idx = folly::Random::rand32() % kvEngines_.size();
|
||||
return *kvEngines_[idx];
|
||||
}
|
||||
|
||||
std::unique_ptr<kv::IReadOnlyTransaction> HybridKvEngine::createReadonlyTransaction() {
|
||||
return pick().createReadonlyTransaction();
|
||||
}
|
||||
|
||||
std::unique_ptr<kv::IReadWriteTransaction> HybridKvEngine::createReadWriteTransaction() {
|
||||
return pick().createReadWriteTransaction();
|
||||
}
|
||||
} // namespace hf3fs::kv
|
||||
46
src/fdb/HybridKvEngine.h
Normal file
46
src/fdb/HybridKvEngine.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/kv/IKVEngine.h"
|
||||
|
||||
namespace hf3fs::kv::fdb {
|
||||
struct FDBConfig;
|
||||
class FDBContext;
|
||||
} // namespace hf3fs::kv::fdb
|
||||
|
||||
namespace hf3fs::kv {
|
||||
struct HybridKvEngineConfig;
|
||||
class HybridKvEngine : public kv::IKVEngine {
|
||||
public:
|
||||
static std::shared_ptr<HybridKvEngine> fromMem();
|
||||
static std::shared_ptr<HybridKvEngine> fromFdb(const kv::fdb::FDBConfig &config);
|
||||
static std::shared_ptr<HybridKvEngine> from(bool useMemKV, const kv::fdb::FDBConfig &config);
|
||||
static std::shared_ptr<HybridKvEngine> from(const HybridKvEngineConfig &config);
|
||||
|
||||
// use `config` if explicitly set, else fallback to use `useMemKV` and `fdbConfig`.
|
||||
static std::shared_ptr<HybridKvEngine> from(const HybridKvEngineConfig &config,
|
||||
bool useMemKV,
|
||||
const kv::fdb::FDBConfig &fdbConfig);
|
||||
|
||||
// config contains:
|
||||
// 1. use_memkv (fallback mode)
|
||||
// 2. fdb (fallback mode)
|
||||
// 3. kv_engine (new)
|
||||
static std::shared_ptr<HybridKvEngine> fromSuperConfig(const auto &cfg) {
|
||||
return from(cfg.kv_engine(), cfg.use_memkv(), cfg.fdb());
|
||||
}
|
||||
|
||||
~HybridKvEngine();
|
||||
|
||||
std::unique_ptr<kv::IReadOnlyTransaction> createReadonlyTransaction() override;
|
||||
std::unique_ptr<kv::IReadWriteTransaction> createReadWriteTransaction() override;
|
||||
|
||||
private:
|
||||
HybridKvEngine();
|
||||
|
||||
kv::IKVEngine &pick() const;
|
||||
|
||||
std::shared_ptr<kv::fdb::FDBContext> fdbContext_;
|
||||
std::vector<std::unique_ptr<kv::IKVEngine>> kvEngines_;
|
||||
};
|
||||
|
||||
} // namespace hf3fs::kv
|
||||
12
src/fdb/HybridKvEngineConfig.h
Normal file
12
src/fdb/HybridKvEngineConfig.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "FDBConfig.h"
|
||||
|
||||
namespace hf3fs::kv {
|
||||
struct HybridKvEngineConfig : public ConfigBase<HybridKvEngineConfig> {
|
||||
bool operator==(const HybridKvEngineConfig &other) const { return static_cast<const ConfigBase &>(*this) == other; }
|
||||
|
||||
CONFIG_ITEM(use_memkv, false);
|
||||
CONFIG_OBJ(fdb, kv::fdb::FDBConfig);
|
||||
};
|
||||
} // namespace hf3fs::kv
|
||||
316
src/fdb/error_definitions.h
Normal file
316
src/fdb/error_definitions.h
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* error_definitions.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef ERROR
|
||||
|
||||
// SOMEDAY: Split this into flow, fdbclient, fdbserver error headers?
|
||||
|
||||
// Error codes defined here are primarily for programmatic use, not debugging: a separate
|
||||
// error should be defined if and only if there is a sensible situation in which code could
|
||||
// catch and react specifically to that error. So for example there is only one
|
||||
// internal_error code even though there are a huge number of internal errors; extra
|
||||
// information is logged in the trace file.
|
||||
|
||||
// 1xxx Normal failure (plausibly these should not even be "errors", but they are failures of
|
||||
// the way operations are currently defined)
|
||||
// clang-format off
|
||||
ERROR( success, 0, "Success" )
|
||||
ERROR( end_of_stream, 1, "End of stream" )
|
||||
ERROR( operation_failed, 1000, "Operation failed")
|
||||
ERROR( wrong_shard_server, 1001, "Shard is not available from this server")
|
||||
ERROR( operation_obsolete, 1002, "Operation result no longer necessary")
|
||||
ERROR( cold_cache_server, 1003, "Cache server is not warm for this range")
|
||||
ERROR( timed_out, 1004, "Operation timed out" )
|
||||
ERROR( coordinated_state_conflict, 1005, "Conflict occurred while changing coordination information" )
|
||||
ERROR( all_alternatives_failed, 1006, "All alternatives failed" )
|
||||
ERROR( transaction_too_old, 1007, "Transaction is too old to perform reads or be committed" )
|
||||
ERROR( no_more_servers, 1008, "Not enough physical servers available" )
|
||||
ERROR( future_version, 1009, "Request for future version" )
|
||||
ERROR( movekeys_conflict, 1010, "Conflicting attempts to change data distribution" )
|
||||
ERROR( tlog_stopped, 1011, "TLog stopped" )
|
||||
ERROR( server_request_queue_full, 1012, "Server request queue is full" )
|
||||
ERROR( not_committed, 1020, "Transaction not committed due to conflict with another transaction" )
|
||||
ERROR( commit_unknown_result, 1021, "Transaction may or may not have committed" )
|
||||
ERROR( transaction_cancelled, 1025, "Operation aborted because the transaction was cancelled" )
|
||||
ERROR( connection_failed, 1026, "Network connection failed" )
|
||||
ERROR( coordinators_changed, 1027, "Coordination servers have changed" )
|
||||
ERROR( new_coordinators_timed_out, 1028, "New coordination servers did not respond in a timely way" )
|
||||
ERROR( watch_cancelled, 1029, "Watch cancelled because storage server watch limit exceeded" )
|
||||
ERROR( request_maybe_delivered, 1030, "Request may or may not have been delivered" )
|
||||
ERROR( transaction_timed_out, 1031, "Operation aborted because the transaction timed out" )
|
||||
ERROR( too_many_watches, 1032, "Too many watches currently set" )
|
||||
ERROR( locality_information_unavailable, 1033, "Locality information not available" )
|
||||
ERROR( watches_disabled, 1034, "Watches cannot be set if read your writes is disabled" )
|
||||
ERROR( default_error_or, 1035, "Default error for an ErrorOr object" )
|
||||
ERROR( accessed_unreadable, 1036, "Read or wrote an unreadable key" )
|
||||
ERROR( process_behind, 1037, "Storage process does not have recent mutations" )
|
||||
ERROR( database_locked, 1038, "Database is locked" )
|
||||
ERROR( cluster_version_changed, 1039, "The protocol version of the cluster has changed" )
|
||||
ERROR( external_client_already_loaded, 1040, "External client has already been loaded" )
|
||||
ERROR( lookup_failed, 1041, "DNS lookup failed" )
|
||||
ERROR( proxy_memory_limit_exceeded, 1042, "CommitProxy commit memory limit exceeded" )
|
||||
ERROR( shutdown_in_progress, 1043, "Operation no longer supported due to shutdown" )
|
||||
ERROR( serialization_failed, 1044, "Failed to deserialize an object" )
|
||||
ERROR( connection_unreferenced, 1048, "No peer references for connection" )
|
||||
ERROR( connection_idle, 1049, "Connection closed after idle timeout" )
|
||||
ERROR( disk_adapter_reset, 1050, "The disk queue adpater reset" )
|
||||
ERROR( batch_transaction_throttled, 1051, "Batch GRV request rate limit exceeded")
|
||||
ERROR( dd_cancelled, 1052, "Data distribution components cancelled")
|
||||
ERROR( dd_not_found, 1053, "Data distributor not found")
|
||||
ERROR( wrong_connection_file, 1054, "Connection file mismatch")
|
||||
ERROR( version_already_compacted, 1055, "The requested changes have been compacted away")
|
||||
ERROR( local_config_changed, 1056, "Local configuration file has changed. Restart and apply these changes" )
|
||||
ERROR( failed_to_reach_quorum, 1057, "Failed to reach quorum from configuration database nodes. Retry sending these requests" )
|
||||
ERROR( unsupported_format_version, 1058, "Format version not supported" )
|
||||
ERROR( unknown_change_feed, 1059, "Change feed not found" )
|
||||
ERROR( change_feed_not_registered, 1060, "Change feed not registered" )
|
||||
ERROR( granule_assignment_conflict, 1061, "Conflicting attempts to assign blob granules" )
|
||||
ERROR( change_feed_cancelled, 1062, "Change feed was cancelled" )
|
||||
ERROR( blob_granule_file_load_error, 1063, "Error loading a blob file during granule materialization" )
|
||||
ERROR( blob_granule_transaction_too_old, 1064, "Read version is older than blob granule history supports" )
|
||||
ERROR( blob_manager_replaced, 1065, "This blob manager has been replaced." )
|
||||
ERROR( change_feed_popped, 1066, "Tried to read a version older than what has been popped from the change feed" )
|
||||
ERROR( remote_kvs_cancelled, 1067, "The remote key-value store is cancelled" )
|
||||
ERROR( page_header_wrong_page_id, 1068, "Page header does not match location on disk" )
|
||||
ERROR( page_header_checksum_failed, 1069, "Page header checksum failed" )
|
||||
ERROR( page_header_version_not_supported, 1070, "Page header version is not supported" )
|
||||
ERROR( page_encoding_not_supported, 1071, "Page encoding type is not supported or not valid" )
|
||||
ERROR( page_decoding_failed, 1072, "Page content decoding failed" )
|
||||
ERROR( unexpected_encoding_type, 1073, "Page content decoding failed" )
|
||||
ERROR( encryption_key_not_found, 1074, "Encryption key not found" )
|
||||
|
||||
ERROR( broken_promise, 1100, "Broken promise" )
|
||||
ERROR( operation_cancelled, 1101, "Asynchronous operation cancelled" )
|
||||
ERROR( future_released, 1102, "Future has been released" )
|
||||
ERROR( connection_leaked, 1103, "Connection object leaked" )
|
||||
ERROR( never_reply, 1104, "Never reply to the request" )
|
||||
|
||||
ERROR( recruitment_failed, 1200, "Recruitment of a server failed" ) // Be careful, catching this will delete the data of a storage server or tlog permanently
|
||||
ERROR( move_to_removed_server, 1201, "Attempt to move keys to a storage server that was removed" )
|
||||
ERROR( worker_removed, 1202, "Normal worker shut down" ) // Be careful, catching this will delete the data of a storage server or tlog permanently
|
||||
ERROR( cluster_recovery_failed, 1203, "Cluster recovery failed")
|
||||
ERROR( master_max_versions_in_flight, 1204, "Master hit maximum number of versions in flight" )
|
||||
ERROR( tlog_failed, 1205, "Cluster recovery terminating because a TLog failed" ) // similar to tlog_stopped, but the tlog has actually died
|
||||
ERROR( worker_recovery_failed, 1206, "Recovery of a worker process failed" )
|
||||
ERROR( please_reboot, 1207, "Reboot of server process requested" )
|
||||
ERROR( please_reboot_delete, 1208, "Reboot of server process requested, with deletion of state" )
|
||||
ERROR( commit_proxy_failed, 1209, "Master terminating because a CommitProxy failed" )
|
||||
ERROR( resolver_failed, 1210, "Cluster recovery terminating because a Resolver failed" )
|
||||
ERROR( server_overloaded, 1211, "Server is under too much load and cannot respond" )
|
||||
ERROR( backup_worker_failed, 1212, "Cluster recovery terminating because a backup worker failed")
|
||||
ERROR( tag_throttled, 1213, "Transaction tag is being throttled" )
|
||||
ERROR( grv_proxy_failed, 1214, "Cluster recovery terminating because a GRVProxy failed" )
|
||||
ERROR( dd_tracker_cancelled, 1215, "The data distribution tracker has been cancelled" )
|
||||
ERROR( failed_to_progress, 1216, "Process has failed to make sufficient progress" )
|
||||
ERROR( invalid_cluster_id, 1217, "Attempted to join cluster with a different cluster ID" )
|
||||
ERROR( restart_cluster_controller, 1218, "Restart cluster controller process" )
|
||||
ERROR( please_reboot_remote_kv_store, 1219, "Need to reboot the storage engine process as it died abnormally")
|
||||
|
||||
// 15xx Platform errors
|
||||
ERROR( platform_error, 1500, "Platform error" )
|
||||
ERROR( large_alloc_failed, 1501, "Large block allocation failed" )
|
||||
ERROR( performance_counter_error, 1502, "QueryPerformanceCounter error" )
|
||||
|
||||
ERROR( io_error, 1510, "Disk i/o operation failed" )
|
||||
ERROR( file_not_found, 1511, "File not found" )
|
||||
ERROR( bind_failed, 1512, "Unable to bind to network" )
|
||||
ERROR( file_not_readable, 1513, "File could not be read" )
|
||||
ERROR( file_not_writable, 1514, "File could not be written" )
|
||||
ERROR( no_cluster_file_found, 1515, "No cluster file found in current directory or default location" )
|
||||
ERROR( file_too_large, 1516, "File too large to be read" )
|
||||
ERROR( non_sequential_op, 1517, "Non sequential file operation not allowed" )
|
||||
ERROR( http_bad_response, 1518, "HTTP response was badly formed" )
|
||||
ERROR( http_not_accepted, 1519, "HTTP request not accepted" )
|
||||
ERROR( checksum_failed, 1520, "A data checksum failed" )
|
||||
ERROR( io_timeout, 1521, "A disk IO operation failed to complete in a timely manner" )
|
||||
ERROR( file_corrupt, 1522, "A structurally corrupt data file was detected" )
|
||||
ERROR( http_request_failed, 1523, "HTTP response code not received or indicated failure" )
|
||||
ERROR( http_auth_failed, 1524, "HTTP request failed due to bad credentials" )
|
||||
ERROR( http_bad_request_id, 1525, "HTTP response contained an unexpected X-Request-ID header" )
|
||||
|
||||
// 2xxx Attempt (presumably by a _client_) to do something illegal. If an error is known to
|
||||
// be internally caused, it should be 41xx
|
||||
ERROR( client_invalid_operation, 2000, "Invalid API call" )
|
||||
ERROR( commit_read_incomplete, 2002, "Commit with incomplete read" )
|
||||
ERROR( test_specification_invalid, 2003, "Invalid test specification" )
|
||||
ERROR( key_outside_legal_range, 2004, "Key outside legal range" )
|
||||
ERROR( inverted_range, 2005, "Range begin key larger than end key" )
|
||||
ERROR( invalid_option_value, 2006, "Option set with an invalid value" )
|
||||
ERROR( invalid_option, 2007, "Option not valid in this context" )
|
||||
ERROR( network_not_setup, 2008, "Action not possible before the network is configured" )
|
||||
ERROR( network_already_setup, 2009, "Network can be configured only once" )
|
||||
ERROR( read_version_already_set, 2010, "Transaction already has a read version set" )
|
||||
ERROR( version_invalid, 2011, "Version not valid" )
|
||||
ERROR( range_limits_invalid, 2012, "Range limits not valid" )
|
||||
ERROR( invalid_database_name, 2013, "Database name must be 'DB'" )
|
||||
ERROR( attribute_not_found, 2014, "Attribute not found" )
|
||||
ERROR( future_not_set, 2015, "Future not ready" )
|
||||
ERROR( future_not_error, 2016, "Future not an error" )
|
||||
ERROR( used_during_commit, 2017, "Operation issued while a commit was outstanding" )
|
||||
ERROR( invalid_mutation_type, 2018, "Unrecognized atomic mutation type" )
|
||||
ERROR( attribute_too_large, 2019, "Attribute too large for type int" )
|
||||
ERROR( transaction_invalid_version, 2020, "Transaction does not have a valid commit version" )
|
||||
ERROR( no_commit_version, 2021, "Transaction is read-only and therefore does not have a commit version" )
|
||||
ERROR( environment_variable_network_option_failed, 2022, "Environment variable network option could not be set" )
|
||||
ERROR( transaction_read_only, 2023, "Attempted to commit a transaction specified as read-only" )
|
||||
ERROR( invalid_cache_eviction_policy, 2024, "Invalid cache eviction policy, only random and lru are supported" )
|
||||
ERROR( network_cannot_be_restarted, 2025, "Network can only be started once" )
|
||||
ERROR( blocked_from_network_thread, 2026, "Detected a deadlock in a callback called from the network thread" )
|
||||
ERROR( invalid_config_db_range_read, 2027, "Invalid configuration database range read" )
|
||||
ERROR( invalid_config_db_key, 2028, "Invalid configuration database key provided" )
|
||||
ERROR( invalid_config_path, 2029, "Invalid configuration path" )
|
||||
ERROR( mapper_bad_index, 2030, "The index in K[] or V[] is not a valid number or out of range" )
|
||||
ERROR( mapper_no_such_key, 2031, "A mapped key is not set in database" )
|
||||
ERROR( mapper_bad_range_decriptor, 2032, "\"{...}\" must be the last element of the mapper tuple" )
|
||||
ERROR( quick_get_key_values_has_more, 2033, "One of the mapped range queries is too large" )
|
||||
ERROR( quick_get_value_miss, 2034, "Found a mapped key that is not served in the same SS" )
|
||||
ERROR( quick_get_key_values_miss, 2035, "Found a mapped range that is not served in the same SS" )
|
||||
ERROR( blob_granule_no_ryw, 2036, "Blob Granule Read Transactions must be specified as ryw-disabled" )
|
||||
ERROR( blob_granule_not_materialized, 2037, "Blob Granule Read was not materialized" )
|
||||
ERROR( get_mapped_key_values_has_more, 2038, "getMappedRange does not support continuation for now" )
|
||||
ERROR( get_mapped_range_reads_your_writes, 2039, "getMappedRange tries to read data that were previously written in the transaction" )
|
||||
ERROR( checkpoint_not_found, 2040, "Checkpoint not found" )
|
||||
ERROR( key_not_tuple, 2041, "The key cannot be parsed as a tuple" );
|
||||
ERROR( value_not_tuple, 2042, "The value cannot be parsed as a tuple" );
|
||||
ERROR( mapper_not_tuple, 2043, "The mapper cannot be parsed as a tuple" );
|
||||
|
||||
|
||||
ERROR( incompatible_protocol_version, 2100, "Incompatible protocol version" )
|
||||
ERROR( transaction_too_large, 2101, "Transaction exceeds byte limit" )
|
||||
ERROR( key_too_large, 2102, "Key length exceeds limit" )
|
||||
ERROR( value_too_large, 2103, "Value length exceeds limit" )
|
||||
ERROR( connection_string_invalid, 2104, "Connection string invalid" )
|
||||
ERROR( address_in_use, 2105, "Local address in use" )
|
||||
ERROR( invalid_local_address, 2106, "Invalid local address" )
|
||||
ERROR( tls_error, 2107, "TLS error" )
|
||||
ERROR( unsupported_operation, 2108, "Operation is not supported" )
|
||||
ERROR( too_many_tags, 2109, "Too many tags set on transaction" )
|
||||
ERROR( tag_too_long, 2110, "Tag set on transaction is too long" )
|
||||
ERROR( too_many_tag_throttles, 2111, "Too many tag throttles have been created" )
|
||||
ERROR( special_keys_cross_module_read, 2112, "Special key space range read crosses modules. Refer to the `special_key_space_relaxed' transaction option for more details." )
|
||||
ERROR( special_keys_no_module_found, 2113, "Special key space range read does not intersect a module. Refer to the `special_key_space_relaxed' transaction option for more details." )
|
||||
ERROR( special_keys_write_disabled, 2114, "Special Key space is not allowed to write by default. Refer to the `special_key_space_enable_writes` transaction option for more details." )
|
||||
ERROR( special_keys_no_write_module_found, 2115, "Special key space key or keyrange in set or clear does not intersect a module" )
|
||||
ERROR( special_keys_cross_module_clear, 2116, "Special key space clear crosses modules" )
|
||||
ERROR( special_keys_api_failure, 2117, "Api call through special keys failed. For more information, call get on special key 0xff0xff/error_message to get a json string of the error message." )
|
||||
ERROR( client_lib_invalid_metadata, 2118, "Invalid client library metadata." )
|
||||
ERROR( client_lib_already_exists, 2119, "Client library with same identifier already exists on the cluster." )
|
||||
ERROR( client_lib_not_found, 2120, "Client library for the given identifier not found." )
|
||||
ERROR( client_lib_not_available, 2121, "Client library exists, but is not available for download." )
|
||||
ERROR( client_lib_invalid_binary, 2122, "Invalid client library binary." )
|
||||
|
||||
ERROR( tenant_name_required, 2130, "Tenant name must be specified to access data in the cluster" )
|
||||
ERROR( tenant_not_found, 2131, "Tenant does not exist" )
|
||||
ERROR( tenant_already_exists, 2132, "A tenant with the given name already exists" )
|
||||
ERROR( tenant_not_empty, 2133, "Cannot delete a non-empty tenant" )
|
||||
ERROR( invalid_tenant_name, 2134, "Tenant name cannot begin with \\xff");
|
||||
ERROR( tenant_prefix_allocator_conflict, 2135, "The database already has keys stored at the prefix allocated for the tenant");
|
||||
ERROR( tenants_disabled, 2136, "Tenants have been disabled in the cluster");
|
||||
ERROR( unknown_tenant, 2137, "Tenant is not available from this server")
|
||||
|
||||
// 2200 - errors from bindings and official APIs
|
||||
ERROR( api_version_unset, 2200, "API version is not set" )
|
||||
ERROR( api_version_already_set, 2201, "API version may be set only once" )
|
||||
ERROR( api_version_invalid, 2202, "API version not valid" )
|
||||
ERROR( api_version_not_supported, 2203, "API version not supported" )
|
||||
ERROR( exact_mode_without_limits, 2210, "EXACT streaming mode requires limits, but none were given" )
|
||||
|
||||
ERROR( invalid_tuple_data_type, 2250, "Unrecognized data type in packed tuple")
|
||||
ERROR( invalid_tuple_index, 2251, "Tuple does not have element at specified index")
|
||||
ERROR( key_not_in_subspace, 2252, "Cannot unpack key that is not in subspace" )
|
||||
ERROR( manual_prefixes_not_enabled, 2253, "Cannot specify a prefix unless manual prefixes are enabled" )
|
||||
ERROR( prefix_in_partition, 2254, "Cannot specify a prefix in a partition" )
|
||||
ERROR( cannot_open_root_directory, 2255, "Root directory cannot be opened" )
|
||||
ERROR( directory_already_exists, 2256, "Directory already exists" )
|
||||
ERROR( directory_does_not_exist, 2257, "Directory does not exist" )
|
||||
ERROR( parent_directory_does_not_exist, 2258, "Directory's parent does not exist" )
|
||||
ERROR( mismatched_layer, 2259, "Directory has already been created with a different layer string" )
|
||||
ERROR( invalid_directory_layer_metadata, 2260, "Invalid directory layer metadata" )
|
||||
ERROR( cannot_move_directory_between_partitions, 2261, "Directory cannot be moved between partitions" )
|
||||
ERROR( cannot_use_partition_as_subspace, 2262, "Directory partition cannot be used as subspace" )
|
||||
ERROR( incompatible_directory_version, 2263, "Directory layer was created with an incompatible version" )
|
||||
ERROR( directory_prefix_not_empty, 2264, "Database has keys stored at the prefix chosen by the automatic prefix allocator" )
|
||||
ERROR( directory_prefix_in_use, 2265, "Directory layer already has a conflicting prefix" )
|
||||
ERROR( invalid_destination_directory, 2266, "Target directory is invalid" )
|
||||
ERROR( cannot_modify_root_directory, 2267, "Root directory cannot be modified" )
|
||||
ERROR( invalid_uuid_size, 2268, "UUID is not sixteen bytes");
|
||||
|
||||
// 2300 - backup and restore errors
|
||||
ERROR( backup_error, 2300, "Backup error")
|
||||
ERROR( restore_error, 2301, "Restore error")
|
||||
ERROR( backup_duplicate, 2311, "Backup duplicate request")
|
||||
ERROR( backup_unneeded, 2312, "Backup unneeded request")
|
||||
ERROR( backup_bad_block_size, 2313, "Backup file block size too small")
|
||||
ERROR( backup_invalid_url, 2314, "Backup Container URL invalid")
|
||||
ERROR( backup_invalid_info, 2315, "Backup Container info invalid")
|
||||
ERROR( backup_cannot_expire, 2316, "Cannot expire requested data from backup without violating minimum restorability")
|
||||
ERROR( backup_auth_missing, 2317, "Cannot find authentication details (such as a password or secret key) for the specified Backup Container URL")
|
||||
ERROR( backup_auth_unreadable, 2318, "Cannot read or parse one or more sources of authentication information for Backup Container URLs")
|
||||
ERROR( backup_does_not_exist, 2319, "Backup does not exist")
|
||||
ERROR( backup_not_filterable_with_key_ranges, 2320, "Backup before 6.3 cannot be filtered with key ranges")
|
||||
ERROR( backup_not_overlapped_with_keys_filter, 2321, "Backup key ranges doesn't overlap with key ranges filter")
|
||||
ERROR( restore_invalid_version, 2361, "Invalid restore version")
|
||||
ERROR( restore_corrupted_data, 2362, "Corrupted backup data")
|
||||
ERROR( restore_missing_data, 2363, "Missing backup data")
|
||||
ERROR( restore_duplicate_tag, 2364, "Restore duplicate request")
|
||||
ERROR( restore_unknown_tag, 2365, "Restore tag does not exist")
|
||||
ERROR( restore_unknown_file_type, 2366, "Unknown backup/restore file type")
|
||||
ERROR( restore_unsupported_file_version, 2367, "Unsupported backup file version")
|
||||
ERROR( restore_bad_read, 2368, "Unexpected number of bytes read")
|
||||
ERROR( restore_corrupted_data_padding, 2369, "Backup file has unexpected padding bytes")
|
||||
ERROR( restore_destination_not_empty, 2370, "Attempted to restore into a non-empty destination database")
|
||||
ERROR( restore_duplicate_uid, 2371, "Attempted to restore using a UID that had been used for an aborted restore")
|
||||
ERROR( task_invalid_version, 2381, "Invalid task version")
|
||||
ERROR( task_interrupted, 2382, "Task execution stopped due to timeout, abort, or completion by another worker")
|
||||
ERROR( invalid_encryption_key_file, 2383, "The provided encryption key file has invalid contents" )
|
||||
|
||||
ERROR( key_not_found, 2400, "Expected key is missing")
|
||||
ERROR( json_malformed, 2401, "JSON string was malformed")
|
||||
ERROR( json_eof_expected, 2402, "JSON string did not terminate where expected")
|
||||
|
||||
// 2500 - disk snapshot based backup errors
|
||||
ERROR( snap_disable_tlog_pop_failed, 2500, "Failed to disable tlog pops")
|
||||
ERROR( snap_storage_failed, 2501, "Failed to snapshot storage nodes")
|
||||
ERROR( snap_tlog_failed, 2502, "Failed to snapshot TLog nodes")
|
||||
ERROR( snap_coord_failed, 2503, "Failed to snapshot coordinator nodes")
|
||||
ERROR( snap_enable_tlog_pop_failed, 2504, "Failed to enable tlog pops")
|
||||
ERROR( snap_path_not_whitelisted, 2505, "Snapshot create binary path not whitelisted")
|
||||
ERROR( snap_not_fully_recovered_unsupported, 2506, "Unsupported when the cluster is not fully recovered")
|
||||
ERROR( snap_log_anti_quorum_unsupported, 2507, "Unsupported when log anti quorum is configured")
|
||||
ERROR( snap_with_recovery_unsupported, 2508, "Cluster recovery during snapshot operation not supported")
|
||||
ERROR( snap_invalid_uid_string, 2509, "The given uid string is not a 32-length hex string")
|
||||
|
||||
// 27XX - Encryption operations errors
|
||||
ERROR( encrypt_ops_error, 2700, "Encryption operation error")
|
||||
ERROR( encrypt_header_metadata_mismatch, 2701, "Encryption header metadata mismatch")
|
||||
ERROR( encrypt_key_not_found, 2702, "Expected encryption key is missing")
|
||||
ERROR( encrypt_key_ttl_expired, 2703, "Expected encryption key TTL has expired")
|
||||
ERROR( encrypt_header_authtoken_mismatch, 2704, "Encryption header authentication token mismatch")
|
||||
ERROR( encrypt_update_cipher, 2705, "Attempt to update encryption cipher key")
|
||||
ERROR( encrypt_invalid_id, 2706, "Invalid encryption domainId or encryption cipher key id")
|
||||
|
||||
// 4xxx Internal errors (those that should be generated only by bugs) are decimal 4xxx
|
||||
ERROR( unknown_error, 4000, "An unknown error occurred" ) // C++ exception not of type Error
|
||||
ERROR( internal_error, 4100, "An internal error occurred" )
|
||||
ERROR( not_implemented, 4200, "Not implemented yet" )
|
||||
// clang-format on
|
||||
|
||||
#undef ERROR
|
||||
#endif
|
||||
Reference in New Issue
Block a user