From 854cdb3354a1cf094b5e543d54b1bf80f652e302 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 8 Jun 2026 17:03:53 -0700 Subject: [PATCH 01/39] Keep old-generation TLogs advertised until master recovery restarts --- .../ClusterController.actor.cpp | 1 - .../clustercontroller/ClusterRecovery.cpp | 2 -- fdbserver/logsystem/LogSystem.cpp | 20 ++++++------------- .../include/fdbserver/logsystem/LogSystem.h | 3 +-- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index fff4719691..1496b49aa3 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -1194,7 +1194,6 @@ void clusterRegisterMaster(ClusterControllerData* self, RegisterMasterRequest co if (req.recoveryState == RecoveryState::FULLY_RECOVERED) { self->db.unfinishedRecoveries = 0; - ASSERT(!req.logSystemConfig.oldTLogs.size()); } db->masterRegistrationCount = req.registrationCount; diff --git a/fdbserver/clustercontroller/ClusterRecovery.cpp b/fdbserver/clustercontroller/ClusterRecovery.cpp index 81156a457a..3bbd71cb74 100644 --- a/fdbserver/clustercontroller/ClusterRecovery.cpp +++ b/fdbserver/clustercontroller/ClusterRecovery.cpp @@ -467,8 +467,6 @@ Future trackTlogRecovery(Reference self, configuration.expectedLogSets(!self->primaryDcId.empty() ? self->primaryDcId[0] : Optional())) .detail("RecoveryCount", newState.recoveryCount); co_await self->cstate.write(newState, finalUpdate); - // Purge in memory state after durability to avoid race conditions. - self->logSystem->purgeOldRecoveredGenerationsInMemory(newState); if (self->cstateUpdated.canBeSet()) { self->cstateUpdated.send(Void()); } diff --git a/fdbserver/logsystem/LogSystem.cpp b/fdbserver/logsystem/LogSystem.cpp index 1e488b0e57..7ad92ab38d 100644 --- a/fdbserver/logsystem/LogSystem.cpp +++ b/fdbserver/logsystem/LogSystem.cpp @@ -528,16 +528,6 @@ void LogSystem::purgeOldRecoveredGenerationsCoreState(DBCoreState& newState) { } } -void LogSystem::purgeOldRecoveredGenerationsInMemory(const DBCoreState& newState) { - auto generations = newState.oldTLogData.size(); - if (generations < oldLogData.size()) { - TraceEvent("PurgeOldTLogGenerationsInMemory", dbgid) - .detail("OldGenerations", oldLogData.size()) - .detail("NewGenerations", generations); - oldLogData.resize(generations); - } -} - void LogSystem::toCoreState(DBCoreState& newState) const { if (recoveryComplete.isValid() && recoveryComplete.isError()) throw recoveryComplete.getError(); @@ -1139,10 +1129,12 @@ LogSystemConfig LogSystem::getLogSystemConfig() const { } } - if (!recoveryCompleteWrittenToCoreState.get()) { - for (const auto& oldData : oldLogData) { - logSystemConfig.oldTLogs.push_back(toOldTLogConf(oldData)); - } + // ServerDBInfo uses oldTLogs to keep old-generation TLog roles from displacing + // themselves while this cluster controller is alive. Durable state/logsKey can + // drop recovered old generations earlier, but these roles may still be needed + // if this recovery has to run again. + for (const auto& oldData : oldLogData) { + logSystemConfig.oldTLogs.push_back(toOldTLogConf(oldData)); } return logSystemConfig; } diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index aa806d0139..8e965d5eb1 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -373,9 +373,8 @@ struct LogSystem : ReferenceCounted { bool remoteStorageRecovered() const; - // Checks older TLog generations and remove no longer needed generations from the log system. + // Removes no-longer-needed older TLog generations from the outgoing core state. void purgeOldRecoveredGenerationsCoreState(DBCoreState&); - void purgeOldRecoveredGenerationsInMemory(const DBCoreState&); Future onCoreStateChanged() const; From bdc761e71bb81bcbaa986b3bf6e75dc59fddd143 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 8 Jun 2026 17:11:28 -0700 Subject: [PATCH 02/39] Add comment --- fdbserver/clustercontroller/ClusterRecovery.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fdbserver/clustercontroller/ClusterRecovery.cpp b/fdbserver/clustercontroller/ClusterRecovery.cpp index 3bbd71cb74..3bf5797447 100644 --- a/fdbserver/clustercontroller/ClusterRecovery.cpp +++ b/fdbserver/clustercontroller/ClusterRecovery.cpp @@ -467,6 +467,8 @@ Future trackTlogRecovery(Reference self, configuration.expectedLogSets(!self->primaryDcId.empty() ? self->primaryDcId[0] : Optional())) .detail("RecoveryCount", newState.recoveryCount); co_await self->cstate.write(newState, finalUpdate); + // Keep oldLogData in memory even after the coordinated state drops old generations. ServerDBInfo uses + // it to keep old-generation TLogs serving in case this master has to run recovery again. if (self->cstateUpdated.canBeSet()) { self->cstateUpdated.send(Void()); } From 8f7a072a3d7550051fcda8062c4a92c9d50663e1 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 11:01:33 -0700 Subject: [PATCH 03/39] Add split point limit support --- bindings/c/fdb_c.cpp | 13 +++++++ bindings/c/foundationdb/fdb_c.h | 8 ++++ bindings/flow/fdb_flow.cpp | 28 +++++++------ bindings/flow/fdb_flow.h | 4 +- bindings/go/src/fdb/snapshot.go | 12 ++++++ bindings/go/src/fdb/transaction.go | 27 +++++++++++++ bindings/java/fdbJNI.cpp | 36 +++++++++++++++++ .../apple/foundationdb/FDBTransaction.java | 26 +++++++++++++ .../apple/foundationdb/ReadTransaction.java | 23 +++++++++++ bindings/python/fdb/impl.py | 27 ++++++++++++- bindings/ruby/lib/fdbimpl.rb | 9 ++++- documentation/sphinx/source/api-c.rst | 6 +++ documentation/sphinx/source/api-python.rst | 5 +-- documentation/sphinx/source/api-ruby.rst | 4 +- fdbclient/MultiVersionTransaction.cpp | 26 ++++++++++--- fdbclient/NativeAPI.actor.cpp | 39 ++++++++++++++----- fdbclient/ReadYourWrites.cpp | 5 ++- fdbclient/ThreadSafeTransaction.cpp | 7 ++-- fdbclient/include/fdbclient/IClientApi.h | 3 +- .../fdbclient/MultiVersionTransaction.h | 13 ++++++- fdbclient/include/fdbclient/NativeAPI.actor.h | 3 +- fdbclient/include/fdbclient/ReadYourWrites.h | 2 +- .../fdbclient/StorageServerInterface.h | 10 ++++- .../include/fdbclient/ThreadSafeTransaction.h | 3 +- fdbserver/core/StorageMetrics.cpp | 36 +++++++++++++++-- .../include/fdbserver/core/StorageMetrics.h | 5 ++- flow/ProtocolVersion.h.cmake | 1 + flow/ProtocolVersions.cmake | 1 + 28 files changed, 330 insertions(+), 52 deletions(-) diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 0b25381553..2eec432485 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -840,6 +840,19 @@ extern "C" DLLEXPORT FDBFuture* fdb_transaction_get_range_split_points(FDBTransa return (FDBFuture*)(TXN(tr)->getRangeSplitPoints(range, chunk_size).extractPtr());); } +extern "C" DLLEXPORT FDBFuture* fdb_transaction_get_range_split_points_with_limit(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int64_t chunk_size, + int limit) { + RETURN_FUTURE_ON_ERROR( + Standalone>, + KeyRangeRef range(KeyRef(begin_key_name, begin_key_name_length), KeyRef(end_key_name, end_key_name_length)); + return (FDBFuture*)(TXN(tr)->getRangeSplitPoints(range, chunk_size, limit).extractPtr());); +} + #include "fdb_c_function_pointers.g.h" #define FDB_API_CHANGED(func, ver) \ diff --git a/bindings/c/foundationdb/fdb_c.h b/bindings/c/foundationdb/fdb_c.h index 9f92eef88c..6f6a197948 100644 --- a/bindings/c/foundationdb/fdb_c.h +++ b/bindings/c/foundationdb/fdb_c.h @@ -559,6 +559,14 @@ DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points(F int end_key_name_length, int64_t chunk_size); +DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points_with_limit(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int64_t chunk_size, + int limit); + #define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0 #define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0 #define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1 diff --git a/bindings/flow/fdb_flow.cpp b/bindings/flow/fdb_flow.cpp index 9599cbece2..639a83cec3 100644 --- a/bindings/flow/fdb_flow.cpp +++ b/bindings/flow/fdb_flow.cpp @@ -140,7 +140,9 @@ public: FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) override; Future getEstimatedRangeSizeBytes(const KeyRange& keys) override; - Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize) override; + Future>> getRangeSplitPoints(const KeyRange& range, + int64_t chunkSize, + int limit = -1) override; void addReadConflictRange(KeyRangeRef const& keys) override; void addReadConflictKey(KeyRef const& key) override; @@ -424,17 +426,21 @@ Future TransactionImpl::getEstimatedRangeSizeBytes(const KeyRange& keys } Future>> TransactionImpl::getRangeSplitPoints(const KeyRange& range, - int64_t chunkSize) { - return backToFuture>>( - fdb_transaction_get_range_split_points( - tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize), - [](Reference f) { - FDBKey const* ks; - int count; - throw_on_error(fdb_future_get_key_array(f->f, &ks, &count)); + int64_t chunkSize, + int limit) { + FDBFuture* f = + limit < 0 + ? fdb_transaction_get_range_split_points( + tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize) + : fdb_transaction_get_range_split_points_with_limit( + tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize, limit); + return backToFuture>>(f, [](Reference f) { + FDBKey const* ks; + int count; + throw_on_error(fdb_future_get_key_array(f->f, &ks, &count)); - return FDBStandalone>(f, VectorRef((KeyRef*)ks, count)); - }); + return FDBStandalone>(f, VectorRef((KeyRef*)ks, count)); + }); } void TransactionImpl::addReadConflictRange(KeyRangeRef const& keys) { diff --git a/bindings/flow/fdb_flow.h b/bindings/flow/fdb_flow.h index 6aba6810d8..34545d0255 100644 --- a/bindings/flow/fdb_flow.h +++ b/bindings/flow/fdb_flow.h @@ -106,7 +106,9 @@ public: } virtual Future getEstimatedRangeSizeBytes(const KeyRange& keys) = 0; - virtual Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize) = 0; + virtual Future>> getRangeSplitPoints(const KeyRange& range, + int64_t chunkSize, + int limit = -1) = 0; virtual void addReadConflictRange(KeyRangeRef const& keys) = 0; virtual void addReadConflictKey(KeyRef const& key) = 0; diff --git a/bindings/go/src/fdb/snapshot.go b/bindings/go/src/fdb/snapshot.go index eb6fb1dbd0..c639009a5c 100644 --- a/bindings/go/src/fdb/snapshot.go +++ b/bindings/go/src/fdb/snapshot.go @@ -115,6 +115,18 @@ func (s Snapshot) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKeyAr ) } +// GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. +// The start and end keys of the given range are always included. +func (s Snapshot) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { + beginKey, endKey := r.FDBRangeKeys() + return s.getRangeSplitPointsWithLimit( + beginKey.FDBKey(), + endKey.FDBKey(), + chunkSize, + limit, + ) +} + // Snapshot returns the receiver and allows Snapshot to satisfy the // ReadTransaction interface. func (s Snapshot) Options() TransactionOptions { diff --git a/bindings/go/src/fdb/transaction.go b/bindings/go/src/fdb/transaction.go index 41ad82041f..0594440840 100644 --- a/bindings/go/src/fdb/transaction.go +++ b/bindings/go/src/fdb/transaction.go @@ -41,6 +41,7 @@ type ReadTransaction interface { Snapshot() Snapshot GetEstimatedRangeSizeBytes(r ExactRange) FutureInt64 GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKeyArray + GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray Options() TransactionOptions Cancel() @@ -354,6 +355,20 @@ func (t *transaction) getRangeSplitPoints(beginKey Key, endKey Key, chunkSize in } } +func (t *transaction) getRangeSplitPointsWithLimit(beginKey Key, endKey Key, chunkSize int64, limit int) FutureKeyArray { + return &futureKeyArray{ + future: newFuture(t, C.fdb_transaction_get_range_split_points_with_limit( + t.ptr, + byteSliceToPtr(beginKey), + C.int(len(beginKey)), + byteSliceToPtr(endKey), + C.int(len(endKey)), + C.int64_t(chunkSize), + C.int(limit), + )), + } +} + // GetRangeSplitPoints returns a list of keys that can split the given range // into (roughly) equally sized chunks based on chunkSize. // Note: the returned split points contain the start key and end key of the given range. @@ -366,6 +381,18 @@ func (t Transaction) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKe ) } +// GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. +// The start and end keys of the given range are always included. +func (t Transaction) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { + beginKey, endKey := r.FDBRangeKeys() + return t.getRangeSplitPointsWithLimit( + beginKey.FDBKey(), + endKey.FDBKey(), + chunkSize, + limit, + ) +} + func (t *transaction) getReadVersion() FutureInt64 { return &futureInt64{ future: newFuture(t, C.fdb_transaction_get_read_version(t.ptr)), diff --git a/bindings/java/fdbJNI.cpp b/bindings/java/fdbJNI.cpp index 451cf8d155..58b07ad93d 100644 --- a/bindings/java/fdbJNI.cpp +++ b/bindings/java/fdbJNI.cpp @@ -1278,6 +1278,42 @@ Java_com_apple_foundationdb_FDBTransaction_Transaction_1getRangeSplitPoints(JNIE return (jlong)f; } +JNIEXPORT jlong JNICALL +Java_com_apple_foundationdb_FDBTransaction_Transaction_1getRangeSplitPointsWithLimit(JNIEnv* jenv, + jobject, + jlong tPtr, + jbyteArray beginKeyBytes, + jbyteArray endKeyBytes, + jlong chunkSize, + jint limit) { + if (!tPtr || !beginKeyBytes || !endKeyBytes) { + throwParamNotNull(jenv); + return 0; + } + auto* tr = (FDBTransaction*)tPtr; + + auto* startKey = (uint8_t*)jenv->GetByteArrayElements(beginKeyBytes, JNI_NULL); + if (!startKey) { + if (!jenv->ExceptionOccurred()) + throwRuntimeEx(jenv, "Error getting handle to native resources"); + return 0; + } + + auto* endKey = (uint8_t*)jenv->GetByteArrayElements(endKeyBytes, JNI_NULL); + if (!endKey) { + jenv->ReleaseByteArrayElements(beginKeyBytes, (jbyte*)startKey, JNI_ABORT); + if (!jenv->ExceptionOccurred()) + throwRuntimeEx(jenv, "Error getting handle to native resources"); + return 0; + } + + FDBFuture* f = fdb_transaction_get_range_split_points_with_limit( + tr, startKey, jenv->GetArrayLength(beginKeyBytes), endKey, jenv->GetArrayLength(endKeyBytes), chunkSize, limit); + jenv->ReleaseByteArrayElements(beginKeyBytes, (jbyte*)startKey, JNI_ABORT); + jenv->ReleaseByteArrayElements(endKeyBytes, (jbyte*)endKey, JNI_ABORT); + return (jlong)f; +} + JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1set(JNIEnv* jenv, jobject, jlong tPtr, diff --git a/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java b/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java index 28068d9c4d..a3d3d36539 100644 --- a/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java +++ b/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java @@ -87,11 +87,21 @@ class FDBTransaction extends NativeObjectWrapper implements Transaction, OptionC return FDBTransaction.this.getRangeSplitPoints(begin, end, chunkSize); } + @Override + public CompletableFuture getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize, int limit) { + return FDBTransaction.this.getRangeSplitPoints(begin, end, chunkSize, limit); + } + @Override public CompletableFuture getRangeSplitPoints(Range range, long chunkSize) { return FDBTransaction.this.getRangeSplitPoints(range, chunkSize); } + @Override + public CompletableFuture getRangeSplitPoints(Range range, long chunkSize, int limit) { + return FDBTransaction.this.getRangeSplitPoints(range, chunkSize, limit); + } + @Override public AsyncIterable getMappedRange(KeySelector begin, KeySelector end, byte[] mapper, int limit, boolean reverse, StreamingMode mode) { @@ -341,11 +351,26 @@ class FDBTransaction extends NativeObjectWrapper implements Transaction, OptionC } } + @Override + public CompletableFuture getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize, int limit) { + pointerReadLock.lock(); + try { + return new FutureKeyArray(Transaction_getRangeSplitPointsWithLimit(getPtr(), begin, end, chunkSize, limit), executor); + } finally { + pointerReadLock.unlock(); + } + } + @Override public CompletableFuture getRangeSplitPoints(Range range, long chunkSize) { return this.getRangeSplitPoints(range.begin, range.end, chunkSize); } + @Override + public CompletableFuture getRangeSplitPoints(Range range, long chunkSize, int limit) { + return this.getRangeSplitPoints(range.begin, range.end, chunkSize, limit); + } + @Override public AsyncIterable getMappedRange(KeySelector begin, KeySelector end, byte[] mapper, int limit, boolean reverse, StreamingMode mode) { @@ -842,4 +867,5 @@ class FDBTransaction extends NativeObjectWrapper implements Transaction, OptionC private native long Transaction_getKeyLocations(long cPtr, byte[] key); private native long Transaction_getEstimatedRangeSizeBytes(long cPtr, byte[] keyBegin, byte[] keyEnd); private native long Transaction_getRangeSplitPoints(long cPtr, byte[] keyBegin, byte[] keyEnd, long chunkSize); + private native long Transaction_getRangeSplitPointsWithLimit(long cPtr, byte[] keyBegin, byte[] keyEnd, long chunkSize, int limit); } diff --git a/bindings/java/src/main/com/apple/foundationdb/ReadTransaction.java b/bindings/java/src/main/com/apple/foundationdb/ReadTransaction.java index 96de34ab57..d48e058454 100644 --- a/bindings/java/src/main/com/apple/foundationdb/ReadTransaction.java +++ b/bindings/java/src/main/com/apple/foundationdb/ReadTransaction.java @@ -504,6 +504,18 @@ public interface ReadTransaction extends ReadTransactionContext { */ CompletableFuture getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize); + /** + * Gets at most limit interior split points, including shard boundaries. + * The start and end keys of the given range are always included. + * + * @param begin the beginning of the range (inclusive) + * @param end the end of the range (exclusive) + * @param chunkSize the target estimated byte size of each chunk + * @param limit the maximum number of interior split points, or a negative value for no limit + * @return a handle to access the results of the asynchronous call + */ + CompletableFuture getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize, int limit); + /** * Gets a list of keys that can split the given range into (roughly) equally sized chunks based on chunkSize * Note: the returned split points contain the start key and end key of the given range. @@ -515,6 +527,17 @@ public interface ReadTransaction extends ReadTransactionContext { */ CompletableFuture getRangeSplitPoints(Range range, long chunkSize); + /** + * Gets at most limit interior split points, including shard boundaries. + * The start and end keys of the given range are always included. + * + * @param range the range of the keys + * @param chunkSize the target estimated byte size of each chunk + * @param limit the maximum number of interior split points, or a negative value for no limit + * @return a handle to access the results of the asynchronous call + */ + CompletableFuture getRangeSplitPoints(Range range, long chunkSize, int limit); + /** * Returns a set of options that can be set on a {@code Transaction} * diff --git a/bindings/python/fdb/impl.py b/bindings/python/fdb/impl.py index 4b74b8dbed..195b1f3240 100644 --- a/bindings/python/fdb/impl.py +++ b/bindings/python/fdb/impl.py @@ -541,17 +541,29 @@ class TransactionRead(_FDBBase): ) ) - def get_range_split_points(self, begin_key, end_key, chunk_size): + def get_range_split_points(self, begin_key, end_key, chunk_size, limit=-1): if begin_key is None or end_key is None or chunk_size <= 0: raise Exception("Invalid begin key, end key or chunk size") + if limit < 0: + return FutureKeyArray( + self.capi.fdb_transaction_get_range_split_points( + self.tpointer, + begin_key, + len(begin_key), + end_key, + len(end_key), + chunk_size, + ) + ) return FutureKeyArray( - self.capi.fdb_transaction_get_range_split_points( + self.capi.fdb_transaction_get_range_split_points_with_limit( self.tpointer, begin_key, len(begin_key), end_key, len(end_key), chunk_size, + limit, ) ) @@ -1793,6 +1805,17 @@ def init_c_api(): ] _capi.fdb_transaction_get_range_split_points.restype = ctypes.c_void_p + _capi.fdb_transaction_get_range_split_points_with_limit.argtypes = [ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_int, + ctypes.c_void_p, + ctypes.c_int, + ctypes.c_int64, + ctypes.c_int, + ] + _capi.fdb_transaction_get_range_split_points_with_limit.restype = ctypes.c_void_p + _capi.fdb_transaction_add_conflict_range.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, diff --git a/bindings/ruby/lib/fdbimpl.rb b/bindings/ruby/lib/fdbimpl.rb index f880f3d2dc..b89b0c37c7 100644 --- a/bindings/ruby/lib/fdbimpl.rb +++ b/bindings/ruby/lib/fdbimpl.rb @@ -110,6 +110,7 @@ module FDB attach_function :fdb_transaction_get_range, [ :pointer, :pointer, :int, :int, :int, :pointer, :int, :int, :int, :int, :int, :int, :int, :int, :int ], :pointer attach_function :fdb_transaction_get_estimated_range_size_bytes, [ :pointer, :pointer, :int, :pointer, :int ], :pointer attach_function :fdb_transaction_get_range_split_points, [ :pointer, :pointer, :int, :pointer, :int, :int64 ], :pointer + attach_function :fdb_transaction_get_range_split_points_with_limit, [ :pointer, :pointer, :int, :pointer, :int, :int64, :int ], :pointer attach_function :fdb_transaction_set, [ :pointer, :pointer, :int, :pointer, :int ], :void attach_function :fdb_transaction_clear, [ :pointer, :pointer, :int ], :void attach_function :fdb_transaction_clear_range, [ :pointer, :pointer, :int, :pointer, :int ], :void @@ -848,13 +849,17 @@ module FDB Int64Future.new(FDBC.fdb_transaction_get_estimated_range_size_bytes(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize)) end - def get_range_split_points(begin_key, end_key, chunk_size) + def get_range_split_points(begin_key, end_key, chunk_size, limit = -1) if chunk_size <=0 raise ArgumentError, "Invalid chunk size" end bkey = FDB.key_to_bytes(begin_key) ekey = FDB.key_to_bytes(end_key) - FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size)) + if limit < 0 + FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size)) + else + FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points_with_limit(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size, limit)) + end end end diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index fa27384c18..b920dfad10 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -613,6 +613,12 @@ Applications must provide error handling and an appropriate retry loop around th |future-return0| the list of split points. |future-return1| call :func:`fdb_future_get_key_array()` to extract the array, |future-return2| +.. function:: FDBFuture* fdb_transaction_get_range_split_points_with_limit( FDBTransaction* tr, uint8_t const* begin_key_name, int begin_key_name_length, uint8_t const* end_key_name, int end_key_name_length, int64_t chunk_size, int limit) + + Returns at most ``limit`` interior split points, including shard boundaries. The start and end keys of the given range are always included. A negative ``limit`` preserves the unlimited behavior of :func:`fdb_transaction_get_range_split_points`. + + |future-return0| the list of split points. |future-return1| call :func:`fdb_future_get_key_array()` to extract the array, |future-return2| + .. function:: FDBFuture* fdb_transaction_get_key(FDBTransaction* transaction, uint8_t const* key_name, int key_name_length, fdb_bool_t or_equal, int offset, fdb_bool_t snapshot) Resolves a :ref:`key selector ` against the keys in the database snapshot represented by ``transaction``. diff --git a/documentation/sphinx/source/api-python.rst b/documentation/sphinx/source/api-python.rst index 76bf8dd121..6164495c2b 100644 --- a/documentation/sphinx/source/api-python.rst +++ b/documentation/sphinx/source/api-python.rst @@ -839,9 +839,9 @@ Transaction misc functions .. note:: The estimated size is calculated based on the sampling done by FDB server. The sampling algorithm works roughly in this way: the larger the key-value pair is, the more likely it would be sampled and the more accurate its sampled size would be. And due to that reason it is recommended to use this API to query against large ranges for accuracy considerations. For a rough reference, if the returned size is larger than 3MB, one can consider the size to be accurate. -.. method:: Transaction.get_range_split_points(self, begin_key, end_key, chunk_size) +.. method:: Transaction.get_range_split_points(self, begin_key, end_key, chunk_size, limit=-1) - Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. Returns a :class:`FutureKeyArray`. + Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Returns a :class:`FutureKeyArray`. .. note:: The returned split points contain the start key and end key of the given range .. method:: Transaction.get_approximate_size() @@ -1547,4 +1547,3 @@ Locality information .. method:: fdb.locality.get_addresses_for_key(tr, key) Returns a :class:`fdb.FutureStringArray`. You must call the :meth:`fdb.Future.wait()` method on this object to retrieve a list of public network addresses as strings, one for each of the storage servers responsible for storing ``key`` and its associated value. - diff --git a/documentation/sphinx/source/api-ruby.rst b/documentation/sphinx/source/api-ruby.rst index c0876d8b16..51ca6b6f6c 100644 --- a/documentation/sphinx/source/api-ruby.rst +++ b/documentation/sphinx/source/api-ruby.rst @@ -747,9 +747,9 @@ Transaction misc functions .. note:: The estimated size is calculated based on the sampling done by FDB server. The sampling algorithm works roughly in this way: the larger the key-value pair is, the more likely it would be sampled and the more accurate its sampled size would be. And due to that reason it is recommended to use this API to query against large ranges for accuracy considerations. For a rough reference, if the returned size is larger than 3MB, one can consider the size to be accurate. -.. method:: Transaction.get_range_split_points(begin_key, end_key, chunk_size) -> FutureKeyArray +.. method:: Transaction.get_range_split_points(begin_key, end_key, chunk_size, limit=-1) -> FutureKeyArray - Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. Returns a :class:`FutureKeyArray`. + Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Returns a :class:`FutureKeyArray`. .. note:: The returned split points contain the start key and end key of the given range .. method:: Transaction.get_approximate_size() -> Int64Future diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index e4e962a90d..1521422ba1 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -250,12 +250,21 @@ ThreadFuture DLTransaction::getEstimatedRangeSizeBytes(const KeyRangeRe } ThreadFuture>> DLTransaction::getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) { + int64_t chunkSize, + int limit) { if (!api->transactionGetRangeSplitPoints) { return unsupported_operation(); } - FdbCApi::FDBFuture* f = api->transactionGetRangeSplitPoints( - tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize); + FdbCApi::FDBFuture* f; + if (limit < 0) { + f = api->transactionGetRangeSplitPoints( + tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize); + } else if (api->transactionGetRangeSplitPointsWithLimit) { + f = api->transactionGetRangeSplitPointsWithLimit( + tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize, limit); + } else { + return unsupported_operation(); + } return toThreadFuture>>(api, f, [](FdbCApi::FDBFuture* f, FdbCApi* api) { const FdbCApi::FDBKey* splitKeys; @@ -660,6 +669,11 @@ void DLApi::init() { fdbCPath, "fdb_transaction_get_range_split_points", headerVersion >= 700); + loadClientFunction(&api->transactionGetRangeSplitPointsWithLimit, + lib, + fdbCPath, + "fdb_transaction_get_range_split_points_with_limit", + headerVersion >= 800); loadClientFunction(&api->futureGetDouble, lib, @@ -1019,8 +1033,10 @@ ThreadFuture MultiVersionTransaction::getEstimatedRangeSizeBytes(const } ThreadFuture>> MultiVersionTransaction::getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) { - return executeOperation(&ITransaction::getRangeSplitPoints, range, std::forward(chunkSize)); + int64_t chunkSize, + int limit) { + return executeOperation( + &ITransaction::getRangeSplitPoints, range, std::forward(chunkSize), std::forward(limit)); } void MultiVersionTransaction::atomicOp(const KeyRef& key, const ValueRef& value, uint32_t operationType) { diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 8c51d852ab..ae91220849 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -3246,7 +3246,8 @@ ACTOR Future getRangeStreamImpl(Reference trState, ACTOR Future>> getRangeSplitPoints(Reference trState, KeyRange keys, - int64_t chunkSize); + int64_t chunkSize, + int limit); // Streams the requested key range directly from storage servers without fragment-level parallelism. ACTOR Future getRangeStream(Reference trState, PromiseStream _results, @@ -5972,7 +5973,8 @@ Future>> DatabaseContext::getReadH ACTOR Future>> getRangeSplitPoints(Reference trState, KeyRange keys, - int64_t chunkSize) { + int64_t chunkSize, + int limit) { state Span span("NAPI:GetRangeSplitPoints"_loc, trState->spanContext); loop { @@ -5980,12 +5982,16 @@ ACTOR Future>> getRangeSplitPoints(ReferenceTOO_MANY, Reverse::False, &StorageServerInterface::getRangeSplitPoints)); try { state int nLocs = locations.size(); + int lastLoc = nLocs - 1; + if (limit >= 0 && nLocs - 1 > limit) { + nLocs = limit + 1; + } state std::vector> fReplies(nLocs); KeyRef partBegin, partEnd; for (int i = 0; i < nLocs; i++) { partBegin = (i == 0) ? keys.begin : locations[i].range.begin; - partEnd = (i == nLocs - 1) ? keys.end : locations[i].range.end; - SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize); + partEnd = (i == lastLoc) ? keys.end : locations[i].range.end; + SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, limit); fReplies[i] = loadBalance(locations[i].locations->locations(), &StorageServerInterface::getRangeSplitPoints, req, @@ -5994,17 +6000,30 @@ ACTOR Future>> getRangeSplitPoints(Reference> results; + int remaining = limit; results.push_back_deep(results.arena(), keys.begin); for (int i = 0; i < nLocs; i++) { if (i > 0) { + if (remaining == 0) { + break; + } results.push_back_deep(results.arena(), locations[i].range.begin); // Need this shard boundary + if (remaining > 0) { + --remaining; + } } - if (fReplies[i].get().splitPoints.size() > 0) { - results.append( - results.arena(), fReplies[i].get().splitPoints.begin(), fReplies[i].get().splitPoints.size()); + int splitPointCount = fReplies[i].get().splitPoints.size(); + if (remaining >= 0) { + splitPointCount = std::min(splitPointCount, remaining); + } + if (splitPointCount > 0) { + results.append(results.arena(), fReplies[i].get().splitPoints.begin(), splitPointCount); results.arena().dependsOn(fReplies[i].get().splitPoints.arena()); + if (remaining > 0) { + remaining -= splitPointCount; + } } } if (results.back() != keys.end) { @@ -6024,8 +6043,10 @@ ACTOR Future>> getRangeSplitPoints(Reference>> Transaction::getRangeSplitPoints(KeyRange const& keys, int64_t chunkSize) { - return ::getRangeSplitPoints(trState, keys, chunkSize); +Future>> Transaction::getRangeSplitPoints(KeyRange const& keys, + int64_t chunkSize, + int limit) { + return ::getRangeSplitPoints(trState, keys, chunkSize, limit); } Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware) { diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 334384abfd..d5e7582fd1 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -1829,7 +1829,8 @@ Future ReadYourWritesTransaction::getEstimatedRangeSizeBytes(const KeyR } Future>> ReadYourWritesTransaction::getRangeSplitPoints(const KeyRange& range, - int64_t chunkSize) { + int64_t chunkSize, + int limit) { if (checkUsedDuringCommit()) { return used_during_commit(); } @@ -1840,7 +1841,7 @@ Future>> ReadYourWritesTransaction::getRangeSplitPo if (range.begin > maxKey || range.end > maxKey) return key_outside_legal_range(); - return waitOrError(tr.getRangeSplitPoints(range, chunkSize), resetPromise.getFuture()); + return waitOrError(tr.getRangeSplitPoints(range, chunkSize, limit), resetPromise.getFuture()); } void ReadYourWritesTransaction::addReadConflictRange(KeyRangeRef const& keys) { diff --git a/fdbclient/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index 5e0a881020..1f009a234e 100644 --- a/fdbclient/ThreadSafeTransaction.cpp +++ b/fdbclient/ThreadSafeTransaction.cpp @@ -247,13 +247,14 @@ ThreadFuture ThreadSafeTransaction::getEstimatedRangeSizeBytes(const Ke } ThreadFuture>> ThreadSafeTransaction::getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) { + int64_t chunkSize, + int limit) { KeyRange r = range; ReadYourWritesTransaction* tr = this->tr; - return onMainThread([tr, r, chunkSize]() -> Future>> { + return onMainThread([tr, r, chunkSize, limit]() -> Future>> { tr->checkDeferredError(); - return tr->getRangeSplitPoints(r, chunkSize); + return tr->getRangeSplitPoints(r, chunkSize, limit); }); } diff --git a/fdbclient/include/fdbclient/IClientApi.h b/fdbclient/include/fdbclient/IClientApi.h index 6f90bc8da1..b5e47ddb47 100644 --- a/fdbclient/include/fdbclient/IClientApi.h +++ b/fdbclient/include/fdbclient/IClientApi.h @@ -74,7 +74,8 @@ public: virtual void addReadConflictRange(const KeyRangeRef& keys) = 0; virtual ThreadFuture getEstimatedRangeSizeBytes(const KeyRangeRef& keys) = 0; virtual ThreadFuture>> getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) = 0; + int64_t chunkSize, + int limit = -1) = 0; virtual void atomicOp(const KeyRef& key, const ValueRef& value, uint32_t operationType) = 0; virtual void set(const KeyRef& key, const ValueRef& value) = 0; diff --git a/fdbclient/include/fdbclient/MultiVersionTransaction.h b/fdbclient/include/fdbclient/MultiVersionTransaction.h index d0c6db4bbf..2530d9a72e 100644 --- a/fdbclient/include/fdbclient/MultiVersionTransaction.h +++ b/fdbclient/include/fdbclient/MultiVersionTransaction.h @@ -215,6 +215,13 @@ struct FdbCApi : public ThreadSafeReferenceCounted { uint8_t const* end_key_name, int end_key_name_length, int64_t chunkSize); + FDBFuture* (*transactionGetRangeSplitPointsWithLimit)(FDBTransaction* tr, + uint8_t const* begin_key_name, + int begin_key_name_length, + uint8_t const* end_key_name, + int end_key_name_length, + int64_t chunkSize, + int limit); FDBFuture* (*transactionCommit)(FDBTransaction* tr); fdb_error_t (*transactionGetCommittedVersion)(FDBTransaction* tr, int64_t* outVersion); @@ -307,7 +314,8 @@ public: ThreadFuture> getVersionstamp() override; ThreadFuture getEstimatedRangeSizeBytes(const KeyRangeRef& keys) override; ThreadFuture>> getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) override; + int64_t chunkSize, + int limit = -1) override; void addReadConflictRange(const KeyRangeRef& keys) override; @@ -473,7 +481,8 @@ public: ThreadFuture getEstimatedRangeSizeBytes(const KeyRangeRef& keys) override; ThreadFuture>> getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) override; + int64_t chunkSize, + int limit = -1) override; void atomicOp(const KeyRef& key, const ValueRef& value, uint32_t operationType) override; void set(const KeyRef& key, const ValueRef& value) override; diff --git a/fdbclient/include/fdbclient/NativeAPI.actor.h b/fdbclient/include/fdbclient/NativeAPI.actor.h index b5efcfe4ee..73e612f22b 100644 --- a/fdbclient/include/fdbclient/NativeAPI.actor.h +++ b/fdbclient/include/fdbclient/NativeAPI.actor.h @@ -425,7 +425,8 @@ public: // Try to split the given range into equally sized chunks based on estimated size. // The returned list would still be in form of [keys.begin, splitPoint1, splitPoint2, ... , keys.end] - Future>> getRangeSplitPoints(KeyRange const& keys, int64_t chunkSize); + // A non-negative limit caps the number of interior split points, including shard boundaries. + Future>> getRangeSplitPoints(KeyRange const& keys, int64_t chunkSize, int limit = -1); // If checkWriteConflictRanges is true, existing write conflict ranges will be searched for this key void set(const KeyRef& key, const ValueRef& value, AddConflictRange = AddConflictRange::True); diff --git a/fdbclient/include/fdbclient/ReadYourWrites.h b/fdbclient/include/fdbclient/ReadYourWrites.h index 36f05bbcde..8d0d00b3d0 100644 --- a/fdbclient/include/fdbclient/ReadYourWrites.h +++ b/fdbclient/include/fdbclient/ReadYourWrites.h @@ -115,7 +115,7 @@ public: Reverse = Reverse::False); [[nodiscard]] Future>> getAddressesForKey(const Key& key); - Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize); + Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize, int limit = -1); Future getEstimatedRangeSizeBytes(const KeyRange& keys); void addReadConflictRange(KeyRangeRef const& keys); diff --git a/fdbclient/include/fdbclient/StorageServerInterface.h b/fdbclient/include/fdbclient/StorageServerInterface.h index 11fb783a16..bbd3682f01 100644 --- a/fdbclient/include/fdbclient/StorageServerInterface.h +++ b/fdbclient/include/fdbclient/StorageServerInterface.h @@ -779,14 +779,20 @@ struct SplitRangeRequest { Arena arena; KeyRangeRef keys; int64_t chunkSize; + int limit = -1; ReplyPromise reply; SplitRangeRequest() = default; - SplitRangeRequest(KeyRangeRef const& keys, int64_t chunkSize) : keys(arena, keys), chunkSize(chunkSize) {} + SplitRangeRequest(KeyRangeRef const& keys, int64_t chunkSize, int limit = -1) + : keys(arena, keys), chunkSize(chunkSize), limit(limit) {} template void serialize(Ar& ar) { - serializer(ar, keys, chunkSize, reply, arena); + serializer(ar, keys, chunkSize, reply); + if (ar.protocolVersion().hasRangeSplitPointsLimit()) { + serializer(ar, limit); + } + serializer(ar, arena); } }; diff --git a/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 65b952ab6b..8d5842cf81 100644 --- a/fdbclient/include/fdbclient/ThreadSafeTransaction.h +++ b/fdbclient/include/fdbclient/ThreadSafeTransaction.h @@ -123,7 +123,8 @@ public: ThreadFuture> getVersionstamp() override; ThreadFuture getEstimatedRangeSizeBytes(const KeyRangeRef& keys) override; ThreadFuture>> getRangeSplitPoints(const KeyRangeRef& range, - int64_t chunkSize) override; + int64_t chunkSize, + int limit = -1) override; void addReadConflictRange(const KeyRangeRef& keys) override; void makeSelfConflicting(); diff --git a/fdbserver/core/StorageMetrics.cpp b/fdbserver/core/StorageMetrics.cpp index 536c33e130..2237b48424 100644 --- a/fdbserver/core/StorageMetrics.cpp +++ b/fdbserver/core/StorageMetrics.cpp @@ -612,7 +612,7 @@ void StorageServerMetrics::getSplitPoints(SplitRangeRequest req, Optional points = getSplitPoints(range, req.chunkSize, prefix); + std::vector points = getSplitPoints(range, req.chunkSize, prefix, req.limit); reply.splitPoints.append_deep(reply.splitPoints.arena(), points.data(), points.size()); req.reply.send(reply); @@ -620,12 +620,13 @@ void StorageServerMetrics::getSplitPoints(SplitRangeRequest req, Optional StorageServerMetrics::getSplitPoints(KeyRangeRef range, int64_t chunkSize, - Optional prefixToRemove) const { + Optional prefixToRemove, + int limit) const { std::vector toReturn; KeyRef beginKey = range.begin; IndexedSet::const_iterator endKey = byteSample.sample.index(byteSample.sample.sumTo(byteSample.sample.lower_bound(beginKey)) + chunkSize); - while (endKey != byteSample.sample.end()) { + while (endKey != byteSample.sample.end() && (limit < 0 || toReturn.size() < static_cast(limit))) { if (*endKey > range.end) { break; } @@ -926,6 +927,35 @@ TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/multipleReturnedPoint return Void(); } +TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/limit") { + + int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; + StorageServerMetrics ssm; + + ssm.byteSample.sample.insert("A"_sr, 200 * sampleUnit); + ssm.byteSample.sample.insert("Absolute"_sr, 800 * sampleUnit); + ssm.byteSample.sample.insert("Apple"_sr, 1000 * sampleUnit); + ssm.byteSample.sample.insert("Bah"_sr, 20 * sampleUnit); + ssm.byteSample.sample.insert("Banana"_sr, 80 * sampleUnit); + ssm.byteSample.sample.insert("Bob"_sr, 200 * sampleUnit); + ssm.byteSample.sample.insert("But"_sr, 100 * sampleUnit); + ssm.byteSample.sample.insert("Cat"_sr, 300 * sampleUnit); + + std::vector limited = ssm.getSplitPoints(KeyRangeRef("A"_sr, "C"_sr), 600 * sampleUnit, {}, 2); + ASSERT(limited.size() == 2 && limited[0] == "Absolute"_sr && limited[1] == "Apple"_sr); + + std::vector none = ssm.getSplitPoints(KeyRangeRef("A"_sr, "C"_sr), 600 * sampleUnit, {}, 0); + ASSERT(none.empty()); + + SplitRangeRequest req(KeyRangeRef("A"_sr, "C"_sr), 600 * sampleUnit, 1); + Future reply = req.reply.getFuture(); + ssm.getSplitPoints(req, {}); + ASSERT(reply.isReady()); + ASSERT(reply.get().splitPoints.size() == 1 && reply.get().splitPoints[0] == "Absolute"_sr); + + return Void(); +} + TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/noneSplitable") { int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; diff --git a/fdbserver/core/include/fdbserver/core/StorageMetrics.h b/fdbserver/core/include/fdbserver/core/StorageMetrics.h index fbc775e2dd..487e2d9734 100644 --- a/fdbserver/core/include/fdbserver/core/StorageMetrics.h +++ b/fdbserver/core/include/fdbserver/core/StorageMetrics.h @@ -148,7 +148,10 @@ struct StorageServerMetrics { int64_t getHotShards(const KeyRange& range) const; - std::vector getSplitPoints(KeyRangeRef range, int64_t chunkSize, Optional prefixToRemove) const; + std::vector getSplitPoints(KeyRangeRef range, + int64_t chunkSize, + Optional prefixToRemove, + int limit = -1) const; void getSplitPoints(SplitRangeRequest req, Optional prefix) const; diff --git a/flow/ProtocolVersion.h.cmake b/flow/ProtocolVersion.h.cmake index 5495d83ec4..a5add2bfaa 100644 --- a/flow/ProtocolVersion.h.cmake +++ b/flow/ProtocolVersion.h.cmake @@ -180,6 +180,7 @@ public: // introduced features PROTOCOL_VERSION_FEATURE(@FDB_PV_MUTATION_CHECKSUM@, MutationChecksum); PROTOCOL_VERSION_FEATURE(@FDB_PV_RANGE_PARTITIONED_BACKUP_WORKER@, RangePartitionedBackupWorker); PROTOCOL_VERSION_FEATURE(@FDB_PV_NATIVE_CDC@, NativeCdc); + PROTOCOL_VERSION_FEATURE(@FDB_PV_RANGE_SPLIT_POINTS_LIMIT@, RangeSplitPointsLimit); }; template <> diff --git a/flow/ProtocolVersions.cmake b/flow/ProtocolVersions.cmake index cf6db7c51f..b6a80dc20a 100644 --- a/flow/ProtocolVersions.cmake +++ b/flow/ProtocolVersions.cmake @@ -97,3 +97,4 @@ set(FDB_PV_MUTATION_CHECKSUM "0x0FDB00B074000000LL") set(FDB_PV_GRPC_ENDPOINT "0x0FDB00B080000000LL") set(FDB_PV_RANGE_PARTITIONED_BACKUP_WORKER "0x0FDB00B080000000LL") set(FDB_PV_NATIVE_CDC "0x0FDB00B080000000LL") +set(FDB_PV_RANGE_SPLIT_POINTS_LIMIT "0x0FDB00B080000000LL") From 224790b1308d9cb150d74b62110506d1c62285de Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 11:41:00 -0700 Subject: [PATCH 04/39] Fix clang-tidy warnings for split point limits --- .github/workflows/tidy.yml | 9 +++- fdbclient/ReadYourWrites.cpp | 79 ++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index 69fab7dc17..cc75d5c51a 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -44,7 +44,9 @@ jobs: ninja -v \ processed_compile_commands \ fdboptions \ - ProtocolVersion + ProtocolVersion \ + fdb_c_generated \ + fdb-java # all flow actors, for generated headers ACTORS=$( @@ -88,8 +90,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. + # on include order from their real consumers. The C API header is intentionally valid C, not C++. case "$FILE" in + bindings/c/foundationdb/fdb_c.h) + continue + ;; fdbserver/core/RocksDBCheckpointUtils.cpp|fdbserver/kvstore/KeyValueStoreRocksDB.cpp|fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp) continue ;; diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index d5e7582fd1..f612f0b664 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -144,7 +144,7 @@ public: co_await getRangeValue(ryw, read.key, firstGreaterOrEqual(ryw->getMaxReadKey()), GetRangeLimits(1), it); if (result.readToBegin) co_return allKeys.begin; - if (result.readThroughEnd || !result.size()) + if (result.readThroughEnd || result.empty()) co_return ryw->getMaxReadKey(); co_return result[0].key; } else { @@ -153,7 +153,7 @@ public: co_await getRangeValueBack(ryw, firstGreaterOrEqual(allKeys.begin), read.key, GetRangeLimits(1), it); if (result.readThroughEnd) co_return ryw->getMaxReadKey(); - if (result.readToBegin || !result.size()) + if (result.readToBegin || result.empty()) co_return allKeys.begin; co_return result[0].key; } @@ -201,7 +201,7 @@ public: RangeResult v = co_await ryw->tr.getRange( read.begin, read.end, read.limits, snapshot, backwards ? Reverse::True : Reverse::False); KeyRef maxKey = ryw->getMaxReadKey(); - if (v.size() > 0) { + if (!v.empty()) { if (!backwards && v[v.size() - 1].key >= maxKey) { RangeResult _v = v; int i = _v.size() - 2; @@ -229,14 +229,15 @@ public: static void addConflictRange(ReadYourWritesTransaction* ryw, GetKeyReq read, WriteMap::iterator& it, Key result) { KeyRangeRef readRange; - if (read.key.offset <= 0) + if (read.key.offset <= 0) { readRange = KeyRangeRef(KeyRef(ryw->arena, result), read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey())); - else + } else { readRange = KeyRangeRef(read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey()), keyAfter(result, ryw->arena)); + } it.skip(readRange.begin); ryw->updateConflictMap(readRange, it); @@ -476,7 +477,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { beginKey = std::min(beginKey, data[0].key); if (data.readThrough.present()) { endKey = std::max(endKey, data.readThrough.get()); @@ -511,8 +512,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -541,8 +543,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -633,7 +636,7 @@ public: .detail("Unknown", it.is_unknown_range()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -645,7 +648,7 @@ public: (begin.offset >= 1 && begin.getKey() >= ryw->getMaxReadKey())) { if (end.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedEnd = co_await read( ryw, @@ -673,7 +676,7 @@ public: break; if (it.is_unknown_range()) { - if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && result.size() && + if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && !result.empty() && itemsPastEnd >= 1 - end.offset) { result.more = true; break; @@ -775,8 +778,9 @@ public: if (count) result.append(result.arena(), start, count); ++it; - } else + } else { ++it; + } } result.more = result.more || limits.isReached(); @@ -805,7 +809,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { if (data.readThrough.present()) { beginKey = std::min(data.readThrough.get(), beginKey); } else { @@ -840,8 +844,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -868,8 +873,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -937,7 +943,7 @@ public: .detail("Kv", it.is_kv()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -949,7 +955,7 @@ public: (end.offset <= 1 && end.getKey() == allKeys.begin)) { if (begin.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedBegin = co_await read( ryw, @@ -980,7 +986,7 @@ public: } if (it.is_unknown_range()) { - if (limits.hasByteLimit() && result.size() && itemsPastBegin >= begin.offset - 1) { + if (limits.hasByteLimit() && !result.empty() && itemsPastBegin >= begin.offset - 1) { result.more = true; break; } @@ -1227,7 +1233,7 @@ public: auto itCopy = it; ++it; - ASSERT(itCopy->value.size()); + ASSERT(!itCopy->value.empty()); CODE_PROBE(itCopy->value.size() > 1, "Multiple watches on the same key triggered by RYOW"); for (int i = 0; i < itCopy->value.size(); i++) { @@ -1248,7 +1254,7 @@ public: } } - if (itCopy->value.size() == 0) + if (itCopy->value.empty()) ryw->watchMap.erase(itCopy); } } @@ -1342,11 +1348,12 @@ public: ryw->nativeReadRanges = ryw->tr.readConflictRanges(); ryw->nativeWriteRanges = ryw->tr.writeConflictRanges(); for (const auto& f : ryw->tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { ryw->nativeReadRanges.push_back( ryw->nativeReadRanges.arena(), KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, ryw->nativeReadRanges.arena())); + } } if (ryw->resetPromise.isSet()) @@ -1481,7 +1488,7 @@ public: } static Future onError(ReadYourWritesTransaction* ryw, Error e) { - if (ryw->debugTraces.size() > 0 || ryw->debugMessages.size() > 0) { + if (!ryw->debugTraces.empty() || !ryw->debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(printDebugMessages(ryw, {}, e).isReady()); } @@ -2045,10 +2052,11 @@ RangeResult ReadYourWritesTransaction::getReadConflictRangeIntersecting(KeyRange for (const auto& range : nativeReadRanges) readConflicts.insert(range.withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); for (const auto& f : tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { readConflicts.insert(KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } auto beginIter = readConflicts.rangeContaining(kr.begin); if (beginIter->begin() != kr.begin) @@ -2075,11 +2083,12 @@ RangeResult ReadYourWritesTransaction::getWriteConflictRangeIntersecting(KeyRang if (it.beginKey() > allKeys.begin) --it; for (; it.beginKey() < strippedWriteRangePrefix.end; ++it) { - if (it.is_conflict_range()) + if (it.is_conflict_range()) { writeConflicts.insert( KeyRangeRef(it.beginKey().toArena(result.arena()), it.endKey().toArena(result.arena())) .withPrefix(writeConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } } else { for (const auto& range : tr.writeConflictRanges()) @@ -2411,7 +2420,7 @@ Future ReadYourWritesTransaction::commit() { result = RYWImpl::commit(this); } - return debugMessages.size() > 0 || debugTraces.size() > 0 ? RYWImpl::printDebugMessages(this, result) : result; + return !debugMessages.empty() || !debugTraces.empty() ? RYWImpl::printDebugMessages(this, result) : result; } Future> ReadYourWritesTransaction::getVersionstamp() { @@ -2534,7 +2543,7 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep reading = std::move(r.reading); resetPromise = std::move(r.resetPromise); r.resetPromise = Promise(); - deferredError = std::move(r.deferredError); + deferredError = r.deferredError; retries = r.retries; approximateSize = r.approximateSize; timeoutActor = r.timeoutActor; @@ -2555,11 +2564,10 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep } ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept - : deferredError(std::move(r.deferredError)), arena(std::move(r.arena)), cache(std::move(r.cache)), - writes(std::move(r.writes)), resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), - retries(r.retries), approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), - creationTime(r.creationTime), commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), - options(r.options) { + : deferredError(r.deferredError), arena(std::move(r.arena)), cache(std::move(r.cache)), writes(std::move(r.writes)), + resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), retries(r.retries), + approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), creationTime(r.creationTime), + commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), options(r.options) { cache.arena = &arena; writes.arena = &arena; tr = std::move(r.tr); @@ -2638,7 +2646,7 @@ void ReadYourWritesTransaction::cancel() { } void ReadYourWritesTransaction::reset() { - if (debugTraces.size() > 0 || debugMessages.size() > 0) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(RYWImpl::printDebugMessages(this, {}).isReady()); } @@ -2677,7 +2685,7 @@ ReadYourWritesTransaction::~ReadYourWritesTransaction() { if (!resetPromise.isSet()) resetPromise.sendError(transaction_cancelled()); - if (debugTraces.size() || debugMessages.size()) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument [[maybe_unused]] Future f = RYWImpl::printDebugMessages(this, {}); } @@ -2701,14 +2709,15 @@ void ReadYourWritesTransaction::debugLogRetries(Optional error) { if (!transactionDebugInfo->transactionName.empty()) transactionNameStr = format(" in transaction '%s'", printable(StringRef(transactionDebugInfo->transactionName)).c_str()); - if (!g_network->isSimulated()) // Fuzz workload turns this on, but we do not want stderr output in - // simulation + // Fuzz workload turns this on, but we do not want stderr output in simulation. + if (!g_network->isSimulated()) { fprintf(stderr, "fdb WARNING: long transaction (%.2fs elapsed%s, %d retries, %s)\n", elapsed, transactionNameStr.c_str(), retries, committed ? "committed" : error.get().what()); + } { TraceEvent trace = TraceEvent("LongTransaction"); if (error.present()) From 5dc1f28ae51272698c45d7a5f10a51256022f622 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 11:51:27 -0700 Subject: [PATCH 05/39] Optimize ActorCollection task callbacks --- flow/ActorCollection.actor.cpp | 142 ++++++++++++++++++++++++++------- 1 file changed, 114 insertions(+), 28 deletions(-) diff --git a/flow/ActorCollection.actor.cpp b/flow/ActorCollection.actor.cpp index 5500cfe1e8..8e19bd7f55 100644 --- a/flow/ActorCollection.actor.cpp +++ b/flow/ActorCollection.actor.cpp @@ -24,11 +24,68 @@ #include #include "flow/actorcompiler.h" // This must be the last #include. -struct Runner : public boost::intrusive::list_base_hook<>, FastAllocated, NonCopyable { - Future handler; +class Runner final : public boost::intrusive::list_base_hook<>, + public Callback, + public FastAllocated, + NonCopyable { +public: + Runner(PromiseStream complete, PromiseStream errors) + : complete(std::move(complete)), errors(std::move(errors)) {} + + ~Runner() { detach(); } + + void start(Future task) { + if (!task.isReady()) { + registered = true; + task.addCallbackAndClear(this); + return; + } + if (task.isError()) { + error(task.getError()); + } else { + fire(Void()); + } + } + + void fire(Void const&) override { +#ifdef ENABLE_SAMPLING + LineageReference callbackLineage = lineage; + LineageScope scope(&callbackLineage); +#endif + auto output = complete; + detach(); + output.send(this); + } + + void error(Error e) override { +#ifdef ENABLE_SAMPLING + LineageReference callbackLineage = lineage; + LineageScope scope(&callbackLineage); +#endif + auto output = errors; + detach(); + if (e.code() != error_code_actor_cancelled) { + output.send(e); + } + } + +private: + void detach() { + if (registered) { + registered = false; + Callback::remove(); + } + } + + PromiseStream complete; + PromiseStream errors; + bool registered = false; +#ifdef ENABLE_SAMPLING + LineageReference lineage = *currentLineage; +#endif }; -// An intrusive list of Runners, which are FastAllocated. Each runner holds a handler future +// An intrusive list of Runners, which are FastAllocated. using RunnerList = boost::intrusive::list>; // The runners list in the ActorCollection must be destroyed when the actor is destructed rather @@ -43,21 +100,6 @@ struct RunnerListDestroyer : NonCopyable { RunnerList* list; }; -ACTOR Future runnerHandler(PromiseStream output, - PromiseStream errors, - Future task, - RunnerList::iterator runner) { - try { - wait(task); - output.send(runner); - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) - throw; - errors.send(e); - } - return Void(); -} - ACTOR Future actorCollection(FutureStream> addActor, int* pCount, double* lastChangeTime, @@ -66,7 +108,7 @@ ACTOR Future actorCollection(FutureStream> addActor, bool returnWhenEmptied) { state RunnerList runners; state RunnerListDestroyer runnersDestroyer(&runners); - state PromiseStream complete; + state PromiseStream complete; state PromiseStream errors; state int count = 0; if (!pCount) @@ -74,12 +116,9 @@ ACTOR Future actorCollection(FutureStream> addActor, loop choose { when(Future f = waitNext(addActor)) { - // Insert new Runner at the end of the instrusive list and get an iterator to it - auto i = runners.insert(runners.end(), *new Runner()); - - // Start the handler for completions or errors from f, sending runner to complete stream - Future handler = runnerHandler(complete, errors, f, i); - i->handler = handler; + auto runner = new Runner(complete, errors); + runners.insert(runners.end(), *runner); + runner->start(std::move(f)); ++*pCount; if (*pCount == 1 && lastChangeTime && idleTime && allTime) { @@ -89,7 +128,7 @@ ACTOR Future actorCollection(FutureStream> addActor, *lastChangeTime = currentTime; } } - when(RunnerList::iterator i = waitNext(complete.getFuture())) { + when(Runner* runner = waitNext(complete.getFuture())) { if (!--*pCount) { if (lastChangeTime && idleTime && allTime) { double currentTime = now(); @@ -99,8 +138,8 @@ ACTOR Future actorCollection(FutureStream> addActor, if (returnWhenEmptied) return Void(); } - // If we didn't return then the entire list wasn't destroyed so erase/destroy i - runners.erase_and_dispose(i, [](Runner* r) { delete r; }); + // If we didn't return then the entire list wasn't destroyed so erase/destroy runner + runners.erase_and_dispose(runners.iterator_to(*runner), [](Runner* r) { delete r; }); } when(Error e = waitNext(errors.getFuture())) { throw e; @@ -165,6 +204,53 @@ Future failedActor() { return operation_failed(); } +TEST_CASE("/flow/actorCollection/testReady") { + state ActorCollection actorCollection(true); + actorCollection.add(Void()); + wait(actorCollection.getResult()); + return Void(); +} + +TEST_CASE("/flow/actorCollection/testReadyWhilePending") { + state ActorCollection actorCollection(true); + state Promise pending; + actorCollection.add(pending.getFuture()); + actorCollection.add(Void()); + wait(delay(0)); + ASSERT(!actorCollection.getResult().isReady()); + pending.send(Void()); + wait(actorCollection.getResult()); + return Void(); +} + +TEST_CASE("/flow/actorCollection/testReadyError") { + state ActorCollection actorCollection(false); + actorCollection.add(failedActor()); + try { + wait(actorCollection.getResult()); + ASSERT(false); + } catch (Error& e) { + ASSERT_EQ(e.code(), error_code_operation_failed); + } + return Void(); +} + +TEST_CASE("/flow/actorCollection/testPendingErrorCancels") { + state ActorCollection actorCollection(false); + state Promise pending; + actorCollection.add(failIfNotCancelled()); + actorCollection.add(pending.getFuture()); + pending.sendError(operation_failed()); + try { + wait(actorCollection.getResult()); + ASSERT(false); + } catch (Error& e) { + ASSERT_EQ(e.code(), error_code_operation_failed); + } + wait(delay(0)); + return Void(); +} + // test contract that even if the actor collection has stopped and new actors are added to the promise stream, they are // all cancelled when resetting actor TEST_CASE("/flow/actorCollection/testCancelPromiseStream") { From a5924a76808509d010b223873212a80692c73e3c Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 12:37:23 -0700 Subject: [PATCH 06/39] Bound split-point location lookups --- fdbclient/NativeAPI.actor.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index ae91220849..e6c5861935 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -5971,6 +5971,10 @@ Future>> DatabaseContext::getReadH return ::getReadHotRanges(Database(Reference::addRef(this)), keys); } +static int getRangeSplitPointsLocationLimit(int splitPointLimit, int maxLocations) { + return splitPointLimit >= 0 && splitPointLimit < maxLocations ? splitPointLimit + 1 : maxLocations; +} + ACTOR Future>> getRangeSplitPoints(Reference trState, KeyRange keys, int64_t chunkSize, @@ -5978,11 +5982,14 @@ ACTOR Future>> getRangeSplitPoints(ReferencespanContext); loop { - state std::vector locations = wait(getKeyRangeLocations( - trState, keys, CLIENT_KNOBS->TOO_MANY, Reverse::False, &StorageServerInterface::getRangeSplitPoints)); + state std::vector locations = + wait(getKeyRangeLocations(trState, + keys, + getRangeSplitPointsLocationLimit(limit, CLIENT_KNOBS->TOO_MANY), + Reverse::False, + &StorageServerInterface::getRangeSplitPoints)); try { state int nLocs = locations.size(); - int lastLoc = nLocs - 1; if (limit >= 0 && nLocs - 1 > limit) { nLocs = limit + 1; } @@ -5990,7 +5997,7 @@ ACTOR Future>> getRangeSplitPoints(Referencelocations(), &StorageServerInterface::getRangeSplitPoints, @@ -6049,6 +6056,19 @@ Future>> Transaction::getRangeSplitPoints(KeyRange return ::getRangeSplitPoints(trState, keys, chunkSize, limit); } +TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/locationLimit") { + constexpr int maxLocations = 1000; + + ASSERT(getRangeSplitPointsLocationLimit(-1, maxLocations) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(0, maxLocations) == 1); + ASSERT(getRangeSplitPointsLocationLimit(16, maxLocations) == 17); + ASSERT(getRangeSplitPointsLocationLimit(maxLocations - 1, maxLocations) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(maxLocations, maxLocations) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(std::numeric_limits::max(), maxLocations) == maxLocations); + + return Void(); +} + Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware) { ReadYourWritesTransaction tr(cx); while (true) { From b48082d888343b1df84366b47d5d1f26a18c7325 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 13:31:52 -0700 Subject: [PATCH 07/39] Allow older external clients without split-point limit API --- fdbclient/MultiVersionTransaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbclient/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 1521422ba1..77ba822d12 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -673,7 +673,7 @@ void DLApi::init() { lib, fdbCPath, "fdb_transaction_get_range_split_points_with_limit", - headerVersion >= 800); + false); loadClientFunction(&api->futureGetDouble, lib, From cd2c16ff38852063f1693481dd58fd9809621649 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 15:29:03 -0700 Subject: [PATCH 08/39] Bound split-point work across shards and bindings --- .../bindingtester/spec/bindingApiTester.md | 7 + bindings/bindingtester/tests/api.py | 9 +- bindings/flow/tester/Tester.cpp | 24 +++ bindings/go/CMakeLists.txt | 1 + bindings/go/src/_stacktester/stacktester.go | 12 ++ bindings/go/src/fdb/snapshot.go | 2 +- bindings/go/src/fdb/transaction.go | 15 +- .../go/src/fdb/transaction_internal_test.go | 45 +++++ .../foundationdb/test/AsyncStackTester.java | 6 + .../foundationdb/test/StackOperation.java | 1 + .../apple/foundationdb/test/StackTester.java | 6 +- bindings/python/fdb/impl.py | 1 + bindings/python/tests/tester.py | 4 + bindings/python/tests/unit_tests.py | 15 ++ bindings/ruby/lib/fdbimpl.rb | 3 +- bindings/ruby/tests/tester.rb | 5 +- documentation/sphinx/source/api-python.rst | 2 +- fdbclient/NativeAPI.actor.cpp | 168 ++++++++++++++---- .../fdbclient/StorageServerInterface.h | 13 +- fdbserver/core/StorageMetrics.cpp | 14 ++ 20 files changed, 303 insertions(+), 50 deletions(-) create mode 100644 bindings/go/src/fdb/transaction_internal_test.go diff --git a/bindings/bindingtester/spec/bindingApiTester.md b/bindings/bindingtester/spec/bindingApiTester.md index 25b9592ce6..9975c5bd73 100644 --- a/bindings/bindingtester/spec/bindingApiTester.md +++ b/bindings/bindingtester/spec/bindingApiTester.md @@ -178,6 +178,13 @@ futures must apply the following rules to the result: binding. Make sure the API returns without error. Finally push the string "GOT_RANGE_SPLIT_POINTS" onto the stack. +#### GET_RANGE_SPLIT_POINTS_WITH_LIMIT + + Pops the top four items off of the stack as BEGIN_KEY, END_KEY, CHUNK_SIZE + and LIMIT. Then call the limited `getRangeSplitPoints` API of the language + binding. Make sure the API returns without error. Finally push the string + "GOT_RANGE_SPLIT_POINTS" onto the stack. + #### GET_KEY (_SNAPSHOT, _DATABASE) Pops the top four items off of the stack as KEY, OR_EQUAL, OFFSET, PREFIX diff --git a/bindings/bindingtester/tests/api.py b/bindings/bindingtester/tests/api.py index 81979a14e1..186ebbc584 100644 --- a/bindings/bindingtester/tests/api.py +++ b/bindings/bindingtester/tests/api.py @@ -209,6 +209,8 @@ class ApiTest(Test): ] txn_sizes = ["GET_APPROXIMATE_SIZE"] storage_metrics = ["GET_ESTIMATED_RANGE_SIZE", "GET_RANGE_SPLIT_POINTS"] + if args.api_version >= 800: + storage_metrics.append("GET_RANGE_SPLIT_POINTS_WITH_LIMIT") op_choices += reads op_choices += mutations @@ -653,7 +655,7 @@ class ApiTest(Test): instructions.push_args(key1, key2) instructions.append(op) self.add_strings(1) - elif op == "GET_RANGE_SPLIT_POINTS": + elif op in ("GET_RANGE_SPLIT_POINTS", "GET_RANGE_SPLIT_POINTS_WITH_LIMIT"): # Protect against inverted range and identical keys key1 = self.workspace.pack(self.random.random_tuple(1)) key2 = self.workspace.pack(self.random.random_tuple(1)) @@ -667,7 +669,10 @@ class ApiTest(Test): # TODO: randomize chunkSize but should not exceed 100M(shard limit) chunkSize = 10000000 # 10M - instructions.push_args(key1, key2, chunkSize) + if op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": + instructions.push_args(key1, key2, chunkSize, random.randint(0, 2)) + else: + instructions.push_args(key1, key2, chunkSize) instructions.append(op) self.add_strings(1) else: diff --git a/bindings/flow/tester/Tester.cpp b/bindings/flow/tester/Tester.cpp index 331f4f3fc0..e0ca695e5e 100644 --- a/bindings/flow/tester/Tester.cpp +++ b/bindings/flow/tester/Tester.cpp @@ -683,6 +683,30 @@ struct GetRangeSplitPoints : InstructionFunc { const char* GetRangeSplitPoints::name = "GET_RANGE_SPLIT_POINTS"; REGISTER_INSTRUCTION_FUNC(GetRangeSplitPoints); +struct GetRangeSplitPointsWithLimit : InstructionFunc { + static const char* name; + + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(4); + if (items.size() != 4) + co_return; + + Standalone s1 = co_await items[0].value; + Standalone beginKey = Tuple::unpack(s1).getString(0); + Standalone s2 = co_await items[1].value; + Standalone endKey = Tuple::unpack(s2).getString(0); + Standalone s3 = co_await items[2].value; + int64_t chunkSize = Tuple::unpack(s3).getInt(0); + Standalone s4 = co_await items[3].value; + int limit = Tuple::unpack(s4).getInt(0); + + co_await instruction->tr->getRangeSplitPoints(KeyRangeRef(beginKey, endKey), chunkSize, limit); + data->stack.pushTuple("GOT_RANGE_SPLIT_POINTS"_sr); + } +}; +const char* GetRangeSplitPointsWithLimit::name = "GET_RANGE_SPLIT_POINTS_WITH_LIMIT"; +REGISTER_INSTRUCTION_FUNC(GetRangeSplitPointsWithLimit); + struct GetKeyFunc : InstructionFunc { static const char* name; diff --git a/bindings/go/CMakeLists.txt b/bindings/go/CMakeLists.txt index d4670e9496..4c99b8fff6 100644 --- a/bindings/go/CMakeLists.txt +++ b/bindings/go/CMakeLists.txt @@ -21,6 +21,7 @@ set(SRCS src/fdb/database.go src/fdb/directory/directory_subspace.go src/fdb/fdb_test.go + src/fdb/transaction_internal_test.go src/fdb/snapshot.go go.mod) diff --git a/bindings/go/src/_stacktester/stacktester.go b/bindings/go/src/_stacktester/stacktester.go index c1e5831455..18a34ca23b 100644 --- a/bindings/go/src/_stacktester/stacktester.go +++ b/bindings/go/src/_stacktester/stacktester.go @@ -590,6 +590,18 @@ func (sm *StackMachine) processInst(idx int, inst tuple.Tuple) { if err != nil { panic(err) } + case op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": + r := sm.popKeyRange() + chunkSize := sm.waitAndPop().item.(int64) + limit := int(sm.waitAndPop().item.(int64)) + _, err := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { + _ = rtr.GetRangeSplitPointsWithLimit(r, chunkSize, limit).MustGet() + sm.store(idx, []byte("GOT_RANGE_SPLIT_POINTS")) + return nil, nil + }) + if err != nil { + panic(err) + } case op == "COMMIT": sm.store(idx, sm.currentTransaction().Commit()) case op == "RESET": diff --git a/bindings/go/src/fdb/snapshot.go b/bindings/go/src/fdb/snapshot.go index c639009a5c..012e577ec8 100644 --- a/bindings/go/src/fdb/snapshot.go +++ b/bindings/go/src/fdb/snapshot.go @@ -116,7 +116,7 @@ func (s Snapshot) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKeyAr } // GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. -// The start and end keys of the given range are always included. +// The start and end keys of the given range are always included. Limits larger than 2^31-1 are clamped to 2^31-1. func (s Snapshot) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { beginKey, endKey := r.FDBRangeKeys() return s.getRangeSplitPointsWithLimit( diff --git a/bindings/go/src/fdb/transaction.go b/bindings/go/src/fdb/transaction.go index 0594440840..470bafb590 100644 --- a/bindings/go/src/fdb/transaction.go +++ b/bindings/go/src/fdb/transaction.go @@ -355,6 +355,17 @@ func (t *transaction) getRangeSplitPoints(beginKey Key, endKey Key, chunkSize in } } +func normalizeRangeSplitPointLimit(limit int) int { + const maxLimit = 1<<31 - 1 + if limit < 0 { + return -1 + } + if limit > maxLimit { + return maxLimit + } + return limit +} + func (t *transaction) getRangeSplitPointsWithLimit(beginKey Key, endKey Key, chunkSize int64, limit int) FutureKeyArray { return &futureKeyArray{ future: newFuture(t, C.fdb_transaction_get_range_split_points_with_limit( @@ -364,7 +375,7 @@ func (t *transaction) getRangeSplitPointsWithLimit(beginKey Key, endKey Key, chu byteSliceToPtr(endKey), C.int(len(endKey)), C.int64_t(chunkSize), - C.int(limit), + C.int(normalizeRangeSplitPointLimit(limit)), )), } } @@ -382,7 +393,7 @@ func (t Transaction) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKe } // GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. -// The start and end keys of the given range are always included. +// The start and end keys of the given range are always included. Limits larger than 2^31-1 are clamped to 2^31-1. func (t Transaction) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { beginKey, endKey := r.FDBRangeKeys() return t.getRangeSplitPointsWithLimit( diff --git a/bindings/go/src/fdb/transaction_internal_test.go b/bindings/go/src/fdb/transaction_internal_test.go new file mode 100644 index 0000000000..fea26d83b3 --- /dev/null +++ b/bindings/go/src/fdb/transaction_internal_test.go @@ -0,0 +1,45 @@ +/* + * transaction_internal_test.go + * + * 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. + */ + +package fdb + +import "testing" + +func TestNormalizeRangeSplitPointLimit(t *testing.T) { + maxInt := int(^uint(0) >> 1) + minInt := -maxInt - 1 + tests := []struct { + limit int + want int + }{ + {minInt, -1}, + {-1, -1}, + {0, 0}, + {2, 2}, + {1<<31 - 1, 1<<31 - 1}, + {maxInt, 1<<31 - 1}, + } + + for _, test := range tests { + if got := normalizeRangeSplitPointLimit(test.limit); got != test.want { + t.Errorf("normalizeRangeSplitPointLimit(%d) = %d, want %d", test.limit, got, test.want) + } + } +} diff --git a/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java b/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java index 7e535e3e29..c847f433af 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java @@ -238,6 +238,12 @@ public class AsyncStackTester { inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); }, FDB.DEFAULT_EXECUTOR); } + else if (op == StackOperation.GET_RANGE_SPLIT_POINTS_WITH_LIMIT) { + List params = inst.popParams(4).join(); + return inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2), StackUtils.getInt(params.get(3))).thenAcceptAsync(splitPoints -> { + inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); + }, FDB.DEFAULT_EXECUTOR); + } else if(op == StackOperation.GET_RANGE) { return inst.popParams(5).thenComposeAsync(params -> { int limit = StackUtils.getInt(params.get(2)); diff --git a/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java b/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java index c72abb9724..8cc19872fc 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java @@ -58,6 +58,7 @@ enum StackOperation { GET_VERSIONSTAMP, GET_ESTIMATED_RANGE_SIZE, GET_RANGE_SPLIT_POINTS, + GET_RANGE_SPLIT_POINTS_WITH_LIMIT, SET_READ_VERSION, ON_ERROR, SUB, diff --git a/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java index 23882902e5..8c41237d36 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java @@ -218,6 +218,11 @@ public class StackTester { KeyArrayResult splitPoints = inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2)).join(); inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); } + else if (op == StackOperation.GET_RANGE_SPLIT_POINTS_WITH_LIMIT) { + List params = inst.popParams(4).join(); + KeyArrayResult splitPoints = inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2), StackUtils.getInt(params.get(3))).join(); + inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); + } else if(op == StackOperation.GET_RANGE) { List params = inst.popParams(5).join(); @@ -790,4 +795,3 @@ public class StackTester { private StackTester() {} } - diff --git a/bindings/python/fdb/impl.py b/bindings/python/fdb/impl.py index 195b1f3240..c898f422fe 100644 --- a/bindings/python/fdb/impl.py +++ b/bindings/python/fdb/impl.py @@ -555,6 +555,7 @@ class TransactionRead(_FDBBase): chunk_size, ) ) + limit = min(limit, 2**31 - 1) return FutureKeyArray( self.capi.fdb_transaction_get_range_split_points_with_limit( self.tpointer, diff --git a/bindings/python/tests/tester.py b/bindings/python/tests/tester.py index a68059958e..a232bae784 100644 --- a/bindings/python/tests/tester.py +++ b/bindings/python/tests/tester.py @@ -248,6 +248,10 @@ class Tester: begin, end, chunkSize = inst.pop(3) obj.get_range_split_points(begin, end, chunkSize).wait() inst.push(b"GOT_RANGE_SPLIT_POINTS") + elif inst.op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": + begin, end, chunkSize, limit = inst.pop(4) + obj.get_range_split_points(begin, end, chunkSize, limit).wait() + inst.push(b"GOT_RANGE_SPLIT_POINTS") elif inst.op == "GET_KEY": key, or_equal, offset, prefix = inst.pop(4) result = obj.get_key(fdb.KeySelector(key, or_equal, offset)) diff --git a/bindings/python/tests/unit_tests.py b/bindings/python/tests/unit_tests.py index 888fa9fc13..c95022007c 100644 --- a/bindings/python/tests/unit_tests.py +++ b/bindings/python/tests/unit_tests.py @@ -226,6 +226,19 @@ def test_get_client_status(db): assert status["Healthy"] +def test_range_split_points(db): + begin = b"\x02range-split-points-a" + end = b"\x02range-split-points-z" + tr = db.create_transaction() + + for limit in (-(2**63), -1, 0, 1, 2, 2**31, 2**63): + split_points = tr.get_range_split_points(begin, end, 1000000, limit).wait() + assert split_points[0] == begin + assert split_points[-1] == end + if limit >= 0: + assert len(split_points) <= min(limit, 2**31 - 1) + 2 + + def run_unit_tests(db): try: log("test_db_options") @@ -256,6 +269,8 @@ def run_unit_tests(db): test_get_approximate_size(db) log("test_get_client_status") test_get_client_status(db) + log("test_range_split_points") + test_range_split_points(db) except fdb.FDBError as e: print("Unit tests failed: %s" % e.description) diff --git a/bindings/ruby/lib/fdbimpl.rb b/bindings/ruby/lib/fdbimpl.rb index b89b0c37c7..5fe5195fe0 100644 --- a/bindings/ruby/lib/fdbimpl.rb +++ b/bindings/ruby/lib/fdbimpl.rb @@ -89,6 +89,7 @@ module FDB attach_function :fdb_future_get_key, [ :pointer, :pointer, :pointer ], :fdb_error attach_function :fdb_future_get_value, [ :pointer, :pointer, :pointer, :pointer ], :fdb_error attach_function :fdb_future_get_keyvalue_array, [ :pointer, :pointer, :pointer, :pointer ], :fdb_error + attach_function :fdb_future_get_key_array, [ :pointer, :pointer, :pointer ], :fdb_error attach_function :fdb_future_get_string_array, [ :pointer, :pointer, :pointer ], :fdb_error attach_function :fdb_create_database, [ :string, :pointer ], :fdb_error @@ -486,7 +487,7 @@ module FDB ks = FFI::MemoryPointer.new :pointer count = FFI::MemoryPointer.new :int - FDBC.check_error FDBC.fdb_future_get_key_array(@fpointer, kvs, count) + FDBC.check_error FDBC.fdb_future_get_key_array(@fpointer, ks, count) ks = ks.read_pointer (0..count.read_int-1).map{|i| diff --git a/bindings/ruby/tests/tester.rb b/bindings/ruby/tests/tester.rb index 12645ead61..d273ba5d7d 100755 --- a/bindings/ruby/tests/tester.rb +++ b/bindings/ruby/tests/tester.rb @@ -321,7 +321,10 @@ class Tester inst.tr.get_estimated_range_size_bytes(inst.wait_and_pop, inst.wait_and_pop).to_i inst.push("GOT_ESTIMATED_RANGE_SIZE") when "GET_RANGE_SPLIT_POINTS" - inst.tr.get_range_split_points(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop).length() + inst.tr.get_range_split_points(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop).wait() + inst.push("GOT_RANGE_SPLIT_POINTS") + when "GET_RANGE_SPLIT_POINTS_WITH_LIMIT" + inst.tr.get_range_split_points(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop).wait() inst.push("GOT_RANGE_SPLIT_POINTS") when "GET_KEY" selector = FDB::KeySelector.new(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop) diff --git a/documentation/sphinx/source/api-python.rst b/documentation/sphinx/source/api-python.rst index 6164495c2b..02617447f6 100644 --- a/documentation/sphinx/source/api-python.rst +++ b/documentation/sphinx/source/api-python.rst @@ -841,7 +841,7 @@ Transaction misc functions .. method:: Transaction.get_range_split_points(self, begin_key, end_key, chunk_size, limit=-1) - Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Returns a :class:`FutureKeyArray`. + Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Limits larger than ``2**31 - 1`` are clamped to ``2**31 - 1``. Returns a :class:`FutureKeyArray`. .. note:: The returned split points contain the start key and end key of the given range .. method:: Transaction.get_approximate_size() diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index e6c5861935..48a58a1bb0 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -5975,6 +5975,51 @@ static int getRangeSplitPointsLocationLimit(int splitPointLimit, int maxLocation return splitPointLimit >= 0 && splitPointLimit < maxLocations ? splitPointLimit + 1 : maxLocations; } +class RangeSplitPointsBuilder { + Standalone> results; + int remaining; + +public: + RangeSplitPointsBuilder(KeyRef begin, int limit) : remaining(limit) { + results.push_back_deep(results.arena(), begin); + } + + int getRemaining() const { return remaining; } + + bool appendShardBoundary(KeyRef boundary) { + if (remaining == 0) { + return false; + } + results.push_back_deep(results.arena(), boundary); + if (remaining > 0) { + --remaining; + } + return true; + } + + void appendSplitPoints(Standalone> const& splitPoints) { + int splitPointCount = splitPoints.size(); + if (remaining >= 0) { + splitPointCount = std::min(splitPointCount, remaining); + } + if (splitPointCount == 0) { + return; + } + results.append(results.arena(), splitPoints.begin(), splitPointCount); + results.arena().dependsOn(splitPoints.arena()); + if (remaining > 0) { + remaining -= splitPointCount; + } + } + + Standalone> finish(KeyRef end) { + if (results.back() != end) { + results.push_back_deep(results.arena(), end); + } + return results; + } +}; + ACTOR Future>> getRangeSplitPoints(Reference trState, KeyRange keys, int64_t chunkSize, @@ -5993,51 +6038,46 @@ ACTOR Future>> getRangeSplitPoints(Reference= 0 && nLocs - 1 > limit) { nLocs = limit + 1; } - state std::vector> fReplies(nLocs); - KeyRef partBegin, partEnd; - for (int i = 0; i < nLocs; i++) { - partBegin = (i == 0) ? keys.begin : locations[i].range.begin; - partEnd = locations[i].range.end; - SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, limit); - fReplies[i] = loadBalance(locations[i].locations->locations(), - &StorageServerInterface::getRangeSplitPoints, - req, - TaskPriority::DataDistribution); - } - - wait(waitForAll(fReplies)); - Standalone> results; - int remaining = limit; - - results.push_back_deep(results.arena(), keys.begin); - for (int i = 0; i < nLocs; i++) { - if (i > 0) { - if (remaining == 0) { + state Optional results; + results = RangeSplitPointsBuilder(keys.begin, limit); + if (limit >= 0) { + state int i = 0; + for (; i < nLocs; i++) { + if (i > 0 && !results.get().appendShardBoundary(locations[i].range.begin)) { break; } - results.push_back_deep(results.arena(), - locations[i].range.begin); // Need this shard boundary - if (remaining > 0) { - --remaining; + if (results.get().getRemaining() == 0) { + break; } + KeyRef partBegin = (i == 0) ? keys.begin : locations[i].range.begin; + KeyRef partEnd = std::min(keys.end, locations[i].range.end); + SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, results.get().getRemaining()); + SplitRangeReply reply = wait(loadBalance(locations[i].locations->locations(), + &StorageServerInterface::getRangeSplitPoints, + req, + TaskPriority::DataDistribution)); + results.get().appendSplitPoints(reply.splitPoints); } - int splitPointCount = fReplies[i].get().splitPoints.size(); - if (remaining >= 0) { - splitPointCount = std::min(splitPointCount, remaining); + } else { + state std::vector> fReplies(nLocs); + for (int i = 0; i < nLocs; i++) { + KeyRef partBegin = (i == 0) ? keys.begin : locations[i].range.begin; + KeyRef partEnd = std::min(keys.end, locations[i].range.end); + SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, limit); + fReplies[i] = loadBalance(locations[i].locations->locations(), + &StorageServerInterface::getRangeSplitPoints, + req, + TaskPriority::DataDistribution); } - if (splitPointCount > 0) { - results.append(results.arena(), fReplies[i].get().splitPoints.begin(), splitPointCount); - results.arena().dependsOn(fReplies[i].get().splitPoints.arena()); - if (remaining > 0) { - remaining -= splitPointCount; + wait(waitForAll(fReplies)); + for (int i = 0; i < nLocs; i++) { + if (i > 0) { + results.get().appendShardBoundary(locations[i].range.begin); } + results.get().appendSplitPoints(fReplies[i].get().splitPoints); } } - if (results.back() != keys.end) { - results.push_back_deep(results.arena(), keys.end); - } - - return results; + return results.get().finish(keys.end); } catch (Error& e) { if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed) { trState->cx->invalidateCache(keys); @@ -6069,6 +6109,60 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/locationLimit") { return Void(); } +TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { + Standalone> firstShard; + firstShard.push_back_deep(firstShard.arena(), "A1"_sr); + firstShard.push_back_deep(firstShard.arena(), "A2"_sr); + Standalone> secondShard; + secondShard.push_back_deep(secondShard.arena(), "B1"_sr); + secondShard.push_back_deep(secondShard.arena(), "B2"_sr); + + RangeSplitPointsBuilder zero("A"_sr, 0); + zero.appendSplitPoints(firstShard); + ASSERT(!zero.appendShardBoundary("B"_sr)); + Standalone> zeroResults = zero.finish("Z"_sr); + ASSERT(zeroResults.size() == 2 && zeroResults[0] == "A"_sr && zeroResults[1] == "Z"_sr); + + RangeSplitPointsBuilder one("A"_sr, 1); + one.appendSplitPoints(firstShard); + ASSERT(one.getRemaining() == 0); + ASSERT(!one.appendShardBoundary("B"_sr)); + Standalone> oneResults = one.finish("Z"_sr); + ASSERT(oneResults.size() == 3 && oneResults[1] == "A1"_sr && oneResults[2] == "Z"_sr); + + RangeSplitPointsBuilder two("A"_sr, 2); + ASSERT(two.appendShardBoundary("B"_sr)); + ASSERT(two.getRemaining() == 1); + two.appendSplitPoints(secondShard); + ASSERT(two.getRemaining() == 0); + ASSERT(!two.appendShardBoundary("C"_sr)); + Standalone> twoResults = two.finish("Z"_sr); + ASSERT(twoResults.size() == 4 && twoResults[1] == "B"_sr && twoResults[2] == "B1"_sr && twoResults[3] == "Z"_sr); + + RangeSplitPointsBuilder four("A"_sr, 4); + four.appendSplitPoints(firstShard); + ASSERT(four.getRemaining() == 2); + ASSERT(four.appendShardBoundary("B"_sr)); + ASSERT(four.getRemaining() == 1); + four.appendSplitPoints(secondShard); + ASSERT(four.getRemaining() == 0); + Standalone> fourResults = four.finish("Z"_sr); + ASSERT(fourResults.size() == 6 && fourResults[1] == "A1"_sr && fourResults[2] == "A2"_sr && + fourResults[3] == "B"_sr && fourResults[4] == "B1"_sr && fourResults[5] == "Z"_sr); + + RangeSplitPointsBuilder unlimited("A"_sr, -1); + unlimited.appendSplitPoints(firstShard); + ASSERT(unlimited.appendShardBoundary("B"_sr)); + unlimited.appendSplitPoints(secondShard); + ASSERT(unlimited.appendShardBoundary("C"_sr)); + Standalone> unlimitedResults = unlimited.finish("Z"_sr); + ASSERT(unlimitedResults.size() == 8 && unlimitedResults[1] == "A1"_sr && unlimitedResults[2] == "A2"_sr && + unlimitedResults[3] == "B"_sr && unlimitedResults[4] == "B1"_sr && unlimitedResults[5] == "B2"_sr && + unlimitedResults[6] == "C"_sr && unlimitedResults[7] == "Z"_sr); + + return Void(); +} + Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware) { ReadYourWritesTransaction tr(cx); while (true) { diff --git a/fdbclient/include/fdbclient/StorageServerInterface.h b/fdbclient/include/fdbclient/StorageServerInterface.h index bbd3682f01..bc60a307f2 100644 --- a/fdbclient/include/fdbclient/StorageServerInterface.h +++ b/fdbclient/include/fdbclient/StorageServerInterface.h @@ -788,11 +788,16 @@ struct SplitRangeRequest { template void serialize(Ar& ar) { - serializer(ar, keys, chunkSize, reply); - if (ar.protocolVersion().hasRangeSplitPointsLimit()) { - serializer(ar, limit); + if constexpr (is_fb_function) { + // FlatBuffer visitors must see every field in one call because each visit starts at field zero. + serializer(ar, keys, chunkSize, reply, limit, arena); + } else { + serializer(ar, keys, chunkSize, reply); + if (ar.protocolVersion().hasRangeSplitPointsLimit()) { + serializer(ar, limit); + } + serializer(ar, arena); } - serializer(ar, arena); } }; diff --git a/fdbserver/core/StorageMetrics.cpp b/fdbserver/core/StorageMetrics.cpp index 2237b48424..72c8fb4a99 100644 --- a/fdbserver/core/StorageMetrics.cpp +++ b/fdbserver/core/StorageMetrics.cpp @@ -24,6 +24,7 @@ #include "flow/CodeProbe.h" #include "flow/Hash3.h" #include "flow/IRandom.h" +#include "flow/ObjectSerializer.h" #include "flow/Trace.h" #include "flow/UnitTest.h" #include "flow/CoroUtils.h" @@ -956,6 +957,19 @@ TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/limit") { return Void(); } +TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/requestFlatBufferRoundTrip") { + SplitRangeRequest request(KeyRangeRef("A"_sr, "C"_sr), 1024, 2); + const Standalone serialized = ObjectWriter::toValue(request, Unversioned()); + const SplitRangeRequest decoded = ObjectReader::fromStringRef(serialized, Unversioned()); + + ASSERT(decoded.keys == request.keys); + ASSERT_EQ(decoded.chunkSize, request.chunkSize); + ASSERT_EQ(decoded.limit, request.limit); + ASSERT_EQ(decoded.reply.getEndpoint().token, request.reply.getEndpoint().token); + + return Void(); +} + TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/noneSplitable") { int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; From cb670943bf3bba66c84cede675e112cd225b2093 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 16:27:20 -0700 Subject: [PATCH 09/39] Address split point limit review follow-ups --- bindings/bindingtester/bindingtester.py | 3 + bindings/bindingtester/known_testers.py | 5 ++ bindings/bindingtester/tests/api.py | 2 +- fdbclient/NativeAPI.actor.cpp | 83 ++++++++++++++++++------- fdbserver/core/StorageMetrics.cpp | 23 ++++++- 5 files changed, 90 insertions(+), 26 deletions(-) diff --git a/bindings/bindingtester/bindingtester.py b/bindings/bindingtester/bindingtester.py index 23f8d15909..e757670aad 100755 --- a/bindings/bindingtester/bindingtester.py +++ b/bindings/bindingtester/bindingtester.py @@ -314,6 +314,9 @@ class TestRunner(object): [not tester.directory_snapshot_ops_enabled for tester in self.testers] ) ) + self.args.range_split_points_with_limit_enabled = all( + [tester.range_split_points_with_limit_enabled for tester in self.testers] + ) def print_test(self): test_instructions = self._generate_test() diff --git a/bindings/bindingtester/known_testers.py b/bindings/bindingtester/known_testers.py index 615d173a6a..3cac5d87d0 100644 --- a/bindings/bindingtester/known_testers.py +++ b/bindings/bindingtester/known_testers.py @@ -48,6 +48,7 @@ class Tester: threads_enabled=True, types=COMMON_TYPES, directory_snapshot_ops_enabled=True, + range_split_points_with_limit_enabled=True, ): self.name = name self.cmd = cmd @@ -57,6 +58,9 @@ class Tester: self.threads_enabled = threads_enabled self.types = types self.directory_snapshot_ops_enabled = directory_snapshot_ops_enabled + self.range_split_points_with_limit_enabled = ( + range_split_points_with_limit_enabled + ) def supports_api_version(self, api_version): return ( @@ -132,5 +136,6 @@ testers = { 730, MAX_API_VERSION, directory_snapshot_ops_enabled=False, + range_split_points_with_limit_enabled=False, ), } diff --git a/bindings/bindingtester/tests/api.py b/bindings/bindingtester/tests/api.py index 186ebbc584..947d516a21 100644 --- a/bindings/bindingtester/tests/api.py +++ b/bindings/bindingtester/tests/api.py @@ -209,7 +209,7 @@ class ApiTest(Test): ] txn_sizes = ["GET_APPROXIMATE_SIZE"] storage_metrics = ["GET_ESTIMATED_RANGE_SIZE", "GET_RANGE_SPLIT_POINTS"] - if args.api_version >= 800: + if args.api_version >= 800 and args.range_split_points_with_limit_enabled: storage_metrics.append("GET_RANGE_SPLIT_POINTS_WITH_LIMIT") op_choices += reads diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 48a58a1bb0..364788b0e4 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -5971,8 +5971,13 @@ Future>> DatabaseContext::getReadH return ::getReadHotRanges(Database(Reference::addRef(this)), keys); } -static int getRangeSplitPointsLocationLimit(int splitPointLimit, int maxLocations) { - return splitPointLimit >= 0 && splitPointLimit < maxLocations ? splitPointLimit + 1 : maxLocations; +static int getRangeSplitPointsLocationLimit(int splitPointLimit, int maxLocations, int avoidLocationLimit) { + int locationLimit = splitPointLimit >= 0 && splitPointLimit < maxLocations ? splitPointLimit + 1 : maxLocations; + if (locationLimit == avoidLocationLimit) { + ASSERT(maxLocations > 1); + locationLimit += locationLimit < maxLocations ? 1 : -1; + } + return locationLimit; } class RangeSplitPointsBuilder { @@ -5987,6 +5992,9 @@ public: int getRemaining() const { return remaining; } bool appendShardBoundary(KeyRef boundary) { + if (results.back() == boundary) { + return true; + } if (remaining == 0) { return false; } @@ -6025,31 +6033,37 @@ ACTOR Future>> getRangeSplitPoints(ReferencespanContext); + state Key beginKey = keys.begin; + state Optional results; + results = RangeSplitPointsBuilder(keys.begin, limit); + if (limit == 0) { + return results.get().finish(keys.end); + } loop { - state std::vector locations = - wait(getKeyRangeLocations(trState, - keys, - getRangeSplitPointsLocationLimit(limit, CLIENT_KNOBS->TOO_MANY), - Reverse::False, - &StorageServerInterface::getRangeSplitPoints)); + state std::vector locations = wait(getKeyRangeLocations( + trState, + KeyRangeRef(beginKey, keys.end), + getRangeSplitPointsLocationLimit( + results.get().getRemaining(), CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->STORAGE_METRICS_SHARD_LIMIT), + Reverse::False, + &StorageServerInterface::getRangeSplitPoints)); try { state int nLocs = locations.size(); - if (limit >= 0 && nLocs - 1 > limit) { - nLocs = limit + 1; + if (limit >= 0 && nLocs - 1 > results.get().getRemaining()) { + nLocs = results.get().getRemaining() + 1; } - state Optional results; - results = RangeSplitPointsBuilder(keys.begin, limit); if (limit >= 0) { state int i = 0; for (; i < nLocs; i++) { - if (i > 0 && !results.get().appendShardBoundary(locations[i].range.begin)) { + if ((i > 0 || beginKey != keys.begin) && + !results.get().appendShardBoundary(locations[i].range.begin)) { break; } if (results.get().getRemaining() == 0) { break; } - KeyRef partBegin = (i == 0) ? keys.begin : locations[i].range.begin; + KeyRef partBegin = (i == 0) ? beginKey : locations[i].range.begin; KeyRef partEnd = std::min(keys.end, locations[i].range.end); SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, results.get().getRemaining()); SplitRangeReply reply = wait(loadBalance(locations[i].locations->locations(), @@ -6061,7 +6075,7 @@ ACTOR Future>> getRangeSplitPoints(Reference> fReplies(nLocs); for (int i = 0; i < nLocs; i++) { - KeyRef partBegin = (i == 0) ? keys.begin : locations[i].range.begin; + KeyRef partBegin = (i == 0) ? beginKey : locations[i].range.begin; KeyRef partEnd = std::min(keys.end, locations[i].range.end); SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, limit); fReplies[i] = loadBalance(locations[i].locations->locations(), @@ -6071,16 +6085,21 @@ ACTOR Future>> getRangeSplitPoints(Reference 0) { + if (i > 0 || beginKey != keys.begin) { results.get().appendShardBoundary(locations[i].range.begin); } results.get().appendSplitPoints(fReplies[i].get().splitPoints); } } - return results.get().finish(keys.end); + if (results.get().getRemaining() == 0 || keys.end <= locations.back().range.end) { + return results.get().finish(keys.end); + } + beginKey = locations.back().range.end; } catch (Error& e) { if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed) { trState->cx->invalidateCache(keys); + beginKey = keys.begin; + results = RangeSplitPointsBuilder(keys.begin, limit); wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution)); } else { TraceEvent(SevError, "GetRangeSplitPoints").error(e); @@ -6098,13 +6117,19 @@ Future>> Transaction::getRangeSplitPoints(KeyRange TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/locationLimit") { constexpr int maxLocations = 1000; + constexpr int dataDistributionLocationLimit = 100; - ASSERT(getRangeSplitPointsLocationLimit(-1, maxLocations) == maxLocations); - ASSERT(getRangeSplitPointsLocationLimit(0, maxLocations) == 1); - ASSERT(getRangeSplitPointsLocationLimit(16, maxLocations) == 17); - ASSERT(getRangeSplitPointsLocationLimit(maxLocations - 1, maxLocations) == maxLocations); - ASSERT(getRangeSplitPointsLocationLimit(maxLocations, maxLocations) == maxLocations); - ASSERT(getRangeSplitPointsLocationLimit(std::numeric_limits::max(), maxLocations) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(-1, maxLocations, dataDistributionLocationLimit) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(0, maxLocations, dataDistributionLocationLimit) == 1); + ASSERT(getRangeSplitPointsLocationLimit(16, maxLocations, dataDistributionLocationLimit) == 17); + ASSERT(getRangeSplitPointsLocationLimit(99, maxLocations, dataDistributionLocationLimit) == 101); + ASSERT(getRangeSplitPointsLocationLimit(9, maxLocations, 10) == 11); + ASSERT(getRangeSplitPointsLocationLimit(maxLocations - 1, maxLocations, dataDistributionLocationLimit) == + maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(maxLocations, maxLocations, dataDistributionLocationLimit) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit( + std::numeric_limits::max(), maxLocations, dataDistributionLocationLimit) == maxLocations); + ASSERT(getRangeSplitPointsLocationLimit(maxLocations, maxLocations, maxLocations) == maxLocations - 1); return Void(); } @@ -6116,6 +6141,8 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { Standalone> secondShard; secondShard.push_back_deep(secondShard.arena(), "B1"_sr); secondShard.push_back_deep(secondShard.arena(), "B2"_sr); + Standalone> firstShardEndingAtBoundary; + firstShardEndingAtBoundary.push_back_deep(firstShardEndingAtBoundary.arena(), "B"_sr); RangeSplitPointsBuilder zero("A"_sr, 0); zero.appendSplitPoints(firstShard); @@ -6150,6 +6177,16 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { ASSERT(fourResults.size() == 6 && fourResults[1] == "A1"_sr && fourResults[2] == "A2"_sr && fourResults[3] == "B"_sr && fourResults[4] == "B1"_sr && fourResults[5] == "Z"_sr); + RangeSplitPointsBuilder duplicateBoundary("A"_sr, 2); + duplicateBoundary.appendSplitPoints(firstShardEndingAtBoundary); + ASSERT(duplicateBoundary.getRemaining() == 1); + ASSERT(duplicateBoundary.appendShardBoundary("B"_sr)); + ASSERT(duplicateBoundary.getRemaining() == 1); + duplicateBoundary.appendSplitPoints(secondShard); + Standalone> duplicateBoundaryResults = duplicateBoundary.finish("Z"_sr); + ASSERT(duplicateBoundaryResults.size() == 4 && duplicateBoundaryResults[1] == "B"_sr && + duplicateBoundaryResults[2] == "B1"_sr && duplicateBoundaryResults[3] == "Z"_sr); + RangeSplitPointsBuilder unlimited("A"_sr, -1); unlimited.appendSplitPoints(firstShard); ASSERT(unlimited.appendShardBoundary("B"_sr)); diff --git a/fdbserver/core/StorageMetrics.cpp b/fdbserver/core/StorageMetrics.cpp index 72c8fb4a99..a0e77a21e5 100644 --- a/fdbserver/core/StorageMetrics.cpp +++ b/fdbserver/core/StorageMetrics.cpp @@ -628,7 +628,7 @@ std::vector StorageServerMetrics::getSplitPoints(KeyRangeRef range, IndexedSet::const_iterator endKey = byteSample.sample.index(byteSample.sample.sumTo(byteSample.sample.lower_bound(beginKey)) + chunkSize); while (endKey != byteSample.sample.end() && (limit < 0 || toReturn.size() < static_cast(limit))) { - if (*endKey > range.end) { + if (*endKey >= range.end) { break; } if (*endKey == beginKey) { @@ -960,7 +960,7 @@ TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/limit") { TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/requestFlatBufferRoundTrip") { SplitRangeRequest request(KeyRangeRef("A"_sr, "C"_sr), 1024, 2); const Standalone serialized = ObjectWriter::toValue(request, Unversioned()); - const SplitRangeRequest decoded = ObjectReader::fromStringRef(serialized, Unversioned()); + const auto decoded = ObjectReader::fromStringRef(serialized, Unversioned()); ASSERT(decoded.keys == request.keys); ASSERT_EQ(decoded.chunkSize, request.chunkSize); @@ -970,6 +970,25 @@ TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/requestFlatBufferRoun return Void(); } +TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/exclusiveEnd") { + int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; + StorageServerMetrics ssm; + + ssm.byteSample.sample.insert("A"_sr, 200 * sampleUnit); + ssm.byteSample.sample.insert("B"_sr, 800 * sampleUnit); + + std::vector direct = ssm.getSplitPoints(KeyRangeRef("A"_sr, "B"_sr), 600 * sampleUnit, {}, 1); + ASSERT(direct.empty()); + + SplitRangeRequest req(KeyRangeRef("A"_sr, "B"_sr), 600 * sampleUnit, 1); + Future reply = req.reply.getFuture(); + ssm.getSplitPoints(req, {}); + ASSERT(reply.isReady()); + ASSERT(reply.get().splitPoints.empty()); + + return Void(); +} + TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/noneSplitable") { int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; From 17c87353cd751b41a1254e248bbdb780210e1d91 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 17:18:29 -0700 Subject: [PATCH 10/39] Simplify split point limit support --- bindings/bindingtester/bindingtester.py | 3 - bindings/bindingtester/known_testers.py | 5 -- .../bindingtester/spec/bindingApiTester.md | 7 --- bindings/bindingtester/tests/api.py | 9 +-- bindings/flow/fdb_flow.cpp | 28 ++++------ bindings/flow/fdb_flow.h | 4 +- bindings/flow/tester/Tester.cpp | 24 -------- bindings/go/CMakeLists.txt | 1 - bindings/go/src/_stacktester/stacktester.go | 12 ---- bindings/go/src/fdb/snapshot.go | 2 +- bindings/go/src/fdb/transaction.go | 15 +---- .../go/src/fdb/transaction_internal_test.go | 45 --------------- .../foundationdb/test/AsyncStackTester.java | 6 -- .../foundationdb/test/StackOperation.java | 1 - .../apple/foundationdb/test/StackTester.java | 5 -- bindings/python/fdb/impl.py | 1 - bindings/python/tests/tester.py | 4 -- bindings/python/tests/unit_tests.py | 4 +- bindings/ruby/lib/fdbimpl.rb | 9 +-- bindings/ruby/tests/tester.rb | 3 - documentation/sphinx/source/api-python.rst | 2 +- documentation/sphinx/source/api-ruby.rst | 4 +- fdbclient/NativeAPI.actor.cpp | 55 ++++++++----------- .../fdbclient/StorageServerInterface.h | 11 +--- fdbserver/core/StorageMetrics.cpp | 14 ----- flow/ProtocolVersion.h.cmake | 1 - flow/ProtocolVersions.cmake | 1 - 27 files changed, 48 insertions(+), 228 deletions(-) delete mode 100644 bindings/go/src/fdb/transaction_internal_test.go diff --git a/bindings/bindingtester/bindingtester.py b/bindings/bindingtester/bindingtester.py index e757670aad..23f8d15909 100755 --- a/bindings/bindingtester/bindingtester.py +++ b/bindings/bindingtester/bindingtester.py @@ -314,9 +314,6 @@ class TestRunner(object): [not tester.directory_snapshot_ops_enabled for tester in self.testers] ) ) - self.args.range_split_points_with_limit_enabled = all( - [tester.range_split_points_with_limit_enabled for tester in self.testers] - ) def print_test(self): test_instructions = self._generate_test() diff --git a/bindings/bindingtester/known_testers.py b/bindings/bindingtester/known_testers.py index 3cac5d87d0..615d173a6a 100644 --- a/bindings/bindingtester/known_testers.py +++ b/bindings/bindingtester/known_testers.py @@ -48,7 +48,6 @@ class Tester: threads_enabled=True, types=COMMON_TYPES, directory_snapshot_ops_enabled=True, - range_split_points_with_limit_enabled=True, ): self.name = name self.cmd = cmd @@ -58,9 +57,6 @@ class Tester: self.threads_enabled = threads_enabled self.types = types self.directory_snapshot_ops_enabled = directory_snapshot_ops_enabled - self.range_split_points_with_limit_enabled = ( - range_split_points_with_limit_enabled - ) def supports_api_version(self, api_version): return ( @@ -136,6 +132,5 @@ testers = { 730, MAX_API_VERSION, directory_snapshot_ops_enabled=False, - range_split_points_with_limit_enabled=False, ), } diff --git a/bindings/bindingtester/spec/bindingApiTester.md b/bindings/bindingtester/spec/bindingApiTester.md index 9975c5bd73..25b9592ce6 100644 --- a/bindings/bindingtester/spec/bindingApiTester.md +++ b/bindings/bindingtester/spec/bindingApiTester.md @@ -178,13 +178,6 @@ futures must apply the following rules to the result: binding. Make sure the API returns without error. Finally push the string "GOT_RANGE_SPLIT_POINTS" onto the stack. -#### GET_RANGE_SPLIT_POINTS_WITH_LIMIT - - Pops the top four items off of the stack as BEGIN_KEY, END_KEY, CHUNK_SIZE - and LIMIT. Then call the limited `getRangeSplitPoints` API of the language - binding. Make sure the API returns without error. Finally push the string - "GOT_RANGE_SPLIT_POINTS" onto the stack. - #### GET_KEY (_SNAPSHOT, _DATABASE) Pops the top four items off of the stack as KEY, OR_EQUAL, OFFSET, PREFIX diff --git a/bindings/bindingtester/tests/api.py b/bindings/bindingtester/tests/api.py index 947d516a21..81979a14e1 100644 --- a/bindings/bindingtester/tests/api.py +++ b/bindings/bindingtester/tests/api.py @@ -209,8 +209,6 @@ class ApiTest(Test): ] txn_sizes = ["GET_APPROXIMATE_SIZE"] storage_metrics = ["GET_ESTIMATED_RANGE_SIZE", "GET_RANGE_SPLIT_POINTS"] - if args.api_version >= 800 and args.range_split_points_with_limit_enabled: - storage_metrics.append("GET_RANGE_SPLIT_POINTS_WITH_LIMIT") op_choices += reads op_choices += mutations @@ -655,7 +653,7 @@ class ApiTest(Test): instructions.push_args(key1, key2) instructions.append(op) self.add_strings(1) - elif op in ("GET_RANGE_SPLIT_POINTS", "GET_RANGE_SPLIT_POINTS_WITH_LIMIT"): + elif op == "GET_RANGE_SPLIT_POINTS": # Protect against inverted range and identical keys key1 = self.workspace.pack(self.random.random_tuple(1)) key2 = self.workspace.pack(self.random.random_tuple(1)) @@ -669,10 +667,7 @@ class ApiTest(Test): # TODO: randomize chunkSize but should not exceed 100M(shard limit) chunkSize = 10000000 # 10M - if op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": - instructions.push_args(key1, key2, chunkSize, random.randint(0, 2)) - else: - instructions.push_args(key1, key2, chunkSize) + instructions.push_args(key1, key2, chunkSize) instructions.append(op) self.add_strings(1) else: diff --git a/bindings/flow/fdb_flow.cpp b/bindings/flow/fdb_flow.cpp index 639a83cec3..9599cbece2 100644 --- a/bindings/flow/fdb_flow.cpp +++ b/bindings/flow/fdb_flow.cpp @@ -140,9 +140,7 @@ public: FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) override; Future getEstimatedRangeSizeBytes(const KeyRange& keys) override; - Future>> getRangeSplitPoints(const KeyRange& range, - int64_t chunkSize, - int limit = -1) override; + Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize) override; void addReadConflictRange(KeyRangeRef const& keys) override; void addReadConflictKey(KeyRef const& key) override; @@ -426,21 +424,17 @@ Future TransactionImpl::getEstimatedRangeSizeBytes(const KeyRange& keys } Future>> TransactionImpl::getRangeSplitPoints(const KeyRange& range, - int64_t chunkSize, - int limit) { - FDBFuture* f = - limit < 0 - ? fdb_transaction_get_range_split_points( - tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize) - : fdb_transaction_get_range_split_points_with_limit( - tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize, limit); - return backToFuture>>(f, [](Reference f) { - FDBKey const* ks; - int count; - throw_on_error(fdb_future_get_key_array(f->f, &ks, &count)); + int64_t chunkSize) { + return backToFuture>>( + fdb_transaction_get_range_split_points( + tr, range.begin.begin(), range.begin.size(), range.end.begin(), range.end.size(), chunkSize), + [](Reference f) { + FDBKey const* ks; + int count; + throw_on_error(fdb_future_get_key_array(f->f, &ks, &count)); - return FDBStandalone>(f, VectorRef((KeyRef*)ks, count)); - }); + return FDBStandalone>(f, VectorRef((KeyRef*)ks, count)); + }); } void TransactionImpl::addReadConflictRange(KeyRangeRef const& keys) { diff --git a/bindings/flow/fdb_flow.h b/bindings/flow/fdb_flow.h index 34545d0255..6aba6810d8 100644 --- a/bindings/flow/fdb_flow.h +++ b/bindings/flow/fdb_flow.h @@ -106,9 +106,7 @@ public: } virtual Future getEstimatedRangeSizeBytes(const KeyRange& keys) = 0; - virtual Future>> getRangeSplitPoints(const KeyRange& range, - int64_t chunkSize, - int limit = -1) = 0; + virtual Future>> getRangeSplitPoints(const KeyRange& range, int64_t chunkSize) = 0; virtual void addReadConflictRange(KeyRangeRef const& keys) = 0; virtual void addReadConflictKey(KeyRef const& key) = 0; diff --git a/bindings/flow/tester/Tester.cpp b/bindings/flow/tester/Tester.cpp index e0ca695e5e..331f4f3fc0 100644 --- a/bindings/flow/tester/Tester.cpp +++ b/bindings/flow/tester/Tester.cpp @@ -683,30 +683,6 @@ struct GetRangeSplitPoints : InstructionFunc { const char* GetRangeSplitPoints::name = "GET_RANGE_SPLIT_POINTS"; REGISTER_INSTRUCTION_FUNC(GetRangeSplitPoints); -struct GetRangeSplitPointsWithLimit : InstructionFunc { - static const char* name; - - static Future call(Reference data, Reference instruction) { - std::vector items = data->stack.pop(4); - if (items.size() != 4) - co_return; - - Standalone s1 = co_await items[0].value; - Standalone beginKey = Tuple::unpack(s1).getString(0); - Standalone s2 = co_await items[1].value; - Standalone endKey = Tuple::unpack(s2).getString(0); - Standalone s3 = co_await items[2].value; - int64_t chunkSize = Tuple::unpack(s3).getInt(0); - Standalone s4 = co_await items[3].value; - int limit = Tuple::unpack(s4).getInt(0); - - co_await instruction->tr->getRangeSplitPoints(KeyRangeRef(beginKey, endKey), chunkSize, limit); - data->stack.pushTuple("GOT_RANGE_SPLIT_POINTS"_sr); - } -}; -const char* GetRangeSplitPointsWithLimit::name = "GET_RANGE_SPLIT_POINTS_WITH_LIMIT"; -REGISTER_INSTRUCTION_FUNC(GetRangeSplitPointsWithLimit); - struct GetKeyFunc : InstructionFunc { static const char* name; diff --git a/bindings/go/CMakeLists.txt b/bindings/go/CMakeLists.txt index 4c99b8fff6..d4670e9496 100644 --- a/bindings/go/CMakeLists.txt +++ b/bindings/go/CMakeLists.txt @@ -21,7 +21,6 @@ set(SRCS src/fdb/database.go src/fdb/directory/directory_subspace.go src/fdb/fdb_test.go - src/fdb/transaction_internal_test.go src/fdb/snapshot.go go.mod) diff --git a/bindings/go/src/_stacktester/stacktester.go b/bindings/go/src/_stacktester/stacktester.go index 18a34ca23b..c1e5831455 100644 --- a/bindings/go/src/_stacktester/stacktester.go +++ b/bindings/go/src/_stacktester/stacktester.go @@ -590,18 +590,6 @@ func (sm *StackMachine) processInst(idx int, inst tuple.Tuple) { if err != nil { panic(err) } - case op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": - r := sm.popKeyRange() - chunkSize := sm.waitAndPop().item.(int64) - limit := int(sm.waitAndPop().item.(int64)) - _, err := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { - _ = rtr.GetRangeSplitPointsWithLimit(r, chunkSize, limit).MustGet() - sm.store(idx, []byte("GOT_RANGE_SPLIT_POINTS")) - return nil, nil - }) - if err != nil { - panic(err) - } case op == "COMMIT": sm.store(idx, sm.currentTransaction().Commit()) case op == "RESET": diff --git a/bindings/go/src/fdb/snapshot.go b/bindings/go/src/fdb/snapshot.go index 012e577ec8..c639009a5c 100644 --- a/bindings/go/src/fdb/snapshot.go +++ b/bindings/go/src/fdb/snapshot.go @@ -116,7 +116,7 @@ func (s Snapshot) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKeyAr } // GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. -// The start and end keys of the given range are always included. Limits larger than 2^31-1 are clamped to 2^31-1. +// The start and end keys of the given range are always included. func (s Snapshot) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { beginKey, endKey := r.FDBRangeKeys() return s.getRangeSplitPointsWithLimit( diff --git a/bindings/go/src/fdb/transaction.go b/bindings/go/src/fdb/transaction.go index 470bafb590..0594440840 100644 --- a/bindings/go/src/fdb/transaction.go +++ b/bindings/go/src/fdb/transaction.go @@ -355,17 +355,6 @@ func (t *transaction) getRangeSplitPoints(beginKey Key, endKey Key, chunkSize in } } -func normalizeRangeSplitPointLimit(limit int) int { - const maxLimit = 1<<31 - 1 - if limit < 0 { - return -1 - } - if limit > maxLimit { - return maxLimit - } - return limit -} - func (t *transaction) getRangeSplitPointsWithLimit(beginKey Key, endKey Key, chunkSize int64, limit int) FutureKeyArray { return &futureKeyArray{ future: newFuture(t, C.fdb_transaction_get_range_split_points_with_limit( @@ -375,7 +364,7 @@ func (t *transaction) getRangeSplitPointsWithLimit(beginKey Key, endKey Key, chu byteSliceToPtr(endKey), C.int(len(endKey)), C.int64_t(chunkSize), - C.int(normalizeRangeSplitPointLimit(limit)), + C.int(limit), )), } } @@ -393,7 +382,7 @@ func (t Transaction) GetRangeSplitPoints(r ExactRange, chunkSize int64) FutureKe } // GetRangeSplitPointsWithLimit returns at most limit interior split points, including shard boundaries. -// The start and end keys of the given range are always included. Limits larger than 2^31-1 are clamped to 2^31-1. +// The start and end keys of the given range are always included. func (t Transaction) GetRangeSplitPointsWithLimit(r ExactRange, chunkSize int64, limit int) FutureKeyArray { beginKey, endKey := r.FDBRangeKeys() return t.getRangeSplitPointsWithLimit( diff --git a/bindings/go/src/fdb/transaction_internal_test.go b/bindings/go/src/fdb/transaction_internal_test.go deleted file mode 100644 index fea26d83b3..0000000000 --- a/bindings/go/src/fdb/transaction_internal_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * transaction_internal_test.go - * - * 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. - */ - -package fdb - -import "testing" - -func TestNormalizeRangeSplitPointLimit(t *testing.T) { - maxInt := int(^uint(0) >> 1) - minInt := -maxInt - 1 - tests := []struct { - limit int - want int - }{ - {minInt, -1}, - {-1, -1}, - {0, 0}, - {2, 2}, - {1<<31 - 1, 1<<31 - 1}, - {maxInt, 1<<31 - 1}, - } - - for _, test := range tests { - if got := normalizeRangeSplitPointLimit(test.limit); got != test.want { - t.Errorf("normalizeRangeSplitPointLimit(%d) = %d, want %d", test.limit, got, test.want) - } - } -} diff --git a/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java b/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java index c847f433af..7e535e3e29 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/AsyncStackTester.java @@ -238,12 +238,6 @@ public class AsyncStackTester { inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); }, FDB.DEFAULT_EXECUTOR); } - else if (op == StackOperation.GET_RANGE_SPLIT_POINTS_WITH_LIMIT) { - List params = inst.popParams(4).join(); - return inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2), StackUtils.getInt(params.get(3))).thenAcceptAsync(splitPoints -> { - inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); - }, FDB.DEFAULT_EXECUTOR); - } else if(op == StackOperation.GET_RANGE) { return inst.popParams(5).thenComposeAsync(params -> { int limit = StackUtils.getInt(params.get(2)); diff --git a/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java b/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java index 8cc19872fc..c72abb9724 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/StackOperation.java @@ -58,7 +58,6 @@ enum StackOperation { GET_VERSIONSTAMP, GET_ESTIMATED_RANGE_SIZE, GET_RANGE_SPLIT_POINTS, - GET_RANGE_SPLIT_POINTS_WITH_LIMIT, SET_READ_VERSION, ON_ERROR, SUB, diff --git a/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java index 8c41237d36..d12d926143 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java @@ -218,11 +218,6 @@ public class StackTester { KeyArrayResult splitPoints = inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2)).join(); inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); } - else if (op == StackOperation.GET_RANGE_SPLIT_POINTS_WITH_LIMIT) { - List params = inst.popParams(4).join(); - KeyArrayResult splitPoints = inst.readTr.getRangeSplitPoints((byte[])params.get(0), (byte[])params.get(1), (long)params.get(2), StackUtils.getInt(params.get(3))).join(); - inst.push("GOT_RANGE_SPLIT_POINTS".getBytes()); - } else if(op == StackOperation.GET_RANGE) { List params = inst.popParams(5).join(); diff --git a/bindings/python/fdb/impl.py b/bindings/python/fdb/impl.py index c898f422fe..195b1f3240 100644 --- a/bindings/python/fdb/impl.py +++ b/bindings/python/fdb/impl.py @@ -555,7 +555,6 @@ class TransactionRead(_FDBBase): chunk_size, ) ) - limit = min(limit, 2**31 - 1) return FutureKeyArray( self.capi.fdb_transaction_get_range_split_points_with_limit( self.tpointer, diff --git a/bindings/python/tests/tester.py b/bindings/python/tests/tester.py index a232bae784..a68059958e 100644 --- a/bindings/python/tests/tester.py +++ b/bindings/python/tests/tester.py @@ -248,10 +248,6 @@ class Tester: begin, end, chunkSize = inst.pop(3) obj.get_range_split_points(begin, end, chunkSize).wait() inst.push(b"GOT_RANGE_SPLIT_POINTS") - elif inst.op == "GET_RANGE_SPLIT_POINTS_WITH_LIMIT": - begin, end, chunkSize, limit = inst.pop(4) - obj.get_range_split_points(begin, end, chunkSize, limit).wait() - inst.push(b"GOT_RANGE_SPLIT_POINTS") elif inst.op == "GET_KEY": key, or_equal, offset, prefix = inst.pop(4) result = obj.get_key(fdb.KeySelector(key, or_equal, offset)) diff --git a/bindings/python/tests/unit_tests.py b/bindings/python/tests/unit_tests.py index c95022007c..767f61d8ca 100644 --- a/bindings/python/tests/unit_tests.py +++ b/bindings/python/tests/unit_tests.py @@ -231,12 +231,12 @@ def test_range_split_points(db): end = b"\x02range-split-points-z" tr = db.create_transaction() - for limit in (-(2**63), -1, 0, 1, 2, 2**31, 2**63): + for limit in (-1, 0, 1, 2): split_points = tr.get_range_split_points(begin, end, 1000000, limit).wait() assert split_points[0] == begin assert split_points[-1] == end if limit >= 0: - assert len(split_points) <= min(limit, 2**31 - 1) + 2 + assert len(split_points) <= limit + 2 def run_unit_tests(db): diff --git a/bindings/ruby/lib/fdbimpl.rb b/bindings/ruby/lib/fdbimpl.rb index 5fe5195fe0..b46e34584c 100644 --- a/bindings/ruby/lib/fdbimpl.rb +++ b/bindings/ruby/lib/fdbimpl.rb @@ -111,7 +111,6 @@ module FDB attach_function :fdb_transaction_get_range, [ :pointer, :pointer, :int, :int, :int, :pointer, :int, :int, :int, :int, :int, :int, :int, :int, :int ], :pointer attach_function :fdb_transaction_get_estimated_range_size_bytes, [ :pointer, :pointer, :int, :pointer, :int ], :pointer attach_function :fdb_transaction_get_range_split_points, [ :pointer, :pointer, :int, :pointer, :int, :int64 ], :pointer - attach_function :fdb_transaction_get_range_split_points_with_limit, [ :pointer, :pointer, :int, :pointer, :int, :int64, :int ], :pointer attach_function :fdb_transaction_set, [ :pointer, :pointer, :int, :pointer, :int ], :void attach_function :fdb_transaction_clear, [ :pointer, :pointer, :int ], :void attach_function :fdb_transaction_clear_range, [ :pointer, :pointer, :int, :pointer, :int ], :void @@ -850,17 +849,13 @@ module FDB Int64Future.new(FDBC.fdb_transaction_get_estimated_range_size_bytes(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize)) end - def get_range_split_points(begin_key, end_key, chunk_size, limit = -1) + def get_range_split_points(begin_key, end_key, chunk_size) if chunk_size <=0 raise ArgumentError, "Invalid chunk size" end bkey = FDB.key_to_bytes(begin_key) ekey = FDB.key_to_bytes(end_key) - if limit < 0 - FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size)) - else - FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points_with_limit(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size, limit)) - end + FutureKeyArray.new(FDBC.fdb_transaction_get_range_split_points(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, chunk_size)) end end diff --git a/bindings/ruby/tests/tester.rb b/bindings/ruby/tests/tester.rb index d273ba5d7d..e7d9ad1716 100755 --- a/bindings/ruby/tests/tester.rb +++ b/bindings/ruby/tests/tester.rb @@ -323,9 +323,6 @@ class Tester when "GET_RANGE_SPLIT_POINTS" inst.tr.get_range_split_points(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop).wait() inst.push("GOT_RANGE_SPLIT_POINTS") - when "GET_RANGE_SPLIT_POINTS_WITH_LIMIT" - inst.tr.get_range_split_points(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop).wait() - inst.push("GOT_RANGE_SPLIT_POINTS") when "GET_KEY" selector = FDB::KeySelector.new(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop) prefix = inst.wait_and_pop diff --git a/documentation/sphinx/source/api-python.rst b/documentation/sphinx/source/api-python.rst index 02617447f6..6164495c2b 100644 --- a/documentation/sphinx/source/api-python.rst +++ b/documentation/sphinx/source/api-python.rst @@ -841,7 +841,7 @@ Transaction misc functions .. method:: Transaction.get_range_split_points(self, begin_key, end_key, chunk_size, limit=-1) - Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Limits larger than ``2**31 - 1`` are clamped to ``2**31 - 1``. Returns a :class:`FutureKeyArray`. + Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Returns a :class:`FutureKeyArray`. .. note:: The returned split points contain the start key and end key of the given range .. method:: Transaction.get_approximate_size() diff --git a/documentation/sphinx/source/api-ruby.rst b/documentation/sphinx/source/api-ruby.rst index 51ca6b6f6c..c0876d8b16 100644 --- a/documentation/sphinx/source/api-ruby.rst +++ b/documentation/sphinx/source/api-ruby.rst @@ -747,9 +747,9 @@ Transaction misc functions .. note:: The estimated size is calculated based on the sampling done by FDB server. The sampling algorithm works roughly in this way: the larger the key-value pair is, the more likely it would be sampled and the more accurate its sampled size would be. And due to that reason it is recommended to use this API to query against large ranges for accuracy considerations. For a rough reference, if the returned size is larger than 3MB, one can consider the size to be accurate. -.. method:: Transaction.get_range_split_points(begin_key, end_key, chunk_size, limit=-1) -> FutureKeyArray +.. method:: Transaction.get_range_split_points(begin_key, end_key, chunk_size) -> FutureKeyArray - Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. A non-negative ``limit`` caps the number of interior split points, including shard boundaries. Returns a :class:`FutureKeyArray`. + Gets a list of keys that can split the given range into (roughly) equally sized chunks based on ``chunk_size``. Returns a :class:`FutureKeyArray`. .. note:: The returned split points contain the start key and end key of the given range .. method:: Transaction.get_approximate_size() -> Int64Future diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 364788b0e4..46706d74b5 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -5991,18 +5991,14 @@ public: int getRemaining() const { return remaining; } - bool appendShardBoundary(KeyRef boundary) { - if (results.back() == boundary) { - return true; - } - if (remaining == 0) { - return false; + void appendShardBoundary(KeyRef boundary) { + if (results.back() == boundary || remaining == 0) { + return; } results.push_back_deep(results.arena(), boundary); if (remaining > 0) { --remaining; } - return true; } void appendSplitPoints(Standalone> const& splitPoints) { @@ -6034,10 +6030,9 @@ ACTOR Future>> getRangeSplitPoints(ReferencespanContext); state Key beginKey = keys.begin; - state Optional results; - results = RangeSplitPointsBuilder(keys.begin, limit); + state RangeSplitPointsBuilder results(keys.begin, limit); if (limit == 0) { - return results.get().finish(keys.end); + return results.finish(keys.end); } loop { @@ -6045,32 +6040,28 @@ ACTOR Future>> getRangeSplitPoints(ReferenceTOO_MANY, CLIENT_KNOBS->STORAGE_METRICS_SHARD_LIMIT), + results.getRemaining(), CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->STORAGE_METRICS_SHARD_LIMIT), Reverse::False, &StorageServerInterface::getRangeSplitPoints)); try { state int nLocs = locations.size(); - if (limit >= 0 && nLocs - 1 > results.get().getRemaining()) { - nLocs = results.get().getRemaining() + 1; - } if (limit >= 0) { state int i = 0; for (; i < nLocs; i++) { - if ((i > 0 || beginKey != keys.begin) && - !results.get().appendShardBoundary(locations[i].range.begin)) { - break; + if (i > 0 || beginKey != keys.begin) { + results.appendShardBoundary(locations[i].range.begin); } - if (results.get().getRemaining() == 0) { + if (results.getRemaining() == 0) { break; } KeyRef partBegin = (i == 0) ? beginKey : locations[i].range.begin; KeyRef partEnd = std::min(keys.end, locations[i].range.end); - SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, results.get().getRemaining()); + SplitRangeRequest req(KeyRangeRef(partBegin, partEnd), chunkSize, results.getRemaining()); SplitRangeReply reply = wait(loadBalance(locations[i].locations->locations(), &StorageServerInterface::getRangeSplitPoints, req, TaskPriority::DataDistribution)); - results.get().appendSplitPoints(reply.splitPoints); + results.appendSplitPoints(reply.splitPoints); } } else { state std::vector> fReplies(nLocs); @@ -6086,13 +6077,13 @@ ACTOR Future>> getRangeSplitPoints(Reference 0 || beginKey != keys.begin) { - results.get().appendShardBoundary(locations[i].range.begin); + results.appendShardBoundary(locations[i].range.begin); } - results.get().appendSplitPoints(fReplies[i].get().splitPoints); + results.appendSplitPoints(fReplies[i].get().splitPoints); } } - if (results.get().getRemaining() == 0 || keys.end <= locations.back().range.end) { - return results.get().finish(keys.end); + if (results.getRemaining() == 0 || keys.end <= locations.back().range.end) { + return results.finish(keys.end); } beginKey = locations.back().range.end; } catch (Error& e) { @@ -6146,30 +6137,30 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { RangeSplitPointsBuilder zero("A"_sr, 0); zero.appendSplitPoints(firstShard); - ASSERT(!zero.appendShardBoundary("B"_sr)); + zero.appendShardBoundary("B"_sr); Standalone> zeroResults = zero.finish("Z"_sr); ASSERT(zeroResults.size() == 2 && zeroResults[0] == "A"_sr && zeroResults[1] == "Z"_sr); RangeSplitPointsBuilder one("A"_sr, 1); one.appendSplitPoints(firstShard); ASSERT(one.getRemaining() == 0); - ASSERT(!one.appendShardBoundary("B"_sr)); + one.appendShardBoundary("B"_sr); Standalone> oneResults = one.finish("Z"_sr); ASSERT(oneResults.size() == 3 && oneResults[1] == "A1"_sr && oneResults[2] == "Z"_sr); RangeSplitPointsBuilder two("A"_sr, 2); - ASSERT(two.appendShardBoundary("B"_sr)); + two.appendShardBoundary("B"_sr); ASSERT(two.getRemaining() == 1); two.appendSplitPoints(secondShard); ASSERT(two.getRemaining() == 0); - ASSERT(!two.appendShardBoundary("C"_sr)); + two.appendShardBoundary("C"_sr); Standalone> twoResults = two.finish("Z"_sr); ASSERT(twoResults.size() == 4 && twoResults[1] == "B"_sr && twoResults[2] == "B1"_sr && twoResults[3] == "Z"_sr); RangeSplitPointsBuilder four("A"_sr, 4); four.appendSplitPoints(firstShard); ASSERT(four.getRemaining() == 2); - ASSERT(four.appendShardBoundary("B"_sr)); + four.appendShardBoundary("B"_sr); ASSERT(four.getRemaining() == 1); four.appendSplitPoints(secondShard); ASSERT(four.getRemaining() == 0); @@ -6180,7 +6171,7 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { RangeSplitPointsBuilder duplicateBoundary("A"_sr, 2); duplicateBoundary.appendSplitPoints(firstShardEndingAtBoundary); ASSERT(duplicateBoundary.getRemaining() == 1); - ASSERT(duplicateBoundary.appendShardBoundary("B"_sr)); + duplicateBoundary.appendShardBoundary("B"_sr); ASSERT(duplicateBoundary.getRemaining() == 1); duplicateBoundary.appendSplitPoints(secondShard); Standalone> duplicateBoundaryResults = duplicateBoundary.finish("Z"_sr); @@ -6189,9 +6180,9 @@ TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/multipleShards") { RangeSplitPointsBuilder unlimited("A"_sr, -1); unlimited.appendSplitPoints(firstShard); - ASSERT(unlimited.appendShardBoundary("B"_sr)); + unlimited.appendShardBoundary("B"_sr); unlimited.appendSplitPoints(secondShard); - ASSERT(unlimited.appendShardBoundary("C"_sr)); + unlimited.appendShardBoundary("C"_sr); Standalone> unlimitedResults = unlimited.finish("Z"_sr); ASSERT(unlimitedResults.size() == 8 && unlimitedResults[1] == "A1"_sr && unlimitedResults[2] == "A2"_sr && unlimitedResults[3] == "B"_sr && unlimitedResults[4] == "B1"_sr && unlimitedResults[5] == "B2"_sr && diff --git a/fdbclient/include/fdbclient/StorageServerInterface.h b/fdbclient/include/fdbclient/StorageServerInterface.h index bc60a307f2..9b9f841994 100644 --- a/fdbclient/include/fdbclient/StorageServerInterface.h +++ b/fdbclient/include/fdbclient/StorageServerInterface.h @@ -788,16 +788,7 @@ struct SplitRangeRequest { template void serialize(Ar& ar) { - if constexpr (is_fb_function) { - // FlatBuffer visitors must see every field in one call because each visit starts at field zero. - serializer(ar, keys, chunkSize, reply, limit, arena); - } else { - serializer(ar, keys, chunkSize, reply); - if (ar.protocolVersion().hasRangeSplitPointsLimit()) { - serializer(ar, limit); - } - serializer(ar, arena); - } + serializer(ar, keys, chunkSize, reply, limit, arena); } }; diff --git a/fdbserver/core/StorageMetrics.cpp b/fdbserver/core/StorageMetrics.cpp index a0e77a21e5..a403a0eead 100644 --- a/fdbserver/core/StorageMetrics.cpp +++ b/fdbserver/core/StorageMetrics.cpp @@ -24,7 +24,6 @@ #include "flow/CodeProbe.h" #include "flow/Hash3.h" #include "flow/IRandom.h" -#include "flow/ObjectSerializer.h" #include "flow/Trace.h" #include "flow/UnitTest.h" #include "flow/CoroUtils.h" @@ -957,19 +956,6 @@ TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/limit") { return Void(); } -TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/requestFlatBufferRoundTrip") { - SplitRangeRequest request(KeyRangeRef("A"_sr, "C"_sr), 1024, 2); - const Standalone serialized = ObjectWriter::toValue(request, Unversioned()); - const auto decoded = ObjectReader::fromStringRef(serialized, Unversioned()); - - ASSERT(decoded.keys == request.keys); - ASSERT_EQ(decoded.chunkSize, request.chunkSize); - ASSERT_EQ(decoded.limit, request.limit); - ASSERT_EQ(decoded.reply.getEndpoint().token, request.reply.getEndpoint().token); - - return Void(); -} - TEST_CASE("/fdbserver/StorageMetricSample/rangeSplitPoints/exclusiveEnd") { int64_t sampleUnit = SERVER_KNOBS->BYTES_READ_UNITS_PER_SAMPLE; StorageServerMetrics ssm; diff --git a/flow/ProtocolVersion.h.cmake b/flow/ProtocolVersion.h.cmake index a5add2bfaa..5495d83ec4 100644 --- a/flow/ProtocolVersion.h.cmake +++ b/flow/ProtocolVersion.h.cmake @@ -180,7 +180,6 @@ public: // introduced features PROTOCOL_VERSION_FEATURE(@FDB_PV_MUTATION_CHECKSUM@, MutationChecksum); PROTOCOL_VERSION_FEATURE(@FDB_PV_RANGE_PARTITIONED_BACKUP_WORKER@, RangePartitionedBackupWorker); PROTOCOL_VERSION_FEATURE(@FDB_PV_NATIVE_CDC@, NativeCdc); - PROTOCOL_VERSION_FEATURE(@FDB_PV_RANGE_SPLIT_POINTS_LIMIT@, RangeSplitPointsLimit); }; template <> diff --git a/flow/ProtocolVersions.cmake b/flow/ProtocolVersions.cmake index b6a80dc20a..cf6db7c51f 100644 --- a/flow/ProtocolVersions.cmake +++ b/flow/ProtocolVersions.cmake @@ -97,4 +97,3 @@ set(FDB_PV_MUTATION_CHECKSUM "0x0FDB00B074000000LL") set(FDB_PV_GRPC_ENDPOINT "0x0FDB00B080000000LL") set(FDB_PV_RANGE_PARTITIONED_BACKUP_WORKER "0x0FDB00B080000000LL") set(FDB_PV_NATIVE_CDC "0x0FDB00B080000000LL") -set(FDB_PV_RANGE_SPLIT_POINTS_LIMIT "0x0FDB00B080000000LL") From 1f217f61d67208503b6c048d4fbd952cb57fff89 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 14 Jul 2026 20:43:34 -0700 Subject: [PATCH 11/39] Retire recovered TLogs after recovery completes --- fdbserver/tlog/TLogServer.cpp | 178 +++++++++++++++++++++++++- fdbserver/workloads/GcGenerations.cpp | 15 ++- 2 files changed, 185 insertions(+), 8 deletions(-) diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index db3552e483..e760fa2e54 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -348,6 +348,7 @@ struct TLogData : NonCopyable { Promise terminated; FlowLock concurrentLogRouterReads; Reference persistentDataCommitLock; + bool retireRecoveredLogsRunning = false; // Beginning of fields used by snapshot based backup and restore double ignorePopDeadline; // time until which the ignorePopRequest will be @@ -523,7 +524,11 @@ struct LogData : NonCopyable, public ReferenceCounted { */ AsyncTrigger stopCommit; + AsyncTrigger persistentDataUpdated; bool initialized; + bool retirementRequested = false; + bool retirementStarted = false; + bool retired = false; Promise stoppedPromise; DBRecoveryCount recoveryCount; @@ -1259,6 +1264,167 @@ Future updatePersistentData(TLogData* self, Reference logData, Ve } } logData->newPersistentDataVersion = invalidVersion; + logData->persistentDataUpdated.trigger(); +} + +void advanceRetiredLogQueues(TLogData* self) { + // A restored queue has no committed location until its first post-recovery commit. + if (self->queueCommitEnd.get() == 0) { + return; + } + + while (!self->popOrder.empty()) { + auto log = self->id_data.find(self->popOrder.front()); + if (log == self->id_data.end()) { + self->popOrder.pop_front(); + continue; + } + if (!log->second->retired || log->second->persistentDataDurableVersion != log->second->version.get()) { + break; + } + + if (!log->second->versionLocation.empty()) { + auto lastLocation = log->second->versionLocation.lastItem(); + self->persistentQueue->pop(lastLocation->value.second); + } + log->second->queuePoppedVersion = std::max(log->second->queuePoppedVersion, log->second->version.get()); + self->popOrder.pop_front(); + } +} + +Future waitForRetirementStep(Future step, Future removed) { + if (removed.isReady()) { + co_return false; + } + auto result = co_await race(step, errorOr(removed)); + co_return result.index() == 0 && !removed.isReady(); +} + +Future retireRecoveredLog(TLogData* self, Reference logData) { + if (!logData->stopped()) { + if (!co_await waitForRetirementStep(logData->stoppedPromise.getFuture(), logData->removed)) { + co_return; + } + } + ASSERT(logData->stopped()); + ASSERT(logData->version.get() < MAX_VERSION); + if (!co_await waitForRetirementStep(logData->queueCommittedVersion.whenAtLeast(logData->version.get()), + logData->removed)) { + co_return; + } + if (!self->id_data.contains(logData->logId)) { + co_return; + } + while (logData->persistentDataDurableVersion < logData->version.get()) { + Future persistentDataUpdated = logData->persistentDataUpdated.onTrigger(); + if (logData->persistentDataDurableVersion < logData->version.get()) { + if (!co_await waitForRetirementStep(persistentDataUpdated, logData->removed)) { + co_return; + } + } + if (!self->id_data.contains(logData->logId)) { + co_return; + } + } + ASSERT(logData->persistentDataDurableVersion == logData->version.get()); + + Reference persistentDataCommitLock = self->persistentDataCommitLock; + co_await persistentDataCommitLock->take(); + FlowLock::Releaser commitLockReleaser(*persistentDataCommitLock); + if (!self->id_data.contains(logData->logId)) { + co_return; + } + ASSERT(logData->persistentDataVersion == logData->version.get()); + ASSERT(logData->persistentDataDurableVersion == logData->version.get()); + + const Version popTo = logData->version.get() + 1; + for (int tagLocality = 0; tagLocality < logData->tag_data.size(); ++tagLocality) { + for (int tagId = 0; tagId < logData->tag_data[tagLocality].size(); ++tagId) { + Reference tagData = logData->tag_data[tagLocality][tagId]; + if (tagData && tagData->popped < popTo) { + tagData->popped = popTo; + tagData->poppedRecently = true; + co_await tagData->eraseMessagesBefore(popTo, self, logData, TaskPriority::UpdateStorage); + } + } + } + for (const auto& locality : logData->tag_data) { + for (const auto& tagData : locality) { + if (tagData) { + updatePersistentPopped(self, logData, tagData); + } + } + } + double tLogMaxCreateDuration = SERVER_KNOBS->TLOG_MAX_CREATE_DURATION; + if (g_network->isSimulated() && logData->logSpillType == TLogSpillType::VALUE) { + tLogMaxCreateDuration *= 2; + } + co_await ioTimeoutError(self->persistentData->commit(), tLogMaxCreateDuration, "TLogRetireCommit"); + + logData->retired = true; + advanceRetiredLogQueues(self); +} + +Reference getNextRecoveredLogToRetire(TLogData* self) { + // updateStorage owns spillOrder removals; changing its front here can skip the following generation. + if (!self->spillOrder.empty()) { + auto log = self->id_data.find(self->spillOrder.front()); + if (log != self->id_data.end() && log->second->retirementRequested && !log->second->retirementStarted && + !log->second->retired) { + return log->second; + } + } + for (const auto& entry : self->id_data) { + const auto& logData = entry.second; + if (logData->retirementRequested && !logData->retirementStarted && !logData->retired && + logData->persistentDataDurableVersion == logData->version.get()) { + return logData; + } + } + return {}; +} + +Future retireRecoveredLogs(TLogData* self) { + while (Reference logData = getNextRecoveredLogToRetire(self)) { + logData->retirementStarted = true; + co_await retireRecoveredLog(self, logData); + } + self->retireRecoveredLogsRunning = false; +} + +void startRetiringRecoveredLogs(TLogData* self) { + if (!self->retireRecoveredLogsRunning) { + self->retireRecoveredLogsRunning = true; + self->sharedActors.send(retireRecoveredLogs(self)); + } +} + +Future monitorRetainedOldLogs(TLogData* self) { + while (true) { + Future dbInfoChange = self->dbInfo->onChange(); + const auto& dbInfo = self->dbInfo->get(); + bool retirementRequested = false; + if (dbInfo.recoveryState == RecoveryState::FULLY_RECOVERED) { + for (const auto& entry : self->id_data) { + const auto& logData = entry.second; + bool currentLog = false; + for (const auto& logSet : dbInfo.logSystemConfig.tLogs) { + if (std::find(logSet.tLogs.begin(), logSet.tLogs.end(), logData->logId) != logSet.tLogs.end()) { + currentLog = true; + break; + } + } + if (!currentLog && dbInfo.logSystemConfig.hasTLog(logData->logId) && !logData->retirementRequested) { + logData->retirementRequested = true; + retirementRequested = true; + } + } + } + if (retirementRequested) { + startRetiringRecoveredLogs(self); + } + co_await dbInfoChange; + } } Future tLogPopCore(TLogData* self, Tag inputTag, Version to, Reference logData) { @@ -1412,8 +1578,13 @@ double getTLogStorageUpdateDelayDuration() { // This actor is just a loop that calls updatePersistentData and popDiskQueue whenever // (a) there's data to be spilled or (b) we should update metadata after some commits have been fully popped. Future updateStorage(TLogData* self) { + bool removedGeneration = false; while (!self->spillOrder.empty() && !self->id_data.contains(self->spillOrder.front())) { self->spillOrder.pop_front(); + removedGeneration = true; + } + if (removedGeneration) { + startRetiringRecoveredLogs(self); } if (self->spillOrder.empty()) { @@ -1429,7 +1600,7 @@ Future updateStorage(TLogData* self) { FlowLock::Releaser commitLockReleaser; if (logData->stopped()) { - if (self->bytesInput - self->bytesDurable >= self->targetVolatileBytes) { + if (self->bytesInput - self->bytesDurable >= self->targetVolatileBytes || logData->retirementRequested) { while (logData->persistentDataDurableVersion != logData->version.get()) { totalSize = 0; Map>::iterator sizeItr = logData->version_sizes.begin(); @@ -1468,6 +1639,9 @@ Future updateStorage(TLogData* self) { if (logData->persistentDataDurableVersion == logData->version.get()) { self->spillOrder.pop_front(); + if (logData->retirementRequested) { + startRetiringRecoveredLogs(self); + } } co_await delay(0.0, TaskPriority::UpdateStorage); } else { @@ -2350,6 +2524,7 @@ Future doQueueCommit(TLogData* self, CODE_PROBE(true, "A TLog was replaced before having a chance to commit its queue", probe::decoration::rare); it->queueCommittedVersion.set(it->version.get()); } + advanceRetiredLogQueues(self); } Future commitQueue(TLogData* self) { @@ -3952,6 +4127,7 @@ Future tLog(IKeyValueStore* persistentData, self.sharedActors.send(commitQueue(&self)); self.sharedActors.send(updateStorageLoop(&self)); + self.sharedActors.send(monitorRetainedOldLogs(&self)); self.sharedActors.send(traceRole(Role::SHARED_TRANSACTION_LOG, tlogId)); Future activeSharedChange = Void(); diff --git a/fdbserver/workloads/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index 8de9314b71..1832603510 100644 --- a/fdbserver/workloads/GcGenerations.cpp +++ b/fdbserver/workloads/GcGenerations.cpp @@ -162,7 +162,6 @@ struct GcGenerationsWorkload : TestWorkload { Future generateMultipleTxnGenerations(GcGenerationsWorkload* self, Database cx) { co_await self->clogRemoteDc(self, cx); - int generationCount = 0; int successfulReboots = 0; while (successfulReboots < 6) { // Re-enable connection failures each iteration to keep the partition active. @@ -176,7 +175,6 @@ struct GcGenerationsWorkload : TestWorkload { .detail("Iteration", successfulReboots) .detail("RecoveryState", self->dbInfo->get().recoveryState); co_await self->dbAvailable(self, /*rebootRemoteDcMaster=*/true); - generationCount = self->dbInfo->get().logSystemConfig.oldTLogs.size(); // 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 @@ -188,21 +186,24 @@ struct GcGenerationsWorkload : TestWorkload { continue; } + const LogEpoch previousEpoch = self->dbInfo->get().logSystemConfig.epoch; + const int previousGenerationCount = self->dbInfo->get().logSystemConfig.oldTLogs.size(); auto masterAddr = self->dbInfo->get().master.address(); TraceEvent("RebootingPrimaryDcMaster").detail("Iteration", successfulReboots).detail("Master", masterAddr); g_simulator->rebootProcess(g_simulator->getProcessByAddress(masterAddr), ISimulator::KillType::Reboot); // Wait for recovery to create a new generation. - while (self->dbInfo->get().logSystemConfig.oldTLogs.size() == generationCount || + while (self->dbInfo->get().logSystemConfig.epoch <= previousEpoch || self->dbInfo->get().recoveryState < RecoveryState::RECOVERY_TRANSACTION) { co_await self->dbInfo->onChange(); } TraceEvent("CurrentGenerations") .detail("Iteration", successfulReboots) - .detail("PrevCount", generationCount) - .detail("New", self->dbInfo->get().logSystemConfig.oldTLogs.size()); - ASSERT(self->dbInfo->get().logSystemConfig.oldTLogs.size() > generationCount); - generationCount = self->dbInfo->get().logSystemConfig.oldTLogs.size(); + .detail("PreviousEpoch", previousEpoch) + .detail("NewEpoch", self->dbInfo->get().logSystemConfig.epoch) + .detail("PreviousCount", previousGenerationCount) + .detail("NewCount", self->dbInfo->get().logSystemConfig.oldTLogs.size()); + ASSERT(self->dbInfo->get().logSystemConfig.epoch > previousEpoch); ++successfulReboots; } TraceEvent("AfterMultipleRecovery") From 6e30915cfcea18ac398817ea31458a220796fbf6 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 14 Jul 2026 21:33:43 -0700 Subject: [PATCH 12/39] Fix LogSystem clang-tidy warning --- fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index 8e965d5eb1..e53a0a1699 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -616,7 +616,7 @@ void LogPushData::writeTypedMessage(T const& item, bool metadataMessage, bool al ASSERT(this->subsequence > 0); } } else { - ASSERT(writtenLocations.size() == 0); + ASSERT(writtenLocations.empty()); } uint32_t subseq = this->subsequence++; From 9c3949d4093706b4cd2ebe632d143ff046af51cc Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 13:18:11 -0700 Subject: [PATCH 13/39] Convert worker actor to coroutines --- .../foundationdb_subsystem_map.md | 2 +- .../include/fdbserver/core/ServerDBInfo.h | 2 +- .../fdbserver/core/WorkerInterface.actor.h | 6 +- .../worker/{worker.actor.cpp => worker.cpp} | 855 +++++++++--------- 4 files changed, 451 insertions(+), 414 deletions(-) rename fdbserver/worker/{worker.actor.cpp => worker.cpp} (86%) diff --git a/design/AI-generated/foundationdb_subsystem_map.md b/design/AI-generated/foundationdb_subsystem_map.md index e3e472f7d6..bcf576a46e 100644 --- a/design/AI-generated/foundationdb_subsystem_map.md +++ b/design/AI-generated/foundationdb_subsystem_map.md @@ -97,7 +97,7 @@ Plus supporting code: [`fdbserver/worker/`](https://github.com/apple/foundationd - Coordinator state is persisted via `KeyValueStoreMemory` backed by a `DiskQueue` (`.fdq` files) -- the same engine used by TLogs. **Role recruitment:** -- Worker processes register with CC via `registrationClient()` ([`worker.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/worker/worker.actor.cpp)). +- Worker processes register with CC via `registrationClient()` ([`worker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/worker/worker.cpp)). - CC maintains a pool of available workers with their `ProcessClass` and `LocalityData`. - When the transaction system needs to be (re)constituted, CC recruits: Master/Sequencer, CommitProxies, GrvProxies, Resolvers, TLogs. - Recruitment considers fitness (class match), locality (datacenter placement), and excludes failed/excluded processes. diff --git a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h index 5d94835bb3..6adc7b1652 100644 --- a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h +++ b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h @@ -111,7 +111,7 @@ struct GetServerDBInfoRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h index 38bf059807..73d0cbb982 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h @@ -776,7 +776,7 @@ struct RecruitMasterRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; @@ -805,7 +805,7 @@ struct InitializeCommitProxyRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; @@ -822,7 +822,7 @@ struct InitializeGrvProxyRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; diff --git a/fdbserver/worker/worker.actor.cpp b/fdbserver/worker/worker.cpp similarity index 86% rename from fdbserver/worker/worker.actor.cpp rename to fdbserver/worker/worker.cpp index 6bea87ca19..8a546b2a0b 100644 --- a/fdbserver/worker/worker.actor.cpp +++ b/fdbserver/worker/worker.cpp @@ -1,5 +1,5 @@ /* - * worker.actor.cpp + * worker.cpp * * This source file is part of the FoundationDB open source project * @@ -61,6 +61,7 @@ #include "fdbserver/core/BackupInterface.h" #include "RoleLineage.h" #include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/CoroFlow.h" #include "fdbserver/worker/Worker.h" #include "fdbserver/kvstore/IKeyValueStore.h" #include "fdbserver/ratekeeper/Ratekeeper.h" @@ -105,7 +106,7 @@ #include #endif #include "fdbserver/core/TesterInterface.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/CoroUtils.h" #if CENABLED(0, NOT_IN_CLEAN) extern IKeyValueStore* keyValueStoreCompressTestData(IKeyValueStore* store); @@ -118,7 +119,7 @@ extern IKeyValueStore* keyValueStoreCompressTestData(IKeyValueStore* store); struct ErrorInfo { Error error; - const Role& role; + Role role; UID id; ErrorInfo(Error e, const Role& role, UID id) : error(e), role(role), id(id) {} template @@ -165,44 +166,49 @@ Future forwardError(PromiseStream errors, Role role, UID id, Fu } } -ACTOR Future handleIOErrors(Future actor, - Future> storeError, - UID id, - Future onClosed = Void()) { - choose { - when(state ErrorOr e = wait(errorOr(actor))) { - if (e.isError() && e.getError().code() == error_code_please_reboot) { - // no need to wait. - } else { - wait(onClosed); - } - if (e.isError() && e.getError().code() == error_code_broken_promise && !storeError.isReady()) { - wait(delay(0.00001 + FLOW_KNOBS->MAX_BUGGIFIED_DELAY)); - } - if (storeError.isReady() && storeError.isError() && - storeError.getError().code() != error_code_file_not_found) { - throw storeError.get().getError(); - } - if (e.isError()) { - throw e.getError(); - } else - return e.get(); +Future handleIOErrors(Future actor, + Future> storeError, + UID id, + Future onClosed = Void()) { + auto res = co_await race(errorOr(actor), storeError); + if (res.index() == 0) { + ErrorOr e = std::get<0>(std::move(res)); + + if (e.isError() && e.getError().code() == error_code_please_reboot) { + // no need to wait. + } else { + co_await onClosed; } - when(ErrorOr e = wait(storeError)) { - TraceEvent("WorkerTerminatingByIOError", id).errorUnsuppressed(e.getError()); - actor.cancel(); - // file_not_found can occur due to attempting to open a partially deleted DiskQueue, which should not be - // reported SevError. - if (e.getError().code() == error_code_file_not_found) { - CODE_PROBE(true, "Worker terminated with file_not_found error"); - return Void(); - } else if (e.getError().code() == error_code_lock_file_failure) { - CODE_PROBE(true, "Unable to lock file", probe::context::net2, probe::assert::noSim); - throw please_reboot_kv_store(); - } + if (e.isError() && e.getError().code() == error_code_broken_promise && !storeError.isReady()) { + co_await delay(0.00001 + FLOW_KNOBS->MAX_BUGGIFIED_DELAY); + } + if (storeError.isReady() && storeError.isError() && storeError.getError().code() != error_code_file_not_found) { + throw storeError.get().getError(); + } + if (e.isError()) { throw e.getError(); } + e.get(); + co_return; } + if (res.index() != 1) { + UNREACHABLE(); + } + + ErrorOr e = std::get<1>(std::move(res)); + TraceEvent("WorkerTerminatingByIOError", id).errorUnsuppressed(e.getError()); + actor.cancel(); + // file_not_found can occur due to attempting to open a partially deleted DiskQueue, which should not be reported + // SevError. + if (e.getError().code() == error_code_file_not_found) { + CODE_PROBE(true, "Worker terminated with file_not_found error"); + co_return; + } + if (e.getError().code() == error_code_lock_file_failure) { + CODE_PROBE(true, "Unable to lock file", probe::context::net2, probe::assert::noSim); + throw please_reboot_kv_store(); + } + throw e.getError(); } Future handleIOErrors(Future actor, IClosable* store, UID id, Future onClosed = Void()) { @@ -223,38 +229,53 @@ Future deregisterGrpcService(UID id) { return Void(); } -ACTOR Future workerHandleErrors(FutureStream errors) { - loop choose { - when(ErrorInfo _err = waitNext(errors)) { - ErrorInfo err = _err; - bool ok = err.error.code() == error_code_success || err.error.code() == error_code_please_reboot || - err.error.code() == error_code_actor_cancelled || - err.error.code() == error_code_coordinators_changed || // The worker server was cancelled - err.error.code() == error_code_shutdown_in_progress || - err.error.code() == error_code_audit_storage_task_outdated; // Expected during DD failover +Future workerHandleErrors(FutureStream errors) { + while (true) { + ErrorInfo err = co_await errors; + const bool ok = err.error.code() == error_code_success || err.error.code() == error_code_please_reboot || + err.error.code() == error_code_actor_cancelled || + err.error.code() == error_code_coordinators_changed || // The worker server was cancelled + err.error.code() == error_code_shutdown_in_progress || + err.error.code() == error_code_audit_storage_task_outdated; // Expected during DD failover - if (!ok) { - err.error = checkIOTimeout(err.error); // Possibly convert error to io_timeout - } + if (!ok) { + err.error = checkIOTimeout(err.error); // Possibly convert error to io_timeout + } - endRole(err.role, err.id, "Error", ok, err.error); - - state std::optional rethrow = std::nullopt; - if (err.error.code() == error_code_please_reboot || - (err.role == Role::SHARED_TRANSACTION_LOG && - (err.error.code() == error_code_io_error || err.error.code() == error_code_io_timeout)) || - (SERVER_KNOBS->STORAGE_SERVER_REBOOT_ON_IO_TIMEOUT && err.role == Role::STORAGE_SERVER && - err.error.code() == error_code_io_timeout)) { - rethrow = err.error; - } - - if (rethrow != std::nullopt) { - throw *rethrow; - } + endRole(err.role, err.id, "Error", ok, err.error); + if (err.error.code() == error_code_please_reboot || + (err.role == Role::SHARED_TRANSACTION_LOG && + (err.error.code() == error_code_io_error || err.error.code() == error_code_io_timeout)) || + (SERVER_KNOBS->STORAGE_SERVER_REBOOT_ON_IO_TIMEOUT && err.role == Role::STORAGE_SERVER && + err.error.code() == error_code_io_timeout)) { + throw err.error; } } } +static auto waitForWorkerRegistrationEvent( + Future const& registrationReply, + Reference> const> const& ccInterface, + Reference> const> const& ddInterf, + Reference> const> const& rkInterf, + Reference> const> const& csInterf, + Reference const> const& degraded, + Reference> const> const& issues, + Future const& recovered, + Reference>> const& clusterId) { + return race(registrationReply, + delay(SERVER_KNOBS->UNKNOWN_CC_TIMEOUT), + ccInterface->onChange(), + ddInterf->onChange(), + rkInterf->onChange(), + csInterf->onChange(), + degraded->onChange(), + FlowTransport::transport().onIncompatibleChanged(), + issues->onChange(), + recovered, + clusterId->onChange()); +} + // Improve simulation code coverage by sometimes deferring the destruction of workerInterface (and therefore "endpoint // not found" responses to clients // for an extra second, so that clients are more likely to see broken_promise errors @@ -560,34 +581,30 @@ std::vector getDiskStores(std::string dataFolder, std::string tLogSpi // Register the worker interf to cluster controller (cc) and // re-register the worker when key roles interface, e.g., cc, dd, ratekeeper, change. -ACTOR Future registrationClient(Reference> const> ccInterface, - WorkerInterface interf, - Reference> asyncPriorityInfo, - ProcessClass initialClass, - Reference> const> ddInterf, - Reference> const> rkInterf, - Reference> const> csInterf, - Reference const> degraded, - Reference connRecord, - Reference> const> issues, - Reference> dbInfo, - Promise recoveredDiskFiles, - Reference>> clusterId) { +Future registrationClient(Reference> const> ccInterface, + WorkerInterface interf, + Reference> asyncPriorityInfo, + ProcessClass initialClass, + Reference> const> ddInterf, + Reference> const> rkInterf, + Reference> const> csInterf, + Reference const> degraded, + Reference connRecord, + Reference> const> issues, + Reference> dbInfo, + Promise recoveredDiskFiles, + Reference>> clusterId) { // Keeps the cluster controller (as it may be re-elected) informed that this worker exists // The cluster controller uses waitFailureClient to find out if we die, and returns from registrationReply // (requiring us to re-register) The registration request piggybacks optional distributor interface if it exists. - state Generation requestGeneration = 0; - state ProcessClass processClass = initialClass; - state Reference>>> scInterf( - new AsyncVar>>()); - state Future cacheProcessFuture; - state Future cacheErrorsFuture; - state Optional incorrectTime; - loop { - state ClusterConnectionString storedConnectionString; - state bool upToDate = true; + Generation requestGeneration = 0; + ProcessClass processClass = initialClass; + Optional incorrectTime; + while (true) { + ClusterConnectionString storedConnectionString; + bool upToDate = true; if (connRecord) { - bool upToDateResult = wait(connRecord->upToDate(storedConnectionString)); + bool upToDateResult = co_await connRecord->upToDate(storedConnectionString); upToDate = upToDateResult; } if (upToDate) { @@ -634,7 +651,7 @@ ACTOR Future registrationClient(Referenceget().present(); + bool ccInterfacePresent = ccInterface->get().present(); if (ccInterfacePresent) { TraceEvent("WorkerRegister") .detail("CCID", ccInterface->get().get().id()) @@ -642,50 +659,27 @@ ACTOR Future registrationClient(Referenceget()); } - state Future registrationReply = + Future registrationReply = ccInterfacePresent ? brokenPromiseToNever(ccInterface->get().get().registerWorker.getReply(request)) : Never(); - state Future recovered = recoveredDiskFiles.isSet() ? Never() : recoveredDiskFiles.getFuture(); - state double startTime = now(); - loop choose { - when(RegisterWorkerReply reply = wait(registrationReply)) { + Future recovered = recoveredDiskFiles.isSet() ? Never() : recoveredDiskFiles.getFuture(); + double startTime = now(); + while (true) { + auto res = co_await waitForWorkerRegistrationEvent( + registrationReply, ccInterface, ddInterf, rkInterf, csInterf, degraded, issues, recovered, clusterId); + if (res.index() == 0) { + RegisterWorkerReply reply = std::get<0>(std::move(res)); processClass = reply.processClass; asyncPriorityInfo->set(reply.priorityInfo); TraceEvent("WorkerRegisterReply") .detail("CCID", ccInterface->get().get().id()) .detail("ProcessClass", reply.processClass.toString()); break; - } - when(wait(delay(SERVER_KNOBS->UNKNOWN_CC_TIMEOUT))) { + } else if (res.index() == 1) { if (!ccInterfacePresent) { TraceEvent(SevWarn, "WorkerRegisterTimeout").detail("WaitTime", now() - startTime); } - } - when(wait(ccInterface->onChange())) { - break; - } - when(wait(ddInterf->onChange())) { - break; - } - when(wait(rkInterf->onChange())) { - break; - } - when(wait(csInterf->onChange())) { - break; - } - when(wait(degraded->onChange())) { - break; - } - when(wait(FlowTransport::transport().onIncompatibleChanged())) { - break; - } - when(wait(issues->onChange())) { - break; - } - when(wait(recovered)) { - break; - } - when(wait(clusterId->onChange())) { + } else { break; } } @@ -1340,19 +1334,19 @@ Future> getStorageServers(Database db, } // The actor that actively monitors the health of local and peer servers, and reports anomaly to the cluster controller. -ACTOR Future healthMonitor(Reference> const> ccInterface, - WorkerInterface interf, - LocalityData locality, - Reference const> dbInfo, - Reference> enablePrimaryTxnSystemHealthCheck) { - state UpdateWorkerHealthRequest req; - state Optional db; +Future healthMonitor(Reference> const> ccInterface, + WorkerInterface interf, + LocalityData locality, + Reference const> dbInfo, + Reference> enablePrimaryTxnSystemHealthCheck) { + UpdateWorkerHealthRequest req; + Optional db; if (SERVER_KNOBS->GRAY_FAILURE_ALLOW_PRIMARY_SS_TO_COMPLAIN || SERVER_KNOBS->GRAY_FAILURE_ALLOW_REMOTE_SS_TO_COMPLAIN) { db = openDBOnServer(dbInfo, TaskPriority::DefaultEndpoint, LockAware::True); } - loop { - state Future nextHealthCheckDelay = Never(); + while (true) { + Future nextHealthCheckDelay = Never(); const RecoveryState& recoveryState = dbInfo->get().recoveryState; const bool primaryTxnSystemHealthCheckEnabled = enablePrimaryTxnSystemHealthCheck->get(); const bool ccInterfacePresent = ccInterface->get().present(); @@ -1363,9 +1357,9 @@ ACTOR Future healthMonitor(Reference= RecoveryState::ACCEPTING_COMMITS || primaryTxnSystemHealthCheckEnabled) && ccInterfacePresent) { nextHealthCheckDelay = delay(SERVER_KNOBS->WORKER_HEALTH_MONITOR_INTERVAL); - state Optional storageServers; + Optional storageServers; if (db.present()) { - wait(store(storageServers, getStorageServers(db.get(), dbInfo))); + co_await store(storageServers, getStorageServers(db.get(), dbInfo)); } req = doPeerHealthCheck(interf, locality, dbInfo, req, enablePrimaryTxnSystemHealthCheck, storageServers); @@ -1390,12 +1384,10 @@ ACTOR Future healthMonitor(ReferenceonChange())) {} - when(wait(dbInfo->onChange())) {} - when(wait(enablePrimaryTxnSystemHealthCheck->onChange())) {} - } + co_await race(nextHealthCheckDelay, + ccInterface->onChange(), + dbInfo->onChange(), + enablePrimaryTxnSystemHealthCheck->onChange()); } } @@ -2003,81 +1995,113 @@ Future registerWorkerGrpcServices(UID id, Reference workerServer(Reference connRecord, - Reference> const> ccInterface, - LocalityData locality, - Reference> asyncPriorityInfo, - ProcessClass initialClass, - std::string folder, - std::string tLogSpillFolder, - int64_t memoryLimit, - std::string metricsConnFile, - std::string metricsPrefix, - int64_t memoryProfileThreshold, - std::string _coordFolder, - std::string whitelistBinPaths, - Reference> dbInfo, - Reference>> clusterId, - bool consistencyCheckUrgentMode) { - state PromiseStream errors; - state Reference>> ddInterf( +static auto waitForWorkerServerEvent(WorkerInterface& interf, + Future const& loggingTrigger, + ActorCollection& errorForwarders, + Future const& handleErrors) { + return race(interf.updateServerDBInfo.getFuture(), + interf.clientInterface.reboot.getFuture(), + interf.clientInterface.setFailureInjection.getFuture(), + interf.clientInterface.profiler.getFuture(), + interf.master.getFuture(), + interf.dataDistributor.getFuture(), + interf.ratekeeper.getFuture(), + interf.consistencyScan.getFuture(), + interf.backup.getFuture(), + interf.rangePartitionedBackup.getFuture(), + interf.tLog.getFuture(), + interf.storage.getFuture(), + interf.commitProxy.getFuture(), + interf.grvProxy.getFuture(), + interf.cdcProxy.getFuture(), + interf.resolver.getFuture(), + interf.logRouter.getFuture(), + interf.coordinationPing.getFuture(), + interf.setMetricsRate.getFuture(), + interf.eventLogRequest.getFuture(), + interf.traceBatchDumpRequest.getFuture(), + interf.diskStoreRequest.getFuture(), + loggingTrigger, + interf.workerSnapReq.getFuture(), + errorForwarders.getResult(), + handleErrors); +} + +Future workerServer(Reference connRecord, + Reference> const> ccInterface, + LocalityData locality, + Reference> asyncPriorityInfo, + ProcessClass initialClass, + std::string folder, + std::string tLogSpillFolder, + int64_t memoryLimit, + std::string metricsConnFile, + std::string metricsPrefix, + int64_t memoryProfileThreshold, + std::string _coordFolder, + std::string whitelistBinPaths, + Reference> dbInfo, + Reference>> clusterId, + bool consistencyCheckUrgentMode) { + PromiseStream errors; + Reference>> ddInterf( new AsyncVar>()); - state Reference>> rkInterf(new AsyncVar>()); - state Reference>> csInterf( + Reference>> rkInterf(new AsyncVar>()); + Reference>> csInterf( new AsyncVar>()); - state Future handleErrors = workerHandleErrors(errors.getFuture()); // Needs to be stopped last - state ActorCollection errorForwarders(false); - state Future loggingTrigger = Void(); - state double loggingDelay = SERVER_KNOBS->WORKER_LOGGING_INTERVAL; + Future handleErrors = workerHandleErrors(errors.getFuture()); // Needs to be stopped last + ActorCollection errorForwarders(false); + Future loggingTrigger = Void(); + double loggingDelay = SERVER_KNOBS->WORKER_LOGGING_INTERVAL; // These two promises are destroyed after the "filesClosed" below to avoid broken_promise - state Promise rebootKVSPromise; - state Promise rebootKVSPromise2; - state ActorCollection filesClosed(true); - state Promise stopping; - state Future metricsLogger; - state Future chaosMetricsActor; - state Reference> degraded = FlowTransport::transport().getDegraded(); - state Reference>> issues(new AsyncVar>()); - state Reference>> traceLogIssues(new AsyncVar>()); - state Reference>> tlogIssues(new AsyncVar>()); - state Reference> lowDiskTLogExclusion(new AsyncVar(false)); + Promise rebootKVSPromise; + Promise rebootKVSPromise2; + ActorCollection filesClosed(true); + Promise stopping; + Future metricsLogger; + Future chaosMetricsActor; + Reference> degraded = FlowTransport::transport().getDegraded(); + Reference>> issues(new AsyncVar>()); + Reference>> traceLogIssues(new AsyncVar>()); + Reference>> tlogIssues(new AsyncVar>()); + Reference> lowDiskTLogExclusion(new AsyncVar(false)); // tLogFnForOptions() can return a function that doesn't correspond with the FDB version that the // TLogVersion represents. This can be done if the newer TLog doesn't support a requested option. // As (store type, spill type) can map to the same TLogFn across multiple TLogVersions, we need to // decide if we should collapse them into the same SharedTLog instance as well. The answer // here is no, so that when running with log_version==3, all files should say V=3. - state std::map> sharedLogs; - state Reference> activeSharedTLog(new AsyncVar()); - state WorkerCache backupWorkerCache; - state WorkerCache rangePartitionedBackupWorkerCache; - state WorkerCache logRouterCache; + std::map> sharedLogs; + Reference> activeSharedTLog(new AsyncVar()); + WorkerCache backupWorkerCache; + WorkerCache rangePartitionedBackupWorkerCache; + WorkerCache logRouterCache; - state WorkerSnapRequest lastSnapReq; + WorkerSnapRequest lastSnapReq; // Here the key is UID+role, as we still send duplicate requests to a process which is both storage and tlog - state std::map snapReqMap; - state std::map> snapReqResultMap; - state double lastSnapTime = -SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP; // always successful for the first Snap Request - state std::string coordFolder = abspath(_coordFolder); + std::map snapReqMap; + std::map> snapReqResultMap; + double lastSnapTime = -SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP; // always successful for the first Snap Request + std::string coordFolder = abspath(_coordFolder); - state WorkerInterface interf(locality); + WorkerInterface interf(locality); - state std::set> runningStorages; + std::set> runningStorages; // storageCleaners manages cleanup actors after a storage server is terminated. It cleans up // stale disk files in case storage server is terminated for io_timeout or io_error but the worker // process is still alive. If worker process is alive, it may be recruited as a new storage server // and leave the stale disk file unattended. - state std::unordered_map storageCleaners; + std::unordered_map storageCleaners; interf.initEndpoints(); - state Future updateClusterIdFuture; + Future updateClusterIdFuture; // When set to true, the health monitor running in this worker starts monitor other transaction process in this // cluster. - state Reference> enablePrimaryTxnSystemHealthCheck = makeReference>(false); + Reference> enablePrimaryTxnSystemHealthCheck = makeReference>(false); - wait(yield()); - state Future grpc = registerWorkerGrpcServices(interf.id(), connRecord); + co_await yield(); + Future grpc = registerWorkerGrpcServices(interf.id(), connRecord); if (FLOW_KNOBS->ENABLE_CHAOS_FEATURES) { TraceEvent(SevInfo, "ChaosFeaturesEnabled"); @@ -2090,7 +2114,7 @@ ACTOR Future workerServer(Reference connRecord, if (metricsPrefix.size() > 0) { if (metricsConnFile.size() > 0) { try { - state Database db = + Database db = Database::createDatabase(metricsConnFile, ApiVersion::LATEST_VERSION, IsInternal::True, locality); metricsLogger = runMetrics(db, KeyRef(metricsPrefix)); db->globalConfig->trigger(samplingFrequency, samplingProfilerUpdateFrequency); @@ -2156,24 +2180,24 @@ ACTOR Future workerServer(Reference connRecord, DUMPTOKEN(recruited.updateServerDBInfo); } - state std::vector> recoveries; + std::vector> recoveries; + Error e; try { - state std::vector stores = getDiskStores(folder, tLogSpillFolder); + std::vector stores = getDiskStores(folder, tLogSpillFolder); // Recovery validation remains process-wide: the datadir sentinel covers both disk queues // and tlog spill KV stores, even when the spill files live on a separate volume. - state bool validateDataFiles = deleteFile(joinPath(folder, validationFilename)); - state int index = 0; - for (; index < stores.size(); ++index) { - state DiskStore s = stores[index]; + bool validateDataFiles = deleteFile(joinPath(folder, validationFilename)); + for (int index = 0; index < stores.size(); ++index) { + DiskStore s = stores[index]; // FIXME: Error handling // META-FIXME: what does the above comment refer to? It dates to <= 2017. // Either describe the problem(s) and (perhaps) make a plan to fix them, or take out the FIXME. if (s.storedComponent == DiskStore::Storage) { - // Opening multiple KVSs at the same time could make worker run out of memory. Add delay to allow the - // extra storage process to be removed. + // Opening multiple KVSs at the same time could make worker run out of memory. Add delay to allow + // the extra storage process to be removed. if (index >= 2 && SERVER_KNOBS->WORKER_START_STORAGE_DELAY > 0.0) { - wait(delay(SERVER_KNOBS->WORKER_START_STORAGE_DELAY)); + co_await delay(SERVER_KNOBS->WORKER_START_STORAGE_DELAY); } LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Storage; @@ -2365,8 +2389,11 @@ ACTOR Future workerServer(Reference connRecord, healthMonitor(ccInterface, interf, locality, dbInfo, enablePrimaryTxnSystemHealthCheck)); } - loop choose { - when(UpdateServerDBInfoRequest req = waitNext(interf.updateServerDBInfo.getFuture())) { + while (true) { + auto res = co_await waitForWorkerServerEvent(interf, loggingTrigger, errorForwarders, handleErrors); + if (res.index() == 0) { + UpdateServerDBInfoRequest req = std::get<0>(std::move(res)); + auto localInfo = BinaryReader::fromStringRef(req.serializedDbInfo, AssumeVersion(g_network->protocolVersion())); localInfo.myLocality = locality; @@ -2400,9 +2427,10 @@ ACTOR Future workerServer(Reference connRecord, updateClusterIdFuture = updateClusterId(localInfo.client.clusterId, clusterId, folder); } } - } - when(RebootRequest req = waitNext(interf.clientInterface.reboot.getFuture())) { - state RebootRequest rebootReq = req; + } else if (res.index() == 1) { + RebootRequest req = std::get<1>(std::move(res)); + + RebootRequest rebootReq = req; // If suspendDuration is INT_MAX, the trace will not be logged if it was inside the next block // Also a useful trace to have even if suspendDuration is 0 TraceEvent("RebootRequestSuspendingProcess").detail("Duration", req.waitForDuration); @@ -2413,11 +2441,11 @@ ACTOR Future workerServer(Reference connRecord, threadSleep(req.waitForDuration); } if (rebootReq.checkData) { - Reference checkFile = - wait(IAsyncFileSystem::filesystem()->open(joinPath(folder, validationFilename), - IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, - 0600)); - wait(checkFile->sync()); + Reference checkFile = co_await IAsyncFileSystem::filesystem()->open( + joinPath(folder, validationFilename), + IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, + 0600); + co_await checkFile->sync(); } if (g_network->isSimulated()) { @@ -2431,8 +2459,9 @@ ACTOR Future workerServer(Reference connRecord, ASSERT(!rebootReq.deleteData); flushAndExit(0); } - } - when(SetFailureInjection req = waitNext(interf.clientInterface.setFailureInjection.getFuture())) { + } else if (res.index() == 2) { + SetFailureInjection req = std::get<2>(std::move(res)); + if (FLOW_KNOBS->ENABLE_CHAOS_FEATURES) { if (req.diskFailure.present()) { auto diskFailureInjector = DiskFailureInjector::injector(); @@ -2447,9 +2476,10 @@ ACTOR Future workerServer(Reference connRecord, } else { req.reply.sendError(client_invalid_operation()); } - } - when(ProfilerRequest req = waitNext(interf.clientInterface.profiler.getFuture())) { - state ProfilerRequest profilerReq = req; + } else if (res.index() == 3) { + ProfilerRequest req = std::get<3>(std::move(res)); + + ProfilerRequest profilerReq = req; // There really isn't a great "filepath sanitizer" or "filepath escape" function available, // thus we instead enforce a different requirement. One can only write to a file that's // beneath the working directory, and we remove the ability to do any symlink or ../.. @@ -2468,8 +2498,9 @@ ACTOR Future workerServer(Reference connRecord, } catch (Error& e) { profilerReq.reply.sendError(e); } - } - when(RecruitMasterRequest req = waitNext(interf.master.getFuture())) { + } else if (res.index() == 4) { + RecruitMasterRequest req = std::get<4>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Master; MasterInterface recruited; @@ -2490,8 +2521,9 @@ ACTOR Future workerServer(Reference connRecord, errorForwarders.add( zombie(recruited, forwardError(errors, Role::MASTER, recruited.id(), masterProcess))); req.reply.send(recruited); - } - when(InitializeDataDistributorRequest req = waitNext(interf.dataDistributor.getFuture())) { + } else if (res.index() == 5) { + InitializeDataDistributorRequest req = std::get<5>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::DataDistributor; DataDistributorInterface recruited(locality, req.reqId); @@ -2516,8 +2548,9 @@ ACTOR Future workerServer(Reference connRecord, .detail("DataDistributorId", recruited.id()) .detail("Folder", folder); // double check if this works with SS restore req.reply.send(recruited); - } - when(InitializeRatekeeperRequest req = waitNext(interf.ratekeeper.getFuture())) { + } else if (res.index() == 6) { + InitializeRatekeeperRequest req = std::get<6>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Ratekeeper; RatekeeperInterface recruited(locality, req.reqId); @@ -2543,8 +2576,9 @@ ACTOR Future workerServer(Reference connRecord, } TraceEvent("Ratekeeper_InitRequest", req.reqId).detail("RatekeeperId", recruited.id()); req.reply.send(recruited); - } - when(InitializeConsistencyScanRequest req = waitNext(interf.consistencyScan.getFuture())) { + } else if (res.index() == 7) { + InitializeConsistencyScanRequest req = std::get<7>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::ConsistencyScan; ConsistencyScanInterface recruited(locality, req.reqId); @@ -2568,8 +2602,9 @@ ACTOR Future workerServer(Reference connRecord, } TraceEvent("ConsistencyScanReceived", req.reqId).detail("ConsistencyScanId", recruited.id()); req.reply.send(recruited); - } - when(InitializeBackupRequest req = waitNext(interf.backup.getFuture())) { + } else if (res.index() == 8) { + InitializeBackupRequest req = std::get<8>(std::move(res)); + if (!backupWorkerCache.exists(req.reqId)) { LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; @@ -2590,8 +2625,9 @@ ACTOR Future workerServer(Reference connRecord, } else { forwardPromise(Uncancellable{}, req.reply, backupWorkerCache.get(req.reqId)); } - } - when(InitializeRangePartitionedBackupRequest req = waitNext(interf.rangePartitionedBackup.getFuture())) { + } else if (res.index() == 9) { + InitializeRangePartitionedBackupRequest req = std::get<9>(std::move(res)); + if (!rangePartitionedBackupWorkerCache.exists(req.reqId)) { LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; @@ -2612,8 +2648,9 @@ ACTOR Future workerServer(Reference connRecord, } else { forwardPromise(Uncancellable{}, req.reply, rangePartitionedBackupWorkerCache.get(req.reqId)); } - } - when(InitializeTLogRequest req = waitNext(interf.tLog.getFuture())) { + } else if (res.index() == 10) { + InitializeTLogRequest req = std::get<10>(std::move(res)); + // For now, there's a one-to-one mapping of spill type to TLogVersion. // With future work, a particular version of the TLog can support multiple // different spilling strategies, at which point SpillType will need to be @@ -2682,8 +2719,9 @@ ACTOR Future workerServer(Reference connRecord, } logData.back().requests.send(req); activeSharedTLog->set(logData.back().uid); - } - when(InitializeStorageRequest req = waitNext(interf.storage.getFuture())) { + } else if (res.index() == 11) { + InitializeStorageRequest req = std::get<11>(std::move(res)); + TraceEvent e("StorageServerInitProgress", req.interfaceId); e.detail("Step", "1.RequestReceived"); e.detail("ReqID", req.reqId); @@ -2808,8 +2846,9 @@ ACTOR Future workerServer(Reference connRecord, return Void(); })); } - } - when(InitializeCommitProxyRequest req = waitNext(interf.commitProxy.getFuture())) { + } else if (res.index() == 12) { + InitializeCommitProxyRequest req = std::get<12>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::CommitProxy; CommitProxyInterface recruited; @@ -2833,8 +2872,9 @@ ACTOR Future workerServer(Reference connRecord, recruited.id(), commitProxyServer(recruited, req, dbInfo, whitelistBinPaths)))); req.reply.send(recruited); - } - when(InitializeGrvProxyRequest req = waitNext(interf.grvProxy.getFuture())) { + } else if (res.index() == 13) { + InitializeGrvProxyRequest req = std::get<13>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::GrvProxy; GrvProxyInterface recruited; @@ -2855,8 +2895,9 @@ ACTOR Future workerServer(Reference connRecord, recruited, forwardError(errors, Role::GRV_PROXY, recruited.id(), grvProxyServer(recruited, req, dbInfo)))); req.reply.send(recruited); - } - when(InitializeCDCProxyRequest req = waitNext(interf.cdcProxy.getFuture())) { + } else if (res.index() == 14) { + InitializeCDCProxyRequest req = std::get<14>(std::move(res)); + LocalLineage _; CDCProxyInterface recruited; recruited.processId = locality.processId(); @@ -2880,8 +2921,9 @@ ACTOR Future workerServer(Reference connRecord, recruited.id(), cdcProxyServer(recruited, req.recoveryCount, dbInfo)))); req.reply.send(recruited); - } - when(InitializeResolverRequest req = waitNext(interf.resolver.getFuture())) { + } else if (res.index() == 15) { + InitializeResolverRequest req = std::get<15>(std::move(res)); + LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Resolver; ResolverInterface recruited; @@ -2899,8 +2941,9 @@ ACTOR Future workerServer(Reference connRecord, errorForwarders.add(zombie( recruited, forwardError(errors, Role::RESOLVER, recruited.id(), resolver(recruited, req, dbInfo)))); req.reply.send(recruited); - } - when(InitializeLogRouterRequest req = waitNext(interf.logRouter.getFuture())) { + } else if (res.index() == 16) { + InitializeLogRouterRequest req = std::get<16>(std::move(res)); + if (!logRouterCache.exists(req.reqId)) { LocalLineage _; getCurrentLineage()->modify(&RoleLineage::role) = recruitment::LogRouter; @@ -2937,13 +2980,15 @@ ACTOR Future workerServer(Reference connRecord, } else { forwardPromise(Uncancellable{}, req.reply, logRouterCache.get(req.reqId)); } - } - when(CoordinationPingMessage m = waitNext(interf.coordinationPing.getFuture())) { + } else if (res.index() == 17) { + CoordinationPingMessage m = std::get<17>(std::move(res)); + TraceEvent("CoordinationPing", interf.id()) .detail("CCID", m.clusterControllerId) .detail("TimeStep", m.timeStep); - } - when(SetMetricsLogRateRequest req = waitNext(interf.setMetricsRate.getFuture())) { + } else if (res.index() == 18) { + SetMetricsLogRateRequest req = std::get<18>(std::move(res)); + TraceEvent("LoggingRateChange", interf.id()) .detail("OldDelay", loggingDelay) .detail("NewLogPS", req.metricsLogsPerSecond); @@ -2951,20 +2996,23 @@ ACTOR Future workerServer(Reference connRecord, loggingDelay = 1.0 / req.metricsLogsPerSecond; loggingTrigger = Void(); } - } - when(EventLogRequest req = waitNext(interf.eventLogRequest.getFuture())) { + } else if (res.index() == 19) { + EventLogRequest req = std::get<19>(std::move(res)); + TraceEventFields e; if (req.getLastError) e = latestEventCache.getLatestError(); else e = latestEventCache.get(req.eventName.toString()); req.reply.send(e); - } - when(TraceBatchDumpRequest req = waitNext(interf.traceBatchDumpRequest.getFuture())) { + } else if (res.index() == 20) { + TraceBatchDumpRequest req = std::get<20>(std::move(res)); + g_traceBatch.dump(); req.reply.send(Void()); - } - when(DiskStoreRequest req = waitNext(interf.diskStoreRequest.getFuture())) { + } else if (res.index() == 21) { + DiskStoreRequest req = std::get<21>(std::move(res)); + Standalone> ids; // NOTE: this request is mainly for consistency checking. The current // logic below seems to be holding up OK, but if we discover bugs in this @@ -3004,12 +3052,13 @@ ACTOR Future workerServer(Reference connRecord, } } req.reply.send(ids); - } - when(wait(loggingTrigger)) { + } else if (res.index() == 22) { + systemMonitor(); loggingTrigger = delay(loggingDelay, TaskPriority::FlushTrace); - } - when(state WorkerSnapRequest snapReq = waitNext(interf.workerSnapReq.getFuture())) { + } else if (res.index() == 23) { + WorkerSnapRequest snapReq = std::get<23>(std::move(res)); + std::string snapReqKey = snapReq.snapUID.toString() + snapReq.role.toString(); if (snapReqResultMap.contains(snapReqKey)) { CODE_PROBE(true, "Worker received a duplicate finished snapshot request", probe::decoration::rare); @@ -3027,8 +3076,8 @@ ACTOR Future workerServer(Reference connRecord, ASSERT(snapReq.role == snapReqMap[snapReqKey].role); ASSERT(snapReq.snapPayload == snapReqMap[snapReqKey].snapPayload); // Discard the old request if a duplicate new request is received - // In theory, the old request should be discarded when we send this error since DD won't resend a - // request unless for a network error, where the old request is discarded before sending the + // In theory, the old request should be discarded when we send this error since DD won't resend + // a request unless for a network error, where the old request is discarded before sending the // duplicate request. snapReqMap[snapReqKey].reply.sendError(duplicate_snapshot_request()); snapReqMap[snapReqKey] = snapReq; @@ -3063,35 +3112,36 @@ ACTOR Future workerServer(Reference connRecord, lastSnapTime = now(); } } + } else { + ASSERT(res.index() == 24 || res.index() == 25); } - when(wait(errorForwarders.getResult())) {} - when(wait(handleErrors)) {} } } catch (Error& err) { - // Make sure actors are cancelled before "recovery" promises are destructed. - for (auto f : recoveries) - f.cancel(); - state Error e = err; - bool ok = e.code() == error_code_please_reboot || e.code() == error_code_actor_cancelled || - e.code() == error_code_please_reboot_delete || e.code() == error_code_local_config_changed || - e.code() == error_code_invalid_cluster_id; - endRole(Role::WORKER, interf.id(), "WorkerError", ok, e); - errorForwarders.clear(false); - sharedLogs.clear(); - - if (e.code() != error_code_actor_cancelled) { - // actor_cancelled: - // We get cancelled e.g. when an entire simulation times out, but in that case - // we won't be restarted and don't need to wait for shutdown - stopping.send(Void()); - wait(filesClosed.getResult()); // Wait for complete shutdown of KV stores - wait(delay(0.0)); // Unwind the callstack to make sure that IAsyncFile references are all gone - TraceEvent(SevInfo, "WorkerShutdownComplete", interf.id()); - } - - wait(deregisterGrpcService(interf.id())); - throw e; + e = err; } + + // Make sure actors are cancelled before "recovery" promises are destructed. + for (auto f : recoveries) + f.cancel(); + const bool ok = e.code() == error_code_please_reboot || e.code() == error_code_actor_cancelled || + e.code() == error_code_please_reboot_delete || e.code() == error_code_local_config_changed || + e.code() == error_code_invalid_cluster_id; + endRole(Role::WORKER, interf.id(), "WorkerError", ok, e); + errorForwarders.clear(false); + sharedLogs.clear(); + + if (e.code() != error_code_actor_cancelled) { + // actor_cancelled: + // We get cancelled e.g. when an entire simulation times out, but in that case + // we won't be restarted and don't need to wait for shutdown + stopping.send(Void()); + co_await filesClosed.getResult(); // Wait for complete shutdown of KV stores + co_await delay(0.0); // Unwind the callstack to make sure that IAsyncFile references are all gone + TraceEvent(SevInfo, "WorkerShutdownComplete", interf.id()); + } + + co_await deregisterGrpcService(interf.id()); + throw e; } namespace { @@ -3308,34 +3358,32 @@ static const std::string swversionTestDirName = "sw-version-test"; TEST_CASE("/fdbserver/worker/swversion/noversionhistory") { if (!platform::createDirectory("sw-version-test")) { TraceEvent(SevWarnAlways, "FailedToCreateDirectory").detail("Directory", "sw-version-test"); - return Void(); + co_return; } - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(!swversion.get().isValid()); } - wait(IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true)); - - return Void(); + co_await IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true); } TEST_CASE("/fdbserver/worker/swversion/writeVerifyVersion") { if (!platform::createDirectory("sw-version-test")) { TraceEvent(SevWarnAlways, "FailedToCreateDirectory").detail("Directory", "sw-version-test"); - return Void(); + co_return; } - wait(success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withTSS())))); + co_await success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withTSS()))); - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == ProtocolVersion::withStorageInterfaceReadiness().version()); @@ -3343,27 +3391,23 @@ TEST_CASE("/fdbserver/worker/swversion/writeVerifyVersion") { ASSERT(swversion.get().lowestCompatibleProtocolVersion() == ProtocolVersion::withTSS().version()); } - wait(IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true)); - - return Void(); + co_await IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true); } TEST_CASE("/fdbserver/worker/swversion/runCompatibleOlder") { if (!platform::createDirectory("sw-version-test")) { TraceEvent(SevWarnAlways, "FailedToCreateDirectory").detail("Directory", "sw-version-test"); - return Void(); + co_return; } - { - wait(success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withTSS())))); - } + co_await success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withTSS()))); { - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == @@ -3376,16 +3420,14 @@ TEST_CASE("/fdbserver/worker/swversion/runCompatibleOlder") { } } - { - wait(success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withTSS(), - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withTSS())))); - } + co_await success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withTSS(), + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withTSS()))); { - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == @@ -3395,28 +3437,23 @@ TEST_CASE("/fdbserver/worker/swversion/runCompatibleOlder") { } } - wait(IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true)); - - return Void(); + co_await IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true); } TEST_CASE("/fdbserver/worker/swversion/runIncompatibleOlder") { if (!platform::createDirectory("sw-version-test")) { TraceEvent(SevWarnAlways, "FailedToCreateDirectory").detail("Directory", "sw-version-test"); - return Void(); + co_return; } - { - ErrorOr f = wait(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withTSS()))); - (void)f; - } + co_await errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withTSS())); { - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == @@ -3429,32 +3466,28 @@ TEST_CASE("/fdbserver/worker/swversion/runIncompatibleOlder") { { ErrorOr swversion = - wait(errorOr(testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withCacheRole()))); + co_await errorOr(testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withCacheRole())); ASSERT(swversion.isError() && swversion.getError().code() == error_code_incompatible_software_version); } - wait(IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true)); - - return Void(); + co_await IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true); } TEST_CASE("/fdbserver/worker/swversion/runNewer") { if (!platform::createDirectory("sw-version-test")) { TraceEvent(SevWarnAlways, "FailedToCreateDirectory").detail("Directory", "sw-version-test"); - return Void(); + co_return; } - { - wait(success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withTSS(), - ProtocolVersion::withTSS(), - ProtocolVersion::withCacheRole())))); - } + co_await success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withTSS(), + ProtocolVersion::withTSS(), + ProtocolVersion::withCacheRole()))); { - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == ProtocolVersion::withTSS().version()); @@ -3463,16 +3496,14 @@ TEST_CASE("/fdbserver/worker/swversion/runNewer") { } } - { - wait(success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withStorageInterfaceReadiness(), - ProtocolVersion::withTSS())))); - } + co_await success(errorOr(updateNewestSoftwareVersion(swversionTestDirName, + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withStorageInterfaceReadiness(), + ProtocolVersion::withTSS()))); { - ErrorOr swversion = wait(errorOr( - testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness()))); + ErrorOr swversion = co_await errorOr( + testSoftwareVersionCompatibility(swversionTestDirName, ProtocolVersion::withStorageInterfaceReadiness())); if (!swversion.isError()) { ASSERT(swversion.get().newestProtocolVersion() == @@ -3483,9 +3514,7 @@ TEST_CASE("/fdbserver/worker/swversion/runNewer") { } } - wait(IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true)); - - return Void(); + co_await IAsyncFileSystem::filesystem()->deleteFile(joinPath(swversionTestDirName, versionFileName), true); } namespace { @@ -3504,7 +3533,7 @@ KeyValueStoreType randomStoreType() { // Test the engine can clear in-flight commits TEST_CASE("/fdbserver/storageengine/clearInflightCommits") { - state const std::string testDir = "engine-basic-test"; + const std::string testDir = "engine-basic-test"; platform::eraseDirectoryRecursive(testDir); platform::createDirectory(testDir); @@ -3514,57 +3543,57 @@ TEST_CASE("/fdbserver/storageengine/clearInflightCommits") { UID uid = deterministicRandom()->randomUniqueID(); std::string filename = filenameFromId(storeType, testDir, "", uid); - state IKeyValueStore* kvStore = openKVStore(storeType, filename, uid, 1 << 30); - wait(kvStore->init()); + CoroThreadPool::init(); + IKeyValueStore* kvStore = openKVStore(storeType, filename, uid, 1 << 30); + co_await kvStore->init(); // sharded rocksdb needs to be initialized with a shard - wait(kvStore->addRange(allKeys, "shard")); + co_await kvStore->addRange(allKeys, "shard"); // Insert keys - state StringRef foo = "foo"_sr; - state StringRef bar = "bar"_sr; + StringRef foo = "foo"_sr; + StringRef bar = "bar"_sr; kvStore->set({ foo, foo }); kvStore->set({ keyAfter(foo), keyAfter(foo) }); kvStore->set({ bar, bar }); kvStore->set({ keyAfter(bar), keyAfter(bar) }); - // Note there is no wait() here. We want to test that the commit is still in flight - state Future commit1 = kvStore->commit(false); + // Note there is no co_await here. We want to test that the commit is still in flight. + Future commit1 = kvStore->commit(false); // Clear keys, so that only keyAfter(foo) will be present kvStore->clear(KeyRangeRef(bar, keyAfter(foo))); // Wait for the commit to finish and check that the keys are gone - wait(commit1); - wait(kvStore->commit(false)); + co_await commit1; + co_await kvStore->commit(false); { - Optional val = wait(kvStore->readValue(bar)); + Optional val = co_await kvStore->readValue(bar); ASSERT(!val.present()); } { - Optional val = wait(kvStore->readValue(keyAfter(bar))); + Optional val = co_await kvStore->readValue(keyAfter(bar)); ASSERT(!val.present()); } { - Optional val = wait(kvStore->readValue(foo)); + Optional val = co_await kvStore->readValue(foo); ASSERT(!val.present()); } { - Optional val = wait(kvStore->readValue(keyAfter(foo))); + Optional val = co_await kvStore->readValue(keyAfter(foo)); ASSERT(val.present() and val.get() == keyAfter(foo)); } Future closed = kvStore->onClosed(); kvStore->dispose(); fmt::print("Waiting for engine to close\n"); - wait(closed); + co_await closed; platform::eraseDirectoryRecursive(testDir); - return Void(); } } // namespace @@ -3737,17 +3766,27 @@ Future monitorLeaderWithDelayedCandidacyImpl( return m || deserializer(serializedInfo, outKnownLeader); } -ACTOR Future monitorLeaderWithDelayedCandidacy( - Reference connRecord, - Reference>> currentCC, - Reference> asyncPriorityInfo, - LocalityData locality, - Reference> dbInfo, - Reference>> clusterId) { - state Future monitor = monitorLeaderWithDelayedCandidacyImpl(connRecord, currentCC); - state Future timeout; +static auto waitForDelayedCandidacyEvent(Reference>> const& currentCC, + Reference> const& dbInfo, + Future const& timeout) { + return race(currentCC->onChange(), + dbInfo->onChange(), + currentCC->get().present() ? IFailureMonitor::failureMonitor().onStateChanged( + currentCC->get().get().registerWorker.getEndpoint()) + : Never(), + timeout.isValid() ? timeout : Never()); +} - loop { +Future monitorLeaderWithDelayedCandidacy(Reference connRecord, + Reference>> currentCC, + Reference> asyncPriorityInfo, + LocalityData locality, + Reference> dbInfo, + Reference>> clusterId) { + Future monitor = monitorLeaderWithDelayedCandidacyImpl(connRecord, currentCC); + Future timeout; + + while (true) { if (currentCC->get().present() && dbInfo->get().clusterInterface == currentCC->get().get() && IFailureMonitor::failureMonitor() .getState(currentCC->get().get().registerWorker.getEndpoint()) @@ -3759,17 +3798,11 @@ ACTOR Future monitorLeaderWithDelayedCandidacy( (deterministicRandom()->random01() * (SERVER_KNOBS->MAX_DELAY_CC_WORST_FIT_CANDIDACY_SECONDS - SERVER_KNOBS->MIN_DELAY_CC_WORST_FIT_CANDIDACY_SECONDS))); } - choose { - when(wait(currentCC->onChange())) {} - when(wait(dbInfo->onChange())) {} - when(wait(currentCC->get().present() ? IFailureMonitor::failureMonitor().onStateChanged( - currentCC->get().get().registerWorker.getEndpoint()) - : Never())) {} - when(wait(timeout.isValid() ? timeout : Never())) { - monitor.cancel(); - wait(clusterController(connRecord, currentCC, asyncPriorityInfo, locality, clusterId)); - return Void(); - } + auto res = co_await waitForDelayedCandidacyEvent(currentCC, dbInfo, timeout); + if (res.index() == 3) { + monitor.cancel(); + co_await clusterController(connRecord, currentCC, asyncPriorityInfo, locality, clusterId); + co_return; } } } @@ -3788,31 +3821,35 @@ Future serveProtocolInfo() { // Handles requests from ProcessInterface, an interface meant for direct // communication between the client and FDB processes. -ACTOR Future serveProcess() { - state ProcessInterface process; +Future serveProcess() { + ProcessInterface process; process.getInterface.makeWellKnownEndpoint(WLTOKEN_PROCESS, TaskPriority::DefaultEndpoint); - loop { - choose { - when(GetProcessInterfaceRequest req = waitNext(process.getInterface.getFuture())) { - req.reply.send(process); - } - when(ActorLineageRequest req = waitNext(process.actorLineage.getFuture())) { - state SampleCollection sampleCollector; - auto samples = sampleCollector->get(req.timeStart, req.timeEnd); + while (true) { + auto res = co_await race(process.getInterface.getFuture(), process.actorLineage.getFuture()); + if (res.index() == 0) { + GetProcessInterfaceRequest req = std::get<0>(std::move(res)); - std::vector serializedSamples; - for (const auto& samplePtr : samples) { - auto serialized = SerializedSample{ .time = samplePtr->time, .data = {} }; - for (const auto& [waitState, pair] : samplePtr->data) { - if (waitState >= req.waitStateStart && waitState <= req.waitStateEnd) { - serialized.data[waitState] = std::string(pair.first, pair.second); - } + req.reply.send(process); + } else if (res.index() == 1) { + ActorLineageRequest req = std::get<1>(std::move(res)); + + SampleCollection sampleCollector; + auto samples = sampleCollector->get(req.timeStart, req.timeEnd); + + std::vector serializedSamples; + for (const auto& samplePtr : samples) { + auto serialized = SerializedSample{ .time = samplePtr->time, .data = {} }; + for (const auto& [waitState, pair] : samplePtr->data) { + if (waitState >= req.waitStateStart && waitState <= req.waitStateEnd) { + serialized.data[waitState] = std::string(pair.first, pair.second); } - serializedSamples.push_back(std::move(serialized)); } - ActorLineageReply reply{ serializedSamples }; - req.reply.send(reply); + serializedSamples.push_back(std::move(serialized)); } + ActorLineageReply reply{ serializedSamples }; + req.reply.send(reply); + } else { + UNREACHABLE(); } } } From d34ff7d6476eb8800e2a06c2638db1949b64929f Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 13:39:46 -0700 Subject: [PATCH 14/39] Use makeReference in worker coroutines --- fdbserver/worker/worker.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index 8a546b2a0b..a776f1e818 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -1924,7 +1924,7 @@ Future cleanupStaleStorageDisk(Reference> dbInfo, } TraceEvent("StorageServerLivenessCheck").detail("StoreID", storeID).detail("Retry", retries); - Reference commitProxies(new CommitProxyInfo(dbInfo->get().client.commitProxies)); + auto commitProxies = makeReference(dbInfo->get().client.commitProxies); if (commitProxies->size() == 0) { TraceEvent("SkipDiskCleanup").log(); co_return; @@ -2044,11 +2044,9 @@ Future workerServer(Reference connRecord, Reference>> clusterId, bool consistencyCheckUrgentMode) { PromiseStream errors; - Reference>> ddInterf( - new AsyncVar>()); - Reference>> rkInterf(new AsyncVar>()); - Reference>> csInterf( - new AsyncVar>()); + auto ddInterf = makeReference>>(); + auto rkInterf = makeReference>>(); + auto csInterf = makeReference>>(); Future handleErrors = workerHandleErrors(errors.getFuture()); // Needs to be stopped last ActorCollection errorForwarders(false); Future loggingTrigger = Void(); @@ -2061,17 +2059,17 @@ Future workerServer(Reference connRecord, Future metricsLogger; Future chaosMetricsActor; Reference> degraded = FlowTransport::transport().getDegraded(); - Reference>> issues(new AsyncVar>()); - Reference>> traceLogIssues(new AsyncVar>()); - Reference>> tlogIssues(new AsyncVar>()); - Reference> lowDiskTLogExclusion(new AsyncVar(false)); + auto issues = makeReference>>(); + auto traceLogIssues = makeReference>>(); + auto tlogIssues = makeReference>>(); + auto lowDiskTLogExclusion = makeReference>(false); // tLogFnForOptions() can return a function that doesn't correspond with the FDB version that the // TLogVersion represents. This can be done if the newer TLog doesn't support a requested option. // As (store type, spill type) can map to the same TLogFn across multiple TLogVersions, we need to // decide if we should collapse them into the same SharedTLog instance as well. The answer // here is no, so that when running with log_version==3, all files should say V=3. std::map> sharedLogs; - Reference> activeSharedTLog(new AsyncVar()); + auto activeSharedTLog = makeReference>(); WorkerCache backupWorkerCache; WorkerCache rangePartitionedBackupWorkerCache; WorkerCache logRouterCache; @@ -3924,8 +3922,7 @@ Future fdbd(Reference connRecord, auto serverDBInfo = ServerDBInfo(); serverDBInfo.myLocality = localities; auto dbInfo = makeReference>(serverDBInfo); - Reference>> clusterId( - new AsyncVar>(readClusterId(joinPath(dataFolder, clusterIdFilename)))); + auto clusterId = makeReference>>(readClusterId(joinPath(dataFolder, clusterIdFilename))); TraceEvent("MyLocality").detail("Locality", dbInfo->get().myLocality.toString()); actors.push_back(reportErrors(monitorAndWriteCCPriorityInfo(fitnessFilePath, asyncPriorityInfo), From 5a08e0e737c04fda21fa7f527a5db20327667cc5 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Mon, 13 Jul 2026 14:02:45 -0700 Subject: [PATCH 15/39] Split worker server dispatch into coroutine loops --- fdbserver/worker/worker.cpp | 1716 +++++++++++++++++++---------------- 1 file changed, 958 insertions(+), 758 deletions(-) diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index a776f1e818..6d86f460ac 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -1995,37 +1995,928 @@ Future registerWorkerGrpcServices(UID id, Reference const& loggingTrigger, - ActorCollection& errorForwarders, - Future const& handleErrors) { - return race(interf.updateServerDBInfo.getFuture(), - interf.clientInterface.reboot.getFuture(), - interf.clientInterface.setFailureInjection.getFuture(), - interf.clientInterface.profiler.getFuture(), - interf.master.getFuture(), - interf.dataDistributor.getFuture(), - interf.ratekeeper.getFuture(), - interf.consistencyScan.getFuture(), - interf.backup.getFuture(), - interf.rangePartitionedBackup.getFuture(), - interf.tLog.getFuture(), - interf.storage.getFuture(), - interf.commitProxy.getFuture(), - interf.grvProxy.getFuture(), - interf.cdcProxy.getFuture(), - interf.resolver.getFuture(), - interf.logRouter.getFuture(), - interf.coordinationPing.getFuture(), - interf.setMetricsRate.getFuture(), - interf.eventLogRequest.getFuture(), - interf.traceBatchDumpRequest.getFuture(), - interf.diskStoreRequest.getFuture(), - loggingTrigger, - interf.workerSnapReq.getFuture(), - errorForwarders.getResult(), - handleErrors); -} +class WorkerServerCore { + WorkerInterface& interf; + Reference connRecord; + Reference> const> ccInterface; + LocalityData locality; + Reference> dbInfo; + Reference>> clusterId; + std::string const& folder; + std::string const& tLogSpillFolder; + std::string const& coordFolder; + std::string const& whitelistBinPaths; + int64_t memoryLimit; + PromiseStream errors; + ActorCollection& errorForwarders; + ActorCollection& filesClosed; + Reference>> ddInterf; + Reference>> rkInterf; + Reference>> csInterf; + Reference> degraded; + Reference> lowDiskTLogExclusion; + Reference> activeSharedTLog; + Reference> enablePrimaryTxnSystemHealthCheck; + std::map>& sharedLogs; + WorkerCache& backupWorkerCache; + WorkerCache& rangePartitionedBackupWorkerCache; + WorkerCache& logRouterCache; + std::set>& runningStorages; + std::unordered_map& storageCleaners; + Promise& rebootKVSPromise2; + Future& updateClusterIdFuture; + Future& loggingTrigger; + double& loggingDelay; + WorkerSnapRequest& lastSnapReq; + std::map& snapReqMap; + std::map>& snapReqResultMap; + double& lastSnapTime; + + Future serveServerDBInfoUpdates() { + while (true) { + UpdateServerDBInfoRequest req = co_await interf.updateServerDBInfo.getFuture(); + + auto localInfo = BinaryReader::fromStringRef(req.serializedDbInfo, + AssumeVersion(g_network->protocolVersion())); + localInfo.myLocality = locality; + + if (localInfo.infoGeneration < dbInfo->get().infoGeneration && + localInfo.clusterInterface == dbInfo->get().clusterInterface) { + std::vector rep = req.broadcastInfo; + rep.push_back(interf.updateServerDBInfo.getEndpoint()); + req.reply.send(rep); + } else { + Optional notUpdated; + if (!ccInterface->get().present() || localInfo.clusterInterface != ccInterface->get().get()) { + notUpdated = interf.updateServerDBInfo.getEndpoint(); + } else if (localInfo.infoGeneration > dbInfo->get().infoGeneration || + dbInfo->get().clusterInterface != ccInterface->get().get()) { + TraceEvent("GotServerDBInfoChange") + .detail("ChangeID", localInfo.id) + .detail("InfoGeneration", localInfo.infoGeneration) + .detail("MasterID", localInfo.master.id()) + .detail("RatekeeperID", + localInfo.ratekeeper.present() ? localInfo.ratekeeper.get().id() : UID()) + .detail("DataDistributorID", + localInfo.distributor.present() ? localInfo.distributor.get().id() : UID()); + dbInfo->set(localInfo); + } + errorForwarders.add( + success(broadcastDBInfoRequest(req, SERVER_KNOBS->DBINFO_SEND_AMOUNT, notUpdated, true))); + + if (!updateClusterIdFuture.isValid() && !clusterId->get().present() && + localInfo.client.clusterId.isValid()) { + updateClusterIdFuture = updateClusterId(localInfo.client.clusterId, clusterId, folder); + } + } + } + } + + Future handleRebootRequest(RebootRequest req) { + RebootRequest rebootReq = req; + // If suspendDuration is INT_MAX, the trace will not be logged if it was inside the next block + // Also a useful trace to have even if suspendDuration is 0 + TraceEvent("RebootRequestSuspendingProcess").detail("Duration", req.waitForDuration); + if (req.waitForDuration) { + flushTraceFileVoid(); + setProfilingEnabled(0); + g_network->stop(); + threadSleep(req.waitForDuration); + } + if (rebootReq.checkData) { + Reference checkFile = co_await IAsyncFileSystem::filesystem()->open( + joinPath(folder, validationFilename), IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, 0600); + co_await checkFile->sync(); + } + + if (g_network->isSimulated()) { + TraceEvent("SimulatedReboot").detail("Deletion", rebootReq.deleteData); + if (rebootReq.deleteData) { + throw please_reboot_delete(); + } + throw please_reboot(); + } else { + TraceEvent("ProcessReboot").log(); + ASSERT(!rebootReq.deleteData); + flushAndExit(0); + } + } + + Future serveFailureInjectionRequests() { + while (true) { + SetFailureInjection req = co_await interf.clientInterface.setFailureInjection.getFuture(); + + if (FLOW_KNOBS->ENABLE_CHAOS_FEATURES) { + if (req.diskFailure.present()) { + auto diskFailureInjector = DiskFailureInjector::injector(); + diskFailureInjector->setDiskFailure(req.diskFailure.get().stallInterval, + req.diskFailure.get().stallPeriod, + req.diskFailure.get().throttlePeriod); + } else if (req.flipBits.present()) { + auto bitFlipper = BitFlipper::flipper(); + bitFlipper->setBitFlipPercentage(req.flipBits.get().percentBitFlips); + } + req.reply.send(Void()); + } else { + req.reply.sendError(client_invalid_operation()); + } + } + } + + Future serveProfilerRequests() { + while (true) { + ProfilerRequest req = co_await interf.clientInterface.profiler.getFuture(); + + ProfilerRequest profilerReq = req; + // There really isn't a great "filepath sanitizer" or "filepath escape" function available, + // thus we instead enforce a different requirement. One can only write to a file that's + // beneath the working directory, and we remove the ability to do any symlink or ../.. + // tricks by resolving all paths through `abspath` first. + try { + std::string realLogDir = abspath(SERVER_KNOBS->LOG_DIRECTORY); + std::string realOutPath = abspath(realLogDir + "/" + profilerReq.outputFile.toString()); + if (realLogDir.size() < realOutPath.size() && + strncmp(realLogDir.c_str(), realOutPath.c_str(), realLogDir.size()) == 0) { + profilerReq.outputFile = realOutPath; + uncancellable(runProfiler(profilerReq)); + profilerReq.reply.send(Void()); + } else { + profilerReq.reply.sendError(client_invalid_operation()); + } + } catch (Error& e) { + profilerReq.reply.sendError(e); + } + } + } + + Future serveMasterRecruitment() { + while (true) { + RecruitMasterRequest req = co_await interf.master.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Master; + MasterInterface recruited; + recruited.locality = locality; + recruited.initEndpoints(); + + startRole(Role::MASTER, recruited.id(), interf.id()); + + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.getCommitVersion); + DUMPTOKEN(recruited.getLiveCommittedVersion); + DUMPTOKEN(recruited.reportLiveCommittedVersion); + DUMPTOKEN(recruited.updateRecoveryData); + + // printf("Recruited as masterServer\n"); + Future masterProcess = masterServer( + recruited, dbInfo, ccInterface, ServerCoordinators(connRecord), req.lifetime, req.forceRecovery); + errorForwarders.add(zombie(recruited, forwardError(errors, Role::MASTER, recruited.id(), masterProcess))); + req.reply.send(recruited); + } + } + + Future serveDataDistributorRecruitment() { + while (true) { + InitializeDataDistributorRequest req = co_await interf.dataDistributor.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::DataDistributor; + DataDistributorInterface recruited(locality, req.reqId); + recruited.initEndpoints(); + + if (ddInterf->get().present()) { + recruited = ddInterf->get().get(); + CODE_PROBE(true, "Recruited while already a data distributor."); + } else { + startRole(Role::DATA_DISTRIBUTOR, recruited.id(), interf.id()); + DUMPTOKEN(recruited.waitFailure); + + Future dataDistributorProcess = dataDistributor(recruited, dbInfo, folder); + errorForwarders.add(forwardError( + errors, + Role::DATA_DISTRIBUTOR, + recruited.id(), + setWhenDoneOrError(dataDistributorProcess, ddInterf, Optional()))); + ddInterf->set(Optional(recruited)); + } + TraceEvent("DataDistributorReceived", req.reqId) + .detail("DataDistributorId", recruited.id()) + .detail("Folder", folder); // double check if this works with SS restore + req.reply.send(recruited); + } + } + + Future serveRatekeeperRecruitment() { + while (true) { + InitializeRatekeeperRequest req = co_await interf.ratekeeper.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Ratekeeper; + RatekeeperInterface recruited(locality, req.reqId); + recruited.initEndpoints(); + + if (rkInterf->get().present()) { + recruited = rkInterf->get().get(); + CODE_PROBE(true, "Recruited while already a ratekeeper."); + } else { + startRole(Role::RATEKEEPER, recruited.id(), interf.id()); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.getRateInfo); + DUMPTOKEN(recruited.haltRatekeeper); + DUMPTOKEN(recruited.reportCommitCostEstimation); + + Future ratekeeperProcess = ratekeeper(recruited, dbInfo); + errorForwarders.add( + forwardError(errors, + Role::RATEKEEPER, + recruited.id(), + setWhenDoneOrError(ratekeeperProcess, rkInterf, Optional()))); + rkInterf->set(Optional(recruited)); + } + TraceEvent("Ratekeeper_InitRequest", req.reqId).detail("RatekeeperId", recruited.id()); + req.reply.send(recruited); + } + } + + Future serveConsistencyScanRecruitment() { + while (true) { + InitializeConsistencyScanRequest req = co_await interf.consistencyScan.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::ConsistencyScan; + ConsistencyScanInterface recruited(locality, req.reqId); + recruited.initEndpoints(); + + if (csInterf->get().present()) { + recruited = csInterf->get().get(); + CODE_PROBE(true, "Recovered while already a consistencyscan"); + } else { + startRole(Role::CONSISTENCYSCAN, recruited.id(), interf.id()); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.haltConsistencyScan); + + Future consistencyScanProcess = consistencyScan(recruited, dbInfo); + errorForwarders.add(forwardError( + errors, + Role::CONSISTENCYSCAN, + recruited.id(), + setWhenDoneOrError(consistencyScanProcess, csInterf, Optional()))); + csInterf->set(Optional(recruited)); + } + TraceEvent("ConsistencyScanReceived", req.reqId).detail("ConsistencyScanId", recruited.id()); + req.reply.send(recruited); + } + } + + Future serveBackupRecruitment() { + while (true) { + InitializeBackupRequest req = co_await interf.backup.getFuture(); + + if (!backupWorkerCache.exists(req.reqId)) { + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; + BackupInterface recruited(locality); + recruited.initEndpoints(); + + startRole(Role::BACKUP, recruited.id(), interf.id()); + DUMPTOKEN(recruited.waitFailure); + + ReplyPromise backupReady = req.reply; + backupWorkerCache.set(req.reqId, backupReady.getFuture()); + Future backupProcess = backupWorker(recruited, req, dbInfo); + backupProcess = backupWorkerCache.removeOnReady(req.reqId, backupProcess); + errorForwarders.add(forwardError(errors, Role::BACKUP, recruited.id(), backupProcess)); + TraceEvent("BackupInitRequest", req.reqId).detail("BackupId", recruited.id()); + InitializeBackupReply reply(recruited, req.backupEpoch); + backupReady.send(reply); + } else { + forwardPromise(Uncancellable{}, req.reply, backupWorkerCache.get(req.reqId)); + } + } + } + + Future serveRangePartitionedBackupRecruitment() { + while (true) { + InitializeRangePartitionedBackupRequest req = co_await interf.rangePartitionedBackup.getFuture(); + + if (!rangePartitionedBackupWorkerCache.exists(req.reqId)) { + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; + BackupInterface recruited(locality); + recruited.initEndpoints(); + + startRole(Role::BACKUP, recruited.id(), interf.id()); + DUMPTOKEN(recruited.waitFailure); + + ReplyPromise backupReady = req.reply; + rangePartitionedBackupWorkerCache.set(req.reqId, backupReady.getFuture()); + Future backupProcess = rangePartitionedBackupWorker(recruited, req, dbInfo); + backupProcess = rangePartitionedBackupWorkerCache.removeOnReady(req.reqId, backupProcess); + errorForwarders.add(forwardError(errors, Role::BACKUP, recruited.id(), backupProcess)); + TraceEvent("RangePartitionedBWInitRequest", req.reqId).detail("BackupId", recruited.id()); + InitializeRangePartitionedBackupReply reply(recruited, req.backupEpoch); + backupReady.send(reply); + } else { + forwardPromise(Uncancellable{}, req.reply, rangePartitionedBackupWorkerCache.get(req.reqId)); + } + } + } + + Future serveTLogRecruitment() { + while (true) { + InitializeTLogRequest req = co_await interf.tLog.getFuture(); + + // For now, there's a one-to-one mapping of spill type to TLogVersion. + // With future work, a particular version of the TLog can support multiple + // different spilling strategies, at which point SpillType will need to be + // plumbed down into tLogFn. + if (req.logVersion < TLogVersion::MIN_RECRUITABLE) { + TraceEvent(SevError, "InitializeTLogInvalidLogVersion") + .detail("Version", req.logVersion) + .detail("MinRecruitable", TLogVersion::MIN_RECRUITABLE); + req.reply.sendError(internal_error()); + } + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::TLog; + TLogOptions tLogOptions(req.logVersion, req.spillType); + TLogFn tLogFn = tLogFnForOptions(tLogOptions); + auto& logData = sharedLogs[SharedLogsKey(tLogOptions, req.storeType)]; + while (!logData.empty() && (!logData.back().actor.isValid() || logData.back().actor.isReady())) { + logData.pop_back(); + } + if (logData.empty()) { + UID logId = deterministicRandom()->randomUniqueID(); + std::map details; + details["ForMaster"] = req.recruitmentID.shortString(); + details["StorageEngine"] = req.storeType.toString(); + + // FIXME: start role for every tlog instance, rather that just for the shared actor, also use a + // different role type for the shared actor + startRole(Role::SHARED_TRANSACTION_LOG, logId, interf.id(), details); + + const StringRef prefix = + req.logVersion > TLogVersion::V2 ? fileVersionedLogDataPrefix : fileLogDataPrefix; + std::string filename = + filenameFromId(req.storeType, tLogSpillFolder, prefix.toString() + tLogOptions.toPrefix(), logId); + IKeyValueStore* data = openKVStore(req.storeType, filename, logId, memoryLimit, false, false, dbInfo); + const DiskQueueVersion dqv = tLogOptions.getDiskQueueVersion(); + IDiskQueue* queue = openDiskQueue( + joinPath(folder, fileLogQueuePrefix.toString() + tLogOptions.toPrefix() + logId.toString() + "-"), + tlogQueueExtension.toString(), + logId, + dqv); + filesClosed.add(data->onClosed()); + filesClosed.add(queue->onClosed()); + + logData.push_back(SharedLogsValue()); + Future tLogCore = tLogFn(data, + queue, + dbInfo, + locality, + logData.back().requests, + logId, + interf.id(), + false, + Promise(), + Promise(), + folder, + degraded, + lowDiskTLogExclusion, + activeSharedTLog, + enablePrimaryTxnSystemHealthCheck); + tLogCore = handleIOErrors(tLogCore, data, logId); + tLogCore = handleIOErrors(tLogCore, queue, logId); + errorForwarders.add(forwardError(errors, Role::SHARED_TRANSACTION_LOG, logId, tLogCore)); + logData.back().actor = tLogCore; + logData.back().uid = logId; + } + logData.back().requests.send(req); + activeSharedTLog->set(logData.back().uid); + } + } + + Future serveStorageRecruitment() { + while (true) { + InitializeStorageRequest req = co_await interf.storage.getFuture(); + + TraceEvent e("StorageServerInitProgress", req.interfaceId); + e.detail("Step", "1.RequestReceived"); + e.detail("ReqID", req.reqId); + e.detail("WorkerID", interf.id()); + e.detail("StorageType", req.storeType.toString()); + e.detail("SeedTag", req.seedTag.toString()); + e.detail("IsTssPair", req.tssPairIDAndVersion.present()); + if (req.tssPairIDAndVersion.present()) { + e.detail("TssPairID", req.tssPairIDAndVersion.get().first); + } + int j = 0; + for (const auto& runningStorage : runningStorages) { + e.detail("RunningStorageIDOnSameWorker" + std::to_string(j), runningStorage.first); + e.detail("RunningStorageEngineOnSameWorker" + std::to_string(j), runningStorage.second); + j++; + } + // We want to prevent double recruiting on a worker unless we try to recruit something + // with a different storage engine (otherwise storage migration won't work for certain + // configuration). Additionally we also need to allow double recruitment for seed servers. + // The reason for this is that a storage will only remove itself if after it was able + // to read the system key space. But if recovery fails right after a `configure new ...` + // was run it won't be able to do so. + if (std::all_of(runningStorages.begin(), + runningStorages.end(), + [&req](const auto& p) { return p.second != req.storeType; }) || + req.seedTag != invalidTag) { + ASSERT(req.initialClusterVersion >= 0); + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Storage; + + // When a new storage server is recruited, we need to check if any other storage + // server has run on this worker process(a.k.a double recruitment). The previous storage + // server may have leftover disk files if it stopped with io_error or io_timeout. Now DD + // already repairs the team and it's time to start the cleanup + cleanupStorageDisks(dbInfo, storageCleaners, memoryLimit); + + bool isTss = req.tssPairIDAndVersion.present(); + StorageServerInterface recruited(req.interfaceId); + recruited.locality = locality; + recruited.tssPairID = isTss ? req.tssPairIDAndVersion.get().first : Optional(); + recruited.initEndpoints(); + + std::map details; + details["StorageEngine"] = req.storeType.toString(); + details["IsTSS"] = std::to_string(isTss); + Role ssRole = isTss ? Role::TESTING_STORAGE_SERVER : Role::STORAGE_SERVER; + startRole(ssRole, recruited.id(), interf.id(), details); + TraceEvent("StorageServerInitProgress", recruited.id()) + .detail("ReqID", req.reqId) + .detail("StorageType", req.storeType.toString()) + .detail("Step", "2.RoleStarted") + .detail("WorkerID", interf.id()); + + DUMPTOKEN(recruited.getValue); + DUMPTOKEN(recruited.getKey); + DUMPTOKEN(recruited.getKeyValues); + DUMPTOKEN(recruited.getMappedKeyValues); + DUMPTOKEN(recruited.getShardState); + DUMPTOKEN(recruited.waitMetrics); + DUMPTOKEN(recruited.splitMetrics); + DUMPTOKEN(recruited.getReadHotRanges); + DUMPTOKEN(recruited.getRangeSplitPoints); + DUMPTOKEN(recruited.getStorageMetrics); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.getQueuingMetrics); + DUMPTOKEN(recruited.getKeyValueStoreType); + DUMPTOKEN(recruited.watchValue); + DUMPTOKEN(recruited.getKeyValuesStream); + DUMPTOKEN(recruited.changeFeedStream); + DUMPTOKEN(recruited.changeFeedPop); + DUMPTOKEN(recruited.changeFeedVersionUpdate); + + std::string filename = + filenameFromId(req.storeType, + folder, + isTss ? testingStoragePrefix.toString() : fileStoragePrefix.toString(), + recruited.id()); + IKeyValueStore* data = + openKVStore(req.storeType, filename, recruited.id(), memoryLimit, false, false, dbInfo, 0); + TraceEvent("StorageServerInitProgress", recruited.id()) + .detail("ReqID", req.reqId) + .detail("StorageType", req.storeType.toString()) + .detail("Step", "3.KVStoreOpened") + .detail("WorkerID", interf.id()); + + Future kvClosed = + data->onClosed() || + rebootKVSPromise2.getFuture() /* clear the onClosed() Future in actorCollection when rebooting */; + filesClosed.add(kvClosed); + ReplyPromise storageReady = req.reply; + Future> storeError = errorOr(data->getError()); + Future s = storageServer(data, + recruited, + req.seedTag, + req.initialClusterVersion, + isTss ? req.tssPairIDAndVersion.get().second : 0, + storageReady, + dbInfo, + folder); + s = handleIOErrors(s, storeError, recruited.id(), kvClosed); + s = storageServerRollbackRebooter(&runningStorages, + &storageCleaners, + s, + req.storeType, + filename, + recruited.id(), + recruited.locality, + isTss, + dbInfo, + folder, + &filesClosed, + memoryLimit, + data, + false, + &rebootKVSPromise2); + errorForwarders.add(forwardError(errors, ssRole, recruited.id(), s)); + } else { + TraceEvent("AttemptedDoubleRecruitment", interf.id()).detail("ForRole", "StorageServer"); + errorForwarders.add(map(delay(0.5), [reply = req.reply](Void) { + reply.sendError(recruitment_failed()); + return Void(); + })); + } + } + } + + Future serveCommitProxyRecruitment() { + while (true) { + InitializeCommitProxyRequest req = co_await interf.commitProxy.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::CommitProxy; + CommitProxyInterface recruited; + recruited.processId = locality.processId(); + recruited.provisional = false; + recruited.initEndpoints(); + + std::map details; + details["ForMaster"] = req.master.id().shortString(); + startRole(Role::COMMIT_PROXY, recruited.id(), interf.id(), details); + + DUMPTOKEN(recruited.commit); + DUMPTOKEN(recruited.getKeyServersLocations); + DUMPTOKEN(recruited.getStorageServerRejoinInfo); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.txnState); + + errorForwarders.add(zombie(recruited, + forwardError(errors, + Role::COMMIT_PROXY, + recruited.id(), + commitProxyServer(recruited, req, dbInfo, whitelistBinPaths)))); + req.reply.send(recruited); + } + } + + Future serveGrvProxyRecruitment() { + while (true) { + InitializeGrvProxyRequest req = co_await interf.grvProxy.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::GrvProxy; + GrvProxyInterface recruited; + recruited.processId = locality.processId(); + recruited.provisional = false; + recruited.initEndpoints(); + + std::map details; + details["ForMaster"] = req.master.id().shortString(); + startRole(Role::GRV_PROXY, recruited.id(), interf.id(), details); + + DUMPTOKEN(recruited.getConsistentReadVersion); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.getHealthMetrics); + + // printf("Recruited as grvProxyServer\n"); + errorForwarders.add( + zombie(recruited, + forwardError(errors, Role::GRV_PROXY, recruited.id(), grvProxyServer(recruited, req, dbInfo)))); + req.reply.send(recruited); + } + } + + Future serveCDCProxyRecruitment() { + while (true) { + InitializeCDCProxyRequest req = co_await interf.cdcProxy.getFuture(); + + LocalLineage _; + CDCProxyInterface recruited; + recruited.processId = locality.processId(); + recruited.initEndpoints(); + + std::map details; + startRole(Role::CDC_PROXY, recruited.id(), interf.id(), details); + + DUMPTOKEN(recruited.consume); + DUMPTOKEN(recruited.registerStream); + DUMPTOKEN(recruited.removeStream); + DUMPTOKEN(recruited.ack); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.haltForTesting); + DUMPTOKEN(recruited.getBufferStatusForTesting); + DUMPTOKEN(recruited.setPopsPausedForTesting); + + errorForwarders.add(zombie( + recruited, + forwardError( + errors, Role::CDC_PROXY, recruited.id(), cdcProxyServer(recruited, req.recoveryCount, dbInfo)))); + req.reply.send(recruited); + } + } + + Future serveResolverRecruitment() { + while (true) { + InitializeResolverRequest req = co_await interf.resolver.getFuture(); + + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Resolver; + ResolverInterface recruited; + recruited.locality = locality; + recruited.initEndpoints(); + + std::map details; + startRole(Role::RESOLVER, recruited.id(), interf.id(), details); + + DUMPTOKEN(recruited.resolve); + DUMPTOKEN(recruited.metrics); + DUMPTOKEN(recruited.split); + DUMPTOKEN(recruited.waitFailure); + + errorForwarders.add(zombie( + recruited, forwardError(errors, Role::RESOLVER, recruited.id(), resolver(recruited, req, dbInfo)))); + req.reply.send(recruited); + } + } + + Future serveLogRouterRecruitment() { + while (true) { + InitializeLogRouterRequest req = co_await interf.logRouter.getFuture(); + + if (!logRouterCache.exists(req.reqId)) { + LocalLineage _; + getCurrentLineage()->modify(&RoleLineage::role) = recruitment::LogRouter; + TLogInterface recruited(locality); + recruited.initEndpoints(); + + std::map details; + startRole(Role::LOG_ROUTER, recruited.id(), interf.id(), details); + + DUMPTOKEN(recruited.peekMessages); + DUMPTOKEN(recruited.peekStreamMessages); + DUMPTOKEN(recruited.popMessages); + DUMPTOKEN(recruited.commit); + DUMPTOKEN(recruited.lock); + DUMPTOKEN(recruited.getQueuingMetrics); + DUMPTOKEN(recruited.confirmRunning); + DUMPTOKEN(recruited.waitFailure); + DUMPTOKEN(recruited.recoveryFinished); + DUMPTOKEN(recruited.disablePopRequest); + DUMPTOKEN(recruited.enablePopRequest); + DUMPTOKEN(recruited.snapRequest); + + ReplyPromise logRouterReady = req.reply; + logRouterCache.set(req.reqId, logRouterReady.getFuture()); + Future logRouterProcess = logRouter(recruited, req, dbInfo); + logRouterProcess = logRouterCache.removeOnReady(req.reqId, logRouterProcess); + errorForwarders.add( + zombie(recruited, forwardError(errors, Role::LOG_ROUTER, recruited.id(), logRouterProcess))); + + TraceEvent("LogRouterInitRequest", req.reqId).detail("LogRouterId", recruited.id()); + if (!skipInitRspInSim(interf.id(), req.allowDropInSim)) { + logRouterReady.send(recruited); + } + } else { + forwardPromise(Uncancellable{}, req.reply, logRouterCache.get(req.reqId)); + } + } + } + + Future serveCoordinationPings() { + while (true) { + CoordinationPingMessage m = co_await interf.coordinationPing.getFuture(); + + TraceEvent("CoordinationPing", interf.id()) + .detail("CCID", m.clusterControllerId) + .detail("TimeStep", m.timeStep); + } + } + + Future serveMetricsLogging() { + while (true) { + auto res = co_await race(interf.setMetricsRate.getFuture(), loggingTrigger); + if (res.index() == 0) { + SetMetricsLogRateRequest req = std::get<0>(std::move(res)); + + TraceEvent("LoggingRateChange", interf.id()) + .detail("OldDelay", loggingDelay) + .detail("NewLogPS", req.metricsLogsPerSecond); + if (req.metricsLogsPerSecond != 0) { + loggingDelay = 1.0 / req.metricsLogsPerSecond; + loggingTrigger = Void(); + } + } else { + ASSERT(res.index() == 1); + + systemMonitor(); + loggingTrigger = delay(loggingDelay, TaskPriority::FlushTrace); + } + } + } + + Future serveEventLogRequests() { + while (true) { + EventLogRequest req = co_await interf.eventLogRequest.getFuture(); + + TraceEventFields e; + if (req.getLastError) + e = latestEventCache.getLatestError(); + else + e = latestEventCache.get(req.eventName.toString()); + req.reply.send(e); + } + } + + Future serveTraceBatchDumpRequests() { + while (true) { + TraceBatchDumpRequest req = co_await interf.traceBatchDumpRequest.getFuture(); + + g_traceBatch.dump(); + req.reply.send(Void()); + } + } + + Future serveDiskStoreRequests() { + while (true) { + DiskStoreRequest req = co_await interf.diskStoreRequest.getFuture(); + + Standalone> ids; + // NOTE: this request is mainly for consistency checking. The current + // logic below seems to be holding up OK, but if we discover bugs in this + // area, another approach would be to make the server here simply return + // everything it knows about the DiskStore, and put all the checking logic + // on the client side. This makes the checking logic itself easier to test + // locally via test cases with defined consistency bugs. + for (DiskStore d : getDiskStores(folder, tLogSpillFolder)) { + bool included = true; + if (!req.includePartialStores) { + if (d.storeType == KeyValueStoreType::SSD_BTREE_V1) { + included = fileExists(d.filename + ".fdb-wal"); + } else if (d.storeType == KeyValueStoreType::SSD_BTREE_V2) { + included = fileExists(d.filename + ".sqlite-wal"); + } else if (d.storeType == KeyValueStoreType::SSD_REDWOOD_V1) { + included = fileExists(d.filename + "0.pagerlog") && fileExists(d.filename + "1.pagerlog"); + } else if (d.storeType == KeyValueStoreType::SSD_ROCKSDB_V1) { + included = + fileExists(joinPath(d.filename, "CURRENT")) && fileExists(joinPath(d.filename, "IDENTITY")); + } else if (d.storeType == KeyValueStoreType::SSD_SHARDED_ROCKSDB) { + included = + fileExists(joinPath(d.filename, "CURRENT")) && fileExists(joinPath(d.filename, "IDENTITY")); + } else if (d.storeType == KeyValueStoreType::MEMORY) { + included = fileExists(d.filename + "1.fdq"); + } else { + ASSERT(d.storeType == KeyValueStoreType::MEMORY_RADIXTREE); + included = fileExists(d.filename + "1.fdr"); + } + if (d.storedComponent == DiskStore::COMPONENT::TLogData) { + // Changes to tlog spilling design are believed to make this check + // unnecessary. + included = false; + } + } + if (included) { + ids.push_back(ids.arena(), d.storeID); + } + } + req.reply.send(ids); + } + } + + Future serveSnapshotRequests() { + while (true) { + WorkerSnapRequest snapReq = co_await interf.workerSnapReq.getFuture(); + + std::string snapReqKey = snapReq.snapUID.toString() + snapReq.role.toString(); + if (snapReqResultMap.contains(snapReqKey)) { + CODE_PROBE(true, "Worker received a duplicate finished snapshot request", probe::decoration::rare); + auto result = snapReqResultMap[snapReqKey]; + result.isError() ? snapReq.reply.sendError(result.getError()) : snapReq.reply.send(result.get()); + TraceEvent("RetryFinishedWorkerSnapRequest") + .detail("SnapUID", snapReq.snapUID.toString()) + .detail("Role", snapReq.role) + .detail("Result", result.isError() ? result.getError().code() : success().code()); + } else if (snapReqMap.contains(snapReqKey)) { + CODE_PROBE(true, "Worker received a duplicate ongoing snapshot request", probe::decoration::rare); + TraceEvent("RetryOngoingWorkerSnapRequest") + .detail("SnapUID", snapReq.snapUID.toString()) + .detail("Role", snapReq.role); + ASSERT(snapReq.role == snapReqMap[snapReqKey].role); + ASSERT(snapReq.snapPayload == snapReqMap[snapReqKey].snapPayload); + // Discard the old request if a duplicate new request is received + // In theory, the old request should be discarded when we send this error since DD won't resend + // a request unless for a network error, where the old request is discarded before sending the + // duplicate request. + snapReqMap[snapReqKey].reply.sendError(duplicate_snapshot_request()); + snapReqMap[snapReqKey] = snapReq; + } else { + snapReqMap[snapReqKey] = snapReq; // set map point to the request + if (g_network->isSimulated() && (now() - lastSnapTime) < SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP) { + // duplicate snapshots on the same process for the same role is not allowed + auto okay = lastSnapReq.snapUID != snapReq.snapUID || lastSnapReq.role != snapReq.role; + TraceEvent(okay ? SevInfo : SevError, "RapidSnapRequestsOnSameProcess") + .detail("CurrSnapUID", snapReq.snapUID) + .detail("PrevSnapUID", lastSnapReq.snapUID) + .detail("CurrRole", snapReq.role) + .detail("PrevRole", lastSnapReq.role) + .detail("GapTime", now() - lastSnapTime); + } + auto* snapReqResultMapPtr = &snapReqResultMap; + errorForwarders.add(fmap( + [snapReqResultMapPtr, snapReqKey](Void _) { + snapReqResultMapPtr->erase(snapReqKey); + return Void(); + }, + delayed(workerSnapCreate(snapReq, + snapReq.role.toString() == "coord" ? coordFolder : folder, + snapReq.role.toString() == "tlog" && folder != tLogSpillFolder + ? Optional(tLogSpillFolder) + : Optional(), + &snapReqMap, + &snapReqResultMap), + SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP))); + if (g_network->isSimulated()) { + lastSnapReq = snapReq; + lastSnapTime = now(); + } + } + } + } + +public: + WorkerServerCore(WorkerInterface& interf, + Reference connRecord, + Reference> const> ccInterface, + LocalityData locality, + Reference> dbInfo, + Reference>> clusterId, + std::string const& folder, + std::string const& tLogSpillFolder, + std::string const& coordFolder, + std::string const& whitelistBinPaths, + int64_t memoryLimit, + PromiseStream errors, + ActorCollection& errorForwarders, + ActorCollection& filesClosed, + Reference>> ddInterf, + Reference>> rkInterf, + Reference>> csInterf, + Reference> degraded, + Reference> lowDiskTLogExclusion, + Reference> activeSharedTLog, + Reference> enablePrimaryTxnSystemHealthCheck, + std::map>& sharedLogs, + WorkerCache& backupWorkerCache, + WorkerCache& rangePartitionedBackupWorkerCache, + WorkerCache& logRouterCache, + std::set>& runningStorages, + std::unordered_map& storageCleaners, + Promise& rebootKVSPromise2, + Future& updateClusterIdFuture, + Future& loggingTrigger, + double& loggingDelay, + WorkerSnapRequest& lastSnapReq, + std::map& snapReqMap, + std::map>& snapReqResultMap, + double& lastSnapTime) + : interf(interf), connRecord(connRecord), ccInterface(ccInterface), locality(locality), dbInfo(dbInfo), + clusterId(clusterId), folder(folder), tLogSpillFolder(tLogSpillFolder), coordFolder(coordFolder), + whitelistBinPaths(whitelistBinPaths), memoryLimit(memoryLimit), errors(errors), + errorForwarders(errorForwarders), filesClosed(filesClosed), ddInterf(ddInterf), rkInterf(rkInterf), + csInterf(csInterf), degraded(degraded), lowDiskTLogExclusion(lowDiskTLogExclusion), + activeSharedTLog(activeSharedTLog), enablePrimaryTxnSystemHealthCheck(enablePrimaryTxnSystemHealthCheck), + sharedLogs(sharedLogs), backupWorkerCache(backupWorkerCache), + rangePartitionedBackupWorkerCache(rangePartitionedBackupWorkerCache), logRouterCache(logRouterCache), + runningStorages(runningStorages), storageCleaners(storageCleaners), rebootKVSPromise2(rebootKVSPromise2), + updateClusterIdFuture(updateClusterIdFuture), loggingTrigger(loggingTrigger), loggingDelay(loggingDelay), + lastSnapReq(lastSnapReq), snapReqMap(snapReqMap), snapReqResultMap(snapReqResultMap), + lastSnapTime(lastSnapTime) {} + + Future run(Future const& handleErrors) { + auto res = co_await race(serveServerDBInfoUpdates(), + interf.clientInterface.reboot.getFuture(), + serveFailureInjectionRequests(), + serveProfilerRequests(), + serveMasterRecruitment(), + serveDataDistributorRecruitment(), + serveRatekeeperRecruitment(), + serveConsistencyScanRecruitment(), + serveBackupRecruitment(), + serveRangePartitionedBackupRecruitment(), + serveTLogRecruitment(), + serveStorageRecruitment(), + serveCommitProxyRecruitment(), + serveGrvProxyRecruitment(), + serveCDCProxyRecruitment(), + serveResolverRecruitment(), + serveLogRouterRecruitment(), + serveCoordinationPings(), + serveMetricsLogging(), + serveEventLogRequests(), + serveTraceBatchDumpRequests(), + serveDiskStoreRequests(), + serveSnapshotRequests(), + errorForwarders.getResult(), + handleErrors); + ASSERT(res.index() == 1); + co_await handleRebootRequest(std::get<1>(std::move(res))); + } +}; Future workerServer(Reference connRecord, Reference> const> ccInterface, @@ -2387,733 +3278,42 @@ Future workerServer(Reference connRecord, healthMonitor(ccInterface, interf, locality, dbInfo, enablePrimaryTxnSystemHealthCheck)); } - while (true) { - auto res = co_await waitForWorkerServerEvent(interf, loggingTrigger, errorForwarders, handleErrors); - if (res.index() == 0) { - UpdateServerDBInfoRequest req = std::get<0>(std::move(res)); - - auto localInfo = BinaryReader::fromStringRef(req.serializedDbInfo, - AssumeVersion(g_network->protocolVersion())); - localInfo.myLocality = locality; - - if (localInfo.infoGeneration < dbInfo->get().infoGeneration && - localInfo.clusterInterface == dbInfo->get().clusterInterface) { - std::vector rep = req.broadcastInfo; - rep.push_back(interf.updateServerDBInfo.getEndpoint()); - req.reply.send(rep); - } else { - Optional notUpdated; - if (!ccInterface->get().present() || localInfo.clusterInterface != ccInterface->get().get()) { - notUpdated = interf.updateServerDBInfo.getEndpoint(); - } else if (localInfo.infoGeneration > dbInfo->get().infoGeneration || - dbInfo->get().clusterInterface != ccInterface->get().get()) { - TraceEvent("GotServerDBInfoChange") - .detail("ChangeID", localInfo.id) - .detail("InfoGeneration", localInfo.infoGeneration) - .detail("MasterID", localInfo.master.id()) - .detail("RatekeeperID", - localInfo.ratekeeper.present() ? localInfo.ratekeeper.get().id() : UID()) - .detail("DataDistributorID", - localInfo.distributor.present() ? localInfo.distributor.get().id() : UID()); - dbInfo->set(localInfo); - } - errorForwarders.add( - success(broadcastDBInfoRequest(req, SERVER_KNOBS->DBINFO_SEND_AMOUNT, notUpdated, true))); - - if (!updateClusterIdFuture.isValid() && !clusterId->get().present() && - localInfo.client.clusterId.isValid()) { - updateClusterIdFuture = updateClusterId(localInfo.client.clusterId, clusterId, folder); - } - } - } else if (res.index() == 1) { - RebootRequest req = std::get<1>(std::move(res)); - - RebootRequest rebootReq = req; - // If suspendDuration is INT_MAX, the trace will not be logged if it was inside the next block - // Also a useful trace to have even if suspendDuration is 0 - TraceEvent("RebootRequestSuspendingProcess").detail("Duration", req.waitForDuration); - if (req.waitForDuration) { - flushTraceFileVoid(); - setProfilingEnabled(0); - g_network->stop(); - threadSleep(req.waitForDuration); - } - if (rebootReq.checkData) { - Reference checkFile = co_await IAsyncFileSystem::filesystem()->open( - joinPath(folder, validationFilename), - IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, - 0600); - co_await checkFile->sync(); - } - - if (g_network->isSimulated()) { - TraceEvent("SimulatedReboot").detail("Deletion", rebootReq.deleteData); - if (rebootReq.deleteData) { - throw please_reboot_delete(); - } - throw please_reboot(); - } else { - TraceEvent("ProcessReboot").log(); - ASSERT(!rebootReq.deleteData); - flushAndExit(0); - } - } else if (res.index() == 2) { - SetFailureInjection req = std::get<2>(std::move(res)); - - if (FLOW_KNOBS->ENABLE_CHAOS_FEATURES) { - if (req.diskFailure.present()) { - auto diskFailureInjector = DiskFailureInjector::injector(); - diskFailureInjector->setDiskFailure(req.diskFailure.get().stallInterval, - req.diskFailure.get().stallPeriod, - req.diskFailure.get().throttlePeriod); - } else if (req.flipBits.present()) { - auto bitFlipper = BitFlipper::flipper(); - bitFlipper->setBitFlipPercentage(req.flipBits.get().percentBitFlips); - } - req.reply.send(Void()); - } else { - req.reply.sendError(client_invalid_operation()); - } - } else if (res.index() == 3) { - ProfilerRequest req = std::get<3>(std::move(res)); - - ProfilerRequest profilerReq = req; - // There really isn't a great "filepath sanitizer" or "filepath escape" function available, - // thus we instead enforce a different requirement. One can only write to a file that's - // beneath the working directory, and we remove the ability to do any symlink or ../.. - // tricks by resolving all paths through `abspath` first. - try { - std::string realLogDir = abspath(SERVER_KNOBS->LOG_DIRECTORY); - std::string realOutPath = abspath(realLogDir + "/" + profilerReq.outputFile.toString()); - if (realLogDir.size() < realOutPath.size() && - strncmp(realLogDir.c_str(), realOutPath.c_str(), realLogDir.size()) == 0) { - profilerReq.outputFile = realOutPath; - uncancellable(runProfiler(profilerReq)); - profilerReq.reply.send(Void()); - } else { - profilerReq.reply.sendError(client_invalid_operation()); - } - } catch (Error& e) { - profilerReq.reply.sendError(e); - } - } else if (res.index() == 4) { - RecruitMasterRequest req = std::get<4>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Master; - MasterInterface recruited; - recruited.locality = locality; - recruited.initEndpoints(); - - startRole(Role::MASTER, recruited.id(), interf.id()); - - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.getCommitVersion); - DUMPTOKEN(recruited.getLiveCommittedVersion); - DUMPTOKEN(recruited.reportLiveCommittedVersion); - DUMPTOKEN(recruited.updateRecoveryData); - - // printf("Recruited as masterServer\n"); - Future masterProcess = masterServer( - recruited, dbInfo, ccInterface, ServerCoordinators(connRecord), req.lifetime, req.forceRecovery); - errorForwarders.add( - zombie(recruited, forwardError(errors, Role::MASTER, recruited.id(), masterProcess))); - req.reply.send(recruited); - } else if (res.index() == 5) { - InitializeDataDistributorRequest req = std::get<5>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::DataDistributor; - DataDistributorInterface recruited(locality, req.reqId); - recruited.initEndpoints(); - - if (ddInterf->get().present()) { - recruited = ddInterf->get().get(); - CODE_PROBE(true, "Recruited while already a data distributor."); - } else { - startRole(Role::DATA_DISTRIBUTOR, recruited.id(), interf.id()); - DUMPTOKEN(recruited.waitFailure); - - Future dataDistributorProcess = dataDistributor(recruited, dbInfo, folder); - errorForwarders.add(forwardError( - errors, - Role::DATA_DISTRIBUTOR, - recruited.id(), - setWhenDoneOrError(dataDistributorProcess, ddInterf, Optional()))); - ddInterf->set(Optional(recruited)); - } - TraceEvent("DataDistributorReceived", req.reqId) - .detail("DataDistributorId", recruited.id()) - .detail("Folder", folder); // double check if this works with SS restore - req.reply.send(recruited); - } else if (res.index() == 6) { - InitializeRatekeeperRequest req = std::get<6>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Ratekeeper; - RatekeeperInterface recruited(locality, req.reqId); - recruited.initEndpoints(); - - if (rkInterf->get().present()) { - recruited = rkInterf->get().get(); - CODE_PROBE(true, "Recruited while already a ratekeeper."); - } else { - startRole(Role::RATEKEEPER, recruited.id(), interf.id()); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.getRateInfo); - DUMPTOKEN(recruited.haltRatekeeper); - DUMPTOKEN(recruited.reportCommitCostEstimation); - - Future ratekeeperProcess = ratekeeper(recruited, dbInfo); - errorForwarders.add( - forwardError(errors, - Role::RATEKEEPER, - recruited.id(), - setWhenDoneOrError(ratekeeperProcess, rkInterf, Optional()))); - rkInterf->set(Optional(recruited)); - } - TraceEvent("Ratekeeper_InitRequest", req.reqId).detail("RatekeeperId", recruited.id()); - req.reply.send(recruited); - } else if (res.index() == 7) { - InitializeConsistencyScanRequest req = std::get<7>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::ConsistencyScan; - ConsistencyScanInterface recruited(locality, req.reqId); - recruited.initEndpoints(); - - if (csInterf->get().present()) { - recruited = csInterf->get().get(); - CODE_PROBE(true, "Recovered while already a consistencyscan"); - } else { - startRole(Role::CONSISTENCYSCAN, recruited.id(), interf.id()); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.haltConsistencyScan); - - Future consistencyScanProcess = consistencyScan(recruited, dbInfo); - errorForwarders.add(forwardError( - errors, - Role::CONSISTENCYSCAN, - recruited.id(), - setWhenDoneOrError(consistencyScanProcess, csInterf, Optional()))); - csInterf->set(Optional(recruited)); - } - TraceEvent("ConsistencyScanReceived", req.reqId).detail("ConsistencyScanId", recruited.id()); - req.reply.send(recruited); - } else if (res.index() == 8) { - InitializeBackupRequest req = std::get<8>(std::move(res)); - - if (!backupWorkerCache.exists(req.reqId)) { - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; - BackupInterface recruited(locality); - recruited.initEndpoints(); - - startRole(Role::BACKUP, recruited.id(), interf.id()); - DUMPTOKEN(recruited.waitFailure); - - ReplyPromise backupReady = req.reply; - backupWorkerCache.set(req.reqId, backupReady.getFuture()); - Future backupProcess = backupWorker(recruited, req, dbInfo); - backupProcess = backupWorkerCache.removeOnReady(req.reqId, backupProcess); - errorForwarders.add(forwardError(errors, Role::BACKUP, recruited.id(), backupProcess)); - TraceEvent("BackupInitRequest", req.reqId).detail("BackupId", recruited.id()); - InitializeBackupReply reply(recruited, req.backupEpoch); - backupReady.send(reply); - } else { - forwardPromise(Uncancellable{}, req.reply, backupWorkerCache.get(req.reqId)); - } - } else if (res.index() == 9) { - InitializeRangePartitionedBackupRequest req = std::get<9>(std::move(res)); - - if (!rangePartitionedBackupWorkerCache.exists(req.reqId)) { - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Backup; - BackupInterface recruited(locality); - recruited.initEndpoints(); - - startRole(Role::BACKUP, recruited.id(), interf.id()); - DUMPTOKEN(recruited.waitFailure); - - ReplyPromise backupReady = req.reply; - rangePartitionedBackupWorkerCache.set(req.reqId, backupReady.getFuture()); - Future backupProcess = rangePartitionedBackupWorker(recruited, req, dbInfo); - backupProcess = rangePartitionedBackupWorkerCache.removeOnReady(req.reqId, backupProcess); - errorForwarders.add(forwardError(errors, Role::BACKUP, recruited.id(), backupProcess)); - TraceEvent("RangePartitionedBWInitRequest", req.reqId).detail("BackupId", recruited.id()); - InitializeRangePartitionedBackupReply reply(recruited, req.backupEpoch); - backupReady.send(reply); - } else { - forwardPromise(Uncancellable{}, req.reply, rangePartitionedBackupWorkerCache.get(req.reqId)); - } - } else if (res.index() == 10) { - InitializeTLogRequest req = std::get<10>(std::move(res)); - - // For now, there's a one-to-one mapping of spill type to TLogVersion. - // With future work, a particular version of the TLog can support multiple - // different spilling strategies, at which point SpillType will need to be - // plumbed down into tLogFn. - if (req.logVersion < TLogVersion::MIN_RECRUITABLE) { - TraceEvent(SevError, "InitializeTLogInvalidLogVersion") - .detail("Version", req.logVersion) - .detail("MinRecruitable", TLogVersion::MIN_RECRUITABLE); - req.reply.sendError(internal_error()); - } - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::TLog; - TLogOptions tLogOptions(req.logVersion, req.spillType); - TLogFn tLogFn = tLogFnForOptions(tLogOptions); - auto& logData = sharedLogs[SharedLogsKey(tLogOptions, req.storeType)]; - while (!logData.empty() && (!logData.back().actor.isValid() || logData.back().actor.isReady())) { - logData.pop_back(); - } - if (logData.empty()) { - UID logId = deterministicRandom()->randomUniqueID(); - std::map details; - details["ForMaster"] = req.recruitmentID.shortString(); - details["StorageEngine"] = req.storeType.toString(); - - // FIXME: start role for every tlog instance, rather that just for the shared actor, also use a - // different role type for the shared actor - startRole(Role::SHARED_TRANSACTION_LOG, logId, interf.id(), details); - - const StringRef prefix = - req.logVersion > TLogVersion::V2 ? fileVersionedLogDataPrefix : fileLogDataPrefix; - std::string filename = filenameFromId( - req.storeType, tLogSpillFolder, prefix.toString() + tLogOptions.toPrefix(), logId); - IKeyValueStore* data = - openKVStore(req.storeType, filename, logId, memoryLimit, false, false, dbInfo); - const DiskQueueVersion dqv = tLogOptions.getDiskQueueVersion(); - IDiskQueue* queue = openDiskQueue( - joinPath(folder, - fileLogQueuePrefix.toString() + tLogOptions.toPrefix() + logId.toString() + "-"), - tlogQueueExtension.toString(), - logId, - dqv); - filesClosed.add(data->onClosed()); - filesClosed.add(queue->onClosed()); - - logData.push_back(SharedLogsValue()); - Future tLogCore = tLogFn(data, - queue, - dbInfo, - locality, - logData.back().requests, - logId, - interf.id(), - false, - Promise(), - Promise(), - folder, - degraded, - lowDiskTLogExclusion, - activeSharedTLog, - enablePrimaryTxnSystemHealthCheck); - tLogCore = handleIOErrors(tLogCore, data, logId); - tLogCore = handleIOErrors(tLogCore, queue, logId); - errorForwarders.add(forwardError(errors, Role::SHARED_TRANSACTION_LOG, logId, tLogCore)); - logData.back().actor = tLogCore; - logData.back().uid = logId; - } - logData.back().requests.send(req); - activeSharedTLog->set(logData.back().uid); - } else if (res.index() == 11) { - InitializeStorageRequest req = std::get<11>(std::move(res)); - - TraceEvent e("StorageServerInitProgress", req.interfaceId); - e.detail("Step", "1.RequestReceived"); - e.detail("ReqID", req.reqId); - e.detail("WorkerID", interf.id()); - e.detail("StorageType", req.storeType.toString()); - e.detail("SeedTag", req.seedTag.toString()); - e.detail("IsTssPair", req.tssPairIDAndVersion.present()); - if (req.tssPairIDAndVersion.present()) { - e.detail("TssPairID", req.tssPairIDAndVersion.get().first); - } - int j = 0; - for (const auto& runningStorage : runningStorages) { - e.detail("RunningStorageIDOnSameWorker" + std::to_string(j), runningStorage.first); - e.detail("RunningStorageEngineOnSameWorker" + std::to_string(j), runningStorage.second); - j++; - } - // We want to prevent double recruiting on a worker unless we try to recruit something - // with a different storage engine (otherwise storage migration won't work for certain - // configuration). Additionally we also need to allow double recruitment for seed servers. - // The reason for this is that a storage will only remove itself if after it was able - // to read the system key space. But if recovery fails right after a `configure new ...` - // was run it won't be able to do so. - if (std::all_of(runningStorages.begin(), - runningStorages.end(), - [&req](const auto& p) { return p.second != req.storeType; }) || - req.seedTag != invalidTag) { - ASSERT(req.initialClusterVersion >= 0); - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Storage; - - // When a new storage server is recruited, we need to check if any other storage - // server has run on this worker process(a.k.a double recruitment). The previous storage - // server may have leftover disk files if it stopped with io_error or io_timeout. Now DD - // already repairs the team and it's time to start the cleanup - cleanupStorageDisks(dbInfo, storageCleaners, memoryLimit); - - bool isTss = req.tssPairIDAndVersion.present(); - StorageServerInterface recruited(req.interfaceId); - recruited.locality = locality; - recruited.tssPairID = isTss ? req.tssPairIDAndVersion.get().first : Optional(); - recruited.initEndpoints(); - - std::map details; - details["StorageEngine"] = req.storeType.toString(); - details["IsTSS"] = std::to_string(isTss); - Role ssRole = isTss ? Role::TESTING_STORAGE_SERVER : Role::STORAGE_SERVER; - startRole(ssRole, recruited.id(), interf.id(), details); - TraceEvent("StorageServerInitProgress", recruited.id()) - .detail("ReqID", req.reqId) - .detail("StorageType", req.storeType.toString()) - .detail("Step", "2.RoleStarted") - .detail("WorkerID", interf.id()); - - DUMPTOKEN(recruited.getValue); - DUMPTOKEN(recruited.getKey); - DUMPTOKEN(recruited.getKeyValues); - DUMPTOKEN(recruited.getMappedKeyValues); - DUMPTOKEN(recruited.getShardState); - DUMPTOKEN(recruited.waitMetrics); - DUMPTOKEN(recruited.splitMetrics); - DUMPTOKEN(recruited.getReadHotRanges); - DUMPTOKEN(recruited.getRangeSplitPoints); - DUMPTOKEN(recruited.getStorageMetrics); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.getQueuingMetrics); - DUMPTOKEN(recruited.getKeyValueStoreType); - DUMPTOKEN(recruited.watchValue); - DUMPTOKEN(recruited.getKeyValuesStream); - DUMPTOKEN(recruited.changeFeedStream); - DUMPTOKEN(recruited.changeFeedPop); - DUMPTOKEN(recruited.changeFeedVersionUpdate); - - std::string filename = - filenameFromId(req.storeType, - folder, - isTss ? testingStoragePrefix.toString() : fileStoragePrefix.toString(), - recruited.id()); - IKeyValueStore* data = - openKVStore(req.storeType, filename, recruited.id(), memoryLimit, false, false, dbInfo, 0); - TraceEvent("StorageServerInitProgress", recruited.id()) - .detail("ReqID", req.reqId) - .detail("StorageType", req.storeType.toString()) - .detail("Step", "3.KVStoreOpened") - .detail("WorkerID", interf.id()); - - Future kvClosed = - data->onClosed() || - rebootKVSPromise2 - .getFuture() /* clear the onClosed() Future in actorCollection when rebooting */; - filesClosed.add(kvClosed); - ReplyPromise storageReady = req.reply; - Future> storeError = errorOr(data->getError()); - Future s = storageServer(data, - recruited, - req.seedTag, - req.initialClusterVersion, - isTss ? req.tssPairIDAndVersion.get().second : 0, - storageReady, - dbInfo, - folder); - s = handleIOErrors(s, storeError, recruited.id(), kvClosed); - s = storageServerRollbackRebooter(&runningStorages, - &storageCleaners, - s, - req.storeType, - filename, - recruited.id(), - recruited.locality, - isTss, - dbInfo, - folder, - &filesClosed, - memoryLimit, - data, - false, - &rebootKVSPromise2); - errorForwarders.add(forwardError(errors, ssRole, recruited.id(), s)); - } else { - TraceEvent("AttemptedDoubleRecruitment", interf.id()).detail("ForRole", "StorageServer"); - errorForwarders.add(map(delay(0.5), [reply = req.reply](Void) { - reply.sendError(recruitment_failed()); - return Void(); - })); - } - } else if (res.index() == 12) { - InitializeCommitProxyRequest req = std::get<12>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::CommitProxy; - CommitProxyInterface recruited; - recruited.processId = locality.processId(); - recruited.provisional = false; - recruited.initEndpoints(); - - std::map details; - details["ForMaster"] = req.master.id().shortString(); - startRole(Role::COMMIT_PROXY, recruited.id(), interf.id(), details); - - DUMPTOKEN(recruited.commit); - DUMPTOKEN(recruited.getKeyServersLocations); - DUMPTOKEN(recruited.getStorageServerRejoinInfo); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.txnState); - - errorForwarders.add(zombie(recruited, - forwardError(errors, - Role::COMMIT_PROXY, - recruited.id(), - commitProxyServer(recruited, req, dbInfo, whitelistBinPaths)))); - req.reply.send(recruited); - } else if (res.index() == 13) { - InitializeGrvProxyRequest req = std::get<13>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::GrvProxy; - GrvProxyInterface recruited; - recruited.processId = locality.processId(); - recruited.provisional = false; - recruited.initEndpoints(); - - std::map details; - details["ForMaster"] = req.master.id().shortString(); - startRole(Role::GRV_PROXY, recruited.id(), interf.id(), details); - - DUMPTOKEN(recruited.getConsistentReadVersion); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.getHealthMetrics); - - // printf("Recruited as grvProxyServer\n"); - errorForwarders.add(zombie( - recruited, - forwardError(errors, Role::GRV_PROXY, recruited.id(), grvProxyServer(recruited, req, dbInfo)))); - req.reply.send(recruited); - } else if (res.index() == 14) { - InitializeCDCProxyRequest req = std::get<14>(std::move(res)); - - LocalLineage _; - CDCProxyInterface recruited; - recruited.processId = locality.processId(); - recruited.initEndpoints(); - - std::map details; - startRole(Role::CDC_PROXY, recruited.id(), interf.id(), details); - - DUMPTOKEN(recruited.consume); - DUMPTOKEN(recruited.registerStream); - DUMPTOKEN(recruited.removeStream); - DUMPTOKEN(recruited.ack); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.haltForTesting); - DUMPTOKEN(recruited.getBufferStatusForTesting); - DUMPTOKEN(recruited.setPopsPausedForTesting); - - errorForwarders.add(zombie(recruited, - forwardError(errors, - Role::CDC_PROXY, - recruited.id(), - cdcProxyServer(recruited, req.recoveryCount, dbInfo)))); - req.reply.send(recruited); - } else if (res.index() == 15) { - InitializeResolverRequest req = std::get<15>(std::move(res)); - - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::Resolver; - ResolverInterface recruited; - recruited.locality = locality; - recruited.initEndpoints(); - - std::map details; - startRole(Role::RESOLVER, recruited.id(), interf.id(), details); - - DUMPTOKEN(recruited.resolve); - DUMPTOKEN(recruited.metrics); - DUMPTOKEN(recruited.split); - DUMPTOKEN(recruited.waitFailure); - - errorForwarders.add(zombie( - recruited, forwardError(errors, Role::RESOLVER, recruited.id(), resolver(recruited, req, dbInfo)))); - req.reply.send(recruited); - } else if (res.index() == 16) { - InitializeLogRouterRequest req = std::get<16>(std::move(res)); - - if (!logRouterCache.exists(req.reqId)) { - LocalLineage _; - getCurrentLineage()->modify(&RoleLineage::role) = recruitment::LogRouter; - TLogInterface recruited(locality); - recruited.initEndpoints(); - - std::map details; - startRole(Role::LOG_ROUTER, recruited.id(), interf.id(), details); - - DUMPTOKEN(recruited.peekMessages); - DUMPTOKEN(recruited.peekStreamMessages); - DUMPTOKEN(recruited.popMessages); - DUMPTOKEN(recruited.commit); - DUMPTOKEN(recruited.lock); - DUMPTOKEN(recruited.getQueuingMetrics); - DUMPTOKEN(recruited.confirmRunning); - DUMPTOKEN(recruited.waitFailure); - DUMPTOKEN(recruited.recoveryFinished); - DUMPTOKEN(recruited.disablePopRequest); - DUMPTOKEN(recruited.enablePopRequest); - DUMPTOKEN(recruited.snapRequest); - - ReplyPromise logRouterReady = req.reply; - logRouterCache.set(req.reqId, logRouterReady.getFuture()); - Future logRouterProcess = logRouter(recruited, req, dbInfo); - logRouterProcess = logRouterCache.removeOnReady(req.reqId, logRouterProcess); - errorForwarders.add( - zombie(recruited, forwardError(errors, Role::LOG_ROUTER, recruited.id(), logRouterProcess))); - - TraceEvent("LogRouterInitRequest", req.reqId).detail("LogRouterId", recruited.id()); - if (!skipInitRspInSim(interf.id(), req.allowDropInSim)) { - logRouterReady.send(recruited); - } - } else { - forwardPromise(Uncancellable{}, req.reply, logRouterCache.get(req.reqId)); - } - } else if (res.index() == 17) { - CoordinationPingMessage m = std::get<17>(std::move(res)); - - TraceEvent("CoordinationPing", interf.id()) - .detail("CCID", m.clusterControllerId) - .detail("TimeStep", m.timeStep); - } else if (res.index() == 18) { - SetMetricsLogRateRequest req = std::get<18>(std::move(res)); - - TraceEvent("LoggingRateChange", interf.id()) - .detail("OldDelay", loggingDelay) - .detail("NewLogPS", req.metricsLogsPerSecond); - if (req.metricsLogsPerSecond != 0) { - loggingDelay = 1.0 / req.metricsLogsPerSecond; - loggingTrigger = Void(); - } - } else if (res.index() == 19) { - EventLogRequest req = std::get<19>(std::move(res)); - - TraceEventFields e; - if (req.getLastError) - e = latestEventCache.getLatestError(); - else - e = latestEventCache.get(req.eventName.toString()); - req.reply.send(e); - } else if (res.index() == 20) { - TraceBatchDumpRequest req = std::get<20>(std::move(res)); - - g_traceBatch.dump(); - req.reply.send(Void()); - } else if (res.index() == 21) { - DiskStoreRequest req = std::get<21>(std::move(res)); - - Standalone> ids; - // NOTE: this request is mainly for consistency checking. The current - // logic below seems to be holding up OK, but if we discover bugs in this - // area, another approach would be to make the server here simply return - // everything it knows about the DiskStore, and put all the checking logic - // on the client side. This makes the checking logic itself easier to test - // locally via test cases with defined consistency bugs. - for (DiskStore d : getDiskStores(folder, tLogSpillFolder)) { - bool included = true; - if (!req.includePartialStores) { - if (d.storeType == KeyValueStoreType::SSD_BTREE_V1) { - included = fileExists(d.filename + ".fdb-wal"); - } else if (d.storeType == KeyValueStoreType::SSD_BTREE_V2) { - included = fileExists(d.filename + ".sqlite-wal"); - } else if (d.storeType == KeyValueStoreType::SSD_REDWOOD_V1) { - included = fileExists(d.filename + "0.pagerlog") && fileExists(d.filename + "1.pagerlog"); - } else if (d.storeType == KeyValueStoreType::SSD_ROCKSDB_V1) { - included = fileExists(joinPath(d.filename, "CURRENT")) && - fileExists(joinPath(d.filename, "IDENTITY")); - } else if (d.storeType == KeyValueStoreType::SSD_SHARDED_ROCKSDB) { - included = fileExists(joinPath(d.filename, "CURRENT")) && - fileExists(joinPath(d.filename, "IDENTITY")); - } else if (d.storeType == KeyValueStoreType::MEMORY) { - included = fileExists(d.filename + "1.fdq"); - } else { - ASSERT(d.storeType == KeyValueStoreType::MEMORY_RADIXTREE); - included = fileExists(d.filename + "1.fdr"); - } - if (d.storedComponent == DiskStore::COMPONENT::TLogData) { - // Changes to tlog spilling design are believed to make this check - // unnecessary. - included = false; - } - } - if (included) { - ids.push_back(ids.arena(), d.storeID); - } - } - req.reply.send(ids); - } else if (res.index() == 22) { - - systemMonitor(); - loggingTrigger = delay(loggingDelay, TaskPriority::FlushTrace); - } else if (res.index() == 23) { - WorkerSnapRequest snapReq = std::get<23>(std::move(res)); - - std::string snapReqKey = snapReq.snapUID.toString() + snapReq.role.toString(); - if (snapReqResultMap.contains(snapReqKey)) { - CODE_PROBE(true, "Worker received a duplicate finished snapshot request", probe::decoration::rare); - auto result = snapReqResultMap[snapReqKey]; - result.isError() ? snapReq.reply.sendError(result.getError()) : snapReq.reply.send(result.get()); - TraceEvent("RetryFinishedWorkerSnapRequest") - .detail("SnapUID", snapReq.snapUID.toString()) - .detail("Role", snapReq.role) - .detail("Result", result.isError() ? result.getError().code() : success().code()); - } else if (snapReqMap.contains(snapReqKey)) { - CODE_PROBE(true, "Worker received a duplicate ongoing snapshot request", probe::decoration::rare); - TraceEvent("RetryOngoingWorkerSnapRequest") - .detail("SnapUID", snapReq.snapUID.toString()) - .detail("Role", snapReq.role); - ASSERT(snapReq.role == snapReqMap[snapReqKey].role); - ASSERT(snapReq.snapPayload == snapReqMap[snapReqKey].snapPayload); - // Discard the old request if a duplicate new request is received - // In theory, the old request should be discarded when we send this error since DD won't resend - // a request unless for a network error, where the old request is discarded before sending the - // duplicate request. - snapReqMap[snapReqKey].reply.sendError(duplicate_snapshot_request()); - snapReqMap[snapReqKey] = snapReq; - } else { - snapReqMap[snapReqKey] = snapReq; // set map point to the request - if (g_network->isSimulated() && (now() - lastSnapTime) < SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP) { - // duplicate snapshots on the same process for the same role is not allowed - auto okay = lastSnapReq.snapUID != snapReq.snapUID || lastSnapReq.role != snapReq.role; - TraceEvent(okay ? SevInfo : SevError, "RapidSnapRequestsOnSameProcess") - .detail("CurrSnapUID", snapReq.snapUID) - .detail("PrevSnapUID", lastSnapReq.snapUID) - .detail("CurrRole", snapReq.role) - .detail("PrevRole", lastSnapReq.role) - .detail("GapTime", now() - lastSnapTime); - } - auto* snapReqResultMapPtr = &snapReqResultMap; - errorForwarders.add(fmap( - [snapReqResultMapPtr, snapReqKey](Void _) { - snapReqResultMapPtr->erase(snapReqKey); - return Void(); - }, - delayed(workerSnapCreate(snapReq, - snapReq.role.toString() == "coord" ? coordFolder : folder, - snapReq.role.toString() == "tlog" && folder != tLogSpillFolder - ? Optional(tLogSpillFolder) - : Optional(), - &snapReqMap, - &snapReqResultMap), - SERVER_KNOBS->SNAP_MINIMUM_TIME_GAP))); - if (g_network->isSimulated()) { - lastSnapReq = snapReq; - lastSnapTime = now(); - } - } - } else { - ASSERT(res.index() == 24 || res.index() == 25); - } - } + WorkerServerCore workerServerCore(interf, + connRecord, + ccInterface, + locality, + dbInfo, + clusterId, + folder, + tLogSpillFolder, + coordFolder, + whitelistBinPaths, + memoryLimit, + errors, + errorForwarders, + filesClosed, + ddInterf, + rkInterf, + csInterf, + degraded, + lowDiskTLogExclusion, + activeSharedTLog, + enablePrimaryTxnSystemHealthCheck, + sharedLogs, + backupWorkerCache, + rangePartitionedBackupWorkerCache, + logRouterCache, + runningStorages, + storageCleaners, + rebootKVSPromise2, + updateClusterIdFuture, + loggingTrigger, + loggingDelay, + lastSnapReq, + snapReqMap, + snapReqResultMap, + lastSnapTime); + co_await workerServerCore.run(handleErrors); } catch (Error& err) { e = err; } From d4edb8721796bcfdfe6dc073be978286209bfe35 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Tue, 14 Jul 2026 06:00:09 -0700 Subject: [PATCH 16/39] Remove redundant worker coroutine guards --- fdbserver/worker/worker.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index 6d86f460ac..c8afaa79bc 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -191,10 +191,6 @@ Future handleIOErrors(Future actor, e.get(); co_return; } - if (res.index() != 1) { - UNREACHABLE(); - } - ErrorOr e = std::get<1>(std::move(res)); TraceEvent("WorkerTerminatingByIOError", id).errorUnsuppressed(e.getError()); actor.cancel(); @@ -2702,8 +2698,6 @@ class WorkerServerCore { loggingTrigger = Void(); } } else { - ASSERT(res.index() == 1); - systemMonitor(); loggingTrigger = delay(loggingDelay, TaskPriority::FlushTrace); } @@ -4028,7 +4022,7 @@ Future serveProcess() { GetProcessInterfaceRequest req = std::get<0>(std::move(res)); req.reply.send(process); - } else if (res.index() == 1) { + } else { ActorLineageRequest req = std::get<1>(std::move(res)); SampleCollection sampleCollector; @@ -4046,8 +4040,6 @@ Future serveProcess() { } ActorLineageReply reply{ serializedSamples }; req.reply.send(reply); - } else { - UNREACHABLE(); } } } From 604a4b3c58141329893c0d80bbc6513f4461ba58 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 09:55:43 -0700 Subject: [PATCH 17/39] Fix worker coroutine clang-tidy warnings --- fdbserver/worker/worker.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index c8afaa79bc..200eb6408a 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -172,7 +172,7 @@ Future handleIOErrors(Future actor, Future onClosed = Void()) { auto res = co_await race(errorOr(actor), storeError); if (res.index() == 0) { - ErrorOr e = std::get<0>(std::move(res)); + ErrorOr e = std::get<0>(res); if (e.isError() && e.getError().code() == error_code_please_reboot) { // no need to wait. @@ -191,7 +191,7 @@ Future handleIOErrors(Future actor, e.get(); co_return; } - ErrorOr e = std::get<1>(std::move(res)); + ErrorOr e = std::get<1>(res); TraceEvent("WorkerTerminatingByIOError", id).errorUnsuppressed(e.getError()); actor.cancel(); // file_not_found can occur due to attempting to open a partially deleted DiskQueue, which should not be reported @@ -378,9 +378,9 @@ struct TLogOptions { static ErrorOr FromStringRef(StringRef s) { TLogOptions options; - for (StringRef key = s.eat("_"), value = s.eat("_"); s.size() != 0 || key.size(); + for (StringRef key = s.eat("_"), value = s.eat("_"); !s.empty() || !key.empty(); key = s.eat("_"), value = s.eat("_")) { - if (key.size() != 0 && value.size() == 0) + if (!key.empty() && value.empty()) return default_error_or(); if (key == "V"_sr) { @@ -664,7 +664,7 @@ Future registrationClient(Reference(std::move(res)); + RegisterWorkerReply reply = std::get<0>(res); processClass = reply.processClass; asyncPriorityInfo->set(reply.priorityInfo); TraceEvent("WorkerRegisterReply") @@ -1822,7 +1822,7 @@ Future chaosMetricsLogger() { if (!res) co_return; - ChaosMetrics* chaosMetrics = static_cast(res); + auto* chaosMetrics = static_cast(res); chaosMetrics->clear(); while (true) { @@ -2688,7 +2688,7 @@ class WorkerServerCore { while (true) { auto res = co_await race(interf.setMetricsRate.getFuture(), loggingTrigger); if (res.index() == 0) { - SetMetricsLogRateRequest req = std::get<0>(std::move(res)); + SetMetricsLogRateRequest req = std::get<0>(res); TraceEvent("LoggingRateChange", interf.id()) .detail("OldDelay", loggingDelay) @@ -2737,7 +2737,7 @@ class WorkerServerCore { // everything it knows about the DiskStore, and put all the checking logic // on the client side. This makes the checking logic itself easier to test // locally via test cases with defined consistency bugs. - for (DiskStore d : getDiskStores(folder, tLogSpillFolder)) { + for (const DiskStore& d : getDiskStores(folder, tLogSpillFolder)) { bool included = true; if (!req.includePartialStores) { if (d.storeType == KeyValueStoreType::SSD_BTREE_V1) { @@ -2908,7 +2908,7 @@ public: errorForwarders.getResult(), handleErrors); ASSERT(res.index() == 1); - co_await handleRebootRequest(std::get<1>(std::move(res))); + co_await handleRebootRequest(std::get<1>(res)); } }; @@ -2994,8 +2994,8 @@ Future workerServer(Reference connRecord, folder = abspath(folder); tLogSpillFolder = abspath(tLogSpillFolder); - if (metricsPrefix.size() > 0) { - if (metricsConnFile.size() > 0) { + if (!metricsPrefix.empty()) { + if (!metricsConnFile.empty()) { try { Database db = Database::createDatabase(metricsConnFile, ApiVersion::LATEST_VERSION, IsInternal::True, locality); @@ -3005,7 +3005,7 @@ Future workerServer(Reference connRecord, TraceEvent(SevWarnAlways, "TDMetricsBadClusterFile").error(e).detail("ConnFile", metricsConnFile); } } else { - auto lockAware = metricsPrefix.size() && metricsPrefix[0] == '\xff' ? LockAware::True : LockAware::False; + auto lockAware = !metricsPrefix.empty() && metricsPrefix[0] == '\xff' ? LockAware::True : LockAware::False; auto database = openDBOnServer(dbInfo, TaskPriority::DefaultEndpoint, lockAware); metricsLogger = runMetrics(database, KeyRef(metricsPrefix)); database->globalConfig->trigger(samplingFrequency, samplingProfilerUpdateFrequency); @@ -3376,12 +3376,13 @@ Future printOnFirstConnected(Reference } ClusterControllerPriorityInfo getCCPriorityInfo(std::string filePath, ProcessClass processClass) { - if (!fileExists(filePath)) + if (!fileExists(filePath)) { return ClusterControllerPriorityInfo( recruitment::machineClassFitness(ProcessClass(processClass.classType(), ProcessClass::CommandLineSource), recruitment::ClusterController), false, ClusterControllerPriorityInfo::FitnessUnknown); + } std::string contents(readFileBytes(filePath, 1000)); BinaryReader br(StringRef(contents), IncludeVersion()); ClusterControllerPriorityInfo priorityInfo( @@ -3919,7 +3920,7 @@ Future monitorLeaderWithDelayedCandidacyImplOneGeneration( request.knownLeader = leader.get().get().changeID; ClusterControllerPriorityInfo info = leader.get().get().getPriorityInfo(); - if (leader.get().get().serializedInfo.size() && !info.isExcluded && + if (!leader.get().get().serializedInfo.empty() && !info.isExcluded && (info.dcFitness == ClusterControllerPriorityInfo::FitnessPrimary || info.dcFitness == ClusterControllerPriorityInfo::FitnessPreferred || info.dcFitness == ClusterControllerPriorityInfo::FitnessUnknown)) { @@ -4093,7 +4094,7 @@ Future fdbd(Reference connRecord, // SOMEDAY: start the services on the machine in a staggered fashion in simulation? // Endpoints should be registered first before any process trying to connect to it. // So coordinationServer actor should be the first one executed before any other. - if (coordFolder.size()) { + if (!coordFolder.empty()) { actors.push_back(coordinationServer(coordFolder, connRecord)); } From 22ce634182c20f74d39e80b1d6ed333ad419e0ef Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 15:02:11 -0700 Subject: [PATCH 18/39] Convert ClusterController monitors to coroutines --- .../ClusterController.actor.cpp | 173 +++++++++--------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 54c89f7271..f4c14b5b8e 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -2384,63 +2384,61 @@ Future updatedChangingDatacenters(ClusterControllerData* self) { } } -ACTOR Future updatedChangedDatacenters(ClusterControllerData* self) { - state Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); - state Future onChange = self->changingDcIds.onChange(); - loop { - choose { - when(wait(onChange)) { - changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); - onChange = self->changingDcIds.onChange(); - } - when(wait(changeDelay)) { - changeDelay = Never(); - onChange = self->changingDcIds.onChange(); +Future updatedChangedDatacenters(ClusterControllerData* self) { + Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); + Future onChange = self->changingDcIds.onChange(); + while (true) { + auto res = co_await race(onChange, changeDelay); + if (res.index() == 0) { + changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); + onChange = self->changingDcIds.onChange(); + } else if (res.index() == 1) { + changeDelay = Never(); + onChange = self->changingDcIds.onChange(); - self->changedDcIds.set(self->changingDcIds.get()); - if (self->changedDcIds.get().second.present()) { - TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); - if (!self->changedDcIds.get().first) { - auto& worker = self->id_worker[self->clusterControllerProcessId]; - uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( - worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); - if (worker.priorityInfo.dcFitness != newFitness) { - worker.priorityInfo.dcFitness = newFitness; - if (!worker.reply.isSet()) { - worker.reply.send( - RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); - } + self->changedDcIds.set(self->changingDcIds.get()); + if (self->changedDcIds.get().second.present()) { + TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); + if (!self->changedDcIds.get().first) { + auto& worker = self->id_worker[self->clusterControllerProcessId]; + uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( + worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (worker.priorityInfo.dcFitness != newFitness) { + worker.priorityInfo.dcFitness = newFitness; + if (!worker.reply.isSet()) { + worker.reply.send(RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); } - } else { - state int currentFit = recruitment::BestFit; - while (currentFit <= recruitment::NeverAssign) { - bool updated = false; - for (auto& it : self->id_worker) { - if ((!it.second.priorityInfo.isExcluded && - it.second.priorityInfo.processClassFitness == currentFit) || - currentFit == recruitment::NeverAssign) { - uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( - it.second.details.interf.locality.dcId(), - self->changedDcIds.get().second.get()); - if (it.first != self->clusterControllerProcessId && - it.second.priorityInfo.dcFitness != fitness) { - updated = true; - it.second.priorityInfo.dcFitness = fitness; - if (!it.second.reply.isSet()) { - it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, - it.second.priorityInfo)); - } + } + } else { + int currentFit = recruitment::BestFit; + while (currentFit <= recruitment::NeverAssign) { + bool updated = false; + for (auto& it : self->id_worker) { + if ((!it.second.priorityInfo.isExcluded && + it.second.priorityInfo.processClassFitness == currentFit) || + currentFit == recruitment::NeverAssign) { + uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( + it.second.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (it.first != self->clusterControllerProcessId && + it.second.priorityInfo.dcFitness != fitness) { + updated = true; + it.second.priorityInfo.dcFitness = fitness; + if (!it.second.reply.isSet()) { + it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, + it.second.priorityInfo)); } } } - if (updated && currentFit < recruitment::NeverAssign) { - wait(delay(SERVER_KNOBS->CC_CLASS_DELAY)); - } - currentFit++; } + if (updated && currentFit < recruitment::NeverAssign) { + co_await delay(SERVER_KNOBS->CC_CLASS_DELAY); + } + currentFit++; } } } + } else { + UNREACHABLE(); } } } @@ -2807,13 +2805,13 @@ Future startDataDistributor(ClusterControllerData* self, double waitTime) } } -ACTOR Future monitorDataDistributor(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future monitorDataDistributor(ClusterControllerData* self) { + SingletonRecruitThrottler recruitThrottler; while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } - loop { + while (true) { bool ddExist = self->db.serverInfo->get().distributor.present(); TraceEvent(SevInfo, "CCMonitorDataDistributor", self->id) .detail("Recruiting", self->recruitDistributor.get()) @@ -2821,18 +2819,17 @@ ACTOR Future monitorDataDistributor(ClusterControllerData* self) { .detail("ExistingDD", ddExist ? self->db.serverInfo->get().distributor.get().id().toString() : ""); if (self->db.serverInfo->get().distributor.present() && !self->recruitDistributor.get()) { - choose { - when(wait(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure, - SERVER_KNOBS->DD_FAILURE_TIME))) { - const auto& distributor = self->db.serverInfo->get().distributor; - TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id()); - DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId()); - self->db.clearInterf(ProcessClass::DataDistributorClass); - } - when(wait(self->recruitDistributor.onChange())) {} + auto res = co_await race(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure, + SERVER_KNOBS->DD_FAILURE_TIME), + self->recruitDistributor.onChange()); + if (res.index() == 0) { + const auto& distributor = self->db.serverInfo->get().distributor; + TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id()); + DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId()); + self->db.clearInterf(ProcessClass::DataDistributorClass); } } else { - wait(startDataDistributor(self, recruitThrottler.newRecruitment())); + co_await startDataDistributor(self, recruitThrottler.newRecruitment()); } } } @@ -2905,26 +2902,25 @@ Future startRatekeeper(ClusterControllerData* self, double waitTime) { } } -ACTOR Future monitorRatekeeper(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future monitorRatekeeper(ClusterControllerData* self) { + SingletonRecruitThrottler recruitThrottler; while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } - loop { + while (true) { if (self->db.serverInfo->get().ratekeeper.present() && !self->recruitRatekeeper.get()) { - choose { - when(wait(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure, - SERVER_KNOBS->RATEKEEPER_FAILURE_TIME))) { - const auto& ratekeeper = self->db.serverInfo->get().ratekeeper; - TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id()); - RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId()); - self->db.clearInterf(ProcessClass::RatekeeperClass); - } - when(wait(self->recruitRatekeeper.onChange())) {} + auto res = co_await race(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure, + SERVER_KNOBS->RATEKEEPER_FAILURE_TIME), + self->recruitRatekeeper.onChange()); + if (res.index() == 0) { + const auto& ratekeeper = self->db.serverInfo->get().ratekeeper; + TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id()); + RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId()); + self->db.clearInterf(ProcessClass::RatekeeperClass); } } else { - wait(startRatekeeper(self, recruitThrottler.newRecruitment())); + co_await startRatekeeper(self, recruitThrottler.newRecruitment()); } } } @@ -2997,28 +2993,25 @@ Future startConsistencyScan(ClusterControllerData* self) { } } -ACTOR Future monitorConsistencyScan(ClusterControllerData* self) { +Future monitorConsistencyScan(ClusterControllerData* self) { while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { TraceEvent("CCMonitorConsistencyScanWaitingForRecovery", self->id).log(); - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } TraceEvent("CCMonitorConsistencyScan", self->id).log(); - loop { + while (true) { if (self->db.serverInfo->get().consistencyScan.present() && !self->recruitConsistencyScan.get()) { - state Future wfClient = - waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure, - SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME); - choose { - when(wait(wfClient)) { - TraceEvent("CCMonitorConsistencyScanDied", self->id) - .detail("CKID", self->db.serverInfo->get().consistencyScan.get().id()); - self->db.clearInterf(ProcessClass::ConsistencyScanClass); - } - when(wait(self->recruitConsistencyScan.onChange())) {} + Future wfClient = waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure, + SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME); + auto res = co_await race(wfClient, self->recruitConsistencyScan.onChange()); + if (res.index() == 0) { + TraceEvent("CCMonitorConsistencyScanDied", self->id) + .detail("CKID", self->db.serverInfo->get().consistencyScan.get().id()); + self->db.clearInterf(ProcessClass::ConsistencyScanClass); } } else { TraceEvent("CCMonitorConsistencyScanStarting", self->id).log(); - wait(startConsistencyScan(self)); + co_await startConsistencyScan(self); } } } From 44038b3d40e160ea4ce99731e367e1941727b532 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 18:41:46 -0700 Subject: [PATCH 19/39] Remove redundant coroutine race branch --- fdbserver/clustercontroller/ClusterController.actor.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index f4c14b5b8e..967c66d6ec 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -2392,7 +2392,7 @@ Future updatedChangedDatacenters(ClusterControllerData* self) { if (res.index() == 0) { changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); onChange = self->changingDcIds.onChange(); - } else if (res.index() == 1) { + } else { changeDelay = Never(); onChange = self->changingDcIds.onChange(); @@ -2437,8 +2437,6 @@ Future updatedChangedDatacenters(ClusterControllerData* self) { } } } - } else { - UNREACHABLE(); } } } From d523d509a7ef8d018e737b7afffae211e02180ce Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 15 Jul 2026 22:00:36 -0700 Subject: [PATCH 20/39] Enable additional clang-tidy correctness checks --- .clang-tidy | 6 + documentation/sphinx/source/clang-tidy.rst | 7 +- fdbclient/BackupContainerFileSystem.cpp | 3 +- fdbclient/FileBackupAgent.cpp | 9 +- fdbclient/ReadYourWrites.cpp | 4 +- fdbclient/StorageServerInterface.cpp | 2 +- fdbclient/Subspace.cpp | 2 +- fdbrpc/FlowTransport.cpp | 2 +- fdbrpc/Net2FileSystem.cpp | 7 +- fdbrpc/tests/AuthzTlsTest.cpp | 2 +- fdbserver/consistencyscan/ConsistencyScan.cpp | 5 +- fdbserver/coordinator/Coordination.cpp | 12 +- fdbserver/core/BulkLoadUtil.cpp | 2 +- fdbserver/core/ServerKnobs.cpp | 18 +-- fdbserver/core/StorageMetrics.cpp | 4 +- fdbserver/kvstore/DiskQueue.cpp | 5 +- fdbserver/kvstore/FDBExecHelper.cpp | 2 +- fdbserver/kvstore/KeyValueStoreRocksDB.cpp | 2 +- fdbserver/kvstore/KeyValueStoreSQLite.cpp | 2 +- .../kvstore/KeyValueStoreShardedRocksDB.cpp | 2 +- fdbserver/logsystem/LogSystem.cpp | 4 +- fdbserver/resolver/ConflictSet.cpp | 7 +- fdbserver/storageserver/storageserver.cpp | 3 +- fdbserver/tester/ConsistencyChecker.cpp | 5 +- fdbserver/tlog/TestTLogServer.cpp | 8 +- fdbserver/workloads/AsyncFileCorrectness.cpp | 5 +- fdbserver/workloads/AsyncFileRead.cpp | 2 +- fdbserver/workloads/AsyncFileWrite.cpp | 2 +- fdbserver/workloads/BackupCorrectness.cpp | 2 +- .../BackupCorrectnessPartitioned.cpp | 2 +- .../workloads/BackupS3BlobCorrectness.cpp | 2 +- fdbserver/workloads/DDBalance.cpp | 2 +- fdbserver/workloads/DiskDurability.cpp | 2 +- fdbserver/workloads/DiskDurabilityTest.cpp | 4 +- fdbserver/workloads/GetEstimatedRangeSize.cpp | 3 +- fdbserver/workloads/S3ClientWorkload.cpp | 4 +- fdbserver/workloads/SimpleAtomicAdd.cpp | 2 +- fdbserver/workloads/Watches.cpp | 129 ++++++++++-------- fdbserver/workloads/pubsub.cpp | 8 +- flow/Arena.cpp | 4 +- flow/FastAlloc.cpp | 15 +- flow/Knobs.cpp | 2 +- flow/MkCert.cpp | 2 +- flow/flow.cpp | 4 +- 44 files changed, 180 insertions(+), 141 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index d413c59027..ba4bfcdec5 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,6 +2,7 @@ Checks: > -*, bugprone-dangling-handle, + bugprone-implicit-widening-of-multiplication-result, bugprone-redundant-branch-condition, bugprone-shared-ptr-array-mismatch, bugprone-sizeof-container, @@ -14,9 +15,12 @@ Checks: > bugprone-suspicious-semicolon, bugprone-suspicious-string-compare, bugprone-suspicious-stringview-data-usage, + bugprone-too-small-loop-variable, bugprone-unique-ptr-array-mismatch, bugprone-use-after-move, + cppcoreguidelines-avoid-capturing-lambda-coroutines, misc-coroutine-hostile-raii, + misc-redundant-expression, modernize-use-auto, modernize-use-equals-default, modernize-use-override, @@ -31,6 +35,8 @@ Checks: > readability-duplicate-include, readability-inconsistent-ifelse-braces CheckOptions: + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view;std::span;StringRef' - key: modernize-use-auto.MinTypeNameLength value: '11' - key: readability-braces-around-statements.ShortStatementLines diff --git a/documentation/sphinx/source/clang-tidy.rst b/documentation/sphinx/source/clang-tidy.rst index 81bef1001b..46640efd87 100644 --- a/documentation/sphinx/source/clang-tidy.rst +++ b/documentation/sphinx/source/clang-tidy.rst @@ -10,11 +10,12 @@ This guide explains how to run ``clang-tidy`` locally so you can fix issues befo What clang-tidy checks ====================== -FoundationDB enables 29 checks configured in the ``.clang-tidy`` file at the repository root. The +FoundationDB enables 33 checks configured in the ``.clang-tidy`` file at the repository root. The intent is to enable more as we go forward. Here are some example rules: -* **15 Bugprone rules** -- catch potential runtime errors (e.g., ``bugprone-use-after-move``, ``bugprone-suspicious-memory-comparison``) -* **1 Misc rule** -- catch RAII objects held across coroutine suspension points (``misc-coroutine-hostile-raii``) +* **17 Bugprone rules** -- catch potential runtime errors (e.g., ``bugprone-too-small-loop-variable``, ``bugprone-implicit-widening-of-multiplication-result``) +* **1 C++ Core Guidelines rule** -- catch unsafe captures in coroutine lambdas (``cppcoreguidelines-avoid-capturing-lambda-coroutines``) +* **2 Misc rules** -- catch redundant expressions and RAII objects held across coroutine suspension points * **4 Modernize rules** -- encourage modern C++ practices (e.g., ``modernize-use-auto``, ``modernize-use-override``) * **2 Performance rules** -- avoid unnecessary copies and pointless moves (e.g., ``performance-for-range-copy``, ``performance-move-const-arg``) * **7 Readability rules** -- improve code clarity (e.g., ``readability-container-contains``, ``readability-container-size-empty``) diff --git a/fdbclient/BackupContainerFileSystem.cpp b/fdbclient/BackupContainerFileSystem.cpp index f885a6c0cf..2be8d39605 100644 --- a/fdbclient/BackupContainerFileSystem.cpp +++ b/fdbclient/BackupContainerFileSystem.cpp @@ -1619,7 +1619,8 @@ Future> BackupContainerFileSystem::listLogFiles(Version beg std::string firstPath = BackupContainerFileSystemImpl::cleanFolderString(BackupContainerFileSystemImpl::logVersionFolderString( std::max(0, - beginVersion - CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES * CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE), + beginVersion - static_cast(CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES) * + CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE), mutationLogType)); std::string lastPath = BackupContainerFileSystemImpl::cleanFolderString( BackupContainerFileSystemImpl::logVersionFolderString(targetVersion, mutationLogType)); diff --git a/fdbclient/FileBackupAgent.cpp b/fdbclient/FileBackupAgent.cpp index 7d1f6d1a3d..858a1608e8 100644 --- a/fdbclient/FileBackupAgent.cpp +++ b/fdbclient/FileBackupAgent.cpp @@ -1087,7 +1087,7 @@ PartitionedLogIteratorSimple::PartitionedLogIteratorSimple(Reference _files, std::vector _endVersions) : bc(_bc), tag(_tag), endVersions(_endVersions), files(std::move(_files)), bufferOffset(0) { - bufferCapacity = BATCH_READ_BLOCK_COUNT * BLOCK_SIZE; + bufferCapacity = static_cast(BATCH_READ_BLOCK_COUNT) * BLOCK_SIZE; buffer = std::shared_ptr(new char[bufferCapacity]()); fileOffset = 0; fileIndex = 0; @@ -3174,9 +3174,10 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { co_return; } - Version endVersion = std::max(tr->getReadVersion().get() + 1, - beginVersion + (CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES - 1) * - CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE); + Version endVersion = + std::max(tr->getReadVersion().get() + 1, + beginVersion + static_cast(CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES - 1) * + CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE); TraceEvent("FileBackupLogDispatch") .suppressFor(60) diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 334384abfd..79374ef3fc 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -2188,7 +2188,7 @@ void ReadYourWritesTransaction::atomicOp(const KeyRef& key, const ValueRef& oper } approximateSize += k.expectedSize() + v.expectedSize() + sizeof(MutationRef) + - (addWriteConflict ? sizeof(KeyRangeRef) + 2 * key.expectedSize() + 1 : 0); + (addWriteConflict ? sizeof(KeyRangeRef) + 2ULL * key.expectedSize() + 1 : 0); if (options.readYourWritesDisabled) { return tr.atomicOp(k, v, (MutationRef::Type)operationType, addWriteConflict); } @@ -2236,7 +2236,7 @@ void ReadYourWritesTransaction::set(const KeyRef& key, const ValueRef& value) { throw key_outside_legal_range(); approximateSize += key.expectedSize() + value.expectedSize() + sizeof(MutationRef) + - (addWriteConflict ? sizeof(KeyRangeRef) + 2 * key.expectedSize() + 1 : 0); + (addWriteConflict ? sizeof(KeyRangeRef) + 2ULL * key.expectedSize() + 1 : 0); if (options.readYourWritesDisabled) { return tr.set(key, value, addWriteConflict); } diff --git a/fdbclient/StorageServerInterface.cpp b/fdbclient/StorageServerInterface.cpp index 9fda4145ed..0cb5713040 100644 --- a/fdbclient/StorageServerInterface.cpp +++ b/fdbclient/StorageServerInterface.cpp @@ -228,7 +228,7 @@ static void traceKeyValuesSummary(TraceEvent& event, // convert a StringRef to Hex string static std::string hexStringRef(const StringRef& s) { std::string result; - result.reserve(s.size() * 2); + result.reserve(static_cast(s.size()) * 2); for (int i = 0; i < s.size(); i++) { result.append(format("%02x", s[i])); } diff --git a/fdbclient/Subspace.cpp b/fdbclient/Subspace.cpp index 3ec4da6cd9..d106bf7464 100644 --- a/fdbclient/Subspace.cpp +++ b/fdbclient/Subspace.cpp @@ -21,7 +21,7 @@ #include "fdbclient/Subspace.h" Subspace::Subspace(Tuple const& tuple, StringRef const& rawPrefix) { - StringRef packed = tuple.pack(); + Standalone packed = tuple.pack(); this->rawPrefix.reserve(this->rawPrefix.arena(), rawPrefix.size() + packed.size()); this->rawPrefix.append(this->rawPrefix.arena(), rawPrefix.begin(), rawPrefix.size()); diff --git a/fdbrpc/FlowTransport.cpp b/fdbrpc/FlowTransport.cpp index dd4e7139fc..6f72e816ce 100644 --- a/fdbrpc/FlowTransport.cpp +++ b/fdbrpc/FlowTransport.cpp @@ -417,7 +417,7 @@ struct ConnectionLogWriter : IThreadPoolReceiver { throw io_error(); } - if (file.tellg() > 100 * 1024 * 1024 /* 100 MB */) { + if (file.tellg() > 100LL * 1024 * 1024 /* 100 MB */) { file.close(); fileName = newFileName(); TraceEvent("RollConnectionLog").detail("FileName", fileName); diff --git a/fdbrpc/Net2FileSystem.cpp b/fdbrpc/Net2FileSystem.cpp index 38d2ee7901..f915a5a82a 100644 --- a/fdbrpc/Net2FileSystem.cpp +++ b/fdbrpc/Net2FileSystem.cpp @@ -57,10 +57,11 @@ Future runAsyncFileKAIOTestOps(Reference f, int numIterations, std::vector> futures; for (int numOps = deterministicRandom()->randomInt(1, 20); numOps > 0; --numOps) { if (deterministicRandom()->coinflip()) { - futures.push_back( - success(f->read(buf, 4096, deterministicRandom()->randomInt(0, fileSize) / 4096 * 4096))); + futures.push_back(success(f->read( + buf, 4096, static_cast(deterministicRandom()->randomInt(0, fileSize)) / 4096 * 4096))); } else { - futures.push_back(f->write(buf, 4096, deterministicRandom()->randomInt(0, fileSize) / 4096 * 4096)); + futures.push_back(f->write( + buf, 4096, static_cast(deterministicRandom()->randomInt(0, fileSize)) / 4096 * 4096)); } } for (int fIndex = 0; fIndex < futures.size(); ++fIndex) { diff --git a/fdbrpc/tests/AuthzTlsTest.cpp b/fdbrpc/tests/AuthzTlsTest.cpp index 915f2deb19..1da63d411c 100644 --- a/fdbrpc/tests/AuthzTlsTest.cpp +++ b/fdbrpc/tests/AuthzTlsTest.cpp @@ -352,7 +352,7 @@ int runHost(TLSCreds creds, int addrPipe, int completionPipe, Result expect) { Result getExpectedResult(ChainLength serverChainLen, ChainLength clientChainLen) { auto expect = Result::ERROR; if (serverChainLen > 0) { - if (clientChainLen == NO_TLS || clientChainLen < 0) { + if (clientChainLen < 0) { expect = Result::TIMEOUT; } else if (clientChainLen > 0) { expect = Result::TRUSTED; diff --git a/fdbserver/consistencyscan/ConsistencyScan.cpp b/fdbserver/consistencyscan/ConsistencyScan.cpp index 2d88c836d8..c33c2324ce 100644 --- a/fdbserver/consistencyscan/ConsistencyScan.cpp +++ b/fdbserver/consistencyscan/ConsistencyScan.cpp @@ -1687,8 +1687,9 @@ Future checkDataConsistency(Database cx, if (firstClient && performQuiescentChecks && ((configuration.usableRegions == 1 && (sourceStorageServers.size() > expectedReplicas || sourceStorageServers.size() < configuration.storageTeamSize)) || - sourceStorageServers.size() < configuration.usableRegions * configuration.storageTeamSize || - sourceStorageServers.size() > configuration.usableRegions * expectedReplicas)) { + sourceStorageServers.size() < + static_cast(configuration.usableRegions) * configuration.storageTeamSize || + sourceStorageServers.size() > static_cast(configuration.usableRegions) * expectedReplicas)) { TraceEvent("ConsistencyCheck_InvalidTeamSize") .detail("ShardBegin", printable(range.begin)) .detail("ShardEnd", printable(range.end)) diff --git a/fdbserver/coordinator/Coordination.cpp b/fdbserver/coordinator/Coordination.cpp index 5735aad82b..5cb7706f8c 100644 --- a/fdbserver/coordinator/Coordination.cpp +++ b/fdbserver/coordinator/Coordination.cpp @@ -713,7 +713,7 @@ class LeaderServer { info.forward = forward.get().serializedInfo; req.reply.send(CachedSerialization(info)); } else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.clusterKey).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") @@ -736,7 +736,7 @@ class LeaderServer { if (forward.present()) { req.reply.send(forward.get()); } else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.key).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") .detail("RequestType", "ElectionResultRequest") @@ -759,7 +759,7 @@ class LeaderServer { if (forward.present()) req.reply.send(forward.get()); else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.key).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") .detail("RequestType", "GetLeaderRequest") @@ -781,7 +781,7 @@ class LeaderServer { if (forward.present()) req.reply.send(forward.get()); else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.key).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") .detail("RequestType", "CandidacyRequest") @@ -802,7 +802,7 @@ class LeaderServer { if (forward.present()) req.reply.send(LeaderHeartbeatReply{ false }); else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.key).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") .detail("RequestType", "LeaderHeartbeatRequest") @@ -823,7 +823,7 @@ class LeaderServer { if (forward.present()) { req.reply.send(Void()); } else { - StringRef clusterName = ccr->getConnectionString().clusterKeyName(); + Key clusterName = ccr->getConnectionString().clusterKeyName(); if (!SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT && getClusterDescriptor(req.key).compare(clusterName)) { TraceEvent(SevWarn, "CCRMismatch") .detail("RequestType", "ForwardRequest") diff --git a/fdbserver/core/BulkLoadUtil.cpp b/fdbserver/core/BulkLoadUtil.cpp index 95bccef1a3..6c950fad40 100644 --- a/fdbserver/core/BulkLoadUtil.cpp +++ b/fdbserver/core/BulkLoadUtil.cpp @@ -575,7 +575,7 @@ Future getBulkLoadJobFileManifestEntryFromJobManifestFile( throw file_too_large(); } - int64_t chunkSize = 64 * 1024; // 64KB chunks + int64_t chunkSize = 64LL * 1024; // 64KB chunks std::string buffer; int64_t offset = 0; std::string leftover; diff --git a/fdbserver/core/ServerKnobs.cpp b/fdbserver/core/ServerKnobs.cpp index 7494426039..ffe1ca71b4 100644 --- a/fdbserver/core/ServerKnobs.cpp +++ b/fdbserver/core/ServerKnobs.cpp @@ -358,7 +358,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi init( MAX_SHARD_BYTES, 500000000 ); init( KEY_SERVER_SHARD_BYTES, 500000000 ); - init( SHARD_MAX_READ_OPS_PER_KSEC, 45000 * 1000 ); + init( SHARD_MAX_READ_OPS_PER_KSEC, 45000LL * 1000 ); init( SHARD_READ_OPS_CHANGE_THRESHOLD, SHARD_MAX_READ_OPS_PER_KSEC / 4); if(randomize && buggify()) SHARD_READ_OPS_CHANGE_THRESHOLD = 2000; /* * The assumption is when the read ops reach to 45k/s the Storage Server instance will be CPU-saturated. @@ -367,7 +367,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi /* The bytesRead/byteSize radio. Will be declared as read hot when larger than this. 8.0 was chosen to avoid reporting table scan as read hot. */ - init ( SHARD_READ_HOT_BANDWIDTH_MIN_PER_KSECONDS, 1666667 * 1000); + init ( SHARD_READ_HOT_BANDWIDTH_MIN_PER_KSECONDS, 1666667LL * 1000); /* The read bandwidth of a given shard needs to be larger than this value in order to be evaluated if it's read hot. The roughly 1.67MB per second is calculated as following: - Heuristic data suggests that each storage process can do max 500K read operations per second @@ -389,7 +389,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi team indefinitely, limiting performance. */ - init( SHARD_MIN_BYTES_PER_KSEC, 100 * 1000 * 1000 ); if( buggifySmallBandwidthSplit ) SHARD_MIN_BYTES_PER_KSEC = 20*1000*1000; + init( SHARD_MIN_BYTES_PER_KSEC, 100LL * 1000 * 1000 ); if( buggifySmallBandwidthSplit ) SHARD_MIN_BYTES_PER_KSEC = 20LL*1000*1000; /* 100*1KB/sec * 1000sec/ksec Shards with more than this bandwidth will not be merged. Obviously this needs to be significantly less than SHARD_MAX_BYTES_PER_KSEC, else we will repeatedly merge and split. @@ -403,7 +403,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi BYTES_WRITTEN_UNITS_PER_SAMPLE. If this number is too low, the storage server needs to spend more memory and time on sampling. */ - init( SHARD_SPLIT_BYTES_PER_KSEC, 250 * 1000 * 1000 ); if( buggifySmallBandwidthSplit ) SHARD_SPLIT_BYTES_PER_KSEC = 50 * 1000 * 1000; + init( SHARD_SPLIT_BYTES_PER_KSEC, 250LL * 1000 * 1000 ); if( buggifySmallBandwidthSplit ) SHARD_SPLIT_BYTES_PER_KSEC = 50LL * 1000 * 1000; /* 250*1KB/sec * 1000sec/ksec When splitting a shard, it is split into pieces with less than this bandwidth. Obviously this should be less than half of SHARD_MAX_BYTES_PER_KSEC. @@ -529,7 +529,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi init( DD_BULKDUMP_PARALLELISM, 50 ); if( randomize && buggify() ) DD_BULKDUMP_PARALLELISM = deterministicRandom()->randomInt(1, 5); init( DD_BULKDUMP_BUILD_JOB_MANIFEST_BATCH_SIZE, 10000 ); if( isSimulated ) DD_BULKDUMP_BUILD_JOB_MANIFEST_BATCH_SIZE = deterministicRandom()->randomInt(1, 100); init( SS_SERVE_BULKDUMP_PARALLELISM, 1 ); // TODO(BulkDump): Do not set to 1 after SS can resolve the file folder conflict - init( SS_BULKDUMP_BATCH_BYTES, 100*1024*1024 ); if( isSimulated ) SS_BULKDUMP_BATCH_BYTES = deterministicRandom()->randomInt(1000, 10000); + init( SS_BULKDUMP_BATCH_BYTES, 100LL*1024*1024 ); if( isSimulated ) SS_BULKDUMP_BATCH_BYTES = deterministicRandom()->randomInt(1000, 10000); init( SS_BULKDUMP_BATCH_COUNT_MAX_PER_REQUEST, 10 ); if( isSimulated ) SS_BULKDUMP_BATCH_COUNT_MAX_PER_REQUEST = deterministicRandom()->randomInt(1, 10); // TeamRemover @@ -629,7 +629,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi init( ROCKSDB_PERIODIC_COMPACTION_SECONDS, 0 ); if( isSimulated ) ROCKSDB_PERIODIC_COMPACTION_SECONDS = deterministicRandom()->randomInt(5*60, 24*60*60); init( ROCKSDB_TTL_COMPACTION_SECONDS, 2160000 ); if( isSimulated ) ROCKSDB_TTL_COMPACTION_SECONDS = deterministicRandom()->randomInt(5*60, 24*60*60); int64_t maxCompactionBytes = 160LL * 64 * 1024 * 1024; - init( ROCKSDB_MAX_COMPACTION_BYTES, 0 ); /* default = 25*64MB */ if( randomize && buggify() ) ROCKSDB_MAX_COMPACTION_BYTES = deterministicRandom()->randomInt64(5*64*1024*1024, maxCompactionBytes); + init( ROCKSDB_MAX_COMPACTION_BYTES, 0 ); /* default = 25*64MB */ if( randomize && buggify() ) ROCKSDB_MAX_COMPACTION_BYTES = deterministicRandom()->randomInt64(5LL*64*1024*1024, maxCompactionBytes); init( ROCKSDB_PREFIX_LEN, 11 ); if( randomize && buggify() ) ROCKSDB_PREFIX_LEN = deterministicRandom()->randomInt(1, 20); init( ROCKSDB_MEMTABLE_PREFIX_BLOOM_SIZE_RATIO, 0.1 ); init( ROCKSDB_BLOOM_BITS_PER_KEY, 10 ); @@ -920,7 +920,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi // Backup Worker init( BACKUP_TIMEOUT, 0.4 ); init( BACKUP_FILE_BLOCK_BYTES, 1024 * 1024 ); - init( BACKUP_WORKER_LOCK_BYTES, 3e9 ); if(randomize && buggify()) BACKUP_WORKER_LOCK_BYTES = deterministicRandom()->randomInt(2048, 4096) * 4096; + init( BACKUP_WORKER_LOCK_BYTES, 3e9 ); if(randomize && buggify()) BACKUP_WORKER_LOCK_BYTES = deterministicRandom()->randomInt(2048, 4096) * 4096LL; init( BACKUP_UPLOAD_DELAY, 10.0 ); if(randomize && buggify()) BACKUP_UPLOAD_DELAY = deterministicRandom()->random01() * 60; //Cluster Controller @@ -1068,7 +1068,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi init( CONSISTENCY_CHECK_BACKWARD_READ, false ); if (isSimulated) CONSISTENCY_CHECK_BACKWARD_READ = deterministicRandom()->coinflip(); init (STORAGE_FETCH_KEYS_DELAY, 0.0 ); if ( randomize && buggify() ) { STORAGE_FETCH_KEYS_DELAY = deterministicRandom()->random01() * 5.0; } init (STORAGE_FETCH_KEYS_USE_COMMIT_BUDGET, false ); if (isSimulated) STORAGE_FETCH_KEYS_USE_COMMIT_BUDGET = deterministicRandom()->coinflip(); - init (STORAGE_FETCH_KEYS_RATE_LIMIT, 0 ); if (isSimulated && buggify()) STORAGE_FETCH_KEYS_RATE_LIMIT = 100 * 1024 * deterministicRandom()->randomInt(1, 10); // In MB/s + init (STORAGE_FETCH_KEYS_RATE_LIMIT, 0 ); if (isSimulated && buggify()) STORAGE_FETCH_KEYS_RATE_LIMIT = 100LL * 1024 * deterministicRandom()->randomInt(1, 10); // In MB/s init (STORAGE_ROCKSDB_LOG_CLEAN_UP_DELAY, 3600 * 2 ); if (isSimulated) STORAGE_ROCKSDB_LOG_CLEAN_UP_DELAY = 20.0; init (STORAGE_ROCKSDB_LOG_TTL, 3600 * 24 * 15 ); if (isSimulated) STORAGE_ROCKSDB_LOG_TTL = 3600.0; @@ -1321,7 +1321,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi // Timekeeper init( TIME_KEEPER_DELAY, 10 ); - init( TIME_KEEPER_MAX_ENTRIES, 3600 * 24 * 30 * 6 ); if( randomize && buggify() ) { TIME_KEEPER_MAX_ENTRIES = 2; } + init( TIME_KEEPER_MAX_ENTRIES, 3600LL * 24 * 30 * 6 ); if( randomize && buggify() ) { TIME_KEEPER_MAX_ENTRIES = 2; } init( REDWOOD_DEFAULT_PAGE_SIZE, 8192 ); init( REDWOOD_DEFAULT_EXTENT_SIZE, 32 * 1024 * 1024 ); diff --git a/fdbserver/core/StorageMetrics.cpp b/fdbserver/core/StorageMetrics.cpp index 374fe461c9..1b182354e0 100644 --- a/fdbserver/core/StorageMetrics.cpp +++ b/fdbserver/core/StorageMetrics.cpp @@ -357,8 +357,8 @@ void StorageServerMetrics::splitMetrics(SplitMetricsRequest req) const { //TraceEvent("SplitMetrics").detail("Begin", req.keys.begin).detail("End", req.keys.end).detail("Remaining", remaining.bytes).detail("Used", used.bytes).detail("MinSplitBytes", minSplitBytes); while (true) { - if (remaining.bytes < 2 * minSplitBytes && (!SERVER_KNOBS->ENABLE_WRITE_BASED_SHARD_SPLIT || - remaining.bytesWrittenPerKSecond < minSplitWriteTraffic)) + if (remaining.bytes < 2LL * minSplitBytes && (!SERVER_KNOBS->ENABLE_WRITE_BASED_SHARD_SPLIT || + remaining.bytesWrittenPerKSecond < minSplitWriteTraffic)) break; KeyRef key = req.keys.end; bool hasUsed = used.bytes != 0 || used.bytesWrittenPerKSecond != 0 || used.iosPerKSecond != 0; diff --git a/fdbserver/kvstore/DiskQueue.cpp b/fdbserver/kvstore/DiskQueue.cpp index 10b56f8d13..2a8341dd9b 100644 --- a/fdbserver/kvstore/DiskQueue.cpp +++ b/fdbserver/kvstore/DiskQueue.cpp @@ -172,9 +172,10 @@ public: readingPage(-1), writingPos(-1), fileExtensionBytes(SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES), fileShrinkBytes(SERVER_KNOBS->DISK_QUEUE_FILE_SHRINK_BYTES) { if (buggify()) - fileExtensionBytes = _PAGE_SIZE * deterministicRandom()->randomSkewedUInt32(1, 10 << 10); + fileExtensionBytes = + static_cast(_PAGE_SIZE) * deterministicRandom()->randomSkewedUInt32(1, 10 << 10); if (buggify()) - fileShrinkBytes = _PAGE_SIZE * deterministicRandom()->randomSkewedUInt32(1, 10 << 10); + fileShrinkBytes = static_cast(_PAGE_SIZE) * deterministicRandom()->randomSkewedUInt32(1, 10 << 10); files[0].dbgFilename = filename(0); files[1].dbgFilename = filename(1); // We issue reads into firstPages, so it needs to be 4k aligned. diff --git a/fdbserver/kvstore/FDBExecHelper.cpp b/fdbserver/kvstore/FDBExecHelper.cpp index 39e2d209a3..dd4b00ab83 100644 --- a/fdbserver/kvstore/FDBExecHelper.cpp +++ b/fdbserver/kvstore/FDBExecHelper.cpp @@ -63,7 +63,7 @@ void ExecCmdValueString::setCmdValueString(StringRef pCmdValueString) { } StringRef ExecCmdValueString::getCmdValueString() const { - return cmdValueString.toString(); + return cmdValueString; } StringRef ExecCmdValueString::getBinaryPath() const { diff --git a/fdbserver/kvstore/KeyValueStoreRocksDB.cpp b/fdbserver/kvstore/KeyValueStoreRocksDB.cpp index f838ebdfb5..f1e1e5e884 100644 --- a/fdbserver/kvstore/KeyValueStoreRocksDB.cpp +++ b/fdbserver/kvstore/KeyValueStoreRocksDB.cpp @@ -1307,7 +1307,7 @@ struct RocksDBKeyValueStore : IKeyValueStore { rateLimiter(SERVER_KNOBS->ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC > 0 ? rocksdb::NewGenericRateLimiter( SERVER_KNOBS->ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC, // rate_bytes_per_sec - 100 * 1000, // refill_period_us + 100LL * 1000, // refill_period_us SERVER_KNOBS->ROCKSDB_WRITE_RATE_LIMITER_FAIRNESS, // fairness rocksdb::RateLimiter::Mode::kAllIo, SERVER_KNOBS->ROCKSDB_WRITE_RATE_LIMITER_AUTO_TUNE) diff --git a/fdbserver/kvstore/KeyValueStoreSQLite.cpp b/fdbserver/kvstore/KeyValueStoreSQLite.cpp index 756e69c22f..8d317a044e 100644 --- a/fdbserver/kvstore/KeyValueStoreSQLite.cpp +++ b/fdbserver/kvstore/KeyValueStoreSQLite.cpp @@ -159,7 +159,7 @@ struct PageChecksumCodec { if (g_network->isSimulated()) { // Calculate file offsets for the read/write operation space // Operation starts at a 1-based pageNumber and is of size pageLen - int64_t fileOffsetStart = (pageNumber - 1) * pageLen; + int64_t fileOffsetStart = static_cast(pageNumber - 1) * pageLen; // End refers to the offset after the operation, not the last byte. int64_t fileOffsetEnd = fileOffsetStart + pageLen; diff --git a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp index b95b636b05..80ed55e4eb 100644 --- a/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp +++ b/fdbserver/kvstore/KeyValueStoreShardedRocksDB.cpp @@ -1244,7 +1244,7 @@ public: auto rateLimiter = rocksdb::NewGenericRateLimiter(SERVER_KNOBS->SHARDED_ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC, - 100 * 1000, // refill_period_us + 100LL * 1000, // refill_period_us 10, // fairness mode, SERVER_KNOBS->ROCKSDB_WRITE_RATE_LIMITER_AUTO_TUNE); diff --git a/fdbserver/logsystem/LogSystem.cpp b/fdbserver/logsystem/LogSystem.cpp index f59c263db7..e33118bfff 100644 --- a/fdbserver/logsystem/LogSystem.cpp +++ b/fdbserver/logsystem/LogSystem.cpp @@ -363,7 +363,7 @@ UID LogSystem::getDebugID() const { void LogSystem::addPseudoLocality(int8_t locality) { ASSERT(locality < 0); pseudoLocalities.insert(locality); - for (uint16_t i = 0; i < logRouterTags; i++) { + for (int i = 0; i < logRouterTags; i++) { pseudoLocalityPopVersion[Tag(locality, i)] = 0; } } @@ -1597,7 +1597,7 @@ void getTLogLocIds(const std::vector>& tLogs, if (!it->isLocal) { continue; } - for (uint16_t i = 0; i < it->logServers.size(); i++) { + for (size_t i = 0; i < it->logServers.size(); i++) { if (it->logServers[i]->get().present()) { interfLocMap[it->logServers[i]->get().interf().id()] = location; } diff --git a/fdbserver/resolver/ConflictSet.cpp b/fdbserver/resolver/ConflictSet.cpp index faeab7848c..4e820ef96f 100644 --- a/fdbserver/resolver/ConflictSet.cpp +++ b/fdbserver/resolver/ConflictSet.cpp @@ -431,8 +431,9 @@ public: void addConflictRanges(const Finger* fingers, int rangeCount, Version version) { for (int r = rangeCount - 1; r >= 0; r--) { - const Finger& startF = fingers[r * 2]; - const Finger& endF = fingers[r * 2 + 1]; + const size_t fingerIndex = static_cast(r) * 2; + const Finger& startF = fingers[fingerIndex]; + const Finger& endF = fingers[fingerIndex + 1]; if (endF.found() == nullptr) insert(endF, endF.finger[0]->getMaxVersion(0)); @@ -1018,7 +1019,7 @@ void ConflictBatch::addConflictRanges(Version now, int ss = stringCount - (stripes - 1) * stripeSize; for (int s = stripes - 1; s >= 0; s--) { - part->find(&strings[s * stripeSize], fingers, temp, ss); + part->find(&strings[static_cast(s) * stripeSize], fingers, temp, ss); part->addConflictRanges(fingers, ss / 2, now); ss = stripeSize; } diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index dbc8cb8adb..b7be1522e1 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -3496,7 +3496,8 @@ Future getKeyValuesQ(StorageServer* data, GetKeyValuesRequest req) if (req.taskID.present() && req.taskID.get() == TaskPriority::FetchKeys) { data->counters.kvFetchServed += r.data.size(); - data->counters.kvFetchBytesServed += (totalByteSize + (8 - (int)sizeof(KeyValueRef)) * r.data.size()); + data->counters.kvFetchBytesServed += + totalByteSize + (8LL - static_cast(sizeof(KeyValueRef))) * r.data.size(); } if (totalByteSize > 0 && SERVER_KNOBS->READ_SAMPLING_ENABLED) { diff --git a/fdbserver/tester/ConsistencyChecker.cpp b/fdbserver/tester/ConsistencyChecker.cpp index d0cab73a65..ec19a1e940 100644 --- a/fdbserver/tester/ConsistencyChecker.cpp +++ b/fdbserver/tester/ConsistencyChecker.cpp @@ -544,8 +544,9 @@ std::unordered_map> makeTaskAssignment(Database cx, int batchSize = CLIENT_KNOBS->CONSISTENCY_CHECK_URGENT_BATCH_SHARD_COUNT; int startingPoint = 0; - if (shardsToCheck.size() > batchSize * testersCount) { - startingPoint = deterministicRandom()->randomInt(0, shardsToCheck.size() - batchSize * testersCount); + const size_t batchShardCount = static_cast(batchSize) * testersCount; + if (shardsToCheck.size() > batchShardCount) { + startingPoint = deterministicRandom()->randomInt(0, shardsToCheck.size() - batchShardCount); // We randomly pick a set of successive shards: // (1) We want to retry for different shards to avoid repeated failure on the same shards // (2) We want to check successive shards to avoid inefficiency incurred by fragments diff --git a/fdbserver/tlog/TestTLogServer.cpp b/fdbserver/tlog/TestTLogServer.cpp index 00ed45f1ca..a4abfb46a2 100644 --- a/fdbserver/tlog/TestTLogServer.cpp +++ b/fdbserver/tlog/TestTLogServer.cpp @@ -247,7 +247,7 @@ Future TLogTestContext::sendPushMessages(TLogTestContext* pTLogTestContext TraceEvent("TestTLogServerEnterPush", pTLogTestContext->workerID); - for (uint16_t logID = 0; logID < pTLogTestContext->numLogServers; ++logID) { + for (uint32_t logID = 0; logID < pTLogTestContext->numLogServers; ++logID) { Reference pTLogContext = pTLogTestContext->pTLogContextList[logID]; bool tLogReady = co_await pTLogContext->TLogStarted.getFuture(); ASSERT_EQ(tLogReady, true); @@ -393,7 +393,7 @@ Future buildTLogSet(Reference pTLogTestContext) { tLogSet.isLocal = true; tLogSet.tLogVersion = TLogVersion::V6; tLogSet.tLogReplicationFactor = 1; - for (uint16_t processID = 0; processID < pTLogTestContext->numLogServers; ++processID) { + for (uint32_t processID = 0; processID < pTLogTestContext->numLogServers; ++processID) { Reference pTLogContext = pTLogTestContext->pTLogContextList[processID]; bool isCreated = co_await pTLogContext->TLogCreated.getFuture(); ASSERT_EQ(isCreated, true); @@ -401,7 +401,7 @@ Future buildTLogSet(Reference pTLogTestContext) { tLogSet.tLogs.push_back(OptionalInterface(pTLogContext->TestTLogInterface)); } pTLogTestContext->dbInfo.logSystemConfig.tLogs.push_back(tLogSet); - for (uint16_t processID = 0; processID < pTLogTestContext->numLogServers; ++processID) { + for (uint32_t processID = 0; processID < pTLogTestContext->numLogServers; ++processID) { Reference pTLogContext = pTLogTestContext->pTLogContextList[processID]; // start transactions pTLogContext->TLogStarted.send(true); @@ -420,7 +420,7 @@ Future startTestsTLogRecoveryActors(TestTLogOptions params) { FlowTransport::createInstance(false, 1, WLTOKEN_RESERVED_COUNT); - uint16_t tLogIdx = 0; + uint32_t tLogIdx = 0; TraceEvent("TestTLogServerEnterRecoveryTest"); diff --git a/fdbserver/workloads/AsyncFileCorrectness.cpp b/fdbserver/workloads/AsyncFileCorrectness.cpp index 7265116e3f..c0ecdc6c49 100644 --- a/fdbserver/workloads/AsyncFileCorrectness.cpp +++ b/fdbserver/workloads/AsyncFileCorrectness.cpp @@ -295,8 +295,9 @@ struct AsyncFileCorrectnessWorkload : public AsyncFileWorkload { do { // Generate random length and offset if (unbufferedIO) { - info.length = - deterministicRandom()->randomInt(1, maxOperationSize / _PAGE_SIZE + 1) * _PAGE_SIZE; + info.length = static_cast( + deterministicRandom()->randomInt(1, maxOperationSize / _PAGE_SIZE + 1)) * + _PAGE_SIZE; info.offset = (int64_t)(deterministicRandom()->random01() * maxOffset / _PAGE_SIZE) * _PAGE_SIZE; } else { diff --git a/fdbserver/workloads/AsyncFileRead.cpp b/fdbserver/workloads/AsyncFileRead.cpp index c036319c1b..3aa18d62cc 100644 --- a/fdbserver/workloads/AsyncFileRead.cpp +++ b/fdbserver/workloads/AsyncFileRead.cpp @@ -307,7 +307,7 @@ struct AsyncFileReadWorkload : public AsyncFileWorkload { } co_await waitForAll(self->readFutures); - self->bytesRead += self->readSize * self->numParallelReads; + self->bytesRead += static_cast(self->readSize) * self->numParallelReads; self->readFutures.clear(); diff --git a/fdbserver/workloads/AsyncFileWrite.cpp b/fdbserver/workloads/AsyncFileWrite.cpp index 54e2046c72..1ef143a666 100644 --- a/fdbserver/workloads/AsyncFileWrite.cpp +++ b/fdbserver/workloads/AsyncFileWrite.cpp @@ -136,7 +136,7 @@ struct AsyncFileWriteWorkload : public AsyncFileWorkload { self->writeFutures.clear(); - self->bytesWritten += self->writeSize * self->numParallelWrites; + self->bytesWritten += static_cast(self->writeSize) * self->numParallelWrites; } } diff --git a/fdbserver/workloads/BackupCorrectness.cpp b/fdbserver/workloads/BackupCorrectness.cpp index 165f77192e..e0273612ee 100644 --- a/fdbserver/workloads/BackupCorrectness.cpp +++ b/fdbserver/workloads/BackupCorrectness.cpp @@ -116,7 +116,7 @@ struct BackupAndRestoreCorrectnessWorkload : TestWorkload { } else { // Add backup ranges std::set rangeEndpoints; - while (rangeEndpoints.size() < backupRangesCount * 2) { + while (rangeEndpoints.size() < static_cast(backupRangesCount) * 2) { rangeEndpoints.insert(deterministicRandom()->randomAlphaNumeric( deterministicRandom()->randomInt(1, backupRangeLengthMax + 1))); } diff --git a/fdbserver/workloads/BackupCorrectnessPartitioned.cpp b/fdbserver/workloads/BackupCorrectnessPartitioned.cpp index 06e945079f..33321a98f5 100644 --- a/fdbserver/workloads/BackupCorrectnessPartitioned.cpp +++ b/fdbserver/workloads/BackupCorrectnessPartitioned.cpp @@ -114,7 +114,7 @@ struct BackupAndRestorePartitionedCorrectnessWorkload : TestWorkload { } else { // Add backup ranges std::set rangeEndpoints; - while (rangeEndpoints.size() < backupRangesCount * 2) { + while (rangeEndpoints.size() < static_cast(backupRangesCount) * 2) { rangeEndpoints.insert(deterministicRandom()->randomAlphaNumeric( deterministicRandom()->randomInt(1, backupRangeLengthMax + 1))); } diff --git a/fdbserver/workloads/BackupS3BlobCorrectness.cpp b/fdbserver/workloads/BackupS3BlobCorrectness.cpp index 5cea756790..32bd5083db 100644 --- a/fdbserver/workloads/BackupS3BlobCorrectness.cpp +++ b/fdbserver/workloads/BackupS3BlobCorrectness.cpp @@ -221,7 +221,7 @@ struct BackupS3BlobCorrectnessWorkload : TestWorkload { } else { // Add backup ranges std::set rangeEndpoints; - while (rangeEndpoints.size() < backupRangesCount * 2) { + while (rangeEndpoints.size() < static_cast(backupRangesCount) * 2) { rangeEndpoints.insert(deterministicRandom()->randomAlphaNumeric( deterministicRandom()->randomInt(1, backupRangeLengthMax + 1))); } diff --git a/fdbserver/workloads/DDBalance.cpp b/fdbserver/workloads/DDBalance.cpp index fa59d7fbf4..7da58faf05 100644 --- a/fdbserver/workloads/DDBalance.cpp +++ b/fdbserver/workloads/DDBalance.cpp @@ -200,7 +200,7 @@ struct DDBalanceWorkload : TestWorkload { tr = Transaction(); if (self->shouldRecord(clientBegin)) { - self->operations += 3 * moves; + self->operations += 3LL * moves; double latency = now() - tstart; self->latencies.addSample(latency); } diff --git a/fdbserver/workloads/DiskDurability.cpp b/fdbserver/workloads/DiskDurability.cpp index 0384622de0..4b4ea612d9 100644 --- a/fdbserver/workloads/DiskDurability.cpp +++ b/fdbserver/workloads/DiskDurability.cpp @@ -85,7 +85,7 @@ struct DiskDurabilityWorkload : public AsyncFileWorkload { explicit DiskDurabilityWorkload(WorkloadContext const& wcx) : AsyncFileWorkload(wcx) { writers = getOption(options, "writers"_sr, 1); filePages = getOption(options, "filePages"_sr, 1000000); - fileSize = filePages * _PAGE_SIZE; + fileSize = static_cast(filePages) * _PAGE_SIZE; unbufferedIO = true; uncachedIO = true; fillRandom = false; diff --git a/fdbserver/workloads/DiskDurabilityTest.cpp b/fdbserver/workloads/DiskDurabilityTest.cpp index 3d93c438ed..e5d79d4c88 100644 --- a/fdbserver/workloads/DiskDurabilityTest.cpp +++ b/fdbserver/workloads/DiskDurabilityTest.cpp @@ -78,7 +78,7 @@ struct DiskDurabilityTest : TestWorkload { IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNBUFFERED | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_LOCK, 0600); - std::vector pagedata(4096 * 128); + std::vector pagedata(size_t{ 4096 } * 128); uint8_t* page = (uint8_t*)((intptr_t(&pagedata[0]) | intptr_t(4095)) + 1); int64_t size = co_await file->size(); @@ -162,7 +162,7 @@ struct DiskDurabilityTest : TestWorkload { std::vector> fresults; for (int i = 0; i < targetPages.size(); i++) { - uint8_t* p = page + 4096 * i; + uint8_t* p = page + size_t{ 4096 } * i; encodePage(p, targetValues[i]); fresults.push_back(file->write(p, 4096, targetPages[i] * 4096)); } diff --git a/fdbserver/workloads/GetEstimatedRangeSize.cpp b/fdbserver/workloads/GetEstimatedRangeSize.cpp index 41d4e1d463..b06e53e334 100644 --- a/fdbserver/workloads/GetEstimatedRangeSize.cpp +++ b/fdbserver/workloads/GetEstimatedRangeSize.cpp @@ -86,7 +86,8 @@ struct GetEstimatedRangeSizeWorkload : TestWorkload { int nodeSize = key(0).size() + value(0).size(); // We use a wide range to avoid flakiness because the underlying function // is making an estimation. - return size > nodeCount * nodeSize / 2 && size < nodeCount * nodeSize * 5; + return size > static_cast(nodeCount) * nodeSize / 2 && + size < static_cast(nodeCount) * nodeSize * 5; } Future getSize(Database cx) { diff --git a/fdbserver/workloads/S3ClientWorkload.cpp b/fdbserver/workloads/S3ClientWorkload.cpp index b8e2dc3bdc..58878891f6 100644 --- a/fdbserver/workloads/S3ClientWorkload.cpp +++ b/fdbserver/workloads/S3ClientWorkload.cpp @@ -260,8 +260,8 @@ private: // Compare the contents of the original and downloaded files // Paths are now inside uniqueRunDir - std::string originalContent = readFileBytes(credentials, 1024 * 1024); // 1MB max size - std::string downloadedContent = readFileBytes(download, 1024 * 1024); // 1MB max size + std::string originalContent = readFileBytes(credentials, size_t{ 1024 } * 1024); // 1MB max size + std::string downloadedContent = readFileBytes(download, size_t{ 1024 } * 1024); // 1MB max size if (originalContent != downloadedContent) { TraceEvent(SevError, "S3ClientWorkloadContentMismatch") .detailf("OriginalSize", "%zu", originalContent.size()) diff --git a/fdbserver/workloads/SimpleAtomicAdd.cpp b/fdbserver/workloads/SimpleAtomicAdd.cpp index b590bceed5..98561d665d 100644 --- a/fdbserver/workloads/SimpleAtomicAdd.cpp +++ b/fdbserver/workloads/SimpleAtomicAdd.cpp @@ -108,7 +108,7 @@ struct SimpleAtomicAddWorkload : TestWorkload { Future _check(Database cx) { ReadYourWritesTransaction tr(cx); - uint64_t expectedValue = addValue * iterations; + uint64_t expectedValue = static_cast(addValue) * iterations; if (initialize) { expectedValue += initialValue; } diff --git a/fdbserver/workloads/Watches.cpp b/fdbserver/workloads/Watches.cpp index 7c1dca9d03..63825a9f73 100644 --- a/fdbserver/workloads/Watches.cpp +++ b/fdbserver/workloads/Watches.cpp @@ -106,21 +106,22 @@ struct WatchesWorkload : TestWorkload { return result; } + static Future watcherInitTransaction(Transaction* tr, Key watchKey, int* extraLoc, int extraNodes) { + for (int i = 0; i < 1000 && *extraLoc + i < extraNodes; i++) { + Key extraKey = KeyRef(watchKey.toString() + format("%d", *extraLoc + i)); + Value extraValue = ValueRef(std::string(100, '.')); + tr->set(extraKey, extraValue); + } + co_await tr->commit(); + *extraLoc += 1000; + CODE_PROBE(true, "Watches workload initial setup"); + } + Future watcherInit(Database cx, Key watchKey, Key setKey, int extraNodes) { int extraLoc = 0; while (extraLoc < extraNodes) { - co_await cx.run([&](Transaction* tr) -> Future { - for (int i = 0; i < 1000 && extraLoc + i < extraNodes; i++) { - Key extraKey = KeyRef(watchKey.toString() + format("%d", extraLoc + i)); - Value extraValue = ValueRef(std::string(100, '.')); - tr->set(extraKey, extraValue); - // TraceEvent("WatcherInitialSetupExtra").detail("Key", extraKey).detail("Value", extraValue); - } - co_await tr->commit(); - extraLoc += 1000; - CODE_PROBE(true, "Watches workload initial setup"); - // TraceEvent("WatcherInitialSetup").detail("Watch", watchKey).detail("Ver", tr->getCommittedVersion()); - }); + co_await cx.run( + [&](Transaction* tr) { return watcherInitTransaction(tr, watchKey, &extraLoc, extraNodes); }); } } @@ -178,6 +179,63 @@ struct WatchesWorkload : TestWorkload { } } + static Future setWatchValue(Transaction* tr, + Key startKey, + Value assignedValue, + bool isValue, + Optional* startValue, + Optional* expectedValue, + bool* firstAttempt) { + co_await tr->getReadVersion(); + Optional observedStartValue = co_await tr->get(startKey); + if (*firstAttempt) { + *startValue = observedStartValue; + *firstAttempt = false; + } + *expectedValue = Optional(); + if (startValue->present()) { + if (isValue) + *expectedValue = assignedValue; + } else { + *expectedValue = assignedValue; + } + + if (expectedValue->present()) + tr->set(startKey, expectedValue->get()); + else + tr->clear(startKey); + + co_await tr->commit(); + CODE_PROBE(expectedValue->present(), "watches workload set a key"); + CODE_PROBE(!expectedValue->present(), "watches workload clear a key"); + } + + static Future waitForWatchValue(Transaction* tr, + Key endKey, + Optional startValue, + Optional expectedValue, + bool* firstAttempt, + bool* finished) { + Optional endValue = co_await tr->get(endKey); + if (endValue == expectedValue) { + *finished = true; + co_return; + } + if (!*firstAttempt || endValue != startValue) { + TraceEvent(SevError, "WatcherError") + .detail("FirstAttempt", *firstAttempt) + .detail("StartValue", printable(startValue)) + .detail("EndValue", printable(endValue)) + .detail("ExpectedValue", printable(expectedValue)) + .detail("EndVersion", tr->getReadVersion().get()); + } + Future watchFuture = tr->watch(makeReference(endKey, startValue)); + co_await tr->commit(); + co_await watchFuture; + CODE_PROBE(true, "watcher workload watch fired"); + *firstAttempt = false; + } + Future watchesWorker(Database cx, WatchesWorkload* self) { Key startKey = self->keyForIndex(self->nodeOrder[0]); Key endKey = self->keyForIndex(self->nodeOrder[self->nodes]); @@ -189,55 +247,16 @@ struct WatchesWorkload : TestWorkload { bool isValue = deterministicRandom()->random01() > 0.5; Value assignedValue = Value(deterministicRandom()->randomUniqueID().toString()); bool firstAttempt = true; - co_await cx.run([&](Transaction* tr) -> Future { - co_await tr->getReadVersion(); - Optional _startValue = co_await tr->get(startKey); - if (firstAttempt) { - startValue = _startValue; - firstAttempt = false; - } - expectedValue = Optional(); - if (startValue.present()) { - if (isValue) - expectedValue = assignedValue; - } else { - expectedValue = assignedValue; - } - - if (expectedValue.present()) - tr->set(startKey, expectedValue.get()); - else - tr->clear(startKey); - - co_await tr->commit(); - CODE_PROBE(expectedValue.present(), "watches workload set a key"); - CODE_PROBE(!expectedValue.present(), "watches workload clear a key"); - co_return; + co_await cx.run([&](Transaction* tr) { + return setWatchValue(tr, startKey, assignedValue, isValue, &startValue, &expectedValue, &firstAttempt); }); chainStartTime = now(); firstAttempt = true; bool finished = false; while (!finished) { - co_await cx.run([&](Transaction* tr2) -> Future { - Optional endValue = co_await tr2->get(endKey); - if (endValue == expectedValue) { - finished = true; - co_return; - } - if (!firstAttempt || endValue != startValue) { - TraceEvent(SevError, "WatcherError") - .detail("FirstAttempt", firstAttempt) - .detail("StartValue", printable(startValue)) - .detail("EndValue", printable(endValue)) - .detail("ExpectedValue", printable(expectedValue)) - .detail("EndVersion", tr2->getReadVersion().get()); - } - Future watchFuture = tr2->watch(makeReference(endKey, startValue)); - co_await tr2->commit(); - co_await watchFuture; - CODE_PROBE(true, "watcher workload watch fired"); - firstAttempt = false; + co_await cx.run([&](Transaction* tr) { + return waitForWatchValue(tr, endKey, startValue, expectedValue, &firstAttempt, &finished); }); } self->cycleLatencies.addSample(now() - chainStartTime); diff --git a/fdbserver/workloads/pubsub.cpp b/fdbserver/workloads/pubsub.cpp index c813a5bed4..a21da7382f 100644 --- a/fdbserver/workloads/pubsub.cpp +++ b/fdbserver/workloads/pubsub.cpp @@ -212,7 +212,7 @@ Future PubSub::createSubscription(uint64_t feed, uint64_t inbox) { // the highest-numbered inbox that we've cleared from the watchers list and // make sure that further requests start after this inbox. Future updateFeedWatchers(Transaction* tr, uint64_t feed) { - StringRef watcherPrefix = keyForFeedWatcherPrefix(feed); + Key watcherPrefix = keyForFeedWatcherPrefix(feed); uint64_t highestInbox{ 0 }; bool first = true; while (true) { @@ -333,7 +333,7 @@ Future singlePassInboxCacheUpdate(Database cx, uint64_t inbox, int swath) { if (staleFeeds.empty()) // If there are no stale feeds, return. co_return 0; - StringRef stalePrefix = keyForInboxStalePrefix(inbox); + Key stalePrefix = keyForInboxStalePrefix(inbox); for (int idx = 0; idx < staleFeeds.size(); idx++) { StringRef feedStr = staleFeeds[idx].key.removePrefix(stalePrefix); // printf(" --> clearing stale entry: %s\n", feedStr.toString().c_str()); @@ -388,7 +388,7 @@ Future getFeedLatestAtOrAfter(Transaction* tr, Feed feed, MessageId p if (lastMessageRange.empty()) co_return uint64_t(0); KeyValueRef m = lastMessageRange[0]; - StringRef prefix = keyForFeedMessagePrefix(feed); + Key prefix = keyForFeedMessagePrefix(feed); StringRef mIdStr = m.key.removePrefix(prefix); co_return valueToUInt64(mIdStr); } @@ -409,7 +409,7 @@ Future> _listInboxMessages(Database cx, uint64_t inbox, int Future> _listInboxMessages(Database cx, uint64_t inbox, int count, uint64_t cursor) { TraceEvent("PubSubListInbox").detail("Inbox", inbox).detail("Count", count).detail("Cursor", cursor); co_await updateInboxCache(cx, inbox); - StringRef perIdPrefix = keyForInboxCacheByIDPrefix(inbox); + Key perIdPrefix = keyForInboxCacheByIDPrefix(inbox); while (true) { Transaction tr(cx); std::vector messages; diff --git a/flow/Arena.cpp b/flow/Arena.cpp index 02134701d1..ed6170c17a 100644 --- a/flow/Arena.cpp +++ b/flow/Arena.cpp @@ -146,7 +146,7 @@ std::string StringRef::toHexString(int limit) const { } rv = substr(0, limit).toHexString() + format("...[%d]", length); } else { - rv.reserve(length * 7); + rv.reserve(static_cast(length) * 7); for (int i = 0; i < length; i++) { uint8_t b = (*this)[i]; if (isalnum(b)) @@ -163,7 +163,7 @@ std::string StringRef::toHexString(int limit) const { std::string StringRef::toFullHexStringPlain() const { std::string s; - s.reserve(length * 7); + s.reserve(static_cast(length) * 7); for (int i = 0; i < length; i++) { uint8_t b = (*this)[i]; s.append(format("%02x ", b)); diff --git a/flow/FastAlloc.cpp b/flow/FastAlloc.cpp index c913c6bac6..ed51df74b6 100644 --- a/flow/FastAlloc.cpp +++ b/flow/FastAlloc.cpp @@ -635,17 +635,20 @@ void FastAllocator::getMagazine() { #endif // NOTE: rely on lower level metrics in allocate() (and whatever it calls) // for accounting the allocations it does. - block = (void**)::allocate(magazine_size * Size, /*allowLargePages*/ false, includeGuardPages); + block = (void**)::allocate(static_cast(magazine_size) * Size, /*allowLargePages*/ false, includeGuardPages); #endif // void** block = new void*[ magazine_size * PSize ]; for (int i = 0; i < magazine_size - 1; i++) { - block[i * PSize + 1] = block[i * PSize] = &block[(i + 1) * PSize]; - check(&block[i * PSize], false); + const size_t offset = static_cast(i) * PSize; + const size_t nextOffset = static_cast(i + 1) * PSize; + block[offset + 1] = block[offset] = &block[nextOffset]; + check(&block[offset], false); } - block[(magazine_size - 1) * PSize + 1] = block[(magazine_size - 1) * PSize] = nullptr; - check(&block[(magazine_size - 1) * PSize], false); + const size_t lastOffset = static_cast(magazine_size - 1) * PSize; + block[lastOffset + 1] = block[lastOffset] = nullptr; + check(&block[lastOffset], false); thr.freelist = block; thr.count = magazine_size; } @@ -714,7 +717,7 @@ TEST_CASE("/jemalloc/4k_aligned_usable_size") { // Check that we can allocate 4k aligned up to 16k with no internal // fragmentation for (int i = 1; i < 4; ++i) { - ptr = aligned_alloc(4096, i * 4096); + ptr = aligned_alloc(4096, static_cast(i) * 4096); ASSERT_EQ(malloc_usable_size(ptr), i * 4096); aligned_free(ptr); ptr = nullptr; diff --git a/flow/Knobs.cpp b/flow/Knobs.cpp index 48c811af5d..291e276596 100644 --- a/flow/Knobs.cpp +++ b/flow/Knobs.cpp @@ -324,7 +324,7 @@ void FlowKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { // Encryption init( ENCRYPT_CIPHER_KEY_CACHE_TTL, isSimulated ? 5 * 60 : 10 * 60 ); - if ( randomize && buggify()) { ENCRYPT_CIPHER_KEY_CACHE_TTL = deterministicRandom()->randomInt(2, 10) * 60; } + if ( randomize && buggify()) { ENCRYPT_CIPHER_KEY_CACHE_TTL = deterministicRandom()->randomInt(2, 10) * 60LL; } init( ENCRYPT_KEY_REFRESH_INTERVAL, isSimulated ? 60 : 8 * 60 ); if ( randomize && buggify()) { ENCRYPT_KEY_REFRESH_INTERVAL = deterministicRandom()->randomInt(2, 10); } init( ENCRYPT_KEY_HEALTH_CHECK_INTERVAL, 10 ); diff --git a/flow/MkCert.cpp b/flow/MkCert.cpp index 8a2bbad7cf..124e4f0733 100644 --- a/flow/MkCert.cpp +++ b/flow/MkCert.cpp @@ -264,7 +264,7 @@ CertSpecRef CertSpecRef::make(Arena& arena, CertKind kind) { auto spec = CertSpecRef{}; spec.serialNumber = static_cast(deterministicRandom()->randomInt64(0, 1e10)); spec.offsetNotBefore = 0; // now - spec.offsetNotAfter = 60 * 60 * 24 * 365; // 1 year from now + spec.offsetNotAfter = 60L * 60 * 24 * 365; // 1 year from now auto& subject = spec.subjectName; subject.push_back(arena, { "countryName"_sr, "DE"_sr }); subject.push_back(arena, { "localityName"_sr, "Berlin"_sr }); diff --git a/flow/flow.cpp b/flow/flow.cpp index 762d9e3eb1..8debd77524 100644 --- a/flow/flow.cpp +++ b/flow/flow.cpp @@ -246,9 +246,9 @@ Optional parseDuration(std::string const& str, std::string const& defa } else if (!unit.compare("m")) { ret *= 60; } else if (!unit.compare("h")) { - ret *= 60 * 60; + ret *= 60ULL * 60; } else if (!unit.compare("d")) { - ret *= 24 * 60 * 60; + ret *= 24ULL * 60 * 60; } else { return Optional(); } From 1a82ade485f5b673ae23005a723ccb66646ab596 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 01:16:32 -0700 Subject: [PATCH 21/39] Preserve retired TLog data for subsequent recovery --- fdbserver/tlog/TLogServer.cpp | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 3a8695ed48..025498c30d 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -1356,30 +1356,8 @@ Future retireRecoveredLog(TLogData* self, Reference logData) { ASSERT(logData->persistentDataVersion == logData->version.get()); ASSERT(logData->persistentDataDurableVersion == logData->version.get()); - const Version popTo = logData->version.get() + 1; - for (int tagLocality = 0; tagLocality < logData->tag_data.size(); ++tagLocality) { - for (int tagId = 0; tagId < logData->tag_data[tagLocality].size(); ++tagId) { - Reference tagData = logData->tag_data[tagLocality][tagId]; - if (tagData && tagData->popped < popTo) { - tagData->popped = popTo; - tagData->poppedRecently = true; - co_await tagData->eraseMessagesBefore(popTo, self, logData, TaskPriority::UpdateStorage); - } - } - } - for (const auto& locality : logData->tag_data) { - for (const auto& tagData : locality) { - if (tagData) { - updatePersistentPopped(self, logData, tagData); - } - } - } - double tLogMaxCreateDuration = SERVER_KNOBS->TLOG_MAX_CREATE_DURATION; - if (g_network->isSimulated() && logData->logSpillType == TLogSpillType::VALUE) { - tLogMaxCreateDuration *= 2; - } - co_await ioTimeoutError(self->persistentData->commit(), tLogMaxCreateDuration, "TLogRetireCommit"); - + // Keep the spilled messages available for a subsequent recovery. Once they are durable, only the shared queue + // needs to advance so later generations can make progress. logData->retired = true; advanceRetiredLogQueues(self); } From c14f8ed550db66072008eefe708c4ffba307dc3b Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 03:36:09 -0700 Subject: [PATCH 22/39] Recover when an old TLog is excluded --- .../clustercontroller/ClusterController.h | 16 ++++++++++++ fdbserver/tlog/TLogServer.cpp | 26 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.h b/fdbserver/clustercontroller/ClusterController.h index f52c2fe5db..ca02fb8ec2 100644 --- a/fdbserver/clustercontroller/ClusterController.h +++ b/fdbserver/clustercontroller/ClusterController.h @@ -2418,6 +2418,22 @@ public: std::vector backup_workers; std::set backup_addresses; + if (dbi.recoveryState == RecoveryState::FULLY_RECOVERED) { + for (const auto& oldLog : dbi.logSystemConfig.oldTLogs) { + for (const auto& logSet : oldLog.tLogs) { + for (const auto& tlog : logSet.tLogs) { + if (tlog.present() && + db.config.isExcludedServer(tlog.interf().addresses(), tlog.interf().filteredLocality)) { + TraceEvent("BetterMasterExists", id) + .detail("Reason", "OldTLogExcluded") + .detail("ProcessID", tlog.interf().filteredLocality.processId()); + return true; + } + } + } + } + } + for (auto& logSet : dbi.logSystemConfig.tLogs) { for (auto& it : logSet.tLogs) { auto tlogWorker = id_worker.find(it.interf().filteredLocality.processId()); diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 025498c30d..3a8695ed48 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -1356,8 +1356,30 @@ Future retireRecoveredLog(TLogData* self, Reference logData) { ASSERT(logData->persistentDataVersion == logData->version.get()); ASSERT(logData->persistentDataDurableVersion == logData->version.get()); - // Keep the spilled messages available for a subsequent recovery. Once they are durable, only the shared queue - // needs to advance so later generations can make progress. + const Version popTo = logData->version.get() + 1; + for (int tagLocality = 0; tagLocality < logData->tag_data.size(); ++tagLocality) { + for (int tagId = 0; tagId < logData->tag_data[tagLocality].size(); ++tagId) { + Reference tagData = logData->tag_data[tagLocality][tagId]; + if (tagData && tagData->popped < popTo) { + tagData->popped = popTo; + tagData->poppedRecently = true; + co_await tagData->eraseMessagesBefore(popTo, self, logData, TaskPriority::UpdateStorage); + } + } + } + for (const auto& locality : logData->tag_data) { + for (const auto& tagData : locality) { + if (tagData) { + updatePersistentPopped(self, logData, tagData); + } + } + } + double tLogMaxCreateDuration = SERVER_KNOBS->TLOG_MAX_CREATE_DURATION; + if (g_network->isSimulated() && logData->logSpillType == TLogSpillType::VALUE) { + tLogMaxCreateDuration *= 2; + } + co_await ioTimeoutError(self->persistentData->commit(), tLogMaxCreateDuration, "TLogRetireCommit"); + logData->retired = true; advanceRetiredLogQueues(self); } From 8b21a7093be4858ab565bcdc496b3c95f11c3af2 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 09:47:05 -0700 Subject: [PATCH 23/39] Avoid reentrant DD teardown when a relocation fails --- fdbserver/datadistributor/DDRelocationQueue.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 1f912f52f2..6ffc73a7db 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -2441,8 +2441,10 @@ Future dataDistributionRelocator(DDQueue* self, .detail("Src", describe(rd.src)) .detail("DataMoveMetaData", rd.dataMove != nullptr ? rd.dataMove->meta.toString() : "Empty"); } else if (err.code() != error_code_actor_cancelled && err.code() != error_code_data_move_cancelled) { + co_await delay(0, TaskPriority::DataDistributionLaunch); // Unwind launchQueuedWork before DD teardown. if (errorOut.canBeSet()) { errorOut.sendError(err); + co_await delay(0); // Check for cancellation, since sendError can tear down DD state inline. } } throw err; From 244e9f9b948678a8f571954ab27e7bcb94b31489 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 09:54:07 -0700 Subject: [PATCH 24/39] Serialize DD relocator error propagation --- .../datadistributor/DDRelocationQueue.cpp | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 6ffc73a7db..cfaed3b488 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -2441,10 +2441,8 @@ Future dataDistributionRelocator(DDQueue* self, .detail("Src", describe(rd.src)) .detail("DataMoveMetaData", rd.dataMove != nullptr ? rd.dataMove->meta.toString() : "Empty"); } else if (err.code() != error_code_actor_cancelled && err.code() != error_code_data_move_cancelled) { - co_await delay(0, TaskPriority::DataDistributionLaunch); // Unwind launchQueuedWork before DD teardown. if (errorOut.canBeSet()) { errorOut.sendError(err); - co_await delay(0); // Check for cancellation, since sendError can tear down DD state inline. } } throw err; @@ -3071,9 +3069,23 @@ struct DDQueueImpl { } static Future waitAndValidate(RunState* state, Future future) { - co_await future; + Error error; + try { + co_await future; + } catch (Error& e) { + error = e; + } + // A relocator can signal an error inline while launchQueuedWork() is repairing its maps. Keep DD alive + // until that mutation finishes before propagating the error and tearing the queue down. Preserve the + // immediate error path when no mutation is active, since taking an available FlowLock still yields. + if (error.isValid() && state->queueMutationLock.available() > 0) { + throw error; + } co_await state->queueMutationLock.take(); FlowLock::Releaser lockGuard(state->queueMutationLock); + if (error.isValid()) { + throw error; + } validate(state); } @@ -3220,3 +3232,34 @@ TEST_CASE("/DataDistribution/DDQueue/BatchDrainRelocationComplete") { std::cout << "BatchDrainRelocationComplete: drained " << drained << " of " << N << " completions\n"; } + +TEST_CASE("/DataDistribution/DDQueue/SerializeRelocatorError") { + Reference self = makeReference(); + DDQueueImpl::RunState state(self); + Promise error; + Future propagated; + + { + co_await state.queueMutationLock.take(); + FlowLock::Releaser lockGuard(state.queueMutationLock); + propagated = DDQueueImpl::waitAndValidate(&state, error.getFuture()); + error.sendError(movekeys_conflict()); + ASSERT(!propagated.isReady()); + } + + Error observed; + try { + co_await propagated; + } catch (Error& e) { + observed = e; + } + ASSERT(observed.code() == error_code_movekeys_conflict); + + Promise immediateError; + Future immediate = DDQueueImpl::waitAndValidate(&state, immediateError.getFuture()); + immediateError.sendError(movekeys_conflict()); + ASSERT(immediate.isReady()); + ASSERT(immediate.isError()); + ASSERT(immediate.getError().code() == error_code_movekeys_conflict); + co_return; +} From fb83ffeb39f327e9d4f36c2f79dcefbb07186ca7 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 11:31:18 -0700 Subject: [PATCH 25/39] Fix ReadYourWrites clang-tidy warnings --- fdbclient/ReadYourWrites.cpp | 79 ++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 79374ef3fc..e89fbe6767 100644 --- a/fdbclient/ReadYourWrites.cpp +++ b/fdbclient/ReadYourWrites.cpp @@ -144,7 +144,7 @@ public: co_await getRangeValue(ryw, read.key, firstGreaterOrEqual(ryw->getMaxReadKey()), GetRangeLimits(1), it); if (result.readToBegin) co_return allKeys.begin; - if (result.readThroughEnd || !result.size()) + if (result.readThroughEnd || result.empty()) co_return ryw->getMaxReadKey(); co_return result[0].key; } else { @@ -153,7 +153,7 @@ public: co_await getRangeValueBack(ryw, firstGreaterOrEqual(allKeys.begin), read.key, GetRangeLimits(1), it); if (result.readThroughEnd) co_return ryw->getMaxReadKey(); - if (result.readToBegin || !result.size()) + if (result.readToBegin || result.empty()) co_return allKeys.begin; co_return result[0].key; } @@ -201,7 +201,7 @@ public: RangeResult v = co_await ryw->tr.getRange( read.begin, read.end, read.limits, snapshot, backwards ? Reverse::True : Reverse::False); KeyRef maxKey = ryw->getMaxReadKey(); - if (v.size() > 0) { + if (!v.empty()) { if (!backwards && v[v.size() - 1].key >= maxKey) { RangeResult _v = v; int i = _v.size() - 2; @@ -229,14 +229,15 @@ public: static void addConflictRange(ReadYourWritesTransaction* ryw, GetKeyReq read, WriteMap::iterator& it, Key result) { KeyRangeRef readRange; - if (read.key.offset <= 0) + if (read.key.offset <= 0) { readRange = KeyRangeRef(KeyRef(ryw->arena, result), read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey())); - else + } else { readRange = KeyRangeRef(read.key.orEqual ? keyAfter(read.key.getKey(), ryw->arena) : KeyRef(ryw->arena, read.key.getKey()), keyAfter(result, ryw->arena)); + } it.skip(readRange.begin); ryw->updateConflictMap(readRange, it); @@ -476,7 +477,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { beginKey = std::min(beginKey, data[0].key); if (data.readThrough.present()) { endKey = std::max(endKey, data.readThrough.get()); @@ -511,8 +512,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -541,8 +543,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { b = e; + } ++it; e = it.endKey(); } @@ -633,7 +636,7 @@ public: .detail("Unknown", it.is_unknown_range()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -645,7 +648,7 @@ public: (begin.offset >= 1 && begin.getKey() >= ryw->getMaxReadKey())) { if (end.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedEnd = co_await read( ryw, @@ -673,7 +676,7 @@ public: break; if (it.is_unknown_range()) { - if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && result.size() && + if (limits.hasByteLimit() && limits.hasSatisfiedMinRows() && !result.empty() && itemsPastEnd >= 1 - end.offset) { result.more = true; break; @@ -775,8 +778,9 @@ public: if (count) result.append(result.arena(), start, count); ++it; - } else + } else { ++it; + } } result.more = result.more || limits.isReached(); @@ -805,7 +809,7 @@ public: if (data.readThroughEnd) endKey = allKeys.end; - if (data.size()) { + if (!data.empty()) { if (data.readThrough.present()) { beginKey = std::min(data.readThrough.get(), beginKey); } else { @@ -840,8 +844,9 @@ public: return singleEmpty; } singleEmpty++; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -868,8 +873,9 @@ public: singleEmpty++; if (singleEmpty >= maxClears) return maxClears; - } else + } else { e = b; + } --it; b = it.beginKey(); } @@ -937,7 +943,7 @@ public: .detail("Kv", it.is_kv()) .detail("Requests", requestCount);*/ - if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { + if (result.empty() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { co_return RangeResultRef(false, false); } @@ -949,7 +955,7 @@ public: (end.offset <= 1 && end.getKey() == allKeys.begin)) { if (begin.isFirstGreaterOrEqual()) break; - if (!result.size()) + if (result.empty()) break; Key resolvedBegin = co_await read( ryw, @@ -980,7 +986,7 @@ public: } if (it.is_unknown_range()) { - if (limits.hasByteLimit() && result.size() && itemsPastBegin >= begin.offset - 1) { + if (limits.hasByteLimit() && !result.empty() && itemsPastBegin >= begin.offset - 1) { result.more = true; break; } @@ -1227,7 +1233,7 @@ public: auto itCopy = it; ++it; - ASSERT(itCopy->value.size()); + ASSERT(!itCopy->value.empty()); CODE_PROBE(itCopy->value.size() > 1, "Multiple watches on the same key triggered by RYOW"); for (int i = 0; i < itCopy->value.size(); i++) { @@ -1248,7 +1254,7 @@ public: } } - if (itCopy->value.size() == 0) + if (itCopy->value.empty()) ryw->watchMap.erase(itCopy); } } @@ -1342,11 +1348,12 @@ public: ryw->nativeReadRanges = ryw->tr.readConflictRanges(); ryw->nativeWriteRanges = ryw->tr.writeConflictRanges(); for (const auto& f : ryw->tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { ryw->nativeReadRanges.push_back( ryw->nativeReadRanges.arena(), KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, ryw->nativeReadRanges.arena())); + } } if (ryw->resetPromise.isSet()) @@ -1481,7 +1488,7 @@ public: } static Future onError(ReadYourWritesTransaction* ryw, Error e) { - if (ryw->debugTraces.size() > 0 || ryw->debugMessages.size() > 0) { + if (!ryw->debugTraces.empty() || !ryw->debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(printDebugMessages(ryw, {}, e).isReady()); } @@ -2044,10 +2051,11 @@ RangeResult ReadYourWritesTransaction::getReadConflictRangeIntersecting(KeyRange for (const auto& range : nativeReadRanges) readConflicts.insert(range.withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); for (const auto& f : tr.getExtraReadConflictRanges()) { - if (f.isReady() && f.get().first < f.get().second) + if (f.isReady() && f.get().first < f.get().second) { readConflicts.insert(KeyRangeRef(f.get().first, f.get().second) .withPrefix(readConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } auto beginIter = readConflicts.rangeContaining(kr.begin); if (beginIter->begin() != kr.begin) @@ -2074,11 +2082,12 @@ RangeResult ReadYourWritesTransaction::getWriteConflictRangeIntersecting(KeyRang if (it.beginKey() > allKeys.begin) --it; for (; it.beginKey() < strippedWriteRangePrefix.end; ++it) { - if (it.is_conflict_range()) + if (it.is_conflict_range()) { writeConflicts.insert( KeyRangeRef(it.beginKey().toArena(result.arena()), it.endKey().toArena(result.arena())) .withPrefix(writeConflictRangeKeysRange.begin, result.arena()), "1"_sr); + } } } else { for (const auto& range : tr.writeConflictRanges()) @@ -2410,7 +2419,7 @@ Future ReadYourWritesTransaction::commit() { result = RYWImpl::commit(this); } - return debugMessages.size() > 0 || debugTraces.size() > 0 ? RYWImpl::printDebugMessages(this, result) : result; + return !debugMessages.empty() || !debugTraces.empty() ? RYWImpl::printDebugMessages(this, result) : result; } Future> ReadYourWritesTransaction::getVersionstamp() { @@ -2533,7 +2542,7 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep reading = std::move(r.reading); resetPromise = std::move(r.resetPromise); r.resetPromise = Promise(); - deferredError = std::move(r.deferredError); + deferredError = r.deferredError; retries = r.retries; approximateSize = r.approximateSize; timeoutActor = r.timeoutActor; @@ -2554,11 +2563,10 @@ void ReadYourWritesTransaction::operator=(ReadYourWritesTransaction&& r) noexcep } ReadYourWritesTransaction::ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept - : deferredError(std::move(r.deferredError)), arena(std::move(r.arena)), cache(std::move(r.cache)), - writes(std::move(r.writes)), resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), - retries(r.retries), approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), - creationTime(r.creationTime), commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), - options(r.options) { + : deferredError(r.deferredError), arena(std::move(r.arena)), cache(std::move(r.cache)), writes(std::move(r.writes)), + resetPromise(std::move(r.resetPromise)), reading(std::move(r.reading)), retries(r.retries), + approximateSize(r.approximateSize), timeoutActor(std::move(r.timeoutActor)), creationTime(r.creationTime), + commitStarted(r.commitStarted), transactionDebugInfo(r.transactionDebugInfo), options(r.options) { cache.arena = &arena; writes.arena = &arena; tr = std::move(r.tr); @@ -2637,7 +2645,7 @@ void ReadYourWritesTransaction::cancel() { } void ReadYourWritesTransaction::reset() { - if (debugTraces.size() > 0 || debugMessages.size() > 0) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(RYWImpl::printDebugMessages(this, {}).isReady()); } @@ -2676,7 +2684,7 @@ ReadYourWritesTransaction::~ReadYourWritesTransaction() { if (!resetPromise.isSet()) resetPromise.sendError(transaction_cancelled()); - if (debugTraces.size() || debugMessages.size()) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument [[maybe_unused]] Future f = RYWImpl::printDebugMessages(this, {}); } @@ -2700,14 +2708,15 @@ void ReadYourWritesTransaction::debugLogRetries(Optional error) { if (!transactionDebugInfo->transactionName.empty()) transactionNameStr = format(" in transaction '%s'", printable(StringRef(transactionDebugInfo->transactionName)).c_str()); - if (!g_network->isSimulated()) // Fuzz workload turns this on, but we do not want stderr output in - // simulation + if (!g_network->isSimulated()) { // Fuzz workload turns this on, but we do not want stderr output in + // simulation fprintf(stderr, "fdb WARNING: long transaction (%.2fs elapsed%s, %d retries, %s)\n", elapsed, transactionNameStr.c_str(), retries, committed ? "committed" : error.get().what()); + } { TraceEvent trace = TraceEvent("LongTransaction"); if (error.present()) From b6c03073dd11e93f34db9d0e0ce439acfbcbc320 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 13:25:28 -0700 Subject: [PATCH 26/39] Recover excluded old TLogs using full worker locality --- .../ClusterController.actor.cpp | 42 +++++++++++++++++++ .../clustercontroller/ClusterController.h | 16 +++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index a5b8ab2504..0f1f147e96 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -3531,6 +3531,48 @@ TEST_CASE("/fdbserver/clustercontroller/ignoreStaleWorkerRegistration") { return Void(); } +TEST_CASE("/fdbserver/clustercontroller/recoverForExcludedOldTLogLocality") { + ClusterControllerData data(ClusterControllerFullInterface(), + LocalityData(), + ServerCoordinators(Reference( + new ClusterConnectionMemoryRecord(ClusterConnectionString()))), + makeReference>>()); + + LocalityData masterLocality; + masterLocality.set(LocalityData::keyProcessId, Standalone(std::string{ "master" })); + data.id_worker[masterLocality.processId()]; + + const NetworkAddress oldTLogAddress(IPAddress(0x02020202), 1); + LocalityData workerLocality; + workerLocality.set(LocalityData::keyProcessId, Standalone(std::string{ "old-tlog" })); + workerLocality.set("instance_id"_sr, Standalone(std::string{ "log-4296" })); + WorkerInterface worker(workerLocality); + worker.tLog = RequestStream(Endpoint({ oldTLogAddress }, UID(1, 2))); + data.id_worker[workerLocality.processId()].details.interf = worker; + + LocalityData filteredLocality; + filteredLocality.set(LocalityData::keyZoneId, Standalone(std::string{ "zone" })); + TLogInterface oldTLog(filteredLocality); + oldTLog.peekMessages = RequestStream(Endpoint({ oldTLogAddress }, UID(3, 4))); + + data.db.config.set(StringRef(encodeExcludedLocalityKey("locality_instance_id:log-4296")), StringRef()); + ASSERT(data.db.config.isExcludedServer(worker.addresses(), worker.locality)); + ASSERT(!data.db.config.isExcludedServer(oldTLog.addresses(), oldTLog.filteredLocality)); + + TLogSet oldTLogSet; + oldTLogSet.tLogs.push_back(OptionalInterface(oldTLog)); + OldTLogConf oldTLogConf; + oldTLogConf.tLogs.push_back(oldTLogSet); + ServerDBInfo dbInfo; + dbInfo.master.locality = masterLocality; + dbInfo.logSystemConfig.oldTLogs.push_back(oldTLogConf); + dbInfo.recoveryState = RecoveryState::FULLY_RECOVERED; + data.db.serverInfo->set(dbInfo); + + ASSERT(data.betterMasterExists()); + return Void(); +} + // Tests `ClusterControllerData::updateWorkerHealth()` can update `ClusterControllerData::workerHealth` // based on `UpdateWorkerHealth` request correctly. TEST_CASE("/fdbserver/clustercontroller/updateWorkerHealth") { diff --git a/fdbserver/clustercontroller/ClusterController.h b/fdbserver/clustercontroller/ClusterController.h index ca02fb8ec2..13d298ac7e 100644 --- a/fdbserver/clustercontroller/ClusterController.h +++ b/fdbserver/clustercontroller/ClusterController.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include "fdbclient/DatabaseContext.h" @@ -2422,11 +2423,20 @@ public: for (const auto& oldLog : dbi.logSystemConfig.oldTLogs) { for (const auto& logSet : oldLog.tLogs) { for (const auto& tlog : logSet.tLogs) { - if (tlog.present() && - db.config.isExcludedServer(tlog.interf().addresses(), tlog.interf().filteredLocality)) { + if (!tlog.present()) { + continue; + } + + auto tlogWorker = std::find_if(id_worker.begin(), id_worker.end(), [&tlog](const auto& worker) { + return worker.second.details.interf.address() == tlog.interf().address(); + }); + const auto& locality = tlogWorker == id_worker.end() + ? tlog.interf().filteredLocality + : tlogWorker->second.details.interf.locality; + if (db.config.isExcludedServer(tlog.interf().addresses(), locality)) { TraceEvent("BetterMasterExists", id) .detail("Reason", "OldTLogExcluded") - .detail("ProcessID", tlog.interf().filteredLocality.processId()); + .detail("ProcessID", locality.processId()); return true; } } From 91497bae87b3a345f944aedcf64631a5476d2c58 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 14:23:29 -0700 Subject: [PATCH 27/39] Disable buggify for TxnTimeout test --- tests/fast/TxnTimeout.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/fast/TxnTimeout.toml b/tests/fast/TxnTimeout.toml index 7f64d6369d..364773c712 100644 --- a/tests/fast/TxnTimeout.toml +++ b/tests/fast/TxnTimeout.toml @@ -17,6 +17,9 @@ # - Transactions consistently stay open for the target duration (7 seconds) # - Test passes even with concurrent Cycle workload providing background load +[configuration] +buggify = false + [[knobs]] # Configure transaction lifetime to 15 seconds # At 1M versions/second rate, this equals 15,000,000 versions From 5fff049dd5487ac2398c7995947d47c3d3752832 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 15:02:27 -0700 Subject: [PATCH 28/39] Disable buggify for MaxGrvQueueDelay test --- tests/fast/MaxGrvQueueDelay.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/fast/MaxGrvQueueDelay.toml b/tests/fast/MaxGrvQueueDelay.toml index e385a2465d..e8cd1989a7 100644 --- a/tests/fast/MaxGrvQueueDelay.toml +++ b/tests/fast/MaxGrvQueueDelay.toml @@ -1,3 +1,6 @@ +[configuration] +buggify = false + [[knobs]] # Keep GRV ratekeeper capacity low enough that the warmup burst creates # queueing without throttling cluster setup. From 7591ab1bddab8acb5e74384450a89a8fb1582553 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 17:42:58 -0700 Subject: [PATCH 29/39] Put worker reboot future in the first race slot --- fdbserver/worker/worker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index 200eb6408a..1cb7679ac6 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -2882,8 +2882,8 @@ public: lastSnapTime(lastSnapTime) {} Future run(Future const& handleErrors) { - auto res = co_await race(serveServerDBInfoUpdates(), - interf.clientInterface.reboot.getFuture(), + auto res = co_await race(interf.clientInterface.reboot.getFuture(), + serveServerDBInfoUpdates(), serveFailureInjectionRequests(), serveProfilerRequests(), serveMasterRecruitment(), @@ -2907,8 +2907,8 @@ public: serveSnapshotRequests(), errorForwarders.getResult(), handleErrors); - ASSERT(res.index() == 1); - co_await handleRebootRequest(std::get<1>(res)); + ASSERT(res.index() == 0); + co_await handleRebootRequest(std::get<0>(res)); } }; From 17376908bef3726d44f95c321a113eb43dd715fd Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 16 Jul 2026 22:21:18 -0700 Subject: [PATCH 30/39] Convert FlowGrpc actor tests to coroutines --- fdbrpc/FlowGrpcTests.actor.cpp | 134 --------------------------------- fdbrpc/FlowGrpcTests.cpp | 92 ++++++++++++++++++++++ 2 files changed, 92 insertions(+), 134 deletions(-) delete mode 100644 fdbrpc/FlowGrpcTests.actor.cpp diff --git a/fdbrpc/FlowGrpcTests.actor.cpp b/fdbrpc/FlowGrpcTests.actor.cpp deleted file mode 100644 index 7bf99f5d90..0000000000 --- a/fdbrpc/FlowGrpcTests.actor.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/** - * FlowGrpcTests.actor.cpp - * - * 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. - */ -#ifdef FLOW_GRPC_ENABLED -#include - -#include "fdbrpc/FlowGrpc.h" -#include "FlowGrpcTests.h" -#include "flow/UnitTest.h" - -#include "flow/actorcompiler.h" // This must be the last #include. - -// So that tests are not optimized out. :/ -void forceLinkGrpcTests() {} - -namespace fdbrpc_test { - -TEST_CASE("/fdbrpc/grpc/basic_sync_client") { - state NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50001")); - state GrpcServer server(addr); - server.registerService(make_shared()); - state Future server_actor = server.run(); - wait(server.onRunning()); - - EchoClient client(grpc::CreateChannel(addr.toString(), grpc::InsecureChannelCredentials())); - std::string reply = client.Echo("Ping!"); - std::cout << "Echo received: " << reply << std::endl; - ASSERT_EQ(reply, "Echo: Ping!"); - - wait(server.shutdown()); - wait(server_actor); - return Void(); -} - -TEST_CASE("/fdbrpc/grpc/basic_async_client") { - state NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50003")); - state GrpcServer server(addr); - server.registerService(make_shared()); - state Future _ = server.run(); - wait(server.onRunning()); - - state shared_ptr pool = make_shared(4); - state AsyncGrpcClient client(addr.toString(), pool); - - try { - state EchoRequest request; - request.set_message("Ping!"); - EchoResponse response = wait(client.call(&TestEchoService::Stub::Echo, request)); - std::cout << "Echo received: " << response.message() << std::endl; - ASSERT_EQ(response.message(), "Echo: Ping!"); - } catch (Error& e) { - ASSERT_EQ(e.code(), error_code_grpc_error); - ASSERT(false); - } - - return Void(); -} - -TEST_CASE("/fdbrpc/grpc/actor_basic_stream_server") { - state NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50002")); - state GrpcServer server(addr); - server.registerService(make_shared()); - state Future _ = server.run(); - wait(server.onRunning()); - - state shared_ptr pool = make_shared(4); - state AsyncGrpcClient client(addr.toString(), pool); - - state int count = 0; - try { - EchoRequest request; - request.set_message("Ping!"); - state ThreadFutureStream stream = client.call(&TestEchoService::Stub::EchoRecvStream10, request); - while (true) { - EchoResponse response = waitNext(stream); - ASSERT_EQ(response.message(), "Echo: Ping!"); - count += 1; - } - } catch (Error& e) { - std::cout << "Error: " << e.name() << std::endl; - if (e.code() == error_code_end_of_stream) { - ASSERT_EQ(count, 10); // Should send 10 reponses. - return Void(); - } - ASSERT(false); - } - return Void(); -} - -TEST_CASE("/fdbrpc/grpc/no_server_running") { - state NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50004")); - state shared_ptr pool = make_shared(4); - state AsyncGrpcClient client(addr.toString(), pool); - - try { - state EchoRequest request; - request.set_message("Ping!"); - EchoResponse response = wait(client.call(&TestEchoService::Stub::Echo, request)); - ASSERT(false); // RPC should fail as there is no server running.; - } catch (Error& e) { - ASSERT_EQ(e.code(), error_code_grpc_error); - } - - return Void(); -} - -TEST_CASE("/fdbrpc/grpc/destroy_server_without_shutdown") { - state NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50005")); - state GrpcServer server(addr); - server.registerService(make_shared()); - state Future _ = server.run(); - wait(server.onRunning()); - return Void(); -} - -} // namespace fdbrpc_test - -#endif diff --git a/fdbrpc/FlowGrpcTests.cpp b/fdbrpc/FlowGrpcTests.cpp index f1c0d017c7..192bc62813 100644 --- a/fdbrpc/FlowGrpcTests.cpp +++ b/fdbrpc/FlowGrpcTests.cpp @@ -30,10 +30,102 @@ #include "flow/flow.h" // So that tests are not optimized out. :/ +void forceLinkGrpcTests() {} void forceLinkGrpcTests2() {} namespace fdbrpc_test { +TEST_CASE("/fdbrpc/grpc/basic_sync_client") { + NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50001")); + GrpcServer server(addr); + server.registerService(make_shared()); + Future server_actor = server.run(); + co_await server.onRunning(); + + EchoClient client(grpc::CreateChannel(addr.toString(), grpc::InsecureChannelCredentials())); + std::string reply = client.Echo("Ping!"); + std::cout << "Echo received: " << reply << std::endl; + ASSERT_EQ(reply, "Echo: Ping!"); + + co_await server.shutdown(); + co_await server_actor; +} + +TEST_CASE("/fdbrpc/grpc/basic_async_client") { + NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50003")); + GrpcServer server(addr); + server.registerService(make_shared()); + Future _ = server.run(); + co_await server.onRunning(); + + auto pool = make_shared(4); + AsyncGrpcClient client(addr.toString(), pool); + + try { + EchoRequest request; + request.set_message("Ping!"); + EchoResponse response = co_await client.call(&TestEchoService::Stub::Echo, request); + std::cout << "Echo received: " << response.message() << std::endl; + ASSERT_EQ(response.message(), "Echo: Ping!"); + } catch (Error& e) { + ASSERT_EQ(e.code(), error_code_grpc_error); + ASSERT(false); + } +} + +TEST_CASE("/fdbrpc/grpc/actor_basic_stream_server") { + NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50002")); + GrpcServer server(addr); + server.registerService(make_shared()); + Future _ = server.run(); + co_await server.onRunning(); + + auto pool = make_shared(4); + AsyncGrpcClient client(addr.toString(), pool); + + int count = 0; + try { + EchoRequest request; + request.set_message("Ping!"); + auto stream = client.call(&TestEchoService::Stub::EchoRecvStream10, request); + while (true) { + auto response = co_await stream; + ASSERT_EQ(response.message(), "Echo: Ping!"); + count += 1; + } + } catch (Error& e) { + std::cout << "Error: " << e.name() << std::endl; + if (e.code() == error_code_end_of_stream) { + ASSERT_EQ(count, 10); // Should send 10 reponses. + co_return; + } + ASSERT(false); + } +} + +TEST_CASE("/fdbrpc/grpc/no_server_running") { + NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50004")); + auto pool = make_shared(4); + AsyncGrpcClient client(addr.toString(), pool); + + try { + EchoRequest request; + request.set_message("Ping!"); + EchoResponse response = co_await client.call(&TestEchoService::Stub::Echo, request); + ASSERT(false); // RPC should fail as there is no server running.; + } catch (Error& e) { + ASSERT_EQ(e.code(), error_code_grpc_error); + } +} + +TEST_CASE("/fdbrpc/grpc/destroy_server_without_shutdown") { + NetworkAddress addr(NetworkAddress::parse("127.0.0.1:50005")); + GrpcServer server(addr); + server.registerService(make_shared()); + Future _ = server.run(); + co_await server.onRunning(); +} + void generate_random_string(std::string* buffer, int size) { buffer->clear(); const std::string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; From 840f463ddc8cd8a1afcab86fa8c9067d69365856 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 00:44:45 -0700 Subject: [PATCH 31/39] Convert WorkerInterface actors to coroutines --- .../fdbserver/core/WorkerInterface.actor.h | 103 +++++++++--------- 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h index 73d0cbb982..74c1eb7ef6 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h @@ -19,11 +19,6 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_WORKERINTERFACE_ACTOR_G_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_G_H -#include "fdbserver/core/WorkerInterface.actor.g.h" -#elif !defined(FDBSERVER_WORKERINTERFACE_ACTOR_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_H #include "fdbserver/core/BackupInterface.h" #include "fdbserver/core/DataDistributorInterface.h" @@ -43,7 +38,7 @@ #include "fdbrpc/MultiInterface.h" #include "fdbclient/ClientWorkerInterface.h" #include "fdbserver/core/RecoveryState.h" -#include "flow/actorcompiler.h" +#include "flow/CoroUtils.h" struct WorkerInterface { constexpr static FileIdentifier file_identifier = 14712718; @@ -1174,42 +1169,44 @@ bool addressInDbAndRemoteDc( extern bool isSimulatorProcessUnreliable(); -ACTOR template -Future ioTimeoutError(Future what, double time, const char* context = nullptr) { +template +Future ioTimeoutError(Future what, double time, const char* context = nullptr, ExplicitVoid = {}) { // Before simulation is sped up, IO operations can take a very long time so limit timeouts // to not end until at least time after simulation is sped up. - state double orig = now(); - state std::string trace = platform::get_backtrace(); + double orig = now(); + std::string trace = platform::get_backtrace(); if (g_network->isSimulated() && !g_simulator->speedUpSimulation) { time += std::max(0.0, FLOW_KNOBS->SIM_SPEEDUP_AFTER_SECONDS - now()); } Future end = lowPriorityDelay(time); - choose { - when(T t = wait(what)) { - return t; + auto res = co_await race(what, end); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + Error err = io_timeout(); + if (isSimulatorProcessUnreliable()) { + err = err.asInjectedFault(); } - when(wait(end)) { - Error err = io_timeout(); - if (isSimulatorProcessUnreliable()) { - err = err.asInjectedFault(); - } - TraceEvent e(SevError, "IoTimeoutError"); - e.error(err); - if (context != nullptr) { - e.detail("Context", context); - } - e.detail("OrigTime", orig).detail("OrigTrace", trace).log(); - throw err; + TraceEvent e(SevError, "IoTimeoutError"); + e.error(err); + if (context != nullptr) { + e.detail("Context", context); } + e.detail("OrigTime", orig).detail("OrigTrace", trace).log(); + throw err; + } else { + UNREACHABLE(); } } -ACTOR template +template Future ioDegradedOrTimeoutError(Future what, double errTime, Reference> degraded, double degradedTime, - const char* context = nullptr) { + const char* context = nullptr, + ExplicitVoid = {}) { // Before simulation is sped up, IO operations can take a very long time so limit timeouts // to not end until at least time after simulation is sped up. if (g_network->isSimulated() && !g_simulator->speedUpSimulation) { @@ -1220,39 +1217,39 @@ Future ioDegradedOrTimeoutError(Future what, if (degradedTime < errTime) { Future degradedEnd = lowPriorityDelay(degradedTime); - choose { - when(T t = wait(what)) { - return t; - } - when(wait(degradedEnd)) { - CODE_PROBE(true, "TLog degraded", probe::func::deduplicate); - TraceEvent(SevWarnAlways, "IoDegraded").log(); - degraded->set(true); - } + auto res = co_await race(what, degradedEnd); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + CODE_PROBE(true, "TLog degraded", probe::func::deduplicate); + TraceEvent(SevWarnAlways, "IoDegraded").log(); + degraded->set(true); + } else { + UNREACHABLE(); } } Future end = lowPriorityDelay(errTime - degradedTime); - choose { - when(T t = wait(what)) { - return t; + auto res = co_await race(what, end); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + Error err = io_timeout(); + if (isSimulatorProcessUnreliable()) { + err = err.asInjectedFault(); } - when(wait(end)) { - Error err = io_timeout(); - if (isSimulatorProcessUnreliable()) { - err = err.asInjectedFault(); - } - TraceEvent e(SevError, "IoTimeoutError"); - e.error(err); - if (context != nullptr) { - e.detail("Context", context); - } - e.log(); - throw err; + TraceEvent e(SevError, "IoTimeoutError"); + e.error(err); + if (context != nullptr) { + e.detail("Context", context); } + e.log(); + throw err; + } else { + UNREACHABLE(); } } -#include "flow/unactorcompiler.h" #include "fdbserver/core/ServerDBInfo.h" -#endif From 4a3db78aa286b629b01fda44308ed4913ab090d3 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 00:58:03 -0700 Subject: [PATCH 32/39] Rename WorkerInterface coroutine header --- design/AI-generated/FDB_NETWORK_PROTOCOL.md | 2 +- design/AI-generated/foundationdb_subsystem_map.md | 2 +- design/AI-generated/subsystem_04_cluster_controller.md | 4 ++-- fdbserver/SimulatedCluster.cpp | 2 +- fdbserver/backupworker/BackupWorker.cpp | 2 +- fdbserver/cdcproxy/CDCProxy.cpp | 2 +- fdbserver/clustercontroller/ClusterController.actor.cpp | 2 +- fdbserver/clustercontroller/ClusterController.h | 2 +- fdbserver/clustercontroller/ClusterRecovery.h | 2 +- fdbserver/clustercontroller/Status.cpp | 2 +- fdbserver/clustercontroller/Status.h | 2 +- fdbserver/commitproxy/CommitProxyServer.cpp | 2 +- fdbserver/consistencyscan/ConsistencyScan.cpp | 2 +- fdbserver/coordinator/Coordination.cpp | 2 +- fdbserver/core/OpenDatabase.cpp | 2 +- fdbserver/core/QuietDatabase.cpp | 2 +- fdbserver/core/WorkerInterface.cpp | 2 +- fdbserver/core/WorkerInterfaceTests.cpp | 2 +- fdbserver/core/include/fdbserver/core/QuietDatabase.h | 2 +- fdbserver/core/include/fdbserver/core/ServerDBInfo.h | 2 +- fdbserver/core/include/fdbserver/core/WorkerEvents.h | 2 +- .../core/{WorkerInterface.actor.h => WorkerInterface.h} | 2 +- fdbserver/fdbserver.cpp | 2 +- fdbserver/grvproxy/GrvProxyServer.cpp | 2 +- fdbserver/kvstore/VersionedBTree.actor.cpp | 2 +- fdbserver/logrouter/LogRouter.cpp | 2 +- fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h | 2 +- fdbserver/resolver/Resolver.cpp | 2 +- fdbserver/storageserver/storageserver.cpp | 2 +- fdbserver/tester/ConsistencyChecker.cpp | 2 +- fdbserver/tester/TesterServer.cpp | 2 +- fdbserver/tester/test.cpp | 2 +- fdbserver/tlog/TLogServer.cpp | 2 +- fdbserver/tlog/TestTLogServer.cpp | 2 +- fdbserver/worker/RoleLineage.h | 2 +- fdbserver/worker/worker.cpp | 2 +- fdbserver/workloads/DiskFailureInjection.cpp | 2 +- fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp | 2 +- fdbserver/workloads/FailoverWithSSLag.cpp | 2 +- fdbserver/workloads/HealthMetricsApi.cpp | 2 +- fdbserver/workloads/KillRegion.cpp | 2 +- fdbserver/workloads/LogMetrics.cpp | 2 +- fdbserver/workloads/MachineAttrition.cpp | 2 +- fdbserver/workloads/Ping.cpp | 2 +- fdbserver/workloads/ReadWrite.cpp | 2 +- fdbserver/workloads/RemoveServersSafely.cpp | 2 +- fdbserver/workloads/SkewedReadWrite.cpp | 2 +- fdbserver/workloads/SnapTest.cpp | 2 +- fdbserver/workloads/TargetedKill.cpp | 2 +- fdbserver/workloads/Throughput.cpp | 2 +- fdbserver/workloads/WorkerErrors.cpp | 2 +- fdbserver/workloads/WriteBandwidth.cpp | 2 +- fdbserver/workloads/WriteTagThrottling.cpp | 2 +- 53 files changed, 54 insertions(+), 54 deletions(-) rename fdbserver/core/include/fdbserver/core/{WorkerInterface.actor.h => WorkerInterface.h} (99%) diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md index 31e0be5fcd..533c710622 100644 --- a/design/AI-generated/FDB_NETWORK_PROTOCOL.md +++ b/design/AI-generated/FDB_NETWORK_PROTOCOL.md @@ -1457,7 +1457,7 @@ Serializes all endpoints directly: `waitFailure`, `getRateInfo`, `haltRatekeeper ## 13. Worker Protocol -**Source:** `fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h` +**Source:** `fdbserver/core/include/fdbserver/core/WorkerInterface.h` Workers host server roles. The cluster controller sends initialization requests. diff --git a/design/AI-generated/foundationdb_subsystem_map.md b/design/AI-generated/foundationdb_subsystem_map.md index 84f0e22ae0..2f53560818 100644 --- a/design/AI-generated/foundationdb_subsystem_map.md +++ b/design/AI-generated/foundationdb_subsystem_map.md @@ -107,7 +107,7 @@ Plus supporting code: [`fdbserver/worker/`](https://github.com/apple/foundationd - `ServerDBInfo` is the cluster-wide configuration broadcast. Contains: master interface, proxy lists, log system config, recovery state, latency band config. - Updated by CC and distributed to all workers. Workers react to changes (e.g., new proxy set). -**Principal files:** [`ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp), `ClusterController.h`, [`Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp), [`LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp), [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp), [`WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h) +**Principal files:** [`ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp), `ClusterController.h`, [`Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp), [`LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp), [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp), [`WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h) --- diff --git a/design/AI-generated/subsystem_04_cluster_controller.md b/design/AI-generated/subsystem_04_cluster_controller.md index 86a285215a..f466e142f3 100644 --- a/design/AI-generated/subsystem_04_cluster_controller.md +++ b/design/AI-generated/subsystem_04_cluster_controller.md @@ -294,7 +294,7 @@ struct ServerDBInfo { --- -## WorkerInterface -- [`WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h)`:45-126` +## WorkerInterface -- [`WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h)`:45-126` RPCs exposed by every worker process: @@ -315,5 +315,5 @@ RPCs exposed by every worker process: | [`fdbserver/coordinator/Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp) | leaderRegister, generation register, coordination | | [`fdbserver/core/LeaderElection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.cpp) | tryBecomeLeaderInternal, candidacy, heartbeat | | [`fdbserver/core/CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp) | Replicated read/write over generation registers | -| [`fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h) | WorkerInterface, ClusterControllerFullInterface | +| [`fdbserver/core/include/fdbserver/core/WorkerInterface.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.h) | WorkerInterface, ClusterControllerFullInterface | | `fdbserver/core/include/fdbserver/core/ServerDBInfo.h` | ServerDBInfo structure and broadcasting | diff --git a/fdbserver/SimulatedCluster.cpp b/fdbserver/SimulatedCluster.cpp index a6263140c2..a0d62a34d7 100644 --- a/fdbserver/SimulatedCluster.cpp +++ b/fdbserver/SimulatedCluster.cpp @@ -39,7 +39,7 @@ #include "fdbclient/DatabaseContext.h" #include "fdbserver/tester/tester.h" #include "fdbserver/core/FDBSimulatorProcessInfo.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/worker/Worker.h" #include "fdbclient/ClusterInterface.h" #include "fdbserver/core/Knobs.h" diff --git a/fdbserver/backupworker/BackupWorker.cpp b/fdbserver/backupworker/BackupWorker.cpp index 9e51b4edb8..993f597401 100644 --- a/fdbserver/backupworker/BackupWorker.cpp +++ b/fdbserver/backupworker/BackupWorker.cpp @@ -34,7 +34,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/backupworker/BackupWorker.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/Error.h" #include "flow/IRandom.h" diff --git a/fdbserver/cdcproxy/CDCProxy.cpp b/fdbserver/cdcproxy/CDCProxy.cpp index 686dbc5c80..91020a7fe8 100644 --- a/fdbserver/cdcproxy/CDCProxy.cpp +++ b/fdbserver/cdcproxy/CDCProxy.cpp @@ -37,7 +37,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/SpanContextMessage.h" #include "fdbserver/core/WaitFailure.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/logsystem/LogSystemConsumer.h" #include "fdbserver/logsystem/LogSystemFactory.h" #include "flow/ActorCollection.h" diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 0f1f147e96..9744258ba3 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -36,7 +36,7 @@ #include "fdbrpc/Locality.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/ProcessClassRecruitment.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "fdbclient/ClusterConnectionMemoryRecord.h" #include "fdbclient/NativeAPI.actor.h" diff --git a/fdbserver/clustercontroller/ClusterController.h b/fdbserver/clustercontroller/ClusterController.h index 13d298ac7e..0cb3635780 100644 --- a/fdbserver/clustercontroller/ClusterController.h +++ b/fdbserver/clustercontroller/ClusterController.h @@ -32,7 +32,7 @@ #include "RatekeeperMonitor.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/ProcessClassRecruitment.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbrpc/Locality.h" #include "flow/CoroUtils.h" #include "flow/NetworkAddress.h" diff --git a/fdbserver/clustercontroller/ClusterRecovery.h b/fdbserver/clustercontroller/ClusterRecovery.h index 2a4941c1a0..ac6f409661 100644 --- a/fdbserver/clustercontroller/ClusterRecovery.h +++ b/fdbserver/clustercontroller/ClusterRecovery.h @@ -35,7 +35,7 @@ #include "fdbserver/logsystem/LogSystem.h" #include "fdbserver/core/LogSystemConfig.h" #include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/CoroUtils.h" #include "flow/Error.h" #include "flow/SystemMonitor.h" diff --git a/fdbserver/clustercontroller/Status.cpp b/fdbserver/clustercontroller/Status.cpp index 8193be0f13..19f5c3cb6d 100644 --- a/fdbserver/clustercontroller/Status.cpp +++ b/fdbserver/clustercontroller/Status.cpp @@ -32,7 +32,7 @@ #include "fdbclient/SystemData.h" #include "fdbclient/ReadYourWrites.h" #include "fdbserver/core/WorkerEvents.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include #include "ClusterRecovery.h" #include "fdbclient/ClusterConnectionMemoryRecord.h" diff --git a/fdbserver/clustercontroller/Status.h b/fdbserver/clustercontroller/Status.h index 827ab5f6b1..dda77be220 100644 --- a/fdbserver/clustercontroller/Status.h +++ b/fdbserver/clustercontroller/Status.h @@ -22,7 +22,7 @@ #include "fdbrpc/fdbrpc.h" #include "fdbserver/core/CoordinationInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/ClusterInterface.h" diff --git a/fdbserver/commitproxy/CommitProxyServer.cpp b/fdbserver/commitproxy/CommitProxyServer.cpp index b836278ee7..869f44a666 100644 --- a/fdbserver/commitproxy/CommitProxyServer.cpp +++ b/fdbserver/commitproxy/CommitProxyServer.cpp @@ -56,7 +56,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/commitproxy/CommitProxyServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "flow/CodeProbe.h" #include "flow/CoroUtils.h" diff --git a/fdbserver/consistencyscan/ConsistencyScan.cpp b/fdbserver/consistencyscan/ConsistencyScan.cpp index c33c2324ce..9382f88af9 100644 --- a/fdbserver/consistencyscan/ConsistencyScan.cpp +++ b/fdbserver/consistencyscan/ConsistencyScan.cpp @@ -25,7 +25,7 @@ #include "fdbclient/json_spirit/json_spirit_writer_template.h" #include "fdbserver/consistencyscan/ConsistencyScan.h" #include "fdbserver/core/FDBSimulationPolicy.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/IRandom.h" #include "flow/IndexedSet.h" #include "fdbrpc/FailureMonitor.h" diff --git a/fdbserver/coordinator/Coordination.cpp b/fdbserver/coordinator/Coordination.cpp index 5cb7706f8c..4bc634f032 100644 --- a/fdbserver/coordinator/Coordination.cpp +++ b/fdbserver/coordinator/Coordination.cpp @@ -23,7 +23,7 @@ #include "fdbserver/coordinator/CoordinationServer.h" #include "fdbserver/core/Knobs.h" #include "OnDemandStore.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "flow/ProtocolVersion.h" #include "flow/UnitTest.h" diff --git a/fdbserver/core/OpenDatabase.cpp b/fdbserver/core/OpenDatabase.cpp index fa83ac4258..6ed2b8a9a0 100644 --- a/fdbserver/core/OpenDatabase.cpp +++ b/fdbserver/core/OpenDatabase.cpp @@ -22,7 +22,7 @@ #include "fdbclient/DatabaseContext.h" #include "fdbclient/GlobalConfig.h" #include "fdbclient/MonitorLeader.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" static Future extractClientInfo(Reference const> db, Reference> info) { diff --git a/fdbserver/core/QuietDatabase.cpp b/fdbserver/core/QuietDatabase.cpp index d6c0861399..7c7f9581de 100644 --- a/fdbserver/core/QuietDatabase.cpp +++ b/fdbserver/core/QuietDatabase.cpp @@ -37,7 +37,7 @@ #include "fdbclient/RunRYWTransaction.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/QuietDatabase.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbclient/ManagementAPI.h" #include "flow/CoroUtils.h" diff --git a/fdbserver/core/WorkerInterface.cpp b/fdbserver/core/WorkerInterface.cpp index 29ebd158c5..1b1addd162 100644 --- a/fdbserver/core/WorkerInterface.cpp +++ b/fdbserver/core/WorkerInterface.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" Future extractClusterInterface(Reference> const> in, Reference>> out) { diff --git a/fdbserver/core/WorkerInterfaceTests.cpp b/fdbserver/core/WorkerInterfaceTests.cpp index f1267a2b61..84326eab41 100644 --- a/fdbserver/core/WorkerInterfaceTests.cpp +++ b/fdbserver/core/WorkerInterfaceTests.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ObjectSerializer.h" #include "flow/UnitTest.h" diff --git a/fdbserver/core/include/fdbserver/core/QuietDatabase.h b/fdbserver/core/include/fdbserver/core/QuietDatabase.h index 4fa8e4a812..9dac489e1b 100644 --- a/fdbserver/core/include/fdbserver/core/QuietDatabase.h +++ b/fdbserver/core/include/fdbserver/core/QuietDatabase.h @@ -24,7 +24,7 @@ #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" Future getDataInFlight(Database cx, Reference const> dbInfo); Future> getTLogQueueInfo(Database cx, diff --git a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h index 6adc7b1652..31a2fdd9be 100644 --- a/fdbserver/core/include/fdbserver/core/ServerDBInfo.h +++ b/fdbserver/core/include/fdbserver/core/ServerDBInfo.h @@ -29,7 +29,7 @@ #include "fdbserver/core/MasterInterface.h" #include "fdbserver/core/RatekeeperInterface.h" #include "fdbserver/core/RecoveryState.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" struct ServerDBInfo { constexpr static FileIdentifier file_identifier = 13838807; diff --git a/fdbserver/core/include/fdbserver/core/WorkerEvents.h b/fdbserver/core/include/fdbserver/core/WorkerEvents.h index e5bc268ac2..b2791d72eb 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerEvents.h +++ b/fdbserver/core/include/fdbserver/core/WorkerEvents.h @@ -25,7 +25,7 @@ #include #include "flow/ITrace.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" struct WorkerEvents : std::map {}; diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.h similarity index 99% rename from fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h rename to fdbserver/core/include/fdbserver/core/WorkerInterface.h index 74c1eb7ef6..53a2814ac9 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.h @@ -1,5 +1,5 @@ /* - * WorkerInterface.actor.h + * WorkerInterface.h * * This source file is part of the FoundationDB open source project * diff --git a/fdbserver/fdbserver.cpp b/fdbserver/fdbserver.cpp index 70a9689740..c981a86bd7 100644 --- a/fdbserver/fdbserver.cpp +++ b/fdbserver/fdbserver.cpp @@ -65,7 +65,7 @@ #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/TestEncryptionUtils.h" #include "fdbserver/tester/tester.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/worker/Worker.h" #include "fdbserver/mocks3/MockS3Server.h" #ifdef WITH_ROCKSDB diff --git a/fdbserver/grvproxy/GrvProxyServer.cpp b/fdbserver/grvproxy/GrvProxyServer.cpp index 5bf8ed3a0f..32e1a713b4 100644 --- a/fdbserver/grvproxy/GrvProxyServer.cpp +++ b/fdbserver/grvproxy/GrvProxyServer.cpp @@ -35,7 +35,7 @@ #include "fdbserver/logsystem/LogSystemFactory.h" #include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h" #include "fdbserver/core/WaitFailure.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbrpc/sim_validation.h" #include "flow/Buggify.h" #include "flow/IRandom.h" diff --git a/fdbserver/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..6afa07849a 100644 --- a/fdbserver/kvstore/VersionedBTree.actor.cpp +++ b/fdbserver/kvstore/VersionedBTree.actor.cpp @@ -30,7 +30,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "VersionedBTreeDebug.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "flow/CoroUtils.h" #include "flow/Error.h" diff --git a/fdbserver/logrouter/LogRouter.cpp b/fdbserver/logrouter/LogRouter.cpp index 10cc18e958..17d3f2470a 100644 --- a/fdbserver/logrouter/LogRouter.cpp +++ b/fdbserver/logrouter/LogRouter.cpp @@ -24,7 +24,7 @@ #include "fdbserver/logsystem/LogSystemConsumer.h" #include "fdbserver/logrouter/LogRouter.h" #include "fdbserver/logsystem/LogSystemFactory.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/RecoveryState.h" #include "fdbserver/core/TLogInterface.h" #include "flow/ActorCollection.h" diff --git a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index c90986872b..b3573501c7 100644 --- a/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h +++ b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h @@ -41,7 +41,7 @@ #include "fdbserver/core/OTELSpanContextMessage.h" #include "fdbserver/core/SpanContextMessage.h" #include "fdbserver/core/TLogInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/Arena.h" #include "flow/Error.h" #include "flow/ActorCollection.h" diff --git a/fdbserver/resolver/Resolver.cpp b/fdbserver/resolver/Resolver.cpp index 4c6c185540..dc9d875a9d 100644 --- a/fdbserver/resolver/Resolver.cpp +++ b/fdbserver/resolver/Resolver.cpp @@ -40,7 +40,7 @@ #include "fdbserver/core/StorageMetrics.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/resolver/Resolver.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "ConflictSet.h" #include "flow/ActorCollection.h" #include "flow/Error.h" diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index b7be1522e1..1e96d697bd 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -96,7 +96,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/DataMovement.h" #include "fdbserver/storageserver/StorageServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "StorageServerUtils.h" #include "flow/CoroUtils.h" #include "flow/TDMetric.h" diff --git a/fdbserver/tester/ConsistencyChecker.cpp b/fdbserver/tester/ConsistencyChecker.cpp index ec19a1e940..55fac113fa 100644 --- a/fdbserver/tester/ConsistencyChecker.cpp +++ b/fdbserver/tester/ConsistencyChecker.cpp @@ -36,7 +36,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/MoveKeys.h" #include "fdbserver/core/QuietDatabase.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "ConsistencyChecker.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/tester/TesterServer.cpp b/fdbserver/tester/TesterServer.cpp index 61501adeb3..5b89b184cd 100644 --- a/fdbserver/tester/TesterServer.cpp +++ b/fdbserver/tester/TesterServer.cpp @@ -34,7 +34,7 @@ #include "fdbserver/core/FDBSimulatorProcessInfo.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/ServerDBInfo.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "TesterServer.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/tester/test.cpp b/fdbserver/tester/test.cpp index 7dcc9e6ff8..41548787b1 100644 --- a/fdbserver/tester/test.cpp +++ b/fdbserver/tester/test.cpp @@ -41,7 +41,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/QuietDatabase.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "KnobProtectiveGroups.h" #include "ConsistencyChecker.h" diff --git a/fdbserver/tlog/TLogServer.cpp b/fdbserver/tlog/TLogServer.cpp index 3a8695ed48..f6b67e1564 100644 --- a/fdbserver/tlog/TLogServer.cpp +++ b/fdbserver/tlog/TLogServer.cpp @@ -36,7 +36,7 @@ #include "fdbserver/core/TLogInterface.h" #include "fdbserver/core/WaitFailure.h" #include "fdbserver/tlog/TLogServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "fdbrpc/FailureMonitor.h" #include "fdbrpc/sim_validation.h" diff --git a/fdbserver/tlog/TestTLogServer.cpp b/fdbserver/tlog/TestTLogServer.cpp index a4abfb46a2..0c222a997b 100644 --- a/fdbserver/tlog/TestTLogServer.cpp +++ b/fdbserver/tlog/TestTLogServer.cpp @@ -30,7 +30,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/TLogInterface.h" #include "fdbserver/tlog/TLogServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/logsystem/LogSystem.h" #include "fdbserver/logsystem/LogSystemFactory.h" #include "flow/IRandom.h" diff --git a/fdbserver/worker/RoleLineage.h b/fdbserver/worker/RoleLineage.h index fd07895eac..10902853eb 100644 --- a/fdbserver/worker/RoleLineage.h +++ b/fdbserver/worker/RoleLineage.h @@ -22,7 +22,7 @@ #include "fdbclient/ActorLineageProfiler.h" #include "fdbclient/ProcessClass.h" #include "fdbserver/core/ProcessClassRecruitment.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include #include diff --git a/fdbserver/worker/worker.cpp b/fdbserver/worker/worker.cpp index 200eb6408a..b9a9bc19a6 100644 --- a/fdbserver/worker/worker.cpp +++ b/fdbserver/worker/worker.cpp @@ -60,7 +60,7 @@ #include "fdbserver/logrouter/LogRouter.h" #include "fdbserver/core/BackupInterface.h" #include "RoleLineage.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/CoroFlow.h" #include "fdbserver/worker/Worker.h" #include "fdbserver/kvstore/IKeyValueStore.h" diff --git a/fdbserver/workloads/DiskFailureInjection.cpp b/fdbserver/workloads/DiskFailureInjection.cpp index af4996d86b..8326f98e20 100644 --- a/fdbserver/workloads/DiskFailureInjection.cpp +++ b/fdbserver/workloads/DiskFailureInjection.cpp @@ -22,7 +22,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/WorkerEvents.h" diff --git a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp index 4bc9df90b9..15be1e5a3b 100644 --- a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp +++ b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" diff --git a/fdbserver/workloads/FailoverWithSSLag.cpp b/fdbserver/workloads/FailoverWithSSLag.cpp index 4d91a0c7b2..9cdd892db4 100644 --- a/fdbserver/workloads/FailoverWithSSLag.cpp +++ b/fdbserver/workloads/FailoverWithSSLag.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/FDBSimulationPolicy.h" diff --git a/fdbserver/workloads/HealthMetricsApi.cpp b/fdbserver/workloads/HealthMetricsApi.cpp index 63793304be..ad6e7c068e 100644 --- a/fdbserver/workloads/HealthMetricsApi.cpp +++ b/fdbserver/workloads/HealthMetricsApi.cpp @@ -20,7 +20,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" // NOTE: it might be simpler to test health metrics via something // other than simulation. Testing equivalent to what this workload does can diff --git a/fdbserver/workloads/KillRegion.cpp b/fdbserver/workloads/KillRegion.cpp index 0d2a4b84ea..d45674a595 100644 --- a/fdbserver/workloads/KillRegion.cpp +++ b/fdbserver/workloads/KillRegion.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/core/RecoveryState.h" diff --git a/fdbserver/workloads/LogMetrics.cpp b/fdbserver/workloads/LogMetrics.cpp index 4ce5f37189..0b74110545 100644 --- a/fdbserver/workloads/LogMetrics.cpp +++ b/fdbserver/workloads/LogMetrics.cpp @@ -25,7 +25,7 @@ #include "fdbrpc/simulator.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/SystemData.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/ServerDBInfo.h" diff --git a/fdbserver/workloads/MachineAttrition.cpp b/fdbserver/workloads/MachineAttrition.cpp index 004d2ef7ab..d82dd4cccb 100644 --- a/fdbserver/workloads/MachineAttrition.cpp +++ b/fdbserver/workloads/MachineAttrition.cpp @@ -23,7 +23,7 @@ #include "fdbclient/CoordinationInterface.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" #include "fdbserver/core/FDBSimulatorProcessInfo.h" diff --git a/fdbserver/workloads/Ping.cpp b/fdbserver/workloads/Ping.cpp index 50cbbf5485..68346356a6 100644 --- a/fdbserver/workloads/Ping.cpp +++ b/fdbserver/workloads/Ping.cpp @@ -22,7 +22,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" struct PingWorkloadInterface { diff --git a/fdbserver/workloads/ReadWrite.cpp b/fdbserver/workloads/ReadWrite.cpp index 2199c73e04..83268b7e1e 100644 --- a/fdbserver/workloads/ReadWrite.cpp +++ b/fdbserver/workloads/ReadWrite.cpp @@ -26,7 +26,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" #include "ReadWriteWorkload.h" diff --git a/fdbserver/workloads/RemoveServersSafely.cpp b/fdbserver/workloads/RemoveServersSafely.cpp index a37f04a5dd..478dbd8c3d 100644 --- a/fdbserver/workloads/RemoveServersSafely.cpp +++ b/fdbserver/workloads/RemoveServersSafely.cpp @@ -21,7 +21,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/FDBSimulatorProcessInfo.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" diff --git a/fdbserver/workloads/SkewedReadWrite.cpp b/fdbserver/workloads/SkewedReadWrite.cpp index a82aa4900d..33f2f5f3bf 100644 --- a/fdbserver/workloads/SkewedReadWrite.cpp +++ b/fdbserver/workloads/SkewedReadWrite.cpp @@ -25,7 +25,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" #include "ReadWriteWorkload.h" diff --git a/fdbserver/workloads/SnapTest.cpp b/fdbserver/workloads/SnapTest.cpp index a21e0bbc8a..461d48e12d 100644 --- a/fdbserver/workloads/SnapTest.cpp +++ b/fdbserver/workloads/SnapTest.cpp @@ -26,7 +26,7 @@ #include "fdbclient/SimpleIni.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "BulkSetup.h" #include "fdbserver/tester/workloads.h" diff --git a/fdbserver/workloads/TargetedKill.cpp b/fdbserver/workloads/TargetedKill.cpp index 78ae9427c4..761282d27b 100644 --- a/fdbserver/workloads/TargetedKill.cpp +++ b/fdbserver/workloads/TargetedKill.cpp @@ -24,7 +24,7 @@ #include "fdbrpc/simulator.h" #include "fdbserver/core/MasterInterface.h" #include "fdbclient/SystemData.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/QuietDatabase.h" diff --git a/fdbserver/workloads/Throughput.cpp b/fdbserver/workloads/Throughput.cpp index 8f87a0a924..c7ecf38899 100644 --- a/fdbserver/workloads/Throughput.cpp +++ b/fdbserver/workloads/Throughput.cpp @@ -21,7 +21,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "flow/ActorCollection.h" #include "fdbrpc/Smoother.h" diff --git a/fdbserver/workloads/WorkerErrors.cpp b/fdbserver/workloads/WorkerErrors.cpp index 288574c3b0..e0ac5a5dbd 100644 --- a/fdbserver/workloads/WorkerErrors.cpp +++ b/fdbserver/workloads/WorkerErrors.cpp @@ -22,7 +22,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/ServerDBInfo.h" diff --git a/fdbserver/workloads/WriteBandwidth.cpp b/fdbserver/workloads/WriteBandwidth.cpp index 9045cebda5..c46d7067d2 100644 --- a/fdbserver/workloads/WriteBandwidth.cpp +++ b/fdbserver/workloads/WriteBandwidth.cpp @@ -23,7 +23,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" diff --git a/fdbserver/workloads/WriteTagThrottling.cpp b/fdbserver/workloads/WriteTagThrottling.cpp index 66fa6d5493..bb8a23f50a 100644 --- a/fdbserver/workloads/WriteTagThrottling.cpp +++ b/fdbserver/workloads/WriteTagThrottling.cpp @@ -21,7 +21,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/TagThrottle.h" From 318195abce4a0078ff17e8b1d593fcdf4c4aadbc Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 02:14:44 -0700 Subject: [PATCH 33/39] Fix WorkerInterface clang-tidy warnings --- .../include/fdbserver/core/WorkerInterface.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.h index 53a2814ac9..428ad32126 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.h @@ -78,7 +78,7 @@ struct WorkerInterface { NetworkAddressList addresses() const { return tLog.getEndpoint().addresses; } Optional grpcAddress() const { return clientInterface.grpcAddress; } - WorkerInterface() {} + WorkerInterface() = default; explicit(false) WorkerInterface(const LocalityData& locality) : locality(locality) {} void initEndpoints() { @@ -366,7 +366,7 @@ struct RecruitFromConfigurationRequest { int maxOldLogRouters; ReplyPromise reply; - RecruitFromConfigurationRequest() {} + RecruitFromConfigurationRequest() = default; explicit RecruitFromConfigurationRequest(DatabaseConfiguration const& configuration, bool recruitSeedServers, int maxOldLogRouters) @@ -399,7 +399,7 @@ struct RecruitRemoteFromConfigurationRequest { Optional dbgId; ReplyPromise reply; - RecruitRemoteFromConfigurationRequest() {} + RecruitRemoteFromConfigurationRequest() = default; RecruitRemoteFromConfigurationRequest(DatabaseConfiguration const& configuration, Optional const& dcId, int logRouterCount, @@ -545,7 +545,7 @@ struct TLogRejoinRequest { TLogInterface myInterface; ReplyPromise reply; - TLogRejoinRequest() {} + TLogRejoinRequest() = default; explicit TLogRejoinRequest(const TLogInterface& interf) : myInterface(interf) {} template void serialize(Ar& ar) { @@ -586,7 +586,7 @@ struct GetEncryptionAtRestModeRequest { UID tlogId; ReplyPromise reply; - GetEncryptionAtRestModeRequest() {} + GetEncryptionAtRestModeRequest() = default; explicit(false) GetEncryptionAtRestModeRequest(UID tId) : tlogId(tId) {} template @@ -841,7 +841,7 @@ struct InitializeDataDistributorRequest { UID reqId; ReplyPromise reply; - InitializeDataDistributorRequest() {} + InitializeDataDistributorRequest() = default; explicit InitializeDataDistributorRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -854,7 +854,7 @@ struct InitializeRatekeeperRequest { UID reqId; ReplyPromise reply; - InitializeRatekeeperRequest() {} + InitializeRatekeeperRequest() = default; explicit InitializeRatekeeperRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -867,7 +867,7 @@ struct InitializeConsistencyScanRequest { UID reqId; ReplyPromise reply; - InitializeConsistencyScanRequest() {} + InitializeConsistencyScanRequest() = default; explicit InitializeConsistencyScanRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -1039,7 +1039,7 @@ struct DebugEntryRef { StringRef context; Version version; MutationRef mutation; - DebugEntryRef() {} + DebugEntryRef() = default; DebugEntryRef(const char* c, Version v, MutationRef const& m) : time(now()), address(g_network->getLocalAddress()), context((const uint8_t*)c, strlen(c)), version(v), mutation(m) {} From 1ae8e7f2383bbd79aa860940507db6ec0754055d Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Fri, 17 Jul 2026 16:36:48 -0400 Subject: [PATCH 34/39] ci: new github action to minimize outdated AWS Codebuild comments (#13721) They can clutter up the PR page, so hide (aka minimize) older comments for the same AWS Codebuild project build/test report, as "OUTDATED". --- .github/workflows/codebuild-cleanup.yml | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .github/workflows/codebuild-cleanup.yml diff --git a/.github/workflows/codebuild-cleanup.yml b/.github/workflows/codebuild-cleanup.yml new file mode 100644 index 0000000000..e3299d6a2d --- /dev/null +++ b/.github/workflows/codebuild-cleanup.yml @@ -0,0 +1,125 @@ +name: CodeBuild Comment Cleanup + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + cleanup: + if: > + github.event.issue.pull_request && + github.event.comment.user.login == 'foundationdb-ci' + runs-on: ubuntu-latest + steps: + - name: Minimize Outdated Comments + uses: actions/github-script@v7 + with: + script: | + // Example header: ### Result of foundationdb-pr-clang on Linux RHEL 9 + // Check if header is at beginning of trimmed body + const commentBody = context.payload.comment.body.trimStart(); + const match = commentBody.match(/^### Result of (.*) on (.*)$/m); + if (!match || commentBody.indexOf(match[0]) !== 0) { + console.log("Comment does not start with the expected header format. Skipping."); + return; + } + const header = match[0].trim(); + + // Get PR info to find the head SHA + const { data: pullRequest } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + const headSha = pullRequest.head.sha; + console.log(`Current PR head SHA: ${headSha}`); + + const allOldComments = []; + const commentsToMinimize = []; + 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, + } + )) { + for (const comment of comments) { + if ( + comment.user.login === 'foundationdb-ci' && + comment.id !== 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. + const m = comment.body.match(/^\* Commit ID: ([a-f0-9]+)/m); + if (m && m[1] === headSha) { + oldCommentAtHead = comment; + } + } + } + } + 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); + for (const old of allOldComments) { + if (old.id !== oldCommentAtHead.id) { + commentsToMinimize.push(old); + } + } + } else { + commentsToMinimize.push(...allOldComments); + } + console.log(`Found ${commentsToMinimize.length} comments to minimize.`); + + 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); + continue; + } + console.log(`Minimizing comment ${comment.id}`); + const minimizeMutation = ` + mutation($id: ID!, $classifier: ReportedContentClassifiers!) { + minimizeComment(input: { subjectId: $id, classifier: $classifier }) { + minimizedComment { + isMinimized + } + } + } + `; + try { + await github.graphql(minimizeMutation, { + id: comment.node_id, + classifier: 'OUTDATED' + }); + } catch (error) { + console.error(`Failed to minimize comment ${comment.id}:`, error); + } + } From 15df0ae8fc90c2ab064e74c2e1c9c7722becbc09 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Fri, 17 Jul 2026 19:49:29 -0700 Subject: [PATCH 35/39] Remove unused FoundationDB helpers --- design/AI-generated/FDB_NETWORK_PROTOCOL.md | 3 - fdbclient/FileBackupAgent.cpp | 81 -------------- fdbclient/NativeAPI.actor.cpp | 8 -- fdbclient/RestoreInterface.cpp | 56 ---------- fdbclient/RestoreInterface.h | 102 ------------------ fdbrpc/dsltest.actor.cpp | 19 ---- .../datadistributor/DDRelocationQueue.cpp | 5 - fdbserver/datadistributor/DDShardTracker.cpp | 38 ------- .../datadistributor/DataDistribution.cpp | 35 ------ fdbserver/fdbserver.cpp | 62 ----------- fdbserver/include/fdbserver/NetworkTest.h | 24 ----- fdbserver/kvstore/FDBExecHelper.cpp | 15 --- fdbserver/kvstore/VersionedBTree.actor.cpp | 4 - fdbserver/networktest.cpp | 79 -------------- fdbserver/storageserver/storageserver.cpp | 41 ------- fdbserver/workloads/pubsub.cpp | 3 - 16 files changed, 575 deletions(-) delete mode 100644 fdbclient/RestoreInterface.cpp delete mode 100644 fdbclient/RestoreInterface.h diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md index 31e0be5fcd..213dbae231 100644 --- a/design/AI-generated/FDB_NETWORK_PROTOCOL.md +++ b/design/AI-generated/FDB_NETWORK_PROTOCOL.md @@ -1543,9 +1543,6 @@ All follow the pattern: fields describing the role configuration + `ReplyPromise ### NetworkTestRequest `Key key`, `uint32_t replySize`, `reply` → **NetworkTestReply** {`Value value`}. -### NetworkTestStreamingRequest -`reply` (stream) → **NetworkTestStreamingReply** {`Optional acknowledgeToken`, `uint16_t sequence`, `int index`}. - --- ## 15. Client Worker / Debug / Process Protocols diff --git a/fdbclient/FileBackupAgent.cpp b/fdbclient/FileBackupAgent.cpp index 858a1608e8..f6bbac26ad 100644 --- a/fdbclient/FileBackupAgent.cpp +++ b/fdbclient/FileBackupAgent.cpp @@ -41,7 +41,6 @@ #include "fdbclient/ManagementAPI.h" #include "fdbclient/RangeLock.h" #include "PartitionedLogIterator.h" -#include "RestoreInterface.h" #include "fdbclient/Status.h" #include "fdbclient/SystemData.h" #include "fdbclient/TaskBucket.h" @@ -171,13 +170,6 @@ Future verifyBulkDumpDatasetCompleteness(Reference bc, s Optional fileBackupAgentProxy = Optional(); -#define SevFRTestInfo SevVerbose -// #define SevFRTestInfo SevInfo - -static std::string boolToYesOrNo(bool val) { - return val ? std::string("Yes") : std::string("No"); -} - static std::string versionToString(Optional version) { if (version.present()) return std::to_string(version.get()); @@ -8430,76 +8422,3 @@ Future FileBackupAgent::waitBackup(Database cx, Future FileBackupAgent::changePause(Database db, bool pause) { return FileBackupAgentImpl::changePause(this, db, pause); } - -// Fast Restore addPrefix test helper functions -static std::pair insideValidRange(KeyValueRef kv, - Standalone> restoreRanges, - Standalone> backupRanges) { - bool insideRestoreRange = false; - bool insideBackupRange = false; - for (auto& range : restoreRanges) { - TraceEvent(SevFRTestInfo, "InsideValidRestoreRange") - .detail("Key", kv.key) - .detail("Range", range) - .detail("Inside", (kv.key >= range.begin && kv.key < range.end)); - if (kv.key >= range.begin && kv.key < range.end) { - insideRestoreRange = true; - break; - } - } - for (auto& range : backupRanges) { - TraceEvent(SevFRTestInfo, "InsideValidBackupRange") - .detail("Key", kv.key) - .detail("Range", range) - .detail("Inside", (kv.key >= range.begin && kv.key < range.end)); - if (kv.key >= range.begin && kv.key < range.end) { - insideBackupRange = true; - break; - } - } - return std::make_pair(insideBackupRange, insideRestoreRange); -} - -// Write [begin, end) in kvs to DB -static Future writeKVs(Database cx, Standalone> kvs, int begin, int end) { - co_await runRYWTransaction(cx, [=](Reference tr) -> Future { - tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr->setOption(FDBTransactionOptions::LOCK_AWARE); - int index = begin; - while (index < end) { - TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKV") - .detail("Index", index) - .detail("KVs", kvs.size()) - .detail("Key", kvs[index].key) - .detail("Value", kvs[index].value); - tr->set(kvs[index].key, kvs[index].value); - ++index; - } - return Void(); - }); - - // Sanity check data has been written to DB - ReadYourWritesTransaction tr(cx); - while (true) { - Error err; - try { - tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); - KeyRef k1 = kvs[begin].key; - KeyRef k2 = end < kvs.size() ? kvs[end].key : allKeys.end; - TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKVReadBack") - .detail("Range", KeyRangeRef(k1, k2)) - .detail("Begin", begin) - .detail("End", end); - RangeResult readKVs = co_await tr.getRange(KeyRangeRef(k1, k2), CLIENT_KNOBS->TOO_MANY); - ASSERT(!readKVs.empty() || begin == end); - break; - } catch (Error& e) { - err = e; - } - TraceEvent("TransformDatabaseContentsWriteKVReadBackError").error(err); - co_await tr.onError(err); - } - - TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKVDone").detail("Begin", begin).detail("End", end); -} diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 46706d74b5..4e21f15d5f 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -300,14 +300,6 @@ int64_t extractIntOption(Optional value, int64_t minValue, int64_t ma return passed; } -uint64_t extractHexOption(StringRef value) { - char* end; - uint64_t id = strtoull(value.toString().c_str(), &end, 16); - if (*end) - throw invalid_option_value(); - return id; -} - void DatabaseContext::setOption(FDBDatabaseOptions::Option option, Optional value) { int defaultFor = FDBDatabaseOptions::optionInfo.getMustExist(option).defaultFor; if (defaultFor >= 0) { diff --git a/fdbclient/RestoreInterface.cpp b/fdbclient/RestoreInterface.cpp deleted file mode 100644 index 96d81225bb..0000000000 --- a/fdbclient/RestoreInterface.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * RestoreInterface.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "RestoreInterface.h" -#include "flow/serialize.h" - -const KeyRef restoreRequestDoneKey = "\xff\x02/restoreRequestDone"_sr; -const KeyRef restoreRequestTriggerKey = "\xff\x02/restoreRequestTrigger"_sr; -const KeyRangeRef restoreRequestKeys("\xff\x02/restoreRequests/"_sr, "\xff\x02/restoreRequests0"_sr); - -// Encode and decode restore request value -Value restoreRequestTriggerValue(UID randomID, int numRequests) { - BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestTriggerValue())); - wr << numRequests; - wr << randomID; - return wr.toValue(); -} - -int decodeRestoreRequestTriggerValue(ValueRef const& value) { - int s; - UID randomID; - BinaryReader reader(value, IncludeVersion()); - reader >> s; - reader >> randomID; - return s; -} - -Key restoreRequestKeyFor(int index) { - BinaryWriter wr(Unversioned()); - wr.serializeBytes(restoreRequestKeys.begin); - wr << index; - return wr.toValue(); -} - -Value restoreRequestValue(RestoreRequest const& request) { - BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestValue())); - wr << request; - return wr.toValue(); -} diff --git a/fdbclient/RestoreInterface.h b/fdbclient/RestoreInterface.h deleted file mode 100644 index bd0c6ff2f3..0000000000 --- a/fdbclient/RestoreInterface.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * RestoreInterface.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "fdbclient/FDBTypes.h" -#include "fdbrpc/fdbrpc.h" - -struct RestoreCommonReply { - constexpr static FileIdentifier file_identifier = 5808787; - UID id; // unique ID of the server who sends the reply - bool isDuplicated; - - RestoreCommonReply() = default; - explicit RestoreCommonReply(UID id, bool isDuplicated = false) : id(id), isDuplicated(isDuplicated) {} - - std::string toString() const { - std::stringstream ss; - ss << "ServerNodeID:" << id.toString() << " isDuplicated:" << isDuplicated; - return ss.str(); - } - - template - void serialize(Ar& ar) { - serializer(ar, id, isDuplicated); - } -}; - -struct RestoreRequest { - constexpr static FileIdentifier file_identifier = 16035338; - - int index; - Key tagName; - Key url; - Optional proxy; - Version targetVersion; - KeyRange range; - UID randomUid; - - // Every key in backup will first removePrefix and then addPrefix; - // Simulation testing does not cover when both addPrefix and removePrefix exist yet. - Key addPrefix; - Key removePrefix; - - ReplyPromise reply; - - RestoreRequest() = default; - explicit RestoreRequest(const int index, - const Key& tagName, - const Key& url, - const Optional& proxy, - Version targetVersion, - const KeyRange& range, - const UID& randomUid, - Key& addPrefix, - Key removePrefix) - : index(index), tagName(tagName), url(url), proxy(proxy), targetVersion(targetVersion), range(range), - randomUid(randomUid), addPrefix(addPrefix), removePrefix(removePrefix) {} - - // To change this serialization, ProtocolVersion::RestoreRequestValue must be updated, and downgrades need to be - // considered - template - void serialize(Ar& ar) { - serializer(ar, index, tagName, url, proxy, targetVersion, range, randomUid, addPrefix, removePrefix, reply); - } - - std::string toString() const { - std::stringstream ss; - ss << "index:" << std::to_string(index) << " tagName:" << tagName.contents().toString() - << " url:" << url.contents().toString() << " proxy:" << (proxy.present() ? proxy.get() : "") - << " targetVersion:" << std::to_string(targetVersion) << " range:" << range.toString() - << " randomUid:" << randomUid.toString() << " addPrefix:" << addPrefix.toString() - << " removePrefix:" << removePrefix.toString(); - return ss.str(); - } -}; - -extern const KeyRef restoreRequestDoneKey; -extern const KeyRef restoreRequestTriggerKey; -extern const KeyRangeRef restoreRequestKeys; - -Value restoreRequestTriggerValue(UID randomID, int numRequests); -int decodeRequestRequestTriggerValue(ValueRef const&); -Key restoreRequestKeyFor(int index); -Value restoreRequestValue(RestoreRequest const&); diff --git a/fdbrpc/dsltest.actor.cpp b/fdbrpc/dsltest.actor.cpp index 7ebf804f2c..a88195fe42 100644 --- a/fdbrpc/dsltest.actor.cpp +++ b/fdbrpc/dsltest.actor.cpp @@ -1103,25 +1103,6 @@ ACTOR [[flow_allow_discard]] Future cycleTime(int nodes, int times) { return Void(); } -void sleeptest() { -#ifdef __linux__ - int times[] = { 0, 100, 500, 1000, 5000, 100000, 500000, 1000000 }; - for (int j = 0; j < 8; j++) { - double b = timer(); - int n = std::min(100, 4000000 / (1 + times[j])); - for (int i = 0; i < n; i++) { - timespec ts; - ts.tv_sec = times[j] / 1000000; - ts.tv_nsec = (times[j] % 1000000) * 1000; - clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, nullptr); - // nanosleep(&ts, nullptr); - } - double t = timer() - b; - printf("Sleep test (%dus x %d): %0.1f\n", times[j], n, double(t) / n * 1e6); - } -#endif -} - void asyncMapTest() { Future c; diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index cfaed3b488..cad3bb533a 100644 --- a/fdbserver/datadistributor/DDRelocationQueue.cpp +++ b/fdbserver/datadistributor/DDRelocationQueue.cpp @@ -49,11 +49,6 @@ using ITeamRef = Reference; using SrcDestTeamPair = std::pair; -inline bool isDataMovementForDiskBalancing(DataMovementReason reason) { - return reason == DataMovementReason::REBALANCE_UNDERUTILIZED_TEAM || - reason == DataMovementReason::REBALANCE_OVERUTILIZED_TEAM; -} - inline bool isDataMovementForReadBalancing(DataMovementReason reason) { return reason == DataMovementReason::REBALANCE_READ_OVERUTIL_TEAM || reason == DataMovementReason::REBALANCE_READ_UNDERUTIL_TEAM; diff --git a/fdbserver/datadistributor/DDShardTracker.cpp b/fdbserver/datadistributor/DDShardTracker.cpp index 284f314a55..e74a513c00 100644 --- a/fdbserver/datadistributor/DDShardTracker.cpp +++ b/fdbserver/datadistributor/DDShardTracker.cpp @@ -415,11 +415,6 @@ std::string describeSplit(KeyRange keys, Standalone>& splitKey return s; } -void traceSplit(KeyRange keys, Standalone>& splitKeys) { - auto s = describeSplit(keys, splitKeys); - TraceEvent(SevInfo, "ExecutingShardSplit").detail("AtKeys", s); -} - void executeShardSplit(DataDistributionTracker* self, KeyRange keys, Standalone> splitKeys, @@ -465,39 +460,6 @@ void executeShardSplit(DataDistributionTracker* self, self->actors.add(changeSizes(self, keys, shardSize->get().get().metrics.bytes, "ShardSplit")); } -struct RangeToSplit { - RangeMap, ShardTrackedData, KeyRangeRef>::iterator shard; - Standalone> faultLines; - - RangeToSplit(RangeMap, ShardTrackedData, KeyRangeRef>::iterator shard, - Standalone> faultLines) - : shard(shard), faultLines(faultLines) {} -}; - -bool faultLinesMatch(std::vector& ranges, std::vector>& expectedFaultLines) { - if (ranges.size() != expectedFaultLines.size()) { - return false; - } - - for (auto& range : ranges) { - KeyRangeRef keys = KeyRangeRef(range.shard->begin(), range.shard->end()); - traceSplit(keys, range.faultLines); - } - - for (int r = 0; r < ranges.size(); r++) { - if (ranges[r].faultLines.size() != expectedFaultLines[r].size()) { - return false; - } - for (int fl = 0; fl < ranges[r].faultLines.size(); fl++) { - if (ranges[r].faultLines[fl] != expectedFaultLines[r][fl]) { - return false; - } - } - } - - return true; -} - Future shardSplitter(DataDistributionTracker* self, KeyRange keys, Reference>> shardSize, diff --git a/fdbserver/datadistributor/DataDistribution.cpp b/fdbserver/datadistributor/DataDistribution.cpp index a6455415c9..d657a1a439 100644 --- a/fdbserver/datadistributor/DataDistribution.cpp +++ b/fdbserver/datadistributor/DataDistribution.cpp @@ -420,41 +420,6 @@ Future monitorBackupPartitionRequired(Database cx, KeyRangeMap debugCheckCoalescing(Database cx) { - Transaction tr(cx); - while (true) { - Error err; - try { - RangeResult serverList = co_await tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); - ASSERT(!serverList.more && serverList.size() < CLIENT_KNOBS->TOO_MANY); - - int i{ 0 }; - for (i = 0; i < serverList.size(); i++) { - UID id = decodeServerListValue(serverList[i].value).id(); - RangeResult ranges = co_await krmGetRanges(&tr, serverKeysPrefixFor(id), allKeys); - ASSERT(ranges.end()[-1].key == allKeys.end); - - for (int j = 0; j < ranges.size() - 2; j++) { - if (ranges[j].value == ranges[j + 1].value) { - TraceEvent(SevError, "UncoalescedValues", id) - .detail("Key1", ranges[j].key) - .detail("Key2", ranges[j + 1].key) - .detail("Value", ranges[j].value); - } - } - } - - TraceEvent("DoneCheckingCoalescing").log(); - co_return; - } catch (Error& e) { - err = e; - } - co_await tr.onError(err); - } -} - struct DataDistributor; void runAuditStorage( Reference self, diff --git a/fdbserver/fdbserver.cpp b/fdbserver/fdbserver.cpp index 70a9689740..ae99a89bc3 100644 --- a/fdbserver/fdbserver.cpp +++ b/fdbserver/fdbserver.cpp @@ -410,68 +410,6 @@ Future metricsReport() { } } -void testSerializationSpeed() { - double tstart; - double build = 0, serialize = 0, deserialize = 0, copy = 0, deallocate = 0; - double bytes = 0; - double testBegin = timer(); - for (int a = 0; a < 10000; a++) { - { - tstart = timer(); - - Arena batchArena; - VectorRef batch; - batch.resize(batchArena, 1000); - for (int t = 0; t < batch.size(); t++) { - CommitTransactionRef& tr = batch[t]; - tr.read_snapshot = 0; - for (int i = 0; i < 2; i++) - tr.mutations.push_back_deep(batchArena, - MutationRef(MutationRef::SetValue, "KeyABCDE"_sr, "SomeValu"_sr)); - tr.mutations.push_back_deep(batchArena, - MutationRef(MutationRef::ClearRange, "BeginKey"_sr, "EndKeyAB"_sr)); - } - - build += timer() - tstart; - - tstart = timer(); - - BinaryWriter wr(IncludeVersion()); - wr << batch; - - bytes += wr.getLength(); - - serialize += timer() - tstart; - - for (int i = 0; i < 1; i++) { - tstart = timer(); - Arena arena; - StringRef data(arena, StringRef((const uint8_t*)wr.getData(), wr.getLength())); - copy += timer() - tstart; - - tstart = timer(); - ArenaReader rd(arena, data, IncludeVersion()); - VectorRef batch2; - rd >> arena >> batch2; - - deserialize += timer() - tstart; - } - - tstart = timer(); - } - deallocate += timer() - tstart; - } - double elapsed = (timer() - testBegin); - printf("Test speed: %0.1f MB/sec (%0.0f/sec)\n", bytes / 1e6 / elapsed, 1000000 / elapsed); - printf(" Build: %0.1f MB/sec\n", bytes / 1e6 / build); - printf(" Serialize: %0.1f MB/sec\n", bytes / 1e6 / serialize); - printf(" Copy: %0.1f MB/sec\n", bytes / 1e6 / copy); - printf(" Deserialize: %0.1f MB/sec\n", bytes / 1e6 / deserialize); - printf(" Deallocate: %0.1f MB/sec\n", bytes / 1e6 / deallocate); - printf(" Bytes: %0.1f MB\n", bytes / 1e6); - printf("\n"); -} - void memoryTest(); void skipListTest(); diff --git a/fdbserver/include/fdbserver/NetworkTest.h b/fdbserver/include/fdbserver/NetworkTest.h index 05ec966d69..ae51501c22 100644 --- a/fdbserver/include/fdbserver/NetworkTest.h +++ b/fdbserver/include/fdbserver/NetworkTest.h @@ -28,7 +28,6 @@ struct NetworkTestInterface { RequestStream test; - RequestStream testStream; NetworkTestInterface() = default; explicit NetworkTestInterface(NetworkAddress remote); explicit NetworkTestInterface(INetwork* local); @@ -58,29 +57,6 @@ struct NetworkTestRequest { } }; -struct NetworkTestStreamingReply : ReplyPromiseStreamReply { - constexpr static FileIdentifier file_identifier = 3726830; - - int index = 0; - NetworkTestStreamingReply() = default; - explicit NetworkTestStreamingReply(int index) : index(index) {} - size_t expectedSize() const { return 4e6; /*sizeof(*this);*/ } - - template - void serialize(Ar& ar) { - serializer(ar, ReplyPromiseStreamReply::acknowledgeToken, ReplyPromiseStreamReply::sequence, index); - } -}; - -struct NetworkTestStreamingRequest { - constexpr static FileIdentifier file_identifier = 2794452; - ReplyPromiseStream reply; - template - void serialize(Ar& ar) { - serializer(ar, reply); - } -}; - Future networkTestServer(); Future networkTestClient(std::string const& testServers); diff --git a/fdbserver/kvstore/FDBExecHelper.cpp b/fdbserver/kvstore/FDBExecHelper.cpp index dd4b00ab83..c24b3ae6f3 100644 --- a/fdbserver/kvstore/FDBExecHelper.cpp +++ b/fdbserver/kvstore/FDBExecHelper.cpp @@ -38,8 +38,6 @@ #include "flow/flow.h" #include "flow/genericactors.actor.h" #include "flow/network.h" -#include "fdbrpc/simulator.h" -#include "fdbrpc/SimulatorProcessInfo.h" #include "fdbclient/IClosable.h" #include "fdbclient/versions.h" #include "fdbserver/CoroFlow.h" @@ -105,19 +103,6 @@ void ExecCmdValueString::dbgPrint() const { return; } -Future destroyChildProcess(Uncancellable, - Future parentSSClosed, - ISimulator::ProcessInfo* childInfo, - std::string message) { - // This code path should be bug free - co_await parentSSClosed; - TraceEvent(SevDebug, message.c_str()).log(); - // This one is root cause for most failures, make sure it's okay to destroy - g_simulator->destroyProcess(childInfo); - // Explicitly reset the connection with the child process in case re-spawn very quickly - FlowTransport::transport().resetConnection(childInfo->address); -} - #if defined(_WIN32) || defined(__APPLE__) || defined(__INTEL_COMPILER) Future spawnProcess(std::string binPath, std::vector paramList, diff --git a/fdbserver/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..0c194b5e87 100644 --- a/fdbserver/kvstore/VersionedBTree.actor.cpp +++ b/fdbserver/kvstore/VersionedBTree.actor.cpp @@ -1365,10 +1365,6 @@ public: } }; -int nextPowerOf2(uint32_t x) { - return 1 << (32 - clz(x - 1)); -} - struct RedwoodMetrics { constexpr static unsigned int btreeLevels = 5; static int maxRecordCount; diff --git a/fdbserver/networktest.cpp b/fdbserver/networktest.cpp index 5cc52893a5..d8f6473cbc 100644 --- a/fdbserver/networktest.cpp +++ b/fdbserver/networktest.cpp @@ -108,60 +108,6 @@ Future networkTestServer() { co_await server.run(); } -class NetworkTestStreamingServer { -public: - NetworkTestStreamingServer() : interf(g_network) {} - - Future run() { co_await race(requests(), logging()); } - -private: - Future requests() { - while (true) { - try { - NetworkTestStreamingRequest req = co_await interf.testStream.getFuture(); - LatencyStats::sample sample = latency.tick(); - for (int i = 0; i < 100; ++i) { - co_await req.reply.onReady(); - req.reply.send(NetworkTestStreamingReply{ i }); - } - req.reply.sendError(end_of_stream()); - latency.tock(sample); - sent++; - } catch (Error& e) { - if (e.code() != error_code_operation_obsolete) { - throw e; - } - } - } - } - - Future logging() { - double lastTime = now(); - - while (true) { - co_await delay(1.0); - auto spd = sent / (now() - lastTime); - if (FLOW_KNOBS->NETWORK_TEST_SCRIPT_MODE) { - fprintf(stderr, "%f\t%.3f\t%.3f\n", spd, latency.mean() * 1e6, latency.stddev() * 1e6); - } else { - fprintf(stderr, "responses per second: %f (%f us)\n", spd, latency.mean() * 1e6); - } - latency.reset(); - lastTime = now(); - sent = 0; - } - } - - NetworkTestInterface interf; - int sent = 0; - LatencyStats latency; -}; - -Future networkTestStreamingServer() { - NetworkTestStreamingServer server; - co_await server.run(); -} - static bool moreRequestsPending(int count) { if (count == -1) { return false; @@ -193,31 +139,6 @@ Future testClient(std::vector interfs, int* sent, in } } -Future testClientStream(std::vector interfs, - int* sent, - int* completed, - LatencyStats* latency) { - while (moreRequestsPending(*sent)) { - (*sent)++; - LatencyStats::sample sample = latency->tick(); - ReplyPromiseStream stream = - interfs[deterministicRandom()->randomInt(0, interfs.size())].testStream.getReplyStream( - NetworkTestStreamingRequest{}); - int j = 0; - try { - while (true) { - NetworkTestStreamingReply rep = co_await stream.getFuture(); - ASSERT(rep.index == j++); - } - } catch (Error& e) { - ASSERT(e.code() == error_code_end_of_stream || e.code() == error_code_connection_failed || - e.code() == error_code_request_maybe_delivered); - } - latency->tock(sample); - (*completed)++; - } -} - Future logger(int* sent, int* completed, LatencyStats* latency) { double lastTime = now(); int logged = 0; diff --git a/fdbserver/storageserver/storageserver.cpp b/fdbserver/storageserver/storageserver.cpp index b7be1522e1..7731096f91 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -1985,16 +1985,6 @@ Future waitForVersionActor(StorageServer* data, Version version, SpanCo } } -// If the latest commit version that mutated the shard(s) being served by the specified storage -// server is below the client specified read version then do a read at the latest commit version -// of the storage server. -Version getRealReadVersion(VersionVector& ssLatestCommitVersions, Tag& tag, Version specifiedReadVersion) { - Version realReadVersion = - ssLatestCommitVersions.hasVersion(tag) ? ssLatestCommitVersions.getVersion(tag) : specifiedReadVersion; - ASSERT(realReadVersion <= specifiedReadVersion); - return realReadVersion; -} - // Find the latest commit version of the given tag. Version getLatestCommitVersion(VersionVector& ssLatestCommitVersions, Tag& tag) { Version commitVersion = @@ -4267,23 +4257,6 @@ Future auditStorageServerShardQ(StorageServer* data, AuditStorageRequest r * */ -// Helper: Issue a GetKeyValues request for a given range and return the future -static Future> issueGetKeyValuesRequest(StorageServer* data, - KeyRange range, - Version version, - int limit, - int limitBytes) { - GetKeyValuesRequest req; - req.begin = firstGreaterOrEqual(range.begin); - req.end = firstGreaterOrEqual(range.end); - req.limit = limit; - req.limitBytes = limitBytes; - req.version = version; - req.tags = TagSet(); - data->actors.add(getKeyValuesQ(data, req)); - return errorOr(req.reply.getFuture()); -} - // Helper: Read both source and restored data for a given range // // Restored data is stored at validateRestoreLogKeys (\xff\x02/rlog/) in system key space. @@ -6250,20 +6223,6 @@ bool changeDurableVersion(StorageServer* data, Version desiredDurableVersion) { return nextDurableVersion == desiredDurableVersion; } -Optional clipMutation(MutationRef const& m, KeyRangeRef range) { - if (isSingleKeyMutation((MutationRef::Type)m.type)) { - if (range.contains(m.param1)) - return m; - } else if (m.type == MutationRef::ClearRange) { - KeyRangeRef i = range & KeyRangeRef(m.param1, m.param2); - if (!i.empty()) - return MutationRef((MutationRef::Type)m.type, i.begin, i.end); - } else { - ASSERT(false); - } - return Optional(); -} - bool convertAtomicOp(MutationRef& m, StorageServer::VersionedData const& data, UpdateEagerReadInfo* eager, Arena& ar) { // After this function call, m should be copied into an arena immediately (before modifying data, shards, or eager) if (m.type != MutationRef::ClearRange && m.type != MutationRef::SetValue) { diff --git a/fdbserver/workloads/pubsub.cpp b/fdbserver/workloads/pubsub.cpp index a21da7382f..b52cd4407e 100644 --- a/fdbserver/workloads/pubsub.cpp +++ b/fdbserver/workloads/pubsub.cpp @@ -52,9 +52,6 @@ Key keyForInboxCacheByIDPrefix(uint64_t inbox) { Key keyForInboxCacheByID(uint64_t inbox, uint64_t messageId) { return StringRef(format("i/%016llx/cid/%016llx", inbox, messageId)); } -Key keyForInboxCacheByFeedPrefix(uint64_t inbox) { - return StringRef(format("i/%016llx/cf/", inbox)); -} Key keyForInboxCacheByFeed(uint64_t inbox, uint64_t feed) { return StringRef(format("i/%016llx/cf/%016llx", inbox, feed)); } From 501a11438ba0a503a5292d88f4af1cfeb944eb51 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 00:48:49 -0700 Subject: [PATCH 36/39] Fix Redwood commit cancellation lifetime crash --- fdbrpc/FlowTests.actor.cpp | 12 ++++++++++++ fdbrpc/include/fdbrpc/AsyncFileNonDurable.h | 2 +- flow/CoroTests.cpp | 6 ++++++ flow/include/flow/CoroUtils.h | 12 +++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/fdbrpc/FlowTests.actor.cpp b/fdbrpc/FlowTests.actor.cpp index bb5565fde6..a0508e031f 100644 --- a/fdbrpc/FlowTests.actor.cpp +++ b/fdbrpc/FlowTests.actor.cpp @@ -30,6 +30,7 @@ #include "flow/IThreadPool.h" #include "flow/WriteOnlySet.h" #include "fdbrpc/fdbrpc.h" +#include "fdbrpc/AsyncFileNonDurable.h" #include "flow/IAsyncFile.h" #include "flow/TLSConfig.h" #include "fdbrpc/grpc/AsyncTaskExecutor.h" @@ -340,6 +341,17 @@ TEST_CASE("/flow/flow/cancel1") { return Void(); } +TEST_CASE("/fdbrpc/asyncFileNonDurable/sendErrorOnShutdownCancellation") { + Promise input; + Future wrapped = sendErrorOnShutdown(input.getFuture()); + ASSERT(input.getFutureReferenceCount() > 0); + wrapped.cancel(); + ASSERT(wrapped.isReady() && wrapped.isError() && wrapped.getError().code() == error_code_actor_cancelled); + ASSERT_EQ(input.getFutureReferenceCount(), 0); + input.send(Void()); + return Void(); +} + ACTOR static Future noteCancel(int* cancelled) { *cancelled = 0; try { diff --git a/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h index 41c066a047..d31627ade3 100644 --- a/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h +++ b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h @@ -39,7 +39,7 @@ extern Future waitShutdownSignal(); template Future sendErrorOnShutdown(Future in, bool assertOnCancel = false) { try { - auto res = co_await race(waitShutdownSignal(), in); + auto res = co_await race(waitShutdownSignal(), std::move(in)); if (res.index() == 0) { throw io_error().asInjectedFault(); } else { diff --git a/flow/CoroTests.cpp b/flow/CoroTests.cpp index 06c6b3404d..b3bde66e88 100644 --- a/flow/CoroTests.cpp +++ b/flow/CoroTests.cpp @@ -2878,6 +2878,8 @@ TEST_CASE("/flow/coro/raceSuccess") { auto result = co_await raced; ASSERT_EQ(result.index(), 1); ASSERT_EQ(std::get<1>(result), "winner"); + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); co_return; } @@ -2903,6 +2905,8 @@ TEST_CASE("/flow/coro/raceError") { } catch (Error const& e) { ASSERT_EQ(e.code(), error_code_io_error); } + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); co_return; } @@ -2914,6 +2918,8 @@ TEST_CASE("/flow/coro/raceCancel") { ASSERT(raced.isReady()); ASSERT(raced.isError()); ASSERT_EQ(raced.getError().code(), error_code_actor_cancelled); + ASSERT_EQ(intPromise.getFutureReferenceCount(), 0); + ASSERT_EQ(stringPromise.getFutureReferenceCount(), 0); intPromise.send(1); stringPromise.send("late"); ASSERT_EQ(raced.getError().code(), error_code_actor_cancelled); diff --git a/flow/include/flow/CoroUtils.h b/flow/include/flow/CoroUtils.h index 904e299155..8014899bcb 100644 --- a/flow/include/flow/CoroUtils.h +++ b/flow/include/flow/CoroUtils.h @@ -348,14 +348,21 @@ struct RaceImplActor final : Actor, template void finish(T&& value) { + Result result(std::in_place_index, std::forward(value)); this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); - this->SAV::sendAndDelPromiseRef(Result(std::in_place_index, std::forward(value))); + { + auto futuresToRelease = std::move(futures); + } + this->SAV::sendAndDelPromiseRef(std::move(result)); } void fail(Error e) { this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(e); } @@ -364,6 +371,9 @@ struct RaceImplActor final : Actor, this->actor_wait_state = ACTOR_WAIT_STATE_CANCELLED; if (actorWaitStateIsWaiting(waitState)) { RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(actor_cancelled()); } } From 26b60d2de7c8ec9621c6a091a7cd9359b53b82a6 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 08:34:53 -0700 Subject: [PATCH 37/39] Report remote storage in aggregate health metrics --- fdbserver/ratekeeper/Ratekeeper.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/fdbserver/ratekeeper/Ratekeeper.cpp b/fdbserver/ratekeeper/Ratekeeper.cpp index 4e0e0cd995..ba733c7b38 100644 --- a/fdbserver/ratekeeper/Ratekeeper.cpp +++ b/fdbserver/ratekeeper/Ratekeeper.cpp @@ -662,7 +662,18 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { // ratio for (auto i = storageQueueInfo.begin(); i != storageQueueInfo.end(); ++i) { auto const& ss = i->value; - if (!ss.valid || !ss.acceptingRequests || (remoteDC.present() && ss.locality.dcId() == remoteDC)) { + if (!ss.valid || !ss.acceptingRequests) { + continue; + } + + int64_t storageQueue = ss.getStorageQueueBytes(); + worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); + + int64_t storageDurabilityLag = ss.getDurabilityLag(); + worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); + + // Remote storage is reported in health metrics but is not used to rate-limit the primary region. + if (remoteDC.present() && ss.locality.dcId() == remoteDC) { continue; } ++sscount; @@ -697,12 +708,6 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { } } - int64_t storageQueue = ss.getStorageQueueBytes(); - worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); - - int64_t storageDurabilityLag = ss.getDurabilityLag(); - worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); - storageDurabilityLagReverseIndex.insert(std::make_pair(-1 * storageDurabilityLag, &ss)); double targetRateRatio = std::min((storageQueue - targetBytes + springBytes) / (double)springBytes, 2.0); From 48d79db157957eb1603e22e595828d00d460b6df Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 08:51:43 -0700 Subject: [PATCH 38/39] Preserve Native CDC configuration during controller failover --- fdbclient/NativeCdc.cpp | 6 ++++-- .../clustercontroller/ClusterController.actor.cpp | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/fdbclient/NativeCdc.cpp b/fdbclient/NativeCdc.cpp index 953e002151..49af252104 100644 --- a/fdbclient/NativeCdc.cpp +++ b/fdbclient/NativeCdc.cpp @@ -394,10 +394,12 @@ Future registerNativeCdcStream(Database cx, Key name, KeyRange keys // Disabling CDC stops new admission, but existing registrations and // owner repair must remain available so durable streams can drain. - validateNativeCdcEnabled(cx->clientInfo->get().nativeCdcEnabled); + const bool nativeCdcEnabled = cx->clientInfo->get().nativeCdcEnabled; + const int nativeCdcTagCount = cx->clientInfo->get().nativeCdcTagCount; + validateNativeCdcEnabled(nativeCdcEnabled); NativeCdcIdentifierAllocator allocator; co_await observeNativeCdcMetadata(&tr, &allocator); - const auto [streamId, tag] = allocator.allocate(cx->clientInfo->get().nativeCdcTagCount); + const auto [streamId, tag] = allocator.allocate(nativeCdcTagCount); // The read version is a conservative lower bound for tag routing. // The versionstamped minimum below is the commit version, and stream // initialization takes their maximum before exposing mutations. diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 0f1f147e96..9ae78cd7f6 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -124,6 +124,8 @@ ClusterControllerData::ClusterControllerData(ClusterControllerFullInterface cons serverInfo.masterLifetime.ccID = id; serverInfo.clusterInterface = ccInterface; serverInfo.myLocality = locality; + serverInfo.client.nativeCdcEnabled = CLIENT_KNOBS->ENABLE_NATIVE_CDC; + serverInfo.client.nativeCdcTagCount = CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT; db.serverInfo->set(serverInfo); cx = openDBOnServer(db.serverInfo, TaskPriority::DefaultEndpoint, LockAware::True); @@ -3478,6 +3480,18 @@ void addProcessesToSameDC(ClusterControllerData& self, const std::vector( + new ClusterConnectionMemoryRecord(ClusterConnectionString()))), + makeReference>>()); + + ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcEnabled, CLIENT_KNOBS->ENABLE_NATIVE_CDC); + ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcTagCount, CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT); + return Void(); +} + TEST_CASE("/fdbserver/clustercontroller/ignoreStaleWorkerRegistration") { ClusterControllerData data(ClusterControllerFullInterface(), LocalityData(), From 167b157f4be5be133c15abc17a19a243d1bcd6f3 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Sat, 18 Jul 2026 09:07:19 -0700 Subject: [PATCH 39/39] Remove redundant Native CDC bootstrap unit test --- .../clustercontroller/ClusterController.actor.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/fdbserver/clustercontroller/ClusterController.actor.cpp b/fdbserver/clustercontroller/ClusterController.actor.cpp index 9ae78cd7f6..f5e3fca03c 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -3480,18 +3480,6 @@ void addProcessesToSameDC(ClusterControllerData& self, const std::vector( - new ClusterConnectionMemoryRecord(ClusterConnectionString()))), - makeReference>>()); - - ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcEnabled, CLIENT_KNOBS->ENABLE_NATIVE_CDC); - ASSERT_EQ(data.db.serverInfo->get().client.nativeCdcTagCount, CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT); - return Void(); -} - TEST_CASE("/fdbserver/clustercontroller/ignoreStaleWorkerRegistration") { ClusterControllerData data(ClusterControllerFullInterface(), LocalityData(),