From b62566b8032b632fc0ea945a6da3457bfda3faa6 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 16:59:20 -0700 Subject: [PATCH 01/69] Add C bindings for native CDC --- bindings/c/fdb_c.cpp | 223 ++++++++++++++++ bindings/c/foundationdb/fdb_c.h | 88 ++++++ bindings/c/foundationdb/fdb_c_types.h | 1 + bindings/c/test/unit/unit_tests.cpp | 37 +++ documentation/sphinx/source/api-c.rst | 118 ++++++++ fdbclient/MultiVersionTransaction.cpp | 251 ++++++++++++++++++ fdbclient/ThreadSafeTransaction.cpp | 165 ++++++++++++ fdbclient/include/fdbclient/IClientApi.h | 10 + .../fdbclient/MultiVersionTransaction.h | 60 +++++ fdbclient/include/fdbclient/NativeCdc.h | 10 +- fdbclient/include/fdbclient/NativeCdcClient.h | 77 ++++++ .../include/fdbclient/ThreadSafeTransaction.h | 6 + flow/ApiVersion.h.cmake | 1 + flow/ApiVersions.cmake | 1 + 14 files changed, 1039 insertions(+), 9 deletions(-) create mode 100644 fdbclient/include/fdbclient/NativeCdcClient.h diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 0b25381553..8cf3a0158e 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -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 + * FDBNativeCdcConsumer -> INativeCdcConsumer */ #define TSAVB(f) ((ThreadSingleAssignmentVarBase*)(f)) #define TSAV(T, f) ((ThreadSingleAssignmentVar*)(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,134 @@ static_assert(static_cast(FDB_BG_MUTATION_TYPE_SET_VALUE) == static_cast(FDB_BG_MUTATION_TYPE_CLEAR_RANGE) == static_cast(MutationRef::Type::ClearRange), "FDB_BG_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE) == static_cast(MutationRef::Type::SetValue), + "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE) == + static_cast(MutationRef::Type::ClearRange), + "FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_ADD) == static_cast(MutationRef::Type::AddValue), + "FDB_NATIVE_CDC_MUTATION_TYPE_ADD enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_AND) == static_cast(MutationRef::Type::And), + "FDB_NATIVE_CDC_MUTATION_TYPE_AND enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_OR) == static_cast(MutationRef::Type::Or), + "FDB_NATIVE_CDC_MUTATION_TYPE_OR enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_XOR) == static_cast(MutationRef::Type::Xor), + "FDB_NATIVE_CDC_MUTATION_TYPE_XOR enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS) == + static_cast(MutationRef::Type::AppendIfFits), + "FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MAX) == static_cast(MutationRef::Type::Max), + "FDB_NATIVE_CDC_MUTATION_TYPE_MAX enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MIN) == static_cast(MutationRef::Type::Min), + "FDB_NATIVE_CDC_MUTATION_TYPE_MIN enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY) == + static_cast(MutationRef::Type::SetVersionstampedKey), + "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE) == + static_cast(MutationRef::Type::SetVersionstampedValue), + "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN) == static_cast(MutationRef::Type::ByteMin), + "FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX) == static_cast(MutationRef::Type::ByteMax), + "FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2) == static_cast(MutationRef::Type::MinV2), + "FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2 enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2) == static_cast(MutationRef::Type::AndV2), + "FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2 enum value mismatch"); +static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR) == + static_cast(MutationRef::Type::CompareAndClear), + "FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR 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 streams; +}; + +struct CNativeCdcConsumeResult { + Arena arena; + VectorRef 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 const& source) { + CNativeCdcStreamInfoArray result; + result.streams.reserve(result.arena, source.size()); + for (auto const& stream : source) { + FDBNativeCdcStreamInfo 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) { + FDBNativeCdcVersionedMutations cVersioned; + cVersioned.version = versioned.version; + cVersioned.mutation_count = versioned.mutations.size(); + cVersioned.mutations = nullptr; + if (!versioned.mutations.empty()) { + auto* cMutations = new (result.arena) FDBNativeCdcMutation[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] = + FDBNativeCdcMutation{ 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> source) { + auto result = mapThreadFuture, CNativeCdcStreamInfoArray>( + source, [](ErrorOr> source) -> ErrorOr { + if (source.isError()) { + return ErrorOr(source.getError()); + } + return makeCNativeCdcStreamInfoArray(source.get()); + }); + return (FDBFuture*)result.extractPtr(); +} + +FDBFuture* mapNativeCdcConsumeFuture(ThreadFuture source) { + auto result = mapThreadFuture( + source, [](ErrorOr source) -> ErrorOr { + if (source.isError()) { + return ErrorOr(source.getError()); + } + return makeCNativeCdcConsumeResult(source.get()); + }); + return (FDBFuture*)result.extractPtr(); +} + +} // namespace + #define TSAV_ERROR(type, error) ((FDBFuture*)(ThreadFuture(error())).extractPtr()) extern "C" DLLEXPORT const char* fdb_get_error(fdb_error_t code) { @@ -332,6 +463,30 @@ 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_native_cdc_stream_info_array(FDBFuture* f, + FDBNativeCdcStreamInfo 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_native_cdc_consumer(FDBFuture* f, FDBNativeCdcConsumer** out_consumer) { + CATCH_AND_RETURN(Reference consumer = TSAV(Reference, f)->get(); + *out_consumer = (FDBNativeCdcConsumer*)consumer.extractPtr();); +} + +extern "C" DLLEXPORT fdb_error_t +fdb_future_get_native_cdc_versioned_mutations(FDBFuture* f, + FDBNativeCdcVersionedMutations 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 +601,74 @@ extern "C" DLLEXPORT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, *out_transaction = (FDBTransaction*)tr.extractPtr();); } +extern "C" DLLEXPORT FDBFuture* fdb_database_register_native_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_native_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_native_cdc_streams(FDBDatabase* db) { + RETURN_FUTURE_ON_ERROR(CNativeCdcStreamInfoArray, + return mapNativeCdcStreamInfoFuture(DB(db)->listNativeCdcStreams());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_create_native_cdc_consumer(FDBDatabase* db, + uint8_t const* name, + int name_length) { + RETURN_FUTURE_ON_ERROR( + Reference, + return (FDBFuture*)(DB(db)->createNativeCdcConsumer(KeyRef(name, name_length)).extractPtr());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_resume_native_cdc_consumer(FDBDatabase* db, + uint64_t stream_id, + int64_t last_consumed_version) { + RETURN_FUTURE_ON_ERROR(Reference, NativeCdcCursor cursor; cursor.streamId = stream_id; + cursor.lastConsumedVersion = last_consumed_version; + return (FDBFuture*)(DB(db)->resumeNativeCdcConsumer(cursor).extractPtr());); +} + +extern "C" DLLEXPORT void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer) { + try { + NATIVE_CDC_CONSUMER(consumer)->delref(); + } catch (...) { + } +} + +extern "C" DLLEXPORT FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer) { + RETURN_FUTURE_ON_ERROR(CNativeCdcConsumeResult, + return mapNativeCdcConsumeFuture(NATIVE_CDC_CONSUMER(consumer)->consume());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer) { + RETURN_FUTURE_ON_ERROR(Void, return (FDBFuture*)(NATIVE_CDC_CONSUMER(consumer)->acknowledge().extractPtr());); +} + +extern "C" DLLEXPORT fdb_error_t fdb_native_cdc_consumer_get_position(FDBNativeCdcConsumer* 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, diff --git a/bindings/c/foundationdb/fdb_c.h b/bindings/c/foundationdb/fdb_c.h index 9f92eef88c..b855e2503f 100644 --- a/bindings/c/foundationdb/fdb_c.h +++ b/bindings/c/foundationdb/fdb_c.h @@ -187,6 +187,50 @@ typedef struct keyrange { int end_key_length; } FDBKeyRange; +/* + * Raw mutation types returned by native CDC. The numeric values match + * MutationRef::Type and, for atomic operations, FDBMutationType. + */ +typedef enum { + FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE = 0, + FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE = 1, + FDB_NATIVE_CDC_MUTATION_TYPE_ADD = 2, + FDB_NATIVE_CDC_MUTATION_TYPE_AND = 6, + FDB_NATIVE_CDC_MUTATION_TYPE_OR = 7, + FDB_NATIVE_CDC_MUTATION_TYPE_XOR = 8, + FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS = 9, + FDB_NATIVE_CDC_MUTATION_TYPE_MAX = 12, + FDB_NATIVE_CDC_MUTATION_TYPE_MIN = 13, + FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY = 14, + FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE = 15, + FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN = 16, + FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX = 17, + FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2 = 18, + FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2 = 19, + FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR = 20 +} FDBNativeCdcMutationType; + +typedef struct native_cdc_stream_info { + FDBKey name; + uint64_t stream_id; + FDBKeyRange key_range; + int64_t min_version; +} FDBNativeCdcStreamInfo; + +typedef struct native_cdc_mutation { + /* FDBNativeCdcMutationType */ uint8_t type; + const uint8_t* param1; + int param1_length; + const uint8_t* param2; + int param2_length; +} FDBNativeCdcMutation; + +typedef struct native_cdc_versioned_mutations { + int64_t version; + const FDBNativeCdcMutation* mutations; + int mutation_count; +} FDBNativeCdcVersionedMutations; + /* * 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_native_cdc_stream_info_array(FDBFuture* f, FDBNativeCdcStreamInfo const** out_streams, int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_native_cdc_consumer(FDBFuture* f, + FDBNativeCdcConsumer** out_consumer); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t +fdb_future_get_native_cdc_versioned_mutations(FDBFuture* f, + FDBNativeCdcVersionedMutations 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_native_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_native_cdc_stream(FDBDatabase* db, + uint8_t const* name, + int name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_list_native_cdc_streams(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_native_cdc_consumer(FDBDatabase* db, + uint8_t const* name, + int name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_resume_native_cdc_consumer(FDBDatabase* db, + uint64_t stream_id, + int64_t last_consumed_version); + +DLLEXPORT void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_native_cdc_consumer_get_position(FDBNativeCdcConsumer* 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. diff --git a/bindings/c/foundationdb/fdb_c_types.h b/bindings/c/foundationdb/fdb_c_types.h index 2d4fc4e082..4e877712f1 100644 --- a/bindings/c/foundationdb/fdb_c_types.h +++ b/bindings/c/foundationdb/fdb_c_types.h @@ -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_native_cdc_consumer FDBNativeCdcConsumer; typedef int fdb_error_t; typedef int fdb_bool_t; diff --git a/bindings/c/test/unit/unit_tests.cpp b/bindings/c/test/unit/unit_tests.cpp index 943c718fd6..d27a7e5f2d 100644 --- a/bindings/c/test/unit/unit_tests.cpp +++ b/bindings/c/test/unit/unit_tests.cpp @@ -1970,6 +1970,43 @@ TEST_CASE("fdb_database_get_server_protocol") { fdb_future_destroy(protocolFuture); } +TEST_CASE("native CDC metadata and synthetic consumer cursor") { + // Listing remains available when native CDC admission is disabled, so this + // exercises the C result conversion without requiring a registered stream. + FDBFuture* listFuture = fdb_database_list_native_cdc_streams(db); + REQUIRE(listFuture != nullptr); + fdb_check(fdb_future_block_until_ready(listFuture)); + + FDBNativeCdcStreamInfo const* streams = nullptr; + int streamCount = -1; + fdb_check(fdb_future_get_native_cdc_stream_info_array(listFuture, &streams, &streamCount)); + CHECK(streamCount >= 0); + if (streamCount > 0) { + CHECK(streams != nullptr); + } + fdb_future_destroy(listFuture); + + // Resuming only reconstructs a cursor-bearing client handle. It does not + // contact CDC infrastructure until consume or acknowledge is requested. + constexpr uint64_t streamId = 0x0102030405060708ULL; + constexpr int64_t lastConsumedVersion = 123456789; + FDBFuture* resumeFuture = fdb_database_resume_native_cdc_consumer(db, streamId, lastConsumedVersion); + REQUIRE(resumeFuture != nullptr); + fdb_check(fdb_future_block_until_ready(resumeFuture)); + + FDBNativeCdcConsumer* consumer = nullptr; + fdb_check(fdb_future_get_native_cdc_consumer(resumeFuture, &consumer)); + fdb_future_destroy(resumeFuture); + REQUIRE(consumer != nullptr); + + uint64_t outStreamId = 0; + int64_t outLastConsumedVersion = 0; + fdb_check(fdb_native_cdc_consumer_get_position(consumer, &outStreamId, &outLastConsumedVersion)); + CHECK(outStreamId == streamId); + CHECK(outLastConsumedVersion == lastConsumedVersion); + fdb_native_cdc_consumer_destroy(consumer); +} + 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. diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index fa27384c18..33ed7df2bc 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -543,6 +543,124 @@ An |database-blurb1| Modifications to a database are performed via transactions. ] } +Native CDC +---------- + +Native CDC exposes durable, named streams of committed mutations for one +half-open user-key range. New stream registration requires native 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. + +.. type:: FDBNativeCdcMutationType + + The raw mutation type returned by native 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. + +.. type:: FDBNativeCdcStreamInfo + + A listed native CDC stream, including its name, stable stream ID, registered + key range, and durable minimum required version. + +.. type:: FDBNativeCdcMutation + + One raw mutation within a native CDC commit-version group. + +.. type:: FDBNativeCdcVersionedMutations + + 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:: FDBNativeCdcConsumer + + An opaque, reference-counted native CDC consumer handle. A handle extracted + with :func:`fdb_future_get_native_cdc_consumer()` is owned by the caller and + remains valid after the originating future is destroyed. Destroy it exactly + once with :func:`fdb_native_cdc_consumer_destroy()`. + +.. function:: FDBFuture* fdb_database_register_native_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_native_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_native_cdc_streams(FDBDatabase* database) + + Returns the currently registered native CDC streams. Extract the result with + :func:`fdb_future_get_native_cdc_stream_info_array()`. + +.. function:: fdb_error_t fdb_future_get_native_cdc_stream_info_array(FDBFuture* future, FDBNativeCdcStreamInfo const** out_streams, int* out_count) + + Extracts the stream-info array returned by + :func:`fdb_database_list_native_cdc_streams()`. |future-get-return1| + |future-get-return2|. + + |future-memory-mine| + +.. function:: FDBFuture* fdb_database_create_native_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_native_cdc_consumer()`. + +.. function:: FDBFuture* fdb_database_resume_native_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version) + + Resumes a consumer 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. 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_native_cdc_consumer(FDBFuture* future, FDBNativeCdcConsumer** out_consumer) + + Extracts the owned consumer handle returned by + :func:`fdb_database_create_native_cdc_consumer()` or + :func:`fdb_database_resume_native_cdc_consumer()`. |future-get-return1| + |future-get-return2|. + +.. function:: void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer) + + Releases an owned native CDC consumer handle. + +.. function:: FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer) + + Long-polls for the next delivered position and complete commit-version + mutation groups. Extract the reply with + :func:`fdb_future_get_native_cdc_versioned_mutations()`. Consumption advances + the in-memory consumer position but does not release durable CDC retention. + +.. function:: fdb_error_t fdb_future_get_native_cdc_versioned_mutations(FDBFuture* future, FDBNativeCdcVersionedMutations const** out_mutations, int* out_count, int64_t* out_last_consumed_version) + + Extracts the grouped mutation reply from + :func:`fdb_native_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_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer) + + Durably acknowledges the consumer's current delivered position. Call this + only after all mutations represented through that position have been durably + processed. A consumer may have only one consume or acknowledge operation + outstanding at a time. The returned future contains no value. + +.. function:: fdb_error_t fdb_native_cdc_consumer_get_position(FDBNativeCdcConsumer* consumer, uint64_t* out_stream_id, int64_t* out_last_consumed_version) + + Returns the consumer's current cursor. + Transaction =========== diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 0c59136de0..285ba92cce 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -387,6 +387,103 @@ ThreadFuture 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(source.keyRange.beginKey), source.keyRange.beginKeyLength), + KeyRef(static_cast(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 { +public: + DLNativeCdcConsumer(Reference api, FdbCApi::FDBNativeCdcConsumer* consumer) + : api(api), consumer(consumer) {} + + ~DLNativeCdcConsumer() override { + if (consumer && api->nativeCdcConsumerDestroy) { + api->nativeCdcConsumerDestroy(consumer); + } + } + + ThreadFuture consume() override { + if (!api->nativeCdcConsumerConsume || !api->futureGetNativeCdcVersionedMutations) { + return unsupported_operation(); + } + FdbCApi::FDBFuture* f = api->nativeCdcConsumerConsume(consumer); + auto self = Reference::addRef(this); + return toThreadFuture(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 = self->getPosition(); + return result; + }); + } + + ThreadFuture acknowledge() override { + if (!api->nativeCdcConsumerAcknowledge) { + return unsupported_operation(); + } + FdbCApi::FDBFuture* f = api->nativeCdcConsumerAcknowledge(consumer); + auto self = Reference::addRef(this); + return toThreadFuture(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::addref(); } + void delref() override { ThreadSafeReferenceCounted::delref(); } + +private: + const Reference api; + FdbCApi::FDBNativeCdcConsumer* const consumer; +}; + +} // namespace + // DLDatabase DLDatabase::DLDatabase(Reference api, ThreadFuture dbFuture) : api(api), db(nullptr) { addref(); @@ -452,6 +549,80 @@ ThreadFuture DLDatabase::createSnapshot(const StringRef& uid, const String return toThreadFuture(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { return Void(); }); } +ThreadFuture DLDatabase::registerNativeCdcStream(KeyRef name, 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(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { + uint64_t streamId; + FdbCApi::fdb_error_t error = api->futureGetUInt64(f, &streamId); + ASSERT(!error); + return streamId; + }); +} + +ThreadFuture DLDatabase::removeNativeCdcStream(KeyRef name) { + if (!api->databaseRemoveNativeCdcStream) { + return unsupported_operation(); + } + + FdbCApi::FDBFuture* f = api->databaseRemoveNativeCdcStream(db, name.begin(), name.size()); + return toThreadFuture(api, f, [](FdbCApi::FDBFuture*, FdbCApi*) { return Void(); }); +} + +ThreadFuture> DLDatabase::listNativeCdcStreams() { + if (!api->databaseListNativeCdcStreams || !api->futureGetNativeCdcStreamInfoArray) { + return unsupported_operation(); + } + + FdbCApi::FDBFuture* f = api->databaseListNativeCdcStreams(db); + return toThreadFuture>(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 result; + result.reserve(count); + for (int i = 0; i < count; ++i) { + result.push_back(copyNativeCdcStreamInfo(streams[i])); + } + return result; + }); +} + +ThreadFuture> DLDatabase::createNativeCdcConsumer(KeyRef name) { + if (!api->databaseCreateNativeCdcConsumer || !api->futureGetNativeCdcConsumer) { + return unsupported_operation(); + } + + FdbCApi::FDBFuture* f = api->databaseCreateNativeCdcConsumer(db, name.begin(), name.size()); + return toThreadFuture>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { + FdbCApi::FDBNativeCdcConsumer* consumer; + FdbCApi::fdb_error_t error = api->futureGetNativeCdcConsumer(f, &consumer); + ASSERT(!error); + return Reference( + makeReference(Reference::addRef(api), consumer)); + }); +} + +ThreadFuture> DLDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { + if (!api->databaseResumeNativeCdcConsumer || !api->futureGetNativeCdcConsumer) { + return unsupported_operation(); + } + + FdbCApi::FDBFuture* f = api->databaseResumeNativeCdcConsumer(db, cursor.streamId, cursor.lastConsumedVersion); + return toThreadFuture>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { + FdbCApi::FDBNativeCdcConsumer* consumer; + FdbCApi::fdb_error_t error = api->futureGetNativeCdcConsumer(f, &consumer); + ASSERT(!error); + return Reference( + makeReference(Reference::addRef(api), consumer)); + }); +} + ThreadFuture DLDatabase::createSharedState() { if (!api->databaseCreateSharedState) { return unsupported_operation(); @@ -600,6 +771,51 @@ void DLApi::init() { fdbCPath, "fdb_database_get_client_status", headerVersion >= ApiVersion::withGetClientStatus().version()); + loadClientFunction(&api->databaseRegisterNativeCdcStream, + lib, + fdbCPath, + "fdb_database_register_native_cdc_stream", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseRemoveNativeCdcStream, + lib, + fdbCPath, + "fdb_database_remove_native_cdc_stream", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseListNativeCdcStreams, + lib, + fdbCPath, + "fdb_database_list_native_cdc_streams", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseCreateNativeCdcConsumer, + lib, + fdbCPath, + "fdb_database_create_native_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseResumeNativeCdcConsumer, + lib, + fdbCPath, + "fdb_database_resume_native_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerDestroy, + lib, + fdbCPath, + "fdb_native_cdc_consumer_destroy", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerConsume, + lib, + fdbCPath, + "fdb_native_cdc_consumer_consume", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerAcknowledge, + lib, + fdbCPath, + "fdb_native_cdc_consumer_acknowledge", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerGetPosition, + lib, + fdbCPath, + "fdb_native_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); @@ -688,6 +904,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_native_cdc_stream_info_array", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->futureGetNativeCdcConsumer, + lib, + fdbCPath, + "fdb_future_get_native_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->futureGetNativeCdcVersionedMutations, + lib, + fdbCPath, + "fdb_future_get_native_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); @@ -1436,6 +1667,26 @@ ThreadFuture MultiVersionDatabase::createSnapshot(const StringRef& uid, co return executeOperation(&IDatabase::createSnapshot, uid, snapshot_command); } +ThreadFuture MultiVersionDatabase::registerNativeCdcStream(KeyRef name, KeyRangeRef keys) { + return executeOperation(&IDatabase::registerNativeCdcStream, std::move(name), std::move(keys)); +} + +ThreadFuture MultiVersionDatabase::removeNativeCdcStream(KeyRef name) { + return executeOperation(&IDatabase::removeNativeCdcStream, std::move(name)); +} + +ThreadFuture> MultiVersionDatabase::listNativeCdcStreams() { + return executeOperation(&IDatabase::listNativeCdcStreams); +} + +ThreadFuture> MultiVersionDatabase::createNativeCdcConsumer(KeyRef name) { + return executeOperation(&IDatabase::createNativeCdcConsumer, std::move(name)); +} + +ThreadFuture> MultiVersionDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { + return executeOperation(&IDatabase::resumeNativeCdcConsumer, std::move(cursor)); +} + ThreadFuture MultiVersionDatabase::createSharedState() { return executeOperation(&IDatabase::createSharedState); } diff --git a/fdbclient/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index 5e0a881020..d445d15d13 100644 --- a/fdbclient/ThreadSafeTransaction.cpp +++ b/fdbclient/ThreadSafeTransaction.cpp @@ -18,6 +18,9 @@ * limitations under the License. */ +#include +#include + #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 const& state, NativeCdcCursor cursor) { + ThreadSpinLockHolder holder(state->lock); + state->cursor = cursor; +} + +NativeCdcCursor readCursor(std::shared_ptr 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 consumeNativeCdc(Reference consumer, + std::shared_ptr 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 acknowledgeNativeCdc(Reference consumer, + std::shared_ptr 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 { +public: + ThreadSafeNativeCdcConsumer(NativeCdcConsumer* consumer, NativeCdcCursor cursor) + : consumer(consumer), cursorState(std::make_shared(cursor)) {} + + ~ThreadSafeNativeCdcConsumer() override { + NativeCdcConsumer* consumer = this->consumer; + onMainThreadVoid([consumer]() { consumer->delref(); }); + } + + ThreadFuture consume() override { + auto self = Reference::addRef(this); + return onMainThread([self]() -> Future { + return consumeNativeCdc(Reference::addRef(self->consumer), self->cursorState); + }); + } + + ThreadFuture acknowledge() override { + auto self = Reference::addRef(this); + return onMainThread([self]() -> Future { + return acknowledgeNativeCdc(Reference::addRef(self->consumer), self->cursorState); + }); + } + + NativeCdcCursor getPosition() override { return readCursor(cursorState); } + + void addref() override { ThreadSafeReferenceCounted::addref(); } + void delref() override { ThreadSafeReferenceCounted::delref(); } + +private: + NativeCdcConsumer* consumer; + std::shared_ptr cursorState; +}; + +Reference wrapNativeCdcConsumer(Reference consumer) { + NativeCdcCursor cursor = toClientCursor(consumer->position()); + return makeReference(consumer.extractPtr(), cursor); +} + +} // namespace + ThreadFuture ThreadSafeDatabase::onConnected() { DatabaseContext* db = this->db; return onMainThread([db]() -> Future { @@ -103,6 +221,53 @@ ThreadFuture ThreadSafeDatabase::createSnapshot(const StringRef& uid, cons }); } +ThreadFuture ThreadSafeDatabase::registerNativeCdcStream(KeyRef name, KeyRangeRef keys) { + DatabaseContext* db = this->db; + Key nameCopy(name); + KeyRange keysCopy(keys); + return onMainThread([db, nameCopy, keysCopy]() -> Future { + db->checkDeferredError(); + return registerNativeCdcStreamClient(Database(Reference::addRef(db)), nameCopy, keysCopy); + }); +} + +ThreadFuture ThreadSafeDatabase::removeNativeCdcStream(KeyRef name) { + DatabaseContext* db = this->db; + Key nameCopy(name); + return onMainThread([db, nameCopy]() -> Future { + db->checkDeferredError(); + return removeNativeCdcStreamClient(Database(Reference::addRef(db)), nameCopy); + }); +} + +ThreadFuture> ThreadSafeDatabase::listNativeCdcStreams() { + DatabaseContext* db = this->db; + return onMainThread([db]() -> Future> { + db->checkDeferredError(); + return listNativeCdcStreamsClient(Database(Reference::addRef(db))); + }); +} + +ThreadFuture> ThreadSafeDatabase::createNativeCdcConsumer(KeyRef name) { + DatabaseContext* db = this->db; + Key nameCopy(name); + return onMainThread([db, nameCopy]() -> Future> { + db->checkDeferredError(); + return map(::createNativeCdcConsumer(Database(Reference::addRef(db)), nameCopy), + [](Reference consumer) { return wrapNativeCdcConsumer(std::move(consumer)); }); + }); +} + +ThreadFuture> ThreadSafeDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { + DatabaseContext* db = this->db; + return onMainThread([db, cursor]() -> Future> { + db->checkDeferredError(); + Reference consumer = + ::resumeNativeCdcConsumer(Database(Reference::addRef(db)), toNativeCursor(cursor)); + return Future>(wrapNativeCdcConsumer(std::move(consumer))); + }); +} + ThreadFuture ThreadSafeDatabase::createSharedState() { DatabaseContext* db = this->db; return onMainThread([db]() -> Future { return db->initSharedState(); }); diff --git a/fdbclient/include/fdbclient/IClientApi.h b/fdbclient/include/fdbclient/IClientApi.h index d7d92765f7..0cba7b84c9 100644 --- a/fdbclient/include/fdbclient/IClientApi.h +++ b/fdbclient/include/fdbclient/IClientApi.h @@ -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.actor.h" @@ -152,6 +153,15 @@ public: // Management API, create snapshot virtual ThreadFuture 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 registerNativeCdcStream(KeyRef name, KeyRangeRef keys) = 0; + virtual ThreadFuture removeNativeCdcStream(KeyRef name) = 0; + virtual ThreadFuture> listNativeCdcStreams() = 0; + virtual ThreadFuture> createNativeCdcConsumer(KeyRef name) = 0; + virtual ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) = 0; + // Interface to manage shared state across multiple connections to the same Database virtual ThreadFuture createSharedState() = 0; virtual void setSharedState(DatabaseSharedState* p) = 0; diff --git a/fdbclient/include/fdbclient/MultiVersionTransaction.h b/fdbclient/include/fdbclient/MultiVersionTransaction.h index 28f327c014..94634c07bb 100644 --- a/fdbclient/include/fdbclient/MultiVersionTransaction.h +++ b/fdbclient/include/fdbclient/MultiVersionTransaction.h @@ -40,6 +40,7 @@ struct FdbCApi : public ThreadSafeReferenceCounted { 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 { 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 { 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, @@ -250,6 +292,14 @@ struct FdbCApi : public ThreadSafeReferenceCounted { 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); @@ -375,6 +425,11 @@ public: ThreadFuture rebootWorker(const StringRef& address, bool check, int duration) override; ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; + ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; + ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture> listNativeCdcStreams() override; + ThreadFuture> createNativeCdcConsumer(KeyRef name) override; + ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; @@ -687,6 +742,11 @@ public: ThreadFuture rebootWorker(const StringRef& address, bool check, int duration) override; ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; + ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; + ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture> listNativeCdcStreams() override; + ThreadFuture> createNativeCdcConsumer(KeyRef name) override; + ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; diff --git a/fdbclient/include/fdbclient/NativeCdc.h b/fdbclient/include/fdbclient/NativeCdc.h index fe5b0aa1c3..2152ac88d5 100644 --- a/fdbclient/include/fdbclient/NativeCdc.h +++ b/fdbclient/include/fdbclient/NativeCdc.h @@ -22,18 +22,10 @@ #define FDBCLIENT_NATIVECDC_H #pragma once -#include - #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 { static Future consumeImpl(Reference self); static Future acknowledgeImpl(Reference self); diff --git a/fdbclient/include/fdbclient/NativeCdcClient.h b/fdbclient/include/fdbclient/NativeCdcClient.h new file mode 100644 index 0000000000..fd5d8ee69b --- /dev/null +++ b/fdbclient/include/fdbclient/NativeCdcClient.h @@ -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 +#include + +#include "fdbclient/FDBTypes.h" +#include "flow/ThreadHelper.actor.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 mutations; +}; + +struct NativeCdcConsumeResult { + std::vector 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 consume() = 0; + virtual ThreadFuture acknowledge() = 0; + virtual NativeCdcCursor getPosition() = 0; + + virtual void addref() = 0; + virtual void delref() = 0; +}; + +#endif // FDBCLIENT_NATIVECDCCLIENT_H diff --git a/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 94699beefb..92cbadc3c7 100644 --- a/fdbclient/include/fdbclient/ThreadSafeTransaction.h +++ b/fdbclient/include/fdbclient/ThreadSafeTransaction.h @@ -58,6 +58,12 @@ public: ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; + ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; + ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture> listNativeCdcStreams() override; + ThreadFuture> createNativeCdcConsumer(KeyRef name) override; + ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; + ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; diff --git a/flow/ApiVersion.h.cmake b/flow/ApiVersion.h.cmake index 4f01ef7070..a0a94eaed4 100644 --- a/flow/ApiVersion.h.cmake +++ b/flow/ApiVersion.h.cmake @@ -83,6 +83,7 @@ public: // introduced features API_VERSION_FEATURE(@FDB_AV_GET_CLIENT_STATUS@, GetClientStatus); API_VERSION_FEATURE(@FDB_AV_INITIALIZE_TRACE_ON_SETUP@, InitializeTraceOnSetup); API_VERSION_FEATURE(@FDB_AV_TENANT_GET_ID@, TenantGetId); + API_VERSION_FEATURE(@FDB_AV_NATIVE_CDC_API@, NativeCdcApi); }; #endif // FLOW_CODE_API_VERSION_H diff --git a/flow/ApiVersions.cmake b/flow/ApiVersions.cmake index e94ff5d9d2..788b42fc7d 100644 --- a/flow/ApiVersions.cmake +++ b/flow/ApiVersions.cmake @@ -25,3 +25,4 @@ set(FDB_AV_FUTURE_GET_DOUBLE "730") set(FDB_AV_GET_CLIENT_STATUS "730") set(FDB_AV_INITIALIZE_TRACE_ON_SETUP "730") set(FDB_AV_TENANT_GET_ID "730") +set(FDB_AV_NATIVE_CDC_API "800") From 840eafd043867bf9d027120f4b4d48b680027c6b Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 20:06:59 -0700 Subject: [PATCH 02/69] Use CDC names in C bindings --- bindings/c/fdb_c.cpp | 142 +++++++++++++------------- bindings/c/foundationdb/fdb_c.h | 114 ++++++++++----------- bindings/c/foundationdb/fdb_c_types.h | 2 +- bindings/c/test/unit/unit_tests.cpp | 20 ++-- documentation/sphinx/source/api-c.rst | 72 ++++++------- fdbclient/MultiVersionTransaction.cpp | 24 ++--- 6 files changed, 185 insertions(+), 189 deletions(-) diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 8cf3a0158e..fb472b48a9 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -42,7 +42,7 @@ int g_api_version = 0; * FDBResult -> ThreadSingleAssignmentVarBase * FDBDatabase -> IDatabase * FDBTransaction -> ITransaction - * FDBNativeCdcConsumer -> INativeCdcConsumer + * FDBCdcConsumer -> INativeCdcConsumer */ #define TSAVB(f) ((ThreadSingleAssignmentVarBase*)(f)) #define TSAV(T, f) ((ThreadSingleAssignmentVar*)(f)) @@ -68,43 +68,42 @@ static_assert(static_cast(FDB_BG_MUTATION_TYPE_SET_VALUE) == static_cast(FDB_BG_MUTATION_TYPE_CLEAR_RANGE) == static_cast(MutationRef::Type::ClearRange), "FDB_BG_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE) == static_cast(MutationRef::Type::SetValue), - "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE) == - static_cast(MutationRef::Type::ClearRange), - "FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_ADD) == static_cast(MutationRef::Type::AddValue), - "FDB_NATIVE_CDC_MUTATION_TYPE_ADD enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_AND) == static_cast(MutationRef::Type::And), - "FDB_NATIVE_CDC_MUTATION_TYPE_AND enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_OR) == static_cast(MutationRef::Type::Or), - "FDB_NATIVE_CDC_MUTATION_TYPE_OR enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_XOR) == static_cast(MutationRef::Type::Xor), - "FDB_NATIVE_CDC_MUTATION_TYPE_XOR enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS) == +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VALUE) == static_cast(MutationRef::Type::SetValue), + "FDB_CDC_MUTATION_TYPE_SET_VALUE enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_CLEAR_RANGE) == static_cast(MutationRef::Type::ClearRange), + "FDB_CDC_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_ADD) == static_cast(MutationRef::Type::AddValue), + "FDB_CDC_MUTATION_TYPE_ADD enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_AND) == static_cast(MutationRef::Type::And), + "FDB_CDC_MUTATION_TYPE_AND enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_OR) == static_cast(MutationRef::Type::Or), + "FDB_CDC_MUTATION_TYPE_OR enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_XOR) == static_cast(MutationRef::Type::Xor), + "FDB_CDC_MUTATION_TYPE_XOR enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS) == static_cast(MutationRef::Type::AppendIfFits), - "FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MAX) == static_cast(MutationRef::Type::Max), - "FDB_NATIVE_CDC_MUTATION_TYPE_MAX enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MIN) == static_cast(MutationRef::Type::Min), - "FDB_NATIVE_CDC_MUTATION_TYPE_MIN enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY) == + "FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MAX) == static_cast(MutationRef::Type::Max), + "FDB_CDC_MUTATION_TYPE_MAX enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MIN) == static_cast(MutationRef::Type::Min), + "FDB_CDC_MUTATION_TYPE_MIN enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY) == static_cast(MutationRef::Type::SetVersionstampedKey), - "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE) == + "FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE) == static_cast(MutationRef::Type::SetVersionstampedValue), - "FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN) == static_cast(MutationRef::Type::ByteMin), - "FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX) == static_cast(MutationRef::Type::ByteMax), - "FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2) == static_cast(MutationRef::Type::MinV2), - "FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2 enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2) == static_cast(MutationRef::Type::AndV2), - "FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2 enum value mismatch"); -static_assert(static_cast(FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR) == + "FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_BYTE_MIN) == static_cast(MutationRef::Type::ByteMin), + "FDB_CDC_MUTATION_TYPE_BYTE_MIN enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_BYTE_MAX) == static_cast(MutationRef::Type::ByteMax), + "FDB_CDC_MUTATION_TYPE_BYTE_MAX enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MIN_V2) == static_cast(MutationRef::Type::MinV2), + "FDB_CDC_MUTATION_TYPE_MIN_V2 enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_AND_V2) == static_cast(MutationRef::Type::AndV2), + "FDB_CDC_MUTATION_TYPE_AND_V2 enum value mismatch"); +static_assert(static_cast(FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR) == static_cast(MutationRef::Type::CompareAndClear), - "FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR enum value mismatch"); + "FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR enum value mismatch"); namespace { @@ -113,12 +112,12 @@ namespace { // FDBFuture is destroyed or releases its result memory. struct CNativeCdcStreamInfoArray { Arena arena; - VectorRef streams; + VectorRef streams; }; struct CNativeCdcConsumeResult { Arena arena; - VectorRef mutations; + VectorRef mutations; Version lastConsumedVersion = invalidVersion; }; @@ -137,7 +136,7 @@ CNativeCdcStreamInfoArray makeCNativeCdcStreamInfoArray(std::vectorget(); *out_streams = result.streams.begin(); *out_count = result.streams.size();); } -extern "C" DLLEXPORT fdb_error_t fdb_future_get_native_cdc_consumer(FDBFuture* f, FDBNativeCdcConsumer** out_consumer) { +extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* f, FDBCdcConsumer** out_consumer) { CATCH_AND_RETURN(Reference consumer = TSAV(Reference, f)->get(); - *out_consumer = (FDBNativeCdcConsumer*)consumer.extractPtr();); + *out_consumer = (FDBCdcConsumer*)consumer.extractPtr();); } -extern "C" DLLEXPORT fdb_error_t -fdb_future_get_native_cdc_versioned_mutations(FDBFuture* f, - FDBNativeCdcVersionedMutations const** out_mutations, - int* out_count, - int64_t* out_last_consumed_version) { +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(); @@ -601,13 +599,13 @@ extern "C" DLLEXPORT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, *out_transaction = (FDBTransaction*)tr.extractPtr();); } -extern "C" DLLEXPORT FDBFuture* fdb_database_register_native_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) { +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) @@ -617,53 +615,51 @@ extern "C" DLLEXPORT FDBFuture* fdb_database_register_native_cdc_stream(FDBDatab .extractPtr());); } -extern "C" DLLEXPORT FDBFuture* fdb_database_remove_native_cdc_stream(FDBDatabase* db, - uint8_t const* name, - int name_length) { +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_native_cdc_streams(FDBDatabase* db) { +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_native_cdc_consumer(FDBDatabase* db, - uint8_t const* name, - int name_length) { +extern "C" DLLEXPORT FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* db, + uint8_t const* name, + int name_length) { RETURN_FUTURE_ON_ERROR( Reference, return (FDBFuture*)(DB(db)->createNativeCdcConsumer(KeyRef(name, name_length)).extractPtr());); } -extern "C" DLLEXPORT FDBFuture* fdb_database_resume_native_cdc_consumer(FDBDatabase* db, - uint64_t stream_id, - int64_t last_consumed_version) { +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, NativeCdcCursor cursor; cursor.streamId = stream_id; cursor.lastConsumedVersion = last_consumed_version; return (FDBFuture*)(DB(db)->resumeNativeCdcConsumer(cursor).extractPtr());); } -extern "C" DLLEXPORT void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer) { +extern "C" DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer) { try { NATIVE_CDC_CONSUMER(consumer)->delref(); } catch (...) { } } -extern "C" DLLEXPORT FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer) { +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_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer) { +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_native_cdc_consumer_get_position(FDBNativeCdcConsumer* consumer, - uint64_t* out_stream_id, - int64_t* out_last_consumed_version) { +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;); diff --git a/bindings/c/foundationdb/fdb_c.h b/bindings/c/foundationdb/fdb_c.h index b855e2503f..81a17462b2 100644 --- a/bindings/c/foundationdb/fdb_c.h +++ b/bindings/c/foundationdb/fdb_c.h @@ -188,48 +188,48 @@ typedef struct keyrange { } FDBKeyRange; /* - * Raw mutation types returned by native CDC. The numeric values match + * Raw mutation types returned by CDC. The numeric values match * MutationRef::Type and, for atomic operations, FDBMutationType. */ typedef enum { - FDB_NATIVE_CDC_MUTATION_TYPE_SET_VALUE = 0, - FDB_NATIVE_CDC_MUTATION_TYPE_CLEAR_RANGE = 1, - FDB_NATIVE_CDC_MUTATION_TYPE_ADD = 2, - FDB_NATIVE_CDC_MUTATION_TYPE_AND = 6, - FDB_NATIVE_CDC_MUTATION_TYPE_OR = 7, - FDB_NATIVE_CDC_MUTATION_TYPE_XOR = 8, - FDB_NATIVE_CDC_MUTATION_TYPE_APPEND_IF_FITS = 9, - FDB_NATIVE_CDC_MUTATION_TYPE_MAX = 12, - FDB_NATIVE_CDC_MUTATION_TYPE_MIN = 13, - FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY = 14, - FDB_NATIVE_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE = 15, - FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MIN = 16, - FDB_NATIVE_CDC_MUTATION_TYPE_BYTE_MAX = 17, - FDB_NATIVE_CDC_MUTATION_TYPE_MIN_V2 = 18, - FDB_NATIVE_CDC_MUTATION_TYPE_AND_V2 = 19, - FDB_NATIVE_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR = 20 -} FDBNativeCdcMutationType; + 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 native_cdc_stream_info { +typedef struct cdc_stream_info { FDBKey name; uint64_t stream_id; FDBKeyRange key_range; int64_t min_version; -} FDBNativeCdcStreamInfo; +} FDBCdcStreamInfo; -typedef struct native_cdc_mutation { - /* FDBNativeCdcMutationType */ uint8_t type; +typedef struct cdc_mutation { + /* FDBCdcMutationType */ uint8_t type; const uint8_t* param1; int param1_length; const uint8_t* param2; int param2_length; -} FDBNativeCdcMutation; +} FDBCdcMutation; -typedef struct native_cdc_versioned_mutations { +typedef struct cdc_versioned_mutations { int64_t version; - const FDBNativeCdcMutation* mutations; + const FDBCdcMutation* mutations; int mutation_count; -} FDBNativeCdcVersionedMutations; +} FDBCdcVersionedMutations; /* * TODO: delete the following "blob granule" and "tenant" related data types @@ -397,17 +397,17 @@ 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_native_cdc_stream_info_array(FDBFuture* f, FDBNativeCdcStreamInfo const** out_streams, 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_native_cdc_consumer(FDBFuture* f, - FDBNativeCdcConsumer** out_consumer); +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_native_cdc_versioned_mutations(FDBFuture* f, - FDBNativeCdcVersionedMutations const** out_mutations, - int* out_count, - int64_t* out_last_consumed_version); +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); @@ -434,37 +434,37 @@ 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_native_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_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_native_cdc_stream(FDBDatabase* db, - uint8_t const* name, - int name_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_native_cdc_streams(FDBDatabase* db); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* db); -DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_native_cdc_consumer(FDBDatabase* db, - uint8_t const* name, - int name_length); +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_native_cdc_consumer(FDBDatabase* db, - uint64_t stream_id, - int64_t last_consumed_version); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* db, + uint64_t stream_id, + int64_t last_consumed_version); -DLLEXPORT void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer); +DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer); -DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer); -DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer); -DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_native_cdc_consumer_get_position(FDBNativeCdcConsumer* consumer, - uint64_t* out_stream_id, - int64_t* out_last_consumed_version); +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 diff --git a/bindings/c/foundationdb/fdb_c_types.h b/bindings/c/foundationdb/fdb_c_types.h index 4e877712f1..efb512a328 100644 --- a/bindings/c/foundationdb/fdb_c_types.h +++ b/bindings/c/foundationdb/fdb_c_types.h @@ -39,7 +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_native_cdc_consumer FDBNativeCdcConsumer; +typedef struct FDB_cdc_consumer FDBCdcConsumer; typedef int fdb_error_t; typedef int fdb_bool_t; diff --git a/bindings/c/test/unit/unit_tests.cpp b/bindings/c/test/unit/unit_tests.cpp index d27a7e5f2d..52907744d3 100644 --- a/bindings/c/test/unit/unit_tests.cpp +++ b/bindings/c/test/unit/unit_tests.cpp @@ -1970,16 +1970,16 @@ TEST_CASE("fdb_database_get_server_protocol") { fdb_future_destroy(protocolFuture); } -TEST_CASE("native CDC metadata and synthetic consumer cursor") { - // Listing remains available when native CDC admission is disabled, so this +TEST_CASE("CDC metadata and synthetic consumer cursor") { + // Listing remains available when CDC admission is disabled, so this // exercises the C result conversion without requiring a registered stream. - FDBFuture* listFuture = fdb_database_list_native_cdc_streams(db); + FDBFuture* listFuture = fdb_database_list_cdc_streams(db); REQUIRE(listFuture != nullptr); fdb_check(fdb_future_block_until_ready(listFuture)); - FDBNativeCdcStreamInfo const* streams = nullptr; + FDBCdcStreamInfo const* streams = nullptr; int streamCount = -1; - fdb_check(fdb_future_get_native_cdc_stream_info_array(listFuture, &streams, &streamCount)); + fdb_check(fdb_future_get_cdc_stream_info_array(listFuture, &streams, &streamCount)); CHECK(streamCount >= 0); if (streamCount > 0) { CHECK(streams != nullptr); @@ -1990,21 +1990,21 @@ TEST_CASE("native CDC metadata and synthetic consumer cursor") { // contact CDC infrastructure until consume or acknowledge is requested. constexpr uint64_t streamId = 0x0102030405060708ULL; constexpr int64_t lastConsumedVersion = 123456789; - FDBFuture* resumeFuture = fdb_database_resume_native_cdc_consumer(db, streamId, lastConsumedVersion); + FDBFuture* resumeFuture = fdb_database_resume_cdc_consumer(db, streamId, lastConsumedVersion); REQUIRE(resumeFuture != nullptr); fdb_check(fdb_future_block_until_ready(resumeFuture)); - FDBNativeCdcConsumer* consumer = nullptr; - fdb_check(fdb_future_get_native_cdc_consumer(resumeFuture, &consumer)); + FDBCdcConsumer* consumer = nullptr; + fdb_check(fdb_future_get_cdc_consumer(resumeFuture, &consumer)); fdb_future_destroy(resumeFuture); REQUIRE(consumer != nullptr); uint64_t outStreamId = 0; int64_t outLastConsumedVersion = 0; - fdb_check(fdb_native_cdc_consumer_get_position(consumer, &outStreamId, &outLastConsumedVersion)); + fdb_check(fdb_cdc_consumer_get_position(consumer, &outStreamId, &outLastConsumedVersion)); CHECK(outStreamId == streamId); CHECK(outLastConsumedVersion == lastConsumedVersion); - fdb_native_cdc_consumer_destroy(consumer); + fdb_cdc_consumer_destroy(consumer); } TEST_CASE("fdb_transaction_watch read_your_writes_disable") { diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index 33ed7df2bc..94fae3189b 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -543,76 +543,76 @@ An |database-blurb1| Modifications to a database are performed via transactions. ] } -Native CDC ----------- +CDC +--- -Native CDC exposes durable, named streams of committed mutations for one -half-open user-key range. New stream registration requires native CDC admission +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. -.. type:: FDBNativeCdcMutationType +.. type:: FDBCdcMutationType - The raw mutation type returned by native CDC. Values match the corresponding + 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. -.. type:: FDBNativeCdcStreamInfo +.. type:: FDBCdcStreamInfo - A listed native CDC stream, including its name, stable stream ID, registered + A listed CDC stream, including its name, stable stream ID, registered key range, and durable minimum required version. -.. type:: FDBNativeCdcMutation +.. type:: FDBCdcMutation - One raw mutation within a native CDC commit-version group. + One raw mutation within a CDC commit-version group. -.. type:: FDBNativeCdcVersionedMutations +.. 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:: FDBNativeCdcConsumer +.. type:: FDBCdcConsumer - An opaque, reference-counted native CDC consumer handle. A handle extracted - with :func:`fdb_future_get_native_cdc_consumer()` is owned by the caller and + 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_native_cdc_consumer_destroy()`. + once with :func:`fdb_cdc_consumer_destroy()`. -.. function:: FDBFuture* fdb_database_register_native_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) +.. 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_native_cdc_stream(FDBDatabase* database, uint8_t const* name, int name_length) +.. 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_native_cdc_streams(FDBDatabase* database) +.. function:: FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* database) - Returns the currently registered native CDC streams. Extract the result with - :func:`fdb_future_get_native_cdc_stream_info_array()`. + 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_native_cdc_stream_info_array(FDBFuture* future, FDBNativeCdcStreamInfo const** out_streams, int* out_count) +.. 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_native_cdc_streams()`. |future-get-return1| + :func:`fdb_database_list_cdc_streams()`. |future-get-return1| |future-get-return2|. |future-memory-mine| -.. function:: FDBFuture* fdb_database_create_native_cdc_consumer(FDBDatabase* database, uint8_t const* name, int name_length) +.. 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_native_cdc_consumer()`. + :func:`fdb_future_get_cdc_consumer()`. -.. function:: FDBFuture* fdb_database_resume_native_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version) +.. function:: FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version) Resumes a consumer from a checkpointed cursor. A cursor is only the stable ``stream_id`` and the version through which the caller has consumed; it does @@ -620,28 +620,28 @@ new admission is disabled so that callers can drain or remove them. acknowledged position because unacknowledged mutations may be redelivered after CDC proxy replacement. -.. function:: fdb_error_t fdb_future_get_native_cdc_consumer(FDBFuture* future, FDBNativeCdcConsumer** out_consumer) +.. 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_native_cdc_consumer()` or - :func:`fdb_database_resume_native_cdc_consumer()`. |future-get-return1| + :func:`fdb_database_create_cdc_consumer()` or + :func:`fdb_database_resume_cdc_consumer()`. |future-get-return1| |future-get-return2|. -.. function:: void fdb_native_cdc_consumer_destroy(FDBNativeCdcConsumer* consumer) +.. function:: void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer) - Releases an owned native CDC consumer handle. + Releases an owned CDC consumer handle. -.. function:: FDBFuture* fdb_native_cdc_consumer_consume(FDBNativeCdcConsumer* consumer) +.. 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_native_cdc_versioned_mutations()`. Consumption advances + :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_native_cdc_versioned_mutations(FDBFuture* future, FDBNativeCdcVersionedMutations const** out_mutations, int* out_count, int64_t* out_last_consumed_version) +.. 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_native_cdc_consumer_consume()`. ``out_last_consumed_version`` is + :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|. @@ -650,14 +650,14 @@ new admission is disabled so that callers can drain or remove them. ``future`` and remain valid until :func:`fdb_future_destroy()` or :func:`fdb_future_release_memory()` is called. -.. function:: FDBFuture* fdb_native_cdc_consumer_acknowledge(FDBNativeCdcConsumer* consumer) +.. 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. A consumer may have only one consume or acknowledge operation outstanding at a time. The returned future contains no value. -.. function:: fdb_error_t fdb_native_cdc_consumer_get_position(FDBNativeCdcConsumer* consumer, uint64_t* out_stream_id, int64_t* out_last_consumed_version) +.. 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. diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 285ba92cce..f4f6ba7a30 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -774,47 +774,47 @@ void DLApi::init() { loadClientFunction(&api->databaseRegisterNativeCdcStream, lib, fdbCPath, - "fdb_database_register_native_cdc_stream", + "fdb_database_register_cdc_stream", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->databaseRemoveNativeCdcStream, lib, fdbCPath, - "fdb_database_remove_native_cdc_stream", + "fdb_database_remove_cdc_stream", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->databaseListNativeCdcStreams, lib, fdbCPath, - "fdb_database_list_native_cdc_streams", + "fdb_database_list_cdc_streams", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->databaseCreateNativeCdcConsumer, lib, fdbCPath, - "fdb_database_create_native_cdc_consumer", + "fdb_database_create_cdc_consumer", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->databaseResumeNativeCdcConsumer, lib, fdbCPath, - "fdb_database_resume_native_cdc_consumer", + "fdb_database_resume_cdc_consumer", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->nativeCdcConsumerDestroy, lib, fdbCPath, - "fdb_native_cdc_consumer_destroy", + "fdb_cdc_consumer_destroy", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->nativeCdcConsumerConsume, lib, fdbCPath, - "fdb_native_cdc_consumer_consume", + "fdb_cdc_consumer_consume", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->nativeCdcConsumerAcknowledge, lib, fdbCPath, - "fdb_native_cdc_consumer_acknowledge", + "fdb_cdc_consumer_acknowledge", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->nativeCdcConsumerGetPosition, lib, fdbCPath, - "fdb_native_cdc_consumer_get_position", + "fdb_cdc_consumer_get_position", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->transactionSetOption, lib, fdbCPath, "fdb_transaction_set_option", headerVersion >= 0); @@ -907,17 +907,17 @@ void DLApi::init() { loadClientFunction(&api->futureGetNativeCdcStreamInfoArray, lib, fdbCPath, - "fdb_future_get_native_cdc_stream_info_array", + "fdb_future_get_cdc_stream_info_array", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->futureGetNativeCdcConsumer, lib, fdbCPath, - "fdb_future_get_native_cdc_consumer", + "fdb_future_get_cdc_consumer", headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->futureGetNativeCdcVersionedMutations, lib, fdbCPath, - "fdb_future_get_native_cdc_versioned_mutations", + "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); From fea717fca1166d9b5fb926589c3348201acca7ad Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 22:08:09 -0700 Subject: [PATCH 03/69] Remove CDC enum static assertions --- bindings/c/fdb_c.cpp | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index fb472b48a9..8148b01427 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -68,43 +68,6 @@ static_assert(static_cast(FDB_BG_MUTATION_TYPE_SET_VALUE) == static_cast(FDB_BG_MUTATION_TYPE_CLEAR_RANGE) == static_cast(MutationRef::Type::ClearRange), "FDB_BG_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VALUE) == static_cast(MutationRef::Type::SetValue), - "FDB_CDC_MUTATION_TYPE_SET_VALUE enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_CLEAR_RANGE) == static_cast(MutationRef::Type::ClearRange), - "FDB_CDC_MUTATION_TYPE_CLEAR_RANGE enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_ADD) == static_cast(MutationRef::Type::AddValue), - "FDB_CDC_MUTATION_TYPE_ADD enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_AND) == static_cast(MutationRef::Type::And), - "FDB_CDC_MUTATION_TYPE_AND enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_OR) == static_cast(MutationRef::Type::Or), - "FDB_CDC_MUTATION_TYPE_OR enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_XOR) == static_cast(MutationRef::Type::Xor), - "FDB_CDC_MUTATION_TYPE_XOR enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS) == - static_cast(MutationRef::Type::AppendIfFits), - "FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MAX) == static_cast(MutationRef::Type::Max), - "FDB_CDC_MUTATION_TYPE_MAX enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MIN) == static_cast(MutationRef::Type::Min), - "FDB_CDC_MUTATION_TYPE_MIN enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY) == - static_cast(MutationRef::Type::SetVersionstampedKey), - "FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE) == - static_cast(MutationRef::Type::SetVersionstampedValue), - "FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_BYTE_MIN) == static_cast(MutationRef::Type::ByteMin), - "FDB_CDC_MUTATION_TYPE_BYTE_MIN enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_BYTE_MAX) == static_cast(MutationRef::Type::ByteMax), - "FDB_CDC_MUTATION_TYPE_BYTE_MAX enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_MIN_V2) == static_cast(MutationRef::Type::MinV2), - "FDB_CDC_MUTATION_TYPE_MIN_V2 enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_AND_V2) == static_cast(MutationRef::Type::AndV2), - "FDB_CDC_MUTATION_TYPE_AND_V2 enum value mismatch"); -static_assert(static_cast(FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR) == - static_cast(MutationRef::Type::CompareAndClear), - "FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR enum value mismatch"); - namespace { // These wrappers own the C-shaped arrays returned by the corresponding future From 07e5bbbbb011e51cfdb543e7a9c6e817e25a11a5 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 22:13:54 -0700 Subject: [PATCH 04/69] Document CDC C binding support --- design/cdc.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/design/cdc.md b/design/cdc.md index 27c2ee7fb9..178110c500 100644 --- a/design/cdc.md +++ b/design/cdc.md @@ -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 From aecd9150473eada8a3dcefde4efaa96b3a3ab765 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 22:43:27 -0700 Subject: [PATCH 05/69] Add end-to-end CDC C binding test --- bindings/c/CMakeLists.txt | 4 + bindings/c/test/unit/unit_tests.cpp | 260 +++++++++++++++++++++++++--- 2 files changed, 236 insertions(+), 28 deletions(-) diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index a1550d965a..678f07074c 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -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 $ diff --git a/bindings/c/test/unit/unit_tests.cpp b/bindings/c/test/unit/unit_tests.cpp index 52907744d3..714bc472f3 100644 --- a/bindings/c/test/unit/unit_tests.cpp +++ b/bindings/c/test/unit/unit_tests.cpp @@ -26,9 +26,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -1970,41 +1972,243 @@ TEST_CASE("fdb_database_get_server_protocol") { fdb_future_destroy(protocolFuture); } -TEST_CASE("CDC metadata and synthetic consumer cursor") { - // Listing remains available when CDC admission is disabled, so this - // exercises the C result conversion without requiring a registered stream. - FDBFuture* listFuture = fdb_database_list_cdc_streams(db); - REQUIRE(listFuture != nullptr); - fdb_check(fdb_future_block_until_ready(listFuture)); +TEST_CASE("CDC C binding end-to-end") { + using FuturePtr = std::unique_ptr; + using ConsumerPtr = std::unique_ptr; + 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> 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 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(mutation.param1), mutation.param1_length), + std::string(reinterpret_cast(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{}); + + // 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(nameInput.data()), + nameInput.size(), + reinterpret_cast(beginInput.data()), + beginInput.size(), + reinterpret_cast(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, &streams, &streamCount)); - CHECK(streamCount >= 0); - if (streamCount > 0) { - CHECK(streams != nullptr); + 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(streams[i].key_range.begin_key), + streams[i].key_range.begin_key_length) == rangeBegin); + CHECK(std::string(reinterpret_cast(streams[i].key_range.end_key), + streams[i].key_range.end_key_length) == rangeEnd); + CHECK(streams[i].min_version >= 0); } - fdb_future_destroy(listFuture); + REQUIRE(foundStream); + fdb_future_release_memory(listFuture.get()); + CHECK(fdb_future_get_cdc_stream_info_array(listFuture.get(), &streams, &streamCount) == 1102); // future_released - // Resuming only reconstructs a cursor-bearing client handle. It does not - // contact CDC infrastructure until consume or acknowledge is requested. - constexpr uint64_t streamId = 0x0102030405060708ULL; - constexpr int64_t lastConsumedVersion = 123456789; - FDBFuture* resumeFuture = fdb_database_resume_cdc_consumer(db, streamId, lastConsumedVersion); - REQUIRE(resumeFuture != nullptr); - fdb_check(fdb_future_block_until_ready(resumeFuture)); - - FDBCdcConsumer* consumer = nullptr; - fdb_check(fdb_future_get_cdc_consumer(resumeFuture, &consumer)); - fdb_future_destroy(resumeFuture); + auto createFuture = ownFuture( + fdb_database_create_cdc_consumer(db, reinterpret_cast(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 outStreamId = 0; - int64_t outLastConsumedVersion = 0; - fdb_check(fdb_cdc_consumer_get_position(consumer, &outStreamId, &outLastConsumedVersion)); - CHECK(outStreamId == streamId); - CHECK(outLastConsumedVersion == lastConsumedVersion); - fdb_cdc_consumer_destroy(consumer); + 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 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(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") { From abd9e926025707b9244e774cf0487cdd2117a508 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 22:44:57 -0700 Subject: [PATCH 06/69] Document CDC binding semantics --- documentation/sphinx/source/api-c.rst | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index 94fae3189b..609256a7e4 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -552,6 +552,9 @@ 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 @@ -559,6 +562,9 @@ new admission is disabled so that callers can drain or remove them. ``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 @@ -614,9 +620,11 @@ new admission is disabled so that callers can drain or remove them. .. function:: FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version) - Resumes a consumer 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. Resume from the last durably processed and + 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. @@ -654,8 +662,11 @@ new admission is disabled so that callers can drain or remove them. Durably acknowledges the consumer's current delivered position. Call this only after all mutations represented through that position have been durably - processed. A consumer may have only one consume or acknowledge operation - outstanding at a time. The returned future contains no value. + 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) From 0d48cdbd818efd6d3c489ccebdfd8d306e36797f Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 22:46:00 -0700 Subject: [PATCH 07/69] Document CDC future result handling --- documentation/sphinx/source/api-c.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index 609256a7e4..eedc10185d 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -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 ` 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 ` 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| From aa4da9eee4368a3e3d99adfe4c0131200b903655 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 9 Jul 2026 23:14:48 -0700 Subject: [PATCH 08/69] Avoid moving CDC view arguments --- fdbclient/MultiVersionTransaction.cpp | 25 ++++++++++--------- fdbclient/ThreadSafeTransaction.cpp | 8 +++--- fdbclient/include/fdbclient/IClientApi.h | 8 +++--- .../fdbclient/MultiVersionTransaction.h | 16 ++++++------ .../include/fdbclient/ThreadSafeTransaction.h | 8 +++--- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index f4f6ba7a30..946a401db3 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -549,7 +549,7 @@ ThreadFuture DLDatabase::createSnapshot(const StringRef& uid, const String return toThreadFuture(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { return Void(); }); } -ThreadFuture DLDatabase::registerNativeCdcStream(KeyRef name, KeyRangeRef keys) { +ThreadFuture DLDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) { if (!api->databaseRegisterNativeCdcStream) { return unsupported_operation(); } @@ -564,7 +564,7 @@ ThreadFuture DLDatabase::registerNativeCdcStream(KeyRef name, KeyRa }); } -ThreadFuture DLDatabase::removeNativeCdcStream(KeyRef name) { +ThreadFuture DLDatabase::removeNativeCdcStream(const KeyRef& name) { if (!api->databaseRemoveNativeCdcStream) { return unsupported_operation(); } @@ -593,7 +593,7 @@ ThreadFuture> DLDatabase::listNativeCdcStreams( }); } -ThreadFuture> DLDatabase::createNativeCdcConsumer(KeyRef name) { +ThreadFuture> DLDatabase::createNativeCdcConsumer(const KeyRef& name) { if (!api->databaseCreateNativeCdcConsumer || !api->futureGetNativeCdcConsumer) { return unsupported_operation(); } @@ -608,7 +608,7 @@ ThreadFuture> DLDatabase::createNativeCdcConsumer( }); } -ThreadFuture> DLDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { +ThreadFuture> DLDatabase::resumeNativeCdcConsumer(const NativeCdcCursor& cursor) { if (!api->databaseResumeNativeCdcConsumer || !api->futureGetNativeCdcConsumer) { return unsupported_operation(); } @@ -1667,24 +1667,25 @@ ThreadFuture MultiVersionDatabase::createSnapshot(const StringRef& uid, co return executeOperation(&IDatabase::createSnapshot, uid, snapshot_command); } -ThreadFuture MultiVersionDatabase::registerNativeCdcStream(KeyRef name, KeyRangeRef keys) { - return executeOperation(&IDatabase::registerNativeCdcStream, std::move(name), std::move(keys)); +ThreadFuture MultiVersionDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) { + return executeOperation(&IDatabase::registerNativeCdcStream, name, keys); } -ThreadFuture MultiVersionDatabase::removeNativeCdcStream(KeyRef name) { - return executeOperation(&IDatabase::removeNativeCdcStream, std::move(name)); +ThreadFuture MultiVersionDatabase::removeNativeCdcStream(const KeyRef& name) { + return executeOperation(&IDatabase::removeNativeCdcStream, name); } ThreadFuture> MultiVersionDatabase::listNativeCdcStreams() { return executeOperation(&IDatabase::listNativeCdcStreams); } -ThreadFuture> MultiVersionDatabase::createNativeCdcConsumer(KeyRef name) { - return executeOperation(&IDatabase::createNativeCdcConsumer, std::move(name)); +ThreadFuture> MultiVersionDatabase::createNativeCdcConsumer(const KeyRef& name) { + return executeOperation(&IDatabase::createNativeCdcConsumer, name); } -ThreadFuture> MultiVersionDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { - return executeOperation(&IDatabase::resumeNativeCdcConsumer, std::move(cursor)); +ThreadFuture> MultiVersionDatabase::resumeNativeCdcConsumer( + const NativeCdcCursor& cursor) { + return executeOperation(&IDatabase::resumeNativeCdcConsumer, cursor); } ThreadFuture MultiVersionDatabase::createSharedState() { diff --git a/fdbclient/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index d445d15d13..aa69142f12 100644 --- a/fdbclient/ThreadSafeTransaction.cpp +++ b/fdbclient/ThreadSafeTransaction.cpp @@ -221,7 +221,7 @@ ThreadFuture ThreadSafeDatabase::createSnapshot(const StringRef& uid, cons }); } -ThreadFuture ThreadSafeDatabase::registerNativeCdcStream(KeyRef name, KeyRangeRef keys) { +ThreadFuture ThreadSafeDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) { DatabaseContext* db = this->db; Key nameCopy(name); KeyRange keysCopy(keys); @@ -231,7 +231,7 @@ ThreadFuture ThreadSafeDatabase::registerNativeCdcStream(KeyRef nam }); } -ThreadFuture ThreadSafeDatabase::removeNativeCdcStream(KeyRef name) { +ThreadFuture ThreadSafeDatabase::removeNativeCdcStream(const KeyRef& name) { DatabaseContext* db = this->db; Key nameCopy(name); return onMainThread([db, nameCopy]() -> Future { @@ -248,7 +248,7 @@ ThreadFuture> ThreadSafeDatabase::listNativeCdc }); } -ThreadFuture> ThreadSafeDatabase::createNativeCdcConsumer(KeyRef name) { +ThreadFuture> ThreadSafeDatabase::createNativeCdcConsumer(const KeyRef& name) { DatabaseContext* db = this->db; Key nameCopy(name); return onMainThread([db, nameCopy]() -> Future> { @@ -258,7 +258,7 @@ ThreadFuture> ThreadSafeDatabase::createNativeCdcC }); } -ThreadFuture> ThreadSafeDatabase::resumeNativeCdcConsumer(NativeCdcCursor cursor) { +ThreadFuture> ThreadSafeDatabase::resumeNativeCdcConsumer(const NativeCdcCursor& cursor) { DatabaseContext* db = this->db; return onMainThread([db, cursor]() -> Future> { db->checkDeferredError(); diff --git a/fdbclient/include/fdbclient/IClientApi.h b/fdbclient/include/fdbclient/IClientApi.h index 0cba7b84c9..3ba74cd828 100644 --- a/fdbclient/include/fdbclient/IClientApi.h +++ b/fdbclient/include/fdbclient/IClientApi.h @@ -156,11 +156,11 @@ public: // 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 registerNativeCdcStream(KeyRef name, KeyRangeRef keys) = 0; - virtual ThreadFuture removeNativeCdcStream(KeyRef name) = 0; + virtual ThreadFuture registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) = 0; + virtual ThreadFuture removeNativeCdcStream(const KeyRef& name) = 0; virtual ThreadFuture> listNativeCdcStreams() = 0; - virtual ThreadFuture> createNativeCdcConsumer(KeyRef name) = 0; - virtual ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) = 0; + virtual ThreadFuture> createNativeCdcConsumer(const KeyRef& name) = 0; + virtual ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) = 0; // Interface to manage shared state across multiple connections to the same Database virtual ThreadFuture createSharedState() = 0; diff --git a/fdbclient/include/fdbclient/MultiVersionTransaction.h b/fdbclient/include/fdbclient/MultiVersionTransaction.h index 94634c07bb..6046667476 100644 --- a/fdbclient/include/fdbclient/MultiVersionTransaction.h +++ b/fdbclient/include/fdbclient/MultiVersionTransaction.h @@ -425,11 +425,11 @@ public: ThreadFuture rebootWorker(const StringRef& address, bool check, int duration) override; ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; - ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; - ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; ThreadFuture> listNativeCdcStreams() override; - ThreadFuture> createNativeCdcConsumer(KeyRef name) override; - ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; + ThreadFuture> createNativeCdcConsumer(const KeyRef& name) override; + ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; @@ -742,11 +742,11 @@ public: ThreadFuture rebootWorker(const StringRef& address, bool check, int duration) override; ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; - ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; - ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; ThreadFuture> listNativeCdcStreams() override; - ThreadFuture> createNativeCdcConsumer(KeyRef name) override; - ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; + ThreadFuture> createNativeCdcConsumer(const KeyRef& name) override; + ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; diff --git a/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 92cbadc3c7..66c18cd1c8 100644 --- a/fdbclient/include/fdbclient/ThreadSafeTransaction.h +++ b/fdbclient/include/fdbclient/ThreadSafeTransaction.h @@ -58,11 +58,11 @@ public: ThreadFuture forceRecoveryWithDataLoss(const StringRef& dcid) override; ThreadFuture createSnapshot(const StringRef& uid, const StringRef& snapshot_command) override; - ThreadFuture registerNativeCdcStream(KeyRef name, KeyRangeRef keys) override; - ThreadFuture removeNativeCdcStream(KeyRef name) override; + ThreadFuture registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; ThreadFuture> listNativeCdcStreams() override; - ThreadFuture> createNativeCdcConsumer(KeyRef name) override; - ThreadFuture> resumeNativeCdcConsumer(NativeCdcCursor cursor) override; + ThreadFuture> createNativeCdcConsumer(const KeyRef& name) override; + ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; From 84896f1b8c7e4fc3c2c14becc40ad817548bad4d Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 10 Jul 2026 00:02:23 -0700 Subject: [PATCH 09/69] Update CDC include after ThreadHelper rename --- fdbclient/include/fdbclient/NativeCdcClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbclient/include/fdbclient/NativeCdcClient.h b/fdbclient/include/fdbclient/NativeCdcClient.h index fd5d8ee69b..7b166bfd6f 100644 --- a/fdbclient/include/fdbclient/NativeCdcClient.h +++ b/fdbclient/include/fdbclient/NativeCdcClient.h @@ -26,7 +26,7 @@ #include #include "fdbclient/FDBTypes.h" -#include "flow/ThreadHelper.actor.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 From ebefa9dcc7f91609f927cffc3cb75c588686bed4 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 10 Jul 2026 00:02:32 -0700 Subject: [PATCH 10/69] Make clang-tidy handle C bindings --- .github/workflows/tidy.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index 69fab7dc17..6146ed55bf 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -43,6 +43,7 @@ jobs: ninja -v \ processed_compile_commands \ + fdb_c_generated \ fdboptions \ ProtocolVersion @@ -87,9 +88,13 @@ 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 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_types.h) + continue + ;; fdbserver/core/RocksDBCheckpointUtils.cpp|fdbserver/kvstore/KeyValueStoreRocksDB.cpp|fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp) continue ;; From 027248c53af563e8a8dc9897f1522e80178ee849 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 10 Jul 2026 04:52:55 -0700 Subject: [PATCH 11/69] Fix CDC C binding CI tests --- bindings/c/CMakeLists.txt | 2 ++ bindings/c/test/fdb_c_client_config_tests.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 0de68ded55..edb5609efb 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -556,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 diff --git a/bindings/c/test/fdb_c_client_config_tests.py b/bindings/c/test/fdb_c_client_config_tests.py index f1fc0f612a..e3914b6a5f 100644 --- a/bindings/c/test/fdb_c_client_config_tests.py +++ b/bindings/c/test/fdb_c_client_config_tests.py @@ -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)) From 83b7e609311775555e57f3b8c15436856614e238 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 11 Jul 2026 06:19:48 -0700 Subject: [PATCH 12/69] Preserve CDC consume reply cursor version --- fdbclient/MultiVersionTransaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 384644cd03..c91c7669b9 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -448,7 +448,7 @@ public: api->futureGetNativeCdcVersionedMutations(f, &mutations, &count, &lastConsumedVersion); ASSERT(!error); NativeCdcConsumeResult result = copyNativeCdcConsumeResult(mutations, count, lastConsumedVersion); - result.cursor = self->getPosition(); + result.cursor.streamId = self->getPosition().streamId; return result; }); } From 00347eaa7d588974b4e92b7cb46b5345578d5d52 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 14 Jul 2026 22:45:18 -0700 Subject: [PATCH 13/69] Requeue cancelled shard-encoded relocations --- .../datadistributor/DDRelocationQueue.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 7ab975cbcb..91bbc14754 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -1394,6 +1394,12 @@ Future cancelDataMove(class DDQueue* self, KeyRange range, const DDEnabled } } +void requeueCancelledRelocation(DDQueue* self, RelocateData const& rd, bool doBulkLoading) { + if (!doBulkLoading) { + self->output.send(RelocateShard(rd.keys, rd.priority, rd.reason, rd.randomId)); + } +} + static std::string destServersString(std::vector, bool>> const& bestTeams) { std::stringstream ss; @@ -2438,6 +2444,7 @@ Future dataDistributionRelocator(DDQueue* self, if (err.code() == error_code_data_move_dest_team_not_found) { co_await cancelDataMove(self, rd.keys, ddEnabledState); + requeueCancelledRelocation(self, rd, doBulkLoading); TraceEvent(SevWarnAlways, "RelocateShardCancelDataMoveTeamNotFound") .detail("Src", describe(rd.src)) .detail("DataMoveMetaData", rd.dataMove != nullptr ? rd.dataMove->meta.toString() : "Empty"); @@ -3219,3 +3226,29 @@ TEST_CASE("/DataDistribution/DDQueue/BatchDrainRelocationComplete") { std::cout << "BatchDrainRelocationComplete: drained " << drained << " of " << N << " completions\n"; } + +TEST_CASE("/DataDistribution/DDQueue/RequeueCancelledRelocation") { + DDQueue self; + FutureStream retries = self.output.getFuture(); + KeyRange keys = KeyRangeRef("begin"_sr, "end"_sr); + UID traceId(1, 2); + RelocateData rd( + RelocateShard(keys, DataMovementReason::TEAM_CONTAINS_UNDESIRED_SERVER, RelocateReason::OTHER, traceId)); + rd.dataMoveId = UID(3, 4); + + requeueCancelledRelocation(&self, rd, false); + ASSERT(retries.isReady()); + RelocateShard retry = retries.pop(); + ASSERT(retry.keys == keys); + ASSERT(retry.priority == rd.priority); + ASSERT(retry.reason == rd.reason); + ASSERT(retry.moveReason == DataMovementReason::TEAM_CONTAINS_UNDESIRED_SERVER); + ASSERT(retry.traceId == traceId); + ASSERT(retry.dataMoveId == anonymousShardId); + ASSERT(!retry.isRestore()); + ASSERT(!retry.cancelled); + + requeueCancelledRelocation(&self, rd, true); + ASSERT(!retries.isReady()); + return Void(); +} From 40715a9c9188735588fb5252c30569dad9c28897 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 14 Jul 2026 23:56:11 -0700 Subject: [PATCH 14/69] Preserve split context when requeueing relocations --- .../datadistributor/DDRelocationQueue.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 91bbc14754..7759a69bfb 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -1396,7 +1396,11 @@ Future cancelDataMove(class DDQueue* self, KeyRange range, const DDEnabled void requeueCancelledRelocation(DDQueue* self, RelocateData const& rd, bool doBulkLoading) { if (!doBulkLoading) { - self->output.send(RelocateShard(rd.keys, rd.priority, rd.reason, rd.randomId)); + RelocateShard retry(rd.keys, rd.priority, rd.reason, rd.randomId); + if (Optional parentRange = rd.getParentRange(); parentRange.present()) { + retry.setParentRange(parentRange.get()); + } + self->output.send(retry); } } @@ -3247,6 +3251,19 @@ TEST_CASE("/DataDistribution/DDQueue/RequeueCancelledRelocation") { ASSERT(retry.dataMoveId == anonymousShardId); ASSERT(!retry.isRestore()); ASSERT(!retry.cancelled); + ASSERT(!retry.getParentRange().present()); + + KeyRange parent = KeyRangeRef("parentBegin"_sr, "parentEnd"_sr); + RelocateShard split(keys, DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT, traceId); + split.setParentRange(parent); + RelocateData splitRd(split); + + requeueCancelledRelocation(&self, splitRd, false); + ASSERT(retries.isReady()); + RelocateShard splitRetry = retries.pop(); + ASSERT(splitRetry.reason == RelocateReason::SIZE_SPLIT); + ASSERT(splitRetry.getParentRange().present()); + ASSERT(splitRetry.getParentRange().get() == parent); requeueCancelledRelocation(&self, rd, true); ASSERT(!retries.isReady()); From 3261c2fb30962931728ef7a85a9c34c132e5c22c Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 02:14:34 -0700 Subject: [PATCH 15/69] Serialize DD relocator error propagation --- .../datadistributor/DDRelocationQueue.cpp | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 7759a69bfb..a6a9897717 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -3081,9 +3081,19 @@ struct DDQueueImpl { } static Future waitAndValidate(RunState* state, Future future) { - co_await future; + Error error; + try { + co_await future; + } catch (Error& e) { + error = e; + } + // A relocator can signal an error inline while launchQueuedWork() is repairing its maps. Keep DD alive + // until that mutation finishes before propagating the error and tearing the queue down. co_await state->queueMutationLock.take(); FlowLock::Releaser lockGuard(state->queueMutationLock); + if (error.isValid()) { + throw error; + } validate(state); } @@ -3269,3 +3279,27 @@ TEST_CASE("/DataDistribution/DDQueue/RequeueCancelledRelocation") { ASSERT(!retries.isReady()); return Void(); } + +TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { + Reference self = makeReference(); + DDQueueImpl::RunState state(self); + Promise error; + Future propagated; + + { + co_await state.queueMutationLock.take(); + FlowLock::Releaser lockGuard(state.queueMutationLock); + propagated = DDQueueImpl::waitAndValidate(&state, error.getFuture()); + error.sendError(movekeys_conflict()); + ASSERT(!propagated.isReady()); + } + + Error observed; + try { + co_await propagated; + } catch (Error& e) { + observed = e; + } + ASSERT(observed.code() == error_code_movekeys_conflict); + co_return; +} From 22ce634182c20f74d39e80b1d6ed333ad419e0ef Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 15:02:11 -0700 Subject: [PATCH 16/69] Convert ClusterController monitors to coroutines --- .../ClusterController.actor.cpp | 173 +++++++++--------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 54c89f7271..f4c14b5b8e 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -2384,63 +2384,61 @@ Future updatedChangingDatacenters(ClusterControllerData* self) { } } -ACTOR Future updatedChangedDatacenters(ClusterControllerData* self) { - state Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); - state Future 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 updatedChangedDatacenters(ClusterControllerData* self) { + Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); + Future 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 if (res.index() == 1) { + 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++; } } } + } else { + UNREACHABLE(); } } } @@ -2807,13 +2805,13 @@ Future startDataDistributor(ClusterControllerData* self, double waitTime) } } -ACTOR Future monitorDataDistributor(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future 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()) @@ -2821,18 +2819,17 @@ ACTOR Future 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()); } } } @@ -2905,26 +2902,25 @@ Future startRatekeeper(ClusterControllerData* self, double waitTime) { } } -ACTOR Future monitorRatekeeper(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future 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()); } } } @@ -2997,28 +2993,25 @@ Future startConsistencyScan(ClusterControllerData* self) { } } -ACTOR Future monitorConsistencyScan(ClusterControllerData* self) { +Future 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 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 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); } } } From 44038b3d40e160ea4ce99731e367e1941727b532 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 18:41:46 -0700 Subject: [PATCH 17/69] Remove redundant coroutine race branch --- fdbserver/clustercontroller/ClusterController.actor.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index f4c14b5b8e..967c66d6ec 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -2392,7 +2392,7 @@ Future updatedChangedDatacenters(ClusterControllerData* self) { if (res.index() == 0) { changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); onChange = self->changingDcIds.onChange(); - } else if (res.index() == 1) { + } else { changeDelay = Never(); onChange = self->changingDcIds.onChange(); @@ -2437,8 +2437,6 @@ Future updatedChangedDatacenters(ClusterControllerData* self) { } } } - } else { - UNREACHABLE(); } } } From cb9f79d484e232eca03605884ca33979f73299d4 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 19:46:10 -0700 Subject: [PATCH 18/69] Make AsyncFileBlobStore header private --- fdbclient/AsyncFileBlobStore.cpp | 2 +- fdbclient/{include/fdbclient => }/AsyncFileBlobStore.h | 0 fdbclient/BackupContainerBlobStore.cpp | 2 +- fdbclient/BackupContainerBlobStore.h | 1 - fdbclient/CMakeLists.txt | 3 ++- 5 files changed, 4 insertions(+), 4 deletions(-) rename fdbclient/{include/fdbclient => }/AsyncFileBlobStore.h (100%) diff --git a/fdbclient/AsyncFileBlobStore.cpp b/fdbclient/AsyncFileBlobStore.cpp index 0952fec075..d2705b7e2b 100644 --- a/fdbclient/AsyncFileBlobStore.cpp +++ b/fdbclient/AsyncFileBlobStore.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbclient/AsyncFileBlobStore.h" +#include "AsyncFileBlobStore.h" #include "flow/UnitTest.h" Future AsyncFileBlobStoreRead::size() const { diff --git a/fdbclient/include/fdbclient/AsyncFileBlobStore.h b/fdbclient/AsyncFileBlobStore.h similarity index 100% rename from fdbclient/include/fdbclient/AsyncFileBlobStore.h rename to fdbclient/AsyncFileBlobStore.h diff --git a/fdbclient/BackupContainerBlobStore.cpp b/fdbclient/BackupContainerBlobStore.cpp index 505dea70a4..dff6cee993 100644 --- a/fdbclient/BackupContainerBlobStore.cpp +++ b/fdbclient/BackupContainerBlobStore.cpp @@ -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" diff --git a/fdbclient/BackupContainerBlobStore.h b/fdbclient/BackupContainerBlobStore.h index 546430b055..9c4b7c71d4 100644 --- a/fdbclient/BackupContainerBlobStore.h +++ b/fdbclient/BackupContainerBlobStore.h @@ -22,7 +22,6 @@ #define FDBCLIENT_BACKUP_CONTAINER_BLOBSTORE_H #pragma once -#include "fdbclient/AsyncFileBlobStore.h" #include "fdbclient/BackupContainerFileSystem.h" #include "fdbclient/IBlobStore.h" diff --git a/fdbclient/CMakeLists.txt b/fdbclient/CMakeLists.txt index 9180f29ad0..0462ccb223 100644 --- a/fdbclient/CMakeLists.txt +++ b/fdbclient/CMakeLists.txt @@ -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) # 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 "$<$: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>") From 7de621144e6d55ccb05b14119b9383b8b6b95f63 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 19:47:44 -0700 Subject: [PATCH 19/69] Make ActorFuzz header private --- fdbrpc/ActorFuzz.actor.cpp | 2 +- fdbrpc/{include/fdbrpc => }/ActorFuzz.h | 0 fdbrpc/ActorFuzzUnitTest.cpp | 4 ++-- fdbrpc/actorFuzz.py | 2 +- fdbrpc/dsltest.actor.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename fdbrpc/{include/fdbrpc => }/ActorFuzz.h (100%) diff --git a/fdbrpc/ActorFuzz.actor.cpp b/fdbrpc/ActorFuzz.actor.cpp index 2cd97463cb..b9bb9e8af1 100644 --- a/fdbrpc/ActorFuzz.actor.cpp +++ b/fdbrpc/ActorFuzz.actor.cpp @@ -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 diff --git a/fdbrpc/include/fdbrpc/ActorFuzz.h b/fdbrpc/ActorFuzz.h similarity index 100% rename from fdbrpc/include/fdbrpc/ActorFuzz.h rename to fdbrpc/ActorFuzz.h diff --git a/fdbrpc/ActorFuzzUnitTest.cpp b/fdbrpc/ActorFuzzUnitTest.cpp index bb8f544199..9cd469cbb6 100644 --- a/fdbrpc/ActorFuzzUnitTest.cpp +++ b/fdbrpc/ActorFuzzUnitTest.cpp @@ -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 result = actorFuzzTests(); ASSERT(result.first == result.second); return Void(); -} \ No newline at end of file +} diff --git a/fdbrpc/actorFuzz.py b/fdbrpc/actorFuzz.py index dda73c47b8..3f41415ebd 100755 --- a/fdbrpc/actorFuzz.py +++ b/fdbrpc/actorFuzz.py @@ -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)] diff --git a/fdbrpc/dsltest.actor.cpp b/fdbrpc/dsltest.actor.cpp index 7ebf804f2c..7bdd9847fa 100644 --- a/fdbrpc/dsltest.actor.cpp +++ b/fdbrpc/dsltest.actor.cpp @@ -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. From 71db891aaea01a123a3572103c015b4b09385382 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 19:53:23 -0700 Subject: [PATCH 20/69] Make read-your-writes internals private --- fdbclient/CMakeLists.txt | 2 +- fdbclient/RYWIterator.cpp | 3 +- fdbclient/RYWIterator.h | 86 +++++++++++++++++++ fdbclient/ReadYourWrites.cpp | 86 +++++++++---------- .../{include/fdbclient => }/SnapshotCache.h | 0 fdbclient/WriteMap.cpp | 2 +- fdbclient/{include/fdbclient => }/WriteMap.h | 2 +- .../{RYWIterator.h => RandomTestUtils.h} | 69 ++------------- fdbclient/include/fdbclient/ReadYourWrites.h | 12 +-- fdbserver/workloads/Unreadable.cpp | 2 + 10 files changed, 146 insertions(+), 118 deletions(-) create mode 100644 fdbclient/RYWIterator.h rename fdbclient/{include/fdbclient => }/SnapshotCache.h (100%) rename fdbclient/{include/fdbclient => }/WriteMap.h (99%) rename fdbclient/include/fdbclient/{RYWIterator.h => RandomTestUtils.h} (60%) diff --git a/fdbclient/CMakeLists.txt b/fdbclient/CMakeLists.txt index 0462ccb223..1444da8399 100644 --- a/fdbclient/CMakeLists.txt +++ b/fdbclient/CMakeLists.txt @@ -165,7 +165,7 @@ if(WITH_SWIFT) include(GenerateModulemap) set(FDBCLIENT_BINARY_DIR "${CMAKE_BINARY_DIR}/fdbclient") generate_modulemap("${CMAKE_BINARY_DIR}/fdbclient/include" "FDBClient" fdbclient OMIT - AsyncFileBlobStore.h) + 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 "$<$: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>") diff --git a/fdbclient/RYWIterator.cpp b/fdbclient/RYWIterator.cpp index c18311e083..c420a479c8 100644 --- a/fdbclient/RYWIterator.cpp +++ b/fdbclient/RYWIterator.cpp @@ -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" diff --git a/fdbclient/RYWIterator.h b/fdbclient/RYWIterator.h new file mode 100644 index 0000000000..dc610bdf40 --- /dev/null +++ b/fdbclient/RYWIterator.h @@ -0,0 +1,86 @@ +/* + * RYWIterator.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FDBCLIENT_RYWITERATOR_H +#define FDBCLIENT_RYWITERATOR_H +#pragma once + +#include "SnapshotCache.h" +#include "WriteMap.h" + +class RYWIterator { +public: + RYWIterator(SnapshotCache* snapshotCache, WriteMap* writeMap) + : begin_key_cmp(0), end_key_cmp(0), cache(snapshotCache), writes(writeMap), bypassUnreadable(false) {} + + enum SEGMENT_TYPE { UNKNOWN_RANGE, EMPTY_RANGE, KV }; + static const SEGMENT_TYPE typeMap[12]; + + SEGMENT_TYPE type() const; + + bool is_kv() const; + bool is_unknown_range() const; + bool is_empty_range() const; + bool is_unreadable() const; + bool is_dependent() const; + + ExtStringRef beginKey(); + ExtStringRef endKey(); + + virtual const KeyValueRef* kv(Arena& arena); + + RYWIterator& operator++(); + + RYWIterator& operator--(); + + bool operator==(const RYWIterator& r) const; + bool operator!=(const RYWIterator& r) const; + + void skip(KeyRef key); + + void skipContiguous(KeyRef key); + + void skipContiguousBack(KeyRef key); + + void bypassUnreadableProtection() { bypassUnreadable = true; } + + 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(); +}; + +void testESR(); +void testSnapshotCache(); + +#endif diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 334384abfd..69e407f057 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -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 @@ -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()); + ryw->rywState->cache.insert(k, Optional()); if (!dependent) co_return Optional(); } @@ -239,7 +247,7 @@ public: keyAfter(result, ryw->arena)); it.skip(readRange.begin); - ryw->updateConflictMap(readRange, it); + updateConflictMap(ryw, readRange, it); } template @@ -359,7 +367,7 @@ public: template static Future 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()); } @@ -367,7 +375,7 @@ public: static Future 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 @@ -744,7 +752,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? @@ -1055,7 +1063,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? @@ -1185,7 +1193,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(ryw, req, writes, result); co_return result; } @@ -1522,9 +1530,11 @@ public: } }; +ReadYourWritesTransaction::ReadYourWritesTransaction() : rywState(std::make_unique(&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(&arena)), retries(0), + approximateSize(0), creationTime(now()), commitStarted(false), versionStampFuture(tr.getVersionstamp()), specialKeySpaceWriteMap(std::make_pair(false, Optional()), specialKeys.end), options(tr) { std::copy( cx.getTransactionDefaults().begin(), cx.getTransactionDefaults().end(), std::back_inserter(persistentOptions)); @@ -1881,22 +1891,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; @@ -1990,7 +1992,7 @@ bool ReadYourWritesTransactionOptions::getAndResetWriteConflictDisabled() { } void ReadYourWritesTransaction::getWriteConflicts(KeyRangeMap* result) { - WriteMap::iterator it(&writes); + WriteMap::iterator it(&rywState->writes); it.skip(allKeys.begin); bool inConflictRange = false; @@ -2069,7 +2071,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; @@ -2169,7 +2171,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 @@ -2193,7 +2195,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(), false); } @@ -2251,7 +2253,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); } @@ -2300,7 +2302,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()); } @@ -2333,7 +2335,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()); } @@ -2397,7 +2399,7 @@ void ReadYourWritesTransaction::addWriteConflictRange(KeyRangeRef const& keys) { } r = KeyRangeRef(arena, r); - writes.addConflictRange(r); + rywState->writes.addConflictRange(r); } Future ReadYourWritesTransaction::commit() { @@ -2438,7 +2440,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; @@ -2524,8 +2526,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); @@ -2541,8 +2542,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); @@ -2554,13 +2555,12 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep } ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept - : deferredError(std::move(r.deferredError)), arena(std::move(r.arena)), cache(std::move(r.cache)), - writes(std::move(r.writes)), 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; + : deferredError(std::move(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) { + rywState->cache.arena = &arena; + rywState->writes.arena = &arena; tr = std::move(r.tr); readConflicts = std::move(r.readConflicts); watchMap = std::move(r.watchMap); @@ -2606,8 +2606,8 @@ void ReadYourWritesTransaction::resetRyow() { timeoutActor.cancel(); arena = Arena(); - cache = SnapshotCache(&arena); - writes = WriteMap(&arena); + rywState->cache = SnapshotCache(&arena); + rywState->writes = WriteMap(&arena); readConflicts = CoalescedKeyRefRangeMap(); versionStampKeys = VectorRef(); nativeReadRanges = Standalone>(); diff --git a/fdbclient/include/fdbclient/SnapshotCache.h b/fdbclient/SnapshotCache.h similarity index 100% rename from fdbclient/include/fdbclient/SnapshotCache.h rename to fdbclient/SnapshotCache.h diff --git a/fdbclient/WriteMap.cpp b/fdbclient/WriteMap.cpp index 1445d8398f..c6c5485561 100644 --- a/fdbclient/WriteMap.cpp +++ b/fdbclient/WriteMap.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbclient/WriteMap.h" +#include "WriteMap.h" void OperationStack::reset(RYWMutation initialEntry) { defaultConstructed = false; diff --git a/fdbclient/include/fdbclient/WriteMap.h b/fdbclient/WriteMap.h similarity index 99% rename from fdbclient/include/fdbclient/WriteMap.h rename to fdbclient/WriteMap.h index 75741349c7..d71d8c8173 100644 --- a/fdbclient/include/fdbclient/WriteMap.h +++ b/fdbclient/WriteMap.h @@ -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 { diff --git a/fdbclient/include/fdbclient/RYWIterator.h b/fdbclient/include/fdbclient/RandomTestUtils.h similarity index 60% rename from fdbclient/include/fdbclient/RYWIterator.h rename to fdbclient/include/fdbclient/RandomTestUtils.h index 12e778687a..f22f665ffc 100644 --- a/fdbclient/include/fdbclient/RYWIterator.h +++ b/fdbclient/include/fdbclient/RandomTestUtils.h @@ -1,5 +1,5 @@ /* - * RYWIterator.h + * RandomTestUtils.h * * This source file is part of the FoundationDB open source project * @@ -18,67 +18,13 @@ * limitations under the License. */ -#ifndef FDBCLIENT_RYWITERATOR_H -#define FDBCLIENT_RYWITERATOR_H +#ifndef FDBCLIENT_RANDOMTESTUTILS_H +#define FDBCLIENT_RANDOMTESTUTILS_H #pragma once -#include "fdbclient/SnapshotCache.h" -#include "fdbclient/WriteMap.h" +#include -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 diff --git a/fdbclient/include/fdbclient/ReadYourWrites.h b/fdbclient/include/fdbclient/ReadYourWrites.h index 36f05bbcde..962b0755f0 100644 --- a/fdbclient/include/fdbclient/ReadYourWrites.h +++ b/fdbclient/include/fdbclient/ReadYourWrites.h @@ -25,10 +25,10 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/KeyRangeMap.h" -#include "fdbclient/RYWIterator.h" #include "flow/FastRef.h" #include "flow/WipedString.h" #include +#include // SOMEDAY: Optimize getKey to avoid using getRange @@ -145,7 +145,7 @@ public: [[nodiscard]] Future 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; CoalescedKeyRefRangeMap readConflicts; Map>> watchMap; // Keys that are being watched in this transaction Promise resetPromise; @@ -246,10 +246,6 @@ private: Optional 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 diff --git a/fdbserver/workloads/Unreadable.cpp b/fdbserver/workloads/Unreadable.cpp index 7156752fc9..2465598128 100644 --- a/fdbserver/workloads/Unreadable.cpp +++ b/fdbserver/workloads/Unreadable.cpp @@ -18,7 +18,9 @@ * limitations under the License. */ +#include "fdbclient/Atomic.h" #include "fdbclient/NativeAPI.actor.h" +#include "fdbclient/RandomTestUtils.h" #include "fdbserver/core/TesterInterface.h" #include "BulkSetup.h" #include "fdbclient/ReadYourWrites.h" From 25285841f18b00e7ca47651064f5c69f495a1805 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 19:54:49 -0700 Subject: [PATCH 21/69] Make logsystem replay cursors private --- fdbserver/logrouter/LogRouter.cpp | 1 + fdbserver/logsystem/LogSet.cpp | 2 +- fdbserver/logsystem/LogSystemConsumer.cpp | 1 + fdbserver/logsystem/LogSystemPeekCursor.cpp | 1 + .../fdbserver/logsystem => }/LogSystemTypes.h | 99 +---------------- .../include/fdbserver/logsystem/LogSet.h | 100 ++++++++++++++++++ .../include/fdbserver/logsystem/LogSystem.h | 38 ++++++- 7 files changed, 139 insertions(+), 103 deletions(-) rename fdbserver/logsystem/{include/fdbserver/logsystem => }/LogSystemTypes.h (78%) create mode 100644 fdbserver/logsystem/include/fdbserver/logsystem/LogSet.h diff --git a/fdbserver/logrouter/LogRouter.cpp b/fdbserver/logrouter/LogRouter.cpp index 10cc18e958..ea785d2562 100644 --- a/fdbserver/logrouter/LogRouter.cpp +++ b/fdbserver/logrouter/LogRouter.cpp @@ -21,6 +21,7 @@ #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" diff --git a/fdbserver/logsystem/LogSet.cpp b/fdbserver/logsystem/LogSet.cpp index 5821227760..f2d96d346f 100644 --- a/fdbserver/logsystem/LogSet.cpp +++ b/fdbserver/logsystem/LogSet.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbserver/logsystem/LogSystem.h" +#include "fdbserver/logsystem/LogSet.h" #include diff --git a/fdbserver/logsystem/LogSystemConsumer.cpp b/fdbserver/logsystem/LogSystemConsumer.cpp index 6f8e774f3d..c8876a6a3f 100644 --- a/fdbserver/logsystem/LogSystemConsumer.cpp +++ b/fdbserver/logsystem/LogSystemConsumer.cpp @@ -1,4 +1,5 @@ #include "fdbserver/logsystem/LogSystemConsumer.h" +#include "LogSystemTypes.h" #include #include diff --git a/fdbserver/logsystem/LogSystemPeekCursor.cpp b/fdbserver/logsystem/LogSystemPeekCursor.cpp index cef82a3aee..31ab225754 100644 --- a/fdbserver/logsystem/LogSystemPeekCursor.cpp +++ b/fdbserver/logsystem/LogSystemPeekCursor.cpp @@ -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" diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystemTypes.h b/fdbserver/logsystem/LogSystemTypes.h similarity index 78% rename from fdbserver/logsystem/include/fdbserver/logsystem/LogSystemTypes.h rename to fdbserver/logsystem/LogSystemTypes.h index 3ae8840055..acdda87ed8 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystemTypes.h +++ b/fdbserver/logsystem/LogSystemTypes.h @@ -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 { - double lastReset; - Future 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 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 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 getPrimaryPeekLocation() const = 0; - virtual Optional getCurrentPeekLocation() const = 0; - virtual Version getMaxKnownVersion() const = 0; - virtual Reference cloneNoMore() = 0; - virtual void advanceTo(LogMessageVersion n) = 0; -}; - -class LogSet : NonCopyable, public ReferenceCounted { -public: - std::vector>>> logServers; - std::vector>>> logRouters; - std::vector>>> backupWorkers; - std::vector> connectionResetTrackers; - std::vector> tlogPushDistTrackers; - int32_t tLogWriteAntiQuorum; - int32_t tLogReplicationFactor; - std::vector tLogLocalities; - TLogVersion tLogVersion; - Reference tLogPolicy; - Reference logServerSet; - std::vector logIndexArray; - std::vector logEntryArray; - bool isLocal; - int8_t locality; - Version startVersion; - std::vector> replies; - std::vector> 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 const& localities); - bool satisfiesPolicy(const std::vector& locations); - void getPushLocations( - VectorRef tags, - std::vector& locations, - int locationOffset, - bool allLocations = false, - const Optional>& restrictedLogSet = Optional>()); - -private: - int satelliteTagLocationIndex(Tag tag) const; - std::vector alsoServers, resultEntries; - std::vector newLocations; -}; +#include "fdbserver/logsystem/LogSystem.h" // Leaf replay cursor backed by a single TLog interface. class ServerPeekCursor final : public IReplayPeekCursor, public ReferenceCounted { diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSet.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSet.h new file mode 100644 index 0000000000..1c23726909 --- /dev/null +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSet.h @@ -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 +#include +#include + +#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.actor.h" +#include "flow/Histogram.h" + +struct ConnectionResetInfo : public ReferenceCounted { + double lastReset; + Future resetCheck; + int slowReplies; + int fastReplies; + + ConnectionResetInfo() : lastReset(now()), resetCheck(Void()), slowReplies(0), fastReplies(0) {} +}; + +class LogSet : NonCopyable, public ReferenceCounted { +public: + std::vector>>> logServers; + std::vector>>> logRouters; + std::vector>>> backupWorkers; + std::vector> connectionResetTrackers; + std::vector> tlogPushDistTrackers; + int32_t tLogWriteAntiQuorum; + int32_t tLogReplicationFactor; + std::vector tLogLocalities; + TLogVersion tLogVersion; + Reference tLogPolicy; + Reference logServerSet; + std::vector logIndexArray; + std::vector logEntryArray; + bool isLocal; + int8_t locality; + Version startVersion; + std::vector> replies; + std::vector> 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 const& localities); + bool satisfiesPolicy(const std::vector& locations); + void getPushLocations( + VectorRef tags, + std::vector& locations, + int locationOffset, + bool allLocations = false, + const Optional>& restrictedLogSet = Optional>()); + +private: + int satelliteTagLocationIndex(Tag tag) const; + std::vector alsoServers, resultEntries; + std::vector newLocations; +}; + +#endif // FDBSERVER_LOGSYSTEM_LOGSET_H diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index 5f1ac93279..0575bbbd43 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -42,6 +42,7 @@ #include "fdbserver/core/SpanContextMessage.h" #include "fdbserver/core/TLogInterface.h" #include "fdbserver/core/WorkerInterface.actor.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 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 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 getPrimaryPeekLocation() const = 0; + virtual Optional getCurrentPeekLocation() const = 0; + virtual Version getMaxKnownVersion() const = 0; + virtual Reference cloneNoMore() = 0; + virtual void advanceTo(LogMessageVersion n) = 0; +}; struct LogPushVersionSet { Version prevVersion; @@ -534,8 +566,6 @@ std::vector LogSystem::getReadyNonError(std::vector> const& futures return result; } -#include "LogSystemTypes.h" - template OldLogData::OldLogData(const T& conf) : logRouterTags(conf.logRouterTags), txsTags(conf.txsTags), epochBegin(conf.epochBegin), epochEnd(conf.epochEnd), From 7318c6161327923f63b124ca7412047d73fb2df1 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 20:28:13 -0700 Subject: [PATCH 22/69] Move RYW iterator comment above its declaration --- fdbclient/RYWIterator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbclient/RYWIterator.h b/fdbclient/RYWIterator.h index dc610bdf40..2ca8a70002 100644 --- a/fdbclient/RYWIterator.h +++ b/fdbclient/RYWIterator.h @@ -61,10 +61,10 @@ public: 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() + virtual WriteMap::iterator& extractWriteMapIterator(); void dbg(); From 1a5ca6302475008a2d15db706ab4057bd1fbc55f Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 21:10:00 -0700 Subject: [PATCH 23/69] Fix ReadYourWrites clang-tidy warnings --- fdbclient/ReadYourWrites.cpp | 72 ++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 69e407f057..1d89f825d3 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -152,7 +152,7 @@ public: co_await getRangeValue(ryw, read.key, firstGreaterOrEqual(ryw->getMaxReadKey()), GetRangeLimits(1), it); if (result.readToBegin) co_return allKeys.begin; - if (result.readThroughEnd || !result.size()) + if (result.readThroughEnd || result.empty()) co_return ryw->getMaxReadKey(); co_return result[0].key; } else { @@ -161,7 +161,7 @@ public: co_await getRangeValueBack(ryw, firstGreaterOrEqual(allKeys.begin), read.key, GetRangeLimits(1), it); if (result.readThroughEnd) co_return ryw->getMaxReadKey(); - if (result.readToBegin || !result.size()) + if (result.readToBegin || result.empty()) co_return allKeys.begin; co_return result[0].key; } @@ -209,7 +209,7 @@ public: RangeResult v = co_await ryw->tr.getRange( read.begin, read.end, read.limits, snapshot, backwards ? Reverse::True : Reverse::False); KeyRef maxKey = ryw->getMaxReadKey(); - if (v.size() > 0) { + if (!v.empty()) { if (!backwards && v[v.size() - 1].key >= maxKey) { RangeResult _v = v; int i = _v.size() - 2; @@ -237,14 +237,15 @@ public: static void addConflictRange(ReadYourWritesTransaction* ryw, GetKeyReq read, WriteMap::iterator& it, Key result) { KeyRangeRef readRange; - if (read.key.offset <= 0) + if (read.key.offset <= 0) { readRange = KeyRangeRef(KeyRef(ryw->arena, result), read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey())); - else + } else { readRange = KeyRangeRef(read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey()), keyAfter(result, ryw->arena)); + } it.skip(readRange.begin); updateConflictMap(ryw, readRange, it); @@ -484,7 +485,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { beginKey = std::min(beginKey, data[0].key); if (data.readThrough.present()) { endKey = std::max(endKey, data.readThrough.get()); @@ -519,8 +520,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -549,8 +551,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -641,7 +644,7 @@ public: .detail("Unknown", it.is_unknown_range()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -653,7 +656,7 @@ public: (begin.offset >= 1 && begin.getKey() >= ryw->getMaxReadKey())) { if (end.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedEnd = co_await read( ryw, @@ -681,7 +684,7 @@ public: break; if (it.is_unknown_range()) { - if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && result.size() && + if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && !result.empty() && itemsPastEnd >= 1 - end.offset) { result.more = true; break; @@ -783,8 +786,9 @@ public: if (count) result.append(result.arena(), start, count); ++it; - } else + } else { ++it; + } } result.more = result.more || limits.isReached(); @@ -813,7 +817,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { if (data.readThrough.present()) { beginKey = std::min(data.readThrough.get(), beginKey); } else { @@ -848,8 +852,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -876,8 +881,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -945,7 +951,7 @@ public: .detail("Kv", it.is_kv()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -957,7 +963,7 @@ public: (end.offset <= 1 && end.getKey() == allKeys.begin)) { if (begin.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedBegin = co_await read( ryw, @@ -988,7 +994,7 @@ public: } if (it.is_unknown_range()) { - if (limits.hasByteLimit() && result.size() && itemsPastBegin >= begin.offset - 1) { + if (limits.hasByteLimit() && !result.empty() && itemsPastBegin >= begin.offset - 1) { result.more = true; break; } @@ -1235,7 +1241,7 @@ public: auto itCopy = it; ++it; - ASSERT(itCopy->value.size()); + ASSERT(!itCopy->value.empty()); CODE_PROBE(itCopy->value.size() > 1, "Multiple watches on the same key triggered by RYOW"); for (int i = 0; i < itCopy->value.size(); i++) { @@ -1256,7 +1262,7 @@ public: } } - if (itCopy->value.size() == 0) + if (itCopy->value.empty()) ryw->watchMap.erase(itCopy); } } @@ -1350,11 +1356,12 @@ public: ryw->nativeReadRanges = ryw->tr.readConflictRanges(); ryw->nativeWriteRanges = ryw->tr.writeConflictRanges(); for (const auto& f : ryw->tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { ryw->nativeReadRanges.push_back( ryw->nativeReadRanges.arena(), KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, ryw->nativeReadRanges.arena())); + } } if (ryw->resetPromise.isSet()) @@ -1489,7 +1496,7 @@ public: } static Future onError(ReadYourWritesTransaction* ryw, Error e) { - if (ryw->debugTraces.size() > 0 || ryw->debugMessages.size() > 0) { + if (!ryw->debugTraces.empty() || !ryw->debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(printDebugMessages(ryw, {}, e).isReady()); } @@ -2046,10 +2053,11 @@ RangeResult ReadYourWritesTransaction::getReadConflictRangeIntersecting(KeyRange for (const auto& range : nativeReadRanges) readConflicts.insert(range.withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); for (const auto& f : tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { readConflicts.insert(KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } auto beginIter = readConflicts.rangeContaining(kr.begin); if (beginIter->begin() != kr.begin) @@ -2076,11 +2084,12 @@ RangeResult ReadYourWritesTransaction::getWriteConflictRangeIntersecting(KeyRang if (it.beginKey() > allKeys.begin) --it; for (; it.beginKey() < strippedWriteRangePrefix.end; ++it) { - if (it.is_conflict_range()) + if (it.is_conflict_range()) { writeConflicts.insert( KeyRangeRef(it.beginKey().toArena(result.arena()), it.endKey().toArena(result.arena())) .withPrefix(writeConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } } else { for (const auto& range : tr.writeConflictRanges()) @@ -2412,7 +2421,7 @@ Future ReadYourWritesTransaction::commit() { result = RYWImpl::commit(this); } - return debugMessages.size() > 0 || debugTraces.size() > 0 ? RYWImpl::printDebugMessages(this, result) : result; + return !debugMessages.empty() || !debugTraces.empty() ? RYWImpl::printDebugMessages(this, result) : result; } Future> ReadYourWritesTransaction::getVersionstamp() { @@ -2534,7 +2543,7 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep reading = std::move(r.reading); resetPromise = std::move(r.resetPromise); r.resetPromise = Promise(); - deferredError = std::move(r.deferredError); + deferredError = r.deferredError; retries = r.retries; approximateSize = r.approximateSize; timeoutActor = r.timeoutActor; @@ -2555,7 +2564,7 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep } ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept - : deferredError(std::move(r.deferredError)), arena(std::move(r.arena)), rywState(std::move(r.rywState)), + : 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) { @@ -2637,7 +2646,7 @@ void ReadYourWritesTransaction::cancel() { } void ReadYourWritesTransaction::reset() { - if (debugTraces.size() > 0 || debugMessages.size() > 0) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(RYWImpl::printDebugMessages(this, {}).isReady()); } @@ -2676,7 +2685,7 @@ ReadYourWritesTransaction::~ReadYourWritesTransaction() { if (!resetPromise.isSet()) resetPromise.sendError(transaction_cancelled()); - if (debugTraces.size() || debugMessages.size()) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument [[maybe_unused]] Future f = RYWImpl::printDebugMessages(this, {}); } @@ -2700,14 +2709,15 @@ void ReadYourWritesTransaction::debugLogRetries(Optional error) { if (!transactionDebugInfo->transactionName.empty()) transactionNameStr = format(" in transaction '%s'", printable(StringRef(transactionDebugInfo->transactionName)).c_str()); - if (!g_network->isSimulated()) // Fuzz workload turns this on, but we do not want stderr output in - // simulation + // Fuzz workload turns this on, but we do not want stderr output in simulation. + if (!g_network->isSimulated()) { fprintf(stderr, "fdb WARNING: long transaction (%.2fs elapsed%s, %d retries, %s)\n", elapsed, transactionNameStr.c_str(), retries, committed ? "committed" : error.get().what()); + } { TraceEvent trace = TraceEvent("LongTransaction"); if (error.present()) From b0142d478c732f4b3e5d18ce562e9bfcdd90c521 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 6 Jul 2026 01:44:54 -0700 Subject: [PATCH 24/69] Select modal leader priority across coordinators --- fdbclient/MonitorLeader.cpp | 107 ++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/fdbclient/MonitorLeader.cpp b/fdbclient/MonitorLeader.cpp index 8ac072e743..ac800eab41 100644 --- a/fdbclient/MonitorLeader.cpp +++ b/fdbclient/MonitorLeader.cpp @@ -532,36 +532,47 @@ Future 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> getLeader(const std::vector>& 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(nominees[i].get(), true); - std::vector> maskedNominees; + struct MaskedNominee { + UID maskedChangeID; + UID changeID; + int nomineeIndex; + }; + + std::vector 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::sort(maskedNominees.begin(), - maskedNominees.end(), - [](const std::pair& l, const std::pair& r) { return l.first < r.first; }); + std::sort(maskedNominees.begin(), maskedNominees.end(), [](const MaskedNominee& l, const MaskedNominee& r) { + if (l.maskedChangeID != r.maskedChangeID) { + return l.maskedChangeID < r.maskedChangeID; + } + return l.changeID < r.changeID; + }); 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 +584,84 @@ Optional> getLeader(const std::vector bestVariantCount) { + representativeIdx = maskedNominees[currentIdx].nomineeIndex; + bestVariantCount = curCount; + } + } + bool majority = bestCount >= nominees.size() / 2 + 1; - return std::pair(nominees[maskedNominees[bestIdx].second].get(), majority); + return std::pair(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>& 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> nominees(10, remote); + nominees[staleIndex] = primary; + assertLeader(nominees, remote, true); + + nominees.assign(10, primary); + nominees[staleIndex] = remote; + assertLeader(nominees, primary, true); + } + + std::vector> 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 From 245fd52800d5a54a69458930c83f78c17d9225d0 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 21:43:34 -0700 Subject: [PATCH 25/69] Simplify MaskedNominee comparison --- fdbclient/MonitorLeader.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fdbclient/MonitorLeader.cpp b/fdbclient/MonitorLeader.cpp index ac800eab41..04ddaeed6d 100644 --- a/fdbclient/MonitorLeader.cpp +++ b/fdbclient/MonitorLeader.cpp @@ -29,6 +29,8 @@ #include "flow/IConnection.h" #include "flow/CoroUtils.h" +#include + namespace { std::string trim(std::string const& connectionString) { @@ -544,6 +546,8 @@ Optional> getLeader(const std::vector(MaskedNominee const&) const = default; }; std::vector maskedNominees; @@ -560,12 +564,7 @@ Optional> getLeader(const std::vector>(); - std::sort(maskedNominees.begin(), maskedNominees.end(), [](const MaskedNominee& l, const MaskedNominee& r) { - if (l.maskedChangeID != r.maskedChangeID) { - return l.maskedChangeID < r.maskedChangeID; - } - return l.changeID < r.changeID; - }); + std::sort(maskedNominees.begin(), maskedNominees.end()); int bestCount = 1; int bestIdx = 0; From 7d023a1f3bd2dfe2ac435e820ff917e1d167c859 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 22:24:27 -0700 Subject: [PATCH 26/69] Preserve immediate DD relocator error propagation --- fdbserver/datadistributor/DDRelocationQueue.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index a6a9897717..d14a4c7c46 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -3088,7 +3088,11 @@ struct DDQueueImpl { error = e; } // A relocator can signal an error inline while launchQueuedWork() is repairing its maps. Keep DD alive - // until that mutation finishes before propagating the error and tearing the queue down. + // until that mutation finishes before propagating the error and tearing the queue down. Preserve the + // immediate error path when no mutation is active, since taking an available FlowLock still yields. + if (error.isValid() && state->queueMutationLock.available() > 0) { + throw error; + } co_await state->queueMutationLock.take(); FlowLock::Releaser lockGuard(state->queueMutationLock); if (error.isValid()) { @@ -3301,5 +3305,12 @@ TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { observed = e; } ASSERT(observed.code() == error_code_movekeys_conflict); + + Promise immediateError; + Future immediate = DDQueueImpl::waitAndValidate(&state, immediateError.getFuture()); + immediateError.sendError(movekeys_conflict()); + ASSERT(immediate.isReady()); + ASSERT(immediate.isError()); + ASSERT(immediate.getError().code() == error_code_movekeys_conflict); co_return; } From e824a6649f796ea9fb8b1d07304bb7bb33c91c06 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 03:42:41 -0700 Subject: [PATCH 27/69] Preserve queued shard splits on destination-team retry --- .../datadistributor/DDRelocationQueue.cpp | 166 ++++++++++++------ fdbserver/datadistributor/DataDistribution.h | 10 ++ 2 files changed, 127 insertions(+), 49 deletions(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 1e67f26876..a1430e5389 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -82,14 +82,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 +134,26 @@ Optional 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& rd) { + return !doBulkLoading && !rd.isRestore(); +} + +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, public IDataDistributionTeam { std::vector> teams; std::vector tempServerIDs; @@ -808,7 +833,31 @@ void DDQueue::queueRelocation(RelocateShard rs, std::set& 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); @@ -1393,16 +1442,6 @@ Future cancelDataMove(class DDQueue* self, KeyRange range, const DDEnabled } } -void requeueCancelledRelocation(DDQueue* self, RelocateData const& rd, bool doBulkLoading) { - if (!doBulkLoading) { - RelocateShard retry(rd.keys, rd.priority, rd.reason, rd.randomId); - if (Optional parentRange = rd.getParentRange(); parentRange.present()) { - retry.setParentRange(parentRange.get()); - } - self->output.send(retry); - } -} - static std::string destServersString(std::vector, bool>> const& bestTeams) { std::stringstream ss; @@ -1507,6 +1546,7 @@ Future dataDistributionRelocator(DDQueue* self, PromiseStream dataTransferComplete(self->dataTransferComplete); PromiseStream relocationComplete(self->relocationComplete); bool signalledTransferComplete = false; + bool retryAfterDestinationTeamFailure = false; UID distributorId = self->distributorId; ParallelTCInfo healthyDestinations; @@ -2250,6 +2290,7 @@ Future dataDistributionRelocator(DDQueue* self, rd.bulkLoadTask.get().completeAck.send( BulkLoadAck(/*unretryableError=*/true, rd.priority)); } + retryAfterDestinationTeamFailure = shouldRetryDestinationTeamFailure(doBulkLoading, rd); throw data_move_dest_team_not_found(); } } @@ -2435,6 +2476,19 @@ Future dataDistributionRelocator(DDQueue* self, if (!signalledTransferComplete) dataTransferComplete.send(rd); + 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) { @@ -2447,7 +2501,6 @@ Future dataDistributionRelocator(DDQueue* self, if (err.code() == error_code_data_move_dest_team_not_found) { co_await cancelDataMove(self, rd.keys, ddEnabledState); - requeueCancelledRelocation(self, rd, doBulkLoading); TraceEvent(SevWarnAlways, "RelocateShardCancelDataMoveTeamNotFound") .detail("Src", describe(rd.src)) .detail("DataMoveMetaData", rd.dataMove != nullptr ? rd.dataMove->meta.toString() : "Empty"); @@ -3244,42 +3297,57 @@ TEST_CASE("/DataDistribution/DDQueue/BatchDrainRelocationComplete") { std::cout << "BatchDrainRelocationComplete: drained " << drained << " of " << N << " completions\n"; } -TEST_CASE("/DataDistribution/DDQueue/RequeueCancelledRelocation") { - DDQueue self; - FutureStream retries = self.output.getFuture(); - KeyRange keys = KeyRangeRef("begin"_sr, "end"_sr); - UID traceId(1, 2); - RelocateData rd( - RelocateShard(keys, DataMovementReason::TEAM_CONTAINS_UNDESIRED_SERVER, RelocateReason::OTHER, traceId)); - rd.dataMoveId = UID(3, 4); +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; - requeueCancelledRelocation(&self, rd, false); - ASSERT(retries.isReady()); - RelocateShard retry = retries.pop(); + 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.moveReason == DataMovementReason::TEAM_CONTAINS_UNDESIRED_SERVER); - ASSERT(retry.traceId == traceId); + 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> 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.cancelled); - ASSERT(!retry.getParentRange().present()); - - KeyRange parent = KeyRangeRef("parentBegin"_sr, "parentEnd"_sr); - RelocateShard split(keys, DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT, traceId); - split.setParentRange(parent); - RelocateData splitRd(split); - - requeueCancelledRelocation(&self, splitRd, false); - ASSERT(retries.isReady()); - RelocateShard splitRetry = retries.pop(); - ASSERT(splitRetry.reason == RelocateReason::SIZE_SPLIT); - ASSERT(splitRetry.getParentRange().present()); - ASSERT(splitRetry.getParentRange().get() == parent); - - requeueCancelledRelocation(&self, rd, true); - ASSERT(!retries.isReady()); + 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(); + ASSERT(!shouldRetryDestinationTeamFailure(false, restore)); return Void(); } diff --git a/fdbserver/datadistributor/DataDistribution.h b/fdbserver/datadistributor/DataDistribution.h index 5b8b12f0e7..4daac8b877 100644 --- a/fdbserver/datadistributor/DataDistribution.h +++ b/fdbserver/datadistributor/DataDistribution.h @@ -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 retryIntent; + // Initialization when define is a better practice. We should avoid assignment of member after definition. // static RelocateShard emptyRelocateShard() { return {}; } From 51ee380152bb921af4246032f8433f745459ef5d Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 21:58:37 -0700 Subject: [PATCH 28/69] Fix Joshua code-probe frequency accounting --- contrib/TestHarness2/test_harness/results.py | 12 ++-- .../TestHarness2/test_harness/test_results.py | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 contrib/TestHarness2/test_harness/test_results.py diff --git a/contrib/TestHarness2/test_harness/results.py b/contrib/TestHarness2/test_harness/results.py index 284468965d..91ef6c64a6 100644 --- a/contrib/TestHarness2/test_harness/results.py +++ b/contrib/TestHarness2/test_harness/results.py @@ -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: diff --git a/contrib/TestHarness2/test_harness/test_results.py b/contrib/TestHarness2/test_harness/test_results.py new file mode 100644 index 0000000000..8b741afde3 --- /dev/null +++ b/contrib/TestHarness2/test_harness/test_results.py @@ -0,0 +1,65 @@ +import collections +import importlib +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.get("fdb") +if fdb_stub is None: + fdb_stub = types.ModuleType("fdb") + fdb_stub.__path__ = [] + fdb_stub.api_version = lambda *_: None + fdb_stub.transactional = lambda function: function + fdb_stub.tuple = types.ModuleType("fdb.tuple") + sys.modules["fdb"] = fdb_stub + sys.modules["fdb.tuple"] = fdb_stub.tuple +harness_fdb = importlib.import_module("test_harness.fdb") +EnsembleResults = importlib.import_module("test_harness.results").EnsembleResults + + +class EnsembleResultsTest(unittest.TestCase): + def test_coverage_threshold_uses_total_test_runs(self): + statistics = SimpleNamespace( + stats=collections.OrderedDict( + ( + ("fast", SimpleNamespace(runtime=20, run_count=60000)), + ("slow", SimpleNamespace(runtime=10, run_count=40000)), + ) + ) + ) + coverage = collections.OrderedDict( + ( + (Coverage("fdbserver/a.cpp", 10, "nonrare", False), 5), + (Coverage("fdbserver/b.cpp", 20, "rare", True), 4), + (Coverage("fdbserver/c.cpp", 30, "hit", False), 6), + ) + ) + with mock.patch.object( + harness_fdb, "Statistics", return_value=statistics + ), 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.global_statistics.total_test_runs, 100000) + self.assertEqual(results.global_statistics.total_cpu_time, 30) + 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.assertEqual(results.min_coverage_hit, 4) + self.assertFalse(results.coverage_ok) + + +if __name__ == "__main__": + unittest.main() From c1bb4f8392f22fedd45675d66a23e8357d90f84c Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 21:59:54 -0700 Subject: [PATCH 29/69] Persist late Joshua code-probe misses --- contrib/TestHarness2/test_harness/fdb.py | 9 ++ .../test_harness/test_fdb_coverage.py | 115 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 contrib/TestHarness2/test_harness/test_fdb_coverage.py diff --git a/contrib/TestHarness2/test_harness/fdb.py b/contrib/TestHarness2/test_harness/fdb.py index 3ee0cc1b5e..d0db8b9608 100644 --- a/contrib/TestHarness2/test_harness/fdb.py +++ b/contrib/TestHarness2/test_harness/fdb.py @@ -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(" Date: Thu, 16 Jul 2026 22:01:00 -0700 Subject: [PATCH 30/69] Propagate Joshua correctness-wrapper failures --- contrib/Joshua/scripts/correctnessTest.sh | 12 ++- contrib/Joshua/tests/correctnessTest_test.sh | 88 ++++++++++++++++++++ 2 files changed, 96 insertions(+), 4 deletions(-) create mode 100755 contrib/Joshua/tests/correctnessTest_test.sh diff --git a/contrib/Joshua/scripts/correctnessTest.sh b/contrib/Joshua/scripts/correctnessTest.sh index 54c9ad7be5..1b342586bb 100755 --- a/contrib/Joshua/scripts/correctnessTest.sh +++ b/contrib/Joshua/scripts/correctnessTest.sh @@ -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 "" + echo "" | 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 diff --git a/contrib/Joshua/tests/correctnessTest_test.sh b/contrib/Joshua/tests/correctnessTest_test.sh new file mode 100755 index 0000000000..73f6500c77 --- /dev/null +++ b/contrib/Joshua/tests/correctnessTest_test.sh @@ -0,0 +1,88 @@ +#!/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 '' + exit 23 + ;; + no_output) + exit 0 + ;; + fail) + echo '' + exit 0 + ;; + pass|tee_failure) + echo '' + exit 0 + ;; +esac +FAKE_PYTHON +chmod +x "${test_root}/bin/python3" + +cat > "${test_root}/bin/tee" <<'FAKE_TEE' +#!/usr/bin/env bash + +/usr/bin/tee "$@" +if [ "${FAKE_HARNESS_MODE}" = tee_failure ]; then + exit 45 +fi +FAKE_TEE +chmod +x "${test_root}/bin/tee" + +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 expected_preserved=$4 + 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 stderr_file="${output_dir}/stderr.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> "${stderr_file}" + status=$? + set -e + + test "${status}" -eq "${expected_exit}" + grep -q "Ok=\"${expected_ok}\"" "${stdout_file}" + if [ "${expected_preserved}" = true ]; 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 true +run_case tee_failure 45 1 true +run_case no_output 1 0 true +run_case fail 1 0 true +run_case pass 0 1 false + +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' From 840f463ddc8cd8a1afcab86fa8c9067d69365856 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 00:44:45 -0700 Subject: [PATCH 31/69] Convert WorkerInterface actors to coroutines --- .../fdbserver/core/WorkerInterface.actor.h | 103 +++++++++--------- 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h index 73d0cbb982..74c1eb7ef6 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h @@ -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; @@ -1174,42 +1169,44 @@ bool addressInDbAndRemoteDc( extern bool isSimulatorProcessUnreliable(); -ACTOR template -Future ioTimeoutError(Future what, double time, const char* context = nullptr) { +template +Future ioTimeoutError(Future 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 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 +template Future ioDegradedOrTimeoutError(Future what, double errTime, Reference> 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 ioDegradedOrTimeoutError(Future what, if (degradedTime < errTime) { Future 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 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 From 4a3db78aa286b629b01fda44308ed4913ab090d3 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 00:58:03 -0700 Subject: [PATCH 32/69] Rename WorkerInterface coroutine header --- design/AI-generated/FDB_NETWORK_PROTOCOL.md | 2 +- design/AI-generated/foundationdb_subsystem_map.md | 2 +- design/AI-generated/subsystem_04_cluster_controller.md | 4 ++-- fdbserver/SimulatedCluster.cpp | 2 +- fdbserver/backupworker/BackupWorker.cpp | 2 +- fdbserver/cdcproxy/CDCProxy.cpp | 2 +- fdbserver/clustercontroller/ClusterController.actor.cpp | 2 +- fdbserver/clustercontroller/ClusterController.h | 2 +- fdbserver/clustercontroller/ClusterRecovery.h | 2 +- fdbserver/clustercontroller/Status.cpp | 2 +- fdbserver/clustercontroller/Status.h | 2 +- fdbserver/commitproxy/CommitProxyServer.cpp | 2 +- fdbserver/consistencyscan/ConsistencyScan.cpp | 2 +- fdbserver/coordinator/Coordination.cpp | 2 +- fdbserver/core/OpenDatabase.cpp | 2 +- fdbserver/core/QuietDatabase.cpp | 2 +- fdbserver/core/WorkerInterface.cpp | 2 +- fdbserver/core/WorkerInterfaceTests.cpp | 2 +- fdbserver/core/include/fdbserver/core/QuietDatabase.h | 2 +- fdbserver/core/include/fdbserver/core/ServerDBInfo.h | 2 +- fdbserver/core/include/fdbserver/core/WorkerEvents.h | 2 +- .../core/{WorkerInterface.actor.h => WorkerInterface.h} | 2 +- fdbserver/fdbserver.cpp | 2 +- fdbserver/grvproxy/GrvProxyServer.cpp | 2 +- fdbserver/kvstore/VersionedBTree.actor.cpp | 2 +- fdbserver/logrouter/LogRouter.cpp | 2 +- fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h | 2 +- fdbserver/resolver/Resolver.cpp | 2 +- fdbserver/storageserver/storageserver.cpp | 2 +- fdbserver/tester/ConsistencyChecker.cpp | 2 +- fdbserver/tester/TesterServer.cpp | 2 +- fdbserver/tester/test.cpp | 2 +- fdbserver/tlog/TLogServer.cpp | 2 +- fdbserver/tlog/TestTLogServer.cpp | 2 +- fdbserver/worker/RoleLineage.h | 2 +- fdbserver/worker/worker.cpp | 2 +- fdbserver/workloads/DiskFailureInjection.cpp | 2 +- fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp | 2 +- fdbserver/workloads/FailoverWithSSLag.cpp | 2 +- fdbserver/workloads/HealthMetricsApi.cpp | 2 +- fdbserver/workloads/KillRegion.cpp | 2 +- fdbserver/workloads/LogMetrics.cpp | 2 +- fdbserver/workloads/MachineAttrition.cpp | 2 +- fdbserver/workloads/Ping.cpp | 2 +- fdbserver/workloads/ReadWrite.cpp | 2 +- fdbserver/workloads/RemoveServersSafely.cpp | 2 +- fdbserver/workloads/SkewedReadWrite.cpp | 2 +- fdbserver/workloads/SnapTest.cpp | 2 +- fdbserver/workloads/TargetedKill.cpp | 2 +- fdbserver/workloads/Throughput.cpp | 2 +- fdbserver/workloads/WorkerErrors.cpp | 2 +- fdbserver/workloads/WriteBandwidth.cpp | 2 +- fdbserver/workloads/WriteTagThrottling.cpp | 2 +- 53 files changed, 54 insertions(+), 54 deletions(-) rename fdbserver/core/include/fdbserver/core/{WorkerInterface.actor.h => WorkerInterface.h} (99%) diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md index 31e0be5fcd..533c710622 100644 --- a/design/AI-generated/FDB_NETWORK_PROTOCOL.md +++ b/design/AI-generated/FDB_NETWORK_PROTOCOL.md @@ -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. diff --git a/design/AI-generated/foundationdb_subsystem_map.md b/design/AI-generated/foundationdb_subsystem_map.md index 84f0e22ae0..2f53560818 100644 --- a/design/AI-generated/foundationdb_subsystem_map.md +++ b/design/AI-generated/foundationdb_subsystem_map.md @@ -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) --- diff --git a/design/AI-generated/subsystem_04_cluster_controller.md b/design/AI-generated/subsystem_04_cluster_controller.md index 86a285215a..f466e142f3 100644 --- a/design/AI-generated/subsystem_04_cluster_controller.md +++ b/design/AI-generated/subsystem_04_cluster_controller.md @@ -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 | diff --git a/fdbserver/SimulatedCluster.cpp b/fdbserver/SimulatedCluster.cpp index a6263140c2..a0d62a34d7 100644 --- a/fdbserver/SimulatedCluster.cpp +++ b/fdbserver/SimulatedCluster.cpp @@ -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" diff --git a/fdbserver/backupworker/BackupWorker.cpp b/fdbserver/backupworker/BackupWorker.cpp index 9e51b4edb8..993f597401 100644 --- a/fdbserver/backupworker/BackupWorker.cpp +++ b/fdbserver/backupworker/BackupWorker.cpp @@ -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" diff --git a/fdbserver/cdcproxy/CDCProxy.cpp b/fdbserver/cdcproxy/CDCProxy.cpp index 686dbc5c80..91020a7fe8 100644 --- a/fdbserver/cdcproxy/CDCProxy.cpp +++ b/fdbserver/cdcproxy/CDCProxy.cpp @@ -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" diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 0f1f147e96..9744258ba3 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -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" diff --git a/fdbserver/clustercontroller/ClusterController.h b/fdbserver/clustercontroller/ClusterController.h index 13d298ac7e..0cb3635780 100644 --- a/fdbserver/clustercontroller/ClusterController.h +++ b/fdbserver/clustercontroller/ClusterController.h @@ -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" diff --git a/fdbserver/clustercontroller/ClusterRecovery.h b/fdbserver/clustercontroller/ClusterRecovery.h index 2a4941c1a0..ac6f409661 100644 --- a/fdbserver/clustercontroller/ClusterRecovery.h +++ b/fdbserver/clustercontroller/ClusterRecovery.h @@ -35,7 +35,7 @@ #include "fdbserver/logsystem/LogSystem.h" #include "fdbserver/core/LogSystemConfig.h" #include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/CoroUtils.h" #include "flow/Error.h" #include "flow/SystemMonitor.h" diff --git a/fdbserver/clustercontroller/Status.cpp b/fdbserver/clustercontroller/Status.cpp index 8193be0f13..19f5c3cb6d 100644 --- a/fdbserver/clustercontroller/Status.cpp +++ b/fdbserver/clustercontroller/Status.cpp @@ -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 #include "ClusterRecovery.h" #include "fdbclient/ClusterConnectionMemoryRecord.h" diff --git a/fdbserver/clustercontroller/Status.h b/fdbserver/clustercontroller/Status.h index 827ab5f6b1..dda77be220 100644 --- a/fdbserver/clustercontroller/Status.h +++ b/fdbserver/clustercontroller/Status.h @@ -22,7 +22,7 @@ #include "fdbrpc/fdbrpc.h" #include "fdbserver/core/CoordinationInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/ClusterInterface.h" diff --git a/fdbserver/commitproxy/CommitProxyServer.cpp b/fdbserver/commitproxy/CommitProxyServer.cpp index b836278ee7..869f44a666 100644 --- a/fdbserver/commitproxy/CommitProxyServer.cpp +++ b/fdbserver/commitproxy/CommitProxyServer.cpp @@ -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" diff --git a/fdbserver/consistencyscan/ConsistencyScan.cpp b/fdbserver/consistencyscan/ConsistencyScan.cpp index c33c2324ce..9382f88af9 100644 --- a/fdbserver/consistencyscan/ConsistencyScan.cpp +++ b/fdbserver/consistencyscan/ConsistencyScan.cpp @@ -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" diff --git a/fdbserver/coordinator/Coordination.cpp b/fdbserver/coordinator/Coordination.cpp index 5cb7706f8c..4bc634f032 100644 --- a/fdbserver/coordinator/Coordination.cpp +++ b/fdbserver/coordinator/Coordination.cpp @@ -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" diff --git a/fdbserver/core/OpenDatabase.cpp b/fdbserver/core/OpenDatabase.cpp index fa83ac4258..6ed2b8a9a0 100644 --- a/fdbserver/core/OpenDatabase.cpp +++ b/fdbserver/core/OpenDatabase.cpp @@ -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 extractClientInfo(Reference const> db, Reference> info) { diff --git a/fdbserver/core/QuietDatabase.cpp b/fdbserver/core/QuietDatabase.cpp index d6c0861399..7c7f9581de 100644 --- a/fdbserver/core/QuietDatabase.cpp +++ b/fdbserver/core/QuietDatabase.cpp @@ -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" diff --git a/fdbserver/core/WorkerInterface.cpp b/fdbserver/core/WorkerInterface.cpp index 29ebd158c5..1b1addd162 100644 --- a/fdbserver/core/WorkerInterface.cpp +++ b/fdbserver/core/WorkerInterface.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" Future extractClusterInterface(Reference> const> in, Reference>> out) { diff --git a/fdbserver/core/WorkerInterfaceTests.cpp b/fdbserver/core/WorkerInterfaceTests.cpp index f1267a2b61..84326eab41 100644 --- a/fdbserver/core/WorkerInterfaceTests.cpp +++ b/fdbserver/core/WorkerInterfaceTests.cpp @@ -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" diff --git a/fdbserver/core/include/fdbserver/core/QuietDatabase.h b/fdbserver/core/include/fdbserver/core/QuietDatabase.h index 4fa8e4a812..9dac489e1b 100644 --- a/fdbserver/core/include/fdbserver/core/QuietDatabase.h +++ b/fdbserver/core/include/fdbserver/core/QuietDatabase.h @@ -24,7 +24,7 @@ #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" Future getDataInFlight(Database cx, Reference const> dbInfo); Future> getTLogQueueInfo(Database cx, diff --git a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h index 6adc7b1652..31a2fdd9be 100644 --- a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h +++ b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h @@ -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; diff --git a/fdbserver/core/include/fdbserver/core/WorkerEvents.h b/fdbserver/core/include/fdbserver/core/WorkerEvents.h index e5bc268ac2..b2791d72eb 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerEvents.h +++ b/fdbserver/core/include/fdbserver/core/WorkerEvents.h @@ -25,7 +25,7 @@ #include #include "flow/ITrace.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" struct WorkerEvents : std::map {}; diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.h similarity index 99% rename from fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h rename to fdbserver/core/include/fdbserver/core/WorkerInterface.h index 74c1eb7ef6..53a2814ac9 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.h @@ -1,5 +1,5 @@ /* - * WorkerInterface.actor.h + * WorkerInterface.h * * This source file is part of the FoundationDB open source project * diff --git a/fdbserver/fdbserver.cpp b/fdbserver/fdbserver.cpp index 70a9689740..c981a86bd7 100644 --- a/fdbserver/fdbserver.cpp +++ b/fdbserver/fdbserver.cpp @@ -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 diff --git a/fdbserver/grvproxy/GrvProxyServer.cpp b/fdbserver/grvproxy/GrvProxyServer.cpp index 5bf8ed3a0f..32e1a713b4 100644 --- a/fdbserver/grvproxy/GrvProxyServer.cpp +++ b/fdbserver/grvproxy/GrvProxyServer.cpp @@ -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" diff --git a/fdbserver/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..6afa07849a 100644 --- a/fdbserver/kvstore/VersionedBTree.actor.cpp +++ b/fdbserver/kvstore/VersionedBTree.actor.cpp @@ -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" diff --git a/fdbserver/logrouter/LogRouter.cpp b/fdbserver/logrouter/LogRouter.cpp index 10cc18e958..17d3f2470a 100644 --- a/fdbserver/logrouter/LogRouter.cpp +++ b/fdbserver/logrouter/LogRouter.cpp @@ -24,7 +24,7 @@ #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" diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index c90986872b..b3573501c7 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -41,7 +41,7 @@ #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 "flow/Arena.h" #include "flow/Error.h" #include "flow/ActorCollection.h" diff --git a/fdbserver/resolver/Resolver.cpp b/fdbserver/resolver/Resolver.cpp index 4c6c185540..dc9d875a9d 100644 --- a/fdbserver/resolver/Resolver.cpp +++ b/fdbserver/resolver/Resolver.cpp @@ -40,7 +40,7 @@ #include "fdbserver/core/StorageMetrics.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/resolver/Resolver.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "ConflictSet.h" #include "flow/ActorCollection.h" #include "flow/Error.h" diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index b7be1522e1..1e96d697bd 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -96,7 +96,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/DataMovement.h" #include "fdbserver/storageserver/StorageServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "StorageServerUtils.h" #include "flow/CoroUtils.h" #include "flow/TDMetric.h" diff --git a/fdbserver/tester/ConsistencyChecker.cpp b/fdbserver/tester/ConsistencyChecker.cpp index ec19a1e940..55fac113fa 100644 --- a/fdbserver/tester/ConsistencyChecker.cpp +++ b/fdbserver/tester/ConsistencyChecker.cpp @@ -36,7 +36,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/MoveKeys.h" #include "fdbserver/core/QuietDatabase.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "ConsistencyChecker.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/tester/TesterServer.cpp b/fdbserver/tester/TesterServer.cpp index 61501adeb3..5b89b184cd 100644 --- a/fdbserver/tester/TesterServer.cpp +++ b/fdbserver/tester/TesterServer.cpp @@ -34,7 +34,7 @@ #include "fdbserver/core/FDBSimulatorProcessInfo.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/ServerDBInfo.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "TesterServer.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/tester/test.cpp b/fdbserver/tester/test.cpp index 7dcc9e6ff8..41548787b1 100644 --- a/fdbserver/tester/test.cpp +++ b/fdbserver/tester/test.cpp @@ -41,7 +41,7 @@ #include "fdbclient/NativeAPI.actor.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 "KnobProtectiveGroups.h" #include "ConsistencyChecker.h" diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 3a8695ed48..f6b67e1564 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -36,7 +36,7 @@ #include "fdbserver/core/TLogInterface.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/tlog/TLogServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "fdbrpc/FailureMonitor.h" #include "fdbrpc/sim_validation.h" diff --git a/fdbserver/tlog/TestTLogServer.cpp b/fdbserver/tlog/TestTLogServer.cpp index a4abfb46a2..0c222a997b 100644 --- a/fdbserver/tlog/TestTLogServer.cpp +++ b/fdbserver/tlog/TestTLogServer.cpp @@ -30,7 +30,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/TLogInterface.h" #include "fdbserver/tlog/TLogServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/logsystem/LogSystem.h" #include "fdbserver/logsystem/LogSystemFactory.h" #include "flow/IRandom.h" diff --git a/fdbserver/worker/RoleLineage.h b/fdbserver/worker/RoleLineage.h index fd07895eac..10902853eb 100644 --- a/fdbserver/worker/RoleLineage.h +++ b/fdbserver/worker/RoleLineage.h @@ -22,7 +22,7 @@ #include "fdbclient/ActorLineageProfiler.h" #include "fdbclient/ProcessClass.h" #include "fdbserver/core/ProcessClassRecruitment.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include #include diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index 200eb6408a..b9a9bc19a6 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -60,7 +60,7 @@ #include "fdbserver/logrouter/LogRouter.h" #include "fdbserver/core/BackupInterface.h" #include "RoleLineage.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/CoroFlow.h" #include "fdbserver/worker/Worker.h" #include "fdbserver/kvstore/IKeyValueStore.h" diff --git a/fdbserver/workloads/DiskFailureInjection.cpp b/fdbserver/workloads/DiskFailureInjection.cpp index af4996d86b..8326f98e20 100644 --- a/fdbserver/workloads/DiskFailureInjection.cpp +++ b/fdbserver/workloads/DiskFailureInjection.cpp @@ -22,7 +22,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/WorkerEvents.h" diff --git a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp index 4bc9df90b9..15be1e5a3b 100644 --- a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp +++ b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" diff --git a/fdbserver/workloads/FailoverWithSSLag.cpp b/fdbserver/workloads/FailoverWithSSLag.cpp index 4d91a0c7b2..9cdd892db4 100644 --- a/fdbserver/workloads/FailoverWithSSLag.cpp +++ b/fdbserver/workloads/FailoverWithSSLag.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/FDBSimulationPolicy.h" diff --git a/fdbserver/workloads/HealthMetricsApi.cpp b/fdbserver/workloads/HealthMetricsApi.cpp index 63793304be..ad6e7c068e 100644 --- a/fdbserver/workloads/HealthMetricsApi.cpp +++ b/fdbserver/workloads/HealthMetricsApi.cpp @@ -20,7 +20,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" // NOTE: it might be simpler to test health metrics via something // other than simulation. Testing equivalent to what this workload does can diff --git a/fdbserver/workloads/KillRegion.cpp b/fdbserver/workloads/KillRegion.cpp index 0d2a4b84ea..d45674a595 100644 --- a/fdbserver/workloads/KillRegion.cpp +++ b/fdbserver/workloads/KillRegion.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/core/RecoveryState.h" diff --git a/fdbserver/workloads/LogMetrics.cpp b/fdbserver/workloads/LogMetrics.cpp index 4ce5f37189..0b74110545 100644 --- a/fdbserver/workloads/LogMetrics.cpp +++ b/fdbserver/workloads/LogMetrics.cpp @@ -25,7 +25,7 @@ #include "fdbrpc/simulator.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/SystemData.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/ServerDBInfo.h" diff --git a/fdbserver/workloads/MachineAttrition.cpp b/fdbserver/workloads/MachineAttrition.cpp index 004d2ef7ab..d82dd4cccb 100644 --- a/fdbserver/workloads/MachineAttrition.cpp +++ b/fdbserver/workloads/MachineAttrition.cpp @@ -23,7 +23,7 @@ #include "fdbclient/CoordinationInterface.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" #include "fdbserver/core/FDBSimulatorProcessInfo.h" diff --git a/fdbserver/workloads/Ping.cpp b/fdbserver/workloads/Ping.cpp index 50cbbf5485..68346356a6 100644 --- a/fdbserver/workloads/Ping.cpp +++ b/fdbserver/workloads/Ping.cpp @@ -22,7 +22,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" struct PingWorkloadInterface { diff --git a/fdbserver/workloads/ReadWrite.cpp b/fdbserver/workloads/ReadWrite.cpp index 2199c73e04..83268b7e1e 100644 --- a/fdbserver/workloads/ReadWrite.cpp +++ b/fdbserver/workloads/ReadWrite.cpp @@ -26,7 +26,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" #include "ReadWriteWorkload.h" diff --git a/fdbserver/workloads/RemoveServersSafely.cpp b/fdbserver/workloads/RemoveServersSafely.cpp index a37f04a5dd..478dbd8c3d 100644 --- a/fdbserver/workloads/RemoveServersSafely.cpp +++ b/fdbserver/workloads/RemoveServersSafely.cpp @@ -21,7 +21,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/FDBSimulatorProcessInfo.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" diff --git a/fdbserver/workloads/SkewedReadWrite.cpp b/fdbserver/workloads/SkewedReadWrite.cpp index a82aa4900d..33f2f5f3bf 100644 --- a/fdbserver/workloads/SkewedReadWrite.cpp +++ b/fdbserver/workloads/SkewedReadWrite.cpp @@ -25,7 +25,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" #include "ReadWriteWorkload.h" diff --git a/fdbserver/workloads/SnapTest.cpp b/fdbserver/workloads/SnapTest.cpp index a21e0bbc8a..461d48e12d 100644 --- a/fdbserver/workloads/SnapTest.cpp +++ b/fdbserver/workloads/SnapTest.cpp @@ -26,7 +26,7 @@ #include "fdbclient/SimpleIni.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "BulkSetup.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/workloads/TargetedKill.cpp b/fdbserver/workloads/TargetedKill.cpp index 78ae9427c4..761282d27b 100644 --- a/fdbserver/workloads/TargetedKill.cpp +++ b/fdbserver/workloads/TargetedKill.cpp @@ -24,7 +24,7 @@ #include "fdbrpc/simulator.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/SystemData.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/QuietDatabase.h" diff --git a/fdbserver/workloads/Throughput.cpp b/fdbserver/workloads/Throughput.cpp index 8f87a0a924..c7ecf38899 100644 --- a/fdbserver/workloads/Throughput.cpp +++ b/fdbserver/workloads/Throughput.cpp @@ -21,7 +21,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "flow/ActorCollection.h" #include "fdbrpc/Smoother.h" diff --git a/fdbserver/workloads/WorkerErrors.cpp b/fdbserver/workloads/WorkerErrors.cpp index 288574c3b0..e0ac5a5dbd 100644 --- a/fdbserver/workloads/WorkerErrors.cpp +++ b/fdbserver/workloads/WorkerErrors.cpp @@ -22,7 +22,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/ServerDBInfo.h" diff --git a/fdbserver/workloads/WriteBandwidth.cpp b/fdbserver/workloads/WriteBandwidth.cpp index 9045cebda5..c46d7067d2 100644 --- a/fdbserver/workloads/WriteBandwidth.cpp +++ b/fdbserver/workloads/WriteBandwidth.cpp @@ -23,7 +23,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" diff --git a/fdbserver/workloads/WriteTagThrottling.cpp b/fdbserver/workloads/WriteTagThrottling.cpp index 66fa6d5493..bb8a23f50a 100644 --- a/fdbserver/workloads/WriteTagThrottling.cpp +++ b/fdbserver/workloads/WriteTagThrottling.cpp @@ -21,7 +21,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/TagThrottle.h" From 318195abce4a0078ff17e8b1d593fcdf4c4aadbc Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 02:14:44 -0700 Subject: [PATCH 33/69] Fix WorkerInterface clang-tidy warnings --- .../include/fdbserver/core/WorkerInterface.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.h index 53a2814ac9..428ad32126 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.h @@ -78,7 +78,7 @@ struct WorkerInterface { NetworkAddressList addresses() const { return tLog.getEndpoint().addresses; } Optional grpcAddress() const { return clientInterface.grpcAddress; } - WorkerInterface() {} + WorkerInterface() = default; explicit(false) WorkerInterface(const LocalityData& locality) : locality(locality) {} void initEndpoints() { @@ -366,7 +366,7 @@ struct RecruitFromConfigurationRequest { int maxOldLogRouters; ReplyPromise reply; - RecruitFromConfigurationRequest() {} + RecruitFromConfigurationRequest() = default; explicit RecruitFromConfigurationRequest(DatabaseConfiguration const& configuration, bool recruitSeedServers, int maxOldLogRouters) @@ -399,7 +399,7 @@ struct RecruitRemoteFromConfigurationRequest { Optional dbgId; ReplyPromise reply; - RecruitRemoteFromConfigurationRequest() {} + RecruitRemoteFromConfigurationRequest() = default; RecruitRemoteFromConfigurationRequest(DatabaseConfiguration const& configuration, Optional const& dcId, int logRouterCount, @@ -545,7 +545,7 @@ struct TLogRejoinRequest { TLogInterface myInterface; ReplyPromise reply; - TLogRejoinRequest() {} + TLogRejoinRequest() = default; explicit TLogRejoinRequest(const TLogInterface& interf) : myInterface(interf) {} template void serialize(Ar& ar) { @@ -586,7 +586,7 @@ struct GetEncryptionAtRestModeRequest { UID tlogId; ReplyPromise reply; - GetEncryptionAtRestModeRequest() {} + GetEncryptionAtRestModeRequest() = default; explicit(false) GetEncryptionAtRestModeRequest(UID tId) : tlogId(tId) {} template @@ -841,7 +841,7 @@ struct InitializeDataDistributorRequest { UID reqId; ReplyPromise reply; - InitializeDataDistributorRequest() {} + InitializeDataDistributorRequest() = default; explicit InitializeDataDistributorRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -854,7 +854,7 @@ struct InitializeRatekeeperRequest { UID reqId; ReplyPromise reply; - InitializeRatekeeperRequest() {} + InitializeRatekeeperRequest() = default; explicit InitializeRatekeeperRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -867,7 +867,7 @@ struct InitializeConsistencyScanRequest { UID reqId; ReplyPromise reply; - InitializeConsistencyScanRequest() {} + InitializeConsistencyScanRequest() = default; explicit InitializeConsistencyScanRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -1039,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) {} From 3f6785a01ca0ce90759f2cab7b4da7871f8b562f Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 10:06:39 -0700 Subject: [PATCH 34/69] Restore shard tracker sources for cancelled data moves --- .../datadistributor/DDRelocationQueue.cpp | 18 ++++++ .../ShardsAffectedByTeamFailure.cpp | 59 +++++++++++++++++ .../ShardsAffectedByTeamFailure.h | 5 ++ .../ShardsAffectedByTeamFailureTests.cpp | 64 +++++++++++++++++++ 4 files changed, 146 insertions(+) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index a1430e5389..2cdf8f4870 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -2476,6 +2476,24 @@ Future dataDistributionRelocator(DDQueue* self, if (!signalledTransferComplete) dataTransferComplete.send(rd); + if (err.code() == error_code_data_move_dest_team_not_found && rd.isRestore()) { + std::vector destinationTeams = { ShardsAffectedByTeamFailure::Team( + rd.dataMove->primaryDest, true) }; + std::vector 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()); diff --git a/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp b/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp index 46958973d6..86318c5a2c 100644 --- a/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp +++ b/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp @@ -166,6 +166,65 @@ void ShardsAffectedByTeamFailure::moveShard(KeyRangeRef keys, std::vector check(); } +std::vector ShardsAffectedByTeamFailure::cancelMove(KeyRangeRef keys, + const std::vector& destinationTeams, + const std::vector& sourceTeams) { + std::vector 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 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 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) { diff --git a/fdbserver/datadistributor/ShardsAffectedByTeamFailure.h b/fdbserver/datadistributor/ShardsAffectedByTeamFailure.h index 84bc09ec95..599816f7f2 100644 --- a/fdbserver/datadistributor/ShardsAffectedByTeamFailure.h +++ b/fdbserver/datadistributor/ShardsAffectedByTeamFailure.h @@ -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 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 cancelMove(KeyRangeRef keys, + const std::vector& destinationTeams, + const std::vector& 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 diff --git a/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp b/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp index bbf0337446..d6d8e53bd7 100644 --- a/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp +++ b/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp @@ -128,3 +128,67 @@ TEST_CASE("/DataDistributor/ShardsAffectedByTeamFailure/DestinationSourceTransit 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{ leftRange, rightRange })); + ASSERT(shards.getTeamsForFirstShard(leftRange).first == std::vector{ source }); + ASSERT(shards.getTeamsForFirstShard(rightRange).first == std::vector{ 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{ leftRange })); + ASSERT(shards.getTeamsForFirstShard(leftRange).first == std::vector{ source }); + ASSERT(shards.getTeamsForFirstShard(rightRange).first == + std::vector{ 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{ leftRange, rightRange })); + ASSERT(shards.getTeamsForFirstShard(leftRange).first == + std::vector{ redirected }); + ASSERT(shards.getTeamsForFirstShard(rightRange).first == std::vector{ 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{ partialCancelRange })); + ASSERT(partialShards.getTeamsForFirstShard(partialCancelRange).first == + std::vector{ source }); + ASSERT(partialShards.getTeamsForFirstShard(KeyRangeRef("b"_sr, "c"_sr)).first == + std::vector{ destination }); + ASSERT_EQ(partialShards.getNumberOfShards(destination), 2); + ASSERT_EQ(partialShards.getNumberOfShards(source), 1); + + return Void(); +} From d2ef9dff80bad5fd838ff563d308a83ceed4fdb3 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 14:46:22 -0700 Subject: [PATCH 35/69] Ignore stale log-router pops after single-region recovery --- fdbserver/logsystem/LogSystem.cpp | 3 ++- fdbserver/logsystem/LogSystemRecoveryTests.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/fdbserver/logsystem/LogSystem.cpp b/fdbserver/logsystem/LogSystem.cpp index 7f55ad925f..3619908ba1 100644 --- a/fdbserver/logsystem/LogSystem.cpp +++ b/fdbserver/logsystem/LogSystem.cpp @@ -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; diff --git a/fdbserver/logsystem/LogSystemRecoveryTests.cpp b/fdbserver/logsystem/LogSystemRecoveryTests.cpp index 14bc552898..70baf43d28 100644 --- a/fdbserver/logsystem/LogSystemRecoveryTests.cpp +++ b/fdbserver/logsystem/LogSystemRecoveryTests.cpp @@ -58,6 +58,16 @@ std::tuple, bool> makeLogGroupResults( void forceLinkLogSystemRecoveryTests() {} +TEST_CASE("/LogSystem/GetPseudoPopTag/LogRouterWithoutMappedLocality") { + LocalityData locality; + auto logSystem = makeReference(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; From 20d4acaef5a3aefe59102e71fa87f7de379ee2e8 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:10:26 -0700 Subject: [PATCH 36/69] Remove unused findBestUniquePolicySet --- fdbrpc/ReplicationUtils.cpp | 91 ------------------------ fdbrpc/include/fdbrpc/ReplicationUtils.h | 13 ---- 2 files changed, 104 deletions(-) diff --git a/fdbrpc/ReplicationUtils.cpp b/fdbrpc/ReplicationUtils.cpp index 115858f959..38566e3c5e 100644 --- a/fdbrpc/ReplicationUtils.cpp +++ b/fdbrpc/ReplicationUtils.cpp @@ -265,97 +265,6 @@ bool findBestPolicySet(std::vector& bestResults, return bestFound; } -bool findBestUniquePolicySet(std::vector& bestResults, - Reference& localitySet, - Reference const& policy, - StringRef localityUniquenessKey, - unsigned int nMinItems, - unsigned int nSelectTests, - unsigned int nPolicyTests) { - bool bSucceeded = true; - Reference bestLocalitySet, testLocalitySet; - std::vector 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 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& offendingCombo, LocalityGroup const& localitySet, Reference const& policy, diff --git a/fdbrpc/include/fdbrpc/ReplicationUtils.h b/fdbrpc/include/fdbrpc/ReplicationUtils.h index 7f9d57b82d..5df76e87a0 100644 --- a/fdbrpc/include/fdbrpc/ReplicationUtils.h +++ b/fdbrpc/include/fdbrpc/ReplicationUtils.h @@ -49,19 +49,6 @@ extern bool findBestPolicySet(std::vector& 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& bestResults, - Reference& localitySet, - Reference 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 From a2b0f01f5eb527d5f414228899a0aee61e3f82e5 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:14:17 -0700 Subject: [PATCH 37/69] Fix ShardedRocksDB lifetime during storage rollback --- .../kvstore/KeyValueStoreShardedRocksDB.cpp | 39 +++++++++++++++++-- fdbserver/storageserver/storageserver.cpp | 20 +++++++++- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp index 80ed55e4eb..05ea93bd87 100644 --- a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp +++ b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp @@ -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 { std::vector> shards; - std::shared_ptr metadataShard; + PhysicalShard* metadataShard; ThreadReturnPromise done; CompactShardsAction(std::vector> 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 = "sharded-rocksdb-close-without-init"; + platform::eraseDirectoryRecursive(rocksDBTestDir); + + for (bool dispose : { false, true }) { + IKeyValueStore* kvStore = + new ShardedRocksDBKeyValueStore(rocksDBTestDir, deterministicRandom()->randomUniqueID()); + Future 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); diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index b7be1522e1..4433c04b7b 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -12624,7 +12624,15 @@ Future storageServer(IKeyValueStore* persistentData, } ssCore.cancel(); self.actors = ActorCollection(false); - co_await delay(0); + try { + co_await delay(0); + } catch (Error& cleanupError) { + // A rollback keeps the KVS open for its rebooter, which cannot reclaim it after cancellation. + if (cleanupError.code() == error_code_actor_cancelled && err.code() == error_code_please_reboot) { + persistentData->close(); + } + throw; + } throw err; } } @@ -12743,7 +12751,15 @@ Future storageServer(IKeyValueStore* persistentData, } ssCore.cancel(); self.actors = ActorCollection(false); - co_await delay(0); + try { + co_await delay(0); + } catch (Error& cleanupError) { + // A rollback keeps the KVS open for its rebooter, which cannot reclaim it after cancellation. + if (cleanupError.code() == error_code_actor_cancelled && err.code() == error_code_please_reboot) { + persistentData->close(); + } + throw; + } throw err; } From fa8891718c2854ce08b9747a6ae023f7f11b3b76 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:17:58 -0700 Subject: [PATCH 38/69] Use unit-test data directory for ShardedRocksDB close test --- fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp index 05ea93bd87..ca9d88fb41 100644 --- a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp +++ b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp @@ -3945,7 +3945,7 @@ TEST_CASE("noSim/ShardedRocksDB/Initialization") { } TEST_CASE("noSim/ShardedRocksDB/CloseWithoutInit") { - const std::string rocksDBTestDir = "sharded-rocksdb-close-without-init"; + const std::string rocksDBTestDir = joinPath(params.getDataDir(), "sharded-rocksdb-close-without-init"); platform::eraseDirectoryRecursive(rocksDBTestDir); for (bool dispose : { false, true }) { From e24ff6a620f3f2505af02eeb3f472fbab00bd405 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:19:07 -0700 Subject: [PATCH 39/69] Fix GcGenerations after region failover --- fdbserver/workloads/GcGenerations.cpp | 50 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/fdbserver/workloads/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index 1832603510..650ef5c8b8 100644 --- a/fdbserver/workloads/GcGenerations.cpp +++ b/fdbserver/workloads/GcGenerations.cpp @@ -45,6 +45,7 @@ struct GcGenerationsWorkload : TestWorkload { double testDuration; double startDelay; std::vector> cloggedPairs; + Optional> cloggedDcId; explicit GcGenerationsWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) { enabled = !clientId; // only do this on the "first" client @@ -85,6 +86,7 @@ struct GcGenerationsWorkload : TestWorkload { g_simulator->unclogPair(pair.first, pair.second); } cloggedPairs.clear(); + cloggedDcId.reset(); } Future clogRemoteDc(GcGenerationsWorkload* self, Database cx) { @@ -104,12 +106,20 @@ struct GcGenerationsWorkload : TestWorkload { return false; }; + auto& simPolicy = fdbSimulationPolicyState(); + Optional> inactiveDcId = simPolicy.remoteDcId; + // A region failover can make the configured remote DC the active primary. Always partition the inactive DC. + if (self->dbInfo->get().master.locality.dcId() == inactiveDcId) { + inactiveDcId = simPolicy.primaryDcId; + } + self->cloggedDcId = inactiveDcId; + std::vector ips; // all non-remote process IPs std::vector remoteIps; // all remote process IPs for (const auto& process : g_simulator->getAllProcesses()) { const auto& ip = process->address.ip; - if (process->locality.dcId().present() && - process->locality.dcId() == fdbSimulationPolicyState().remoteDcId && !isCoordinator(coordinators, ip)) { + if (process->locality.dcId().present() && process->locality.dcId() == inactiveDcId && + !isCoordinator(coordinators, ip)) { remoteIps.push_back(ip); } else { ips.push_back(ip); @@ -128,28 +138,28 @@ struct GcGenerationsWorkload : TestWorkload { } TraceEvent("PartitionRemoteDc") - .detail("RemoteDc", fdbSimulationPolicyState().remoteDcId) + .detail("RemoteDc", inactiveDcId) .detail("CloggedRemoteProcess", describe(remoteIps)); } - bool isMasterInRemoteDc(GcGenerationsWorkload* self) { + bool isMasterInCloggedDc(GcGenerationsWorkload* self) { auto masterAddr = self->dbInfo->get().master.address(); auto* masterProc = g_simulator->getProcessByAddress(masterAddr); return !masterProc || !masterProc->locality.dcId().present() || - masterProc->locality.dcId() == fdbSimulationPolicyState().remoteDcId; + masterProc->locality.dcId() == self->cloggedDcId; } - // Wait for the DB to reach ACCEPTING_COMMITS. If rebootRemoteDcMaster is true and - // the master is in the remote DC, reboot it to force the CC to elect a primary DC - // master. This is required when the remote DC is clogged (otherwise recovery can - // never complete), but must be disabled once the remote DC is unclogged — otherwise - // every CC re-election that lands in the remote DC triggers another reboot, producing + // Wait for the DB to reach ACCEPTING_COMMITS. If rebootCloggedDcMaster is true and + // the master is in the clogged DC, reboot it to force the CC to elect an active DC + // master. This is required when the inactive DC is clogged (otherwise recovery can + // never complete), but must be disabled once that DC is unclogged — otherwise + // every CC re-election that lands there triggers another reboot, producing // a tight loop that prevents recovery from ever reaching ACCEPTING_COMMITS. - Future dbAvailable(GcGenerationsWorkload* self, bool rebootRemoteDcMaster) { + Future dbAvailable(GcGenerationsWorkload* self, bool rebootCloggedDcMaster) { while (self->dbInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { co_await self->dbInfo->onChange(); - if (rebootRemoteDcMaster && self->dbInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS && - self->isMasterInRemoteDc(self)) { + if (rebootCloggedDcMaster && self->dbInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS && + self->isMasterInCloggedDc(self)) { auto masterAddr = self->dbInfo->get().master.address(); auto* masterProc = g_simulator->getProcessByAddress(masterAddr); TraceEvent("DbAvailableRebootRemoteMaster").detail("MasterAddr", masterAddr); @@ -174,12 +184,12 @@ struct GcGenerationsWorkload : TestWorkload { TraceEvent("WaitingForDbAvailable") .detail("Iteration", successfulReboots) .detail("RecoveryState", self->dbInfo->get().recoveryState); - co_await self->dbAvailable(self, /*rebootRemoteDcMaster=*/true); + co_await self->dbAvailable(self, /*rebootCloggedDcMaster=*/true); - // Only reboot the master if it's in the primary DC. If it's in the clogged - // remote DC, recovery will stall because the master can't communicate with - // primary DC processes. Loop back and try again. - if (self->isMasterInRemoteDc(self)) { + // Only reboot the master if it's in the active DC. If it's in the clogged + // DC, recovery will stall because the master can't communicate with active + // DC processes. Loop back and try again. + if (self->isMasterInCloggedDc(self)) { TraceEvent("RetryingRemoteDcMaster") .detail("Iteration", successfulReboots) .detail("MasterAddr", self->dbInfo->get().master.address()); @@ -244,13 +254,13 @@ struct GcGenerationsWorkload : TestWorkload { // Note: the remote DC is unclogged now, so any master (including remote DC) // can coordinate recovery. No need for the primary-DC-only guard here. while (self->dbInfo->get().logSystemConfig.oldTLogs.size() > 1) { - co_await self->dbAvailable(self, /*rebootRemoteDcMaster=*/false); + co_await self->dbAvailable(self, /*rebootCloggedDcMaster=*/false); auto masterAddr = self->dbInfo->get().master.address(); TraceEvent("RebootMasterForGC").detail("Master", masterAddr); g_simulator->rebootProcess(g_simulator->getProcessByAddress(masterAddr), ISimulator::KillType::Reboot); // Give this recovery cycle time to GC before retrying. co_await delay(60); - co_await self->dbAvailable(self, /*rebootRemoteDcMaster=*/false); + co_await self->dbAvailable(self, /*rebootCloggedDcMaster=*/false); TraceEvent("GcGenerationsWaitingForReduction") .detail("OldTLogs", self->dbInfo->get().logSystemConfig.oldTLogs.size()) .detail("RecoveryState", self->dbInfo->get().recoveryState); From 844f66c3bfbc8a42dcbf4aa451f22c465ef1eb5a Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:22:39 -0700 Subject: [PATCH 40/69] Avoid dropped log-router init replies in backup restart test --- tests/restarting/from_7.3.0/UpgradeAndBackupRestore-2.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/restarting/from_7.3.0/UpgradeAndBackupRestore-2.toml b/tests/restarting/from_7.3.0/UpgradeAndBackupRestore-2.toml index e71cd0eda7..34dedca9be 100644 --- a/tests/restarting/from_7.3.0/UpgradeAndBackupRestore-2.toml +++ b/tests/restarting/from_7.3.0/UpgradeAndBackupRestore-2.toml @@ -1,5 +1,8 @@ [[knobs]] dd_max_shards_on_large_teams = 0 +# This restart can retain many old log generations; dropping a log-router init +# reply can repeatedly restart recovery and exhaust the starting-config timeout. +cc_recovery_init_req_allow_drop_in_sim = false [configuration] storageEngineExcludeTypes=[3,5] From 625d3b4eb4fa2a0bbf925a99a006c00fe0d31d7f Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:31:31 -0700 Subject: [PATCH 41/69] Keep pre-7.4 snapshot restart tests single-region --- .../restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml b/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml index 7aca246998..8afe05eebe 100644 --- a/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml +++ b/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml @@ -3,6 +3,9 @@ storageEngineExcludeTypes=[3,4,5] logAntiQuorum=0 encryptModes=['disabled'] tenantModes=['disabled'] +# Snapshot restore only supports one region; keep the 7.3 step from creating remote or satellite TLogs. +generateFearless=false +datacenters=1 [[test]] testTitle="SnapCyclePre" From d501ece5899cf4e29e387d467744e009f2311616 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:35:02 -0700 Subject: [PATCH 42/69] Retry restored data moves after destination failure --- fdbserver/datadistributor/DDRelocationQueue.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 2cdf8f4870..9eb41571b1 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -145,8 +145,8 @@ static RelocateShard makeDestinationFailureRetry(RelocateData const& rd, UID ret return retry; } -static bool shouldRetryDestinationTeamFailure(bool doBulkLoading, RelocateData const& rd) { - return !doBulkLoading && !rd.isRestore(); +static bool shouldRetryDestinationTeamFailure(bool doBulkLoading, RelocateData const&) { + return !doBulkLoading; } static bool shouldYieldDestinationFailureRetry(RelocateData const& retry, RelocateData const& queued) { @@ -3365,7 +3365,8 @@ TEST_CASE("/DataDistribution/DDQueue/RetryDestinationTeamFailure") { ASSERT(!shouldYieldDestinationFailureRetry(retry, unrelated)); RelocateData restore = rd; restore.dataMove = std::make_shared(); - ASSERT(!shouldRetryDestinationTeamFailure(false, restore)); + ASSERT(shouldRetryDestinationTeamFailure(false, restore)); + ASSERT(!shouldRetryDestinationTeamFailure(true, restore)); return Void(); } From 15df0ae8fc90c2ab064e74c2e1c9c7722becbc09 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:49:29 -0700 Subject: [PATCH 43/69] Remove unused FoundationDB helpers --- design/AI-generated/FDB_NETWORK_PROTOCOL.md | 3 - fdbclient/FileBackupAgent.cpp | 81 -------------- fdbclient/NativeAPI.actor.cpp | 8 -- fdbclient/RestoreInterface.cpp | 56 ---------- fdbclient/RestoreInterface.h | 102 ------------------ fdbrpc/dsltest.actor.cpp | 19 ---- .../datadistributor/DDRelocationQueue.cpp | 5 - fdbserver/datadistributor/DDShardTracker.cpp | 38 ------- .../datadistributor/DataDistribution.cpp | 35 ------ fdbserver/fdbserver.cpp | 62 ----------- fdbserver/include/fdbserver/NetworkTest.h | 24 ----- fdbserver/kvstore/FDBExecHelper.cpp | 15 --- fdbserver/kvstore/VersionedBTree.actor.cpp | 4 - fdbserver/networktest.cpp | 79 -------------- fdbserver/storageserver/storageserver.cpp | 41 ------- fdbserver/workloads/pubsub.cpp | 3 - 16 files changed, 575 deletions(-) delete mode 100644 fdbclient/RestoreInterface.cpp delete mode 100644 fdbclient/RestoreInterface.h diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md index 31e0be5fcd..213dbae231 100644 --- a/design/AI-generated/FDB_NETWORK_PROTOCOL.md +++ b/design/AI-generated/FDB_NETWORK_PROTOCOL.md @@ -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 acknowledgeToken`, `uint16_t sequence`, `int index`}. - --- ## 15. Client Worker / Debug / Process Protocols diff --git a/fdbclient/FileBackupAgent.cpp b/fdbclient/FileBackupAgent.cpp index 858a1608e8..f6bbac26ad 100644 --- a/fdbclient/FileBackupAgent.cpp +++ b/fdbclient/FileBackupAgent.cpp @@ -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 verifyBulkDumpDatasetCompleteness(Reference bc, s Optional fileBackupAgentProxy = Optional(); -#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) { if (version.present()) return std::to_string(version.get()); @@ -8430,76 +8422,3 @@ Future FileBackupAgent::waitBackup(Database cx, Future FileBackupAgent::changePause(Database db, bool pause) { return FileBackupAgentImpl::changePause(this, db, pause); } - -// Fast Restore addPrefix test helper functions -static std::pair insideValidRange(KeyValueRef kv, - Standalone> restoreRanges, - Standalone> 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 writeKVs(Database cx, Standalone> kvs, int begin, int end) { - co_await runRYWTransaction(cx, [=](Reference tr) -> Future { - 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); -} diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 46706d74b5..4e21f15d5f 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -300,14 +300,6 @@ int64_t extractIntOption(Optional 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 value) { int defaultFor = FDBDatabaseOptions::optionInfo.getMustExist(option).defaultFor; if (defaultFor >= 0) { diff --git a/fdbclient/RestoreInterface.cpp b/fdbclient/RestoreInterface.cpp deleted file mode 100644 index 96d81225bb..0000000000 --- a/fdbclient/RestoreInterface.cpp +++ /dev/null @@ -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(); -} diff --git a/fdbclient/RestoreInterface.h b/fdbclient/RestoreInterface.h deleted file mode 100644 index bd0c6ff2f3..0000000000 --- a/fdbclient/RestoreInterface.h +++ /dev/null @@ -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 - void serialize(Ar& ar) { - serializer(ar, id, isDuplicated); - } -}; - -struct RestoreRequest { - constexpr static FileIdentifier file_identifier = 16035338; - - int index; - Key tagName; - Key url; - Optional 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 reply; - - RestoreRequest() = default; - explicit RestoreRequest(const int index, - const Key& tagName, - const Key& url, - const Optional& 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 - 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&); diff --git a/fdbrpc/dsltest.actor.cpp b/fdbrpc/dsltest.actor.cpp index 7ebf804f2c..a88195fe42 100644 --- a/fdbrpc/dsltest.actor.cpp +++ b/fdbrpc/dsltest.actor.cpp @@ -1103,25 +1103,6 @@ ACTOR [[flow_allow_discard]] Future 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 c; diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index cfaed3b488..cad3bb533a 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -49,11 +49,6 @@ using ITeamRef = Reference; using SrcDestTeamPair = std::pair; -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; diff --git a/fdbserver/datadistributor/DDShardTracker.cpp b/fdbserver/datadistributor/DDShardTracker.cpp index 284f314a55..e74a513c00 100644 --- a/fdbserver/datadistributor/DDShardTracker.cpp +++ b/fdbserver/datadistributor/DDShardTracker.cpp @@ -415,11 +415,6 @@ std::string describeSplit(KeyRange keys, Standalone>& splitKey return s; } -void traceSplit(KeyRange keys, Standalone>& splitKeys) { - auto s = describeSplit(keys, splitKeys); - TraceEvent(SevInfo, "ExecutingShardSplit").detail("AtKeys", s); -} - void executeShardSplit(DataDistributionTracker* self, KeyRange keys, Standalone> splitKeys, @@ -465,39 +460,6 @@ void executeShardSplit(DataDistributionTracker* self, self->actors.add(changeSizes(self, keys, shardSize->get().get().metrics.bytes, "ShardSplit")); } -struct RangeToSplit { - RangeMap, ShardTrackedData, KeyRangeRef>::iterator shard; - Standalone> faultLines; - - RangeToSplit(RangeMap, ShardTrackedData, KeyRangeRef>::iterator shard, - Standalone> faultLines) - : shard(shard), faultLines(faultLines) {} -}; - -bool faultLinesMatch(std::vector& ranges, std::vector>& 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 shardSplitter(DataDistributionTracker* self, KeyRange keys, Reference>> shardSize, diff --git a/fdbserver/datadistributor/DataDistribution.cpp b/fdbserver/datadistributor/DataDistribution.cpp index a6455415c9..d657a1a439 100644 --- a/fdbserver/datadistributor/DataDistribution.cpp +++ b/fdbserver/datadistributor/DataDistribution.cpp @@ -420,41 +420,6 @@ Future monitorBackupPartitionRequired(Database cx, KeyRangeMap 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 self, diff --git a/fdbserver/fdbserver.cpp b/fdbserver/fdbserver.cpp index 70a9689740..ae99a89bc3 100644 --- a/fdbserver/fdbserver.cpp +++ b/fdbserver/fdbserver.cpp @@ -410,68 +410,6 @@ Future 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 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 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(); diff --git a/fdbserver/include/fdbserver/NetworkTest.h b/fdbserver/include/fdbserver/NetworkTest.h index 05ec966d69..ae51501c22 100644 --- a/fdbserver/include/fdbserver/NetworkTest.h +++ b/fdbserver/include/fdbserver/NetworkTest.h @@ -28,7 +28,6 @@ struct NetworkTestInterface { RequestStream test; - RequestStream 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 - void serialize(Ar& ar) { - serializer(ar, ReplyPromiseStreamReply::acknowledgeToken, ReplyPromiseStreamReply::sequence, index); - } -}; - -struct NetworkTestStreamingRequest { - constexpr static FileIdentifier file_identifier = 2794452; - ReplyPromiseStream reply; - template - void serialize(Ar& ar) { - serializer(ar, reply); - } -}; - Future networkTestServer(); Future networkTestClient(std::string const& testServers); diff --git a/fdbserver/kvstore/FDBExecHelper.cpp b/fdbserver/kvstore/FDBExecHelper.cpp index dd4b00ab83..c24b3ae6f3 100644 --- a/fdbserver/kvstore/FDBExecHelper.cpp +++ b/fdbserver/kvstore/FDBExecHelper.cpp @@ -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 destroyChildProcess(Uncancellable, - Future 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 spawnProcess(std::string binPath, std::vector paramList, diff --git a/fdbserver/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..0c194b5e87 100644 --- a/fdbserver/kvstore/VersionedBTree.actor.cpp +++ b/fdbserver/kvstore/VersionedBTree.actor.cpp @@ -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; diff --git a/fdbserver/networktest.cpp b/fdbserver/networktest.cpp index 5cc52893a5..d8f6473cbc 100644 --- a/fdbserver/networktest.cpp +++ b/fdbserver/networktest.cpp @@ -108,60 +108,6 @@ Future networkTestServer() { co_await server.run(); } -class NetworkTestStreamingServer { -public: - NetworkTestStreamingServer() : interf(g_network) {} - - Future run() { co_await race(requests(), logging()); } - -private: - Future requests() { - while (true) { - try { - NetworkTestStreamingRequest req = co_await interf.testStream.getFuture(); - LatencyStats::sample sample = latency.tick(); - for (int i = 0; i < 100; ++i) { - co_await req.reply.onReady(); - req.reply.send(NetworkTestStreamingReply{ i }); - } - req.reply.sendError(end_of_stream()); - latency.tock(sample); - sent++; - } catch (Error& e) { - if (e.code() != error_code_operation_obsolete) { - throw e; - } - } - } - } - - Future logging() { - double lastTime = now(); - - while (true) { - co_await delay(1.0); - auto spd = sent / (now() - lastTime); - if (FLOW_KNOBS->NETWORK_TEST_SCRIPT_MODE) { - fprintf(stderr, "%f\t%.3f\t%.3f\n", spd, latency.mean() * 1e6, latency.stddev() * 1e6); - } else { - fprintf(stderr, "responses per second: %f (%f us)\n", spd, latency.mean() * 1e6); - } - latency.reset(); - lastTime = now(); - sent = 0; - } - } - - NetworkTestInterface interf; - int sent = 0; - LatencyStats latency; -}; - -Future networkTestStreamingServer() { - NetworkTestStreamingServer server; - co_await server.run(); -} - static bool moreRequestsPending(int count) { if (count == -1) { return false; @@ -193,31 +139,6 @@ Future testClient(std::vector interfs, int* sent, in } } -Future testClientStream(std::vector interfs, - int* sent, - int* completed, - LatencyStats* latency) { - while (moreRequestsPending(*sent)) { - (*sent)++; - LatencyStats::sample sample = latency->tick(); - ReplyPromiseStream stream = - interfs[deterministicRandom()->randomInt(0, interfs.size())].testStream.getReplyStream( - NetworkTestStreamingRequest{}); - int j = 0; - try { - while (true) { - NetworkTestStreamingReply rep = co_await stream.getFuture(); - ASSERT(rep.index == j++); - } - } catch (Error& e) { - ASSERT(e.code() == error_code_end_of_stream || e.code() == error_code_connection_failed || - e.code() == error_code_request_maybe_delivered); - } - latency->tock(sample); - (*completed)++; - } -} - Future logger(int* sent, int* completed, LatencyStats* latency) { double lastTime = now(); int logged = 0; diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index b7be1522e1..7731096f91 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -1985,16 +1985,6 @@ Future waitForVersionActor(StorageServer* data, Version version, SpanCo } } -// If the latest commit version that mutated the shard(s) being served by the specified storage -// server is below the client specified read version then do a read at the latest commit version -// of the storage server. -Version getRealReadVersion(VersionVector& ssLatestCommitVersions, Tag& tag, Version specifiedReadVersion) { - Version realReadVersion = - ssLatestCommitVersions.hasVersion(tag) ? ssLatestCommitVersions.getVersion(tag) : specifiedReadVersion; - ASSERT(realReadVersion <= specifiedReadVersion); - return realReadVersion; -} - // Find the latest commit version of the given tag. Version getLatestCommitVersion(VersionVector& ssLatestCommitVersions, Tag& tag) { Version commitVersion = @@ -4267,23 +4257,6 @@ Future auditStorageServerShardQ(StorageServer* data, AuditStorageRequest r * */ -// Helper: Issue a GetKeyValues request for a given range and return the future -static Future> issueGetKeyValuesRequest(StorageServer* data, - KeyRange range, - Version version, - int limit, - int limitBytes) { - GetKeyValuesRequest req; - req.begin = firstGreaterOrEqual(range.begin); - req.end = firstGreaterOrEqual(range.end); - req.limit = limit; - req.limitBytes = limitBytes; - req.version = version; - req.tags = TagSet(); - data->actors.add(getKeyValuesQ(data, req)); - return errorOr(req.reply.getFuture()); -} - // Helper: Read both source and restored data for a given range // // Restored data is stored at validateRestoreLogKeys (\xff\x02/rlog/) in system key space. @@ -6250,20 +6223,6 @@ bool changeDurableVersion(StorageServer* data, Version desiredDurableVersion) { return nextDurableVersion == desiredDurableVersion; } -Optional clipMutation(MutationRef const& m, KeyRangeRef range) { - if (isSingleKeyMutation((MutationRef::Type)m.type)) { - if (range.contains(m.param1)) - return m; - } else if (m.type == MutationRef::ClearRange) { - KeyRangeRef i = range & KeyRangeRef(m.param1, m.param2); - if (!i.empty()) - return MutationRef((MutationRef::Type)m.type, i.begin, i.end); - } else { - ASSERT(false); - } - return Optional(); -} - bool convertAtomicOp(MutationRef& m, StorageServer::VersionedData const& data, UpdateEagerReadInfo* eager, Arena& ar) { // After this function call, m should be copied into an arena immediately (before modifying data, shards, or eager) if (m.type != MutationRef::ClearRange && m.type != MutationRef::SetValue) { diff --git a/fdbserver/workloads/pubsub.cpp b/fdbserver/workloads/pubsub.cpp index a21da7382f..b52cd4407e 100644 --- a/fdbserver/workloads/pubsub.cpp +++ b/fdbserver/workloads/pubsub.cpp @@ -52,9 +52,6 @@ Key keyForInboxCacheByIDPrefix(uint64_t inbox) { Key keyForInboxCacheByID(uint64_t inbox, uint64_t messageId) { return StringRef(format("i/%016llx/cid/%016llx", inbox, messageId)); } -Key keyForInboxCacheByFeedPrefix(uint64_t inbox) { - return StringRef(format("i/%016llx/cf/", inbox)); -} Key keyForInboxCacheByFeed(uint64_t inbox, uint64_t feed) { return StringRef(format("i/%016llx/cf/%016llx", inbox, feed)); } From 83b9327babb28126547ca6a0380f91e7a378c38d Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:52:34 -0700 Subject: [PATCH 44/69] Initialize recovered TLog queue popped versions --- fdbserver/tlog/TLogServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 3a8695ed48..3ba9f07d1f 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -3871,6 +3871,7 @@ Future restorePersistentState(TLogData* self, Version ver = BinaryReader::fromStringRef(fVers.get()[idx].value, Unversioned()); logData->persistentDataVersion = ver; logData->persistentDataDurableVersion = ver; + logData->queuePoppedVersion = ver; logData->version.set(ver); logData->recoveryCount = BinaryReader::fromStringRef(fRecoverCounts.get()[idx].value, Unversioned()); @@ -4154,6 +4155,7 @@ Future tLogStart(TLogData* self, InitializeTLogRequest req, LocalityData l logData->persistentDataVersion = logData->unrecoveredBefore - 1; logData->persistentDataDurableVersion = logData->unrecoveredBefore - 1; logData->queueCommittedVersion.set(logData->unrecoveredBefore - 1); + logData->queuePoppedVersion = logData->unrecoveredBefore - 1; logData->version.set(logData->unrecoveredBefore - 1); logData->unpoppedRecoveredTagCount = req.allTags.size(); From dccf3d80e12d8290cbfdc3200ca292d4a650e36e Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 20:51:26 -0700 Subject: [PATCH 45/69] Make Native CDC proxy halts failure-aware --- fdbserver/workloads/NativeCdcEndToEnd.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fdbserver/workloads/NativeCdcEndToEnd.cpp b/fdbserver/workloads/NativeCdcEndToEnd.cpp index ca644436ef..bf080dce26 100644 --- a/fdbserver/workloads/NativeCdcEndToEnd.cpp +++ b/fdbserver/workloads/NativeCdcEndToEnd.cpp @@ -453,10 +453,10 @@ class NativeCdcEndToEndWorkload : public TestWorkload { ASSERT(std::find(originalProxies.begin(), originalProxies.end(), original) != originalProxies.end()); Future publications = waitForIndependentProxyPublications(cx, originalProxies); - std::vector> halts; + std::vector>> halts; halts.reserve(originalProxies.size()); for (const auto& proxy : originalProxies) { - halts.push_back(proxy.haltForTesting.getReply(HaltCDCProxyRequest())); + halts.push_back(proxy.haltForTesting.tryGetReply(HaltCDCProxyRequest())); } co_await timeoutError(waitForAll(halts), operationTimeout); co_await timeoutError(publications, operationTimeout); From f5bdc4a54f692c41c634041fecd263676f71d495 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 21:25:22 -0700 Subject: [PATCH 46/69] Fix GetMappedRange selector pagination assertions --- fdbserver/workloads/GetMappedRange.cpp | 72 +++++++++++++++++--------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/fdbserver/workloads/GetMappedRange.cpp b/fdbserver/workloads/GetMappedRange.cpp index 3f2c56e772..f76e542474 100644 --- a/fdbserver/workloads/GetMappedRange.cpp +++ b/fdbserver/workloads/GetMappedRange.cpp @@ -565,31 +565,20 @@ struct GetMappedRangeWorkload : ApiWorkload { Future checkMappedSelectorBoundaries(Database cx, Key mapper, GetMappedRangeWorkload* self) { Key begin = indexEntryKey(10); Key end = indexEntryKey(20); - MappedRangeResult greaterThan = - co_await scanMappedRangeWithLimits(cx, - KeySelector(firstGreaterThan(begin), begin.arena()), - KeySelector(firstGreaterThan(end), end.arena()), - mapper, - /*limit=*/100, - /*byteLimit=*/100000, - /*expectedBeginId=*/11, - self, - /*allMissing=*/false); - ASSERT_EQ(greaterThan.size(), 10); - ASSERT(!greaterThan.more); - - MappedRangeResult offsets = - co_await scanMappedRangeWithLimits(cx, - KeySelector(firstGreaterOrEqual(begin) + 2, begin.arena()), - KeySelector(firstGreaterOrEqual(end) - 2, end.arena()), - mapper, - /*limit=*/100, - /*byteLimit=*/100000, - /*expectedBeginId=*/12, - self, - /*allMissing=*/false); - ASSERT_EQ(offsets.size(), 6); - ASSERT(!offsets.more); + co_await checkMappedSelectorRange(cx, + KeySelector(firstGreaterThan(begin), begin.arena()), + KeySelector(firstGreaterThan(end), end.arena()), + mapper, + /*expectedBeginId=*/11, + /*expectedEndId=*/21, + self); + co_await checkMappedSelectorRange(cx, + KeySelector(firstGreaterOrEqual(begin) + 2, begin.arena()), + KeySelector(firstGreaterOrEqual(end) - 2, end.arena()), + mapper, + /*expectedBeginId=*/12, + /*expectedEndId=*/18, + self); co_await checkEmptyMappedRangeDoesNotConflict(cx, KeySelector(firstGreaterOrEqual(begin), begin.arena()), @@ -621,6 +610,39 @@ struct GetMappedRangeWorkload : ApiWorkload { co_return; } + Future checkMappedSelectorRange(Database cx, + KeySelector begin, + KeySelector end, + Key mapper, + int expectedBeginId, + int expectedEndId, + GetMappedRangeWorkload* self) { + int expectedId = expectedBeginId; + while (true) { + MappedRangeResult result = co_await scanMappedRangeWithLimits(cx, + begin, + end, + mapper, + /*limit=*/100, + /*byteLimit=*/100000, + expectedId, + self, + /*allMissing=*/false); + expectedId += result.size(); + ASSERT_LE(expectedId, expectedEndId); + if (!result.more) { + break; + } + if (result.readThrough.present()) { + begin = KeySelector(firstGreaterOrEqual(result.readThrough.get()), result.arena()); + } else { + ASSERT(!result.empty()); + begin = KeySelector(firstGreaterThan(result.back().key), result.arena()); + } + } + ASSERT_EQ(expectedId, expectedEndId); + } + Future _start(Database cx, GetMappedRangeWorkload* self) { TraceEvent("GetMappedRangeWorkloadConfig").detail("BadMapper", self->BAD_MAPPER); From 3019888dc380e89eebab5fa97cb579489bc9f9f2 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 21:26:37 -0700 Subject: [PATCH 47/69] Preserve restored TLog reference-spill pop boundaries --- fdbserver/tlog/TLogServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 3ba9f07d1f..61bfc9b76f 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -3904,6 +3904,10 @@ Future restorePersistentState(TLogData* self, logData->createTagData( tag, popped, NothingPersistent::False, PoppedRecently::False, UnpoppedRecovered::False); logData->getTagData(tag)->persistentPopped = popped; + // Reference-spilled data can still pin disk queue entries before the restored durable version. + if (logData->shouldSpillByReference(tag)) { + logData->queuePoppedVersion = std::min(logData->queuePoppedVersion, popped); + } } } } From 006dde8023bcd750defa4be570a60908f6301f80 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 23:31:37 -0700 Subject: [PATCH 48/69] Fix GcGenerations clogged-master retry loop --- fdbserver/workloads/GcGenerations.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fdbserver/workloads/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index 650ef5c8b8..ec06fe9ce5 100644 --- a/fdbserver/workloads/GcGenerations.cpp +++ b/fdbserver/workloads/GcGenerations.cpp @@ -188,11 +188,16 @@ struct GcGenerationsWorkload : TestWorkload { // Only reboot the master if it's in the active DC. If it's in the clogged // DC, recovery will stall because the master can't communicate with active - // DC processes. Loop back and try again. + // DC processes. Force a new master election before retrying. if (self->isMasterInCloggedDc(self)) { + auto masterAddr = self->dbInfo->get().master.address(); + auto* masterProc = g_simulator->getProcessByAddress(masterAddr); TraceEvent("RetryingRemoteDcMaster") .detail("Iteration", successfulReboots) - .detail("MasterAddr", self->dbInfo->get().master.address()); + .detail("MasterAddr", masterAddr); + if (masterProc) { + g_simulator->rebootProcess(masterProc, ISimulator::KillType::Reboot); + } continue; } From 849b26f4496b1fe2fa4c312a388d525d85c24011 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 00:12:32 -0700 Subject: [PATCH 49/69] Fix GcGenerations timeout and retry coverage --- fdbserver/workloads/GcGenerations.cpp | 19 ++++++++++++++++--- tests/slow/GcGenerations.toml | 1 + 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/fdbserver/workloads/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index ec06fe9ce5..3003f99d2c 100644 --- a/fdbserver/workloads/GcGenerations.cpp +++ b/fdbserver/workloads/GcGenerations.cpp @@ -32,6 +32,7 @@ #include "fdbrpc/simulator.h" #include "flow/CodeProbe.h" #include "flow/NetworkAddress.h" +#include "flow/ScopeExit.h" #include "flow/Error.h" #include "flow/Trace.h" #include "flow/flow.h" @@ -44,6 +45,8 @@ struct GcGenerationsWorkload : TestWorkload { bool enabled; double testDuration; double startDelay; + bool completed = false; + bool forceCloggedDcMasterRetry; std::vector> cloggedPairs; Optional> cloggedDcId; @@ -51,6 +54,7 @@ struct GcGenerationsWorkload : TestWorkload { enabled = !clientId; // only do this on the "first" client testDuration = getOption(options, "testDuration"_sr, 1000.0); startDelay = getOption(options, "startDelay"_sr, 30.0); + forceCloggedDcMasterRetry = getOption(options, "forceCloggedDcMasterRetry"_sr, false); } void disableFailureInjectionWorkloads(std::set& out) const override { @@ -65,7 +69,7 @@ struct GcGenerationsWorkload : TestWorkload { else return Void(); } - Future check(Database const& cx) override { return true; } + Future check(Database const& cx) override { return !g_network->isSimulated() || !enabled || completed; } void getMetrics(std::vector& m) override {} // Ensure simulator state is cleaned up even if the workload is cancelled by timeout. @@ -189,12 +193,15 @@ struct GcGenerationsWorkload : TestWorkload { // Only reboot the master if it's in the active DC. If it's in the clogged // DC, recovery will stall because the master can't communicate with active // DC processes. Force a new master election before retrying. - if (self->isMasterInCloggedDc(self)) { + const bool forcedRetry = self->forceCloggedDcMasterRetry; + self->forceCloggedDcMasterRetry = false; + if (forcedRetry || self->isMasterInCloggedDc(self)) { auto masterAddr = self->dbInfo->get().master.address(); auto* masterProc = g_simulator->getProcessByAddress(masterAddr); TraceEvent("RetryingRemoteDcMaster") .detail("Iteration", successfulReboots) - .detail("MasterAddr", masterAddr); + .detail("MasterAddr", masterAddr) + .detail("Forced", forcedRetry); if (masterProc) { g_simulator->rebootProcess(masterProc, ISimulator::KillType::Reboot); } @@ -238,6 +245,11 @@ struct GcGenerationsWorkload : TestWorkload { TraceEvent("GcGenerations").detail("StartTime", startTime).detail("EndTime", workloadEnd); // Block TLog recovery while creating generations to test generation accumulation during recovery + ScopeExit cleanup([self]() { + self->unclogAll(); + disableConnectionFailures("GcGenerations"); + fdbSimulationPolicyState().disableTLogRecoveryFinish = false; + }); fdbSimulationPolicyState().disableTLogRecoveryFinish = true; co_await self->generateMultipleTxnGenerations(self, cx); @@ -276,6 +288,7 @@ struct GcGenerationsWorkload : TestWorkload { co_await self->dbInfo->onChange(); } + self->completed = true; TraceEvent("GcGenerationsWorkloadFinish").log(); } }; diff --git a/tests/slow/GcGenerations.toml b/tests/slow/GcGenerations.toml index 96639e855c..cfe06f8c4c 100644 --- a/tests/slow/GcGenerations.toml +++ b/tests/slow/GcGenerations.toml @@ -31,3 +31,4 @@ testTitle = 'GcGenerations' [[test.workload]] testName = 'GcGenerations' testDuration = 1000.0 + forceCloggedDcMasterRetry = true From 1cb271523e01148c9ef640a87650e6fabf184119 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 00:47:20 -0700 Subject: [PATCH 50/69] Simplify Joshua coverage regression tests --- contrib/Joshua/tests/correctnessTest_test.sh | 30 +---- .../test_harness/test_coverage.py | 107 ++++++++++++++++ .../test_harness/test_fdb_coverage.py | 115 ------------------ .../TestHarness2/test_harness/test_results.py | 65 ---------- 4 files changed, 113 insertions(+), 204 deletions(-) create mode 100644 contrib/TestHarness2/test_harness/test_coverage.py delete mode 100644 contrib/TestHarness2/test_harness/test_fdb_coverage.py delete mode 100644 contrib/TestHarness2/test_harness/test_results.py diff --git a/contrib/Joshua/tests/correctnessTest_test.sh b/contrib/Joshua/tests/correctnessTest_test.sh index 73f6500c77..efeb6a3922 100755 --- a/contrib/Joshua/tests/correctnessTest_test.sh +++ b/contrib/Joshua/tests/correctnessTest_test.sh @@ -17,11 +17,7 @@ case "${FAKE_HARNESS_MODE}" in no_output) exit 0 ;; - fail) - echo '' - exit 0 - ;; - pass|tee_failure) + pass) echo '' exit 0 ;; @@ -29,16 +25,6 @@ esac FAKE_PYTHON chmod +x "${test_root}/bin/python3" -cat > "${test_root}/bin/tee" <<'FAKE_TEE' -#!/usr/bin/env bash - -/usr/bin/tee "$@" -if [ "${FAKE_HARNESS_MODE}" = tee_failure ]; then - exit 45 -fi -FAKE_TEE -chmod +x "${test_root}/bin/tee" - script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd) wrapper="${script_dir}/correctnessTest.sh" @@ -46,12 +32,10 @@ run_case() { local mode=$1 local expected_exit=$2 local expected_ok=$3 - local expected_preserved=$4 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 stderr_file="${output_dir}/stderr.log" local status mkdir -p "${output_dir}" @@ -62,13 +46,13 @@ run_case() { JOSHUA_ENSEMBLE_ID="${ensemble_id}" \ TH_OUTPUT_DIR="${output_dir}" \ TH_ARCHIVE_LOGS_ON_FAILURE=true \ - bash "${wrapper}" > "${stdout_file}" 2> "${stderr_file}" + 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_preserved}" = true ]; then + 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 @@ -76,11 +60,9 @@ run_case() { fi } -run_case pass_then_crash 23 1 true -run_case tee_failure 45 1 true -run_case no_output 1 0 true -run_case fail 1 0 true -run_case pass 0 1 false +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 diff --git a/contrib/TestHarness2/test_harness/test_coverage.py b/contrib/TestHarness2/test_harness/test_coverage.py new file mode 100644 index 0000000000..2b6eb44a57 --- /dev/null +++ b/contrib/TestHarness2/test_harness/test_coverage.py @@ -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(" Date: Sat, 18 Jul 2026 00:48:49 -0700 Subject: [PATCH 51/69] Fix Redwood commit cancellation lifetime crash --- fdbrpc/FlowTests.actor.cpp | 12 ++++++++++++ fdbrpc/include/fdbrpc/AsyncFileNonDurable.h | 2 +- flow/CoroTests.cpp | 6 ++++++ flow/include/flow/CoroUtils.h | 12 +++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/fdbrpc/FlowTests.actor.cpp b/fdbrpc/FlowTests.actor.cpp index bb5565fde6..a0508e031f 100644 --- a/fdbrpc/FlowTests.actor.cpp +++ b/fdbrpc/FlowTests.actor.cpp @@ -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 input; + Future 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 noteCancel(int* cancelled) { *cancelled = 0; try { diff --git a/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h index 41c066a047..d31627ade3 100644 --- a/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h +++ b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h @@ -39,7 +39,7 @@ extern Future waitShutdownSignal(); template Future sendErrorOnShutdown(Future 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 { diff --git a/flow/CoroTests.cpp b/flow/CoroTests.cpp index 06c6b3404d..b3bde66e88 100644 --- a/flow/CoroTests.cpp +++ b/flow/CoroTests.cpp @@ -2878,6 +2878,8 @@ TEST_CASE("/flow/coro/raceSuccess") { auto result = co_await raced; ASSERT_EQ(result.index(), 1); ASSERT_EQ(std::get<1>(result), "winner"); + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); co_return; } @@ -2903,6 +2905,8 @@ TEST_CASE("/flow/coro/raceError") { } catch (Error const& e) { ASSERT_EQ(e.code(), error_code_io_error); } + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); co_return; } @@ -2914,6 +2918,8 @@ TEST_CASE("/flow/coro/raceCancel") { ASSERT(raced.isReady()); ASSERT(raced.isError()); ASSERT_EQ(raced.getError().code(), error_code_actor_cancelled); + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); intPromise.send(1); stringPromise.send("late"); ASSERT_EQ(raced.getError().code(), error_code_actor_cancelled); diff --git a/flow/include/flow/CoroUtils.h b/flow/include/flow/CoroUtils.h index 904e299155..8014899bcb 100644 --- a/flow/include/flow/CoroUtils.h +++ b/flow/include/flow/CoroUtils.h @@ -348,14 +348,21 @@ struct RaceImplActor final : Actor, template void finish(T&& value) { + Result result(std::in_place_index, std::forward(value)); this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); - this->SAV::sendAndDelPromiseRef(Result(std::in_place_index, std::forward(value))); + { + auto futuresToRelease = std::move(futures); + } + this->SAV::sendAndDelPromiseRef(std::move(result)); } void fail(Error e) { this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(e); } @@ -364,6 +371,9 @@ struct RaceImplActor final : Actor, this->actor_wait_state = ACTOR_WAIT_STATE_CANCELLED; if (actorWaitStateIsWaiting(waitState)) { RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(actor_cancelled()); } } From 7505451df0111bd9beeacd35c9fe3b7840131d5a Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 02:35:52 -0700 Subject: [PATCH 52/69] Harden pre-7.4 snapshot restart tests --- .../from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml | 1 + .../from_7.3.0_until_7.4.0/SnapTestAttrition-1.toml | 6 ++++++ .../from_7.3.0_until_7.4.0/SnapTestRestart-1.toml | 6 ++++++ .../from_7.3.0_until_7.4.0/SnapTestSimpleRestart-1.toml | 7 +++++++ 4 files changed, 20 insertions(+) diff --git a/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml b/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml index 8afe05eebe..bfebb6b219 100644 --- a/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml +++ b/tests/restarting/from_7.3.0_until_7.4.0/SnapCycleRestart-1.toml @@ -10,6 +10,7 @@ datacenters=1 [[test]] testTitle="SnapCyclePre" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="Cycle" diff --git a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestAttrition-1.toml b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestAttrition-1.toml index 504be0a46a..90184499f5 100644 --- a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestAttrition-1.toml +++ b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestAttrition-1.toml @@ -3,10 +3,14 @@ storageEngineExcludeTypes=[3,4,5] logAntiQuorum=0 encryptModes=['disabled'] tenantModes=['disabled'] +# Snapshot restore only supports one region; keep the 7.3 step from creating remote or satellite TLogs. +generateFearless=false +datacenters=1 [[test]] testTitle="SnapTestPre" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" @@ -17,6 +21,7 @@ clearAfterTest=false [[test]] testTitle="SnapTestTakeSnap" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="ReadWrite" @@ -44,6 +49,7 @@ clearAfterTest=false [[test]] testTitle="SnapTestPost" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" diff --git a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestRestart-1.toml b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestRestart-1.toml index bc02c79b6f..a9974053ae 100644 --- a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestRestart-1.toml +++ b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestRestart-1.toml @@ -3,10 +3,14 @@ storageEngineExcludeTypes=[3,4,5] logAntiQuorum=0 encryptModes=['disabled'] tenantModes=['disabled'] +# Snapshot restore only supports one region; keep the 7.3 step from creating remote or satellite TLogs. +generateFearless=false +datacenters=1 [[test]] testTitle="SnapTestPre" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" @@ -17,6 +21,7 @@ clearAfterTest=false [[test]] testTitle="SnapTestTakeSnap" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="ReadWrite" @@ -40,6 +45,7 @@ clearAfterTest=false [[test]] testTitle="SnapTestPost" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" diff --git a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestSimpleRestart-1.toml b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestSimpleRestart-1.toml index 3c6e6c3665..f2a7b82c48 100644 --- a/tests/restarting/from_7.3.0_until_7.4.0/SnapTestSimpleRestart-1.toml +++ b/tests/restarting/from_7.3.0_until_7.4.0/SnapTestSimpleRestart-1.toml @@ -3,10 +3,14 @@ storageEngineExcludeTypes=[3,4,5] logAntiQuorum=0 encryptModes=['disabled'] tenantModes=['disabled'] +# Snapshot restore only supports one region; keep the 7.3 step from creating remote or satellite TLogs. +generateFearless=false +datacenters=1 [[test]] testTitle="SnapSimplePre" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" @@ -17,6 +21,7 @@ clearAfterTest=false [[test]] testTitle="SnapSimpleTakeSnap" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" @@ -27,6 +32,7 @@ clearAfterTest=false [[test]] testTitle="SnapSimplePost" clearAfterTest=false +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" @@ -36,6 +42,7 @@ clearAfterTest=false [[test]] testTitle="SnapCreateNotWhitelistedBinaryPath" +disabledFailureInjectionWorkloads='Attrition' [[test.workload]] testName="SnapTest" From 26b60d2de7c8ec9621c6a091a7cd9359b53b82a6 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 08:34:53 -0700 Subject: [PATCH 53/69] Report remote storage in aggregate health metrics --- fdbserver/ratekeeper/Ratekeeper.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/fdbserver/ratekeeper/Ratekeeper.cpp b/fdbserver/ratekeeper/Ratekeeper.cpp index 4e0e0cd995..ba733c7b38 100644 --- a/fdbserver/ratekeeper/Ratekeeper.cpp +++ b/fdbserver/ratekeeper/Ratekeeper.cpp @@ -662,7 +662,18 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { // ratio for (auto i = storageQueueInfo.begin(); i != storageQueueInfo.end(); ++i) { auto const& ss = i->value; - if (!ss.valid || !ss.acceptingRequests || (remoteDC.present() && ss.locality.dcId() == remoteDC)) { + if (!ss.valid || !ss.acceptingRequests) { + continue; + } + + int64_t storageQueue = ss.getStorageQueueBytes(); + worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); + + int64_t storageDurabilityLag = ss.getDurabilityLag(); + worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); + + // Remote storage is reported in health metrics but is not used to rate-limit the primary region. + if (remoteDC.present() && ss.locality.dcId() == remoteDC) { continue; } ++sscount; @@ -697,12 +708,6 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { } } - int64_t storageQueue = ss.getStorageQueueBytes(); - worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); - - int64_t storageDurabilityLag = ss.getDurabilityLag(); - worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); - storageDurabilityLagReverseIndex.insert(std::make_pair(-1 * storageDurabilityLag, &ss)); double targetRateRatio = std::min((storageQueue - targetBytes + springBytes) / (double)springBytes, 2.0); From 48d79db157957eb1603e22e595828d00d460b6df Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 08:51:43 -0700 Subject: [PATCH 54/69] Preserve Native CDC configuration during controller failover --- fdbclient/NativeCdc.cpp | 6 ++++-- .../clustercontroller/ClusterController.actor.cpp | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/fdbclient/NativeCdc.cpp b/fdbclient/NativeCdc.cpp index 953e002151..49af252104 100644 --- a/fdbclient/NativeCdc.cpp +++ b/fdbclient/NativeCdc.cpp @@ -394,10 +394,12 @@ Future 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. diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 0f1f147e96..9ae78cd7f6 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -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); @@ -3478,6 +3480,18 @@ void addProcessesToSameDC(ClusterControllerData& self, const std::vector( + new ClusterConnectionMemoryRecord(ClusterConnectionString()))), + makeReference>>()); + + ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcEnabled, CLIENT_KNOBS->ENABLE_NATIVE_CDC); + ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcTagCount, CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT); + return Void(); +} + TEST_CASE("/fdbserver/clustercontroller/ignoreStaleWorkerRegistration") { ClusterControllerData data(ClusterControllerFullInterface(), LocalityData(), From 167b157f4be5be133c15abc17a19a243d1bcd6f3 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 09:07:19 -0700 Subject: [PATCH 55/69] Remove redundant Native CDC bootstrap unit test --- .../clustercontroller/ClusterController.actor.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 9ae78cd7f6..f5e3fca03c 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -3480,18 +3480,6 @@ void addProcessesToSameDC(ClusterControllerData& self, const std::vector( - new ClusterConnectionMemoryRecord(ClusterConnectionString()))), - makeReference>>()); - - ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcEnabled, CLIENT_KNOBS->ENABLE_NATIVE_CDC); - ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcTagCount, CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT); - return Void(); -} - TEST_CASE("/fdbserver/clustercontroller/ignoreStaleWorkerRegistration") { ClusterControllerData data(ClusterControllerFullInterface(), LocalityData(), From 385eaaad1f15134a4f5cbaeab927e99352019260 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 19:11:03 -0700 Subject: [PATCH 56/69] tests: keep storage wiggle disabled for minimum throughput --- tests/fast/MinimumThroughput.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fast/MinimumThroughput.toml b/tests/fast/MinimumThroughput.toml index 853c203ff7..12be0afd19 100644 --- a/tests/fast/MinimumThroughput.toml +++ b/tests/fast/MinimumThroughput.toml @@ -4,6 +4,8 @@ buggify = false [[test]] testTitle = 'MinimumThroughput' connectionFailuresDisableDuration = 100000 +# Restoring perpetual wiggle would force recovery and data movement immediately before this error-free throughput check. +restorePerpetualWiggleSetting = false [[test.workload]] testName = 'MinimumThroughput' From bf9de32ac518382d2cf3d427368e21996c0e52cd Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 19:59:25 -0700 Subject: [PATCH 57/69] Confirm simulated dead regions before repair --- fdbserver/core/QuietDatabase.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fdbserver/core/QuietDatabase.cpp b/fdbserver/core/QuietDatabase.cpp index d6c0861399..b543e3de12 100644 --- a/fdbserver/core/QuietDatabase.cpp +++ b/fdbserver/core/QuietDatabase.cpp @@ -703,6 +703,21 @@ Future repairDeadDatacenter(Database cx, Reference auto& simPolicy = fdbSimulationPolicyState(); 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 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) { + co_return; + } + primaryDead = g_simulator->datacenterDead(simPolicy.primaryDcId); + 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 From 7a5d8e10fa36f143295c5108086094d62b322c38 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 20:11:30 -0700 Subject: [PATCH 58/69] Require a stable dead region before simulated repair --- fdbserver/core/QuietDatabase.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fdbserver/core/QuietDatabase.cpp b/fdbserver/core/QuietDatabase.cpp index b543e3de12..dddb13dea6 100644 --- a/fdbserver/core/QuietDatabase.cpp +++ b/fdbserver/core/QuietDatabase.cpp @@ -701,22 +701,25 @@ Future repairDeadDatacenter(Database cx, Reference 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 reboot time before making an irreversible region change. + // 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) { + if (simPolicy.usableRegions <= 1 || simPolicy.quiesced || simPolicy.primaryDcId != primaryDcId || + simPolicy.remoteDcId != remoteDcId) { co_return; } - primaryDead = g_simulator->datacenterDead(simPolicy.primaryDcId); - remoteDead = g_simulator->datacenterDead(simPolicy.remoteDcId); + 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 From 8c1184d2577eb96dccd703e0e83b0acc9e5bbe0a Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 21:03:44 -0700 Subject: [PATCH 59/69] Disable failure workloads for Native CDC reply chunking --- tests/fast/NativeCdcReplyChunking.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fast/NativeCdcReplyChunking.toml b/tests/fast/NativeCdcReplyChunking.toml index ba25189824..792f363131 100644 --- a/tests/fast/NativeCdcReplyChunking.toml +++ b/tests/fast/NativeCdcReplyChunking.toml @@ -18,6 +18,7 @@ testTitle = 'NativeCdcReplyChunking' useDB = true waitForQuiescenceEnd = false connectionFailuresDisableDuration = 1000000 +runFailureWorkloads = false [[test.workload]] testName = 'NativeCdcEndToEnd' From 816b806380ebf088236c1c8d85d041280017865d Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 22:05:34 -0700 Subject: [PATCH 60/69] tests: avoid dropped log-router replies in HTTP KV test --- tests/fast/HTTPKeyValueStore.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/fast/HTTPKeyValueStore.toml b/tests/fast/HTTPKeyValueStore.toml index f155b750e5..e46ece355d 100644 --- a/tests/fast/HTTPKeyValueStore.toml +++ b/tests/fast/HTTPKeyValueStore.toml @@ -2,6 +2,11 @@ # [[ knobs ]] # http_request_id_header=1 +[[knobs]] +# Dropping a log-router init reply can repeatedly restart recovery and exhaust +# this test's timeout under remote-double Attrition. +cc_recovery_init_req_allow_drop_in_sim = false + [[test]] testTitle = 'HTTPKeyValueStoreTest' timeout = 1000 @@ -27,4 +32,3 @@ timeout = 1000 machinesToLeave = 3 reboot = true testDuration = 30.0 - From 9fbee3b3581476f5e033cc74dd8a2e4f8b2802bd Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sun, 19 Jul 2026 23:31:47 -0700 Subject: [PATCH 61/69] Disable large teams in storage migration restart test --- .../from_7.3.0/ConfigureStorageMigrationTestRestart-1.toml | 6 +++++- .../from_7.3.0/ConfigureStorageMigrationTestRestart-2.toml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-1.toml b/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-1.toml index e6e00f834d..dcb03ec035 100644 --- a/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-1.toml +++ b/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-1.toml @@ -1,3 +1,7 @@ +[[knobs]] +# Keep custom large-team recovery from blocking storage migration in this restart test. +dd_max_shards_on_large_teams = 0 + [configuration] storageEngineExcludeTypes=[3,5] extraMachineCountDC = 2 @@ -27,4 +31,4 @@ clearAfterTest = false [[test.workload]] testName='SaveAndKill' restartInfoLocation='simfdb/restartInfo.ini' - testDuration=30.0 \ No newline at end of file + testDuration=30.0 diff --git a/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-2.toml b/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-2.toml index 96fb90316f..873d1930a2 100644 --- a/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-2.toml +++ b/tests/restarting/from_7.3.0/ConfigureStorageMigrationTestRestart-2.toml @@ -1,3 +1,7 @@ +[[knobs]] +# Keep custom large-team recovery from blocking storage migration in this restart test. +dd_max_shards_on_large_teams = 0 + [configuration] storageEngineExcludeTypes=[5] extraMachineCountDC = 2 @@ -21,4 +25,4 @@ waitForQuiescenceBegin=false testName = 'RandomClogging' testDuration = 300.0 scale = 0.1 - clogginess = 2.0 \ No newline at end of file + clogginess = 2.0 From 78f7434fae06516e3e39be628f2bcab449e25380 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 20 Jul 2026 07:10:48 -0700 Subject: [PATCH 62/69] Fix C binding clang-tidy lifetime warnings --- bindings/c/fdb_c.cpp | 2 +- bindings/c/test/unit/unit_tests.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index a11e5ca052..041efd33dd 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -353,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) { diff --git a/bindings/c/test/unit/unit_tests.cpp b/bindings/c/test/unit/unit_tests.cpp index 714bc472f3..31e2f25c74 100644 --- a/bindings/c/test/unit/unit_tests.cpp +++ b/bindings/c/test/unit/unit_tests.cpp @@ -1023,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 result1 = t.pack(); Tuple t2 = Tuple::unpack(result1); - StringRef result2 = t2.pack(); + Standalone result2 = t2.pack(); ASSERT(t2.getVersionstamp(2) == vs); ASSERT(result1.toString() == result2.toString()); } From b58cf20817820bcb2576797b8f521d877ebff176 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 20 Jul 2026 07:41:19 -0700 Subject: [PATCH 63/69] Retry undelivered Native CDC proxy halts --- fdbserver/workloads/NativeCdcEndToEnd.cpp | 33 +++++++++++++++++-- .../fast/NativeCdcAssignmentPublication.toml | 2 ++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/fdbserver/workloads/NativeCdcEndToEnd.cpp b/fdbserver/workloads/NativeCdcEndToEnd.cpp index bf080dce26..43644f47bc 100644 --- a/fdbserver/workloads/NativeCdcEndToEnd.cpp +++ b/fdbserver/workloads/NativeCdcEndToEnd.cpp @@ -69,6 +69,7 @@ class NativeCdcEndToEndWorkload : public TestWorkload { int rounds; int assignmentPublicationChecks; bool testProxyReplacement; + bool injectUndeliveredProxyHalt; bool testMemoryBound; bool testReplyChunking; bool testOversizedPeek; @@ -436,6 +437,31 @@ class NativeCdcEndToEndWorkload : public TestWorkload { } } + Future haltProxyUntilReplaced(Database cx, CDCProxyInterface proxy, bool dropFirstAttempt) { + while (true) { + { + const auto& proxies = cx->clientInfo->get().cdcProxies; + if (std::find(proxies.begin(), proxies.end(), proxy) == proxies.end()) { + co_return; + } + } + + // The focused scenario can drop one halt before delivery and immediately retry its published proxy. + const bool droppedAttempt = std::exchange(dropFirstAttempt, false); + ErrorOr halted = request_maybe_delivered(); + if (!droppedAttempt) { + halted = co_await proxy.haltForTesting.tryGetReply(HaltCDCProxyRequest()); + } + if (halted.present()) { + co_return; + } + CODE_PROBE(true, "Native CDC retries an undelivered proxy halt"); + if (!droppedAttempt) { + co_await delay(0.1); + } + } + } + Future validateProxyReplacement(Database cx) { const Key name = "native-cdc-e2e/proxy-replacement"_sr; const KeyRange keys( @@ -453,10 +479,11 @@ class NativeCdcEndToEndWorkload : public TestWorkload { ASSERT(std::find(originalProxies.begin(), originalProxies.end(), original) != originalProxies.end()); Future publications = waitForIndependentProxyPublications(cx, originalProxies); - std::vector>> halts; + std::vector> halts; halts.reserve(originalProxies.size()); + bool dropFirstHalt = injectUndeliveredProxyHalt; for (const auto& proxy : originalProxies) { - halts.push_back(proxy.haltForTesting.tryGetReply(HaltCDCProxyRequest())); + halts.push_back(haltProxyUntilReplaced(cx, proxy, std::exchange(dropFirstHalt, false))); } co_await timeoutError(waitForAll(halts), operationTimeout); co_await timeoutError(publications, operationTimeout); @@ -1380,6 +1407,7 @@ public: rounds = getOption(options, "rounds"_sr, 30); assignmentPublicationChecks = getOption(options, "assignmentPublicationChecks"_sr, 0); testProxyReplacement = getOption(options, "testProxyReplacement"_sr, false); + injectUndeliveredProxyHalt = getOption(options, "injectUndeliveredProxyHalt"_sr, false); testMemoryBound = getOption(options, "testMemoryBound"_sr, false); testReplyChunking = getOption(options, "testReplyChunking"_sr, false); testOversizedPeek = getOption(options, "testOversizedPeek"_sr, false); @@ -1401,6 +1429,7 @@ public: ASSERT_GE(writesPerRound, 1); ASSERT_LE(writesPerRound, keyCount); ASSERT_GE(assignmentPublicationChecks, 0); + ASSERT(!injectUndeliveredProxyHalt || testProxyReplacement); ASSERT_GT(memoryTestValueBytes, 0); ASSERT_GE(retentionValidationDelay, 0.0); ASSERT(!(prepareRestartDrain && drainAfterRestart)); diff --git a/tests/fast/NativeCdcAssignmentPublication.toml b/tests/fast/NativeCdcAssignmentPublication.toml index 3ae53b75a5..1a056c781c 100644 --- a/tests/fast/NativeCdcAssignmentPublication.toml +++ b/tests/fast/NativeCdcAssignmentPublication.toml @@ -29,6 +29,8 @@ timeout = 180 rounds = 1 assignmentPublicationChecks = 8 testProxyReplacement = true + # Exercise the at-most-once halt retry without disabling replacement or failure-workload coverage. + injectUndeliveredProxyHalt = true drainProbability = 1.0 delayBetweenRounds = 0.0 operationTimeout = 60.0 From bfcbf7ca4fba6aa8539b3a9ec568a2f1c118035c Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 20 Jul 2026 12:06:46 -0700 Subject: [PATCH 64/69] Restart DD before launching stale restored data moves --- .../datadistributor/DDRelocationQueue.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 6048644e38..fd6910a4a8 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -28,6 +28,7 @@ #include "flow/ActorCollection.h" #include "flow/Buggify.h" #include "flow/FastRef.h" +#include "flow/ScopeExit.h" #include "flow/Trace.h" #include "fdbrpc/sim_validation.h" #include "fdbclient/ManagementAPI.h" @@ -1126,6 +1127,11 @@ void DDQueue::launchQueuedWork(std::set // 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 @@ -3365,6 +3371,31 @@ TEST_CASE("/DataDistribution/DDQueue/RetryDestinationTeamFailure") { return Void(); } +TEST_CASE("/DataDistribution/DDQueue/RejectStaleRestoredRelocation") { + const bool oldShardEncode = SERVER_KNOBS->SHARD_ENCODE_LOCATION_METADATA; + setServerKnob("shard_encode_location_metadata", KnobValueRef::create(false)); + ScopeExit restoreKnob( + [oldShardEncode]() { setServerKnob("shard_encode_location_metadata", KnobValueRef::create(oldShardEncode)); }); + + KeyRange keys(KeyRangeRef("a"_sr, "b"_sr)); + UID dataMoveId(1, 2); + DataMoveMetaData metadata(dataMoveId, keys); + metadata.setPhase(DataMoveMetaData::Running); + RelocateShard restored(keys, DataMovementReason::RECOVER_MOVE, RelocateReason::OTHER); + restored.dataMoveId = dataMoveId; + restored.dataMove = std::make_shared(metadata, true); + + Error observed; + try { + DDQueue queue; + queue.launchQueuedWork(RelocateData(restored), nullptr); + } catch (Error& e) { + observed = e; + } + ASSERT(observed.code() == error_code_dd_config_changed); + return Void(); +} + TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { Reference self = makeReference(); DDQueueImpl::RunState state(self); From 96f56bb29ad21b80ff147e0c9d39d5cb04d4496a Mon Sep 17 00:00:00 2001 From: Akanksha Mahajan <43301668+akankshamahajan15@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:39:15 -0700 Subject: [PATCH 65/69] Fix read-side overflow and simplify append API for large snapshot manifests (#13691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix read-side overflow and simplify append API for large snapshot manifests Follow-up to #13349, which fixed the write-side overflow when a snapshot manifest exceeds ~2 GB but left the read side and the API untidy. This PR addresses both. ### Changes **Simpler append API** There were two `append()` methods — one taking `int`, one `size_t` — and which ran depended on the argument type, which is easy to get wrong. Replaced with a single public `append()` that safely chunks any size, plus a clearly-named backend hook `appendImpl()` that each storage backend implements. No more overload ambiguity. **Read side fix** `readKeyspaceSnapshot` read the manifest into a buffer whose length is an `int`, so a manifest larger than 2 GB could truncate and crash on restore. It now reads into a `std::string` (which can exceed 2 GB) in chunks, matching the write side, and drops a redundant full copy of the manifest. **Knob rename** `BACKUP_MANIFEST_WRITE_CHUNK_SIZE` → `BACKUP_MANIFEST_CHUNK_SIZE`, since it now controls chunk size for both reads and writes. **Test** Added a unit test that reads a manifest back in many small chunks and verifies all range files and key ranges round-trip correctly. ### Notes - Range and log files are unaffected — they're already streamed in small blocks on both read and write. * Addressed comments * Fix clang tidy errors --- fdbclient/BackupContainer.cpp | 13 +-- fdbclient/BackupContainerBlobStore.cpp | 4 +- fdbclient/BackupContainerFileSystem.cpp | 99 +++++++++++++++++-- fdbclient/BackupContainerLocalDirectory.cpp | 4 +- fdbclient/ClientKnobs.cpp | 2 +- fdbclient/include/fdbclient/BackupContainer.h | 8 +- fdbclient/include/fdbclient/Knobs.h | 2 +- 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/fdbclient/BackupContainer.cpp b/fdbclient/BackupContainer.cpp index 739c63aec5..e79eeccc9a 100644 --- a/fdbclient/BackupContainer.cpp +++ b/fdbclient/BackupContainer.cpp @@ -45,15 +45,12 @@ Future appendStringRefWithLen(Reference file, Standaloneappend(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 appendChunked(Reference file, const void* data, size_t len) { +Future append(Reference file, const void* data, size_t len) { const char* ptr = static_cast(data); + size_t chunkLimit = static_cast(CLIENT_KNOBS->BACKUP_MANIFEST_CHUNK_SIZE); for (size_t offset = 0; offset < len;) { - int chunkSize = static_cast( - std::min(len - offset, static_cast(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 IBackupFile::appendStringRefWithLen(Standalone s) { } Future IBackupFile::append(const void* data, size_t len) { - return IBackupFile_impl::appendChunked(Reference::addRef(this), data, len); + return IBackupFile_impl::append(Reference::addRef(this), data, len); } bool isBlobstoreUrl(const std::string& url) { diff --git a/fdbclient/BackupContainerBlobStore.cpp b/fdbclient/BackupContainerBlobStore.cpp index 505dea70a4..b824801225 100644 --- a/fdbclient/BackupContainerBlobStore.cpp +++ b/fdbclient/BackupContainerBlobStore.cpp @@ -52,8 +52,8 @@ public: BackupFile(std::string fileName, Reference file) : IBackupFile(fileName), m_file(file), m_offset(0) {} - Future append(const void* data, int len) override { - Future r = m_file->write(data, len, m_offset); + Future appendImpl(const void* data, size_t len) override { + Future r = m_file->write(data, static_cast(len), m_offset); m_offset += len; return r; } diff --git a/fdbclient/BackupContainerFileSystem.cpp b/fdbclient/BackupContainerFileSystem.cpp index 2be8d39605..fe7a1dd307 100644 --- a/fdbclient/BackupContainerFileSystem.cpp +++ b/fdbclient/BackupContainerFileSystem.cpp @@ -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::map>> readKeyspaceSnapshot( @@ -55,10 +69,22 @@ public: // return them. Reference f = co_await bc->readFile(snapshot.fileName); int64_t size = co_await f->size(); - Standalone 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(std::min(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 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(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(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 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(CLIENT_KNOBS)->BACKUP_MANIFEST_WRITE_CHUNK_SIZE = savedChunkSize; + const_cast(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(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = 7; + + std::string url = format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()); + Reference 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 rangeFileNames; + std::vector> beginEndKeys; + std::map> expected; + for (int i = 0; i < 5; ++i) { + Key begin = StringRef(format("begin-%d", i)); + Key end = StringRef(format("end-%d", i)); + Reference 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 bcfs = c.castTo(); + std::vector 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(CLIENT_KNOBS)->BACKUP_MANIFEST_CHUNK_SIZE = savedChunkSize; co_await c->deleteContainer(); } diff --git a/fdbclient/BackupContainerLocalDirectory.cpp b/fdbclient/BackupContainerLocalDirectory.cpp index ae2f2f2cef..4d3cc97f2a 100644 --- a/fdbclient/BackupContainerLocalDirectory.cpp +++ b/fdbclient/BackupContainerLocalDirectory.cpp @@ -40,8 +40,8 @@ public: m_buffer.reserve(m_buffer.arena(), m_blockSize); } - Future append(const void* data, int len) override { - m_buffer.append(m_buffer.arena(), (const uint8_t*)data, len); + Future appendImpl(const void* data, size_t len) override { + m_buffer.append(m_buffer.arena(), (const uint8_t*)data, static_cast(len)); if (m_buffer.size() >= m_blockSize) { return flush(m_blockSize); diff --git a/fdbclient/ClientKnobs.cpp b/fdbclient/ClientKnobs.cpp index 19f3181dd0..98048f6579 100644 --- a/fdbclient/ClientKnobs.cpp +++ b/fdbclient/ClientKnobs.cpp @@ -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::max() ); if( randomize && buggify() ) BACKUP_MANIFEST_WRITE_CHUNK_SIZE = 64; + init( BACKUP_MANIFEST_CHUNK_SIZE, std::numeric_limits::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 ); diff --git a/fdbclient/include/fdbclient/BackupContainer.h b/fdbclient/include/fdbclient/BackupContainer.h index 09f1788a74..6bdb4fe1d7 100644 --- a/fdbclient/include/fdbclient/BackupContainer.h +++ b/fdbclient/include/fdbclient/BackupContainer.h @@ -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 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 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 append(const void* data, size_t len); virtual Future finish() = 0; inline std::string getFileName() const { return m_fileName; } diff --git a/fdbclient/include/fdbclient/Knobs.h b/fdbclient/include/fdbclient/Knobs.h index d37b48349f..b86bcc0450 100644 --- a/fdbclient/include/fdbclient/Knobs.h +++ b/fdbclient/include/fdbclient/Knobs.h @@ -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; From 1d88e45501041f731d0232689d568834dbdd6cc8 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 20 Jul 2026 13:18:46 -0700 Subject: [PATCH 66/69] Remove stale restored relocation unit test --- .../datadistributor/DDRelocationQueue.cpp | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index fd6910a4a8..e3d40347df 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -28,7 +28,6 @@ #include "flow/ActorCollection.h" #include "flow/Buggify.h" #include "flow/FastRef.h" -#include "flow/ScopeExit.h" #include "flow/Trace.h" #include "fdbrpc/sim_validation.h" #include "fdbclient/ManagementAPI.h" @@ -3371,31 +3370,6 @@ TEST_CASE("/DataDistribution/DDQueue/RetryDestinationTeamFailure") { return Void(); } -TEST_CASE("/DataDistribution/DDQueue/RejectStaleRestoredRelocation") { - const bool oldShardEncode = SERVER_KNOBS->SHARD_ENCODE_LOCATION_METADATA; - setServerKnob("shard_encode_location_metadata", KnobValueRef::create(false)); - ScopeExit restoreKnob( - [oldShardEncode]() { setServerKnob("shard_encode_location_metadata", KnobValueRef::create(oldShardEncode)); }); - - KeyRange keys(KeyRangeRef("a"_sr, "b"_sr)); - UID dataMoveId(1, 2); - DataMoveMetaData metadata(dataMoveId, keys); - metadata.setPhase(DataMoveMetaData::Running); - RelocateShard restored(keys, DataMovementReason::RECOVER_MOVE, RelocateReason::OTHER); - restored.dataMoveId = dataMoveId; - restored.dataMove = std::make_shared(metadata, true); - - Error observed; - try { - DDQueue queue; - queue.launchQueuedWork(RelocateData(restored), nullptr); - } catch (Error& e) { - observed = e; - } - ASSERT(observed.code() == error_code_dd_config_changed); - return Void(); -} - TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { Reference self = makeReference(); DDQueueImpl::RunState state(self); From 7da2679aa6d5d1238141fb4de86c7527fe907e8b Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 20 Jul 2026 21:16:59 -0400 Subject: [PATCH 67/69] ci: codebuild-cleanup action: consolidate graphql queries (#13724) * update codebuild-cleanup action to github-script@v9 just to avoid node 20.x deprecation warnings * consolidate graphql queries: just one graphql query to get all comments along with minimized status (replacing the initial rest query to get all comments) --- .github/workflows/codebuild-cleanup.yml | 90 +++++++++++++++---------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/.github/workflows/codebuild-cleanup.yml b/.github/workflows/codebuild-cleanup.yml index e3299d6a2d..4b859d52d5 100644 --- a/.github/workflows/codebuild-cleanup.yml +++ b/.github/workflows/codebuild-cleanup.yml @@ -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); } } From 3d64ad40beeda93cef4da2003929af77b2df2879 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Tue, 21 Jul 2026 01:53:32 -0400 Subject: [PATCH 68/69] docker: update base to rockylinux-9.8, golang builder, README (#13757) * update Dockerfile base to rockylinux-9.8 * update golang builder to golang 1.25.12 * update docker README build instructions The build context expected by different stages of the Dockerfile is a bit confusing ... most expect the context root to be packaging/docker/ but fdb-kubernetes-monitor expects the repo root. How it's actually built by the release system is in the build-output packages/docker/ dir where the needed sources are all copied. --- packaging/docker/Dockerfile | 7 ++++--- packaging/docker/README.md | 14 +++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index f318149b30..a9e5d8c2b0 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -FROM rockylinux/rockylinux:9.6-minimal AS base +FROM rockylinux/rockylinux:9.8-minimal AS base # Disable the faulty mirror list for RockyLinux for now. Since all the other images use the base image # as base we don't have to repeat this step. @@ -60,12 +60,13 @@ RUN curl -Ls "https://github.com/krallin/tini/releases/download/v0.19.0/tini-$TA WORKDIR / -FROM golang:1.25.8-bookworm AS go-build +# Check that glibc version in base image >= glibc in go-build image (unless CGO_ENABLED=0) + +FROM golang:1.25.12-bookworm AS go-build COPY fdbkubernetesmonitor/ /fdbkubernetesmonitor WORKDIR /fdbkubernetesmonitor RUN go build -o /fdb-kubernetes-monitor *.go -# Build the fdb-aws-s3-credentials-fetcher in a dedicated build FROM go-build AS go-credentials-fetcher-build COPY fdb-aws-s3-credentials-fetcher/ /fdb-aws-s3-credentials-fetcher WORKDIR /fdb-aws-s3-credentials-fetcher diff --git a/packaging/docker/README.md b/packaging/docker/README.md index f7de1cec1a..78fde38b9c 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -18,15 +18,15 @@ the expectation that it is, at least, partially (if not entirely) incorrect. If you only want to build a custom container image based on an already released FDB version you run the following command from the root: ```bash -export REGISTRY=docker.io -export FDB_VERSION=7.3.63 -docker build --build-arg FDB_VERSION=${FDB_VERSION} -t ${REGISTRY}/foundationdb/fdb-kubernetes-monitor:${FDB_VERSION} --target fdb-kubernetes-monitor -f ./packaging/docker/Dockerfile . +FDB_VERSION=7.3.79 +docker build --build-arg FDB_VERSION=${FDB_VERSION} -t foundationdb:${FDB_VERSION} --target foundationdb ./packaging/docker ``` -Or if you want to build the `foundationdb` image and not the `fdb-kubernetes-monitor`: +If you want to build the `fdb-kubernetes-monitor` image (which includes fdb binaries too), +you need to use the build-output directory, even if not using binaries from the build. +(Just the cmake configure step is enough to set this up.) ```bash -export REGISTRY=docker.io -export FDB_VERSION=7.3.63 -docker build --build-arg FDB_VERSION=${FDB_VERSION} -t ${REGISTRY}/foundationdb/foundationdb:${FDB_VERSION} --target foundationdb -f ./packaging/docker/Dockerfile . +FDB_VERSION=7.3.79 +docker build --build-arg FDB_VERSION=${FDB_VERSION} -t fdb-kubernetes-monitor:${FDB_VERSION} --target fdb-kubernetes-monitor .../build-output/packages/docker ``` From 7e391dae1d85ac4a73aedcec080483cc1a6006df Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 21 Jul 2026 10:22:37 -0700 Subject: [PATCH 69/69] tests: avoid dropped log-router replies in SwizzledCycle --- tests/slow/SwizzledCycleTest.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/slow/SwizzledCycleTest.toml b/tests/slow/SwizzledCycleTest.toml index e8e419e6f1..e32c633a45 100644 --- a/tests/slow/SwizzledCycleTest.toml +++ b/tests/slow/SwizzledCycleTest.toml @@ -1,3 +1,8 @@ +[[knobs]] +# Dropping a log-router init reply can repeatedly restart recovery and exhaust +# this test's clear timeout under remote-double Attrition. +cc_recovery_init_req_allow_drop_in_sim = false + [[test]] testTitle = 'SwizzledCycleTest'