Merge remote-tracking branch 'origin/main' into dev/tclinkenbeard/protocol-version-quiet-database-recovery

# Conflicts:
#	fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp
This commit is contained in:
Trevor Clinkenbeard 2026-07-21 12:47:51 -07:00
commit 7ea79c300c
152 changed files with 2821 additions and 1309 deletions

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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;

View File

@ -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))

View File

@ -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.

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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)
---

View File

@ -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 |

View File

@ -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

View File

@ -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
===========

View File

@ -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 {

View File

@ -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) {

View File

@ -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;
}

View File

@ -22,7 +22,6 @@
#define FDBCLIENT_BACKUP_CONTAINER_BLOBSTORE_H
#pragma once
#include "fdbclient/AsyncFileBlobStore.h"
#include "fdbclient/BackupContainerFileSystem.h"
#include "fdbclient/IBlobStore.h"

View File

@ -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();
}

View File

@ -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);

View File

@ -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>")

View File

@ -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 );

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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) {

View File

@ -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.

View File

@ -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"

86
fdbclient/RYWIterator.h Normal file
View File

@ -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

View File

@ -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>>();

View File

@ -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();
}

View File

@ -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&);

View File

@ -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(); });

View File

@ -18,7 +18,7 @@
* limitations under the License.
*/
#include "fdbclient/WriteMap.h"
#include "WriteMap.h"
void OperationStack::reset(RYWMutation initialEntry) {
defaultConstructed = false;

View File

@ -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 {

View File

@ -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; }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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)]

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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);
}
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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"

View File

@ -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,

View File

@ -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;

View File

@ -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> {};

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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 {}; }

View File

@ -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) {

View File

@ -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

View File

@ -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();
}

View File

@ -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();

View File

@ -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"

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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"

View File

@ -18,7 +18,7 @@
* limitations under the License.
*/
#include "fdbserver/logsystem/LogSystem.h"
#include "fdbserver/logsystem/LogSet.h"
#include <limits>

View File

@ -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;

View File

@ -1,4 +1,5 @@
#include "fdbserver/logsystem/LogSystemConsumer.h"
#include "LogSystemTypes.h"
#include <algorithm>
#include <utility>

View File

@ -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"

View File

@ -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;

View File

@ -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> {

View File

@ -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

View File

@ -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