Merge remote-tracking branch 'origin/main' into dev/tclinkenbeard/protocol-version-quiet-database-recovery
# Conflicts: # fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp
This commit is contained in:
commit
7ea79c300c
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Minimize Outdated Comments
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
// Example header: ### Result of foundationdb-pr-clang on Linux RHEL 9
|
||||
|
|
@ -43,37 +43,69 @@ jobs:
|
|||
let oldCommentAtHead = null;
|
||||
|
||||
console.log(`Searching for previous comments with header: "${header}"`);
|
||||
for await (const { data: comments } of github.paginate.iterator(
|
||||
github.rest.issues.listComments,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
per_page: 100,
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $prNumber: Int!, $cursor: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $prNumber) {
|
||||
comments(first: 100, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
id
|
||||
databaseId
|
||||
body
|
||||
isMinimized
|
||||
author {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)) {
|
||||
for (const comment of comments) {
|
||||
`;
|
||||
let hasNextPage = true;
|
||||
let cursor = null;
|
||||
|
||||
while (hasNextPage) {
|
||||
const result = await github.graphql(query, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
prNumber: context.issue.number,
|
||||
cursor: cursor
|
||||
});
|
||||
const prComments = result.repository.pullRequest.comments;
|
||||
|
||||
for (const comment of prComments.nodes) {
|
||||
if (
|
||||
comment.user.login === 'foundationdb-ci' &&
|
||||
comment.id !== context.payload.comment.id &&
|
||||
comment.author &&
|
||||
comment.author.login === 'foundationdb-ci' &&
|
||||
comment.databaseId !== context.payload.comment.id &&
|
||||
comment.body.trimStart().startsWith(header)
|
||||
) {
|
||||
allOldComments.push(comment);
|
||||
|
||||
// Check for older comment matching PR head commit, in case reports came out-of-order.
|
||||
// Default sort by created ascending, newest matching head commit wins.
|
||||
// Default sort is by created ascending, newest matching head commit wins.
|
||||
const m = comment.body.match(/^\* Commit ID: ([a-f0-9]+)/m);
|
||||
if (m && m[1] === headSha) {
|
||||
oldCommentAtHead = comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
hasNextPage = prComments.pageInfo.hasNextPage;
|
||||
cursor = prComments.pageInfo.endCursor;
|
||||
}
|
||||
const newCommentM = commentBody.match(/^\* Commit ID: ([a-f0-9]+)/m);
|
||||
const newCommentSha = newCommentM ? newCommentM[1] : null;
|
||||
if (newCommentSha && newCommentSha !== headSha && oldCommentAtHead) {
|
||||
console.log(`New comment is for old commit (${newCommentSha}), but an older comment exists for the head commit (${headSha})`);
|
||||
commentsToMinimize.push(context.payload.comment);
|
||||
commentsToMinimize.push({
|
||||
id: context.payload.comment.node_id,
|
||||
databaseId: context.payload.comment.id,
|
||||
isMinimized: false // new comment must not be minimized yet
|
||||
});
|
||||
for (const old of allOldComments) {
|
||||
if (old.id !== oldCommentAtHead.id) {
|
||||
commentsToMinimize.push(old);
|
||||
|
|
@ -82,29 +114,13 @@ jobs:
|
|||
} else {
|
||||
commentsToMinimize.push(...allOldComments);
|
||||
}
|
||||
console.log(`Found ${commentsToMinimize.length} comments to minimize.`);
|
||||
|
||||
console.log(`Found ${commentsToMinimize.length} comments to evaluate.`);
|
||||
for (const comment of commentsToMinimize) {
|
||||
const checkQuery = `
|
||||
query($id: ID!) {
|
||||
node(id: $id) {
|
||||
... on Minimizable {
|
||||
isMinimized
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
try {
|
||||
const checkResult = await github.graphql(checkQuery, { id: comment.node_id });
|
||||
if (checkResult.node.isMinimized) {
|
||||
console.log(`Comment ${comment.id} is already minimized.`);
|
||||
continue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to check minimization status for comment ${comment.id}:`, error);
|
||||
if (comment.isMinimized) {
|
||||
console.log(`Comment ${comment.databaseId} is already minimized.`);
|
||||
continue;
|
||||
}
|
||||
console.log(`Minimizing comment ${comment.id}`);
|
||||
console.log(`Minimizing comment ${comment.databaseId}`);
|
||||
const minimizeMutation = `
|
||||
mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
|
||||
minimizeComment(input: { subjectId: $id, classifier: $classifier }) {
|
||||
|
|
@ -116,10 +132,10 @@ jobs:
|
|||
`;
|
||||
try {
|
||||
await github.graphql(minimizeMutation, {
|
||||
id: comment.node_id,
|
||||
id: comment.id,
|
||||
classifier: 'OUTDATED'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to minimize comment ${comment.id}:`, error);
|
||||
console.error(`Failed to minimize comment ${comment.databaseId}:`, error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,10 +92,11 @@ jobs:
|
|||
;;
|
||||
esac
|
||||
# These inputs are not parseable as standalone clang-tidy translation units in this workflow:
|
||||
# the RocksDB compile commands refer to headers that are not built here, and the Flow headers depend
|
||||
# on include order from their real consumers. The C API header is intentionally valid C, not C++.
|
||||
# the public C headers require C consumer setup and intentionally use C typedefs, the RocksDB compile
|
||||
# commands refer to headers that are not built here, and the Flow headers depend on include order
|
||||
# from their real consumers.
|
||||
case "$FILE" in
|
||||
bindings/c/foundationdb/fdb_c.h)
|
||||
bindings/c/foundationdb/fdb_c.h|bindings/c/foundationdb/fdb_c_types.h)
|
||||
continue
|
||||
;;
|
||||
fdbserver/core/RocksDBCheckpointUtils.cpp|fdbserver/kvstore/KeyValueStoreRocksDB.cpp|fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp)
|
||||
|
|
|
|||
|
|
@ -294,6 +294,10 @@ if(NOT WIN32)
|
|||
fdb
|
||||
${CMAKE_CURRENT_BINARY_DIR}/libfdb_c_external.so
|
||||
)
|
||||
# The CDC API test exercises both the direct and external-client paths.
|
||||
# Enable admission on only these temporary clusters.
|
||||
set_property(TEST fdb_c_unit_tests fdb_c_external_client_unit_tests APPEND PROPERTY ENVIRONMENT
|
||||
"FDB_KNOB_enable_native_cdc=true")
|
||||
add_unavailable_fdbclient_test(
|
||||
NAME disconnected_timeout_unit_tests
|
||||
COMMAND $<TARGET_FILE:disconnected_timeout_unit_tests>
|
||||
|
|
@ -552,6 +556,8 @@ elseif(NOT WIN32 AND NOT APPLE) # Linux Only
|
|||
--api-test-dir ${CMAKE_SOURCE_DIR}/bindings/c/test/apitester/tests
|
||||
${SHIM_LIB_TEST_EXTRA_OPTIONS}
|
||||
)
|
||||
set_property(TEST fdb_c_shim_library_tests APPEND PROPERTY ENVIRONMENT
|
||||
"FDB_KNOB_enable_native_cdc=true")
|
||||
|
||||
endif() # End Linux only
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "fdbclient/CommitTransaction.h"
|
||||
#include "fdbclient/MultiVersionTransaction.h"
|
||||
#include "fdbclient/MultiVersionAssignmentVars.h"
|
||||
#include "fdbclient/NativeCdcClient.h"
|
||||
#include "foundationdb/fdb_c.h"
|
||||
#include "foundationdb/fdb_c_internal.h"
|
||||
|
||||
|
|
@ -41,12 +42,14 @@ int g_api_version = 0;
|
|||
* FDBResult -> ThreadSingleAssignmentVarBase
|
||||
* FDBDatabase -> IDatabase
|
||||
* FDBTransaction -> ITransaction
|
||||
* FDBCdcConsumer -> INativeCdcConsumer
|
||||
*/
|
||||
#define TSAVB(f) ((ThreadSingleAssignmentVarBase*)(f))
|
||||
#define TSAV(T, f) ((ThreadSingleAssignmentVar<T>*)(f))
|
||||
|
||||
#define DB(d) ((IDatabase*)d)
|
||||
#define TXN(t) ((ITransaction*)t)
|
||||
#define NATIVE_CDC_CONSUMER(c) ((INativeCdcConsumer*)c)
|
||||
|
||||
// Legacy (pre API version 610)
|
||||
#define CLUSTER(c) ((char*)c)
|
||||
|
|
@ -65,6 +68,96 @@ static_assert(static_cast<int>(FDB_BG_MUTATION_TYPE_SET_VALUE) == static_cast<in
|
|||
static_assert(static_cast<int>(FDB_BG_MUTATION_TYPE_CLEAR_RANGE) == static_cast<int>(MutationRef::Type::ClearRange),
|
||||
"FDB_BG_MUTATION_TYPE_CLEAR_RANGE enum value mismatch");
|
||||
|
||||
namespace {
|
||||
|
||||
// These wrappers own the C-shaped arrays returned by the corresponding future
|
||||
// getters. The mapped ThreadFuture keeps their arenas alive until the public
|
||||
// FDBFuture is destroyed or releases its result memory.
|
||||
struct CNativeCdcStreamInfoArray {
|
||||
Arena arena;
|
||||
VectorRef<FDBCdcStreamInfo> streams;
|
||||
};
|
||||
|
||||
struct CNativeCdcConsumeResult {
|
||||
Arena arena;
|
||||
VectorRef<FDBCdcVersionedMutations> mutations;
|
||||
Version lastConsumedVersion = invalidVersion;
|
||||
};
|
||||
|
||||
FDBKey copyNativeCdcKey(Arena& arena, KeyRef source) {
|
||||
StringRef copy(arena, source);
|
||||
return FDBKey{ copy.begin(), copy.size() };
|
||||
}
|
||||
|
||||
FDBKeyRange copyNativeCdcKeyRange(Arena& arena, KeyRangeRef source) {
|
||||
StringRef begin(arena, source.begin);
|
||||
StringRef end(arena, source.end);
|
||||
return FDBKeyRange{ begin.begin(), begin.size(), end.begin(), end.size() };
|
||||
}
|
||||
|
||||
CNativeCdcStreamInfoArray makeCNativeCdcStreamInfoArray(std::vector<NativeCdcStreamInfo> const& source) {
|
||||
CNativeCdcStreamInfoArray result;
|
||||
result.streams.reserve(result.arena, source.size());
|
||||
for (auto const& stream : source) {
|
||||
FDBCdcStreamInfo cStream;
|
||||
cStream.name = copyNativeCdcKey(result.arena, stream.name);
|
||||
cStream.stream_id = stream.streamId;
|
||||
cStream.key_range = copyNativeCdcKeyRange(result.arena, stream.keys);
|
||||
cStream.min_version = stream.minVersion;
|
||||
result.streams.push_back(result.arena, cStream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CNativeCdcConsumeResult makeCNativeCdcConsumeResult(NativeCdcConsumeResult const& source) {
|
||||
CNativeCdcConsumeResult result;
|
||||
result.lastConsumedVersion = source.cursor.lastConsumedVersion;
|
||||
result.mutations.reserve(result.arena, source.mutations.size());
|
||||
for (auto const& versioned : source.mutations) {
|
||||
FDBCdcVersionedMutations cVersioned;
|
||||
cVersioned.version = versioned.version;
|
||||
cVersioned.mutation_count = versioned.mutations.size();
|
||||
cVersioned.mutations = nullptr;
|
||||
if (!versioned.mutations.empty()) {
|
||||
auto* cMutations = new (result.arena) FDBCdcMutation[versioned.mutations.size()];
|
||||
for (int i = 0; i < versioned.mutations.size(); ++i) {
|
||||
auto const& mutation = versioned.mutations[i];
|
||||
StringRef param1(result.arena, mutation.param1);
|
||||
StringRef param2(result.arena, mutation.param2);
|
||||
cMutations[i] =
|
||||
FDBCdcMutation{ mutation.type, param1.begin(), param1.size(), param2.begin(), param2.size() };
|
||||
}
|
||||
cVersioned.mutations = cMutations;
|
||||
}
|
||||
result.mutations.push_back(result.arena, cVersioned);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FDBFuture* mapNativeCdcStreamInfoFuture(ThreadFuture<std::vector<NativeCdcStreamInfo>> source) {
|
||||
auto result = mapThreadFuture<std::vector<NativeCdcStreamInfo>, CNativeCdcStreamInfoArray>(
|
||||
source, [](ErrorOr<std::vector<NativeCdcStreamInfo>> source) -> ErrorOr<CNativeCdcStreamInfoArray> {
|
||||
if (source.isError()) {
|
||||
return ErrorOr<CNativeCdcStreamInfoArray>(source.getError());
|
||||
}
|
||||
return makeCNativeCdcStreamInfoArray(source.get());
|
||||
});
|
||||
return (FDBFuture*)result.extractPtr();
|
||||
}
|
||||
|
||||
FDBFuture* mapNativeCdcConsumeFuture(ThreadFuture<NativeCdcConsumeResult> source) {
|
||||
auto result = mapThreadFuture<NativeCdcConsumeResult, CNativeCdcConsumeResult>(
|
||||
source, [](ErrorOr<NativeCdcConsumeResult> source) -> ErrorOr<CNativeCdcConsumeResult> {
|
||||
if (source.isError()) {
|
||||
return ErrorOr<CNativeCdcConsumeResult>(source.getError());
|
||||
}
|
||||
return makeCNativeCdcConsumeResult(source.get());
|
||||
});
|
||||
return (FDBFuture*)result.extractPtr();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#define TSAV_ERROR(type, error) ((FDBFuture*)(ThreadFuture<type>(error())).extractPtr())
|
||||
|
||||
extern "C" DLLEXPORT const char* fdb_get_error(fdb_error_t code) {
|
||||
|
|
@ -260,7 +353,7 @@ extern "C" DLLEXPORT fdb_error_t fdb_future_get_double(FDBFuture* f, double* out
|
|||
}
|
||||
|
||||
extern "C" DLLEXPORT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length) {
|
||||
CATCH_AND_RETURN(KeyRef key = TSAV(Key, f)->get(); *out_key = key.begin(); *out_key_length = key.size(););
|
||||
CATCH_AND_RETURN(Key key = TSAV(Key, f)->get(); *out_key = key.begin(); *out_key_length = key.size(););
|
||||
}
|
||||
|
||||
fdb_error_t fdb_future_get_cluster_v609(FDBFuture* f, FDBCluster** out_cluster) {
|
||||
|
|
@ -332,6 +425,29 @@ extern "C" DLLEXPORT fdb_error_t fdb_future_get_key_array(FDBFuture* f, FDBKey c
|
|||
*out_count = na.size(););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* f,
|
||||
FDBCdcStreamInfo const** out_streams,
|
||||
int* out_count) {
|
||||
CATCH_AND_RETURN(CNativeCdcStreamInfoArray result = TSAV(CNativeCdcStreamInfoArray, f)->get();
|
||||
*out_streams = result.streams.begin();
|
||||
*out_count = result.streams.size(););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* f, FDBCdcConsumer** out_consumer) {
|
||||
CATCH_AND_RETURN(Reference<INativeCdcConsumer> consumer = TSAV(Reference<INativeCdcConsumer>, f)->get();
|
||||
*out_consumer = (FDBCdcConsumer*)consumer.extractPtr(););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_versioned_mutations(FDBFuture* f,
|
||||
FDBCdcVersionedMutations const** out_mutations,
|
||||
int* out_count,
|
||||
int64_t* out_last_consumed_version) {
|
||||
CATCH_AND_RETURN(CNativeCdcConsumeResult result = TSAV(CNativeCdcConsumeResult, f)->get();
|
||||
*out_mutations = result.mutations.begin();
|
||||
*out_count = result.mutations.size();
|
||||
*out_last_consumed_version = result.lastConsumedVersion;);
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT void fdb_result_destroy(FDBResult* r) {
|
||||
CATCH_AND_DIE(TSAVB(r)->cancel(););
|
||||
}
|
||||
|
|
@ -446,6 +562,72 @@ extern "C" DLLEXPORT fdb_error_t fdb_database_create_transaction(FDBDatabase* d,
|
|||
*out_transaction = (FDBTransaction*)tr.extractPtr(););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* db,
|
||||
uint8_t const* name,
|
||||
int name_length,
|
||||
uint8_t const* begin_key,
|
||||
int begin_key_length,
|
||||
uint8_t const* end_key,
|
||||
int end_key_length) {
|
||||
RETURN_FUTURE_ON_ERROR(
|
||||
CDCStreamId,
|
||||
return (FDBFuture*)(DB(db)
|
||||
->registerNativeCdcStream(
|
||||
KeyRef(name, name_length),
|
||||
KeyRangeRef(KeyRef(begin_key, begin_key_length), KeyRef(end_key, end_key_length)))
|
||||
.extractPtr()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* db, uint8_t const* name, int name_length) {
|
||||
RETURN_FUTURE_ON_ERROR(Void,
|
||||
return (FDBFuture*)(DB(db)->removeNativeCdcStream(KeyRef(name, name_length)).extractPtr()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* db) {
|
||||
RETURN_FUTURE_ON_ERROR(CNativeCdcStreamInfoArray,
|
||||
return mapNativeCdcStreamInfoFuture(DB(db)->listNativeCdcStreams()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* db,
|
||||
uint8_t const* name,
|
||||
int name_length) {
|
||||
RETURN_FUTURE_ON_ERROR(
|
||||
Reference<INativeCdcConsumer>,
|
||||
return (FDBFuture*)(DB(db)->createNativeCdcConsumer(KeyRef(name, name_length)).extractPtr()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* db,
|
||||
uint64_t stream_id,
|
||||
int64_t last_consumed_version) {
|
||||
RETURN_FUTURE_ON_ERROR(Reference<INativeCdcConsumer>, NativeCdcCursor cursor; cursor.streamId = stream_id;
|
||||
cursor.lastConsumedVersion = last_consumed_version;
|
||||
return (FDBFuture*)(DB(db)->resumeNativeCdcConsumer(cursor).extractPtr()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer) {
|
||||
try {
|
||||
NATIVE_CDC_CONSUMER(consumer)->delref();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer) {
|
||||
RETURN_FUTURE_ON_ERROR(CNativeCdcConsumeResult,
|
||||
return mapNativeCdcConsumeFuture(NATIVE_CDC_CONSUMER(consumer)->consume()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer) {
|
||||
RETURN_FUTURE_ON_ERROR(Void, return (FDBFuture*)(NATIVE_CDC_CONSUMER(consumer)->acknowledge().extractPtr()););
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer,
|
||||
uint64_t* out_stream_id,
|
||||
int64_t* out_last_consumed_version) {
|
||||
CATCH_AND_RETURN(NativeCdcCursor position = NATIVE_CDC_CONSUMER(consumer)->getPosition();
|
||||
*out_stream_id = position.streamId;
|
||||
*out_last_consumed_version = position.lastConsumedVersion;);
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db,
|
||||
uint8_t const* address,
|
||||
int address_length,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,50 @@ typedef struct keyrange {
|
|||
int end_key_length;
|
||||
} FDBKeyRange;
|
||||
|
||||
/*
|
||||
* Raw mutation types returned by CDC. The numeric values match
|
||||
* MutationRef::Type and, for atomic operations, FDBMutationType.
|
||||
*/
|
||||
typedef enum {
|
||||
FDB_CDC_MUTATION_TYPE_SET_VALUE = 0,
|
||||
FDB_CDC_MUTATION_TYPE_CLEAR_RANGE = 1,
|
||||
FDB_CDC_MUTATION_TYPE_ADD = 2,
|
||||
FDB_CDC_MUTATION_TYPE_AND = 6,
|
||||
FDB_CDC_MUTATION_TYPE_OR = 7,
|
||||
FDB_CDC_MUTATION_TYPE_XOR = 8,
|
||||
FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS = 9,
|
||||
FDB_CDC_MUTATION_TYPE_MAX = 12,
|
||||
FDB_CDC_MUTATION_TYPE_MIN = 13,
|
||||
FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY = 14,
|
||||
FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE = 15,
|
||||
FDB_CDC_MUTATION_TYPE_BYTE_MIN = 16,
|
||||
FDB_CDC_MUTATION_TYPE_BYTE_MAX = 17,
|
||||
FDB_CDC_MUTATION_TYPE_MIN_V2 = 18,
|
||||
FDB_CDC_MUTATION_TYPE_AND_V2 = 19,
|
||||
FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR = 20
|
||||
} FDBCdcMutationType;
|
||||
|
||||
typedef struct cdc_stream_info {
|
||||
FDBKey name;
|
||||
uint64_t stream_id;
|
||||
FDBKeyRange key_range;
|
||||
int64_t min_version;
|
||||
} FDBCdcStreamInfo;
|
||||
|
||||
typedef struct cdc_mutation {
|
||||
/* FDBCdcMutationType */ uint8_t type;
|
||||
const uint8_t* param1;
|
||||
int param1_length;
|
||||
const uint8_t* param2;
|
||||
int param2_length;
|
||||
} FDBCdcMutation;
|
||||
|
||||
typedef struct cdc_versioned_mutations {
|
||||
int64_t version;
|
||||
const FDBCdcMutation* mutations;
|
||||
int mutation_count;
|
||||
} FDBCdcVersionedMutations;
|
||||
|
||||
/*
|
||||
* TODO: delete the following "blob granule" and "tenant" related data types
|
||||
* when we are sure it's safe to do so.
|
||||
|
|
@ -353,6 +397,18 @@ DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyrange_array(FDBFuture
|
|||
FDBKeyRange const** out_ranges,
|
||||
int* out_count);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* f,
|
||||
FDBCdcStreamInfo const** out_streams,
|
||||
int* out_count);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* f, FDBCdcConsumer** out_consumer);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t
|
||||
fdb_future_get_cdc_versioned_mutations(FDBFuture* f,
|
||||
FDBCdcVersionedMutations const** out_mutations,
|
||||
int* out_count,
|
||||
int64_t* out_last_consumed_version);
|
||||
|
||||
/* FDBResult is a synchronous computation result, as opposed to a future that is asynchronous. */
|
||||
DLLEXPORT void fdb_result_destroy(FDBResult* r);
|
||||
|
||||
|
|
@ -378,6 +434,38 @@ DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d,
|
|||
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d,
|
||||
FDBTransaction** out_transaction);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* db,
|
||||
uint8_t const* name,
|
||||
int name_length,
|
||||
uint8_t const* begin_key,
|
||||
int begin_key_length,
|
||||
uint8_t const* end_key,
|
||||
int end_key_length);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* db,
|
||||
uint8_t const* name,
|
||||
int name_length);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* db);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* db,
|
||||
uint8_t const* name,
|
||||
int name_length);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* db,
|
||||
uint64_t stream_id,
|
||||
int64_t last_consumed_version);
|
||||
|
||||
DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer);
|
||||
|
||||
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer,
|
||||
uint64_t* out_stream_id,
|
||||
int64_t* out_last_consumed_version);
|
||||
|
||||
/*
|
||||
* Dummy versions of tenant-related functions are needed in the FDB C library
|
||||
* because 7.x python bindings always load these functions on startup.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ typedef struct FDB_result FDBResult;
|
|||
typedef struct FDB_cluster FDBCluster;
|
||||
typedef struct FDB_database FDBDatabase;
|
||||
typedef struct FDB_transaction FDBTransaction;
|
||||
typedef struct FDB_cdc_consumer FDBCdcConsumer;
|
||||
|
||||
typedef int fdb_error_t;
|
||||
typedef int fdb_bool_t;
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ class ClientConfigPrevVersionTests(unittest.TestCase):
|
|||
# Leaving an unsupported API version
|
||||
test = ClientConfigTest(self)
|
||||
test.create_external_lib_path(PREV_RELEASE_VERSION)
|
||||
test.expected_error = 2203 # api_version_not_supported
|
||||
test.expected_error = 2204 # API function missing
|
||||
test.exec()
|
||||
|
||||
def test_external_client_unsupported_api_ignore(self):
|
||||
|
|
@ -739,7 +739,7 @@ class ClientTracingTests(unittest.TestCase):
|
|||
test.create_external_lib_dir([CURRENT_VERSION, PREV_RELEASE_VERSION])
|
||||
test.api_version = api_version_from_str(CURRENT_VERSION)
|
||||
test.disable_local_client = True
|
||||
test.expected_error = 2203 # api_version_not_supported
|
||||
test.expected_error = 2204 # API function missing
|
||||
|
||||
self.exec_test()
|
||||
self.assertEqual(0, len(self.trace_files))
|
||||
|
|
@ -775,7 +775,7 @@ class ClientTracingTests(unittest.TestCase):
|
|||
test.api_version = api_version_from_str(CURRENT_VERSION)
|
||||
test.disable_local_client = True
|
||||
test.trace_initialize_on_setup = True
|
||||
test.expected_error = 2203 # api_version_not_supported
|
||||
test.expected_error = 2204 # API function missing
|
||||
|
||||
self.exec_test()
|
||||
self.assertEqual(1, len(self.trace_files))
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@
|
|||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
|
@ -1021,9 +1023,9 @@ TEST_CASE("tuple_support_versionstamp") {
|
|||
ASSERT(t.getVersionstamp(2) == vs);
|
||||
|
||||
// verify the round-way pack-unpack path for a Tuple containing a versionstamp
|
||||
StringRef result1 = t.pack();
|
||||
Standalone<StringRef> result1 = t.pack();
|
||||
Tuple t2 = Tuple::unpack(result1);
|
||||
StringRef result2 = t2.pack();
|
||||
Standalone<StringRef> result2 = t2.pack();
|
||||
ASSERT(t2.getVersionstamp(2) == vs);
|
||||
ASSERT(result1.toString() == result2.toString());
|
||||
}
|
||||
|
|
@ -1970,6 +1972,245 @@ TEST_CASE("fdb_database_get_server_protocol") {
|
|||
fdb_future_destroy(protocolFuture);
|
||||
}
|
||||
|
||||
TEST_CASE("CDC C binding end-to-end") {
|
||||
using FuturePtr = std::unique_ptr<FDBFuture, decltype(&fdb_future_destroy)>;
|
||||
using ConsumerPtr = std::unique_ptr<FDBCdcConsumer, decltype(&fdb_cdc_consumer_destroy)>;
|
||||
|
||||
auto ownFuture = [](FDBFuture* future) { return FuturePtr(future, &fdb_future_destroy); };
|
||||
auto ownConsumer = [](FDBCdcConsumer* consumer) { return ConsumerPtr(consumer, &fdb_cdc_consumer_destroy); };
|
||||
auto waitForSuccess = [](FDBFuture* future) {
|
||||
fdb_check(fdb_future_block_until_ready(future));
|
||||
fdb_check(fdb_future_get_error(future));
|
||||
};
|
||||
auto commitSetValues = [](std::vector<std::pair<std::string, std::string>> const& values) {
|
||||
fdb::Transaction tr(db);
|
||||
while (true) {
|
||||
for (auto const& [key, value] : values) {
|
||||
tr.set(key, value);
|
||||
}
|
||||
fdb::EmptyFuture commitFuture = tr.commit();
|
||||
fdb_error_t err = wait_future(commitFuture);
|
||||
if (err) {
|
||||
fdb::EmptyFuture onErrorFuture = tr.on_error(err);
|
||||
fdb_check(wait_future(onErrorFuture));
|
||||
continue;
|
||||
}
|
||||
int64_t committedVersion;
|
||||
fdb_check(tr.get_committed_version(&committedVersion));
|
||||
return committedVersion;
|
||||
}
|
||||
};
|
||||
auto commitClearRange = [](std::string const& begin, std::string const& end) {
|
||||
fdb::Transaction tr(db);
|
||||
while (true) {
|
||||
tr.clear_range(begin, end);
|
||||
fdb::EmptyFuture commitFuture = tr.commit();
|
||||
fdb_error_t err = wait_future(commitFuture);
|
||||
if (err) {
|
||||
fdb::EmptyFuture onErrorFuture = tr.on_error(err);
|
||||
fdb_check(wait_future(onErrorFuture));
|
||||
continue;
|
||||
}
|
||||
int64_t committedVersion;
|
||||
fdb_check(tr.get_committed_version(&committedVersion));
|
||||
return committedVersion;
|
||||
}
|
||||
};
|
||||
|
||||
struct CopiedCdcMutation {
|
||||
uint8_t type;
|
||||
std::string param1;
|
||||
std::string param2;
|
||||
};
|
||||
struct CopiedCdcReply {
|
||||
int64_t version;
|
||||
int64_t lastConsumedVersion;
|
||||
std::vector<CopiedCdcMutation> mutations;
|
||||
};
|
||||
|
||||
auto consumeThroughVersion = [&](FDBCdcConsumer* consumer, int64_t targetVersion) {
|
||||
while (true) {
|
||||
auto future = ownFuture(fdb_cdc_consumer_consume(consumer));
|
||||
REQUIRE(future != nullptr);
|
||||
waitForSuccess(future.get());
|
||||
|
||||
FDBCdcVersionedMutations const* groups = nullptr;
|
||||
int groupCount = -1;
|
||||
int64_t lastConsumedVersion = -1;
|
||||
fdb_check(fdb_future_get_cdc_versioned_mutations(future.get(), &groups, &groupCount, &lastConsumedVersion));
|
||||
CHECK(groupCount >= 0);
|
||||
|
||||
FDBCdcVersionedMutations const* targetGroup = nullptr;
|
||||
for (int i = 0; i < groupCount; ++i) {
|
||||
if (groups[i].version == targetVersion) {
|
||||
targetGroup = &groups[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetGroup == nullptr && lastConsumedVersion < targetVersion) {
|
||||
auto acknowledgeFuture = ownFuture(fdb_cdc_consumer_acknowledge(consumer));
|
||||
REQUIRE(acknowledgeFuture != nullptr);
|
||||
waitForSuccess(acknowledgeFuture.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
REQUIRE(targetGroup != nullptr);
|
||||
CopiedCdcReply result{ targetGroup->version, lastConsumedVersion, {} };
|
||||
result.mutations.reserve(targetGroup->mutation_count);
|
||||
for (int i = 0; i < targetGroup->mutation_count; ++i) {
|
||||
auto const& mutation = targetGroup->mutations[i];
|
||||
result.mutations.push_back(CopiedCdcMutation{
|
||||
mutation.type,
|
||||
std::string(reinterpret_cast<char const*>(mutation.param1), mutation.param1_length),
|
||||
std::string(reinterpret_cast<char const*>(mutation.param2), mutation.param2_length) });
|
||||
}
|
||||
|
||||
fdb_future_release_memory(future.get());
|
||||
CHECK(fdb_future_get_cdc_versioned_mutations(future.get(), &groups, &groupCount, &lastConsumedVersion) ==
|
||||
1102); // future_released
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
const std::string streamName = key("cdc-stream");
|
||||
const std::string rangeBegin = key("cdc-data/");
|
||||
const std::string rangeEnd = strinc_str(rangeBegin);
|
||||
const std::string firstKey = rangeBegin + "first";
|
||||
const std::string secondKey = rangeBegin + "second";
|
||||
const std::string outsideKey = key("outside-cdc-range");
|
||||
const std::string firstValue = "first-value";
|
||||
const std::string secondValue = "second-value";
|
||||
|
||||
// Clear test data before registration so CDC sees only mutations below.
|
||||
insert_data(db, std::map<std::string, std::string>{});
|
||||
|
||||
// CDC calls must retain their input bytes after the C function returns.
|
||||
std::string nameInput = streamName;
|
||||
std::string beginInput = rangeBegin;
|
||||
std::string endInput = rangeEnd;
|
||||
auto registerFuture =
|
||||
ownFuture(fdb_database_register_cdc_stream(db,
|
||||
reinterpret_cast<uint8_t const*>(nameInput.data()),
|
||||
nameInput.size(),
|
||||
reinterpret_cast<uint8_t const*>(beginInput.data()),
|
||||
beginInput.size(),
|
||||
reinterpret_cast<uint8_t const*>(endInput.data()),
|
||||
endInput.size()));
|
||||
REQUIRE(registerFuture != nullptr);
|
||||
std::fill(nameInput.begin(), nameInput.end(), 'x');
|
||||
std::fill(beginInput.begin(), beginInput.end(), 'x');
|
||||
std::fill(endInput.begin(), endInput.end(), 'x');
|
||||
waitForSuccess(registerFuture.get());
|
||||
|
||||
uint64_t streamId = 0;
|
||||
fdb_check(fdb_future_get_uint64(registerFuture.get(), &streamId));
|
||||
REQUIRE(streamId != 0);
|
||||
|
||||
auto listFuture = ownFuture(fdb_database_list_cdc_streams(db));
|
||||
REQUIRE(listFuture != nullptr);
|
||||
waitForSuccess(listFuture.get());
|
||||
FDBCdcStreamInfo const* streams = nullptr;
|
||||
int streamCount = -1;
|
||||
fdb_check(fdb_future_get_cdc_stream_info_array(listFuture.get(), &streams, &streamCount));
|
||||
bool foundStream = false;
|
||||
for (int i = 0; i < streamCount; ++i) {
|
||||
if (extractString(streams[i].name) != streamName) {
|
||||
continue;
|
||||
}
|
||||
foundStream = true;
|
||||
CHECK(streams[i].stream_id == streamId);
|
||||
CHECK(std::string(reinterpret_cast<char const*>(streams[i].key_range.begin_key),
|
||||
streams[i].key_range.begin_key_length) == rangeBegin);
|
||||
CHECK(std::string(reinterpret_cast<char const*>(streams[i].key_range.end_key),
|
||||
streams[i].key_range.end_key_length) == rangeEnd);
|
||||
CHECK(streams[i].min_version >= 0);
|
||||
}
|
||||
REQUIRE(foundStream);
|
||||
fdb_future_release_memory(listFuture.get());
|
||||
CHECK(fdb_future_get_cdc_stream_info_array(listFuture.get(), &streams, &streamCount) == 1102); // future_released
|
||||
|
||||
auto createFuture = ownFuture(
|
||||
fdb_database_create_cdc_consumer(db, reinterpret_cast<uint8_t const*>(streamName.data()), streamName.size()));
|
||||
REQUIRE(createFuture != nullptr);
|
||||
waitForSuccess(createFuture.get());
|
||||
FDBCdcConsumer* rawConsumer = nullptr;
|
||||
fdb_check(fdb_future_get_cdc_consumer(createFuture.get(), &rawConsumer));
|
||||
auto consumer = ownConsumer(rawConsumer);
|
||||
createFuture.reset();
|
||||
REQUIRE(consumer != nullptr);
|
||||
|
||||
uint64_t positionStreamId = 0;
|
||||
int64_t positionVersion = 0;
|
||||
fdb_check(fdb_cdc_consumer_get_position(consumer.get(), &positionStreamId, &positionVersion));
|
||||
CHECK(positionStreamId == streamId);
|
||||
CHECK(positionVersion == -1);
|
||||
|
||||
const int64_t setVersion =
|
||||
commitSetValues({ { firstKey, firstValue }, { secondKey, secondValue }, { outsideKey, "outside-value" } });
|
||||
auto setReply = consumeThroughVersion(consumer.get(), setVersion);
|
||||
CHECK(setReply.version == setVersion);
|
||||
CHECK(setReply.lastConsumedVersion >= setVersion);
|
||||
REQUIRE(setReply.mutations.size() == 2);
|
||||
std::map<std::string, std::string> expectedSets{ { firstKey, firstValue }, { secondKey, secondValue } };
|
||||
for (auto const& mutation : setReply.mutations) {
|
||||
CHECK(mutation.type == FDB_CDC_MUTATION_TYPE_SET_VALUE);
|
||||
auto expected = expectedSets.find(mutation.param1);
|
||||
REQUIRE(expected != expectedSets.end());
|
||||
CHECK(mutation.param2 == expected->second);
|
||||
expectedSets.erase(expected);
|
||||
}
|
||||
CHECK(expectedSets.empty());
|
||||
|
||||
auto acknowledgeFuture = ownFuture(fdb_cdc_consumer_acknowledge(consumer.get()));
|
||||
REQUIRE(acknowledgeFuture != nullptr);
|
||||
waitForSuccess(acknowledgeFuture.get());
|
||||
fdb_check(fdb_cdc_consumer_get_position(consumer.get(), &positionStreamId, &positionVersion));
|
||||
CHECK(positionStreamId == streamId);
|
||||
CHECK(positionVersion == setReply.lastConsumedVersion);
|
||||
consumer.reset();
|
||||
|
||||
auto resumeFuture = ownFuture(fdb_database_resume_cdc_consumer(db, positionStreamId, positionVersion));
|
||||
REQUIRE(resumeFuture != nullptr);
|
||||
waitForSuccess(resumeFuture.get());
|
||||
FDBCdcConsumer* rawResumedConsumer = nullptr;
|
||||
fdb_check(fdb_future_get_cdc_consumer(resumeFuture.get(), &rawResumedConsumer));
|
||||
auto resumedConsumer = ownConsumer(rawResumedConsumer);
|
||||
resumeFuture.reset();
|
||||
REQUIRE(resumedConsumer != nullptr);
|
||||
fdb_check(fdb_cdc_consumer_get_position(resumedConsumer.get(), &positionStreamId, &positionVersion));
|
||||
CHECK(positionStreamId == streamId);
|
||||
CHECK(positionVersion == setReply.lastConsumedVersion);
|
||||
|
||||
const std::string clearEnd = strinc_str(firstKey);
|
||||
const int64_t clearVersion = commitClearRange(firstKey, clearEnd);
|
||||
auto clearReply = consumeThroughVersion(resumedConsumer.get(), clearVersion);
|
||||
CHECK(clearReply.version == clearVersion);
|
||||
REQUIRE(clearReply.mutations.size() == 1);
|
||||
CHECK(clearReply.mutations[0].type == FDB_CDC_MUTATION_TYPE_CLEAR_RANGE);
|
||||
CHECK(clearReply.mutations[0].param1 == firstKey);
|
||||
CHECK(clearReply.mutations[0].param2 == clearEnd);
|
||||
|
||||
auto resumedAcknowledgeFuture = ownFuture(fdb_cdc_consumer_acknowledge(resumedConsumer.get()));
|
||||
REQUIRE(resumedAcknowledgeFuture != nullptr);
|
||||
waitForSuccess(resumedAcknowledgeFuture.get());
|
||||
resumedConsumer.reset();
|
||||
|
||||
auto removeFuture = ownFuture(
|
||||
fdb_database_remove_cdc_stream(db, reinterpret_cast<uint8_t const*>(streamName.data()), streamName.size()));
|
||||
REQUIRE(removeFuture != nullptr);
|
||||
waitForSuccess(removeFuture.get());
|
||||
|
||||
auto removedListFuture = ownFuture(fdb_database_list_cdc_streams(db));
|
||||
REQUIRE(removedListFuture != nullptr);
|
||||
waitForSuccess(removedListFuture.get());
|
||||
streams = nullptr;
|
||||
streamCount = -1;
|
||||
fdb_check(fdb_future_get_cdc_stream_info_array(removedListFuture.get(), &streams, &streamCount));
|
||||
for (int i = 0; i < streamCount; ++i) {
|
||||
CHECK(extractString(streams[i].name) != streamName);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fdb_transaction_watch read_your_writes_disable") {
|
||||
// Watches created on a transaction with the option READ_YOUR_WRITES_DISABLE
|
||||
// should return a watches_disabled error.
|
||||
|
|
|
|||
|
|
@ -269,13 +269,17 @@ echo "Executing TestHarness2 with seed ${JOSHUA_SEED}..." >&2
|
|||
|
||||
# Run TestHarness - output goes to stdout via tee AND gets saved to file
|
||||
python3 -m test_harness.app "${PYTHON_CMD_ARGS[@]}" 2> "${PYTHON_APP_STDERR_FILE}" | tee "${PYTHON_APP_STDOUT_FILE}"
|
||||
PYTHON_EXIT_CODE=$?
|
||||
PIPE_EXIT_CODES=("${PIPESTATUS[@]}")
|
||||
PYTHON_EXIT_CODE=${PIPE_EXIT_CODES[0]}
|
||||
if [ "${PYTHON_EXIT_CODE}" -eq 0 ] && [ "${PIPE_EXIT_CODES[1]}" -ne 0 ]; then
|
||||
PYTHON_EXIT_CODE=${PIPE_EXIT_CODES[1]}
|
||||
fi
|
||||
|
||||
echo "TestHarness2 execution finished. Exit code: ${PYTHON_EXIT_CODE}" >&2
|
||||
|
||||
# Check if stdout file is empty and generate fallback if needed
|
||||
# This ensures Joshua ALWAYS gets XML output, never empty string
|
||||
if [ ! -s "${PYTHON_APP_STDOUT_FILE}" ] || [ $(wc -c < "${PYTHON_APP_STDOUT_FILE}") -eq 0 ]; then
|
||||
if [ ! -s "${PYTHON_APP_STDOUT_FILE}" ]; then
|
||||
echo "WARNING: TestHarness2 produced no output - generating fallback XML" >&2
|
||||
ls -l "${PYTHON_APP_STDOUT_FILE}" >&2 2>/dev/null || true
|
||||
|
||||
|
|
@ -287,7 +291,7 @@ if [ ! -s "${PYTHON_APP_STDOUT_FILE}" ] || [ $(wc -c < "${PYTHON_APP_STDOUT_FILE
|
|||
# - JoshuaSeed (from env var) is sufficient to identify the failed test in Joshua FDB:
|
||||
# e.g. 'j tail ENSEMBLE_ID --raw | grep 5836554762367547606' (but unlikely to have
|
||||
# any RandomSeed info, etc.)
|
||||
echo "<Test TestFile=\"UNKNOWN\" RandomSeed=\"UNKNOWN\" BuggifyEnabled=\"UNKNOWN\" FaultInjectionEnabled=\"UNKNOWN\" JoshuaSeed=\"${JOSHUA_SEED}\" Ok=\"0\" CrashReason=\"TestHarnessProducedNoOutput\" PythonExitCode=\"${PYTHON_EXIT_CODE}\"><JoshuaMessage Severity=\"40\" Message=\"TestHarness2 crashed or timed out before producing any output. Seed=${JOSHUA_SEED}\"/></Test>"
|
||||
echo "<Test TestFile=\"UNKNOWN\" RandomSeed=\"UNKNOWN\" BuggifyEnabled=\"UNKNOWN\" FaultInjectionEnabled=\"UNKNOWN\" JoshuaSeed=\"${JOSHUA_SEED}\" Ok=\"0\" CrashReason=\"TestHarnessProducedNoOutput\" PythonExitCode=\"${PYTHON_EXIT_CODE}\"><JoshuaMessage Severity=\"40\" Message=\"TestHarness2 crashed or timed out before producing any output. Seed=${JOSHUA_SEED}\"/></Test>" | tee "${PYTHON_APP_STDOUT_FILE}"
|
||||
fi
|
||||
|
||||
# Note: stdout was already output via tee above (or fallback echo if empty)
|
||||
|
|
@ -303,7 +307,7 @@ fi
|
|||
|
||||
# Exit with appropriate code
|
||||
if [ "${PYTHON_EXIT_CODE}" -ne 0 ]; then
|
||||
exit ${PYTHON_EXIT_CODE}
|
||||
exit "${PYTHON_EXIT_CODE}"
|
||||
elif [ "${TEST_FAILED}" = "true" ]; then
|
||||
exit 1
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
test_root=$(mktemp -d)
|
||||
trap 'rm -rf "${test_root}"' EXIT
|
||||
|
||||
mkdir -p "${test_root}/bin"
|
||||
cat > "${test_root}/bin/python3" <<'FAKE_PYTHON'
|
||||
#!/usr/bin/env bash
|
||||
|
||||
case "${FAKE_HARNESS_MODE}" in
|
||||
pass_then_crash)
|
||||
echo '<Test Ok="1"/>'
|
||||
exit 23
|
||||
;;
|
||||
no_output)
|
||||
exit 0
|
||||
;;
|
||||
pass)
|
||||
echo '<Test Ok="1"/>'
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
FAKE_PYTHON
|
||||
chmod +x "${test_root}/bin/python3"
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)
|
||||
wrapper="${script_dir}/correctnessTest.sh"
|
||||
|
||||
run_case() {
|
||||
local mode=$1
|
||||
local expected_exit=$2
|
||||
local expected_ok=$3
|
||||
local output_dir="${test_root}/${mode}"
|
||||
local ensemble_id="correctness-test-${mode}"
|
||||
local run_dir="${output_dir}/th_run_${ensemble_id}"
|
||||
local stdout_file="${output_dir}/stdout.log"
|
||||
local status
|
||||
|
||||
mkdir -p "${output_dir}"
|
||||
set +e
|
||||
PATH="${test_root}/bin:${PATH}" \
|
||||
FAKE_HARNESS_MODE="${mode}" \
|
||||
JOSHUA_SEED=12345 \
|
||||
JOSHUA_ENSEMBLE_ID="${ensemble_id}" \
|
||||
TH_OUTPUT_DIR="${output_dir}" \
|
||||
TH_ARCHIVE_LOGS_ON_FAILURE=true \
|
||||
bash "${wrapper}" > "${stdout_file}" 2> "${output_dir}/stderr.log"
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
test "${status}" -eq "${expected_exit}"
|
||||
grep -q "Ok=\"${expected_ok}\"" "${stdout_file}"
|
||||
if [ "${expected_exit}" -ne 0 ]; then
|
||||
test -f "${run_dir}/python_app_stdout.log"
|
||||
grep -q "Ok=\"${expected_ok}\"" "${run_dir}/python_app_stdout.log"
|
||||
else
|
||||
test ! -e "${run_dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_case pass_then_crash 23 1
|
||||
run_case no_output 1 0
|
||||
run_case pass 0 1
|
||||
|
||||
grep -q 'CrashReason="TestHarnessProducedNoOutput"' "${test_root}/no_output/stdout.log"
|
||||
test "$(grep -c 'CrashReason="TestHarnessProducedNoOutput"' "${test_root}/no_output/stdout.log")" -eq 1
|
||||
|
||||
echo 'correctnessTest wrapper regressions passed'
|
||||
|
|
@ -57,12 +57,21 @@ def write_coverage_chunk(
|
|||
metadata_dir = fdb.directory.create_or_open(tr, metadata)
|
||||
v = tr[metadata_dir["initialized"]]
|
||||
initialized = v.present()
|
||||
missing = []
|
||||
if initialized:
|
||||
for cov, covered in coverage:
|
||||
if not covered:
|
||||
key = cov_dir.pack((cov.file, cov.line, cov.comment, cov.rare))
|
||||
missing.append((key, tr.snapshot[key]))
|
||||
for cov, covered in coverage:
|
||||
if not initialized or covered:
|
||||
tr.add(
|
||||
cov_dir.pack((cov.file, cov.line, cov.comment, cov.rare)),
|
||||
struct.pack("<I", 1 if covered else 0),
|
||||
)
|
||||
for key, value in missing:
|
||||
if not value.present():
|
||||
tr.add(key, struct.pack("<I", 0))
|
||||
return initialized
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@ class EnsembleResults:
|
|||
)
|
||||
else:
|
||||
coverage_dict = collections.OrderedDict()
|
||||
self.stats: List[Tuple[str, int, int]] = []
|
||||
for k, v in self.statistics.stats.items():
|
||||
self.global_statistics.total_test_runs += v.run_count
|
||||
self.global_statistics.total_cpu_time += v.runtime
|
||||
self.stats.append((k, v.runtime, v.run_count))
|
||||
self.stats.sort(key=lambda x: x[1], reverse=True)
|
||||
self.coverage: List[Tuple[Coverage, int]] = []
|
||||
self.min_coverage_hit: int | None = None
|
||||
self.ratio = self.global_statistics.total_test_runs / config.hit_per_runs_ratio
|
||||
|
|
@ -66,12 +72,6 @@ class EnsembleResults:
|
|||
if self.min_coverage_hit is None or self.min_coverage_hit > count:
|
||||
self.min_coverage_hit = count
|
||||
self.coverage.sort(key=lambda x: (x[1], x[0].file, x[0].line))
|
||||
self.stats: List[Tuple[str, int, int]] = []
|
||||
for k, v in self.statistics.stats.items():
|
||||
self.global_statistics.total_test_runs += v.run_count
|
||||
self.global_statistics.total_cpu_time += v.runtime
|
||||
self.stats.append((k, v.runtime, v.run_count))
|
||||
self.stats.sort(key=lambda x: x[1], reverse=True)
|
||||
if not self.code_probe_tracking_enabled:
|
||||
self.coverage_ok = True
|
||||
elif self.min_coverage_hit is not None:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import importlib
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
from test_harness.config import config
|
||||
from test_harness.summarize import Coverage
|
||||
|
||||
fdb_stub = sys.modules.setdefault("fdb", types.ModuleType("fdb"))
|
||||
fdb_stub.__path__ = []
|
||||
fdb_stub.api_version = lambda *_: None
|
||||
fdb_stub.transactional = lambda function: function
|
||||
fdb_stub.tuple = sys.modules.setdefault("fdb.tuple", types.ModuleType("fdb.tuple"))
|
||||
harness_fdb = importlib.import_module("test_harness.fdb")
|
||||
EnsembleResults = importlib.import_module("test_harness.results").EnsembleResults
|
||||
|
||||
|
||||
class FakeDirectory:
|
||||
def __init__(self, prefix):
|
||||
self.prefix = prefix
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.prefix + (key,)
|
||||
|
||||
def pack(self, key):
|
||||
return self.prefix + key
|
||||
|
||||
|
||||
class FakeTransaction:
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
self.snapshot = self
|
||||
self.mutations = []
|
||||
|
||||
def __getitem__(self, key):
|
||||
return SimpleNamespace(present=lambda: key in self.values)
|
||||
|
||||
def add(self, key, value):
|
||||
self.mutations.append(key)
|
||||
self.values[key] = self.values.get(key, 0) + struct.unpack("<I", value)[0]
|
||||
|
||||
|
||||
class CoverageTest(unittest.TestCase):
|
||||
def test_frequency_threshold_uses_total_test_runs(self):
|
||||
stats = SimpleNamespace(
|
||||
stats={"fast": SimpleNamespace(runtime=30, run_count=100000)}
|
||||
)
|
||||
coverage = {
|
||||
Coverage("a.cpp", 1, "nonrare", False): 5,
|
||||
Coverage("b.cpp", 2, "rare", True): 4,
|
||||
Coverage("c.cpp", 3, "hit", False): 6,
|
||||
}
|
||||
|
||||
with mock.patch.object(
|
||||
harness_fdb, "Statistics", return_value=stats
|
||||
), mock.patch.object(
|
||||
harness_fdb, "read_coverage", return_value=coverage
|
||||
), mock.patch.multiple(
|
||||
config,
|
||||
disable_code_probes=False,
|
||||
hit_per_runs_ratio=20000,
|
||||
cov_include_files=r".*",
|
||||
cov_exclude_files=r".^",
|
||||
):
|
||||
results = EnsembleResults(None, "ensemble")
|
||||
|
||||
self.assertEqual(results.ratio, 5)
|
||||
self.assertEqual(results.global_statistics.total_missed_probes, 2)
|
||||
self.assertEqual(results.global_statistics.total_missed_nonrare_probes, 1)
|
||||
self.assertFalse(results.coverage_ok)
|
||||
|
||||
def test_late_zero_hit_probe_is_persisted_once(self):
|
||||
coverage_path, metadata_path = ("coverage",), ("metadata",)
|
||||
fdb_stub.directory = SimpleNamespace(
|
||||
create_or_open=lambda _, prefix: FakeDirectory(prefix)
|
||||
)
|
||||
hit = Coverage("hit.cpp", 1, "hit", False)
|
||||
existing_zero = Coverage("zero.cpp", 2, "zero", False)
|
||||
late_zero = Coverage("late.cpp", 3, "late", False)
|
||||
|
||||
def key(cov):
|
||||
return coverage_path + (cov.file, cov.line, cov.comment, cov.rare)
|
||||
|
||||
transaction = FakeTransaction(
|
||||
{metadata_path + ("initialized",): True, key(hit): 5, key(existing_zero): 0}
|
||||
)
|
||||
coverage = [(hit, True), (existing_zero, False), (late_zero, False)]
|
||||
|
||||
initialized = harness_fdb.write_coverage_chunk(
|
||||
transaction, coverage_path, metadata_path, coverage, False
|
||||
)
|
||||
|
||||
self.assertTrue(initialized)
|
||||
self.assertEqual(transaction.values[key(late_zero)], 0)
|
||||
self.assertEqual(transaction.mutations, [key(hit), key(late_zero)])
|
||||
transaction.mutations.clear()
|
||||
harness_fdb.write_coverage_chunk(
|
||||
transaction, coverage_path, metadata_path, coverage, initialized
|
||||
)
|
||||
self.assertEqual(transaction.mutations, [key(hit)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -1457,7 +1457,7 @@ Serializes all endpoints directly: `waitFailure`, `getRateInfo`, `haltRatekeeper
|
|||
|
||||
## 13. Worker Protocol
|
||||
|
||||
**Source:** `fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h`
|
||||
**Source:** `fdbserver/core/include/fdbserver/core/WorkerInterface.h`
|
||||
|
||||
Workers host server roles. The cluster controller sends initialization requests.
|
||||
|
||||
|
|
@ -1543,9 +1543,6 @@ All follow the pattern: fields describing the role configuration + `ReplyPromise
|
|||
### NetworkTestRequest
|
||||
`Key key`, `uint32_t replySize`, `reply` → **NetworkTestReply** {`Value value`}.
|
||||
|
||||
### NetworkTestStreamingRequest
|
||||
`reply` (stream) → **NetworkTestStreamingReply** {`Optional<UID> acknowledgeToken`, `uint16_t sequence`, `int index`}.
|
||||
|
||||
---
|
||||
|
||||
## 15. Client Worker / Debug / Process Protocols
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ Plus supporting code: [`fdbserver/worker/`](https://github.com/apple/foundationd
|
|||
- `ServerDBInfo` is the cluster-wide configuration broadcast. Contains: master interface, proxy lists, log system config, recovery state, latency band config.
|
||||
- Updated by CC and distributed to all workers. Workers react to changes (e.g., new proxy set).
|
||||
|
||||
**Principal files:** [`ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp), `ClusterController.h`, [`Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp), [`LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp), [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp), [`WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h)
|
||||
**Principal files:** [`ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp), `ClusterController.h`, [`Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp), [`LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp), [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp), [`WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ struct ServerDBInfo {
|
|||
|
||||
---
|
||||
|
||||
## WorkerInterface -- [`WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h)`:45-126`
|
||||
## WorkerInterface -- [`WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h)`:45-126`
|
||||
|
||||
RPCs exposed by every worker process:
|
||||
|
||||
|
|
@ -315,5 +315,5 @@ RPCs exposed by every worker process:
|
|||
| [`fdbserver/coordinator/Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp) | leaderRegister, generation register, coordination |
|
||||
| [`fdbserver/core/LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp) | tryBecomeLeaderInternal, candidacy, heartbeat |
|
||||
| [`fdbserver/core/CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp) | Replicated read/write over generation registers |
|
||||
| [`fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h) | WorkerInterface, ClusterControllerFullInterface |
|
||||
| [`fdbserver/core/include/fdbserver/core/WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h) | WorkerInterface, ClusterControllerFullInterface |
|
||||
| `fdbserver/core/include/fdbserver/core/ServerDBInfo.h` | ServerDBInfo structure and broadcasting |
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ or transaction-system recovery.
|
|||
|
||||
## Background
|
||||
|
||||
This design describes the native C++ interface and its server implementation.
|
||||
The feature is disabled by default behind `ENABLE_NATIVE_CDC`; the native CDC
|
||||
workloads explicitly enable it, and simulation may randomly enable it. The
|
||||
initial interface is native-only: it does not expose bindings or an external
|
||||
protocol compatibility guarantee.
|
||||
This design describes the native C++ interface, its C binding, and its server
|
||||
implementation. The feature is disabled by default behind `ENABLE_NATIVE_CDC`;
|
||||
the native CDC workloads explicitly enable it, and simulation may randomly
|
||||
enable it. The client interface is exposed through the native C++ API and C
|
||||
binding; it does not expose an external protocol compatibility guarantee.
|
||||
|
||||
The implementation uses the following terms:
|
||||
|
||||
|
|
@ -85,14 +85,17 @@ The current implementation does not attempt to provide:
|
|||
range requires removing and registering a stream.
|
||||
* Throughput-aware assignment of streams across CDC proxies.
|
||||
* Throughput-aware movement of streams between CDC tags.
|
||||
* Client bindings beyond the native API.
|
||||
* Language-specific bindings beyond the C API.
|
||||
|
||||
## Client interface
|
||||
|
||||
The client-facing declarations are in `fdbclient/NativeCdc.h`; durable
|
||||
metadata operations used by server roles are in
|
||||
the private `fdbclient/NativeCdcInternal.h`; cursor and wire request types are in
|
||||
`fdbclient/CDCProxyInterface.h`.
|
||||
The native C++ client-facing declarations are in `fdbclient/NativeCdc.h`;
|
||||
value types and the thread-safe surface shared with language bindings are in
|
||||
`fdbclient/NativeCdcClient.h`; durable metadata operations used by server
|
||||
roles are in the private `fdbclient/NativeCdcInternal.h`; cursor and wire
|
||||
request types are in `fdbclient/CDCProxyInterface.h`. The public C binding is
|
||||
declared in `bindings/c/foundationdb/fdb_c.h` and documented in
|
||||
`documentation/sphinx/source/api-c.rst`.
|
||||
`CDCStreamId` is a `uint64_t` typedef. CDC tag IDs are 16-bit, so one
|
||||
configured tag pool can contain at most 65,536 distinct tags.
|
||||
|
||||
|
|
@ -769,8 +772,9 @@ policy simple.
|
|||
* There is no background process that changes a live stream's CDC tag in
|
||||
response to load. A future implementation can use versioned tag history to
|
||||
make such changes without losing the ability to read earlier tagged data.
|
||||
* The native interface does not yet provide external binding support,
|
||||
administrative tooling, or a higher-level consumer checkpoint abstraction.
|
||||
* The CDC client surface does not yet provide language-specific bindings beyond
|
||||
the C API, administrative tooling, or a higher-level consumer checkpoint
|
||||
abstraction.
|
||||
|
||||
These improvements must preserve the acknowledgement and retired-pop
|
||||
invariants above. In particular, moving a stream between tags cannot forget an
|
||||
|
|
|
|||
|
|
@ -287,7 +287,18 @@ See :ref:`developer-guide-programming-with-futures` for further (language-indepe
|
|||
|
||||
.. note:: This function provides no benefit to most application code. It is designed for use in writing generic, thread-safe language bindings. Applications should normally call :func:`fdb_future_destroy` only.
|
||||
|
||||
This function may only be called after a successful (zero return value) call to :func:`fdb_future_get_key`, :func:`fdb_future_get_value`, or :func:`fdb_future_get_keyvalue_array`. It indicates that the memory returned by the prior get call is no longer needed by the application. After this function has been called the same number of times as ``fdb_future_get_*()``, further calls to ``fdb_future_get_*()`` will return a :ref:`future_released <developer-guide-error-codes>` error. It is still necessary to later destroy the future with :func:`fdb_future_destroy`.
|
||||
This function may only be called after a successful (zero return value)
|
||||
call to an ``fdb_future_get_*()`` function that returns memory owned by
|
||||
the future. This includes :func:`fdb_future_get_key`,
|
||||
:func:`fdb_future_get_value`, :func:`fdb_future_get_keyvalue_array`,
|
||||
:func:`fdb_future_get_cdc_stream_info_array`, and
|
||||
:func:`fdb_future_get_cdc_versioned_mutations`. It indicates that the
|
||||
memory returned by the prior get call is no longer needed by the
|
||||
application. After this function has been called the same number of times
|
||||
as ``fdb_future_get_*()``, further calls to ``fdb_future_get_*()``
|
||||
will return a :ref:`future_released <developer-guide-error-codes>` error.
|
||||
It is still necessary to later destroy the future with
|
||||
:func:`fdb_future_destroy`.
|
||||
|
||||
Calling this function is optional, since :func:`fdb_future_destroy` will also release the memory returned by get functions. However, :func:`fdb_future_release_memory` leaves the future object itself intact and provides a specific error code which can be used for coordination by multiple threads racing to do something with the results of a specific future. This has proven helpful in writing binding code.
|
||||
|
||||
|
|
@ -301,6 +312,13 @@ See :ref:`developer-guide-programming-with-futures` for further (language-indepe
|
|||
|
||||
|future-get-return1| |future-get-return2|.
|
||||
|
||||
.. function:: fdb_error_t fdb_future_get_uint64(FDBFuture* future, uint64_t* out)
|
||||
|
||||
Extracts an unsigned 64-bit integer from a pointer to :type:`FDBFuture`
|
||||
into a caller-provided variable of type ``uint64_t``. |future-warning|
|
||||
|
||||
|future-get-return1| |future-get-return2|.
|
||||
|
||||
.. function:: fdb_error_t fdb_future_get_double(FDBFuture* future, double* out)
|
||||
|
||||
Extracts a double from a pointer to :type:`FDBFuture` into a caller-provided variable of type ``double``. |future-warning|
|
||||
|
|
@ -543,6 +561,135 @@ An |database-blurb1| Modifications to a database are performed via transactions.
|
|||
]
|
||||
}
|
||||
|
||||
CDC
|
||||
---
|
||||
|
||||
CDC exposes durable, named streams of committed mutations for one half-open
|
||||
user-key range. New stream registration requires CDC admission
|
||||
to be enabled on the cluster. Listing, removal, consumer creation, resume,
|
||||
consume, and acknowledgement remain available for already durable streams while
|
||||
new admission is disabled so that callers can drain or remove them.
|
||||
|
||||
The CDC C API is available beginning with API version 800. Applications must
|
||||
select API version 800 or later before calling these functions.
|
||||
|
||||
.. type:: FDBCdcMutationType
|
||||
|
||||
The raw mutation type returned by CDC. Values match the corresponding
|
||||
FoundationDB mutation encoding. ``SET_VALUE`` uses ``param1`` as the key and
|
||||
``param2`` as the value; ``CLEAR_RANGE`` uses them as the clipped begin and
|
||||
end keys; atomic mutations use them as the key and operand.
|
||||
|
||||
The listed constants are not exhaustive. Callers must handle an
|
||||
unrecognized raw ``uint8_t`` value in ``FDBCdcMutation.type``.
|
||||
|
||||
.. type:: FDBCdcStreamInfo
|
||||
|
||||
A listed CDC stream, including its name, stable stream ID, registered
|
||||
key range, and durable minimum required version.
|
||||
|
||||
.. type:: FDBCdcMutation
|
||||
|
||||
One raw mutation within a CDC commit-version group.
|
||||
|
||||
.. type:: FDBCdcVersionedMutations
|
||||
|
||||
A complete group of mutations with one FoundationDB commit version. A
|
||||
consume reply contains only complete groups; callers should preserve this
|
||||
grouping when processing a reply.
|
||||
|
||||
.. type:: FDBCdcConsumer
|
||||
|
||||
An opaque, reference-counted CDC consumer handle. A handle extracted
|
||||
with :func:`fdb_future_get_cdc_consumer()` is owned by the caller and
|
||||
remains valid after the originating future is destroyed. Destroy it exactly
|
||||
once with :func:`fdb_cdc_consumer_destroy()`.
|
||||
|
||||
.. function:: FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* database, uint8_t const* name, int name_length, uint8_t const* begin_key, int begin_key_length, uint8_t const* end_key, int end_key_length)
|
||||
|
||||
Registers ``name`` for the non-empty half-open range ``[begin_key,
|
||||
end_key)`` in normal user key space. Repeating the same name and range is
|
||||
idempotent; reusing a name with a different range fails. The future returns
|
||||
the ``uint64_t`` stream ID, extracted with :func:`fdb_future_get_uint64()`.
|
||||
|
||||
.. function:: FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* database, uint8_t const* name, int name_length)
|
||||
|
||||
Removes the named stream and relinquishes its unread history. Removing a
|
||||
missing name succeeds. The returned future contains no value.
|
||||
|
||||
.. function:: FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* database)
|
||||
|
||||
Returns the currently registered CDC streams. Extract the result with
|
||||
:func:`fdb_future_get_cdc_stream_info_array()`.
|
||||
|
||||
.. function:: fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* future, FDBCdcStreamInfo const** out_streams, int* out_count)
|
||||
|
||||
Extracts the stream-info array returned by
|
||||
:func:`fdb_database_list_cdc_streams()`. |future-get-return1|
|
||||
|future-get-return2|.
|
||||
|
||||
|future-memory-mine|
|
||||
|
||||
.. function:: FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* database, uint8_t const* name, int name_length)
|
||||
|
||||
Creates a consumer for an existing stream name at its initial position.
|
||||
Extract the returned handle with
|
||||
:func:`fdb_future_get_cdc_consumer()`.
|
||||
|
||||
.. function:: FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version)
|
||||
|
||||
Constructs a local consumer handle from a checkpointed cursor. A cursor is
|
||||
only the stable ``stream_id`` and the version through which the caller has
|
||||
consumed; it does not contain process-local state. This call does not
|
||||
validate the stream ID or cursor; those checks occur when the handle
|
||||
consumes or acknowledges. Resume from the last durably processed and
|
||||
acknowledged position because unacknowledged mutations may be redelivered
|
||||
after CDC proxy replacement.
|
||||
|
||||
.. function:: fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* future, FDBCdcConsumer** out_consumer)
|
||||
|
||||
Extracts the owned consumer handle returned by
|
||||
:func:`fdb_database_create_cdc_consumer()` or
|
||||
:func:`fdb_database_resume_cdc_consumer()`. |future-get-return1|
|
||||
|future-get-return2|.
|
||||
|
||||
.. function:: void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer)
|
||||
|
||||
Releases an owned CDC consumer handle.
|
||||
|
||||
.. function:: FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer)
|
||||
|
||||
Long-polls for the next delivered position and complete commit-version
|
||||
mutation groups. Extract the reply with
|
||||
:func:`fdb_future_get_cdc_versioned_mutations()`. Consumption advances
|
||||
the in-memory consumer position but does not release durable CDC retention.
|
||||
|
||||
.. function:: fdb_error_t fdb_future_get_cdc_versioned_mutations(FDBFuture* future, FDBCdcVersionedMutations const** out_mutations, int* out_count, int64_t* out_last_consumed_version)
|
||||
|
||||
Extracts the grouped mutation reply from
|
||||
:func:`fdb_cdc_consumer_consume()`. ``out_last_consumed_version`` is
|
||||
the delivered cursor after the reply and may advance across commit-version
|
||||
gaps that contain no returned mutation. |future-get-return1|
|
||||
|future-get-return2|.
|
||||
|
||||
The returned groups, mutation arrays, and parameter bytes are owned by
|
||||
``future`` and remain valid until :func:`fdb_future_destroy()` or
|
||||
:func:`fdb_future_release_memory()` is called.
|
||||
|
||||
.. function:: FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer)
|
||||
|
||||
Durably acknowledges the consumer's current delivered position. Call this
|
||||
only after all mutations represented through that position have been durably
|
||||
processed. Acknowledgement advances a frontier shared by the stream, not one
|
||||
private to the handle, so a stream may have only one active logical
|
||||
consumer. Independently, a consumer handle may have only one consume or
|
||||
acknowledge operation outstanding at a time. The returned future contains
|
||||
no value.
|
||||
|
||||
.. function:: fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer, uint64_t* out_stream_id, int64_t* out_last_consumed_version)
|
||||
|
||||
Returns the consumer's current cursor.
|
||||
|
||||
Transaction
|
||||
===========
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/AsyncFileBlobStore.h"
|
||||
#include "AsyncFileBlobStore.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
||||
Future<int64_t> AsyncFileBlobStoreRead::size() const {
|
||||
|
|
|
|||
|
|
@ -45,15 +45,12 @@ Future<Void> appendStringRefWithLen(Reference<IBackupFile> file, Standalone<Stri
|
|||
co_await file->append(s.begin(), s.size());
|
||||
}
|
||||
|
||||
// Writes data in chunks of at most BACKUP_MANIFEST_WRITE_CHUNK_SIZE bytes. This is necessary because
|
||||
// IBackupFile::append() takes an int length, so passing a size_t larger than INT_MAX would silently
|
||||
// truncate to a negative value and corrupt the write.
|
||||
Future<Void> appendChunked(Reference<IBackupFile> file, const void* data, size_t len) {
|
||||
Future<Void> append(Reference<IBackupFile> file, const void* data, size_t len) {
|
||||
const char* ptr = static_cast<const char*>(data);
|
||||
size_t chunkLimit = static_cast<size_t>(CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE);
|
||||
for (size_t offset = 0; offset < len;) {
|
||||
int chunkSize = static_cast<int>(
|
||||
std::min(len - offset, static_cast<size_t>(CLIENT_KNOBS->BACKUP_MANIFEST_WRITE_CHUNK_SIZE)));
|
||||
co_await file->append(ptr + offset, chunkSize);
|
||||
size_t chunkSize = std::min(len - offset, chunkLimit);
|
||||
co_await file->appendImpl(ptr + offset, chunkSize);
|
||||
offset += chunkSize;
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +62,7 @@ Future<Void> IBackupFile::appendStringRefWithLen(Standalone<StringRef> s) {
|
|||
}
|
||||
|
||||
Future<Void> IBackupFile::append(const void* data, size_t len) {
|
||||
return IBackupFile_impl::appendChunked(Reference<IBackupFile>::addRef(this), data, len);
|
||||
return IBackupFile_impl::append(Reference<IBackupFile>::addRef(this), data, len);
|
||||
}
|
||||
|
||||
bool isBlobstoreUrl(const std::string& url) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/AsyncFileBlobStore.h"
|
||||
#include "AsyncFileBlobStore.h"
|
||||
#include "BackupContainerBlobStore.h"
|
||||
#include "fdbclient/IBlobStore.h"
|
||||
#include "fdbrpc/AsyncFileEncrypted.h"
|
||||
|
|
@ -52,8 +52,8 @@ public:
|
|||
BackupFile(std::string fileName, Reference<IAsyncFile> file)
|
||||
: IBackupFile(fileName), m_file(file), m_offset(0) {}
|
||||
|
||||
Future<Void> append(const void* data, int len) override {
|
||||
Future<Void> r = m_file->write(data, len, m_offset);
|
||||
Future<Void> appendImpl(const void* data, size_t len) override {
|
||||
Future<Void> r = m_file->write(data, static_cast<int>(len), m_offset);
|
||||
m_offset += len;
|
||||
return r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
#define FDBCLIENT_BACKUP_CONTAINER_BLOBSTORE_H
|
||||
#pragma once
|
||||
|
||||
#include "fdbclient/AsyncFileBlobStore.h"
|
||||
#include "fdbclient/BackupContainerFileSystem.h"
|
||||
#include "fdbclient/IBlobStore.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,20 @@
|
|||
|
||||
class BackupContainerFileSystemImpl {
|
||||
public:
|
||||
// A snapshot manifest is normally a few hundred MB. Warn as it grows and error before it gets dangerously
|
||||
// large, so we see the problem in the logs with time to act before a manifest actually becomes too large
|
||||
// to handle.
|
||||
static void traceManifestSize(const std::string& fileName, int64_t bytes) {
|
||||
constexpr int64_t MB = 1048576; // 1024 * 1024
|
||||
if (bytes >= 750 * MB) {
|
||||
TraceEvent(SevError, "BackupSnapshotManifestTooLarge").detail("FileName", fileName).detail("Bytes", bytes);
|
||||
} else if (bytes >= 500 * MB) {
|
||||
TraceEvent(SevWarnAlways, "BackupSnapshotManifestLarge")
|
||||
.detail("FileName", fileName)
|
||||
.detail("Bytes", bytes);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Do this more efficiently, as the range file list for a snapshot could potentially be hundreds of
|
||||
// megabytes.
|
||||
static Future<std::pair<std::vector<RangeFile>, std::map<std::string, KeyRange>>> readKeyspaceSnapshot(
|
||||
|
|
@ -55,10 +69,22 @@ public:
|
|||
// return them.
|
||||
Reference<IAsyncFile> f = co_await bc->readFile(snapshot.fileName);
|
||||
int64_t size = co_await f->size();
|
||||
Standalone<StringRef> buf = makeString(size);
|
||||
co_await f->read(mutateString(buf), buf.size(), 0);
|
||||
traceManifestSize(snapshot.fileName, size);
|
||||
// A manifest is normally a few hundred MB. Read it into a std::string in chunks; std::string and the
|
||||
// chunked reads guard against an unexpectedly large manifest overflowing the int length that read() takes.
|
||||
// TODO (optimization): the whole manifest is loaded into memory before parsing. Explore if a streaming JSON
|
||||
// parser would avoid this.
|
||||
std::string buf;
|
||||
buf.resize(size);
|
||||
for (int64_t offset = 0; offset < size;) {
|
||||
int toRead = static_cast<int>(std::min<int64_t>(CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE, size - offset));
|
||||
int r = co_await f->read((uint8_t*)buf.data() + offset, toRead, offset);
|
||||
if (r != toRead)
|
||||
throw restore_corrupted_data();
|
||||
offset += r;
|
||||
}
|
||||
json_spirit::mValue json;
|
||||
if (!json_spirit::read_string(buf.toString(), json)) {
|
||||
if (!json_spirit::read_string(buf, json)) {
|
||||
fprintf(stderr,
|
||||
"ERROR: Failed to read data. Verify that backup and restore encryption keys match (if provided) or "
|
||||
"the data is corrupted.\n");
|
||||
|
|
@ -228,6 +254,8 @@ public:
|
|||
}
|
||||
|
||||
co_await yield();
|
||||
// TODO (optimization): the whole manifest is built and serialized in memory before writing. Explore if a
|
||||
// streaming approach would avoid this.
|
||||
std::string docString = json_spirit::write_string(json);
|
||||
|
||||
// Generate filename - add suffixes only when 'both' mode is active to prevent collision
|
||||
|
|
@ -277,6 +305,7 @@ public:
|
|||
|
||||
Reference<IBackupFile> f = co_await bc->writeFile(fileName);
|
||||
|
||||
traceManifestSize(fileName, docString.size());
|
||||
co_await f->append(docString.data(), docString.size());
|
||||
|
||||
co_await f->finish();
|
||||
|
|
@ -2769,12 +2798,12 @@ TEST_CASE("/backup/containers/localdir/expireProgressVersions") {
|
|||
}
|
||||
|
||||
// Verify that writeKeyspaceSnapshotFile correctly writes and reads back a snapshot manifest even when the
|
||||
// JSON document is larger than BACKUP_MANIFEST_WRITE_CHUNK_SIZE, exercising the chunked-append path.
|
||||
// JSON document is larger than BACKUP_MANIFEST_CHUNK_SIZE, exercising the chunked-append path.
|
||||
TEST_CASE("/backup/containers/localdir/writeKeyspaceSnapshotFile/chunked") {
|
||||
// Force a tiny chunk size so a normal-sized manifest triggers multiple append() calls.
|
||||
int savedChunkSize = CLIENT_KNOBS->BACKUP_MANIFEST_WRITE_CHUNK_SIZE;
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_WRITE_CHUNK_SIZE = 64;
|
||||
ASSERT_EQ(CLIENT_KNOBS->BACKUP_MANIFEST_WRITE_CHUNK_SIZE, 64);
|
||||
int savedChunkSize = CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE;
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = 64;
|
||||
ASSERT_EQ(CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE, 64);
|
||||
|
||||
std::string url = format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int());
|
||||
Reference<IBackupContainer> c = IBackupContainer::openContainer(url, {}, {}, 0);
|
||||
|
|
@ -2804,7 +2833,61 @@ TEST_CASE("/backup/containers/localdir/writeKeyspaceSnapshotFile/chunked") {
|
|||
ASSERT_EQ(listing.snapshots[0].beginVersion, 1000);
|
||||
ASSERT_EQ(listing.snapshots[0].endVersion, 1004);
|
||||
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_WRITE_CHUNK_SIZE = savedChunkSize;
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = savedChunkSize;
|
||||
co_await c->deleteContainer();
|
||||
}
|
||||
|
||||
// Verify that readKeyspaceSnapshot correctly reassembles and parses a snapshot manifest when it is read
|
||||
// back in many small pieces, exercising the chunked-read path. A tiny chunk size (that does not divide the
|
||||
// manifest evenly) forces the read loop to run many iterations with a partial final chunk, which catches
|
||||
// off-by-one / wrong-offset / short-read bugs in the loop. Note: a unit test cannot allocate a >2 GB
|
||||
// manifest to reproduce the original int overflow, so this validates the chunking logic instead.
|
||||
TEST_CASE("/backup/containers/localdir/readKeyspaceSnapshot/chunked") {
|
||||
int savedChunkSize = CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE;
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = 7;
|
||||
|
||||
std::string url = format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int());
|
||||
Reference<IBackupContainer> c = IBackupContainer::openContainer(url, {}, {}, 0);
|
||||
co_await c->create();
|
||||
|
||||
Version v = 1000;
|
||||
int blockSize = 64;
|
||||
|
||||
// Write several range files with distinct, non-empty key ranges so the manifest also contains a
|
||||
// populated keyRanges section (exercising that part of the read path too).
|
||||
std::vector<std::string> rangeFileNames;
|
||||
std::vector<std::pair<Key, Key>> beginEndKeys;
|
||||
std::map<std::string, std::pair<std::string, std::string>> expected;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Key begin = StringRef(format("begin-%d", i));
|
||||
Key end = StringRef(format("end-%d", i));
|
||||
Reference<IBackupFile> range = co_await c->writeRangeFile(v, 0, v, blockSize);
|
||||
co_await testWriteSnapshotFile(range, begin, end, blockSize);
|
||||
rangeFileNames.push_back(range->getFileName());
|
||||
beginEndKeys.push_back({ begin, end });
|
||||
expected[range->getFileName()] = { begin.toString(), end.toString() };
|
||||
++v;
|
||||
}
|
||||
|
||||
int64_t totalSize = 99999;
|
||||
co_await c->writeKeyspaceSnapshotFile(rangeFileNames, beginEndKeys, totalSize, IncludeKeyRangeMap::True);
|
||||
|
||||
// Read the manifest back through the chunked-read path and verify every range file and key range.
|
||||
Reference<BackupContainerFileSystem> bcfs = c.castTo<BackupContainerFileSystem>();
|
||||
std::vector<KeyspaceSnapshotFile> snapshots = co_await bcfs->listKeyspaceSnapshots();
|
||||
ASSERT_EQ(snapshots.size(), 1);
|
||||
|
||||
auto [files, keyRanges] = co_await bcfs->readKeyspaceSnapshot(snapshots[0]);
|
||||
ASSERT_EQ(files.size(), rangeFileNames.size());
|
||||
ASSERT_EQ(keyRanges.size(), expected.size());
|
||||
for (const auto& [fileName, range] : expected) {
|
||||
auto it = keyRanges.find(fileName);
|
||||
ASSERT(it != keyRanges.end());
|
||||
ASSERT(it->second.begin == StringRef(range.first));
|
||||
ASSERT(it->second.end == StringRef(range.second));
|
||||
}
|
||||
|
||||
const_cast<ClientKnobs*>(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = savedChunkSize;
|
||||
co_await c->deleteContainer();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ public:
|
|||
m_buffer.reserve(m_buffer.arena(), m_blockSize);
|
||||
}
|
||||
|
||||
Future<Void> append(const void* data, int len) override {
|
||||
m_buffer.append(m_buffer.arena(), (const uint8_t*)data, len);
|
||||
Future<Void> appendImpl(const void* data, size_t len) override {
|
||||
m_buffer.append(m_buffer.arena(), (const uint8_t*)data, static_cast<int>(len));
|
||||
|
||||
if (m_buffer.size() >= m_blockSize) {
|
||||
return flush(m_blockSize);
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ if(WITH_SWIFT)
|
|||
# Generate the module map for FDBClient.
|
||||
include(GenerateModulemap)
|
||||
set(FDBCLIENT_BINARY_DIR "${CMAKE_BINARY_DIR}/fdbclient")
|
||||
generate_modulemap("${CMAKE_BINARY_DIR}/fdbclient/include" "FDBClient" fdbclient)
|
||||
generate_modulemap("${CMAKE_BINARY_DIR}/fdbclient/include" "FDBClient" fdbclient OMIT
|
||||
AsyncFileBlobStore.h RYWIterator.h SnapshotCache.h WriteMap.h)
|
||||
|
||||
# TODO: the TBD validation skip is because of swift_job_run_generic, though it seems weird why we need to do that?
|
||||
target_compile_options(fdbclient_swift PRIVATE "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -std=c++20 -Xfrontend -validate-tbd-against-ir=none -Xcc -DNO_INTELLISENSE -Xcc -ivfsoverlay${CMAKE_BINARY_DIR}/flow/include/headeroverlay.yaml -Xcc -ivfsoverlay${CMAKE_BINARY_DIR}/fdbclient/include/headeroverlay.yaml>")
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) {
|
|||
|
||||
//Backup
|
||||
init( BACKUP_LOCAL_FILE_WRITE_BLOCK, 024*1024 );
|
||||
init( BACKUP_MANIFEST_WRITE_CHUNK_SIZE, std::numeric_limits<int>::max() ); if( randomize && buggify() ) BACKUP_MANIFEST_WRITE_CHUNK_SIZE = 64;
|
||||
init( BACKUP_MANIFEST_CHUNK_SIZE, std::numeric_limits<int>::max() ); if( randomize && buggify() ) BACKUP_MANIFEST_CHUNK_SIZE = 64;
|
||||
init( BACKUP_CONCURRENT_DELETES, 100 );
|
||||
init( BACKUP_SIMULATED_LIMIT_BYTES, 1e6 ); if( randomize && buggify() ) BACKUP_SIMULATED_LIMIT_BYTES = 1000;
|
||||
init( BACKUP_GET_RANGE_LIMIT_BYTES, 1e6 );
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
#include "fdbclient/ManagementAPI.h"
|
||||
#include "fdbclient/RangeLock.h"
|
||||
#include "PartitionedLogIterator.h"
|
||||
#include "RestoreInterface.h"
|
||||
#include "fdbclient/Status.h"
|
||||
#include "fdbclient/SystemData.h"
|
||||
#include "fdbclient/TaskBucket.h"
|
||||
|
|
@ -171,13 +170,6 @@ Future<bool> verifyBulkDumpDatasetCompleteness(Reference<IBackupContainer> bc, s
|
|||
|
||||
Optional<std::string> fileBackupAgentProxy = Optional<std::string>();
|
||||
|
||||
#define SevFRTestInfo SevVerbose
|
||||
// #define SevFRTestInfo SevInfo
|
||||
|
||||
static std::string boolToYesOrNo(bool val) {
|
||||
return val ? std::string("Yes") : std::string("No");
|
||||
}
|
||||
|
||||
static std::string versionToString(Optional<Version> version) {
|
||||
if (version.present())
|
||||
return std::to_string(version.get());
|
||||
|
|
@ -8430,76 +8422,3 @@ Future<EBackupState> FileBackupAgent::waitBackup(Database cx,
|
|||
Future<Void> FileBackupAgent::changePause(Database db, bool pause) {
|
||||
return FileBackupAgentImpl::changePause(this, db, pause);
|
||||
}
|
||||
|
||||
// Fast Restore addPrefix test helper functions
|
||||
static std::pair<bool, bool> insideValidRange(KeyValueRef kv,
|
||||
Standalone<VectorRef<KeyRangeRef>> restoreRanges,
|
||||
Standalone<VectorRef<KeyRangeRef>> backupRanges) {
|
||||
bool insideRestoreRange = false;
|
||||
bool insideBackupRange = false;
|
||||
for (auto& range : restoreRanges) {
|
||||
TraceEvent(SevFRTestInfo, "InsideValidRestoreRange")
|
||||
.detail("Key", kv.key)
|
||||
.detail("Range", range)
|
||||
.detail("Inside", (kv.key >= range.begin && kv.key < range.end));
|
||||
if (kv.key >= range.begin && kv.key < range.end) {
|
||||
insideRestoreRange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto& range : backupRanges) {
|
||||
TraceEvent(SevFRTestInfo, "InsideValidBackupRange")
|
||||
.detail("Key", kv.key)
|
||||
.detail("Range", range)
|
||||
.detail("Inside", (kv.key >= range.begin && kv.key < range.end));
|
||||
if (kv.key >= range.begin && kv.key < range.end) {
|
||||
insideBackupRange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::make_pair(insideBackupRange, insideRestoreRange);
|
||||
}
|
||||
|
||||
// Write [begin, end) in kvs to DB
|
||||
static Future<Void> writeKVs(Database cx, Standalone<VectorRef<KeyValueRef>> kvs, int begin, int end) {
|
||||
co_await runRYWTransaction(cx, [=](Reference<ReadYourWritesTransaction> tr) -> Future<Void> {
|
||||
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
|
||||
int index = begin;
|
||||
while (index < end) {
|
||||
TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKV")
|
||||
.detail("Index", index)
|
||||
.detail("KVs", kvs.size())
|
||||
.detail("Key", kvs[index].key)
|
||||
.detail("Value", kvs[index].value);
|
||||
tr->set(kvs[index].key, kvs[index].value);
|
||||
++index;
|
||||
}
|
||||
return Void();
|
||||
});
|
||||
|
||||
// Sanity check data has been written to DB
|
||||
ReadYourWritesTransaction tr(cx);
|
||||
while (true) {
|
||||
Error err;
|
||||
try {
|
||||
tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE);
|
||||
KeyRef k1 = kvs[begin].key;
|
||||
KeyRef k2 = end < kvs.size() ? kvs[end].key : allKeys.end;
|
||||
TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKVReadBack")
|
||||
.detail("Range", KeyRangeRef(k1, k2))
|
||||
.detail("Begin", begin)
|
||||
.detail("End", end);
|
||||
RangeResult readKVs = co_await tr.getRange(KeyRangeRef(k1, k2), CLIENT_KNOBS->TOO_MANY);
|
||||
ASSERT(!readKVs.empty() || begin == end);
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
err = e;
|
||||
}
|
||||
TraceEvent("TransformDatabaseContentsWriteKVReadBackError").error(err);
|
||||
co_await tr.onError(err);
|
||||
}
|
||||
|
||||
TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKVDone").detail("Begin", begin).detail("End", end);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
#include "flow/IConnection.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
|
||||
#include <compare>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trim(std::string const& connectionString) {
|
||||
|
|
@ -532,36 +534,44 @@ Future<Void> monitorNominee(Key key,
|
|||
|
||||
// Also used in fdbserver/core/LeaderElection.cpp!
|
||||
// bool represents if the LeaderInfo is a majority answer or not.
|
||||
// This function also masks the first 7 bits of changeId of the nominees and returns the Leader with masked changeId
|
||||
// Group nominees by changeID with the priority bits masked, then return the most common full changeID in the winning
|
||||
// group.
|
||||
Optional<std::pair<LeaderInfo, bool>> getLeader(const std::vector<Optional<LeaderInfo>>& nominees) {
|
||||
// If any coordinator says that the quorum is forwarded, then it is
|
||||
for (int i = 0; i < nominees.size(); i++)
|
||||
if (nominees[i].present() && nominees[i].get().forward)
|
||||
return std::pair<LeaderInfo, bool>(nominees[i].get(), true);
|
||||
|
||||
std::vector<std::pair<UID, int>> maskedNominees;
|
||||
struct MaskedNominee {
|
||||
UID maskedChangeID;
|
||||
UID changeID;
|
||||
int nomineeIndex;
|
||||
|
||||
std::strong_ordering operator<=>(MaskedNominee const&) const = default;
|
||||
};
|
||||
|
||||
std::vector<MaskedNominee> maskedNominees;
|
||||
maskedNominees.reserve(nominees.size());
|
||||
for (int i = 0; i < nominees.size(); i++) {
|
||||
if (nominees[i].present()) {
|
||||
maskedNominees.emplace_back(
|
||||
UID(nominees[i].get().changeID.first() & LeaderInfo::changeIDMask, nominees[i].get().changeID.second()),
|
||||
i);
|
||||
maskedNominees.push_back({ UID(nominees[i].get().changeID.first() & LeaderInfo::changeIDMask,
|
||||
nominees[i].get().changeID.second()),
|
||||
nominees[i].get().changeID,
|
||||
i });
|
||||
}
|
||||
}
|
||||
|
||||
if (maskedNominees.empty())
|
||||
return Optional<std::pair<LeaderInfo, bool>>();
|
||||
|
||||
std::sort(maskedNominees.begin(),
|
||||
maskedNominees.end(),
|
||||
[](const std::pair<UID, int>& l, const std::pair<UID, int>& r) { return l.first < r.first; });
|
||||
std::sort(maskedNominees.begin(), maskedNominees.end());
|
||||
|
||||
int bestCount = 1;
|
||||
int bestIdx = 0;
|
||||
int currentIdx = 0;
|
||||
int curCount = 1;
|
||||
for (int i = 1; i < maskedNominees.size(); i++) {
|
||||
if (maskedNominees[currentIdx].first == maskedNominees[i].first) {
|
||||
if (maskedNominees[currentIdx].maskedChangeID == maskedNominees[i].maskedChangeID) {
|
||||
curCount++;
|
||||
} else {
|
||||
currentIdx = i;
|
||||
|
|
@ -573,8 +583,84 @@ Optional<std::pair<LeaderInfo, bool>> getLeader(const std::vector<Optional<Leade
|
|||
}
|
||||
}
|
||||
|
||||
// Coordinators can agree on the leader identity while reporting different priority bits. Select the modal full
|
||||
// changeID so that one stale coordinator cannot pin the aggregate to an obsolete priority. The sort order and
|
||||
// strict count comparison deterministically prefer the fitter (lower) changeID when variant counts tie.
|
||||
int representativeIdx = maskedNominees[bestIdx].nomineeIndex;
|
||||
int bestVariantCount = 1;
|
||||
currentIdx = bestIdx;
|
||||
curCount = 1;
|
||||
for (int i = bestIdx + 1; i < bestIdx + bestCount; i++) {
|
||||
if (maskedNominees[currentIdx].changeID == maskedNominees[i].changeID) {
|
||||
curCount++;
|
||||
} else {
|
||||
currentIdx = i;
|
||||
curCount = 1;
|
||||
}
|
||||
if (curCount > bestVariantCount) {
|
||||
representativeIdx = maskedNominees[currentIdx].nomineeIndex;
|
||||
bestVariantCount = curCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool majority = bestCount >= nominees.size() / 2 + 1;
|
||||
return std::pair<LeaderInfo, bool>(nominees[maskedNominees[bestIdx].second].get(), majority);
|
||||
return std::pair<LeaderInfo, bool>(nominees[representativeIdx].get(), majority);
|
||||
}
|
||||
|
||||
TEST_CASE("/fdbclient/MonitorLeader/getLeader/priorityVariants") {
|
||||
auto makeLeader = [](UID internalID, ClusterControllerPriorityInfo::DCFitness dcFitness) {
|
||||
LeaderInfo leader(internalID);
|
||||
ClusterControllerPriorityInfo priority;
|
||||
priority.dcFitness = dcFitness;
|
||||
leader.updateChangeID(priority);
|
||||
return leader;
|
||||
};
|
||||
auto assertLeader =
|
||||
[](const std::vector<Optional<LeaderInfo>>& nominees, LeaderInfo const& expected, bool majority) {
|
||||
auto result = getLeader(nominees);
|
||||
ASSERT(result.present());
|
||||
ASSERT(result.get().first.changeID == expected.changeID);
|
||||
ASSERT(result.get().second == majority);
|
||||
};
|
||||
|
||||
UID internalID(1, 2);
|
||||
LeaderInfo primary = makeLeader(internalID, ClusterControllerPriorityInfo::FitnessPrimary);
|
||||
LeaderInfo remote = makeLeader(internalID, ClusterControllerPriorityInfo::FitnessRemote);
|
||||
LeaderInfo unknown = makeLeader(internalID, ClusterControllerPriorityInfo::FitnessUnknown);
|
||||
|
||||
for (int staleIndex = 0; staleIndex < 10; staleIndex++) {
|
||||
std::vector<Optional<LeaderInfo>> nominees(10, remote);
|
||||
nominees[staleIndex] = primary;
|
||||
assertLeader(nominees, remote, true);
|
||||
|
||||
nominees.assign(10, primary);
|
||||
nominees[staleIndex] = remote;
|
||||
assertLeader(nominees, primary, true);
|
||||
}
|
||||
|
||||
std::vector<Optional<LeaderInfo>> nominees(5, primary);
|
||||
nominees.insert(nominees.end(), 5, remote);
|
||||
assertLeader(nominees, primary, true);
|
||||
|
||||
nominees.assign(6, remote);
|
||||
nominees.insert(nominees.end(), 4, primary);
|
||||
assertLeader(nominees, remote, true);
|
||||
|
||||
nominees.assign(5, unknown);
|
||||
nominees.insert(nominees.end(), 5, remote);
|
||||
assertLeader(nominees, remote, true);
|
||||
|
||||
LeaderInfo other = makeLeader(UID(3, 4), ClusterControllerPriorityInfo::FitnessPrimary);
|
||||
nominees.assign(3, primary);
|
||||
nominees.insert(nominees.end(), 3, remote);
|
||||
nominees.insert(nominees.end(), 4, other);
|
||||
assertLeader(nominees, primary, true);
|
||||
|
||||
nominees.assign(5, primary);
|
||||
nominees.insert(nominees.end(), 5, other);
|
||||
assertLeader(nominees, primary, false);
|
||||
|
||||
return Void();
|
||||
}
|
||||
|
||||
// Leader is the process that will be elected by coordinators as the cluster controller
|
||||
|
|
|
|||
|
|
@ -392,6 +392,103 @@ ThreadFuture<VersionVector> DLTransaction::getVersionVector() {
|
|||
return VersionVector(); // not implemented
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
NativeCdcStreamInfo copyNativeCdcStreamInfo(FdbCApi::FDBNativeCdcStreamInfo const& source) {
|
||||
NativeCdcStreamInfo result;
|
||||
result.name = Key(StringRef(source.name.key, source.name.keyLength));
|
||||
result.streamId = source.streamId;
|
||||
result.keys = KeyRange(
|
||||
KeyRangeRef(KeyRef(static_cast<const uint8_t*>(source.keyRange.beginKey), source.keyRange.beginKeyLength),
|
||||
KeyRef(static_cast<const uint8_t*>(source.keyRange.endKey), source.keyRange.endKeyLength)));
|
||||
result.minVersion = source.minVersion;
|
||||
return result;
|
||||
}
|
||||
|
||||
NativeCdcConsumeResult copyNativeCdcConsumeResult(FdbCApi::FDBNativeCdcVersionedMutations const* source,
|
||||
int count,
|
||||
Version lastConsumedVersion) {
|
||||
NativeCdcConsumeResult result;
|
||||
result.cursor.lastConsumedVersion = lastConsumedVersion;
|
||||
result.mutations.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
NativeCdcVersionedMutations versioned;
|
||||
versioned.version = source[i].version;
|
||||
versioned.mutations.reserve(source[i].mutationCount);
|
||||
for (int j = 0; j < source[i].mutationCount; ++j) {
|
||||
auto const& sourceMutation = source[i].mutations[j];
|
||||
NativeCdcMutation mutation;
|
||||
mutation.type = sourceMutation.type;
|
||||
mutation.param1 = Key(StringRef(sourceMutation.param1, sourceMutation.param1Length));
|
||||
mutation.param2 = Value(StringRef(sourceMutation.param2, sourceMutation.param2Length));
|
||||
versioned.mutations.push_back(std::move(mutation));
|
||||
}
|
||||
result.mutations.push_back(std::move(versioned));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class DLNativeCdcConsumer final : public INativeCdcConsumer, ThreadSafeReferenceCounted<DLNativeCdcConsumer> {
|
||||
public:
|
||||
DLNativeCdcConsumer(Reference<FdbCApi> api, FdbCApi::FDBNativeCdcConsumer* consumer)
|
||||
: api(api), consumer(consumer) {}
|
||||
|
||||
~DLNativeCdcConsumer() override {
|
||||
if (consumer && api->nativeCdcConsumerDestroy) {
|
||||
api->nativeCdcConsumerDestroy(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
ThreadFuture<NativeCdcConsumeResult> consume() override {
|
||||
if (!api->nativeCdcConsumerConsume || !api->futureGetNativeCdcVersionedMutations) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
FdbCApi::FDBFuture* f = api->nativeCdcConsumerConsume(consumer);
|
||||
auto self = Reference<DLNativeCdcConsumer>::addRef(this);
|
||||
return toThreadFuture<NativeCdcConsumeResult>(api, f, [self](FdbCApi::FDBFuture* f, FdbCApi* api) {
|
||||
FdbCApi::FDBNativeCdcVersionedMutations const* mutations;
|
||||
int count;
|
||||
int64_t lastConsumedVersion;
|
||||
FdbCApi::fdb_error_t error =
|
||||
api->futureGetNativeCdcVersionedMutations(f, &mutations, &count, &lastConsumedVersion);
|
||||
ASSERT(!error);
|
||||
NativeCdcConsumeResult result = copyNativeCdcConsumeResult(mutations, count, lastConsumedVersion);
|
||||
result.cursor.streamId = self->getPosition().streamId;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Void> acknowledge() override {
|
||||
if (!api->nativeCdcConsumerAcknowledge) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
FdbCApi::FDBFuture* f = api->nativeCdcConsumerAcknowledge(consumer);
|
||||
auto self = Reference<DLNativeCdcConsumer>::addRef(this);
|
||||
return toThreadFuture<Void>(api, f, [self](FdbCApi::FDBFuture*, FdbCApi*) {
|
||||
(void)self;
|
||||
return Void();
|
||||
});
|
||||
}
|
||||
|
||||
NativeCdcCursor getPosition() override {
|
||||
if (!api->nativeCdcConsumerGetPosition) {
|
||||
throw unsupported_operation();
|
||||
}
|
||||
NativeCdcCursor position;
|
||||
throwIfError(api->nativeCdcConsumerGetPosition(consumer, &position.streamId, &position.lastConsumedVersion));
|
||||
return position;
|
||||
}
|
||||
|
||||
void addref() override { ThreadSafeReferenceCounted<DLNativeCdcConsumer>::addref(); }
|
||||
void delref() override { ThreadSafeReferenceCounted<DLNativeCdcConsumer>::delref(); }
|
||||
|
||||
private:
|
||||
const Reference<FdbCApi> api;
|
||||
FdbCApi::FDBNativeCdcConsumer* const consumer;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// DLDatabase
|
||||
DLDatabase::DLDatabase(Reference<FdbCApi> api, ThreadFuture<FdbCApi::FDBDatabase*> dbFuture) : api(api), db(nullptr) {
|
||||
addref();
|
||||
|
|
@ -457,6 +554,80 @@ ThreadFuture<Void> DLDatabase::createSnapshot(const StringRef& uid, const String
|
|||
return toThreadFuture<Void>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { return Void(); });
|
||||
}
|
||||
|
||||
ThreadFuture<CDCStreamId> DLDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) {
|
||||
if (!api->databaseRegisterNativeCdcStream) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
|
||||
FdbCApi::FDBFuture* f = api->databaseRegisterNativeCdcStream(
|
||||
db, name.begin(), name.size(), keys.begin.begin(), keys.begin.size(), keys.end.begin(), keys.end.size());
|
||||
return toThreadFuture<CDCStreamId>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) {
|
||||
uint64_t streamId;
|
||||
FdbCApi::fdb_error_t error = api->futureGetUInt64(f, &streamId);
|
||||
ASSERT(!error);
|
||||
return streamId;
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Void> DLDatabase::removeNativeCdcStream(const KeyRef& name) {
|
||||
if (!api->databaseRemoveNativeCdcStream) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
|
||||
FdbCApi::FDBFuture* f = api->databaseRemoveNativeCdcStream(db, name.begin(), name.size());
|
||||
return toThreadFuture<Void>(api, f, [](FdbCApi::FDBFuture*, FdbCApi*) { return Void(); });
|
||||
}
|
||||
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> DLDatabase::listNativeCdcStreams() {
|
||||
if (!api->databaseListNativeCdcStreams || !api->futureGetNativeCdcStreamInfoArray) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
|
||||
FdbCApi::FDBFuture* f = api->databaseListNativeCdcStreams(db);
|
||||
return toThreadFuture<std::vector<NativeCdcStreamInfo>>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) {
|
||||
FdbCApi::FDBNativeCdcStreamInfo const* streams;
|
||||
int count;
|
||||
FdbCApi::fdb_error_t error = api->futureGetNativeCdcStreamInfoArray(f, &streams, &count);
|
||||
ASSERT(!error);
|
||||
std::vector<NativeCdcStreamInfo> result;
|
||||
result.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
result.push_back(copyNativeCdcStreamInfo(streams[i]));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> DLDatabase::createNativeCdcConsumer(const KeyRef& name) {
|
||||
if (!api->databaseCreateNativeCdcConsumer || !api->futureGetNativeCdcConsumer) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
|
||||
FdbCApi::FDBFuture* f = api->databaseCreateNativeCdcConsumer(db, name.begin(), name.size());
|
||||
return toThreadFuture<Reference<INativeCdcConsumer>>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) {
|
||||
FdbCApi::FDBNativeCdcConsumer* consumer;
|
||||
FdbCApi::fdb_error_t error = api->futureGetNativeCdcConsumer(f, &consumer);
|
||||
ASSERT(!error);
|
||||
return Reference<INativeCdcConsumer>(
|
||||
makeReference<DLNativeCdcConsumer>(Reference<FdbCApi>::addRef(api), consumer));
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> DLDatabase::resumeNativeCdcConsumer(const NativeCdcCursor& cursor) {
|
||||
if (!api->databaseResumeNativeCdcConsumer || !api->futureGetNativeCdcConsumer) {
|
||||
return unsupported_operation();
|
||||
}
|
||||
|
||||
FdbCApi::FDBFuture* f = api->databaseResumeNativeCdcConsumer(db, cursor.streamId, cursor.lastConsumedVersion);
|
||||
return toThreadFuture<Reference<INativeCdcConsumer>>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) {
|
||||
FdbCApi::FDBNativeCdcConsumer* consumer;
|
||||
FdbCApi::fdb_error_t error = api->futureGetNativeCdcConsumer(f, &consumer);
|
||||
ASSERT(!error);
|
||||
return Reference<INativeCdcConsumer>(
|
||||
makeReference<DLNativeCdcConsumer>(Reference<FdbCApi>::addRef(api), consumer));
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> DLDatabase::createSharedState() {
|
||||
if (!api->databaseCreateSharedState) {
|
||||
return unsupported_operation();
|
||||
|
|
@ -605,6 +776,51 @@ void DLApi::init() {
|
|||
fdbCPath,
|
||||
"fdb_database_get_client_status",
|
||||
headerVersion >= ApiVersion::withGetClientStatus().version());
|
||||
loadClientFunction(&api->databaseRegisterNativeCdcStream,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_database_register_cdc_stream",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->databaseRemoveNativeCdcStream,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_database_remove_cdc_stream",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->databaseListNativeCdcStreams,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_database_list_cdc_streams",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->databaseCreateNativeCdcConsumer,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_database_create_cdc_consumer",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->databaseResumeNativeCdcConsumer,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_database_resume_cdc_consumer",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->nativeCdcConsumerDestroy,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_cdc_consumer_destroy",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->nativeCdcConsumerConsume,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_cdc_consumer_consume",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->nativeCdcConsumerAcknowledge,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_cdc_consumer_acknowledge",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->nativeCdcConsumerGetPosition,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_cdc_consumer_get_position",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
|
||||
loadClientFunction(&api->transactionSetOption, lib, fdbCPath, "fdb_transaction_set_option", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionDestroy, lib, fdbCPath, "fdb_transaction_destroy", headerVersion >= 0);
|
||||
|
|
@ -698,6 +914,21 @@ void DLApi::init() {
|
|||
&api->futureGetKeyValueArray, lib, fdbCPath, "fdb_future_get_keyvalue_array", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->futureGetMappedKeyValueArray, lib, fdbCPath, "fdb_future_get_mappedkeyvalue_array", headerVersion >= 710);
|
||||
loadClientFunction(&api->futureGetNativeCdcStreamInfoArray,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_future_get_cdc_stream_info_array",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->futureGetNativeCdcConsumer,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_future_get_cdc_consumer",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->futureGetNativeCdcVersionedMutations,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_future_get_cdc_versioned_mutations",
|
||||
headerVersion >= ApiVersion::withNativeCdcApi().version());
|
||||
loadClientFunction(&api->futureGetSharedState, lib, fdbCPath, "fdb_future_get_shared_state", headerVersion >= 710);
|
||||
loadClientFunction(&api->futureSetCallback, lib, fdbCPath, "fdb_future_set_callback", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureCancel, lib, fdbCPath, "fdb_future_cancel", headerVersion >= 0);
|
||||
|
|
@ -1448,6 +1679,27 @@ ThreadFuture<Void> MultiVersionDatabase::createSnapshot(const StringRef& uid, co
|
|||
return executeOperation(&IDatabase::createSnapshot, uid, snapshot_command);
|
||||
}
|
||||
|
||||
ThreadFuture<CDCStreamId> MultiVersionDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) {
|
||||
return executeOperation(&IDatabase::registerNativeCdcStream, name, keys);
|
||||
}
|
||||
|
||||
ThreadFuture<Void> MultiVersionDatabase::removeNativeCdcStream(const KeyRef& name) {
|
||||
return executeOperation(&IDatabase::removeNativeCdcStream, name);
|
||||
}
|
||||
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> MultiVersionDatabase::listNativeCdcStreams() {
|
||||
return executeOperation(&IDatabase::listNativeCdcStreams);
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> MultiVersionDatabase::createNativeCdcConsumer(const KeyRef& name) {
|
||||
return executeOperation(&IDatabase::createNativeCdcConsumer, name);
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> MultiVersionDatabase::resumeNativeCdcConsumer(
|
||||
const NativeCdcCursor& cursor) {
|
||||
return executeOperation(&IDatabase::resumeNativeCdcConsumer, cursor);
|
||||
}
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> MultiVersionDatabase::createSharedState() {
|
||||
return executeOperation(&IDatabase::createSharedState);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,14 +300,6 @@ int64_t extractIntOption(Optional<StringRef> value, int64_t minValue, int64_t ma
|
|||
return passed;
|
||||
}
|
||||
|
||||
uint64_t extractHexOption(StringRef value) {
|
||||
char* end;
|
||||
uint64_t id = strtoull(value.toString().c_str(), &end, 16);
|
||||
if (*end)
|
||||
throw invalid_option_value();
|
||||
return id;
|
||||
}
|
||||
|
||||
void DatabaseContext::setOption(FDBDatabaseOptions::Option option, Optional<StringRef> value) {
|
||||
int defaultFor = FDBDatabaseOptions::optionInfo.getMustExist(option).defaultFor;
|
||||
if (defaultFor >= 0) {
|
||||
|
|
|
|||
|
|
@ -394,10 +394,12 @@ Future<CDCStreamId> registerNativeCdcStream(Database cx, Key name, KeyRange keys
|
|||
|
||||
// Disabling CDC stops new admission, but existing registrations and
|
||||
// owner repair must remain available so durable streams can drain.
|
||||
validateNativeCdcEnabled(cx->clientInfo->get().nativeCdcEnabled);
|
||||
const bool nativeCdcEnabled = cx->clientInfo->get().nativeCdcEnabled;
|
||||
const int nativeCdcTagCount = cx->clientInfo->get().nativeCdcTagCount;
|
||||
validateNativeCdcEnabled(nativeCdcEnabled);
|
||||
NativeCdcIdentifierAllocator allocator;
|
||||
co_await observeNativeCdcMetadata(&tr, &allocator);
|
||||
const auto [streamId, tag] = allocator.allocate(cx->clientInfo->get().nativeCdcTagCount);
|
||||
const auto [streamId, tag] = allocator.allocate(nativeCdcTagCount);
|
||||
// The read version is a conservative lower bound for tag routing.
|
||||
// The versionstamped minimum below is the commit version, and stream
|
||||
// initialization takes their maximum before exposing mutations.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/RYWIterator.h"
|
||||
#include "RYWIterator.h"
|
||||
#include "fdbclient/RandomTestUtils.h"
|
||||
#include "fdbclient/KeyRangeMap.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* RYWIterator.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2026 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.
|
||||
*/
|
||||
|
||||
#ifndef FDBCLIENT_RYWITERATOR_H
|
||||
#define FDBCLIENT_RYWITERATOR_H
|
||||
#pragma once
|
||||
|
||||
#include "SnapshotCache.h"
|
||||
#include "WriteMap.h"
|
||||
|
||||
class RYWIterator {
|
||||
public:
|
||||
RYWIterator(SnapshotCache* snapshotCache, WriteMap* writeMap)
|
||||
: begin_key_cmp(0), end_key_cmp(0), cache(snapshotCache), writes(writeMap), bypassUnreadable(false) {}
|
||||
|
||||
enum SEGMENT_TYPE { UNKNOWN_RANGE, EMPTY_RANGE, KV };
|
||||
static const SEGMENT_TYPE typeMap[12];
|
||||
|
||||
SEGMENT_TYPE type() const;
|
||||
|
||||
bool is_kv() const;
|
||||
bool is_unknown_range() const;
|
||||
bool is_empty_range() const;
|
||||
bool is_unreadable() const;
|
||||
bool is_dependent() const;
|
||||
|
||||
ExtStringRef beginKey();
|
||||
ExtStringRef endKey();
|
||||
|
||||
virtual const KeyValueRef* kv(Arena& arena);
|
||||
|
||||
RYWIterator& operator++();
|
||||
|
||||
RYWIterator& operator--();
|
||||
|
||||
bool operator==(const RYWIterator& r) const;
|
||||
bool operator!=(const RYWIterator& r) const;
|
||||
|
||||
void skip(KeyRef key);
|
||||
|
||||
void skipContiguous(KeyRef key);
|
||||
|
||||
void skipContiguousBack(KeyRef key);
|
||||
|
||||
void bypassUnreadableProtection() { bypassUnreadable = true; }
|
||||
|
||||
// Really this should return an iterator by value, but for performance it's convenient to actually grab the internal
|
||||
// one. Consider copying the return value if performance isn't critical. If you modify the returned iterator, it
|
||||
// invalidates this iterator until the next call to skip()
|
||||
virtual WriteMap::iterator& extractWriteMapIterator();
|
||||
|
||||
void dbg();
|
||||
|
||||
protected:
|
||||
int begin_key_cmp; // -1 if cache.beginKey() < writes.beginKey(), 0 if ==, +1 if >
|
||||
int end_key_cmp; //
|
||||
SnapshotCache::iterator cache;
|
||||
WriteMap::iterator writes;
|
||||
KeyValueRef temp;
|
||||
bool bypassUnreadable; // When set, allows read from sections of keyspace that have become unreadable because of
|
||||
// versionstamp operations
|
||||
|
||||
void updateCmp();
|
||||
};
|
||||
|
||||
void testESR();
|
||||
void testSnapshotCache();
|
||||
|
||||
#endif
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "fdbclient/ReadYourWrites.h"
|
||||
#include "RYWIterator.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "fdbclient/Atomic.h"
|
||||
#include "fdbclient/DatabaseContext.h"
|
||||
|
|
@ -28,6 +29,13 @@
|
|||
#include "flow/CoroUtils.h"
|
||||
#include "flow/Util.h"
|
||||
|
||||
struct ReadYourWritesTransaction::RYWState {
|
||||
explicit RYWState(Arena* arena) : cache(arena), writes(arena) {}
|
||||
|
||||
SnapshotCache cache;
|
||||
WriteMap writes;
|
||||
};
|
||||
|
||||
class RYWImpl {
|
||||
public:
|
||||
template <class Iter>
|
||||
|
|
@ -114,12 +122,12 @@ public:
|
|||
KeyRef k(ryw->arena, read.key);
|
||||
|
||||
if (res.present()) {
|
||||
if (ryw->cache.insert(k, res.get()))
|
||||
if (ryw->rywState->cache.insert(k, res.get()))
|
||||
ryw->arena.dependsOn(res.get().arena());
|
||||
if (!dependent)
|
||||
co_return res;
|
||||
} else {
|
||||
ryw->cache.insert(k, Optional<ValueRef>());
|
||||
ryw->rywState->cache.insert(k, Optional<ValueRef>());
|
||||
if (!dependent)
|
||||
co_return Optional<Value>();
|
||||
}
|
||||
|
|
@ -240,7 +248,7 @@ public:
|
|||
}
|
||||
|
||||
it.skip(readRange.begin);
|
||||
ryw->updateConflictMap(readRange, it);
|
||||
updateConflictMap(ryw, readRange, it);
|
||||
}
|
||||
|
||||
template <bool mustUnmodified = false, class RangeResultFamily = RangeResult>
|
||||
|
|
@ -360,7 +368,7 @@ public:
|
|||
|
||||
template <class Req>
|
||||
static Future<typename Req::Result> readWithConflictRangeSnapshot(ReadYourWritesTransaction* ryw, Req req) {
|
||||
SnapshotCache::iterator it(&ryw->cache, &ryw->writes);
|
||||
SnapshotCache::iterator it(&ryw->rywState->cache, &ryw->rywState->writes);
|
||||
co_return co_await waitOrError(read(ryw, req, &it), ryw->resetPromise.getFuture());
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +376,7 @@ public:
|
|||
static Future<typename Req::Result> readWithConflictRangeRYW(ReadYourWritesTransaction* ryw,
|
||||
Req req,
|
||||
Snapshot snapshot) {
|
||||
RYWIterator it(&ryw->cache, &ryw->writes);
|
||||
RYWIterator it(&ryw->rywState->cache, &ryw->rywState->writes);
|
||||
auto result = co_await waitOrError(read(ryw, req, &it), ryw->resetPromise.getFuture());
|
||||
|
||||
// Some overloads of addConflictRange() require it to point to the "right" key and others don't. The
|
||||
|
|
@ -747,7 +755,7 @@ public:
|
|||
|
||||
//TraceEvent("RYWCacheInsert", randomID).detail("Range", range).detail("ExpectedSize", snapshot_read.expectedSize()).detail("Rows", snapshot_read.size()).detail("Results", snapshot_read).detail("More", snapshot_read.more).detail("ReadToBegin", snapshot_read.readToBegin).detail("ReadThroughEnd", snapshot_read.readThroughEnd).detail("ReadThrough", snapshot_read.readThrough);
|
||||
|
||||
if (ryw->cache.insert(range, snapshot_read))
|
||||
if (ryw->rywState->cache.insert(range, snapshot_read))
|
||||
ryw->arena.dependsOn(snapshot_read.arena());
|
||||
|
||||
// TODO: Is there a more efficient way to deal with invalidation?
|
||||
|
|
@ -1061,7 +1069,7 @@ public:
|
|||
reversed[snapshot_read.size() - i - 1] = snapshot_read[i];
|
||||
}
|
||||
|
||||
if (ryw->cache.insert(range, reversed))
|
||||
if (ryw->rywState->cache.insert(range, reversed))
|
||||
ryw->arena.dependsOn(snapshot_read.arena());
|
||||
|
||||
// TODO: Is there a more efficient way to deal with invalidation?
|
||||
|
|
@ -1191,7 +1199,7 @@ public:
|
|||
// Insert read conflicts (so that it supported Snapshot::True) and check it is not modified (so it masks
|
||||
// sure not break RYW semantic while not implementing RYW) for both the primary getRange and all
|
||||
// underlying getValue/getRanges.
|
||||
WriteMap::iterator writes(&ryw->writes);
|
||||
WriteMap::iterator writes(&ryw->rywState->writes);
|
||||
addConflictRangeAndMustUnmodified<backwards>(ryw, req, writes, result);
|
||||
co_return result;
|
||||
}
|
||||
|
|
@ -1529,9 +1537,11 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
ReadYourWritesTransaction::ReadYourWritesTransaction() : rywState(std::make_unique<RYWState>(&arena)) {}
|
||||
|
||||
ReadYourWritesTransaction::ReadYourWritesTransaction(Database const& cx)
|
||||
: deferredError(cx->deferredError), tr(cx), cache(&arena), writes(&arena), retries(0), approximateSize(0),
|
||||
creationTime(now()), commitStarted(false), versionStampFuture(tr.getVersionstamp()),
|
||||
: deferredError(cx->deferredError), tr(cx), rywState(std::make_unique<RYWState>(&arena)), retries(0),
|
||||
approximateSize(0), creationTime(now()), commitStarted(false), versionStampFuture(tr.getVersionstamp()),
|
||||
specialKeySpaceWriteMap(std::make_pair(false, Optional<Value>()), specialKeys.end), options(tr) {
|
||||
std::copy(
|
||||
cx.getTransactionDefaults().begin(), cx.getTransactionDefaults().end(), std::back_inserter(persistentOptions));
|
||||
|
|
@ -1889,22 +1899,14 @@ void ReadYourWritesTransaction::addReadConflictRange(KeyRangeRef const& keys) {
|
|||
return;
|
||||
}
|
||||
|
||||
WriteMap::iterator it(&writes);
|
||||
WriteMap::iterator it(&rywState->writes);
|
||||
KeyRangeRef readRange(arena, r);
|
||||
it.skip(readRange.begin);
|
||||
updateConflictMap(readRange, it);
|
||||
}
|
||||
|
||||
void ReadYourWritesTransaction::updateConflictMap(KeyRef const& key, WriteMap::iterator& it) {
|
||||
RYWImpl::updateConflictMap(this, key, it);
|
||||
}
|
||||
|
||||
void ReadYourWritesTransaction::updateConflictMap(KeyRangeRef const& keys, WriteMap::iterator& it) {
|
||||
RYWImpl::updateConflictMap(this, keys, it);
|
||||
RYWImpl::updateConflictMap(this, readRange, it);
|
||||
}
|
||||
|
||||
void ReadYourWritesTransaction::writeRangeToNativeTransaction(KeyRangeRef const& keys) {
|
||||
WriteMap::iterator it(&writes);
|
||||
WriteMap::iterator it(&rywState->writes);
|
||||
it.skip(keys.begin);
|
||||
|
||||
bool inClearRange = false;
|
||||
|
|
@ -1998,7 +2000,7 @@ bool ReadYourWritesTransactionOptions::getAndResetWriteConflictDisabled() {
|
|||
}
|
||||
|
||||
void ReadYourWritesTransaction::getWriteConflicts(KeyRangeMap<bool>* result) {
|
||||
WriteMap::iterator it(&writes);
|
||||
WriteMap::iterator it(&rywState->writes);
|
||||
it.skip(allKeys.begin);
|
||||
|
||||
bool inConflictRange = false;
|
||||
|
|
@ -2078,7 +2080,7 @@ RangeResult ReadYourWritesTransaction::getWriteConflictRangeIntersecting(KeyRang
|
|||
|
||||
if (!options.readYourWritesDisabled) {
|
||||
KeyRangeRef strippedWriteRangePrefix = kr.removePrefix(writeConflictRangeKeysRange.begin);
|
||||
WriteMap::iterator it(&writes);
|
||||
WriteMap::iterator it(&rywState->writes);
|
||||
it.skip(strippedWriteRangePrefix.begin);
|
||||
if (it.beginKey() > allKeys.begin)
|
||||
--it;
|
||||
|
|
@ -2179,7 +2181,7 @@ void ReadYourWritesTransaction::atomicOp(const KeyRef& key, const ValueRef& oper
|
|||
addWriteConflict = AddConflictRange::False;
|
||||
if (!options.readYourWritesDisabled) {
|
||||
writeRangeToNativeTransaction(range);
|
||||
writes.addUnmodifiedAndUnreadableRange(range);
|
||||
rywState->writes.addUnmodifiedAndUnreadableRange(range);
|
||||
}
|
||||
// k is the unversionstamped key provided by the user. If we've filled in a minimum bound
|
||||
// for the versionstamp, we need to make sure that's reflected when we insert it into the
|
||||
|
|
@ -2203,7 +2205,7 @@ void ReadYourWritesTransaction::atomicOp(const KeyRef& key, const ValueRef& oper
|
|||
return tr.atomicOp(k, v, (MutationRef::Type)operationType, addWriteConflict);
|
||||
}
|
||||
|
||||
writes.mutate(k, (MutationRef::Type)operationType, v, addWriteConflict);
|
||||
rywState->writes.mutate(k, (MutationRef::Type)operationType, v, addWriteConflict);
|
||||
RYWImpl::triggerWatches(this, k, Optional<ValueRef>(), false);
|
||||
}
|
||||
|
||||
|
|
@ -2261,7 +2263,7 @@ void ReadYourWritesTransaction::set(const KeyRef& key, const ValueRef& value) {
|
|||
KeyRef k = KeyRef(arena, key);
|
||||
ValueRef v = ValueRef(arena, value);
|
||||
|
||||
writes.mutate(k, MutationRef::SetValue, v, addWriteConflict);
|
||||
rywState->writes.mutate(k, MutationRef::SetValue, v, addWriteConflict);
|
||||
RYWImpl::triggerWatches(this, key, value);
|
||||
}
|
||||
|
||||
|
|
@ -2310,7 +2312,7 @@ void ReadYourWritesTransaction::clear(const KeyRangeRef& range) {
|
|||
|
||||
r = KeyRangeRef(arena, r);
|
||||
|
||||
writes.clear(r, addWriteConflict);
|
||||
rywState->writes.clear(r, addWriteConflict);
|
||||
RYWImpl::triggerWatches(this, r, Optional<ValueRef>());
|
||||
}
|
||||
|
||||
|
|
@ -2343,7 +2345,7 @@ void ReadYourWritesTransaction::clear(const KeyRef& key) {
|
|||
r.expectedSize() + sizeof(KeyRangeRef) + (addWriteConflict ? sizeof(KeyRangeRef) + r.expectedSize() : 0);
|
||||
|
||||
// SOMEDAY: add an optimized single key clear to write map
|
||||
writes.clear(r, addWriteConflict);
|
||||
rywState->writes.clear(r, addWriteConflict);
|
||||
|
||||
RYWImpl::triggerWatches(this, r, Optional<ValueRef>());
|
||||
}
|
||||
|
|
@ -2407,7 +2409,7 @@ void ReadYourWritesTransaction::addWriteConflictRange(KeyRangeRef const& keys) {
|
|||
}
|
||||
|
||||
r = KeyRangeRef(arena, r);
|
||||
writes.addConflictRange(r);
|
||||
rywState->writes.addConflictRange(r);
|
||||
}
|
||||
|
||||
Future<Void> ReadYourWritesTransaction::commit() {
|
||||
|
|
@ -2448,7 +2450,7 @@ void ReadYourWritesTransaction::setOptionImpl(FDBTransactionOptions::Option opti
|
|||
case FDBTransactionOptions::READ_YOUR_WRITES_DISABLE:
|
||||
validateOptionValueNotPresent(value);
|
||||
|
||||
if (reading.getFutureCount() > 0 || !cache.empty() || !writes.empty())
|
||||
if (reading.getFutureCount() > 0 || !rywState->cache.empty() || !rywState->writes.empty())
|
||||
throw client_invalid_operation();
|
||||
|
||||
options.readYourWritesDisabled = true;
|
||||
|
|
@ -2534,8 +2536,7 @@ void ReadYourWritesTransaction::setOptionImpl(FDBTransactionOptions::Option opti
|
|||
}
|
||||
|
||||
void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcept {
|
||||
cache = std::move(r.cache);
|
||||
writes = std::move(r.writes);
|
||||
rywState = std::move(r.rywState);
|
||||
arena = std::move(r.arena);
|
||||
tr = std::move(r.tr);
|
||||
readConflicts = std::move(r.readConflicts);
|
||||
|
|
@ -2551,8 +2552,8 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep
|
|||
commitStarted = r.commitStarted;
|
||||
options = r.options;
|
||||
transactionDebugInfo = r.transactionDebugInfo;
|
||||
cache.arena = &arena;
|
||||
writes.arena = &arena;
|
||||
rywState->cache.arena = &arena;
|
||||
rywState->writes.arena = &arena;
|
||||
persistentOptions = std::move(r.persistentOptions);
|
||||
sensitivePersistentOptions = std::move(r.sensitivePersistentOptions);
|
||||
nativeReadRanges = std::move(r.nativeReadRanges);
|
||||
|
|
@ -2564,12 +2565,12 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep
|
|||
}
|
||||
|
||||
ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept
|
||||
: deferredError(r.deferredError), arena(std::move(r.arena)), cache(std::move(r.cache)), writes(std::move(r.writes)),
|
||||
: deferredError(r.deferredError), arena(std::move(r.arena)), rywState(std::move(r.rywState)),
|
||||
resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), retries(r.retries),
|
||||
approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), creationTime(r.creationTime),
|
||||
commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), options(r.options) {
|
||||
cache.arena = &arena;
|
||||
writes.arena = &arena;
|
||||
rywState->cache.arena = &arena;
|
||||
rywState->writes.arena = &arena;
|
||||
tr = std::move(r.tr);
|
||||
readConflicts = std::move(r.readConflicts);
|
||||
watchMap = std::move(r.watchMap);
|
||||
|
|
@ -2615,8 +2616,8 @@ void ReadYourWritesTransaction::resetRyow() {
|
|||
|
||||
timeoutActor.cancel();
|
||||
arena = Arena();
|
||||
cache = SnapshotCache(&arena);
|
||||
writes = WriteMap(&arena);
|
||||
rywState->cache = SnapshotCache(&arena);
|
||||
rywState->writes = WriteMap(&arena);
|
||||
readConflicts = CoalescedKeyRefRangeMap<bool>();
|
||||
versionStampKeys = VectorRef<KeyRef>();
|
||||
nativeReadRanges = Standalone<VectorRef<KeyRangeRef>>();
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* RestoreInterface.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2026 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.
|
||||
*/
|
||||
|
||||
#include "RestoreInterface.h"
|
||||
#include "flow/serialize.h"
|
||||
|
||||
const KeyRef restoreRequestDoneKey = "\xff\x02/restoreRequestDone"_sr;
|
||||
const KeyRef restoreRequestTriggerKey = "\xff\x02/restoreRequestTrigger"_sr;
|
||||
const KeyRangeRef restoreRequestKeys("\xff\x02/restoreRequests/"_sr, "\xff\x02/restoreRequests0"_sr);
|
||||
|
||||
// Encode and decode restore request value
|
||||
Value restoreRequestTriggerValue(UID randomID, int numRequests) {
|
||||
BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestTriggerValue()));
|
||||
wr << numRequests;
|
||||
wr << randomID;
|
||||
return wr.toValue();
|
||||
}
|
||||
|
||||
int decodeRestoreRequestTriggerValue(ValueRef const& value) {
|
||||
int s;
|
||||
UID randomID;
|
||||
BinaryReader reader(value, IncludeVersion());
|
||||
reader >> s;
|
||||
reader >> randomID;
|
||||
return s;
|
||||
}
|
||||
|
||||
Key restoreRequestKeyFor(int index) {
|
||||
BinaryWriter wr(Unversioned());
|
||||
wr.serializeBytes(restoreRequestKeys.begin);
|
||||
wr << index;
|
||||
return wr.toValue();
|
||||
}
|
||||
|
||||
Value restoreRequestValue(RestoreRequest const& request) {
|
||||
BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestValue()));
|
||||
wr << request;
|
||||
return wr.toValue();
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* RestoreInterface.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2026 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbrpc/fdbrpc.h"
|
||||
|
||||
struct RestoreCommonReply {
|
||||
constexpr static FileIdentifier file_identifier = 5808787;
|
||||
UID id; // unique ID of the server who sends the reply
|
||||
bool isDuplicated;
|
||||
|
||||
RestoreCommonReply() = default;
|
||||
explicit RestoreCommonReply(UID id, bool isDuplicated = false) : id(id), isDuplicated(isDuplicated) {}
|
||||
|
||||
std::string toString() const {
|
||||
std::stringstream ss;
|
||||
ss << "ServerNodeID:" << id.toString() << " isDuplicated:" << isDuplicated;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, id, isDuplicated);
|
||||
}
|
||||
};
|
||||
|
||||
struct RestoreRequest {
|
||||
constexpr static FileIdentifier file_identifier = 16035338;
|
||||
|
||||
int index;
|
||||
Key tagName;
|
||||
Key url;
|
||||
Optional<std::string> proxy;
|
||||
Version targetVersion;
|
||||
KeyRange range;
|
||||
UID randomUid;
|
||||
|
||||
// Every key in backup will first removePrefix and then addPrefix;
|
||||
// Simulation testing does not cover when both addPrefix and removePrefix exist yet.
|
||||
Key addPrefix;
|
||||
Key removePrefix;
|
||||
|
||||
ReplyPromise<struct RestoreCommonReply> reply;
|
||||
|
||||
RestoreRequest() = default;
|
||||
explicit RestoreRequest(const int index,
|
||||
const Key& tagName,
|
||||
const Key& url,
|
||||
const Optional<std::string>& proxy,
|
||||
Version targetVersion,
|
||||
const KeyRange& range,
|
||||
const UID& randomUid,
|
||||
Key& addPrefix,
|
||||
Key removePrefix)
|
||||
: index(index), tagName(tagName), url(url), proxy(proxy), targetVersion(targetVersion), range(range),
|
||||
randomUid(randomUid), addPrefix(addPrefix), removePrefix(removePrefix) {}
|
||||
|
||||
// To change this serialization, ProtocolVersion::RestoreRequestValue must be updated, and downgrades need to be
|
||||
// considered
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, index, tagName, url, proxy, targetVersion, range, randomUid, addPrefix, removePrefix, reply);
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
std::stringstream ss;
|
||||
ss << "index:" << std::to_string(index) << " tagName:" << tagName.contents().toString()
|
||||
<< " url:" << url.contents().toString() << " proxy:" << (proxy.present() ? proxy.get() : "")
|
||||
<< " targetVersion:" << std::to_string(targetVersion) << " range:" << range.toString()
|
||||
<< " randomUid:" << randomUid.toString() << " addPrefix:" << addPrefix.toString()
|
||||
<< " removePrefix:" << removePrefix.toString();
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
extern const KeyRef restoreRequestDoneKey;
|
||||
extern const KeyRef restoreRequestTriggerKey;
|
||||
extern const KeyRangeRef restoreRequestKeys;
|
||||
|
||||
Value restoreRequestTriggerValue(UID randomID, int numRequests);
|
||||
int decodeRequestRequestTriggerValue(ValueRef const&);
|
||||
Key restoreRequestKeyFor(int index);
|
||||
Value restoreRequestValue(RestoreRequest const&);
|
||||
|
|
@ -18,6 +18,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "fdbclient/ClusterConnectionFile.h"
|
||||
#include "fdbclient/ClusterConnectionMemoryRecord.h"
|
||||
#include "fdbclient/CoordinationInterface.h"
|
||||
|
|
@ -25,6 +28,7 @@
|
|||
#include "fdbclient/DatabaseContext.h"
|
||||
#include "fdbclient/versions.h"
|
||||
#include "fdbclient/GenericManagementAPI.h"
|
||||
#include "fdbclient/NativeCdc.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/ProtocolVersion.h"
|
||||
|
|
@ -33,6 +37,120 @@
|
|||
// call addRef (e.g. C API follows this). Therefore, it is unsafe to call (explicitly or implicitly) this->addRef in any
|
||||
// of these functions.
|
||||
|
||||
namespace {
|
||||
|
||||
struct NativeCdcCursorState {
|
||||
explicit NativeCdcCursorState(NativeCdcCursor cursor) : cursor(cursor) {}
|
||||
|
||||
ThreadSpinLock lock;
|
||||
NativeCdcCursor cursor;
|
||||
};
|
||||
|
||||
NativeCdcCursor toClientCursor(CDCCursor const& cursor) {
|
||||
return NativeCdcCursor{ cursor.streamId, cursor.lastConsumedVersion };
|
||||
}
|
||||
|
||||
CDCCursor toNativeCursor(NativeCdcCursor const& cursor) {
|
||||
return CDCCursor(cursor.streamId, cursor.lastConsumedVersion);
|
||||
}
|
||||
|
||||
void updateCursor(std::shared_ptr<NativeCdcCursorState> const& state, NativeCdcCursor cursor) {
|
||||
ThreadSpinLockHolder holder(state->lock);
|
||||
state->cursor = cursor;
|
||||
}
|
||||
|
||||
NativeCdcCursor readCursor(std::shared_ptr<NativeCdcCursorState> const& state) {
|
||||
ThreadSpinLockHolder holder(state->lock);
|
||||
return state->cursor;
|
||||
}
|
||||
|
||||
NativeCdcConsumeResult copyNativeCdcConsumeResult(CDCConsumeReply const& reply, NativeCdcCursor cursor) {
|
||||
NativeCdcConsumeResult result;
|
||||
result.cursor = cursor;
|
||||
result.mutations.reserve(reply.mutations.size());
|
||||
for (auto const& versioned : reply.mutations) {
|
||||
NativeCdcVersionedMutations copiedVersion;
|
||||
copiedVersion.version = versioned.version;
|
||||
copiedVersion.mutations.reserve(versioned.mutations.size());
|
||||
for (auto const& mutation : versioned.mutations) {
|
||||
copiedVersion.mutations.push_back(
|
||||
NativeCdcMutation{ mutation.type, Key(mutation.param1), Value(mutation.param2) });
|
||||
}
|
||||
result.mutations.push_back(std::move(copiedVersion));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<NativeCdcConsumeResult> consumeNativeCdc(Reference<NativeCdcConsumer> consumer,
|
||||
std::shared_ptr<NativeCdcCursorState> cursorState) {
|
||||
try {
|
||||
CDCConsumeReply reply = co_await consumer->consume();
|
||||
NativeCdcCursor cursor = toClientCursor(consumer->position());
|
||||
updateCursor(cursorState, cursor);
|
||||
co_return copyNativeCdcConsumeResult(reply, cursor);
|
||||
} catch (Error&) {
|
||||
updateCursor(cursorState, toClientCursor(consumer->position()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Void> acknowledgeNativeCdc(Reference<NativeCdcConsumer> consumer,
|
||||
std::shared_ptr<NativeCdcCursorState> cursorState) {
|
||||
try {
|
||||
co_await consumer->acknowledge();
|
||||
updateCursor(cursorState, toClientCursor(consumer->position()));
|
||||
co_return;
|
||||
} catch (Error&) {
|
||||
updateCursor(cursorState, toClientCursor(consumer->position()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// The native consumer is confined to the network thread. This wrapper keeps
|
||||
// only a raw native pointer off-thread and defers its final release back to the
|
||||
// network thread, matching ThreadSafeDatabase and ThreadSafeTransaction.
|
||||
class ThreadSafeNativeCdcConsumer final : public INativeCdcConsumer,
|
||||
public ThreadSafeReferenceCounted<ThreadSafeNativeCdcConsumer> {
|
||||
public:
|
||||
ThreadSafeNativeCdcConsumer(NativeCdcConsumer* consumer, NativeCdcCursor cursor)
|
||||
: consumer(consumer), cursorState(std::make_shared<NativeCdcCursorState>(cursor)) {}
|
||||
|
||||
~ThreadSafeNativeCdcConsumer() override {
|
||||
NativeCdcConsumer* consumer = this->consumer;
|
||||
onMainThreadVoid([consumer]() { consumer->delref(); });
|
||||
}
|
||||
|
||||
ThreadFuture<NativeCdcConsumeResult> consume() override {
|
||||
auto self = Reference<ThreadSafeNativeCdcConsumer>::addRef(this);
|
||||
return onMainThread([self]() -> Future<NativeCdcConsumeResult> {
|
||||
return consumeNativeCdc(Reference<NativeCdcConsumer>::addRef(self->consumer), self->cursorState);
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Void> acknowledge() override {
|
||||
auto self = Reference<ThreadSafeNativeCdcConsumer>::addRef(this);
|
||||
return onMainThread([self]() -> Future<Void> {
|
||||
return acknowledgeNativeCdc(Reference<NativeCdcConsumer>::addRef(self->consumer), self->cursorState);
|
||||
});
|
||||
}
|
||||
|
||||
NativeCdcCursor getPosition() override { return readCursor(cursorState); }
|
||||
|
||||
void addref() override { ThreadSafeReferenceCounted<ThreadSafeNativeCdcConsumer>::addref(); }
|
||||
void delref() override { ThreadSafeReferenceCounted<ThreadSafeNativeCdcConsumer>::delref(); }
|
||||
|
||||
private:
|
||||
NativeCdcConsumer* consumer;
|
||||
std::shared_ptr<NativeCdcCursorState> cursorState;
|
||||
};
|
||||
|
||||
Reference<INativeCdcConsumer> wrapNativeCdcConsumer(Reference<NativeCdcConsumer> consumer) {
|
||||
NativeCdcCursor cursor = toClientCursor(consumer->position());
|
||||
return makeReference<ThreadSafeNativeCdcConsumer>(consumer.extractPtr(), cursor);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ThreadFuture<Void> ThreadSafeDatabase::onConnected() {
|
||||
DatabaseContext* db = this->db;
|
||||
return onMainThread([db]() -> Future<Void> {
|
||||
|
|
@ -103,6 +221,53 @@ ThreadFuture<Void> ThreadSafeDatabase::createSnapshot(const StringRef& uid, cons
|
|||
});
|
||||
}
|
||||
|
||||
ThreadFuture<CDCStreamId> ThreadSafeDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) {
|
||||
DatabaseContext* db = this->db;
|
||||
Key nameCopy(name);
|
||||
KeyRange keysCopy(keys);
|
||||
return onMainThread([db, nameCopy, keysCopy]() -> Future<CDCStreamId> {
|
||||
db->checkDeferredError();
|
||||
return registerNativeCdcStreamClient(Database(Reference<DatabaseContext>::addRef(db)), nameCopy, keysCopy);
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Void> ThreadSafeDatabase::removeNativeCdcStream(const KeyRef& name) {
|
||||
DatabaseContext* db = this->db;
|
||||
Key nameCopy(name);
|
||||
return onMainThread([db, nameCopy]() -> Future<Void> {
|
||||
db->checkDeferredError();
|
||||
return removeNativeCdcStreamClient(Database(Reference<DatabaseContext>::addRef(db)), nameCopy);
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> ThreadSafeDatabase::listNativeCdcStreams() {
|
||||
DatabaseContext* db = this->db;
|
||||
return onMainThread([db]() -> Future<std::vector<NativeCdcStreamInfo>> {
|
||||
db->checkDeferredError();
|
||||
return listNativeCdcStreamsClient(Database(Reference<DatabaseContext>::addRef(db)));
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> ThreadSafeDatabase::createNativeCdcConsumer(const KeyRef& name) {
|
||||
DatabaseContext* db = this->db;
|
||||
Key nameCopy(name);
|
||||
return onMainThread([db, nameCopy]() -> Future<Reference<INativeCdcConsumer>> {
|
||||
db->checkDeferredError();
|
||||
return map(::createNativeCdcConsumer(Database(Reference<DatabaseContext>::addRef(db)), nameCopy),
|
||||
[](Reference<NativeCdcConsumer> consumer) { return wrapNativeCdcConsumer(std::move(consumer)); });
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> ThreadSafeDatabase::resumeNativeCdcConsumer(const NativeCdcCursor& cursor) {
|
||||
DatabaseContext* db = this->db;
|
||||
return onMainThread([db, cursor]() -> Future<Reference<INativeCdcConsumer>> {
|
||||
db->checkDeferredError();
|
||||
Reference<NativeCdcConsumer> consumer =
|
||||
::resumeNativeCdcConsumer(Database(Reference<DatabaseContext>::addRef(db)), toNativeCursor(cursor));
|
||||
return Future<Reference<INativeCdcConsumer>>(wrapNativeCdcConsumer(std::move(consumer)));
|
||||
});
|
||||
}
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> ThreadSafeDatabase::createSharedState() {
|
||||
DatabaseContext* db = this->db;
|
||||
return onMainThread([db]() -> Future<DatabaseSharedState*> { return db->initSharedState(); });
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/WriteMap.h"
|
||||
#include "WriteMap.h"
|
||||
|
||||
void OperationStack::reset(RYWMutation initialEntry) {
|
||||
defaultConstructed = false;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbclient/VersionedMap.h"
|
||||
#include "fdbclient/SnapshotCache.h"
|
||||
#include "SnapshotCache.h"
|
||||
#include "fdbclient/Atomic.h"
|
||||
|
||||
struct RYWMutation {
|
||||
|
|
@ -48,9 +48,11 @@ public:
|
|||
explicit IBackupFile(const std::string& fileName) : m_fileName(fileName) {}
|
||||
virtual ~IBackupFile() = default;
|
||||
// Backup files are append-only and cannot have more than 1 append outstanding at once.
|
||||
virtual Future<Void> append(const void* data, int len) = 0;
|
||||
// Non-virtual size_t overload: safely chunks large writes so len never overflows the int parameter
|
||||
// of the virtual append(). Uses CLIENT_KNOBS->BACKUP_MANIFEST_WRITE_CHUNK_SIZE as the chunk size.
|
||||
// Backend hook that writes a single chunk. len is bounded by the chunk size (see append()), so
|
||||
// backends may safely narrow it to the int length taken by IAsyncFile::write().
|
||||
virtual Future<Void> appendImpl(const void* data, size_t len) = 0;
|
||||
// Writes len bytes, slicing them into chunks of at most CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE
|
||||
// so appendImpl() never receives more than INT_MAX bytes.
|
||||
Future<Void> append(const void* data, size_t len);
|
||||
virtual Future<Void> finish() = 0;
|
||||
inline std::string getFileName() const { return m_fileName; }
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "fdbclient/FDBOptions.g.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbclient/NativeCdcClient.h"
|
||||
#include "fdbclient/Tracing.h"
|
||||
#include "flow/ProtocolVersion.h"
|
||||
#include "flow/ThreadHelper.h"
|
||||
|
|
@ -153,6 +154,15 @@ public:
|
|||
// Management API, create snapshot
|
||||
virtual ThreadFuture<Void> createSnapshot(const StringRef& uid, const StringRef& snapshot_command) = 0;
|
||||
|
||||
// Native CDC operations. These values are intentionally independent from
|
||||
// NativeAPI so multi-version client wrappers can forward them without
|
||||
// depending on the native client implementation.
|
||||
virtual ThreadFuture<CDCStreamId> registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) = 0;
|
||||
virtual ThreadFuture<Void> removeNativeCdcStream(const KeyRef& name) = 0;
|
||||
virtual ThreadFuture<std::vector<NativeCdcStreamInfo>> listNativeCdcStreams() = 0;
|
||||
virtual ThreadFuture<Reference<INativeCdcConsumer>> createNativeCdcConsumer(const KeyRef& name) = 0;
|
||||
virtual ThreadFuture<Reference<INativeCdcConsumer>> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) = 0;
|
||||
|
||||
// Interface to manage shared state across multiple connections to the same Database
|
||||
virtual ThreadFuture<DatabaseSharedState*> createSharedState() = 0;
|
||||
virtual void setSharedState(DatabaseSharedState* p) = 0;
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ public:
|
|||
|
||||
// Backup
|
||||
int BACKUP_LOCAL_FILE_WRITE_BLOCK;
|
||||
int BACKUP_MANIFEST_WRITE_CHUNK_SIZE;
|
||||
int BACKUP_MANIFEST_CHUNK_SIZE;
|
||||
int BACKUP_CONCURRENT_DELETES;
|
||||
int BACKUP_SIMULATED_LIMIT_BYTES;
|
||||
int BACKUP_GET_RANGE_LIMIT_BYTES;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ struct FdbCApi : public ThreadSafeReferenceCounted<FdbCApi> {
|
|||
using FDBCluster = struct FDB_cluster;
|
||||
using FDBDatabase = struct FDB_database;
|
||||
using FDBTransaction = struct FDB_transaction;
|
||||
using FDBNativeCdcConsumer = struct FDB_native_cdc_consumer;
|
||||
|
||||
using fdb_error_t = int;
|
||||
using fdb_bool_t = int;
|
||||
|
|
@ -91,6 +92,27 @@ struct FdbCApi : public ThreadSafeReferenceCounted<FdbCApi> {
|
|||
int endKeyLength;
|
||||
};
|
||||
|
||||
using FDBNativeCdcStreamInfo = struct native_cdc_stream_info {
|
||||
FDBKey name;
|
||||
uint64_t streamId;
|
||||
FDBKeyRange keyRange;
|
||||
int64_t minVersion;
|
||||
};
|
||||
|
||||
using FDBNativeCdcMutation = struct native_cdc_mutation {
|
||||
uint8_t type;
|
||||
const uint8_t* param1;
|
||||
int param1Length;
|
||||
const uint8_t* param2;
|
||||
int param2Length;
|
||||
};
|
||||
|
||||
using FDBNativeCdcVersionedMutations = struct native_cdc_versioned_mutations {
|
||||
int64_t version;
|
||||
const FDBNativeCdcMutation* mutations;
|
||||
int mutationCount;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
using FDBCallback = void (*)(FDBFuture*, void*);
|
||||
|
|
@ -132,6 +154,26 @@ struct FdbCApi : public ThreadSafeReferenceCounted<FdbCApi> {
|
|||
FDBFuture* (*databaseGetServerProtocol)(FDBDatabase* database, uint64_t expectedVersion);
|
||||
|
||||
FDBFuture* (*databaseGetClientStatus)(FDBDatabase* db);
|
||||
FDBFuture* (*databaseRegisterNativeCdcStream)(FDBDatabase* database,
|
||||
uint8_t const* name,
|
||||
int nameLength,
|
||||
uint8_t const* beginKey,
|
||||
int beginKeyLength,
|
||||
uint8_t const* endKey,
|
||||
int endKeyLength);
|
||||
FDBFuture* (*databaseRemoveNativeCdcStream)(FDBDatabase* database, uint8_t const* name, int nameLength);
|
||||
FDBFuture* (*databaseListNativeCdcStreams)(FDBDatabase* database);
|
||||
FDBFuture* (*databaseCreateNativeCdcConsumer)(FDBDatabase* database, uint8_t const* name, int nameLength);
|
||||
FDBFuture* (*databaseResumeNativeCdcConsumer)(FDBDatabase* database,
|
||||
uint64_t streamId,
|
||||
int64_t lastConsumedVersion);
|
||||
|
||||
void (*nativeCdcConsumerDestroy)(FDBNativeCdcConsumer* consumer);
|
||||
FDBFuture* (*nativeCdcConsumerConsume)(FDBNativeCdcConsumer* consumer);
|
||||
FDBFuture* (*nativeCdcConsumerAcknowledge)(FDBNativeCdcConsumer* consumer);
|
||||
fdb_error_t (*nativeCdcConsumerGetPosition)(FDBNativeCdcConsumer* consumer,
|
||||
uint64_t* outStreamId,
|
||||
int64_t* outLastConsumedVersion);
|
||||
|
||||
// Transaction
|
||||
fdb_error_t (*transactionSetOption)(FDBTransaction* tr,
|
||||
|
|
@ -257,6 +299,14 @@ struct FdbCApi : public ThreadSafeReferenceCounted<FdbCApi> {
|
|||
FDBMappedKeyValue const** outKVM,
|
||||
int* outCount,
|
||||
fdb_bool_t* outMore);
|
||||
fdb_error_t (*futureGetNativeCdcStreamInfoArray)(FDBFuture* f,
|
||||
FDBNativeCdcStreamInfo const** outStreams,
|
||||
int* outCount);
|
||||
fdb_error_t (*futureGetNativeCdcConsumer)(FDBFuture* f, FDBNativeCdcConsumer** outConsumer);
|
||||
fdb_error_t (*futureGetNativeCdcVersionedMutations)(FDBFuture* f,
|
||||
FDBNativeCdcVersionedMutations const** outMutations,
|
||||
int* outCount,
|
||||
int64_t* outLastConsumedVersion);
|
||||
|
||||
fdb_error_t (*futureGetSharedState)(FDBFuture* f, DatabaseSharedState** outPtr);
|
||||
fdb_error_t (*futureSetCallback)(FDBFuture* f, FDBCallback callback, void* callback_parameter);
|
||||
|
|
@ -383,6 +433,11 @@ public:
|
|||
ThreadFuture<int64_t> rebootWorker(const StringRef& address, bool check, int duration) override;
|
||||
ThreadFuture<Void> forceRecoveryWithDataLoss(const StringRef& dcid) override;
|
||||
ThreadFuture<Void> createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override;
|
||||
ThreadFuture<CDCStreamId> registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override;
|
||||
ThreadFuture<Void> removeNativeCdcStream(const KeyRef& name) override;
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> listNativeCdcStreams() override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> createNativeCdcConsumer(const KeyRef& name) override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override;
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> createSharedState() override;
|
||||
void setSharedState(DatabaseSharedState* p) override;
|
||||
|
|
@ -696,6 +751,11 @@ public:
|
|||
ThreadFuture<int64_t> rebootWorker(const StringRef& address, bool check, int duration) override;
|
||||
ThreadFuture<Void> forceRecoveryWithDataLoss(const StringRef& dcid) override;
|
||||
ThreadFuture<Void> createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override;
|
||||
ThreadFuture<CDCStreamId> registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override;
|
||||
ThreadFuture<Void> removeNativeCdcStream(const KeyRef& name) override;
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> listNativeCdcStreams() override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> createNativeCdcConsumer(const KeyRef& name) override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override;
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> createSharedState() override;
|
||||
void setSharedState(DatabaseSharedState* p) override;
|
||||
|
|
|
|||
|
|
@ -22,18 +22,10 @@
|
|||
#define FDBCLIENT_NATIVECDC_H
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "fdbclient/CDCProxyInterface.h"
|
||||
#include "fdbclient/NativeCdcClient.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
|
||||
struct NativeCdcStreamInfo {
|
||||
Key name;
|
||||
CDCStreamId streamId = 0;
|
||||
KeyRange keys;
|
||||
Version minVersion = invalidVersion;
|
||||
};
|
||||
|
||||
class NativeCdcConsumer : public ReferenceCounted<NativeCdcConsumer> {
|
||||
static Future<CDCConsumeReply> consumeImpl(Reference<NativeCdcConsumer> self);
|
||||
static Future<Void> acknowledgeImpl(Reference<NativeCdcConsumer> self);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* NativeCdcClient.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2026 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.
|
||||
*/
|
||||
|
||||
#ifndef FDBCLIENT_NATIVECDCCLIENT_H
|
||||
#define FDBCLIENT_NATIVECDCCLIENT_H
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "flow/ThreadHelper.h"
|
||||
|
||||
// Native CDC value types shared by thread-safe client surfaces and language
|
||||
// bindings. Keep this header independent from NativeAPI so multi-version
|
||||
// client plumbing does not depend on the native client implementation.
|
||||
struct NativeCdcStreamInfo {
|
||||
Key name;
|
||||
CDCStreamId streamId = 0;
|
||||
KeyRange keys;
|
||||
Version minVersion = invalidVersion;
|
||||
};
|
||||
|
||||
struct NativeCdcCursor {
|
||||
CDCStreamId streamId = 0;
|
||||
Version lastConsumedVersion = invalidVersion;
|
||||
};
|
||||
|
||||
struct NativeCdcMutation {
|
||||
uint8_t type = 0;
|
||||
Key param1;
|
||||
Value param2;
|
||||
};
|
||||
|
||||
struct NativeCdcVersionedMutations {
|
||||
Version version = invalidVersion;
|
||||
std::vector<NativeCdcMutation> mutations;
|
||||
};
|
||||
|
||||
struct NativeCdcConsumeResult {
|
||||
std::vector<NativeCdcVersionedMutations> mutations;
|
||||
NativeCdcCursor cursor;
|
||||
};
|
||||
|
||||
// A thread-safe, reference-counted CDC consumer surface for language bindings.
|
||||
// Implementations own any native consumer state and must keep returned values
|
||||
// alive independently of the originating native reply arena.
|
||||
class INativeCdcConsumer {
|
||||
public:
|
||||
virtual ~INativeCdcConsumer() = default;
|
||||
|
||||
virtual ThreadFuture<NativeCdcConsumeResult> consume() = 0;
|
||||
virtual ThreadFuture<Void> acknowledge() = 0;
|
||||
virtual NativeCdcCursor getPosition() = 0;
|
||||
|
||||
virtual void addref() = 0;
|
||||
virtual void delref() = 0;
|
||||
};
|
||||
|
||||
#endif // FDBCLIENT_NATIVECDCCLIENT_H
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* RYWIterator.h
|
||||
* RandomTestUtils.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
|
|
@ -18,67 +18,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FDBCLIENT_RYWITERATOR_H
|
||||
#define FDBCLIENT_RYWITERATOR_H
|
||||
#ifndef FDBCLIENT_RANDOMTESTUTILS_H
|
||||
#define FDBCLIENT_RANDOMTESTUTILS_H
|
||||
#pragma once
|
||||
|
||||
#include "fdbclient/SnapshotCache.h"
|
||||
#include "fdbclient/WriteMap.h"
|
||||
#include <string>
|
||||
|
||||
class RYWIterator {
|
||||
public:
|
||||
RYWIterator(SnapshotCache* snapshotCache, WriteMap* writeMap)
|
||||
: begin_key_cmp(0), end_key_cmp(0), cache(snapshotCache), writes(writeMap), bypassUnreadable(false) {}
|
||||
|
||||
enum SEGMENT_TYPE { UNKNOWN_RANGE, EMPTY_RANGE, KV };
|
||||
static const SEGMENT_TYPE typeMap[12];
|
||||
|
||||
SEGMENT_TYPE type() const;
|
||||
|
||||
bool is_kv() const;
|
||||
bool is_unknown_range() const;
|
||||
bool is_empty_range() const;
|
||||
bool is_unreadable() const;
|
||||
bool is_dependent() const;
|
||||
|
||||
ExtStringRef beginKey();
|
||||
ExtStringRef endKey();
|
||||
|
||||
virtual const KeyValueRef* kv(Arena& arena);
|
||||
|
||||
RYWIterator& operator++();
|
||||
|
||||
RYWIterator& operator--();
|
||||
|
||||
bool operator==(const RYWIterator& r) const;
|
||||
bool operator!=(const RYWIterator& r) const;
|
||||
|
||||
void skip(KeyRef key);
|
||||
|
||||
void skipContiguous(KeyRef key);
|
||||
|
||||
void skipContiguousBack(KeyRef key);
|
||||
|
||||
void bypassUnreadableProtection() { bypassUnreadable = true; }
|
||||
|
||||
virtual WriteMap::iterator& extractWriteMapIterator();
|
||||
// Really this should return an iterator by value, but for performance it's convenient to actually grab the internal
|
||||
// one. Consider copying the return value if performance isn't critical. If you modify the returned iterator, it
|
||||
// invalidates this iterator until the next call to skip()
|
||||
|
||||
void dbg();
|
||||
|
||||
protected:
|
||||
int begin_key_cmp; // -1 if cache.beginKey() < writes.beginKey(), 0 if ==, +1 if >
|
||||
int end_key_cmp; //
|
||||
SnapshotCache::iterator cache;
|
||||
WriteMap::iterator writes;
|
||||
KeyValueRef temp;
|
||||
bool bypassUnreadable; // When set, allows read from sections of keyspace that have become unreadable because of
|
||||
// versionstamp operations
|
||||
|
||||
void updateCmp();
|
||||
};
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
|
||||
class RandomTestImpl {
|
||||
public:
|
||||
|
|
@ -141,7 +87,4 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
void testESR();
|
||||
void testSnapshotCache();
|
||||
|
||||
#endif
|
||||
#endif // FDBCLIENT_RANDOMTESTUTILS_H
|
||||
|
|
@ -25,10 +25,10 @@
|
|||
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "fdbclient/KeyRangeMap.h"
|
||||
#include "fdbclient/RYWIterator.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/WipedString.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
// SOMEDAY: Optimize getKey to avoid using getRange
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ public:
|
|||
[[nodiscard]] Future<Void> onError(Error const& e);
|
||||
|
||||
// These are to permit use as state variables in actors:
|
||||
ReadYourWritesTransaction() : cache(&arena), writes(&arena) {}
|
||||
ReadYourWritesTransaction();
|
||||
void operator=(ReadYourWritesTransaction&& r) noexcept;
|
||||
explicit(false) ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept;
|
||||
|
||||
|
|
@ -217,11 +217,11 @@ public:
|
|||
|
||||
private:
|
||||
friend class RYWImpl;
|
||||
struct RYWState;
|
||||
|
||||
Arena arena;
|
||||
Transaction tr;
|
||||
SnapshotCache cache;
|
||||
WriteMap writes;
|
||||
std::unique_ptr<RYWState> rywState;
|
||||
CoalescedKeyRefRangeMap<bool> readConflicts;
|
||||
Map<Key, std::vector<Reference<Watch>>> watchMap; // Keys that are being watched in this transaction
|
||||
Promise<Void> resetPromise;
|
||||
|
|
@ -246,10 +246,6 @@ private:
|
|||
Optional<std::string> specialKeySpaceErrorMsg;
|
||||
|
||||
void resetTimeout();
|
||||
void updateConflictMap(KeyRef const& key, WriteMap::iterator& it); // pre: it.segmentContains(key)
|
||||
void updateConflictMap(
|
||||
KeyRangeRef const& keys,
|
||||
WriteMap::iterator& it); // pre: it.segmentContains(keys.begin), keys are already inside this->arena
|
||||
void writeRangeToNativeTransaction(KeyRangeRef const& keys);
|
||||
|
||||
void resetRyow(); // doesn't reset the encapsulated transaction, or creation time/retry state
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@ public:
|
|||
ThreadFuture<Void> forceRecoveryWithDataLoss(const StringRef& dcid) override;
|
||||
ThreadFuture<Void> createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override;
|
||||
|
||||
ThreadFuture<CDCStreamId> registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override;
|
||||
ThreadFuture<Void> removeNativeCdcStream(const KeyRef& name) override;
|
||||
ThreadFuture<std::vector<NativeCdcStreamInfo>> listNativeCdcStreams() override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> createNativeCdcConsumer(const KeyRef& name) override;
|
||||
ThreadFuture<Reference<INativeCdcConsumer>> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override;
|
||||
|
||||
ThreadFuture<DatabaseSharedState*> createSharedState() override;
|
||||
void setSharedState(DatabaseSharedState* p) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
// THIS FILE WAS GENERATED BY actorFuzz.py; DO NOT MODIFY IT DIRECTLY
|
||||
|
||||
#include "fdbrpc/ActorFuzz.h"
|
||||
#include "ActorFuzz.h"
|
||||
#include "flow/actorcompiler.h" // has to be last include
|
||||
|
||||
#ifndef WIN32
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbrpc/ActorFuzz.h"
|
||||
#include "ActorFuzz.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
||||
// Only used to link unit tests
|
||||
|
|
@ -28,4 +28,4 @@ TEST_CASE("/actorFuzz") {
|
|||
std::pair<int, int> result = actorFuzzTests();
|
||||
ASSERT(result.first == result.second);
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "flow/IThreadPool.h"
|
||||
#include "flow/WriteOnlySet.h"
|
||||
#include "fdbrpc/fdbrpc.h"
|
||||
#include "fdbrpc/AsyncFileNonDurable.h"
|
||||
#include "flow/IAsyncFile.h"
|
||||
#include "flow/TLSConfig.h"
|
||||
#include "fdbrpc/grpc/AsyncTaskExecutor.h"
|
||||
|
|
@ -340,6 +341,17 @@ TEST_CASE("/flow/flow/cancel1") {
|
|||
return Void();
|
||||
}
|
||||
|
||||
TEST_CASE("/fdbrpc/asyncFileNonDurable/sendErrorOnShutdownCancellation") {
|
||||
Promise<Void> input;
|
||||
Future<Void> wrapped = sendErrorOnShutdown(input.getFuture());
|
||||
ASSERT(input.getFutureReferenceCount() > 0);
|
||||
wrapped.cancel();
|
||||
ASSERT(wrapped.isReady() && wrapped.isError() && wrapped.getError().code() == error_code_actor_cancelled);
|
||||
ASSERT_EQ(input.getFutureReferenceCount(), 0);
|
||||
input.send(Void());
|
||||
return Void();
|
||||
}
|
||||
|
||||
ACTOR static Future<Void> noteCancel(int* cancelled) {
|
||||
*cancelled = 0;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -265,97 +265,6 @@ bool findBestPolicySet(std::vector<LocalityEntry>& bestResults,
|
|||
return bestFound;
|
||||
}
|
||||
|
||||
bool findBestUniquePolicySet(std::vector<LocalityEntry>& bestResults,
|
||||
Reference<LocalitySet>& localitySet,
|
||||
Reference<IReplicationPolicy> const& policy,
|
||||
StringRef localityUniquenessKey,
|
||||
unsigned int nMinItems,
|
||||
unsigned int nSelectTests,
|
||||
unsigned int nPolicyTests) {
|
||||
bool bSucceeded = true;
|
||||
Reference<LocalitySet> bestLocalitySet, testLocalitySet;
|
||||
std::vector<LocalityEntry> results;
|
||||
double testRate, bestRate = -1.0;
|
||||
|
||||
if (g_replicationdebug > 3) {
|
||||
printf("Finding best unique from LocalitySet: %3d\n", localitySet->size());
|
||||
localitySet->DisplayEntries();
|
||||
}
|
||||
|
||||
for (auto policyTest = 0u; policyTest < nPolicyTests; policyTest++) {
|
||||
results.clear();
|
||||
if (!policy->selectReplicas(localitySet, results)) {
|
||||
bSucceeded = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (g_replicationdebug > 5) {
|
||||
printf("policy set #%5d:\n", policyTest);
|
||||
LocalitySet::staticDisplayEntries(localitySet, results, "result");
|
||||
}
|
||||
|
||||
// Get some additional random unique items, if needed
|
||||
if (nMinItems > results.size()) {
|
||||
std::vector<LocalityEntry> exclusionList;
|
||||
auto keyIndex = localitySet->keyIndex(localityUniquenessKey);
|
||||
|
||||
for (auto& result : results) {
|
||||
auto& entryValue = localitySet->getValueViaEntry(result, keyIndex);
|
||||
localitySet->getMatches(exclusionList, keyIndex, entryValue.get());
|
||||
}
|
||||
|
||||
if (g_replicationdebug > 7) {
|
||||
printf("Excluded: %3lu\n", exclusionList.size());
|
||||
LocalitySet::staticDisplayEntries(localitySet, exclusionList, "exclude ");
|
||||
}
|
||||
|
||||
while ((nMinItems > results.size()) && (localitySet->random(results, exclusionList, 1))) {
|
||||
auto& entryValue = localitySet->getValueViaEntry(results.back(), keyIndex);
|
||||
localitySet->getMatches(exclusionList, keyIndex, entryValue.get());
|
||||
}
|
||||
|
||||
if (g_replicationdebug > 6) {
|
||||
printf("Final: %3lu\n", results.size());
|
||||
LocalitySet::staticDisplayEntries(localitySet, results, "final ");
|
||||
}
|
||||
}
|
||||
|
||||
if (g_replicationdebug > 4) {
|
||||
printf("policy with extras #%5d:\n", policyTest);
|
||||
LocalitySet::staticDisplayEntries(localitySet, results, "extra ");
|
||||
}
|
||||
|
||||
// Create the test locality Set
|
||||
testLocalitySet = localitySet->restrict(results);
|
||||
|
||||
// Get the test rate
|
||||
testRate = ratePolicy(testLocalitySet, policy, nSelectTests);
|
||||
|
||||
if (g_replicationdebug > 3) {
|
||||
printf(" rate: %7.5f\n", testRate);
|
||||
}
|
||||
|
||||
if (bestRate < 0.0) {
|
||||
bestResults = results;
|
||||
bestRate = testRate;
|
||||
bestLocalitySet = testLocalitySet;
|
||||
}
|
||||
// Allow the occasional bad comparison, if buggified
|
||||
else if (!buggify() ? (testRate < bestRate) : (testRate > bestRate)) {
|
||||
bestResults = results;
|
||||
bestRate = testRate;
|
||||
bestLocalitySet = testLocalitySet;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_replicationdebug > 2) {
|
||||
printf("BestSet: %7.5f\n", bestRate);
|
||||
bestLocalitySet->DisplayEntries();
|
||||
}
|
||||
|
||||
return bSucceeded;
|
||||
}
|
||||
|
||||
bool validateAllCombinations(std::vector<LocalityData>& offendingCombo,
|
||||
LocalityGroup const& localitySet,
|
||||
Reference<IReplicationPolicy> const& policy,
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ print(
|
|||
"// THIS FILE WAS GENERATED BY actorFuzz.py; DO NOT MODIFY IT DIRECTLY\n",
|
||||
file=outputFile,
|
||||
)
|
||||
print('#include "fdbrpc/ActorFuzz.h"\n', file=outputFile)
|
||||
print('#include "ActorFuzz.h"\n', file=outputFile)
|
||||
print("#ifndef WIN32\n", file=outputFile)
|
||||
|
||||
actors = [randomActor(i) for i in range(testCaseCount)]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include "flow/FastRef.h"
|
||||
#undef ERROR
|
||||
#include "fdbrpc/simulator.h"
|
||||
#include "fdbrpc/ActorFuzz.h"
|
||||
#include "ActorFuzz.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "flow/ThreadHelper.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
|
@ -1103,25 +1103,6 @@ ACTOR [[flow_allow_discard]] Future<Void> cycleTime(int nodes, int times) {
|
|||
return Void();
|
||||
}
|
||||
|
||||
void sleeptest() {
|
||||
#ifdef __linux__
|
||||
int times[] = { 0, 100, 500, 1000, 5000, 100000, 500000, 1000000 };
|
||||
for (int j = 0; j < 8; j++) {
|
||||
double b = timer();
|
||||
int n = std::min(100, 4000000 / (1 + times[j]));
|
||||
for (int i = 0; i < n; i++) {
|
||||
timespec ts;
|
||||
ts.tv_sec = times[j] / 1000000;
|
||||
ts.tv_nsec = (times[j] % 1000000) * 1000;
|
||||
clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, nullptr);
|
||||
// nanosleep(&ts, nullptr);
|
||||
}
|
||||
double t = timer() - b;
|
||||
printf("Sleep test (%dus x %d): %0.1f\n", times[j], n, double(t) / n * 1e6);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void asyncMapTest() {
|
||||
Future<Void> c;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ extern Future<Void> waitShutdownSignal();
|
|||
template <class T>
|
||||
Future<T> sendErrorOnShutdown(Future<T> in, bool assertOnCancel = false) {
|
||||
try {
|
||||
auto res = co_await race(waitShutdownSignal(), in);
|
||||
auto res = co_await race(waitShutdownSignal(), std::move(in));
|
||||
if (res.index() == 0) {
|
||||
throw io_error().asInjectedFault();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -49,19 +49,6 @@ extern bool findBestPolicySet(std::vector<LocalityEntry>& bestResults,
|
|||
unsigned int nMinItems,
|
||||
unsigned int nSelectTests,
|
||||
unsigned int nPolicyTests);
|
||||
// returns the best policy set
|
||||
// given locality set, replication policy, number of min items, number of select
|
||||
// test, number of policy tests, find the best from locality set, including few
|
||||
// random items, get the rate policy having test rate, best rate and returning
|
||||
// the success state.
|
||||
|
||||
extern bool findBestUniquePolicySet(std::vector<LocalityEntry>& bestResults,
|
||||
Reference<LocalitySet>& localitySet,
|
||||
Reference<IReplicationPolicy> const& policy,
|
||||
StringRef localityUniquenessKey,
|
||||
unsigned int nMinItems,
|
||||
unsigned int nSelectTests,
|
||||
unsigned int nPolicyTests);
|
||||
|
||||
// The following function will return TRUE if all possible combinations
|
||||
// of the new Item array will not pass the specified policy
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
#include "fdbclient/DatabaseContext.h"
|
||||
#include "fdbserver/tester/tester.h"
|
||||
#include "fdbserver/core/FDBSimulatorProcessInfo.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/worker/Worker.h"
|
||||
#include "fdbclient/ClusterInterface.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
#include "fdbserver/core/ServerDBInfo.h"
|
||||
#include "fdbserver/core/WaitFailure.h"
|
||||
#include "fdbserver/backupworker/BackupWorker.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/Error.h"
|
||||
|
||||
#include "flow/IRandom.h"
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
#include "fdbserver/core/ServerDBInfo.h"
|
||||
#include "fdbserver/core/SpanContextMessage.h"
|
||||
#include "fdbserver/core/WaitFailure.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/logsystem/LogSystemConsumer.h"
|
||||
#include "fdbserver/logsystem/LogSystemFactory.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
#include "fdbrpc/Locality.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/core/ProcessClassRecruitment.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "fdbclient/ClusterConnectionMemoryRecord.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
|
|
@ -124,6 +124,8 @@ ClusterControllerData::ClusterControllerData(ClusterControllerFullInterface cons
|
|||
serverInfo.masterLifetime.ccID = id;
|
||||
serverInfo.clusterInterface = ccInterface;
|
||||
serverInfo.myLocality = locality;
|
||||
serverInfo.client.nativeCdcEnabled = CLIENT_KNOBS->ENABLE_NATIVE_CDC;
|
||||
serverInfo.client.nativeCdcTagCount = CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT;
|
||||
db.serverInfo->set(serverInfo);
|
||||
cx = openDBOnServer(db.serverInfo, TaskPriority::DefaultEndpoint, LockAware::True);
|
||||
|
||||
|
|
@ -2383,60 +2385,56 @@ Future<Void> updatedChangingDatacenters(ClusterControllerData* self) {
|
|||
}
|
||||
}
|
||||
|
||||
ACTOR Future<Void> updatedChangedDatacenters(ClusterControllerData* self) {
|
||||
state Future<Void> changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY);
|
||||
state Future<Void> onChange = self->changingDcIds.onChange();
|
||||
loop {
|
||||
choose {
|
||||
when(wait(onChange)) {
|
||||
changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY);
|
||||
onChange = self->changingDcIds.onChange();
|
||||
}
|
||||
when(wait(changeDelay)) {
|
||||
changeDelay = Never();
|
||||
onChange = self->changingDcIds.onChange();
|
||||
Future<Void> updatedChangedDatacenters(ClusterControllerData* self) {
|
||||
Future<Void> changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY);
|
||||
Future<Void> onChange = self->changingDcIds.onChange();
|
||||
while (true) {
|
||||
auto res = co_await race(onChange, changeDelay);
|
||||
if (res.index() == 0) {
|
||||
changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY);
|
||||
onChange = self->changingDcIds.onChange();
|
||||
} else {
|
||||
changeDelay = Never();
|
||||
onChange = self->changingDcIds.onChange();
|
||||
|
||||
self->changedDcIds.set(self->changingDcIds.get());
|
||||
if (self->changedDcIds.get().second.present()) {
|
||||
TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first);
|
||||
if (!self->changedDcIds.get().first) {
|
||||
auto& worker = self->id_worker[self->clusterControllerProcessId];
|
||||
uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness(
|
||||
worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get());
|
||||
if (worker.priorityInfo.dcFitness != newFitness) {
|
||||
worker.priorityInfo.dcFitness = newFitness;
|
||||
if (!worker.reply.isSet()) {
|
||||
worker.reply.send(
|
||||
RegisterWorkerReply(worker.details.processClass, worker.priorityInfo));
|
||||
}
|
||||
self->changedDcIds.set(self->changingDcIds.get());
|
||||
if (self->changedDcIds.get().second.present()) {
|
||||
TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first);
|
||||
if (!self->changedDcIds.get().first) {
|
||||
auto& worker = self->id_worker[self->clusterControllerProcessId];
|
||||
uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness(
|
||||
worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get());
|
||||
if (worker.priorityInfo.dcFitness != newFitness) {
|
||||
worker.priorityInfo.dcFitness = newFitness;
|
||||
if (!worker.reply.isSet()) {
|
||||
worker.reply.send(RegisterWorkerReply(worker.details.processClass, worker.priorityInfo));
|
||||
}
|
||||
} else {
|
||||
state int currentFit = recruitment::BestFit;
|
||||
while (currentFit <= recruitment::NeverAssign) {
|
||||
bool updated = false;
|
||||
for (auto& it : self->id_worker) {
|
||||
if ((!it.second.priorityInfo.isExcluded &&
|
||||
it.second.priorityInfo.processClassFitness == currentFit) ||
|
||||
currentFit == recruitment::NeverAssign) {
|
||||
uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness(
|
||||
it.second.details.interf.locality.dcId(),
|
||||
self->changedDcIds.get().second.get());
|
||||
if (it.first != self->clusterControllerProcessId &&
|
||||
it.second.priorityInfo.dcFitness != fitness) {
|
||||
updated = true;
|
||||
it.second.priorityInfo.dcFitness = fitness;
|
||||
if (!it.second.reply.isSet()) {
|
||||
it.second.reply.send(RegisterWorkerReply(it.second.details.processClass,
|
||||
it.second.priorityInfo));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int currentFit = recruitment::BestFit;
|
||||
while (currentFit <= recruitment::NeverAssign) {
|
||||
bool updated = false;
|
||||
for (auto& it : self->id_worker) {
|
||||
if ((!it.second.priorityInfo.isExcluded &&
|
||||
it.second.priorityInfo.processClassFitness == currentFit) ||
|
||||
currentFit == recruitment::NeverAssign) {
|
||||
uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness(
|
||||
it.second.details.interf.locality.dcId(), self->changedDcIds.get().second.get());
|
||||
if (it.first != self->clusterControllerProcessId &&
|
||||
it.second.priorityInfo.dcFitness != fitness) {
|
||||
updated = true;
|
||||
it.second.priorityInfo.dcFitness = fitness;
|
||||
if (!it.second.reply.isSet()) {
|
||||
it.second.reply.send(RegisterWorkerReply(it.second.details.processClass,
|
||||
it.second.priorityInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updated && currentFit < recruitment::NeverAssign) {
|
||||
wait(delay(SERVER_KNOBS->CC_CLASS_DELAY));
|
||||
}
|
||||
currentFit++;
|
||||
}
|
||||
if (updated && currentFit < recruitment::NeverAssign) {
|
||||
co_await delay(SERVER_KNOBS->CC_CLASS_DELAY);
|
||||
}
|
||||
currentFit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2806,13 +2804,13 @@ Future<Void> startDataDistributor(ClusterControllerData* self, double waitTime)
|
|||
}
|
||||
}
|
||||
|
||||
ACTOR Future<Void> monitorDataDistributor(ClusterControllerData* self) {
|
||||
state SingletonRecruitThrottler recruitThrottler;
|
||||
Future<Void> monitorDataDistributor(ClusterControllerData* self) {
|
||||
SingletonRecruitThrottler recruitThrottler;
|
||||
while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) {
|
||||
wait(self->db.serverInfo->onChange());
|
||||
co_await self->db.serverInfo->onChange();
|
||||
}
|
||||
|
||||
loop {
|
||||
while (true) {
|
||||
bool ddExist = self->db.serverInfo->get().distributor.present();
|
||||
TraceEvent(SevInfo, "CCMonitorDataDistributor", self->id)
|
||||
.detail("Recruiting", self->recruitDistributor.get())
|
||||
|
|
@ -2820,18 +2818,17 @@ ACTOR Future<Void> monitorDataDistributor(ClusterControllerData* self) {
|
|||
.detail("ExistingDD", ddExist ? self->db.serverInfo->get().distributor.get().id().toString() : "");
|
||||
|
||||
if (self->db.serverInfo->get().distributor.present() && !self->recruitDistributor.get()) {
|
||||
choose {
|
||||
when(wait(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure,
|
||||
SERVER_KNOBS->DD_FAILURE_TIME))) {
|
||||
const auto& distributor = self->db.serverInfo->get().distributor;
|
||||
TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id());
|
||||
DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId());
|
||||
self->db.clearInterf(ProcessClass::DataDistributorClass);
|
||||
}
|
||||
when(wait(self->recruitDistributor.onChange())) {}
|
||||
auto res = co_await race(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure,
|
||||
SERVER_KNOBS->DD_FAILURE_TIME),
|
||||
self->recruitDistributor.onChange());
|
||||
if (res.index() == 0) {
|
||||
const auto& distributor = self->db.serverInfo->get().distributor;
|
||||
TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id());
|
||||
DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId());
|
||||
self->db.clearInterf(ProcessClass::DataDistributorClass);
|
||||
}
|
||||
} else {
|
||||
wait(startDataDistributor(self, recruitThrottler.newRecruitment()));
|
||||
co_await startDataDistributor(self, recruitThrottler.newRecruitment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2904,26 +2901,25 @@ Future<Void> startRatekeeper(ClusterControllerData* self, double waitTime) {
|
|||
}
|
||||
}
|
||||
|
||||
ACTOR Future<Void> monitorRatekeeper(ClusterControllerData* self) {
|
||||
state SingletonRecruitThrottler recruitThrottler;
|
||||
Future<Void> monitorRatekeeper(ClusterControllerData* self) {
|
||||
SingletonRecruitThrottler recruitThrottler;
|
||||
while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) {
|
||||
wait(self->db.serverInfo->onChange());
|
||||
co_await self->db.serverInfo->onChange();
|
||||
}
|
||||
|
||||
loop {
|
||||
while (true) {
|
||||
if (self->db.serverInfo->get().ratekeeper.present() && !self->recruitRatekeeper.get()) {
|
||||
choose {
|
||||
when(wait(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure,
|
||||
SERVER_KNOBS->RATEKEEPER_FAILURE_TIME))) {
|
||||
const auto& ratekeeper = self->db.serverInfo->get().ratekeeper;
|
||||
TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id());
|
||||
RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId());
|
||||
self->db.clearInterf(ProcessClass::RatekeeperClass);
|
||||
}
|
||||
when(wait(self->recruitRatekeeper.onChange())) {}
|
||||
auto res = co_await race(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure,
|
||||
SERVER_KNOBS->RATEKEEPER_FAILURE_TIME),
|
||||
self->recruitRatekeeper.onChange());
|
||||
if (res.index() == 0) {
|
||||
const auto& ratekeeper = self->db.serverInfo->get().ratekeeper;
|
||||
TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id());
|
||||
RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId());
|
||||
self->db.clearInterf(ProcessClass::RatekeeperClass);
|
||||
}
|
||||
} else {
|
||||
wait(startRatekeeper(self, recruitThrottler.newRecruitment()));
|
||||
co_await startRatekeeper(self, recruitThrottler.newRecruitment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2996,28 +2992,25 @@ Future<Void> startConsistencyScan(ClusterControllerData* self) {
|
|||
}
|
||||
}
|
||||
|
||||
ACTOR Future<Void> monitorConsistencyScan(ClusterControllerData* self) {
|
||||
Future<Void> monitorConsistencyScan(ClusterControllerData* self) {
|
||||
while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) {
|
||||
TraceEvent("CCMonitorConsistencyScanWaitingForRecovery", self->id).log();
|
||||
wait(self->db.serverInfo->onChange());
|
||||
co_await self->db.serverInfo->onChange();
|
||||
}
|
||||
TraceEvent("CCMonitorConsistencyScan", self->id).log();
|
||||
loop {
|
||||
while (true) {
|
||||
if (self->db.serverInfo->get().consistencyScan.present() && !self->recruitConsistencyScan.get()) {
|
||||
state Future<Void> wfClient =
|
||||
waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure,
|
||||
SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME);
|
||||
choose {
|
||||
when(wait(wfClient)) {
|
||||
TraceEvent("CCMonitorConsistencyScanDied", self->id)
|
||||
.detail("CKID", self->db.serverInfo->get().consistencyScan.get().id());
|
||||
self->db.clearInterf(ProcessClass::ConsistencyScanClass);
|
||||
}
|
||||
when(wait(self->recruitConsistencyScan.onChange())) {}
|
||||
Future<Void> wfClient = waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure,
|
||||
SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME);
|
||||
auto res = co_await race(wfClient, self->recruitConsistencyScan.onChange());
|
||||
if (res.index() == 0) {
|
||||
TraceEvent("CCMonitorConsistencyScanDied", self->id)
|
||||
.detail("CKID", self->db.serverInfo->get().consistencyScan.get().id());
|
||||
self->db.clearInterf(ProcessClass::ConsistencyScanClass);
|
||||
}
|
||||
} else {
|
||||
TraceEvent("CCMonitorConsistencyScanStarting", self->id).log();
|
||||
wait(startConsistencyScan(self));
|
||||
co_await startConsistencyScan(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
#include "RatekeeperMonitor.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/core/ProcessClassRecruitment.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbrpc/Locality.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
#include "flow/NetworkAddress.h"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
#include "fdbserver/logsystem/LogSystem.h"
|
||||
#include "fdbserver/core/LogSystemConfig.h"
|
||||
#include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/SystemMonitor.h"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
#include "fdbclient/SystemData.h"
|
||||
#include "fdbclient/ReadYourWrites.h"
|
||||
#include "fdbserver/core/WorkerEvents.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include <time.h>
|
||||
#include "ClusterRecovery.h"
|
||||
#include "fdbclient/ClusterConnectionMemoryRecord.h"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
#include "fdbrpc/fdbrpc.h"
|
||||
#include "fdbserver/core/CoordinationInterface.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/core/MasterInterface.h"
|
||||
#include "fdbclient/ClusterInterface.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
#include "fdbserver/core/ServerDBInfo.h"
|
||||
#include "fdbserver/core/WaitFailure.h"
|
||||
#include "fdbserver/commitproxy/CommitProxyServer.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "flow/CodeProbe.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#include "fdbclient/json_spirit/json_spirit_writer_template.h"
|
||||
#include "fdbserver/consistencyscan/ConsistencyScan.h"
|
||||
#include "fdbserver/core/FDBSimulationPolicy.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/IndexedSet.h"
|
||||
#include "fdbrpc/FailureMonitor.h"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include "fdbserver/coordinator/CoordinationServer.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "OnDemandStore.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "flow/ProtocolVersion.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
#include "fdbclient/DatabaseContext.h"
|
||||
#include "fdbclient/GlobalConfig.h"
|
||||
#include "fdbclient/MonitorLeader.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
static Future<Void> extractClientInfo(Reference<AsyncVar<ServerDBInfo> const> db,
|
||||
Reference<AsyncVar<ClientDBInfo>> info) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
#include "fdbclient/RunRYWTransaction.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/core/QuietDatabase.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/core/FDBSimulationPolicy.h"
|
||||
#include "fdbclient/ManagementAPI.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
|
|
@ -701,8 +701,26 @@ Future<Void> repairDeadDatacenter(Database cx, Reference<AsyncVar<ServerDBInfo>
|
|||
if (g_network->isSimulated() && fdbSimulationPolicyState().usableRegions > 1 &&
|
||||
!fdbSimulationPolicyState().quiesced) {
|
||||
auto& simPolicy = fdbSimulationPolicyState();
|
||||
const auto primaryDcId = simPolicy.primaryDcId;
|
||||
const auto remoteDcId = simPolicy.remoteDcId;
|
||||
bool primaryDead = g_simulator->datacenterDead(simPolicy.primaryDcId);
|
||||
bool remoteDead = g_simulator->datacenterDead(simPolicy.remoteDcId);
|
||||
if (primaryDead || remoteDead) {
|
||||
// A single-replica region can look dead while a workload intentionally reboots its master. Confirm the
|
||||
// failure after the maximum simulated worker reboot time before making an irreversible region change.
|
||||
TraceEvent("ConfirmingDeadDatacenter")
|
||||
.detail("Location", context)
|
||||
.detail("PrimaryDead", primaryDead)
|
||||
.detail("RemoteDead", remoteDead)
|
||||
.detail("Delay", SERVER_KNOBS->MAX_REBOOT_TIME);
|
||||
co_await delay(SERVER_KNOBS->MAX_REBOOT_TIME);
|
||||
if (simPolicy.usableRegions <= 1 || simPolicy.quiesced || simPolicy.primaryDcId != primaryDcId ||
|
||||
simPolicy.remoteDcId != remoteDcId) {
|
||||
co_return;
|
||||
}
|
||||
primaryDead = primaryDead && g_simulator->datacenterDead(simPolicy.primaryDcId);
|
||||
remoteDead = remoteDead && g_simulator->datacenterDead(simPolicy.remoteDcId);
|
||||
}
|
||||
|
||||
// FIXME: the primary and remote can both be considered dead because excludes are not handled properly by the
|
||||
// datacenterDead function
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
Future<Void> extractClusterInterface(Reference<AsyncVar<Optional<ClusterControllerFullInterface>> const> in,
|
||||
Reference<AsyncVar<Optional<ClusterInterface>>> out) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
#include "flow/ObjectSerializer.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "fdbserver/core/TesterInterface.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
Future<int64_t> getDataInFlight(Database cx, Reference<AsyncVar<struct ServerDBInfo> const> dbInfo);
|
||||
Future<std::pair<int64_t, int64_t>> getTLogQueueInfo(Database cx,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
#include "fdbserver/core/MasterInterface.h"
|
||||
#include "fdbserver/core/RatekeeperInterface.h"
|
||||
#include "fdbserver/core/RecoveryState.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
struct ServerDBInfo {
|
||||
constexpr static FileIdentifier file_identifier = 13838807;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "flow/ITrace.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
|
||||
struct WorkerEvents : std::map<NetworkAddress, TraceEventFields> {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* WorkerInterface.actor.h
|
||||
* WorkerInterface.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
|
|
@ -19,11 +19,6 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_WORKERINTERFACE_ACTOR_G_H)
|
||||
#define FDBSERVER_WORKERINTERFACE_ACTOR_G_H
|
||||
#include "fdbserver/core/WorkerInterface.actor.g.h"
|
||||
#elif !defined(FDBSERVER_WORKERINTERFACE_ACTOR_H)
|
||||
#define FDBSERVER_WORKERINTERFACE_ACTOR_H
|
||||
|
||||
#include "fdbserver/core/BackupInterface.h"
|
||||
#include "fdbserver/core/DataDistributorInterface.h"
|
||||
|
|
@ -43,7 +38,7 @@
|
|||
#include "fdbrpc/MultiInterface.h"
|
||||
#include "fdbclient/ClientWorkerInterface.h"
|
||||
#include "fdbserver/core/RecoveryState.h"
|
||||
#include "flow/actorcompiler.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
|
||||
struct WorkerInterface {
|
||||
constexpr static FileIdentifier file_identifier = 14712718;
|
||||
|
|
@ -83,7 +78,7 @@ struct WorkerInterface {
|
|||
NetworkAddressList addresses() const { return tLog.getEndpoint().addresses; }
|
||||
Optional<NetworkAddress> grpcAddress() const { return clientInterface.grpcAddress; }
|
||||
|
||||
WorkerInterface() {}
|
||||
WorkerInterface() = default;
|
||||
explicit(false) WorkerInterface(const LocalityData& locality) : locality(locality) {}
|
||||
|
||||
void initEndpoints() {
|
||||
|
|
@ -371,7 +366,7 @@ struct RecruitFromConfigurationRequest {
|
|||
int maxOldLogRouters;
|
||||
ReplyPromise<RecruitFromConfigurationReply> reply;
|
||||
|
||||
RecruitFromConfigurationRequest() {}
|
||||
RecruitFromConfigurationRequest() = default;
|
||||
explicit RecruitFromConfigurationRequest(DatabaseConfiguration const& configuration,
|
||||
bool recruitSeedServers,
|
||||
int maxOldLogRouters)
|
||||
|
|
@ -404,7 +399,7 @@ struct RecruitRemoteFromConfigurationRequest {
|
|||
Optional<UID> dbgId;
|
||||
ReplyPromise<RecruitRemoteFromConfigurationReply> reply;
|
||||
|
||||
RecruitRemoteFromConfigurationRequest() {}
|
||||
RecruitRemoteFromConfigurationRequest() = default;
|
||||
RecruitRemoteFromConfigurationRequest(DatabaseConfiguration const& configuration,
|
||||
Optional<Key> const& dcId,
|
||||
int logRouterCount,
|
||||
|
|
@ -550,7 +545,7 @@ struct TLogRejoinRequest {
|
|||
TLogInterface myInterface;
|
||||
ReplyPromise<TLogRejoinReply> reply;
|
||||
|
||||
TLogRejoinRequest() {}
|
||||
TLogRejoinRequest() = default;
|
||||
explicit TLogRejoinRequest(const TLogInterface& interf) : myInterface(interf) {}
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
|
|
@ -591,7 +586,7 @@ struct GetEncryptionAtRestModeRequest {
|
|||
UID tlogId;
|
||||
ReplyPromise<GetEncryptionAtRestModeResponse> reply;
|
||||
|
||||
GetEncryptionAtRestModeRequest() {}
|
||||
GetEncryptionAtRestModeRequest() = default;
|
||||
explicit(false) GetEncryptionAtRestModeRequest(UID tId) : tlogId(tId) {}
|
||||
|
||||
template <class Ar>
|
||||
|
|
@ -846,7 +841,7 @@ struct InitializeDataDistributorRequest {
|
|||
UID reqId;
|
||||
ReplyPromise<DataDistributorInterface> reply;
|
||||
|
||||
InitializeDataDistributorRequest() {}
|
||||
InitializeDataDistributorRequest() = default;
|
||||
explicit InitializeDataDistributorRequest(UID uid) : reqId(uid) {}
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
|
|
@ -859,7 +854,7 @@ struct InitializeRatekeeperRequest {
|
|||
UID reqId;
|
||||
ReplyPromise<RatekeeperInterface> reply;
|
||||
|
||||
InitializeRatekeeperRequest() {}
|
||||
InitializeRatekeeperRequest() = default;
|
||||
explicit InitializeRatekeeperRequest(UID uid) : reqId(uid) {}
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
|
|
@ -872,7 +867,7 @@ struct InitializeConsistencyScanRequest {
|
|||
UID reqId;
|
||||
ReplyPromise<ConsistencyScanInterface> reply;
|
||||
|
||||
InitializeConsistencyScanRequest() {}
|
||||
InitializeConsistencyScanRequest() = default;
|
||||
explicit InitializeConsistencyScanRequest(UID uid) : reqId(uid) {}
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
|
|
@ -1044,7 +1039,7 @@ struct DebugEntryRef {
|
|||
StringRef context;
|
||||
Version version;
|
||||
MutationRef mutation;
|
||||
DebugEntryRef() {}
|
||||
DebugEntryRef() = default;
|
||||
DebugEntryRef(const char* c, Version v, MutationRef const& m)
|
||||
: time(now()), address(g_network->getLocalAddress()), context((const uint8_t*)c, strlen(c)), version(v),
|
||||
mutation(m) {}
|
||||
|
|
@ -1174,42 +1169,44 @@ bool addressInDbAndRemoteDc(
|
|||
|
||||
extern bool isSimulatorProcessUnreliable();
|
||||
|
||||
ACTOR template <class T>
|
||||
Future<T> ioTimeoutError(Future<T> what, double time, const char* context = nullptr) {
|
||||
template <class T>
|
||||
Future<T> ioTimeoutError(Future<T> what, double time, const char* context = nullptr, ExplicitVoid = {}) {
|
||||
// Before simulation is sped up, IO operations can take a very long time so limit timeouts
|
||||
// to not end until at least time after simulation is sped up.
|
||||
state double orig = now();
|
||||
state std::string trace = platform::get_backtrace();
|
||||
double orig = now();
|
||||
std::string trace = platform::get_backtrace();
|
||||
if (g_network->isSimulated() && !g_simulator->speedUpSimulation) {
|
||||
time += std::max(0.0, FLOW_KNOBS->SIM_SPEEDUP_AFTER_SECONDS - now());
|
||||
}
|
||||
Future<Void> end = lowPriorityDelay(time);
|
||||
choose {
|
||||
when(T t = wait(what)) {
|
||||
return t;
|
||||
auto res = co_await race(what, end);
|
||||
if (res.index() == 0) {
|
||||
T t = std::get<0>(std::move(res));
|
||||
co_return t;
|
||||
} else if (res.index() == 1) {
|
||||
Error err = io_timeout();
|
||||
if (isSimulatorProcessUnreliable()) {
|
||||
err = err.asInjectedFault();
|
||||
}
|
||||
when(wait(end)) {
|
||||
Error err = io_timeout();
|
||||
if (isSimulatorProcessUnreliable()) {
|
||||
err = err.asInjectedFault();
|
||||
}
|
||||
TraceEvent e(SevError, "IoTimeoutError");
|
||||
e.error(err);
|
||||
if (context != nullptr) {
|
||||
e.detail("Context", context);
|
||||
}
|
||||
e.detail("OrigTime", orig).detail("OrigTrace", trace).log();
|
||||
throw err;
|
||||
TraceEvent e(SevError, "IoTimeoutError");
|
||||
e.error(err);
|
||||
if (context != nullptr) {
|
||||
e.detail("Context", context);
|
||||
}
|
||||
e.detail("OrigTime", orig).detail("OrigTrace", trace).log();
|
||||
throw err;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
ACTOR template <class T>
|
||||
template <class T>
|
||||
Future<T> ioDegradedOrTimeoutError(Future<T> what,
|
||||
double errTime,
|
||||
Reference<AsyncVar<bool>> degraded,
|
||||
double degradedTime,
|
||||
const char* context = nullptr) {
|
||||
const char* context = nullptr,
|
||||
ExplicitVoid = {}) {
|
||||
// Before simulation is sped up, IO operations can take a very long time so limit timeouts
|
||||
// to not end until at least time after simulation is sped up.
|
||||
if (g_network->isSimulated() && !g_simulator->speedUpSimulation) {
|
||||
|
|
@ -1220,39 +1217,39 @@ Future<T> ioDegradedOrTimeoutError(Future<T> what,
|
|||
|
||||
if (degradedTime < errTime) {
|
||||
Future<Void> degradedEnd = lowPriorityDelay(degradedTime);
|
||||
choose {
|
||||
when(T t = wait(what)) {
|
||||
return t;
|
||||
}
|
||||
when(wait(degradedEnd)) {
|
||||
CODE_PROBE(true, "TLog degraded", probe::func::deduplicate);
|
||||
TraceEvent(SevWarnAlways, "IoDegraded").log();
|
||||
degraded->set(true);
|
||||
}
|
||||
auto res = co_await race(what, degradedEnd);
|
||||
if (res.index() == 0) {
|
||||
T t = std::get<0>(std::move(res));
|
||||
co_return t;
|
||||
} else if (res.index() == 1) {
|
||||
CODE_PROBE(true, "TLog degraded", probe::func::deduplicate);
|
||||
TraceEvent(SevWarnAlways, "IoDegraded").log();
|
||||
degraded->set(true);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Void> end = lowPriorityDelay(errTime - degradedTime);
|
||||
choose {
|
||||
when(T t = wait(what)) {
|
||||
return t;
|
||||
auto res = co_await race(what, end);
|
||||
if (res.index() == 0) {
|
||||
T t = std::get<0>(std::move(res));
|
||||
co_return t;
|
||||
} else if (res.index() == 1) {
|
||||
Error err = io_timeout();
|
||||
if (isSimulatorProcessUnreliable()) {
|
||||
err = err.asInjectedFault();
|
||||
}
|
||||
when(wait(end)) {
|
||||
Error err = io_timeout();
|
||||
if (isSimulatorProcessUnreliable()) {
|
||||
err = err.asInjectedFault();
|
||||
}
|
||||
TraceEvent e(SevError, "IoTimeoutError");
|
||||
e.error(err);
|
||||
if (context != nullptr) {
|
||||
e.detail("Context", context);
|
||||
}
|
||||
e.log();
|
||||
throw err;
|
||||
TraceEvent e(SevError, "IoTimeoutError");
|
||||
e.error(err);
|
||||
if (context != nullptr) {
|
||||
e.detail("Context", context);
|
||||
}
|
||||
e.log();
|
||||
throw err;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#include "fdbserver/core/ServerDBInfo.h"
|
||||
#endif
|
||||
|
|
@ -49,11 +49,6 @@
|
|||
using ITeamRef = Reference<IDataDistributionTeam>;
|
||||
using SrcDestTeamPair = std::pair<ITeamRef, ITeamRef>;
|
||||
|
||||
inline bool isDataMovementForDiskBalancing(DataMovementReason reason) {
|
||||
return reason == DataMovementReason::REBALANCE_UNDERUTILIZED_TEAM ||
|
||||
reason == DataMovementReason::REBALANCE_OVERUTILIZED_TEAM;
|
||||
}
|
||||
|
||||
inline bool isDataMovementForReadBalancing(DataMovementReason reason) {
|
||||
return reason == DataMovementReason::REBALANCE_READ_OVERUTIL_TEAM ||
|
||||
reason == DataMovementReason::REBALANCE_READ_UNDERUTIL_TEAM;
|
||||
|
|
@ -82,14 +77,19 @@ RelocateData::RelocateData()
|
|||
|
||||
RelocateData::RelocateData(RelocateShard const& rs)
|
||||
: parent_range(rs.getParentRange()), keys(rs.keys), priority(rs.priority),
|
||||
boundaryPriority(isBoundaryPriority(rs.priority) ? rs.priority : -1),
|
||||
healthPriority(isHealthPriority(rs.priority) ? rs.priority : -1), reason(rs.reason), dmReason(rs.moveReason),
|
||||
startTime(now()), randomId(rs.traceId.isValid() ? rs.traceId : deterministicRandom()->randomUniqueID()),
|
||||
dataMoveId(rs.dataMoveId), workFactor(0),
|
||||
wantsNewServers(isDataMovementForMountainChopper(rs.moveReason) || isDataMovementForValleyFiller(rs.moveReason) ||
|
||||
rs.moveReason == DataMovementReason::SPLIT_SHARD ||
|
||||
rs.moveReason == DataMovementReason::TEAM_REDUNDANT ||
|
||||
rs.moveReason == DataMovementReason::REBALANCE_STORAGE_QUEUE),
|
||||
boundaryPriority(rs.retryIntent.present() ? rs.retryIntent.get().boundaryPriority
|
||||
: (isBoundaryPriority(rs.priority) ? rs.priority : -1)),
|
||||
healthPriority(rs.retryIntent.present() ? rs.retryIntent.get().healthPriority
|
||||
: (isHealthPriority(rs.priority) ? rs.priority : -1)),
|
||||
reason(rs.reason), dmReason(rs.moveReason), startTime(now()),
|
||||
randomId(rs.traceId.isValid() ? rs.traceId : deterministicRandom()->randomUniqueID()), dataMoveId(rs.dataMoveId),
|
||||
workFactor(0),
|
||||
wantsNewServers(rs.retryIntent.present() ? rs.retryIntent.get().wantsNewServers
|
||||
: (isDataMovementForMountainChopper(rs.moveReason) ||
|
||||
isDataMovementForValleyFiller(rs.moveReason) ||
|
||||
rs.moveReason == DataMovementReason::SPLIT_SHARD ||
|
||||
rs.moveReason == DataMovementReason::TEAM_REDUNDANT ||
|
||||
rs.moveReason == DataMovementReason::REBALANCE_STORAGE_QUEUE)),
|
||||
cancellable(true), interval("QueuedRelocation", randomId), dataMove(rs.dataMove) {
|
||||
if (dataMove != nullptr) {
|
||||
this->src.insert(this->src.end(), dataMove->meta.src.begin(), dataMove->meta.src.end());
|
||||
|
|
@ -129,6 +129,26 @@ Optional<KeyRange> RelocateData::getParentRange() const {
|
|||
return parent_range;
|
||||
}
|
||||
|
||||
static RelocateShard makeDestinationFailureRetry(RelocateData const& rd, UID retryTraceId) {
|
||||
RelocateShard retry(rd.keys, rd.dmReason, rd.reason, retryTraceId);
|
||||
retry.priority = rd.priority;
|
||||
retry.retryIntent =
|
||||
RelocateShard::RetryRelocationIntent{ rd.boundaryPriority, rd.healthPriority, rd.wantsNewServers };
|
||||
if (rd.getParentRange().present()) {
|
||||
retry.setParentRange(rd.getParentRange().get());
|
||||
}
|
||||
return retry;
|
||||
}
|
||||
|
||||
static bool shouldRetryDestinationTeamFailure(bool doBulkLoading, RelocateData const&) {
|
||||
return !doBulkLoading;
|
||||
}
|
||||
|
||||
static bool shouldYieldDestinationFailureRetry(RelocateData const& retry, RelocateData const& queued) {
|
||||
bool isSplit = retry.reason == RelocateReason::SIZE_SPLIT || retry.reason == RelocateReason::WRITE_SPLIT;
|
||||
return isSplit && retry.keys != queued.keys && retry.keys.contains(queued.keys);
|
||||
}
|
||||
|
||||
class ParallelTCInfo final : public ReferenceCounted<ParallelTCInfo>, public IDataDistributionTeam {
|
||||
std::vector<Reference<IDataDistributionTeam>> teams;
|
||||
std::vector<UID> tempServerIDs;
|
||||
|
|
@ -808,7 +828,31 @@ void DDQueue::queueRelocation(RelocateShard rs, std::set<UID>& serversToLaunchFr
|
|||
//TraceEvent("QueueRelocationBegin").detail("Begin", rd.keys.begin).detail("End", rd.keys.end);
|
||||
|
||||
// remove all items from both queues that are fully contained in the new relocation (i.e. will be overwritten)
|
||||
bool destinationFailureRetry = rs.retryIntent.present();
|
||||
RelocateData rd(rs);
|
||||
if (destinationFailureRetry) {
|
||||
auto ranges = queueMap.intersectingRanges(rd.keys);
|
||||
for (auto r = ranges.begin(); r != ranges.end(); ++r) {
|
||||
RelocateData const& queued = r->value();
|
||||
if (!shouldYieldDestinationFailureRetry(rd, queued)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool active = fetchingSourcesQueue.contains(queued);
|
||||
if (!active && !queued.src.empty()) {
|
||||
auto sourceQueue = queue.find(queued.src.front());
|
||||
active = sourceQueue != queue.end() && sourceQueue->second.contains(queued);
|
||||
}
|
||||
if (active) {
|
||||
TraceEvent(SevInfo, "DestinationFailureRetryYieldedToQueuedSplit", distributorId)
|
||||
.detail("RetryRange", rd.keys)
|
||||
.detail("RetryTraceID", rd.randomId)
|
||||
.detail("QueuedRange", queued.keys)
|
||||
.detail("QueuedTraceID", queued.randomId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool hasHealthPriority = RelocateData::isHealthPriority(rd.priority);
|
||||
bool hasBoundaryPriority = RelocateData::isBoundaryPriority(rd.priority);
|
||||
|
||||
|
|
@ -1082,6 +1126,11 @@ void DDQueue::launchQueuedWork(std::set<RelocateData, std::greater<RelocateData>
|
|||
// kick off relocators from items in the queue as need be
|
||||
for (auto it = combined.begin(); it != combined.end(); it++) {
|
||||
RelocateData rd(*it);
|
||||
// A restored move can be held behind the pipeline gate while shard-encoded metadata is disabled.
|
||||
// Restart DD before mutating the queue so the move is cancelled by the rollback path.
|
||||
if (rd.isRestore() && !SERVER_KNOBS->SHARD_ENCODE_LOCATION_METADATA) {
|
||||
throw dd_config_changed();
|
||||
}
|
||||
|
||||
// If having a bulk load task overlapping the rd range,
|
||||
// attach bulk load task to the input rd if rd is not a data move
|
||||
|
|
@ -1497,6 +1546,7 @@ Future<Void> dataDistributionRelocator(DDQueue* self,
|
|||
PromiseStream<RelocateData> dataTransferComplete(self->dataTransferComplete);
|
||||
PromiseStream<RelocateData> relocationComplete(self->relocationComplete);
|
||||
bool signalledTransferComplete = false;
|
||||
bool retryAfterDestinationTeamFailure = false;
|
||||
UID distributorId = self->distributorId;
|
||||
ParallelTCInfo healthyDestinations;
|
||||
|
||||
|
|
@ -2240,6 +2290,7 @@ Future<Void> dataDistributionRelocator(DDQueue* self,
|
|||
rd.bulkLoadTask.get().completeAck.send(
|
||||
BulkLoadAck(/*unretryableError=*/true, rd.priority));
|
||||
}
|
||||
retryAfterDestinationTeamFailure = shouldRetryDestinationTeamFailure(doBulkLoading, rd);
|
||||
throw data_move_dest_team_not_found();
|
||||
}
|
||||
}
|
||||
|
|
@ -2425,6 +2476,37 @@ Future<Void> dataDistributionRelocator(DDQueue* self,
|
|||
if (!signalledTransferComplete)
|
||||
dataTransferComplete.send(rd);
|
||||
|
||||
if (err.code() == error_code_data_move_dest_team_not_found && rd.isRestore()) {
|
||||
std::vector<ShardsAffectedByTeamFailure::Team> destinationTeams = { ShardsAffectedByTeamFailure::Team(
|
||||
rd.dataMove->primaryDest, true) };
|
||||
std::vector<ShardsAffectedByTeamFailure::Team> sourceTeams = { ShardsAffectedByTeamFailure::Team(
|
||||
rd.dataMove->primarySrc, true) };
|
||||
if (!rd.dataMove->remoteDest.empty()) {
|
||||
destinationTeams.emplace_back(rd.dataMove->remoteDest, false);
|
||||
}
|
||||
if (!rd.dataMove->remoteSrc.empty()) {
|
||||
sourceTeams.emplace_back(rd.dataMove->remoteSrc, false);
|
||||
}
|
||||
auto restoredRanges = self->shardsAffectedByTeamFailure->cancelMove(rd.keys, destinationTeams, sourceTeams);
|
||||
TraceEvent(SevWarnAlways, "RelocateShardRestoreDataMoveSources", self->distributorId)
|
||||
.detail("Range", rd.keys)
|
||||
.detail("DataMoveID", rd.dataMoveId)
|
||||
.detail("RestoredRanges", restoredRanges.size());
|
||||
}
|
||||
|
||||
if (err.code() == error_code_data_move_dest_team_not_found && retryAfterDestinationTeamFailure) {
|
||||
// randomId participates in RelocateData's queue ordering, so a new attempt needs a new identity.
|
||||
RelocateShard retry = makeDestinationFailureRetry(rd, deterministicRandom()->randomUniqueID());
|
||||
self->output.send(retry);
|
||||
TraceEvent(SevWarnAlways, "RelocateShardRetryDestinationTeamFailure", self->distributorId)
|
||||
.detail("Range", rd.keys)
|
||||
.detail("DataMoveID", rd.dataMoveId)
|
||||
.detail("Priority", rd.priority)
|
||||
.detail("Reason", rd.reason.toString())
|
||||
.detail("TraceID", retry.traceId)
|
||||
.detail("PreviousTraceID", rd.randomId);
|
||||
}
|
||||
|
||||
relocationComplete.send(rd);
|
||||
|
||||
if (doBulkLoading && err.code() != error_code_actor_cancelled && err.code() != error_code_movekeys_conflict) {
|
||||
|
|
@ -3233,6 +3315,61 @@ TEST_CASE("/DataDistribution/DDQueue/BatchDrainRelocationComplete") {
|
|||
std::cout << "BatchDrainRelocationComplete: drained " << drained << " of " << N << " completions\n";
|
||||
}
|
||||
|
||||
TEST_CASE("/DataDistribution/DDQueue/RetryDestinationTeamFailure") {
|
||||
KeyRange keys(KeyRangeRef("a"_sr, "b"_sr));
|
||||
KeyRange parent(KeyRangeRef(""_sr, "z"_sr));
|
||||
RelocateShard original(keys, DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT, UID(1, 2));
|
||||
original.setParentRange(parent);
|
||||
RelocateData rd(original);
|
||||
rd.priority = SERVER_KNOBS->PRIORITY_TEAM_UNHEALTHY;
|
||||
rd.boundaryPriority = SERVER_KNOBS->PRIORITY_SPLIT_SHARD;
|
||||
rd.healthPriority = SERVER_KNOBS->PRIORITY_TEAM_UNHEALTHY;
|
||||
rd.wantsNewServers = true;
|
||||
|
||||
RelocateShard retryRequest = makeDestinationFailureRetry(rd, UID(3, 4));
|
||||
RelocateData retry(retryRequest);
|
||||
ASSERT(retry.keys == keys);
|
||||
ASSERT(retry.priority == rd.priority);
|
||||
ASSERT(retry.boundaryPriority == rd.boundaryPriority);
|
||||
ASSERT(retry.healthPriority == rd.healthPriority);
|
||||
ASSERT(retry.reason == rd.reason);
|
||||
ASSERT(retry.dmReason == rd.dmReason);
|
||||
ASSERT(retry.wantsNewServers == rd.wantsNewServers);
|
||||
ASSERT(retry.randomId == UID(3, 4));
|
||||
ASSERT(retry.randomId != rd.randomId);
|
||||
retry.startTime = rd.startTime;
|
||||
std::set<RelocateData, std::greater<RelocateData>> relocations;
|
||||
relocations.insert(retry);
|
||||
ASSERT(!relocations.contains(rd));
|
||||
relocations.insert(rd);
|
||||
ASSERT(relocations.size() == 2);
|
||||
ASSERT(retry.getParentRange().present());
|
||||
ASSERT(retry.getParentRange().get() == parent);
|
||||
ASSERT(retry.src.empty());
|
||||
ASSERT(retry.completeSources.empty());
|
||||
ASSERT(retry.completeDests.empty());
|
||||
ASSERT(retry.workFactor == 0);
|
||||
ASSERT(retry.cancellable);
|
||||
ASSERT(retry.dataMove == nullptr);
|
||||
ASSERT(retry.dataMoveId == anonymousShardId);
|
||||
ASSERT(!retry.isRestore());
|
||||
ASSERT(!retry.bulkLoadTask.present());
|
||||
ASSERT(shouldRetryDestinationTeamFailure(false, rd));
|
||||
ASSERT(!shouldRetryDestinationTeamFailure(true, rd));
|
||||
RelocateData nested(
|
||||
RelocateShard(KeyRangeRef("a"_sr, "aa"_sr), DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT));
|
||||
ASSERT(shouldYieldDestinationFailureRetry(retry, nested));
|
||||
ASSERT(!shouldYieldDestinationFailureRetry(retry, retry));
|
||||
RelocateData unrelated(
|
||||
RelocateShard(KeyRangeRef("c"_sr, "d"_sr), DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT));
|
||||
ASSERT(!shouldYieldDestinationFailureRetry(retry, unrelated));
|
||||
RelocateData restore = rd;
|
||||
restore.dataMove = std::make_shared<DataMove>();
|
||||
ASSERT(shouldRetryDestinationTeamFailure(false, restore));
|
||||
ASSERT(!shouldRetryDestinationTeamFailure(true, restore));
|
||||
return Void();
|
||||
}
|
||||
|
||||
TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") {
|
||||
Reference<DDQueue> self = makeReference<DDQueue>();
|
||||
DDQueueImpl::RunState state(self);
|
||||
|
|
|
|||
|
|
@ -415,11 +415,6 @@ std::string describeSplit(KeyRange keys, Standalone<VectorRef<KeyRef>>& splitKey
|
|||
|
||||
return s;
|
||||
}
|
||||
void traceSplit(KeyRange keys, Standalone<VectorRef<KeyRef>>& splitKeys) {
|
||||
auto s = describeSplit(keys, splitKeys);
|
||||
TraceEvent(SevInfo, "ExecutingShardSplit").detail("AtKeys", s);
|
||||
}
|
||||
|
||||
void executeShardSplit(DataDistributionTracker* self,
|
||||
KeyRange keys,
|
||||
Standalone<VectorRef<KeyRef>> splitKeys,
|
||||
|
|
@ -465,39 +460,6 @@ void executeShardSplit(DataDistributionTracker* self,
|
|||
self->actors.add(changeSizes(self, keys, shardSize->get().get().metrics.bytes, "ShardSplit"));
|
||||
}
|
||||
|
||||
struct RangeToSplit {
|
||||
RangeMap<Standalone<StringRef>, ShardTrackedData, KeyRangeRef>::iterator shard;
|
||||
Standalone<VectorRef<KeyRef>> faultLines;
|
||||
|
||||
RangeToSplit(RangeMap<Standalone<StringRef>, ShardTrackedData, KeyRangeRef>::iterator shard,
|
||||
Standalone<VectorRef<KeyRef>> faultLines)
|
||||
: shard(shard), faultLines(faultLines) {}
|
||||
};
|
||||
|
||||
bool faultLinesMatch(std::vector<RangeToSplit>& ranges, std::vector<std::vector<KeyRef>>& expectedFaultLines) {
|
||||
if (ranges.size() != expectedFaultLines.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& range : ranges) {
|
||||
KeyRangeRef keys = KeyRangeRef(range.shard->begin(), range.shard->end());
|
||||
traceSplit(keys, range.faultLines);
|
||||
}
|
||||
|
||||
for (int r = 0; r < ranges.size(); r++) {
|
||||
if (ranges[r].faultLines.size() != expectedFaultLines[r].size()) {
|
||||
return false;
|
||||
}
|
||||
for (int fl = 0; fl < ranges[r].faultLines.size(); fl++) {
|
||||
if (ranges[r].faultLines[fl] != expectedFaultLines[r][fl]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<Void> shardSplitter(DataDistributionTracker* self,
|
||||
KeyRange keys,
|
||||
Reference<AsyncVar<Optional<ShardMetrics>>> shardSize,
|
||||
|
|
|
|||
|
|
@ -420,41 +420,6 @@ Future<Void> monitorBackupPartitionRequired(Database cx, KeyRangeMap<ShardTracke
|
|||
}
|
||||
}
|
||||
|
||||
// Ensures that the serverKeys key space is properly coalesced
|
||||
// This method is only used for testing and is not implemented in a manner that is safe for large databases
|
||||
Future<Void> debugCheckCoalescing(Database cx) {
|
||||
Transaction tr(cx);
|
||||
while (true) {
|
||||
Error err;
|
||||
try {
|
||||
RangeResult serverList = co_await tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY);
|
||||
ASSERT(!serverList.more && serverList.size() < CLIENT_KNOBS->TOO_MANY);
|
||||
|
||||
int i{ 0 };
|
||||
for (i = 0; i < serverList.size(); i++) {
|
||||
UID id = decodeServerListValue(serverList[i].value).id();
|
||||
RangeResult ranges = co_await krmGetRanges(&tr, serverKeysPrefixFor(id), allKeys);
|
||||
ASSERT(ranges.end()[-1].key == allKeys.end);
|
||||
|
||||
for (int j = 0; j < ranges.size() - 2; j++) {
|
||||
if (ranges[j].value == ranges[j + 1].value) {
|
||||
TraceEvent(SevError, "UncoalescedValues", id)
|
||||
.detail("Key1", ranges[j].key)
|
||||
.detail("Key2", ranges[j + 1].key)
|
||||
.detail("Value", ranges[j].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("DoneCheckingCoalescing").log();
|
||||
co_return;
|
||||
} catch (Error& e) {
|
||||
err = e;
|
||||
}
|
||||
co_await tr.onError(err);
|
||||
}
|
||||
}
|
||||
|
||||
struct DataDistributor;
|
||||
void runAuditStorage(
|
||||
Reference<DataDistributor> self,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,16 @@ struct RelocateShard {
|
|||
|
||||
UID traceId; // track the lifetime of this relocate shard
|
||||
|
||||
struct RetryRelocationIntent {
|
||||
int boundaryPriority;
|
||||
int healthPriority;
|
||||
bool wantsNewServers;
|
||||
};
|
||||
|
||||
// Retry-only overrides used when an in-flight relocation must be recreated without carrying
|
||||
// attempt-specific state. Queue coalescing can update these independently of moveReason.
|
||||
Optional<RetryRelocationIntent> retryIntent;
|
||||
|
||||
// Initialization when define is a better practice. We should avoid assignment of member after definition.
|
||||
// static RelocateShard emptyRelocateShard() { return {}; }
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,65 @@ void ShardsAffectedByTeamFailure::moveShard(KeyRangeRef keys, std::vector<Team>
|
|||
check();
|
||||
}
|
||||
|
||||
std::vector<KeyRange> ShardsAffectedByTeamFailure::cancelMove(KeyRangeRef keys,
|
||||
const std::vector<Team>& destinationTeams,
|
||||
const std::vector<Team>& sourceTeams) {
|
||||
std::vector<KeyRange> restoredRanges;
|
||||
// A later shard split or merge can leave the cancelled move range strictly inside a tracked shard. Recreate only
|
||||
// the move's boundary points before removing its destinations. defineShard() would merge all tracked shards inside
|
||||
// keys, losing the distinct destinations of overlapping newer moves.
|
||||
std::vector<KeyRange> rangesToSplit;
|
||||
auto beginRange = shard_teams.rangeContaining(keys.begin);
|
||||
if (beginRange->begin() != keys.begin) {
|
||||
rangesToSplit.push_back(beginRange->range());
|
||||
}
|
||||
auto endRange = shard_teams.rangeContaining(keys.end);
|
||||
if (endRange->begin() != keys.end && (rangesToSplit.empty() || rangesToSplit.back() != endRange->range())) {
|
||||
rangesToSplit.push_back(endRange->range());
|
||||
}
|
||||
for (const auto& range : rangesToSplit) {
|
||||
for (const auto& team : shard_teams.rangeContaining(range.begin)->value().first) {
|
||||
erase(team, range);
|
||||
}
|
||||
}
|
||||
shard_teams.modify(keys);
|
||||
for (const auto& range : rangesToSplit) {
|
||||
for (auto splitRange : shard_teams.containedRanges(range)) {
|
||||
for (const auto& team : splitRange.value().first) {
|
||||
insert(team, splitRange.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
auto ranges = shard_teams.containedRanges(keys);
|
||||
for (auto it = ranges.begin(); it != ranges.end(); ++it) {
|
||||
std::vector<Team> retainedTeams;
|
||||
for (const auto& team : it->value().first) {
|
||||
if (std::find(destinationTeams.begin(), destinationTeams.end(), team) == destinationTeams.end()) {
|
||||
retainedTeams.push_back(team);
|
||||
}
|
||||
}
|
||||
if (retainedTeams.size() == it->value().first.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
KeyRange range = it->range();
|
||||
for (const auto& team : it->value().first) {
|
||||
erase(team, range);
|
||||
}
|
||||
const auto& replacementTeams = retainedTeams.empty() ? sourceTeams : retainedTeams;
|
||||
for (const auto& team : replacementTeams) {
|
||||
insert(team, range);
|
||||
}
|
||||
it->value().first = replacementTeams;
|
||||
if (retainedTeams.empty()) {
|
||||
it->value().second.clear();
|
||||
}
|
||||
restoredRanges.push_back(range);
|
||||
}
|
||||
check();
|
||||
return restoredRanges;
|
||||
}
|
||||
|
||||
void ShardsAffectedByTeamFailure::finishMove(KeyRangeRef keys) {
|
||||
auto ranges = shard_teams.containedRanges(keys);
|
||||
for (auto it = ranges.begin(); it != ranges.end(); ++it) {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,11 @@ public:
|
|||
// moveShard never change the shard boundary but just change the team value. Move keys to destinationTeams by
|
||||
// updating shard_teams, the old destination teams will be added to new source teams.
|
||||
void moveShard(KeyRangeRef keys, std::vector<Team> destinationTeam);
|
||||
// Remove a cancelled move's destination teams from contained shards. Restore the move's source teams when no
|
||||
// newer destination remains; otherwise retain the newer destination teams.
|
||||
std::vector<KeyRange> cancelMove(KeyRangeRef keys,
|
||||
const std::vector<Team>& destinationTeams,
|
||||
const std::vector<Team>& sourceTeams);
|
||||
// finishMove never change the shard boundary but just clear the old source team value
|
||||
void finishMove(KeyRangeRef keys);
|
||||
// a convenient function for (defineShard, moveShard, finishMove) pipeline
|
||||
|
|
|
|||
|
|
@ -164,3 +164,67 @@ TEST_CASE("/DataDistributor/ShardsAffectedByTeamFailure/RetryMergedShardAfterPar
|
|||
|
||||
return Void();
|
||||
}
|
||||
|
||||
TEST_CASE("/DataDistributor/ShardsAffectedByTeamFailure/CancelMove") {
|
||||
ShardsAffectedByTeamFailure shards;
|
||||
shards.setCheckMode(ShardsAffectedByTeamFailure::CheckMode::ForceCheck);
|
||||
|
||||
const UID source1(1, 0), source2(2, 0), destination1(3, 0), destination2(4, 0), redirected1(5, 0),
|
||||
redirected2(6, 0);
|
||||
const ShardsAffectedByTeamFailure::Team source({ source1, source2 }, true);
|
||||
const ShardsAffectedByTeamFailure::Team destination({ destination1, destination2 }, true);
|
||||
const ShardsAffectedByTeamFailure::Team redirected({ redirected1, redirected2 }, true);
|
||||
const KeyRange moveRange = KeyRangeRef("a"_sr, "c"_sr);
|
||||
const KeyRange leftRange = KeyRangeRef("a"_sr, "b"_sr);
|
||||
const KeyRange rightRange = KeyRangeRef("b"_sr, "c"_sr);
|
||||
|
||||
shards.assignRangeToTeams(allKeys, { source });
|
||||
shards.defineShard(moveRange);
|
||||
shards.moveShard(moveRange, { destination });
|
||||
shards.defineShard(leftRange);
|
||||
|
||||
auto restored = shards.cancelMove(moveRange, { destination }, { source });
|
||||
ASSERT((restored == std::vector<KeyRange>{ leftRange, rightRange }));
|
||||
ASSERT(shards.getTeamsForFirstShard(leftRange).first == std::vector<ShardsAffectedByTeamFailure::Team>{ source });
|
||||
ASSERT(shards.getTeamsForFirstShard(rightRange).first == std::vector<ShardsAffectedByTeamFailure::Team>{ source });
|
||||
ASSERT(shards.getTeamsForFirstShard(leftRange).second.empty());
|
||||
ASSERT(shards.getTeamsForFirstShard(rightRange).second.empty());
|
||||
ASSERT_EQ(shards.getNumberOfShards(destination), 0);
|
||||
ASSERT_EQ(shards.getNumberOfShards(source), 4);
|
||||
|
||||
shards.moveShard(moveRange, { destination });
|
||||
shards.moveShard(rightRange, { redirected });
|
||||
restored = shards.cancelMove(moveRange, { destination }, { source });
|
||||
ASSERT((restored == std::vector<KeyRange>{ leftRange }));
|
||||
ASSERT(shards.getTeamsForFirstShard(leftRange).first == std::vector<ShardsAffectedByTeamFailure::Team>{ source });
|
||||
ASSERT(shards.getTeamsForFirstShard(rightRange).first ==
|
||||
std::vector<ShardsAffectedByTeamFailure::Team>{ redirected });
|
||||
ASSERT_EQ(shards.getNumberOfShards(destination), 0);
|
||||
ASSERT_EQ(shards.getNumberOfShards(redirected), 1);
|
||||
|
||||
shards.moveShard(moveRange, { destination });
|
||||
shards.moveShard(KeyRangeRef("aa"_sr, "ab"_sr), { redirected });
|
||||
restored = shards.cancelMove(moveRange, { destination }, { source });
|
||||
ASSERT((restored == std::vector<KeyRange>{ leftRange, rightRange }));
|
||||
ASSERT(shards.getTeamsForFirstShard(leftRange).first ==
|
||||
std::vector<ShardsAffectedByTeamFailure::Team>{ redirected });
|
||||
ASSERT(shards.getTeamsForFirstShard(rightRange).first == std::vector<ShardsAffectedByTeamFailure::Team>{ source });
|
||||
ASSERT_EQ(shards.getNumberOfShards(destination), 0);
|
||||
ASSERT_EQ(shards.getNumberOfShards(redirected), 1);
|
||||
|
||||
ShardsAffectedByTeamFailure partialShards;
|
||||
partialShards.setCheckMode(ShardsAffectedByTeamFailure::CheckMode::ForceCheck);
|
||||
partialShards.assignRangeToTeams(allKeys, { source });
|
||||
partialShards.moveShard(allKeys, { destination });
|
||||
const KeyRange partialCancelRange = KeyRangeRef("a"_sr, "b"_sr);
|
||||
restored = partialShards.cancelMove(partialCancelRange, { destination }, { source });
|
||||
ASSERT((restored == std::vector<KeyRange>{ partialCancelRange }));
|
||||
ASSERT(partialShards.getTeamsForFirstShard(partialCancelRange).first ==
|
||||
std::vector<ShardsAffectedByTeamFailure::Team>{ source });
|
||||
ASSERT(partialShards.getTeamsForFirstShard(KeyRangeRef("b"_sr, "c"_sr)).first ==
|
||||
std::vector<ShardsAffectedByTeamFailure::Team>{ destination });
|
||||
ASSERT_EQ(partialShards.getNumberOfShards(destination), 2);
|
||||
ASSERT_EQ(partialShards.getNumberOfShards(source), 1);
|
||||
|
||||
return Void();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
#include "fdbserver/core/FDBSimulationPolicy.h"
|
||||
#include "fdbserver/tester/TestEncryptionUtils.h"
|
||||
#include "fdbserver/tester/tester.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/worker/Worker.h"
|
||||
#include "fdbserver/mocks3/MockS3Server.h"
|
||||
#ifdef WITH_ROCKSDB
|
||||
|
|
@ -410,68 +410,6 @@ Future<Void> metricsReport() {
|
|||
}
|
||||
}
|
||||
|
||||
void testSerializationSpeed() {
|
||||
double tstart;
|
||||
double build = 0, serialize = 0, deserialize = 0, copy = 0, deallocate = 0;
|
||||
double bytes = 0;
|
||||
double testBegin = timer();
|
||||
for (int a = 0; a < 10000; a++) {
|
||||
{
|
||||
tstart = timer();
|
||||
|
||||
Arena batchArena;
|
||||
VectorRef<CommitTransactionRef> batch;
|
||||
batch.resize(batchArena, 1000);
|
||||
for (int t = 0; t < batch.size(); t++) {
|
||||
CommitTransactionRef& tr = batch[t];
|
||||
tr.read_snapshot = 0;
|
||||
for (int i = 0; i < 2; i++)
|
||||
tr.mutations.push_back_deep(batchArena,
|
||||
MutationRef(MutationRef::SetValue, "KeyABCDE"_sr, "SomeValu"_sr));
|
||||
tr.mutations.push_back_deep(batchArena,
|
||||
MutationRef(MutationRef::ClearRange, "BeginKey"_sr, "EndKeyAB"_sr));
|
||||
}
|
||||
|
||||
build += timer() - tstart;
|
||||
|
||||
tstart = timer();
|
||||
|
||||
BinaryWriter wr(IncludeVersion());
|
||||
wr << batch;
|
||||
|
||||
bytes += wr.getLength();
|
||||
|
||||
serialize += timer() - tstart;
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
tstart = timer();
|
||||
Arena arena;
|
||||
StringRef data(arena, StringRef((const uint8_t*)wr.getData(), wr.getLength()));
|
||||
copy += timer() - tstart;
|
||||
|
||||
tstart = timer();
|
||||
ArenaReader rd(arena, data, IncludeVersion());
|
||||
VectorRef<CommitTransactionRef> batch2;
|
||||
rd >> arena >> batch2;
|
||||
|
||||
deserialize += timer() - tstart;
|
||||
}
|
||||
|
||||
tstart = timer();
|
||||
}
|
||||
deallocate += timer() - tstart;
|
||||
}
|
||||
double elapsed = (timer() - testBegin);
|
||||
printf("Test speed: %0.1f MB/sec (%0.0f/sec)\n", bytes / 1e6 / elapsed, 1000000 / elapsed);
|
||||
printf(" Build: %0.1f MB/sec\n", bytes / 1e6 / build);
|
||||
printf(" Serialize: %0.1f MB/sec\n", bytes / 1e6 / serialize);
|
||||
printf(" Copy: %0.1f MB/sec\n", bytes / 1e6 / copy);
|
||||
printf(" Deserialize: %0.1f MB/sec\n", bytes / 1e6 / deserialize);
|
||||
printf(" Deallocate: %0.1f MB/sec\n", bytes / 1e6 / deallocate);
|
||||
printf(" Bytes: %0.1f MB\n", bytes / 1e6);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void memoryTest();
|
||||
void skipListTest();
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
#include "fdbserver/logsystem/LogSystemFactory.h"
|
||||
#include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h"
|
||||
#include "fdbserver/core/WaitFailure.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbrpc/sim_validation.h"
|
||||
#include "flow/Buggify.h"
|
||||
#include "flow/IRandom.h"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
struct NetworkTestInterface {
|
||||
RequestStream<struct NetworkTestRequest> test;
|
||||
RequestStream<struct NetworkTestStreamingRequest> testStream;
|
||||
NetworkTestInterface() = default;
|
||||
explicit NetworkTestInterface(NetworkAddress remote);
|
||||
explicit NetworkTestInterface(INetwork* local);
|
||||
|
|
@ -58,29 +57,6 @@ struct NetworkTestRequest {
|
|||
}
|
||||
};
|
||||
|
||||
struct NetworkTestStreamingReply : ReplyPromiseStreamReply {
|
||||
constexpr static FileIdentifier file_identifier = 3726830;
|
||||
|
||||
int index = 0;
|
||||
NetworkTestStreamingReply() = default;
|
||||
explicit NetworkTestStreamingReply(int index) : index(index) {}
|
||||
size_t expectedSize() const { return 4e6; /*sizeof(*this);*/ }
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, ReplyPromiseStreamReply::acknowledgeToken, ReplyPromiseStreamReply::sequence, index);
|
||||
}
|
||||
};
|
||||
|
||||
struct NetworkTestStreamingRequest {
|
||||
constexpr static FileIdentifier file_identifier = 2794452;
|
||||
ReplyPromiseStream<struct NetworkTestStreamingReply> reply;
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, reply);
|
||||
}
|
||||
};
|
||||
|
||||
Future<Void> networkTestServer();
|
||||
|
||||
Future<Void> networkTestClient(std::string const& testServers);
|
||||
|
|
|
|||
|
|
@ -38,8 +38,6 @@
|
|||
#include "flow/flow.h"
|
||||
#include "flow/genericactors.actor.h"
|
||||
#include "flow/network.h"
|
||||
#include "fdbrpc/simulator.h"
|
||||
#include "fdbrpc/SimulatorProcessInfo.h"
|
||||
#include "fdbclient/IClosable.h"
|
||||
#include "fdbclient/versions.h"
|
||||
#include "fdbserver/CoroFlow.h"
|
||||
|
|
@ -105,19 +103,6 @@ void ExecCmdValueString::dbgPrint() const {
|
|||
return;
|
||||
}
|
||||
|
||||
Future<Void> destroyChildProcess(Uncancellable,
|
||||
Future<Void> parentSSClosed,
|
||||
ISimulator::ProcessInfo* childInfo,
|
||||
std::string message) {
|
||||
// This code path should be bug free
|
||||
co_await parentSSClosed;
|
||||
TraceEvent(SevDebug, message.c_str()).log();
|
||||
// This one is root cause for most failures, make sure it's okay to destroy
|
||||
g_simulator->destroyProcess(childInfo);
|
||||
// Explicitly reset the connection with the child process in case re-spawn very quickly
|
||||
FlowTransport::transport().resetConnection(childInfo->address);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(__APPLE__) || defined(__INTEL_COMPILER)
|
||||
Future<int> spawnProcess(std::string binPath,
|
||||
std::vector<std::string> paramList,
|
||||
|
|
|
|||
|
|
@ -1033,7 +1033,6 @@ struct PhysicalShard {
|
|||
if (!s.ok()) {
|
||||
logRocksDBError(s, "DestroyShard");
|
||||
logShardEvent(id, ShardOp::DESTROY, SevError, s.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto s = db->DestroyColumnFamilyHandle(cf);
|
||||
|
|
@ -1888,8 +1887,13 @@ public:
|
|||
void closeAllShards() {
|
||||
columnFamilyMap.clear();
|
||||
physicalShards.clear();
|
||||
if (db == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Close DB.
|
||||
auto s = db->Close();
|
||||
delete db;
|
||||
db = nullptr;
|
||||
if (!s.ok()) {
|
||||
logRocksDBError(s, "Close");
|
||||
return;
|
||||
|
|
@ -1898,6 +1902,9 @@ public:
|
|||
}
|
||||
|
||||
void destroyAllShards() {
|
||||
if (db == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto metadataShard = getMetaDataShard();
|
||||
KeyRange metadataRange = prefixRange(shardMappingPrefix);
|
||||
rocksdb::WriteOptions options;
|
||||
|
|
@ -1908,6 +1915,8 @@ public:
|
|||
physicalShards.clear();
|
||||
// Close DB.
|
||||
auto s = db->Close();
|
||||
delete db;
|
||||
db = nullptr;
|
||||
if (!s.ok()) {
|
||||
logRocksDBError(s, "Close");
|
||||
return;
|
||||
|
|
@ -2327,7 +2336,7 @@ struct ShardedRocksDBKeyValueStore : IKeyValueStore {
|
|||
|
||||
struct CompactShardsAction : TypedAction<CompactionWorker, CompactShardsAction> {
|
||||
std::vector<std::shared_ptr<PhysicalShard>> shards;
|
||||
std::shared_ptr<PhysicalShard> metadataShard;
|
||||
PhysicalShard* metadataShard;
|
||||
ThreadReturnPromise<Void> done;
|
||||
CompactShardsAction(std::vector<std::shared_ptr<PhysicalShard>> shards, PhysicalShard* metadataShard)
|
||||
: shards(shards), metadataShard(metadataShard) {}
|
||||
|
|
@ -3381,6 +3390,7 @@ struct ShardedRocksDBKeyValueStore : IKeyValueStore {
|
|||
self->refreshHolder.cancel();
|
||||
self->refreshRocksDBBackgroundWorkHolder.cancel();
|
||||
self->cleanUpJob.cancel();
|
||||
self->compactionJob.cancel();
|
||||
self->counterLogger.cancel();
|
||||
|
||||
try {
|
||||
|
|
@ -3388,6 +3398,12 @@ struct ShardedRocksDBKeyValueStore : IKeyValueStore {
|
|||
} catch (Error& e) {
|
||||
TraceEvent(SevError, "ShardedRocksCloseReadThreadError").errorUnsuppressed(e);
|
||||
}
|
||||
try {
|
||||
co_await self->compactionThread->stop();
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevError, "ShardedRocksCloseCompactionThreadError").errorUnsuppressed(e);
|
||||
}
|
||||
self->compactionThread.clear();
|
||||
|
||||
TraceEvent("CloseKeyValueStore").detail("DeleteKVS", deleteOnClose);
|
||||
self->iteratorPool->clear();
|
||||
|
|
@ -3402,7 +3418,6 @@ struct ShardedRocksDBKeyValueStore : IKeyValueStore {
|
|||
|
||||
try {
|
||||
co_await self->writeThread->stop();
|
||||
co_await self->compactionThread->stop();
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevError, "ShardedRocksCloseWriteThreadError").errorUnsuppressed(e);
|
||||
}
|
||||
|
|
@ -3929,6 +3944,24 @@ TEST_CASE("noSim/ShardedRocksDB/Initialization") {
|
|||
ASSERT(!directoryExists(rocksDBTestDir));
|
||||
}
|
||||
|
||||
TEST_CASE("noSim/ShardedRocksDB/CloseWithoutInit") {
|
||||
const std::string rocksDBTestDir = joinPath(params.getDataDir(), "sharded-rocksdb-close-without-init");
|
||||
platform::eraseDirectoryRecursive(rocksDBTestDir);
|
||||
|
||||
for (bool dispose : { false, true }) {
|
||||
IKeyValueStore* kvStore =
|
||||
new ShardedRocksDBKeyValueStore(rocksDBTestDir, deterministicRandom()->randomUniqueID());
|
||||
Future<Void> closed = kvStore->onClosed();
|
||||
if (dispose) {
|
||||
kvStore->dispose();
|
||||
} else {
|
||||
kvStore->close();
|
||||
}
|
||||
co_await closed;
|
||||
ASSERT(!directoryExists(rocksDBTestDir));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("noSim/ShardedRocksDB/SingleShardRead") {
|
||||
const std::string rocksDBTestDir = "sharded-rocksdb-test-db";
|
||||
platform::eraseDirectoryRecursive(rocksDBTestDir);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/core/FDBSimulationPolicy.h"
|
||||
#include "VersionedBTreeDebug.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "flow/CoroUtils.h"
|
||||
#include "flow/Error.h"
|
||||
|
|
@ -1365,10 +1365,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
int nextPowerOf2(uint32_t x) {
|
||||
return 1 << (32 - clz(x - 1));
|
||||
}
|
||||
|
||||
struct RedwoodMetrics {
|
||||
constexpr static unsigned int btreeLevels = 5;
|
||||
static int maxRecordCount;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@
|
|||
#include "fdbrpc/Stats.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/logsystem/LogSystem.h"
|
||||
#include "fdbserver/logsystem/LogSet.h"
|
||||
#include "fdbserver/logsystem/LogSystemConsumer.h"
|
||||
#include "fdbserver/logrouter/LogRouter.h"
|
||||
#include "fdbserver/logsystem/LogSystemFactory.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/core/RecoveryState.h"
|
||||
#include "fdbserver/core/TLogInterface.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbserver/logsystem/LogSystem.h"
|
||||
#include "fdbserver/logsystem/LogSet.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
|
|
|
|||
|
|
@ -372,7 +372,8 @@ Tag LogSystem::getPseudoPopTag(Tag tag, ProcessClass::ClassType type) const {
|
|||
switch (type) {
|
||||
case ProcessClass::LogRouterClass:
|
||||
if (tag.locality == tagLocalityLogRouter) {
|
||||
ASSERT(pseudoLocalities.contains(tagLocalityLogRouterMapped));
|
||||
// A log router from an earlier multi-region epoch can still forward a delayed pop after the
|
||||
// current epoch becomes single-region. Keep the mapped tag so the TLog can safely discard it.
|
||||
tag.locality = tagLocalityLogRouterMapped;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "fdbserver/logsystem/LogSystemConsumer.h"
|
||||
#include "LogSystemTypes.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "fdbserver/logsystem/LogSystem.h"
|
||||
#include "LogSystemTypes.h"
|
||||
#include "fdbrpc/FailureMonitor.h"
|
||||
#include "fdbserver/core/Knobs.h"
|
||||
#include "fdbserver/core/MutationTracking.h"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,16 @@ std::tuple<int, std::vector<TLogLockResult>, bool> makeLogGroupResults(
|
|||
|
||||
void forceLinkLogSystemRecoveryTests() {}
|
||||
|
||||
TEST_CASE("/LogSystem/GetPseudoPopTag/LogRouterWithoutMappedLocality") {
|
||||
LocalityData locality;
|
||||
auto logSystem = makeReference<LogSystem>(UID(), locality, LogEpoch(1));
|
||||
ASSERT(!logSystem->hasPseudoLocality(tagLocalityLogRouterMapped));
|
||||
|
||||
Tag tag = logSystem->getPseudoPopTag(Tag(tagLocalityLogRouter, 0), ProcessClass::LogRouterClass);
|
||||
ASSERT(tag == Tag(tagLocalityLogRouterMapped, 0));
|
||||
return Void();
|
||||
}
|
||||
|
||||
TEST_CASE("/LogSystem/PopLogRouter/CurrentGenerationAcceptsPredecessor") {
|
||||
constexpr Version generationStart = 100;
|
||||
constexpr int8_t remoteTLogLocality = 1;
|
||||
|
|
|
|||
|
|
@ -22,104 +22,7 @@
|
|||
#define FDBSERVER_LOGSYSTEM_LOGSYSTEMTYPES_H
|
||||
#pragma once
|
||||
|
||||
#include "fdbrpc/Replication.h"
|
||||
#include "fdbserver/core/LogSystemConfig.h"
|
||||
#include "fdbserver/core/DBCoreState.h"
|
||||
|
||||
struct ConnectionResetInfo : public ReferenceCounted<ConnectionResetInfo> {
|
||||
double lastReset;
|
||||
Future<Void> resetCheck;
|
||||
int slowReplies;
|
||||
int fastReplies;
|
||||
|
||||
ConnectionResetInfo() : lastReset(now()), resetCheck(Void()), slowReplies(0), fastReplies(0) {}
|
||||
};
|
||||
|
||||
// Base cursor contract for consuming a sequential log peek stream.
|
||||
struct IPeekCursor {
|
||||
virtual void setProtocolVersion(ProtocolVersion version) = 0;
|
||||
|
||||
virtual bool hasMessage() const = 0;
|
||||
virtual VectorRef<Tag> getTags() const = 0;
|
||||
virtual Arena& arena() = 0;
|
||||
virtual ArenaReader* reader() = 0;
|
||||
virtual StringRef getMessage() = 0;
|
||||
virtual StringRef getMessageWithTags() = 0;
|
||||
virtual void nextMessage() = 0;
|
||||
virtual Future<Void> getMore(TaskPriority taskID = TaskPriority::TLogPeekReply) = 0;
|
||||
virtual bool isExhausted() const = 0;
|
||||
virtual const LogMessageVersion& version() const = 0;
|
||||
virtual Version popped() const = 0;
|
||||
virtual Version getMinKnownCommittedVersion() const = 0;
|
||||
virtual void addref() = 0;
|
||||
virtual void delref() = 0;
|
||||
};
|
||||
|
||||
// Peek cursor that reports log location and can be cloned and repositioned for replay.
|
||||
struct IReplayPeekCursor : IPeekCursor {
|
||||
// Upper bound on TLogPeekReply arenas retained before or while satisfying the next getMore().
|
||||
virtual int64_t getMaxRetainedReplyCount() const = 0;
|
||||
// Applies a per-reply cap before the cursor issues its first TLog peek. Zero leaves replies uncapped.
|
||||
virtual void setReplyByteLimit(int limitBytes) = 0;
|
||||
virtual Optional<UID> getPrimaryPeekLocation() const = 0;
|
||||
virtual Optional<UID> getCurrentPeekLocation() const = 0;
|
||||
virtual Version getMaxKnownVersion() const = 0;
|
||||
virtual Reference<IReplayPeekCursor> cloneNoMore() = 0;
|
||||
virtual void advanceTo(LogMessageVersion n) = 0;
|
||||
};
|
||||
|
||||
class LogSet : NonCopyable, public ReferenceCounted<LogSet> {
|
||||
public:
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<TLogInterface>>>> logServers;
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<TLogInterface>>>> logRouters;
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<BackupInterface>>>> backupWorkers;
|
||||
std::vector<Reference<ConnectionResetInfo>> connectionResetTrackers;
|
||||
std::vector<Reference<Histogram>> tlogPushDistTrackers;
|
||||
int32_t tLogWriteAntiQuorum;
|
||||
int32_t tLogReplicationFactor;
|
||||
std::vector<LocalityData> tLogLocalities;
|
||||
TLogVersion tLogVersion;
|
||||
Reference<IReplicationPolicy> tLogPolicy;
|
||||
Reference<LocalitySet> logServerSet;
|
||||
std::vector<int> logIndexArray;
|
||||
std::vector<LocalityEntry> logEntryArray;
|
||||
bool isLocal;
|
||||
int8_t locality;
|
||||
Version startVersion;
|
||||
std::vector<Future<TLogLockResult>> replies;
|
||||
std::vector<std::vector<int>> satelliteTagLocations;
|
||||
|
||||
LogSet()
|
||||
: tLogWriteAntiQuorum(0), tLogReplicationFactor(0), isLocal(true), locality(tagLocalityInvalid),
|
||||
startVersion(invalidVersion) {}
|
||||
explicit LogSet(const TLogSet& tlogSet);
|
||||
explicit LogSet(const CoreTLogSet& coreSet);
|
||||
|
||||
std::string logRouterString();
|
||||
bool hasLogRouter(UID id) const;
|
||||
bool hasBackupWorker(UID id) const;
|
||||
std::string logServerString();
|
||||
void populateSatelliteTagLocations(int logRouterTags,
|
||||
int oldLogRouterTags,
|
||||
int txsTags,
|
||||
int oldTxsTags,
|
||||
int cdcTags);
|
||||
void checkSatelliteTagLocations();
|
||||
int bestLocationFor(Tag tag);
|
||||
void updateLocalitySet(std::vector<LocalityData> const& localities);
|
||||
bool satisfiesPolicy(const std::vector<LocalityEntry>& locations);
|
||||
void getPushLocations(
|
||||
VectorRef<Tag> tags,
|
||||
std::vector<int>& locations,
|
||||
int locationOffset,
|
||||
bool allLocations = false,
|
||||
const Optional<Reference<LocalitySet>>& restrictedLogSet = Optional<Reference<LocalitySet>>());
|
||||
|
||||
private:
|
||||
int satelliteTagLocationIndex(Tag tag) const;
|
||||
std::vector<LocalityEntry> alsoServers, resultEntries;
|
||||
std::vector<int> newLocations;
|
||||
};
|
||||
#include "fdbserver/logsystem/LogSystem.h"
|
||||
|
||||
// Leaf replay cursor backed by a single TLog interface.
|
||||
class ServerPeekCursor final : public IReplayPeekCursor, public ReferenceCounted<ServerPeekCursor> {
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* LogSet.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2026 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.
|
||||
*/
|
||||
|
||||
#ifndef FDBSERVER_LOGSYSTEM_LOGSET_H
|
||||
#define FDBSERVER_LOGSYSTEM_LOGSET_H
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fdbrpc/Locality.h"
|
||||
#include "fdbrpc/Replication.h"
|
||||
#include "fdbrpc/ReplicationPolicy.h"
|
||||
#include "fdbserver/core/DBCoreState.h"
|
||||
#include "fdbserver/core/LogSystemConfig.h"
|
||||
#include "fdbserver/core/TLogInterface.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "flow/Histogram.h"
|
||||
|
||||
struct ConnectionResetInfo : public ReferenceCounted<ConnectionResetInfo> {
|
||||
double lastReset;
|
||||
Future<Void> resetCheck;
|
||||
int slowReplies;
|
||||
int fastReplies;
|
||||
|
||||
ConnectionResetInfo() : lastReset(now()), resetCheck(Void()), slowReplies(0), fastReplies(0) {}
|
||||
};
|
||||
|
||||
class LogSet : NonCopyable, public ReferenceCounted<LogSet> {
|
||||
public:
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<TLogInterface>>>> logServers;
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<TLogInterface>>>> logRouters;
|
||||
std::vector<Reference<AsyncVar<OptionalInterface<BackupInterface>>>> backupWorkers;
|
||||
std::vector<Reference<ConnectionResetInfo>> connectionResetTrackers;
|
||||
std::vector<Reference<Histogram>> tlogPushDistTrackers;
|
||||
int32_t tLogWriteAntiQuorum;
|
||||
int32_t tLogReplicationFactor;
|
||||
std::vector<LocalityData> tLogLocalities;
|
||||
TLogVersion tLogVersion;
|
||||
Reference<IReplicationPolicy> tLogPolicy;
|
||||
Reference<LocalitySet> logServerSet;
|
||||
std::vector<int> logIndexArray;
|
||||
std::vector<LocalityEntry> logEntryArray;
|
||||
bool isLocal;
|
||||
int8_t locality;
|
||||
Version startVersion;
|
||||
std::vector<Future<TLogLockResult>> replies;
|
||||
std::vector<std::vector<int>> satelliteTagLocations;
|
||||
|
||||
LogSet()
|
||||
: tLogWriteAntiQuorum(0), tLogReplicationFactor(0), isLocal(true), locality(tagLocalityInvalid),
|
||||
startVersion(invalidVersion) {}
|
||||
explicit LogSet(const TLogSet& tlogSet);
|
||||
explicit LogSet(const CoreTLogSet& coreSet);
|
||||
|
||||
std::string logRouterString();
|
||||
bool hasLogRouter(UID id) const;
|
||||
bool hasBackupWorker(UID id) const;
|
||||
std::string logServerString();
|
||||
void populateSatelliteTagLocations(int logRouterTags,
|
||||
int oldLogRouterTags,
|
||||
int txsTags,
|
||||
int oldTxsTags,
|
||||
int cdcTags);
|
||||
void checkSatelliteTagLocations();
|
||||
int bestLocationFor(Tag tag);
|
||||
void updateLocalitySet(std::vector<LocalityData> const& localities);
|
||||
bool satisfiesPolicy(const std::vector<LocalityEntry>& locations);
|
||||
void getPushLocations(
|
||||
VectorRef<Tag> tags,
|
||||
std::vector<int>& locations,
|
||||
int locationOffset,
|
||||
bool allLocations = false,
|
||||
const Optional<Reference<LocalitySet>>& restrictedLogSet = Optional<Reference<LocalitySet>>());
|
||||
|
||||
private:
|
||||
int satelliteTagLocationIndex(Tag tag) const;
|
||||
std::vector<LocalityEntry> alsoServers, resultEntries;
|
||||
std::vector<int> newLocations;
|
||||
};
|
||||
|
||||
#endif // FDBSERVER_LOGSYSTEM_LOGSET_H
|
||||
|
|
@ -41,7 +41,8 @@
|
|||
#include "fdbserver/core/OTELSpanContextMessage.h"
|
||||
#include "fdbserver/core/SpanContextMessage.h"
|
||||
#include "fdbserver/core/TLogInterface.h"
|
||||
#include "fdbserver/core/WorkerInterface.actor.h"
|
||||
#include "fdbserver/core/WorkerInterface.h"
|
||||
#include "fdbserver/logsystem/LogSet.h"
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
|
|
@ -54,8 +55,39 @@ struct LogPushData;
|
|||
struct LocalityData;
|
||||
struct LogSystem;
|
||||
struct LogSystemConsumer;
|
||||
class LogSet;
|
||||
struct ConnectionResetInfo;
|
||||
|
||||
// Base cursor contract for consuming a sequential log peek stream.
|
||||
struct IPeekCursor {
|
||||
virtual void setProtocolVersion(ProtocolVersion version) = 0;
|
||||
|
||||
virtual bool hasMessage() const = 0;
|
||||
virtual VectorRef<Tag> getTags() const = 0;
|
||||
virtual Arena& arena() = 0;
|
||||
virtual ArenaReader* reader() = 0;
|
||||
virtual StringRef getMessage() = 0;
|
||||
virtual StringRef getMessageWithTags() = 0;
|
||||
virtual void nextMessage() = 0;
|
||||
virtual Future<Void> getMore(TaskPriority taskID = TaskPriority::TLogPeekReply) = 0;
|
||||
virtual bool isExhausted() const = 0;
|
||||
virtual const LogMessageVersion& version() const = 0;
|
||||
virtual Version popped() const = 0;
|
||||
virtual Version getMinKnownCommittedVersion() const = 0;
|
||||
virtual void addref() = 0;
|
||||
virtual void delref() = 0;
|
||||
};
|
||||
|
||||
// Peek cursor that reports log location and can be cloned and repositioned for replay.
|
||||
struct IReplayPeekCursor : IPeekCursor {
|
||||
// Upper bound on TLogPeekReply arenas retained before or while satisfying the next getMore().
|
||||
virtual int64_t getMaxRetainedReplyCount() const = 0;
|
||||
// Applies a per-reply cap before the cursor issues its first TLog peek. Zero leaves replies uncapped.
|
||||
virtual void setReplyByteLimit(int limitBytes) = 0;
|
||||
virtual Optional<UID> getPrimaryPeekLocation() const = 0;
|
||||
virtual Optional<UID> getCurrentPeekLocation() const = 0;
|
||||
virtual Version getMaxKnownVersion() const = 0;
|
||||
virtual Reference<IReplayPeekCursor> cloneNoMore() = 0;
|
||||
virtual void advanceTo(LogMessageVersion n) = 0;
|
||||
};
|
||||
|
||||
struct LogPushVersionSet {
|
||||
Version prevVersion;
|
||||
|
|
@ -533,8 +565,6 @@ std::vector<T> LogSystem::getReadyNonError(std::vector<Future<T>> const& futures
|
|||
return result;
|
||||
}
|
||||
|
||||
#include "LogSystemTypes.h"
|
||||
|
||||
template <class T>
|
||||
OldLogData::OldLogData(const T& conf)
|
||||
: logRouterTags(conf.logRouterTags), txsTags(conf.txsTags), epochBegin(conf.epochBegin), epochEnd(conf.epochEnd),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue