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); } } diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index 085638eb55..8b2093b52e 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -92,10 +92,11 @@ jobs: ;; esac # These inputs are not parseable as standalone clang-tidy translation units in this workflow: - # the RocksDB compile commands refer to headers that are not built here, and the Flow headers depend - # on include order from their real consumers. The C API header is intentionally valid C, not C++. + # the public C headers require C consumer setup and intentionally use C typedefs, the RocksDB compile + # commands refer to headers that are not built here, and the Flow headers depend on include order + # from their real consumers. case "$FILE" in - bindings/c/foundationdb/fdb_c.h) + bindings/c/foundationdb/fdb_c.h|bindings/c/foundationdb/fdb_c_types.h) continue ;; fdbserver/core/RocksDBCheckpointUtils.cpp|fdbserver/kvstore/KeyValueStoreRocksDB.cpp|fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp) diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 9b48736b37..edb5609efb 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 $ @@ -552,6 +556,8 @@ elseif(NOT WIN32 AND NOT APPLE) # Linux Only --api-test-dir ${CMAKE_SOURCE_DIR}/bindings/c/test/apitester/tests ${SHIM_LIB_TEST_EXTRA_OPTIONS} ) + set_property(TEST fdb_c_shim_library_tests APPEND PROPERTY ENVIRONMENT + "FDB_KNOB_enable_native_cdc=true") endif() # End Linux only diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 2eec432485..041efd33dd 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 + * FDBCdcConsumer -> 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,96 @@ 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"); +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) { + FDBCdcStreamInfo cStream; + cStream.name = copyNativeCdcKey(result.arena, stream.name); + cStream.stream_id = stream.streamId; + cStream.key_range = copyNativeCdcKeyRange(result.arena, stream.keys); + cStream.min_version = stream.minVersion; + result.streams.push_back(result.arena, cStream); + } + return result; +} + +CNativeCdcConsumeResult makeCNativeCdcConsumeResult(NativeCdcConsumeResult const& source) { + CNativeCdcConsumeResult result; + result.lastConsumedVersion = source.cursor.lastConsumedVersion; + result.mutations.reserve(result.arena, source.mutations.size()); + for (auto const& versioned : source.mutations) { + FDBCdcVersionedMutations cVersioned; + cVersioned.version = versioned.version; + cVersioned.mutation_count = versioned.mutations.size(); + cVersioned.mutations = nullptr; + if (!versioned.mutations.empty()) { + auto* cMutations = new (result.arena) FDBCdcMutation[versioned.mutations.size()]; + for (int i = 0; i < versioned.mutations.size(); ++i) { + auto const& mutation = versioned.mutations[i]; + StringRef param1(result.arena, mutation.param1); + StringRef param2(result.arena, mutation.param2); + cMutations[i] = + FDBCdcMutation{ mutation.type, param1.begin(), param1.size(), param2.begin(), param2.size() }; + } + cVersioned.mutations = cMutations; + } + result.mutations.push_back(result.arena, cVersioned); + } + return result; +} + +FDBFuture* mapNativeCdcStreamInfoFuture(ThreadFuture> 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) { @@ -260,7 +353,7 @@ extern "C" DLLEXPORT fdb_error_t fdb_future_get_double(FDBFuture* f, double* out } extern "C" DLLEXPORT fdb_error_t fdb_future_get_key(FDBFuture* f, uint8_t const** out_key, int* out_key_length) { - CATCH_AND_RETURN(KeyRef key = TSAV(Key, f)->get(); *out_key = key.begin(); *out_key_length = key.size();); + CATCH_AND_RETURN(Key key = TSAV(Key, f)->get(); *out_key = key.begin(); *out_key_length = key.size();); } fdb_error_t fdb_future_get_cluster_v609(FDBFuture* f, FDBCluster** out_cluster) { @@ -332,6 +425,29 @@ extern "C" DLLEXPORT fdb_error_t fdb_future_get_key_array(FDBFuture* f, FDBKey c *out_count = na.size();); } +extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* f, + FDBCdcStreamInfo const** out_streams, + int* out_count) { + CATCH_AND_RETURN(CNativeCdcStreamInfoArray result = TSAV(CNativeCdcStreamInfoArray, f)->get(); + *out_streams = result.streams.begin(); + *out_count = result.streams.size();); +} + +extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* f, FDBCdcConsumer** out_consumer) { + CATCH_AND_RETURN(Reference consumer = TSAV(Reference, f)->get(); + *out_consumer = (FDBCdcConsumer*)consumer.extractPtr();); +} + +extern "C" DLLEXPORT fdb_error_t fdb_future_get_cdc_versioned_mutations(FDBFuture* f, + FDBCdcVersionedMutations const** out_mutations, + int* out_count, + int64_t* out_last_consumed_version) { + CATCH_AND_RETURN(CNativeCdcConsumeResult result = TSAV(CNativeCdcConsumeResult, f)->get(); + *out_mutations = result.mutations.begin(); + *out_count = result.mutations.size(); + *out_last_consumed_version = result.lastConsumedVersion;); +} + extern "C" DLLEXPORT void fdb_result_destroy(FDBResult* r) { CATCH_AND_DIE(TSAVB(r)->cancel();); } @@ -446,6 +562,72 @@ extern "C" DLLEXPORT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, *out_transaction = (FDBTransaction*)tr.extractPtr();); } +extern "C" DLLEXPORT FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* db, + uint8_t const* name, + int name_length, + uint8_t const* begin_key, + int begin_key_length, + uint8_t const* end_key, + int end_key_length) { + RETURN_FUTURE_ON_ERROR( + CDCStreamId, + return (FDBFuture*)(DB(db) + ->registerNativeCdcStream( + KeyRef(name, name_length), + KeyRangeRef(KeyRef(begin_key, begin_key_length), KeyRef(end_key, end_key_length))) + .extractPtr());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* db, uint8_t const* name, int name_length) { + RETURN_FUTURE_ON_ERROR(Void, + return (FDBFuture*)(DB(db)->removeNativeCdcStream(KeyRef(name, name_length)).extractPtr());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* db) { + RETURN_FUTURE_ON_ERROR(CNativeCdcStreamInfoArray, + return mapNativeCdcStreamInfoFuture(DB(db)->listNativeCdcStreams());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* db, + uint8_t const* name, + int name_length) { + RETURN_FUTURE_ON_ERROR( + Reference, + return (FDBFuture*)(DB(db)->createNativeCdcConsumer(KeyRef(name, name_length)).extractPtr());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* db, + uint64_t stream_id, + int64_t last_consumed_version) { + RETURN_FUTURE_ON_ERROR(Reference, NativeCdcCursor cursor; cursor.streamId = stream_id; + cursor.lastConsumedVersion = last_consumed_version; + return (FDBFuture*)(DB(db)->resumeNativeCdcConsumer(cursor).extractPtr());); +} + +extern "C" DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer) { + try { + NATIVE_CDC_CONSUMER(consumer)->delref(); + } catch (...) { + } +} + +extern "C" DLLEXPORT FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer) { + RETURN_FUTURE_ON_ERROR(CNativeCdcConsumeResult, + return mapNativeCdcConsumeFuture(NATIVE_CDC_CONSUMER(consumer)->consume());); +} + +extern "C" DLLEXPORT FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer) { + RETURN_FUTURE_ON_ERROR(Void, return (FDBFuture*)(NATIVE_CDC_CONSUMER(consumer)->acknowledge().extractPtr());); +} + +extern "C" DLLEXPORT fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer, + uint64_t* out_stream_id, + int64_t* out_last_consumed_version) { + CATCH_AND_RETURN(NativeCdcCursor position = NATIVE_CDC_CONSUMER(consumer)->getPosition(); + *out_stream_id = position.streamId; + *out_last_consumed_version = position.lastConsumedVersion;); +} + extern "C" DLLEXPORT FDBFuture* fdb_database_reboot_worker(FDBDatabase* db, uint8_t const* address, int address_length, diff --git a/bindings/c/foundationdb/fdb_c.h b/bindings/c/foundationdb/fdb_c.h index 6f6a197948..cf344d51df 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 CDC. The numeric values match + * MutationRef::Type and, for atomic operations, FDBMutationType. + */ +typedef enum { + FDB_CDC_MUTATION_TYPE_SET_VALUE = 0, + FDB_CDC_MUTATION_TYPE_CLEAR_RANGE = 1, + FDB_CDC_MUTATION_TYPE_ADD = 2, + FDB_CDC_MUTATION_TYPE_AND = 6, + FDB_CDC_MUTATION_TYPE_OR = 7, + FDB_CDC_MUTATION_TYPE_XOR = 8, + FDB_CDC_MUTATION_TYPE_APPEND_IF_FITS = 9, + FDB_CDC_MUTATION_TYPE_MAX = 12, + FDB_CDC_MUTATION_TYPE_MIN = 13, + FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY = 14, + FDB_CDC_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE = 15, + FDB_CDC_MUTATION_TYPE_BYTE_MIN = 16, + FDB_CDC_MUTATION_TYPE_BYTE_MAX = 17, + FDB_CDC_MUTATION_TYPE_MIN_V2 = 18, + FDB_CDC_MUTATION_TYPE_AND_V2 = 19, + FDB_CDC_MUTATION_TYPE_COMPARE_AND_CLEAR = 20 +} FDBCdcMutationType; + +typedef struct cdc_stream_info { + FDBKey name; + uint64_t stream_id; + FDBKeyRange key_range; + int64_t min_version; +} FDBCdcStreamInfo; + +typedef struct cdc_mutation { + /* FDBCdcMutationType */ uint8_t type; + const uint8_t* param1; + int param1_length; + const uint8_t* param2; + int param2_length; +} FDBCdcMutation; + +typedef struct cdc_versioned_mutations { + int64_t version; + const FDBCdcMutation* mutations; + int mutation_count; +} FDBCdcVersionedMutations; + /* * TODO: delete the following "blob granule" and "tenant" related data types * when we are sure it's safe to do so. @@ -353,6 +397,18 @@ DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_keyrange_array(FDBFuture FDBKeyRange const** out_ranges, int* out_count); +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* f, + FDBCdcStreamInfo const** out_streams, + int* out_count); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* f, FDBCdcConsumer** out_consumer); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t +fdb_future_get_cdc_versioned_mutations(FDBFuture* f, + FDBCdcVersionedMutations const** out_mutations, + int* out_count, + int64_t* out_last_consumed_version); + /* FDBResult is a synchronous computation result, as opposed to a future that is asynchronous. */ DLLEXPORT void fdb_result_destroy(FDBResult* r); @@ -378,6 +434,38 @@ DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_set_option(FDBDatabase* d, DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_database_create_transaction(FDBDatabase* d, FDBTransaction** out_transaction); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* db, + uint8_t const* name, + int name_length, + uint8_t const* begin_key, + int begin_key_length, + uint8_t const* end_key, + int end_key_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* db, + uint8_t const* name, + int name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* db); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* db, + uint8_t const* name, + int name_length); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* db, + uint64_t stream_id, + int64_t last_consumed_version); + +DLLEXPORT void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer); + +DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer, + uint64_t* out_stream_id, + int64_t* out_last_consumed_version); + /* * Dummy versions of tenant-related functions are needed in the FDB C library * because 7.x python bindings always load these functions on startup. diff --git a/bindings/c/foundationdb/fdb_c_types.h b/bindings/c/foundationdb/fdb_c_types.h index 2d4fc4e082..efb512a328 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_cdc_consumer FDBCdcConsumer; typedef int fdb_error_t; typedef int fdb_bool_t; 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)) diff --git a/bindings/c/test/unit/unit_tests.cpp b/bindings/c/test/unit/unit_tests.cpp index 943c718fd6..31e2f25c74 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 @@ -1021,9 +1023,9 @@ TEST_CASE("tuple_support_versionstamp") { ASSERT(t.getVersionstamp(2) == vs); // verify the round-way pack-unpack path for a Tuple containing a versionstamp - StringRef result1 = t.pack(); + Standalone 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()); } @@ -1970,6 +1972,245 @@ TEST_CASE("fdb_database_get_server_protocol") { fdb_future_destroy(protocolFuture); } +TEST_CASE("CDC C binding end-to-end") { + using FuturePtr = std::unique_ptr; + 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.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); + } + REQUIRE(foundStream); + fdb_future_release_memory(listFuture.get()); + CHECK(fdb_future_get_cdc_stream_info_array(listFuture.get(), &streams, &streamCount) == 1102); // future_released + + auto createFuture = ownFuture( + fdb_database_create_cdc_consumer(db, reinterpret_cast(streamName.data()), streamName.size())); + REQUIRE(createFuture != nullptr); + waitForSuccess(createFuture.get()); + FDBCdcConsumer* rawConsumer = nullptr; + fdb_check(fdb_future_get_cdc_consumer(createFuture.get(), &rawConsumer)); + auto consumer = ownConsumer(rawConsumer); + createFuture.reset(); + REQUIRE(consumer != nullptr); + + uint64_t positionStreamId = 0; + int64_t positionVersion = 0; + fdb_check(fdb_cdc_consumer_get_position(consumer.get(), &positionStreamId, &positionVersion)); + CHECK(positionStreamId == streamId); + CHECK(positionVersion == -1); + + const int64_t setVersion = + commitSetValues({ { firstKey, firstValue }, { secondKey, secondValue }, { outsideKey, "outside-value" } }); + auto setReply = consumeThroughVersion(consumer.get(), setVersion); + CHECK(setReply.version == setVersion); + CHECK(setReply.lastConsumedVersion >= setVersion); + REQUIRE(setReply.mutations.size() == 2); + std::map 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") { // Watches created on a transaction with the option READ_YOUR_WRITES_DISABLE // should return a watches_disabled error. 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..efeb6a3922 --- /dev/null +++ b/contrib/Joshua/tests/correctnessTest_test.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +test_root=$(mktemp -d) +trap 'rm -rf "${test_root}"' EXIT + +mkdir -p "${test_root}/bin" +cat > "${test_root}/bin/python3" <<'FAKE_PYTHON' +#!/usr/bin/env bash + +case "${FAKE_HARNESS_MODE}" in + pass_then_crash) + echo '' + exit 23 + ;; + no_output) + exit 0 + ;; + pass) + echo '' + exit 0 + ;; +esac +FAKE_PYTHON +chmod +x "${test_root}/bin/python3" + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd) +wrapper="${script_dir}/correctnessTest.sh" + +run_case() { + local mode=$1 + local expected_exit=$2 + local expected_ok=$3 + local output_dir="${test_root}/${mode}" + local ensemble_id="correctness-test-${mode}" + local run_dir="${output_dir}/th_run_${ensemble_id}" + local stdout_file="${output_dir}/stdout.log" + local status + + mkdir -p "${output_dir}" + set +e + PATH="${test_root}/bin:${PATH}" \ + FAKE_HARNESS_MODE="${mode}" \ + JOSHUA_SEED=12345 \ + JOSHUA_ENSEMBLE_ID="${ensemble_id}" \ + TH_OUTPUT_DIR="${output_dir}" \ + TH_ARCHIVE_LOGS_ON_FAILURE=true \ + bash "${wrapper}" > "${stdout_file}" 2> "${output_dir}/stderr.log" + status=$? + set -e + + test "${status}" -eq "${expected_exit}" + grep -q "Ok=\"${expected_ok}\"" "${stdout_file}" + if [ "${expected_exit}" -ne 0 ]; then + test -f "${run_dir}/python_app_stdout.log" + grep -q "Ok=\"${expected_ok}\"" "${run_dir}/python_app_stdout.log" + else + test ! -e "${run_dir}" + fi +} + +run_case pass_then_crash 23 1 +run_case no_output 1 0 +run_case pass 0 1 + +grep -q 'CrashReason="TestHarnessProducedNoOutput"' "${test_root}/no_output/stdout.log" +test "$(grep -c 'CrashReason="TestHarnessProducedNoOutput"' "${test_root}/no_output/stdout.log")" -eq 1 + +echo 'correctnessTest wrapper regressions passed' 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(" 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_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(" acknowledgeToken`, `uint16_t sequence`, `int index`}. - --- ## 15. Client Worker / Debug / Process Protocols 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/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 diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index b920dfad10..c568dea5ff 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| @@ -543,6 +561,135 @@ An |database-blurb1| Modifications to a database are performed via transactions. ] } +CDC +--- + +CDC exposes durable, named streams of committed mutations for one half-open +user-key range. New stream registration requires CDC admission +to be enabled on the cluster. Listing, removal, consumer creation, resume, +consume, and acknowledgement remain available for already durable streams while +new admission is disabled so that callers can drain or remove them. + +The CDC C API is available beginning with API version 800. Applications must +select API version 800 or later before calling these functions. + +.. type:: FDBCdcMutationType + + The raw mutation type returned by CDC. Values match the corresponding + FoundationDB mutation encoding. ``SET_VALUE`` uses ``param1`` as the key and + ``param2`` as the value; ``CLEAR_RANGE`` uses them as the clipped begin and + end keys; atomic mutations use them as the key and operand. + + The listed constants are not exhaustive. Callers must handle an + unrecognized raw ``uint8_t`` value in ``FDBCdcMutation.type``. + +.. type:: FDBCdcStreamInfo + + A listed CDC stream, including its name, stable stream ID, registered + key range, and durable minimum required version. + +.. type:: FDBCdcMutation + + One raw mutation within a CDC commit-version group. + +.. type:: FDBCdcVersionedMutations + + A complete group of mutations with one FoundationDB commit version. A + consume reply contains only complete groups; callers should preserve this + grouping when processing a reply. + +.. type:: FDBCdcConsumer + + An opaque, reference-counted CDC consumer handle. A handle extracted + with :func:`fdb_future_get_cdc_consumer()` is owned by the caller and + remains valid after the originating future is destroyed. Destroy it exactly + once with :func:`fdb_cdc_consumer_destroy()`. + +.. function:: FDBFuture* fdb_database_register_cdc_stream(FDBDatabase* database, uint8_t const* name, int name_length, uint8_t const* begin_key, int begin_key_length, uint8_t const* end_key, int end_key_length) + + Registers ``name`` for the non-empty half-open range ``[begin_key, + end_key)`` in normal user key space. Repeating the same name and range is + idempotent; reusing a name with a different range fails. The future returns + the ``uint64_t`` stream ID, extracted with :func:`fdb_future_get_uint64()`. + +.. function:: FDBFuture* fdb_database_remove_cdc_stream(FDBDatabase* database, uint8_t const* name, int name_length) + + Removes the named stream and relinquishes its unread history. Removing a + missing name succeeds. The returned future contains no value. + +.. function:: FDBFuture* fdb_database_list_cdc_streams(FDBDatabase* database) + + Returns the currently registered CDC streams. Extract the result with + :func:`fdb_future_get_cdc_stream_info_array()`. + +.. function:: fdb_error_t fdb_future_get_cdc_stream_info_array(FDBFuture* future, FDBCdcStreamInfo const** out_streams, int* out_count) + + Extracts the stream-info array returned by + :func:`fdb_database_list_cdc_streams()`. |future-get-return1| + |future-get-return2|. + + |future-memory-mine| + +.. function:: FDBFuture* fdb_database_create_cdc_consumer(FDBDatabase* database, uint8_t const* name, int name_length) + + Creates a consumer for an existing stream name at its initial position. + Extract the returned handle with + :func:`fdb_future_get_cdc_consumer()`. + +.. function:: FDBFuture* fdb_database_resume_cdc_consumer(FDBDatabase* database, uint64_t stream_id, int64_t last_consumed_version) + + Constructs a local consumer handle from a checkpointed cursor. A cursor is + only the stable ``stream_id`` and the version through which the caller has + consumed; it does not contain process-local state. This call does not + validate the stream ID or cursor; those checks occur when the handle + consumes or acknowledges. Resume from the last durably processed and + acknowledged position because unacknowledged mutations may be redelivered + after CDC proxy replacement. + +.. function:: fdb_error_t fdb_future_get_cdc_consumer(FDBFuture* future, FDBCdcConsumer** out_consumer) + + Extracts the owned consumer handle returned by + :func:`fdb_database_create_cdc_consumer()` or + :func:`fdb_database_resume_cdc_consumer()`. |future-get-return1| + |future-get-return2|. + +.. function:: void fdb_cdc_consumer_destroy(FDBCdcConsumer* consumer) + + Releases an owned CDC consumer handle. + +.. function:: FDBFuture* fdb_cdc_consumer_consume(FDBCdcConsumer* consumer) + + Long-polls for the next delivered position and complete commit-version + mutation groups. Extract the reply with + :func:`fdb_future_get_cdc_versioned_mutations()`. Consumption advances + the in-memory consumer position but does not release durable CDC retention. + +.. function:: fdb_error_t fdb_future_get_cdc_versioned_mutations(FDBFuture* future, FDBCdcVersionedMutations const** out_mutations, int* out_count, int64_t* out_last_consumed_version) + + Extracts the grouped mutation reply from + :func:`fdb_cdc_consumer_consume()`. ``out_last_consumed_version`` is + the delivered cursor after the reply and may advance across commit-version + gaps that contain no returned mutation. |future-get-return1| + |future-get-return2|. + + The returned groups, mutation arrays, and parameter bytes are owned by + ``future`` and remain valid until :func:`fdb_future_destroy()` or + :func:`fdb_future_release_memory()` is called. + +.. function:: FDBFuture* fdb_cdc_consumer_acknowledge(FDBCdcConsumer* consumer) + + Durably acknowledges the consumer's current delivered position. Call this + only after all mutations represented through that position have been durably + processed. Acknowledgement advances a frontier shared by the stream, not one + private to the handle, so a stream may have only one active logical + consumer. Independently, a consumer handle may have only one consume or + acknowledge operation outstanding at a time. The returned future contains + no value. + +.. function:: fdb_error_t fdb_cdc_consumer_get_position(FDBCdcConsumer* consumer, uint64_t* out_stream_id, int64_t* out_last_consumed_version) + + Returns the consumer's current cursor. + Transaction =========== 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/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..d115fdab83 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" @@ -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/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/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/CMakeLists.txt b/fdbclient/CMakeLists.txt index 9180f29ad0..1444da8399 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 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/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/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/MonitorLeader.cpp b/fdbclient/MonitorLeader.cpp index 8ac072e743..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) { @@ -532,36 +534,44 @@ 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::strong_ordering operator<=>(MaskedNominee const&) const = default; + }; + + 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()); int bestCount = 1; int bestIdx = 0; int currentIdx = 0; int curCount = 1; for (int i = 1; i < maskedNominees.size(); i++) { - if (maskedNominees[currentIdx].first == maskedNominees[i].first) { + if (maskedNominees[currentIdx].maskedChangeID == maskedNominees[i].maskedChangeID) { curCount++; } else { currentIdx = i; @@ -573,8 +583,84 @@ Optional> 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 diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 6c2c77f31c..d05ce28881 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -392,6 +392,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.streamId = self->getPosition().streamId; + 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(); @@ -457,6 +554,80 @@ ThreadFuture DLDatabase::createSnapshot(const StringRef& uid, const String return toThreadFuture(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { return Void(); }); } +ThreadFuture DLDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) { + if (!api->databaseRegisterNativeCdcStream) { + return unsupported_operation(); + } + + FdbCApi::FDBFuture* f = api->databaseRegisterNativeCdcStream( + db, name.begin(), name.size(), keys.begin.begin(), keys.begin.size(), keys.end.begin(), keys.end.size()); + return toThreadFuture(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(const 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(const 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(const 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(); @@ -605,6 +776,51 @@ void DLApi::init() { fdbCPath, "fdb_database_get_client_status", headerVersion >= ApiVersion::withGetClientStatus().version()); + loadClientFunction(&api->databaseRegisterNativeCdcStream, + lib, + fdbCPath, + "fdb_database_register_cdc_stream", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseRemoveNativeCdcStream, + lib, + fdbCPath, + "fdb_database_remove_cdc_stream", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseListNativeCdcStreams, + lib, + fdbCPath, + "fdb_database_list_cdc_streams", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseCreateNativeCdcConsumer, + lib, + fdbCPath, + "fdb_database_create_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->databaseResumeNativeCdcConsumer, + lib, + fdbCPath, + "fdb_database_resume_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerDestroy, + lib, + fdbCPath, + "fdb_cdc_consumer_destroy", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerConsume, + lib, + fdbCPath, + "fdb_cdc_consumer_consume", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerAcknowledge, + lib, + fdbCPath, + "fdb_cdc_consumer_acknowledge", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->nativeCdcConsumerGetPosition, + lib, + fdbCPath, + "fdb_cdc_consumer_get_position", + headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->transactionSetOption, lib, fdbCPath, "fdb_transaction_set_option", headerVersion >= 0); loadClientFunction(&api->transactionDestroy, lib, fdbCPath, "fdb_transaction_destroy", headerVersion >= 0); @@ -698,6 +914,21 @@ void DLApi::init() { &api->futureGetKeyValueArray, lib, fdbCPath, "fdb_future_get_keyvalue_array", headerVersion >= 0); loadClientFunction( &api->futureGetMappedKeyValueArray, lib, fdbCPath, "fdb_future_get_mappedkeyvalue_array", headerVersion >= 710); + loadClientFunction(&api->futureGetNativeCdcStreamInfoArray, + lib, + fdbCPath, + "fdb_future_get_cdc_stream_info_array", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->futureGetNativeCdcConsumer, + lib, + fdbCPath, + "fdb_future_get_cdc_consumer", + headerVersion >= ApiVersion::withNativeCdcApi().version()); + loadClientFunction(&api->futureGetNativeCdcVersionedMutations, + lib, + fdbCPath, + "fdb_future_get_cdc_versioned_mutations", + headerVersion >= ApiVersion::withNativeCdcApi().version()); loadClientFunction(&api->futureGetSharedState, lib, fdbCPath, "fdb_future_get_shared_state", headerVersion >= 710); loadClientFunction(&api->futureSetCallback, lib, fdbCPath, "fdb_future_set_callback", headerVersion >= 0); loadClientFunction(&api->futureCancel, lib, fdbCPath, "fdb_future_cancel", headerVersion >= 0); @@ -1448,6 +1679,27 @@ ThreadFuture MultiVersionDatabase::createSnapshot(const StringRef& uid, co return executeOperation(&IDatabase::createSnapshot, uid, snapshot_command); } +ThreadFuture MultiVersionDatabase::registerNativeCdcStream(const KeyRef& name, const KeyRangeRef& keys) { + return executeOperation(&IDatabase::registerNativeCdcStream, name, keys); +} + +ThreadFuture MultiVersionDatabase::removeNativeCdcStream(const KeyRef& name) { + return executeOperation(&IDatabase::removeNativeCdcStream, name); +} + +ThreadFuture> MultiVersionDatabase::listNativeCdcStreams() { + return executeOperation(&IDatabase::listNativeCdcStreams); +} + +ThreadFuture> MultiVersionDatabase::createNativeCdcConsumer(const KeyRef& name) { + return executeOperation(&IDatabase::createNativeCdcConsumer, name); +} + +ThreadFuture> MultiVersionDatabase::resumeNativeCdcConsumer( + const NativeCdcCursor& cursor) { + return executeOperation(&IDatabase::resumeNativeCdcConsumer, cursor); +} + ThreadFuture MultiVersionDatabase::createSharedState() { return executeOperation(&IDatabase::createSharedState); } 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/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/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..2ca8a70002 --- /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; } + + // Really this should return an iterator by value, but for performance it's convenient to actually grab the internal + // one. Consider copying the return value if performance isn't critical. If you modify the returned iterator, it + // invalidates this iterator until the next call to skip() + virtual WriteMap::iterator& extractWriteMapIterator(); + + void dbg(); + +protected: + int begin_key_cmp; // -1 if cache.beginKey() < writes.beginKey(), 0 if ==, +1 if > + int end_key_cmp; // + SnapshotCache::iterator cache; + WriteMap::iterator writes; + KeyValueRef temp; + bool bypassUnreadable; // When set, allows read from sections of keyspace that have become unreadable because of + // versionstamp operations + + void updateCmp(); +}; + +void testESR(); +void testSnapshotCache(); + +#endif diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index d7bbdb697c..04e43872c8 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(); } @@ -240,7 +248,7 @@ public: } it.skip(readRange.begin); - ryw->updateConflictMap(readRange, it); + updateConflictMap(ryw, readRange, it); } template @@ -360,7 +368,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()); } @@ -368,7 +376,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 @@ -747,7 +755,7 @@ public: //TraceEvent("RYWCacheInsert", randomID).detail("Range", range).detail("ExpectedSize", snapshot_read.expectedSize()).detail("Rows", snapshot_read.size()).detail("Results", snapshot_read).detail("More", snapshot_read.more).detail("ReadToBegin", snapshot_read.readToBegin).detail("ReadThroughEnd", snapshot_read.readThroughEnd).detail("ReadThrough", snapshot_read.readThrough); - if (ryw->cache.insert(range, snapshot_read)) + if (ryw->rywState->cache.insert(range, snapshot_read)) ryw->arena.dependsOn(snapshot_read.arena()); // TODO: Is there a more efficient way to deal with invalidation? @@ -1061,7 +1069,7 @@ public: reversed[snapshot_read.size() - i - 1] = snapshot_read[i]; } - if (ryw->cache.insert(range, reversed)) + if (ryw->rywState->cache.insert(range, reversed)) ryw->arena.dependsOn(snapshot_read.arena()); // TODO: Is there a more efficient way to deal with invalidation? @@ -1191,7 +1199,7 @@ public: // Insert read conflicts (so that it supported Snapshot::True) and check it is not modified (so it masks // sure not break RYW semantic while not implementing RYW) for both the primary getRange and all // underlying getValue/getRanges. - WriteMap::iterator writes(&ryw->writes); + WriteMap::iterator writes(&ryw->rywState->writes); addConflictRangeAndMustUnmodified(ryw, req, writes, result); co_return result; } @@ -1529,9 +1537,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)); @@ -1889,22 +1899,14 @@ void ReadYourWritesTransaction::addReadConflictRange(KeyRangeRef const& keys) { return; } - WriteMap::iterator it(&writes); + WriteMap::iterator it(&rywState->writes); KeyRangeRef readRange(arena, r); it.skip(readRange.begin); - updateConflictMap(readRange, it); -} - -void ReadYourWritesTransaction::updateConflictMap(KeyRef const& key, WriteMap::iterator& it) { - RYWImpl::updateConflictMap(this, key, it); -} - -void ReadYourWritesTransaction::updateConflictMap(KeyRangeRef const& keys, WriteMap::iterator& it) { - RYWImpl::updateConflictMap(this, keys, it); + RYWImpl::updateConflictMap(this, readRange, it); } void ReadYourWritesTransaction::writeRangeToNativeTransaction(KeyRangeRef const& keys) { - WriteMap::iterator it(&writes); + WriteMap::iterator it(&rywState->writes); it.skip(keys.begin); bool inClearRange = false; @@ -1998,7 +2000,7 @@ bool ReadYourWritesTransactionOptions::getAndResetWriteConflictDisabled() { } void ReadYourWritesTransaction::getWriteConflicts(KeyRangeMap* result) { - WriteMap::iterator it(&writes); + WriteMap::iterator it(&rywState->writes); it.skip(allKeys.begin); bool inConflictRange = false; @@ -2078,7 +2080,7 @@ RangeResult ReadYourWritesTransaction::getWriteConflictRangeIntersecting(KeyRang if (!options.readYourWritesDisabled) { KeyRangeRef strippedWriteRangePrefix = kr.removePrefix(writeConflictRangeKeysRange.begin); - WriteMap::iterator it(&writes); + WriteMap::iterator it(&rywState->writes); it.skip(strippedWriteRangePrefix.begin); if (it.beginKey() > allKeys.begin) --it; @@ -2179,7 +2181,7 @@ void ReadYourWritesTransaction::atomicOp(const KeyRef& key, const ValueRef& oper addWriteConflict = AddConflictRange::False; if (!options.readYourWritesDisabled) { writeRangeToNativeTransaction(range); - writes.addUnmodifiedAndUnreadableRange(range); + rywState->writes.addUnmodifiedAndUnreadableRange(range); } // k is the unversionstamped key provided by the user. If we've filled in a minimum bound // for the versionstamp, we need to make sure that's reflected when we insert it into the @@ -2203,7 +2205,7 @@ void ReadYourWritesTransaction::atomicOp(const KeyRef& key, const ValueRef& oper return tr.atomicOp(k, v, (MutationRef::Type)operationType, addWriteConflict); } - writes.mutate(k, (MutationRef::Type)operationType, v, addWriteConflict); + rywState->writes.mutate(k, (MutationRef::Type)operationType, v, addWriteConflict); RYWImpl::triggerWatches(this, k, Optional(), false); } @@ -2261,7 +2263,7 @@ void ReadYourWritesTransaction::set(const KeyRef& key, const ValueRef& value) { KeyRef k = KeyRef(arena, key); ValueRef v = ValueRef(arena, value); - writes.mutate(k, MutationRef::SetValue, v, addWriteConflict); + rywState->writes.mutate(k, MutationRef::SetValue, v, addWriteConflict); RYWImpl::triggerWatches(this, key, value); } @@ -2310,7 +2312,7 @@ void ReadYourWritesTransaction::clear(const KeyRangeRef& range) { r = KeyRangeRef(arena, r); - writes.clear(r, addWriteConflict); + rywState->writes.clear(r, addWriteConflict); RYWImpl::triggerWatches(this, r, Optional()); } @@ -2343,7 +2345,7 @@ void ReadYourWritesTransaction::clear(const KeyRef& key) { r.expectedSize() + sizeof(KeyRangeRef) + (addWriteConflict ? sizeof(KeyRangeRef) + r.expectedSize() : 0); // SOMEDAY: add an optimized single key clear to write map - writes.clear(r, addWriteConflict); + rywState->writes.clear(r, addWriteConflict); RYWImpl::triggerWatches(this, r, Optional()); } @@ -2407,7 +2409,7 @@ void ReadYourWritesTransaction::addWriteConflictRange(KeyRangeRef const& keys) { } r = KeyRangeRef(arena, r); - writes.addConflictRange(r); + rywState->writes.addConflictRange(r); } Future ReadYourWritesTransaction::commit() { @@ -2448,7 +2450,7 @@ void ReadYourWritesTransaction::setOptionImpl(FDBTransactionOptions::Option opti case FDBTransactionOptions::READ_YOUR_WRITES_DISABLE: validateOptionValueNotPresent(value); - if (reading.getFutureCount() > 0 || !cache.empty() || !writes.empty()) + if (reading.getFutureCount() > 0 || !rywState->cache.empty() || !rywState->writes.empty()) throw client_invalid_operation(); options.readYourWritesDisabled = true; @@ -2534,8 +2536,7 @@ void ReadYourWritesTransaction::setOptionImpl(FDBTransactionOptions::Option opti } void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcept { - cache = std::move(r.cache); - writes = std::move(r.writes); + rywState = std::move(r.rywState); arena = std::move(r.arena); tr = std::move(r.tr); readConflicts = std::move(r.readConflicts); @@ -2551,8 +2552,8 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep commitStarted = r.commitStarted; options = r.options; transactionDebugInfo = r.transactionDebugInfo; - cache.arena = &arena; - writes.arena = &arena; + rywState->cache.arena = &arena; + rywState->writes.arena = &arena; persistentOptions = std::move(r.persistentOptions); sensitivePersistentOptions = std::move(r.sensitivePersistentOptions); nativeReadRanges = std::move(r.nativeReadRanges); @@ -2564,12 +2565,12 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep } ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept - : deferredError(r.deferredError), arena(std::move(r.arena)), cache(std::move(r.cache)), writes(std::move(r.writes)), + : deferredError(r.deferredError), arena(std::move(r.arena)), rywState(std::move(r.rywState)), resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), retries(r.retries), approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), creationTime(r.creationTime), commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), options(r.options) { - cache.arena = &arena; - writes.arena = &arena; + rywState->cache.arena = &arena; + rywState->writes.arena = &arena; tr = std::move(r.tr); readConflicts = std::move(r.readConflicts); watchMap = std::move(r.watchMap); @@ -2615,8 +2616,8 @@ void ReadYourWritesTransaction::resetRyow() { timeoutActor.cancel(); arena = Arena(); - cache = SnapshotCache(&arena); - writes = WriteMap(&arena); + rywState->cache = SnapshotCache(&arena); + rywState->writes = WriteMap(&arena); readConflicts = CoalescedKeyRefRangeMap(); versionStampKeys = VectorRef(); nativeReadRanges = Standalone>(); 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/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/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index 1f009a234e..dfed06ed17 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(const KeyRef& name, const 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(const 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(const 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(const 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/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/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/IClientApi.h b/fdbclient/include/fdbclient/IClientApi.h index b5e47ddb47..c669966346 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.h" @@ -153,6 +154,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(const KeyRef& name, const KeyRangeRef& keys) = 0; + virtual ThreadFuture removeNativeCdcStream(const KeyRef& name) = 0; + virtual ThreadFuture> listNativeCdcStreams() = 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; virtual void setSharedState(DatabaseSharedState* p) = 0; 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; diff --git a/fdbclient/include/fdbclient/MultiVersionTransaction.h b/fdbclient/include/fdbclient/MultiVersionTransaction.h index 2530d9a72e..084a5697af 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, @@ -257,6 +299,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); @@ -383,6 +433,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(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; + ThreadFuture> listNativeCdcStreams() override; + ThreadFuture> createNativeCdcConsumer(const KeyRef& name) override; + ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override; ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; @@ -696,6 +751,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(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; + ThreadFuture> listNativeCdcStreams() 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/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..7b166bfd6f --- /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.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/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 8d0d00b3d0..f9c33c14cd 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/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 8d5842cf81..fca3803616 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(const KeyRef& name, const KeyRangeRef& keys) override; + ThreadFuture removeNativeCdcStream(const KeyRef& name) override; + ThreadFuture> listNativeCdcStreams() override; + ThreadFuture> createNativeCdcConsumer(const KeyRef& name) override; + ThreadFuture> resumeNativeCdcConsumer(const NativeCdcCursor& cursor) override; + ThreadFuture createSharedState() override; void setSharedState(DatabaseSharedState* p) override; 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/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/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/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..ece5deffb3 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. @@ -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/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/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 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..2d3cdd49da 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" @@ -124,6 +124,8 @@ ClusterControllerData::ClusterControllerData(ClusterControllerFullInterface cons serverInfo.masterLifetime.ccID = id; serverInfo.clusterInterface = ccInterface; serverInfo.myLocality = locality; + serverInfo.client.nativeCdcEnabled = CLIENT_KNOBS->ENABLE_NATIVE_CDC; + serverInfo.client.nativeCdcTagCount = CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT; db.serverInfo->set(serverInfo); cx = openDBOnServer(db.serverInfo, TaskPriority::DefaultEndpoint, LockAware::True); @@ -2383,60 +2385,56 @@ Future 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 { + changeDelay = Never(); + onChange = self->changingDcIds.onChange(); - self->changedDcIds.set(self->changingDcIds.get()); - if (self->changedDcIds.get().second.present()) { - TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); - if (!self->changedDcIds.get().first) { - auto& worker = self->id_worker[self->clusterControllerProcessId]; - uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( - worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); - if (worker.priorityInfo.dcFitness != newFitness) { - worker.priorityInfo.dcFitness = newFitness; - if (!worker.reply.isSet()) { - worker.reply.send( - RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); - } + self->changedDcIds.set(self->changingDcIds.get()); + if (self->changedDcIds.get().second.present()) { + TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); + if (!self->changedDcIds.get().first) { + auto& worker = self->id_worker[self->clusterControllerProcessId]; + uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( + worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (worker.priorityInfo.dcFitness != newFitness) { + worker.priorityInfo.dcFitness = newFitness; + if (!worker.reply.isSet()) { + worker.reply.send(RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); } - } else { - state int currentFit = recruitment::BestFit; - while (currentFit <= recruitment::NeverAssign) { - bool updated = false; - for (auto& it : self->id_worker) { - if ((!it.second.priorityInfo.isExcluded && - it.second.priorityInfo.processClassFitness == currentFit) || - currentFit == recruitment::NeverAssign) { - uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( - it.second.details.interf.locality.dcId(), - self->changedDcIds.get().second.get()); - if (it.first != self->clusterControllerProcessId && - it.second.priorityInfo.dcFitness != fitness) { - updated = true; - it.second.priorityInfo.dcFitness = fitness; - if (!it.second.reply.isSet()) { - it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, - it.second.priorityInfo)); - } + } + } else { + int currentFit = recruitment::BestFit; + while (currentFit <= recruitment::NeverAssign) { + bool updated = false; + for (auto& it : self->id_worker) { + if ((!it.second.priorityInfo.isExcluded && + it.second.priorityInfo.processClassFitness == currentFit) || + currentFit == recruitment::NeverAssign) { + uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( + it.second.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (it.first != self->clusterControllerProcessId && + it.second.priorityInfo.dcFitness != fitness) { + updated = true; + it.second.priorityInfo.dcFitness = fitness; + if (!it.second.reply.isSet()) { + it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, + it.second.priorityInfo)); } } } - if (updated && currentFit < recruitment::NeverAssign) { - wait(delay(SERVER_KNOBS->CC_CLASS_DELAY)); - } - currentFit++; } + if (updated && currentFit < recruitment::NeverAssign) { + co_await delay(SERVER_KNOBS->CC_CLASS_DELAY); + } + currentFit++; } } } @@ -2806,13 +2804,13 @@ Future 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()) @@ -2820,18 +2818,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()); } } } @@ -2904,26 +2901,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()); } } } @@ -2996,28 +2992,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); } } } 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..65fb87861a 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" @@ -701,8 +701,26 @@ 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 worker reboot time before making an irreversible region change. + TraceEvent("ConfirmingDeadDatacenter") + .detail("Location", context) + .detail("PrimaryDead", primaryDead) + .detail("RemoteDead", remoteDead) + .detail("Delay", SERVER_KNOBS->MAX_REBOOT_TIME); + co_await delay(SERVER_KNOBS->MAX_REBOOT_TIME); + if (simPolicy.usableRegions <= 1 || simPolicy.quiesced || simPolicy.primaryDcId != primaryDcId || + simPolicy.remoteDcId != remoteDcId) { + co_return; + } + primaryDead = primaryDead && g_simulator->datacenterDead(simPolicy.primaryDcId); + remoteDead = remoteDead && g_simulator->datacenterDead(simPolicy.remoteDcId); + } // FIXME: the primary and remote can both be considered dead because excludes are not handled properly by the // datacenterDead function 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 95% rename from fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h rename to fdbserver/core/include/fdbserver/core/WorkerInterface.h index 73d0cbb982..428ad32126 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 * @@ -19,11 +19,6 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_WORKERINTERFACE_ACTOR_G_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_G_H -#include "fdbserver/core/WorkerInterface.actor.g.h" -#elif !defined(FDBSERVER_WORKERINTERFACE_ACTOR_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_H #include "fdbserver/core/BackupInterface.h" #include "fdbserver/core/DataDistributorInterface.h" @@ -43,7 +38,7 @@ #include "fdbrpc/MultiInterface.h" #include "fdbclient/ClientWorkerInterface.h" #include "fdbserver/core/RecoveryState.h" -#include "flow/actorcompiler.h" +#include "flow/CoroUtils.h" struct WorkerInterface { constexpr static FileIdentifier file_identifier = 14712718; @@ -83,7 +78,7 @@ struct WorkerInterface { NetworkAddressList addresses() const { return tLog.getEndpoint().addresses; } Optional grpcAddress() const { return clientInterface.grpcAddress; } - WorkerInterface() {} + WorkerInterface() = default; explicit(false) WorkerInterface(const LocalityData& locality) : locality(locality) {} void initEndpoints() { @@ -371,7 +366,7 @@ struct RecruitFromConfigurationRequest { int maxOldLogRouters; ReplyPromise reply; - RecruitFromConfigurationRequest() {} + RecruitFromConfigurationRequest() = default; explicit RecruitFromConfigurationRequest(DatabaseConfiguration const& configuration, bool recruitSeedServers, int maxOldLogRouters) @@ -404,7 +399,7 @@ struct RecruitRemoteFromConfigurationRequest { Optional dbgId; ReplyPromise reply; - RecruitRemoteFromConfigurationRequest() {} + RecruitRemoteFromConfigurationRequest() = default; RecruitRemoteFromConfigurationRequest(DatabaseConfiguration const& configuration, Optional const& dcId, int logRouterCount, @@ -550,7 +545,7 @@ struct TLogRejoinRequest { TLogInterface myInterface; ReplyPromise reply; - TLogRejoinRequest() {} + TLogRejoinRequest() = default; explicit TLogRejoinRequest(const TLogInterface& interf) : myInterface(interf) {} template void serialize(Ar& ar) { @@ -591,7 +586,7 @@ struct GetEncryptionAtRestModeRequest { UID tlogId; ReplyPromise reply; - GetEncryptionAtRestModeRequest() {} + GetEncryptionAtRestModeRequest() = default; explicit(false) GetEncryptionAtRestModeRequest(UID tId) : tlogId(tId) {} template @@ -846,7 +841,7 @@ struct InitializeDataDistributorRequest { UID reqId; ReplyPromise reply; - InitializeDataDistributorRequest() {} + InitializeDataDistributorRequest() = default; explicit InitializeDataDistributorRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -859,7 +854,7 @@ struct InitializeRatekeeperRequest { UID reqId; ReplyPromise reply; - InitializeRatekeeperRequest() {} + InitializeRatekeeperRequest() = default; explicit InitializeRatekeeperRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -872,7 +867,7 @@ struct InitializeConsistencyScanRequest { UID reqId; ReplyPromise reply; - InitializeConsistencyScanRequest() {} + InitializeConsistencyScanRequest() = default; explicit InitializeConsistencyScanRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -1044,7 +1039,7 @@ struct DebugEntryRef { StringRef context; Version version; MutationRef mutation; - DebugEntryRef() {} + DebugEntryRef() = default; DebugEntryRef(const char* c, Version v, MutationRef const& m) : time(now()), address(g_network->getLocalAddress()), context((const uint8_t*)c, strlen(c)), version(v), mutation(m) {} @@ -1174,42 +1169,44 @@ bool addressInDbAndRemoteDc( extern bool isSimulatorProcessUnreliable(); -ACTOR template -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 diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index cfaed3b488..e3d40347df 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; @@ -82,14 +77,19 @@ RelocateData::RelocateData() RelocateData::RelocateData(RelocateShard const& rs) : parent_range(rs.getParentRange()), keys(rs.keys), priority(rs.priority), - boundaryPriority(isBoundaryPriority(rs.priority) ? rs.priority : -1), - healthPriority(isHealthPriority(rs.priority) ? rs.priority : -1), reason(rs.reason), dmReason(rs.moveReason), - startTime(now()), randomId(rs.traceId.isValid() ? rs.traceId : deterministicRandom()->randomUniqueID()), - dataMoveId(rs.dataMoveId), workFactor(0), - wantsNewServers(isDataMovementForMountainChopper(rs.moveReason) || isDataMovementForValleyFiller(rs.moveReason) || - rs.moveReason == DataMovementReason::SPLIT_SHARD || - rs.moveReason == DataMovementReason::TEAM_REDUNDANT || - rs.moveReason == DataMovementReason::REBALANCE_STORAGE_QUEUE), + boundaryPriority(rs.retryIntent.present() ? rs.retryIntent.get().boundaryPriority + : (isBoundaryPriority(rs.priority) ? rs.priority : -1)), + healthPriority(rs.retryIntent.present() ? rs.retryIntent.get().healthPriority + : (isHealthPriority(rs.priority) ? rs.priority : -1)), + reason(rs.reason), dmReason(rs.moveReason), startTime(now()), + randomId(rs.traceId.isValid() ? rs.traceId : deterministicRandom()->randomUniqueID()), dataMoveId(rs.dataMoveId), + workFactor(0), + wantsNewServers(rs.retryIntent.present() ? rs.retryIntent.get().wantsNewServers + : (isDataMovementForMountainChopper(rs.moveReason) || + isDataMovementForValleyFiller(rs.moveReason) || + rs.moveReason == DataMovementReason::SPLIT_SHARD || + rs.moveReason == DataMovementReason::TEAM_REDUNDANT || + rs.moveReason == DataMovementReason::REBALANCE_STORAGE_QUEUE)), cancellable(true), interval("QueuedRelocation", randomId), dataMove(rs.dataMove) { if (dataMove != nullptr) { this->src.insert(this->src.end(), dataMove->meta.src.begin(), dataMove->meta.src.end()); @@ -129,6 +129,26 @@ Optional RelocateData::getParentRange() const { return parent_range; } +static RelocateShard makeDestinationFailureRetry(RelocateData const& rd, UID retryTraceId) { + RelocateShard retry(rd.keys, rd.dmReason, rd.reason, retryTraceId); + retry.priority = rd.priority; + retry.retryIntent = + RelocateShard::RetryRelocationIntent{ rd.boundaryPriority, rd.healthPriority, rd.wantsNewServers }; + if (rd.getParentRange().present()) { + retry.setParentRange(rd.getParentRange().get()); + } + return retry; +} + +static bool shouldRetryDestinationTeamFailure(bool doBulkLoading, RelocateData const&) { + return !doBulkLoading; +} + +static bool shouldYieldDestinationFailureRetry(RelocateData const& retry, RelocateData const& queued) { + bool isSplit = retry.reason == RelocateReason::SIZE_SPLIT || retry.reason == RelocateReason::WRITE_SPLIT; + return isSplit && retry.keys != queued.keys && retry.keys.contains(queued.keys); +} + class ParallelTCInfo final : public ReferenceCounted, public IDataDistributionTeam { std::vector> teams; std::vector tempServerIDs; @@ -808,7 +828,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); @@ -1082,6 +1126,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 @@ -1497,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; @@ -2240,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(); } } @@ -2425,6 +2476,37 @@ 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()); + self->output.send(retry); + TraceEvent(SevWarnAlways, "RelocateShardRetryDestinationTeamFailure", self->distributorId) + .detail("Range", rd.keys) + .detail("DataMoveID", rd.dataMoveId) + .detail("Priority", rd.priority) + .detail("Reason", rd.reason.toString()) + .detail("TraceID", retry.traceId) + .detail("PreviousTraceID", rd.randomId); + } + relocationComplete.send(rd); if (doBulkLoading && err.code() != error_code_actor_cancelled && err.code() != error_code_movekeys_conflict) { @@ -3233,6 +3315,61 @@ TEST_CASE("/DataDistribution/DDQueue/BatchDrainRelocationComplete") { std::cout << "BatchDrainRelocationComplete: drained " << drained << " of " << N << " completions\n"; } +TEST_CASE("/DataDistribution/DDQueue/RetryDestinationTeamFailure") { + KeyRange keys(KeyRangeRef("a"_sr, "b"_sr)); + KeyRange parent(KeyRangeRef(""_sr, "z"_sr)); + RelocateShard original(keys, DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT, UID(1, 2)); + original.setParentRange(parent); + RelocateData rd(original); + rd.priority = SERVER_KNOBS->PRIORITY_TEAM_UNHEALTHY; + rd.boundaryPriority = SERVER_KNOBS->PRIORITY_SPLIT_SHARD; + rd.healthPriority = SERVER_KNOBS->PRIORITY_TEAM_UNHEALTHY; + rd.wantsNewServers = true; + + RelocateShard retryRequest = makeDestinationFailureRetry(rd, UID(3, 4)); + RelocateData retry(retryRequest); + ASSERT(retry.keys == keys); + ASSERT(retry.priority == rd.priority); + ASSERT(retry.boundaryPriority == rd.boundaryPriority); + ASSERT(retry.healthPriority == rd.healthPriority); + ASSERT(retry.reason == rd.reason); + ASSERT(retry.dmReason == rd.dmReason); + ASSERT(retry.wantsNewServers == rd.wantsNewServers); + ASSERT(retry.randomId == UID(3, 4)); + ASSERT(retry.randomId != rd.randomId); + retry.startTime = rd.startTime; + std::set> relocations; + relocations.insert(retry); + ASSERT(!relocations.contains(rd)); + relocations.insert(rd); + ASSERT(relocations.size() == 2); + ASSERT(retry.getParentRange().present()); + ASSERT(retry.getParentRange().get() == parent); + ASSERT(retry.src.empty()); + ASSERT(retry.completeSources.empty()); + ASSERT(retry.completeDests.empty()); + ASSERT(retry.workFactor == 0); + ASSERT(retry.cancellable); + ASSERT(retry.dataMove == nullptr); + ASSERT(retry.dataMoveId == anonymousShardId); + ASSERT(!retry.isRestore()); + ASSERT(!retry.bulkLoadTask.present()); + ASSERT(shouldRetryDestinationTeamFailure(false, rd)); + ASSERT(!shouldRetryDestinationTeamFailure(true, rd)); + RelocateData nested( + RelocateShard(KeyRangeRef("a"_sr, "aa"_sr), DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT)); + ASSERT(shouldYieldDestinationFailureRetry(retry, nested)); + ASSERT(!shouldYieldDestinationFailureRetry(retry, retry)); + RelocateData unrelated( + RelocateShard(KeyRangeRef("c"_sr, "d"_sr), DataMovementReason::SPLIT_SHARD, RelocateReason::SIZE_SPLIT)); + ASSERT(!shouldYieldDestinationFailureRetry(retry, unrelated)); + RelocateData restore = rd; + restore.dataMove = std::make_shared(); + ASSERT(shouldRetryDestinationTeamFailure(false, restore)); + ASSERT(!shouldRetryDestinationTeamFailure(true, restore)); + return Void(); +} + TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { Reference self = makeReference(); DDQueueImpl::RunState state(self); 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/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 {}; } 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 78353268f1..856b49a9e1 100644 --- a/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp +++ b/fdbserver/datadistributor/ShardsAffectedByTeamFailureTests.cpp @@ -164,3 +164,67 @@ TEST_CASE("/DataDistributor/ShardsAffectedByTeamFailure/RetryMergedShardAfterPar return Void(); } + +TEST_CASE("/DataDistributor/ShardsAffectedByTeamFailure/CancelMove") { + ShardsAffectedByTeamFailure shards; + shards.setCheckMode(ShardsAffectedByTeamFailure::CheckMode::ForceCheck); + + const UID source1(1, 0), source2(2, 0), destination1(3, 0), destination2(4, 0), redirected1(5, 0), + redirected2(6, 0); + const ShardsAffectedByTeamFailure::Team source({ source1, source2 }, true); + const ShardsAffectedByTeamFailure::Team destination({ destination1, destination2 }, true); + const ShardsAffectedByTeamFailure::Team redirected({ redirected1, redirected2 }, true); + const KeyRange moveRange = KeyRangeRef("a"_sr, "c"_sr); + const KeyRange leftRange = KeyRangeRef("a"_sr, "b"_sr); + const KeyRange rightRange = KeyRangeRef("b"_sr, "c"_sr); + + shards.assignRangeToTeams(allKeys, { source }); + shards.defineShard(moveRange); + shards.moveShard(moveRange, { destination }); + shards.defineShard(leftRange); + + auto restored = shards.cancelMove(moveRange, { destination }, { source }); + ASSERT((restored == std::vector{ 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(); +} diff --git a/fdbserver/fdbserver.cpp b/fdbserver/fdbserver.cpp index 70a9689740..f969c577c4 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 @@ -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/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/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/KeyValueStoreShardedRocksDB.cpp b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp index 80ed55e4eb..ca9d88fb41 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 = joinPath(params.getDataDir(), "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/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..4d691100b1 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" @@ -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/logrouter/LogRouter.cpp b/fdbserver/logrouter/LogRouter.cpp index 10cc18e958..e33681e9f3 100644 --- a/fdbserver/logrouter/LogRouter.cpp +++ b/fdbserver/logrouter/LogRouter.cpp @@ -21,10 +21,11 @@ #include "fdbrpc/Stats.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/logsystem/LogSystem.h" +#include "fdbserver/logsystem/LogSet.h" #include "fdbserver/logsystem/LogSystemConsumer.h" #include "fdbserver/logrouter/LogRouter.h" #include "fdbserver/logsystem/LogSystemFactory.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/RecoveryState.h" #include "fdbserver/core/TLogInterface.h" #include "flow/ActorCollection.h" 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/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/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/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; 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..d0dc5b14ad --- /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.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 c90986872b..01f443cdcf 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -41,7 +41,8 @@ #include "fdbserver/core/OTELSpanContextMessage.h" #include "fdbserver/core/SpanContextMessage.h" #include "fdbserver/core/TLogInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" +#include "fdbserver/logsystem/LogSet.h" #include "flow/Arena.h" #include "flow/Error.h" #include "flow/ActorCollection.h" @@ -54,8 +55,39 @@ struct LogPushData; struct LocalityData; struct LogSystem; struct LogSystemConsumer; -class LogSet; -struct ConnectionResetInfo; + +// Base cursor contract for consuming a sequential log peek stream. +struct IPeekCursor { + virtual void setProtocolVersion(ProtocolVersion version) = 0; + + virtual bool hasMessage() const = 0; + virtual VectorRef 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; @@ -533,8 +565,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), 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/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); 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..57468797da 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" @@ -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) { @@ -12624,7 +12583,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 +12710,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; } 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..3ee3250859 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" @@ -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()); @@ -3903,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); + } } } } @@ -4154,6 +4159,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(); 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 1cb7679ac6..0320eceb3f 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/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index 1832603510..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,12 +45,16 @@ struct GcGenerationsWorkload : TestWorkload { bool enabled; double testDuration; double startDelay; + bool completed = false; + bool forceCloggedDcMasterRetry; std::vector> cloggedPairs; + Optional> cloggedDcId; explicit GcGenerationsWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) { 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 { @@ -64,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. @@ -85,6 +90,7 @@ struct GcGenerationsWorkload : TestWorkload { g_simulator->unclogPair(pair.first, pair.second); } cloggedPairs.clear(); + cloggedDcId.reset(); } Future clogRemoteDc(GcGenerationsWorkload* self, Database cx) { @@ -104,12 +110,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 +142,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,15 +188,23 @@ 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. Force a new master election before retrying. + 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", self->dbInfo->get().master.address()); + .detail("MasterAddr", masterAddr) + .detail("Forced", forcedRetry); + if (masterProc) { + g_simulator->rebootProcess(masterProc, ISimulator::KillType::Reboot); + } continue; } @@ -223,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); @@ -244,13 +271,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); @@ -261,6 +288,7 @@ struct GcGenerationsWorkload : TestWorkload { co_await self->dbInfo->onChange(); } + self->completed = true; TraceEvent("GcGenerationsWorkloadFinish").log(); } }; 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); 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/NativeCdcEndToEnd.cpp b/fdbserver/workloads/NativeCdcEndToEnd.cpp index ca644436ef..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( @@ -455,8 +481,9 @@ class NativeCdcEndToEndWorkload : public TestWorkload { std::vector> halts; halts.reserve(originalProxies.size()); + bool dropFirstHalt = injectUndeliveredProxyHalt; for (const auto& proxy : originalProxies) { - halts.push_back(proxy.haltForTesting.getReply(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/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/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" 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" 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)); } 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") 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()); } } 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 ``` 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 - 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' 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 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' 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 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] 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..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 @@ -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="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" 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 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'