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/.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); + } + } diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index d9eb773e00..8b2093b52e 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -43,9 +43,10 @@ jobs: ninja -v \ processed_compile_commands \ - fdb_c_generated \ fdboptions \ - ProtocolVersion + ProtocolVersion \ + fdb_c_generated \ + fdb-java # all flow actors, for generated headers ACTORS=$( diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index 8148b01427..a11e5ca052 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -1022,6 +1022,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 81a17462b2..cf344d51df 100644 --- a/bindings/c/foundationdb/fdb_c.h +++ b/bindings/c/foundationdb/fdb_c.h @@ -647,6 +647,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/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/java/src/test/com/apple/foundationdb/test/StackTester.java b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java index 23882902e5..d12d926143 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/StackTester.java @@ -790,4 +790,3 @@ public class StackTester { private StackTester() {} } - diff --git a/bindings/python/fdb/impl.py b/bindings/python/fdb/impl.py index c7eaef7640..a34592411b 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/python/tests/unit_tests.py b/bindings/python/tests/unit_tests.py index 1aaee66bc7..75b01eff3b 100644 --- a/bindings/python/tests/unit_tests.py +++ b/bindings/python/tests/unit_tests.py @@ -225,6 +225,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 (-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) <= limit + 2 + + def run_unit_tests(db): try: log("test_db_options") @@ -255,6 +268,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 f880f3d2dc..b46e34584c 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 @@ -485,7 +486,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..e7d9ad1716 100755 --- a/bindings/ruby/tests/tester.rb +++ b/bindings/ruby/tests/tester.rb @@ -321,7 +321,7 @@ 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_KEY" selector = FDB::KeySelector.new(inst.wait_and_pop, inst.wait_and_pop, inst.wait_and_pop) diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md index 31e0be5fcd..626ef8843b 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. @@ -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/design/AI-generated/foundationdb_subsystem_map.md b/design/AI-generated/foundationdb_subsystem_map.md index 6a6f9ab029..2f53560818 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. @@ -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/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index eedc10185d..c568dea5ff 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -760,6 +760,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/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..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()); @@ -1087,7 +1079,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 +3166,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) @@ -8429,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/MultiVersionTransaction.cpp b/fdbclient/MultiVersionTransaction.cpp index 3c6924de2e..d05ce28881 100644 --- a/fdbclient/MultiVersionTransaction.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -246,12 +246,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; @@ -872,6 +881,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", + false); loadClientFunction(&api->futureGetDouble, lib, @@ -1246,8 +1260,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..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) { @@ -3246,7 +3238,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, @@ -5970,51 +5963,126 @@ Future>> DatabaseContext::getReadH return ::getReadHotRanges(Database(Reference::addRef(this)), keys); } +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 { + Standalone> results; + int remaining; + +public: + RangeSplitPointsBuilder(KeyRef begin, int limit) : remaining(limit) { + results.push_back_deep(results.arena(), begin); + } + + int getRemaining() const { return remaining; } + + void appendShardBoundary(KeyRef boundary) { + if (results.back() == boundary || remaining == 0) { + return; + } + results.push_back_deep(results.arena(), boundary); + if (remaining > 0) { + --remaining; + } + } + + 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) { + int64_t chunkSize, + int limit) { state Span span("NAPI:GetRangeSplitPoints"_loc, trState->spanContext); + state Key beginKey = keys.begin; + state RangeSplitPointsBuilder results(keys.begin, limit); + if (limit == 0) { + return results.finish(keys.end); + } loop { state std::vector locations = wait(getKeyRangeLocations( - trState, keys, CLIENT_KNOBS->TOO_MANY, Reverse::False, &StorageServerInterface::getRangeSplitPoints)); + trState, + KeyRangeRef(beginKey, keys.end), + getRangeSplitPointsLocationLimit( + results.getRemaining(), CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->STORAGE_METRICS_SHARD_LIMIT), + Reverse::False, + &StorageServerInterface::getRangeSplitPoints)); try { state int nLocs = locations.size(); - 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); - fReplies[i] = loadBalance(locations[i].locations->locations(), - &StorageServerInterface::getRangeSplitPoints, - req, - TaskPriority::DataDistribution); - } - - wait(waitForAll(fReplies)); - Standalone> results; - - results.push_back_deep(results.arena(), keys.begin); - for (int i = 0; i < nLocs; i++) { - if (i > 0) { - results.push_back_deep(results.arena(), - locations[i].range.begin); // Need this shard boundary + if (limit >= 0) { + state int i = 0; + for (; i < nLocs; i++) { + if (i > 0 || beginKey != keys.begin) { + results.appendShardBoundary(locations[i].range.begin); + } + 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.getRemaining()); + SplitRangeReply reply = wait(loadBalance(locations[i].locations->locations(), + &StorageServerInterface::getRangeSplitPoints, + req, + TaskPriority::DataDistribution)); + results.appendSplitPoints(reply.splitPoints); } - if (fReplies[i].get().splitPoints.size() > 0) { - results.append( - results.arena(), fReplies[i].get().splitPoints.begin(), fReplies[i].get().splitPoints.size()); - results.arena().dependsOn(fReplies[i].get().splitPoints.arena()); + } else { + state std::vector> fReplies(nLocs); + for (int i = 0; i < nLocs; i++) { + 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(), + &StorageServerInterface::getRangeSplitPoints, + req, + TaskPriority::DataDistribution); + } + wait(waitForAll(fReplies)); + for (int i = 0; i < nLocs; i++) { + if (i > 0 || beginKey != keys.begin) { + results.appendShardBoundary(locations[i].range.begin); + } + results.appendSplitPoints(fReplies[i].get().splitPoints); } } - if (results.back() != keys.end) { - results.push_back_deep(results.arena(), keys.end); + if (results.getRemaining() == 0 || keys.end <= locations.back().range.end) { + return results.finish(keys.end); } - - return results; + 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); @@ -6024,8 +6092,95 @@ 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); +} + +TEST_CASE("/fdbclient/NativeAPI/rangeSplitPoints/locationLimit") { + constexpr int maxLocations = 1000; + constexpr int dataDistributionLocationLimit = 100; + + 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(); +} + +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); + Standalone> firstShardEndingAtBoundary; + firstShardEndingAtBoundary.push_back_deep(firstShardEndingAtBoundary.arena(), "B"_sr); + + RangeSplitPointsBuilder zero("A"_sr, 0); + zero.appendSplitPoints(firstShard); + 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); + 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); + two.appendShardBoundary("B"_sr); + ASSERT(two.getRemaining() == 1); + two.appendSplitPoints(secondShard); + ASSERT(two.getRemaining() == 0); + 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); + 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 duplicateBoundary("A"_sr, 2); + duplicateBoundary.appendSplitPoints(firstShardEndingAtBoundary); + ASSERT(duplicateBoundary.getRemaining() == 1); + 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); + unlimited.appendShardBoundary("B"_sr); + unlimited.appendSplitPoints(secondShard); + 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) { diff --git a/fdbclient/NativeCdc.cpp b/fdbclient/NativeCdc.cpp index 953e002151..49af252104 100644 --- a/fdbclient/NativeCdc.cpp +++ b/fdbclient/NativeCdc.cpp @@ -394,10 +394,12 @@ Future registerNativeCdcStream(Database cx, Key name, KeyRange keys // Disabling CDC stops new admission, but existing registrations and // owner repair must remain available so durable streams can drain. - validateNativeCdcEnabled(cx->clientInfo->get().nativeCdcEnabled); + const bool nativeCdcEnabled = cx->clientInfo->get().nativeCdcEnabled; + const int nativeCdcTagCount = cx->clientInfo->get().nativeCdcTagCount; + validateNativeCdcEnabled(nativeCdcEnabled); NativeCdcIdentifierAllocator allocator; co_await observeNativeCdcMetadata(&tr, &allocator); - const auto [streamId, tag] = allocator.allocate(cx->clientInfo->get().nativeCdcTagCount); + const auto [streamId, tag] = allocator.allocate(nativeCdcTagCount); // The read version is a conservative lower bound for tag routing. // The versionstamped minimum below is the commit version, and stream // initialization takes their maximum before exposing mutations. diff --git a/fdbclient/ReadYourWrites.cpp b/fdbclient/ReadYourWrites.cpp index 334384abfd..d7bbdb697c 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()); } @@ -1829,7 +1836,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 +1848,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) { @@ -2044,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) @@ -2074,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()) @@ -2188,7 +2198,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 +2246,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); } @@ -2410,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() { @@ -2533,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; @@ -2554,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); @@ -2637,7 +2646,7 @@ void ReadYourWritesTransaction::cancel() { } void ReadYourWritesTransaction::reset() { - if (debugTraces.size() > 0 || debugMessages.size() > 0) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(RYWImpl::printDebugMessages(this, {}).isReady()); } @@ -2676,7 +2685,7 @@ ReadYourWritesTransaction::~ReadYourWritesTransaction() { if (!resetPromise.isSet()) resetPromise.sendError(transaction_cancelled()); - if (debugTraces.size() || debugMessages.size()) { + if (!debugTraces.empty() || !debugMessages.empty()) { // printDebugMessages returns a future but will not block if called with an empty second argument [[maybe_unused]] Future f = RYWImpl::printDebugMessages(this, {}); } @@ -2700,14 +2709,15 @@ void ReadYourWritesTransaction::debugLogRetries(Optional error) { if (!transactionDebugInfo->transactionName.empty()) transactionNameStr = format(" in transaction '%s'", printable(StringRef(transactionDebugInfo->transactionName)).c_str()); - if (!g_network->isSimulated()) // Fuzz workload turns this on, but we do not want stderr output in - // simulation + // Fuzz workload turns this on, but we do not want stderr output in simulation. + if (!g_network->isSimulated()) { fprintf(stderr, "fdb WARNING: long transaction (%.2fs elapsed%s, %d retries, %s)\n", elapsed, transactionNameStr.c_str(), retries, committed ? "committed" : error.get().what()); + } { TraceEvent trace = TraceEvent("LongTransaction"); if (error.present()) diff --git a/fdbclient/RestoreInterface.cpp b/fdbclient/RestoreInterface.cpp deleted file mode 100644 index 96d81225bb..0000000000 --- a/fdbclient/RestoreInterface.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * RestoreInterface.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "RestoreInterface.h" -#include "flow/serialize.h" - -const KeyRef restoreRequestDoneKey = "\xff\x02/restoreRequestDone"_sr; -const KeyRef restoreRequestTriggerKey = "\xff\x02/restoreRequestTrigger"_sr; -const KeyRangeRef restoreRequestKeys("\xff\x02/restoreRequests/"_sr, "\xff\x02/restoreRequests0"_sr); - -// Encode and decode restore request value -Value restoreRequestTriggerValue(UID randomID, int numRequests) { - BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestTriggerValue())); - wr << numRequests; - wr << randomID; - return wr.toValue(); -} - -int decodeRestoreRequestTriggerValue(ValueRef const& value) { - int s; - UID randomID; - BinaryReader reader(value, IncludeVersion()); - reader >> s; - reader >> randomID; - return s; -} - -Key restoreRequestKeyFor(int index) { - BinaryWriter wr(Unversioned()); - wr.serializeBytes(restoreRequestKeys.begin); - wr << index; - return wr.toValue(); -} - -Value restoreRequestValue(RestoreRequest const& request) { - BinaryWriter wr(IncludeVersion(ProtocolVersion::withRestoreRequestValue())); - wr << request; - return wr.toValue(); -} diff --git a/fdbclient/RestoreInterface.h b/fdbclient/RestoreInterface.h deleted file mode 100644 index bd0c6ff2f3..0000000000 --- a/fdbclient/RestoreInterface.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * RestoreInterface.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "fdbclient/FDBTypes.h" -#include "fdbrpc/fdbrpc.h" - -struct RestoreCommonReply { - constexpr static FileIdentifier file_identifier = 5808787; - UID id; // unique ID of the server who sends the reply - bool isDuplicated; - - RestoreCommonReply() = default; - explicit RestoreCommonReply(UID id, bool isDuplicated = false) : id(id), isDuplicated(isDuplicated) {} - - std::string toString() const { - std::stringstream ss; - ss << "ServerNodeID:" << id.toString() << " isDuplicated:" << isDuplicated; - return ss.str(); - } - - template - void serialize(Ar& ar) { - serializer(ar, id, isDuplicated); - } -}; - -struct RestoreRequest { - constexpr static FileIdentifier file_identifier = 16035338; - - int index; - Key tagName; - Key url; - Optional proxy; - Version targetVersion; - KeyRange range; - UID randomUid; - - // Every key in backup will first removePrefix and then addPrefix; - // Simulation testing does not cover when both addPrefix and removePrefix exist yet. - Key addPrefix; - Key removePrefix; - - ReplyPromise reply; - - RestoreRequest() = default; - explicit RestoreRequest(const int index, - const Key& tagName, - const Key& url, - const Optional& proxy, - Version targetVersion, - const KeyRange& range, - const UID& randomUid, - Key& addPrefix, - Key removePrefix) - : index(index), tagName(tagName), url(url), proxy(proxy), targetVersion(targetVersion), range(range), - randomUid(randomUid), addPrefix(addPrefix), removePrefix(removePrefix) {} - - // To change this serialization, ProtocolVersion::RestoreRequestValue must be updated, and downgrades need to be - // considered - template - void serialize(Ar& ar) { - serializer(ar, index, tagName, url, proxy, targetVersion, range, randomUid, addPrefix, removePrefix, reply); - } - - std::string toString() const { - std::stringstream ss; - ss << "index:" << std::to_string(index) << " tagName:" << tagName.contents().toString() - << " url:" << url.contents().toString() << " proxy:" << (proxy.present() ? proxy.get() : "") - << " targetVersion:" << std::to_string(targetVersion) << " range:" << range.toString() - << " randomUid:" << randomUid.toString() << " addPrefix:" << addPrefix.toString() - << " removePrefix:" << removePrefix.toString(); - return ss.str(); - } -}; - -extern const KeyRef restoreRequestDoneKey; -extern const KeyRef restoreRequestTriggerKey; -extern const KeyRangeRef restoreRequestKeys; - -Value restoreRequestTriggerValue(UID randomID, int numRequests); -int decodeRequestRequestTriggerValue(ValueRef const&); -Key restoreRequestKeyFor(int index); -Value restoreRequestValue(RestoreRequest const&); diff --git a/fdbclient/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/fdbclient/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index aa69142f12..dfed06ed17 100644 --- a/fdbclient/ThreadSafeTransaction.cpp +++ b/fdbclient/ThreadSafeTransaction.cpp @@ -412,13 +412,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 b2d56aa6c2..c669966346 100644 --- a/fdbclient/include/fdbclient/IClientApi.h +++ b/fdbclient/include/fdbclient/IClientApi.h @@ -75,7 +75,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 b16c20d4f3..084a5697af 100644 --- a/fdbclient/include/fdbclient/MultiVersionTransaction.h +++ b/fdbclient/include/fdbclient/MultiVersionTransaction.h @@ -257,6 +257,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); @@ -357,7 +364,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; @@ -528,7 +536,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..9b9f841994 100644 --- a/fdbclient/include/fdbclient/StorageServerInterface.h +++ b/fdbclient/include/fdbclient/StorageServerInterface.h @@ -779,14 +779,16 @@ 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, limit, arena); } }; diff --git a/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 541724cd50..fca3803616 100644 --- a/fdbclient/include/fdbclient/ThreadSafeTransaction.h +++ b/fdbclient/include/fdbclient/ThreadSafeTransaction.h @@ -129,7 +129,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/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"; 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/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/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/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h index 41c066a047..d31627ade3 100644 --- a/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h +++ b/fdbrpc/include/fdbrpc/AsyncFileNonDurable.h @@ -39,7 +39,7 @@ extern Future waitShutdownSignal(); template Future sendErrorOnShutdown(Future in, bool assertOnCancel = false) { try { - auto res = co_await race(waitShutdownSignal(), in); + auto res = co_await race(waitShutdownSignal(), std::move(in)); if (res.index() == 0) { throw io_error().asInjectedFault(); } else { diff --git a/fdbrpc/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/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 54c89f7271..2d3cdd49da 100644 --- a/fdbserver/clustercontroller/ClusterController.actor.cpp +++ b/fdbserver/clustercontroller/ClusterController.actor.cpp @@ -36,7 +36,7 @@ #include "fdbrpc/Locality.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/ProcessClassRecruitment.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "fdbclient/ClusterConnectionMemoryRecord.h" #include "fdbclient/NativeAPI.actor.h" @@ -124,6 +124,8 @@ ClusterControllerData::ClusterControllerData(ClusterControllerFullInterface cons serverInfo.masterLifetime.ccID = id; serverInfo.clusterInterface = ccInterface; serverInfo.myLocality = locality; + serverInfo.client.nativeCdcEnabled = CLIENT_KNOBS->ENABLE_NATIVE_CDC; + serverInfo.client.nativeCdcTagCount = CLIENT_KNOBS->NATIVE_CDC_TAG_COUNT; db.serverInfo->set(serverInfo); cx = openDBOnServer(db.serverInfo, TaskPriority::DefaultEndpoint, LockAware::True); @@ -1378,7 +1380,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; @@ -2384,60 +2385,56 @@ Future updatedChangingDatacenters(ClusterControllerData* self) { } } -ACTOR Future updatedChangedDatacenters(ClusterControllerData* self) { - state Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); - state Future onChange = self->changingDcIds.onChange(); - loop { - choose { - when(wait(onChange)) { - changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); - onChange = self->changingDcIds.onChange(); - } - when(wait(changeDelay)) { - changeDelay = Never(); - onChange = self->changingDcIds.onChange(); +Future updatedChangedDatacenters(ClusterControllerData* self) { + Future changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); + Future onChange = self->changingDcIds.onChange(); + while (true) { + auto res = co_await race(onChange, changeDelay); + if (res.index() == 0) { + changeDelay = delay(SERVER_KNOBS->CC_CHANGE_DELAY); + onChange = self->changingDcIds.onChange(); + } else { + changeDelay = Never(); + onChange = self->changingDcIds.onChange(); - self->changedDcIds.set(self->changingDcIds.get()); - if (self->changedDcIds.get().second.present()) { - TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); - if (!self->changedDcIds.get().first) { - auto& worker = self->id_worker[self->clusterControllerProcessId]; - uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( - worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); - if (worker.priorityInfo.dcFitness != newFitness) { - worker.priorityInfo.dcFitness = newFitness; - if (!worker.reply.isSet()) { - worker.reply.send( - RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); - } + self->changedDcIds.set(self->changingDcIds.get()); + if (self->changedDcIds.get().second.present()) { + TraceEvent("UpdateChangedDatacenter", self->id).detail("CCFirst", self->changedDcIds.get().first); + if (!self->changedDcIds.get().first) { + auto& worker = self->id_worker[self->clusterControllerProcessId]; + uint8_t newFitness = ClusterControllerPriorityInfo::calculateDCFitness( + worker.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (worker.priorityInfo.dcFitness != newFitness) { + worker.priorityInfo.dcFitness = newFitness; + if (!worker.reply.isSet()) { + worker.reply.send(RegisterWorkerReply(worker.details.processClass, worker.priorityInfo)); } - } else { - state int currentFit = recruitment::BestFit; - while (currentFit <= recruitment::NeverAssign) { - bool updated = false; - for (auto& it : self->id_worker) { - if ((!it.second.priorityInfo.isExcluded && - it.second.priorityInfo.processClassFitness == currentFit) || - currentFit == recruitment::NeverAssign) { - uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( - it.second.details.interf.locality.dcId(), - self->changedDcIds.get().second.get()); - if (it.first != self->clusterControllerProcessId && - it.second.priorityInfo.dcFitness != fitness) { - updated = true; - it.second.priorityInfo.dcFitness = fitness; - if (!it.second.reply.isSet()) { - it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, - it.second.priorityInfo)); - } + } + } else { + int currentFit = recruitment::BestFit; + while (currentFit <= recruitment::NeverAssign) { + bool updated = false; + for (auto& it : self->id_worker) { + if ((!it.second.priorityInfo.isExcluded && + it.second.priorityInfo.processClassFitness == currentFit) || + currentFit == recruitment::NeverAssign) { + uint8_t fitness = ClusterControllerPriorityInfo::calculateDCFitness( + it.second.details.interf.locality.dcId(), self->changedDcIds.get().second.get()); + if (it.first != self->clusterControllerProcessId && + it.second.priorityInfo.dcFitness != fitness) { + updated = true; + it.second.priorityInfo.dcFitness = fitness; + if (!it.second.reply.isSet()) { + it.second.reply.send(RegisterWorkerReply(it.second.details.processClass, + it.second.priorityInfo)); } } } - if (updated && currentFit < recruitment::NeverAssign) { - wait(delay(SERVER_KNOBS->CC_CLASS_DELAY)); - } - currentFit++; } + if (updated && currentFit < recruitment::NeverAssign) { + co_await delay(SERVER_KNOBS->CC_CLASS_DELAY); + } + currentFit++; } } } @@ -2807,13 +2804,13 @@ Future startDataDistributor(ClusterControllerData* self, double waitTime) } } -ACTOR Future monitorDataDistributor(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future monitorDataDistributor(ClusterControllerData* self) { + SingletonRecruitThrottler recruitThrottler; while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } - loop { + while (true) { bool ddExist = self->db.serverInfo->get().distributor.present(); TraceEvent(SevInfo, "CCMonitorDataDistributor", self->id) .detail("Recruiting", self->recruitDistributor.get()) @@ -2821,18 +2818,17 @@ ACTOR Future monitorDataDistributor(ClusterControllerData* self) { .detail("ExistingDD", ddExist ? self->db.serverInfo->get().distributor.get().id().toString() : ""); if (self->db.serverInfo->get().distributor.present() && !self->recruitDistributor.get()) { - choose { - when(wait(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure, - SERVER_KNOBS->DD_FAILURE_TIME))) { - const auto& distributor = self->db.serverInfo->get().distributor; - TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id()); - DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId()); - self->db.clearInterf(ProcessClass::DataDistributorClass); - } - when(wait(self->recruitDistributor.onChange())) {} + auto res = co_await race(waitFailureClient(self->db.serverInfo->get().distributor.get().waitFailure, + SERVER_KNOBS->DD_FAILURE_TIME), + self->recruitDistributor.onChange()); + if (res.index() == 0) { + const auto& distributor = self->db.serverInfo->get().distributor; + TraceEvent("CCDataDistributorDied", self->id).detail("DDID", distributor.get().id()); + DataDistributorSingleton(distributor).halt(*self, distributor.get().locality.processId()); + self->db.clearInterf(ProcessClass::DataDistributorClass); } } else { - wait(startDataDistributor(self, recruitThrottler.newRecruitment())); + co_await startDataDistributor(self, recruitThrottler.newRecruitment()); } } } @@ -2905,26 +2901,25 @@ Future startRatekeeper(ClusterControllerData* self, double waitTime) { } } -ACTOR Future monitorRatekeeper(ClusterControllerData* self) { - state SingletonRecruitThrottler recruitThrottler; +Future monitorRatekeeper(ClusterControllerData* self) { + SingletonRecruitThrottler recruitThrottler; while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } - loop { + while (true) { if (self->db.serverInfo->get().ratekeeper.present() && !self->recruitRatekeeper.get()) { - choose { - when(wait(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure, - SERVER_KNOBS->RATEKEEPER_FAILURE_TIME))) { - const auto& ratekeeper = self->db.serverInfo->get().ratekeeper; - TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id()); - RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId()); - self->db.clearInterf(ProcessClass::RatekeeperClass); - } - when(wait(self->recruitRatekeeper.onChange())) {} + auto res = co_await race(waitFailureClient(self->db.serverInfo->get().ratekeeper.get().waitFailure, + SERVER_KNOBS->RATEKEEPER_FAILURE_TIME), + self->recruitRatekeeper.onChange()); + if (res.index() == 0) { + const auto& ratekeeper = self->db.serverInfo->get().ratekeeper; + TraceEvent("CCRatekeeperDied", self->id).detail("RKID", ratekeeper.get().id()); + RatekeeperSingleton(ratekeeper).halt(*self, ratekeeper.get().locality.processId()); + self->db.clearInterf(ProcessClass::RatekeeperClass); } } else { - wait(startRatekeeper(self, recruitThrottler.newRecruitment())); + co_await startRatekeeper(self, recruitThrottler.newRecruitment()); } } } @@ -2997,28 +2992,25 @@ Future startConsistencyScan(ClusterControllerData* self) { } } -ACTOR Future monitorConsistencyScan(ClusterControllerData* self) { +Future monitorConsistencyScan(ClusterControllerData* self) { while (self->db.serverInfo->get().recoveryState < RecoveryState::ACCEPTING_COMMITS) { TraceEvent("CCMonitorConsistencyScanWaitingForRecovery", self->id).log(); - wait(self->db.serverInfo->onChange()); + co_await self->db.serverInfo->onChange(); } TraceEvent("CCMonitorConsistencyScan", self->id).log(); - loop { + while (true) { if (self->db.serverInfo->get().consistencyScan.present() && !self->recruitConsistencyScan.get()) { - state Future wfClient = - waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure, - SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME); - choose { - when(wait(wfClient)) { - TraceEvent("CCMonitorConsistencyScanDied", self->id) - .detail("CKID", self->db.serverInfo->get().consistencyScan.get().id()); - self->db.clearInterf(ProcessClass::ConsistencyScanClass); - } - when(wait(self->recruitConsistencyScan.onChange())) {} + Future wfClient = waitFailureClient(self->db.serverInfo->get().consistencyScan.get().waitFailure, + SERVER_KNOBS->CONSISTENCYSCAN_FAILURE_TIME); + auto res = co_await race(wfClient, self->recruitConsistencyScan.onChange()); + if (res.index() == 0) { + TraceEvent("CCMonitorConsistencyScanDied", self->id) + .detail("CKID", self->db.serverInfo->get().consistencyScan.get().id()); + self->db.clearInterf(ProcessClass::ConsistencyScanClass); } } else { TraceEvent("CCMonitorConsistencyScanStarting", self->id).log(); - wait(startConsistencyScan(self)); + co_await startConsistencyScan(self); } } } @@ -3532,6 +3524,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 f52c2fe5db..0cb3635780 100644 --- a/fdbserver/clustercontroller/ClusterController.h +++ b/fdbserver/clustercontroller/ClusterController.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include "fdbclient/DatabaseContext.h" @@ -31,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" @@ -2418,6 +2419,31 @@ 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()) { + 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", locality.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/clustercontroller/ClusterRecovery.cpp b/fdbserver/clustercontroller/ClusterRecovery.cpp index 5b6363df7d..b61fa01282 100644 --- a/fdbserver/clustercontroller/ClusterRecovery.cpp +++ b/fdbserver/clustercontroller/ClusterRecovery.cpp @@ -540,8 +540,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); - // Purge in memory state after durability to avoid race conditions. - self->logSystem->purgeOldRecoveredGenerationsInMemory(newState); + // 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()); } 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 2d88c836d8..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" @@ -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..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" @@ -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/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/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..5c1bbe3891 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; @@ -610,7 +610,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); @@ -618,13 +618,14 @@ 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()) { - if (*endKey > range.end) { + while (endKey != byteSample.sample.end() && (limit < 0 || toReturn.size() < static_cast(limit))) { + if (*endKey >= range.end) { break; } if (*endKey == beginKey) { @@ -924,6 +925,54 @@ 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/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; 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 5d94835bb3..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; @@ -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/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/fdbserver/core/include/fdbserver/core/WorkerEvents.h b/fdbserver/core/include/fdbserver/core/WorkerEvents.h index e5bc268ac2..b2791d72eb 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerEvents.h +++ b/fdbserver/core/include/fdbserver/core/WorkerEvents.h @@ -25,7 +25,7 @@ #include #include "flow/ITrace.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" struct WorkerEvents : std::map {}; diff --git a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h b/fdbserver/core/include/fdbserver/core/WorkerInterface.h similarity index 95% rename from fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h rename to fdbserver/core/include/fdbserver/core/WorkerInterface.h index 38bf059807..428ad32126 100644 --- a/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h +++ b/fdbserver/core/include/fdbserver/core/WorkerInterface.h @@ -1,5 +1,5 @@ /* - * WorkerInterface.actor.h + * WorkerInterface.h * * This source file is part of the FoundationDB open source project * @@ -19,11 +19,6 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_WORKERINTERFACE_ACTOR_G_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_G_H -#include "fdbserver/core/WorkerInterface.actor.g.h" -#elif !defined(FDBSERVER_WORKERINTERFACE_ACTOR_H) -#define FDBSERVER_WORKERINTERFACE_ACTOR_H #include "fdbserver/core/BackupInterface.h" #include "fdbserver/core/DataDistributorInterface.h" @@ -43,7 +38,7 @@ #include "fdbrpc/MultiInterface.h" #include "fdbclient/ClientWorkerInterface.h" #include "fdbserver/core/RecoveryState.h" -#include "flow/actorcompiler.h" +#include "flow/CoroUtils.h" struct WorkerInterface { constexpr static FileIdentifier file_identifier = 14712718; @@ -83,7 +78,7 @@ struct WorkerInterface { NetworkAddressList addresses() const { return tLog.getEndpoint().addresses; } Optional grpcAddress() const { return clientInterface.grpcAddress; } - WorkerInterface() {} + WorkerInterface() = default; explicit(false) WorkerInterface(const LocalityData& locality) : locality(locality) {} void initEndpoints() { @@ -371,7 +366,7 @@ struct RecruitFromConfigurationRequest { int maxOldLogRouters; ReplyPromise reply; - RecruitFromConfigurationRequest() {} + RecruitFromConfigurationRequest() = default; explicit RecruitFromConfigurationRequest(DatabaseConfiguration const& configuration, bool recruitSeedServers, int maxOldLogRouters) @@ -404,7 +399,7 @@ struct RecruitRemoteFromConfigurationRequest { Optional dbgId; ReplyPromise reply; - RecruitRemoteFromConfigurationRequest() {} + RecruitRemoteFromConfigurationRequest() = default; RecruitRemoteFromConfigurationRequest(DatabaseConfiguration const& configuration, Optional const& dcId, int logRouterCount, @@ -550,7 +545,7 @@ struct TLogRejoinRequest { TLogInterface myInterface; ReplyPromise reply; - TLogRejoinRequest() {} + TLogRejoinRequest() = default; explicit TLogRejoinRequest(const TLogInterface& interf) : myInterface(interf) {} template void serialize(Ar& ar) { @@ -591,7 +586,7 @@ struct GetEncryptionAtRestModeRequest { UID tlogId; ReplyPromise reply; - GetEncryptionAtRestModeRequest() {} + GetEncryptionAtRestModeRequest() = default; explicit(false) GetEncryptionAtRestModeRequest(UID tId) : tlogId(tId) {} template @@ -776,7 +771,7 @@ struct RecruitMasterRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; @@ -805,7 +800,7 @@ struct InitializeCommitProxyRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; @@ -822,7 +817,7 @@ struct InitializeGrvProxyRequest { } }; -// Instantiated in worker.actor.cpp +// Instantiated in worker.cpp extern template class RequestStream; extern template struct NetNotifiedQueue; @@ -846,7 +841,7 @@ struct InitializeDataDistributorRequest { UID reqId; ReplyPromise reply; - InitializeDataDistributorRequest() {} + InitializeDataDistributorRequest() = default; explicit InitializeDataDistributorRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -859,7 +854,7 @@ struct InitializeRatekeeperRequest { UID reqId; ReplyPromise reply; - InitializeRatekeeperRequest() {} + InitializeRatekeeperRequest() = default; explicit InitializeRatekeeperRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -872,7 +867,7 @@ struct InitializeConsistencyScanRequest { UID reqId; ReplyPromise reply; - InitializeConsistencyScanRequest() {} + InitializeConsistencyScanRequest() = default; explicit InitializeConsistencyScanRequest(UID uid) : reqId(uid) {} template void serialize(Ar& ar) { @@ -1044,7 +1039,7 @@ struct DebugEntryRef { StringRef context; Version version; MutationRef mutation; - DebugEntryRef() {} + DebugEntryRef() = default; DebugEntryRef(const char* c, Version v, MutationRef const& m) : time(now()), address(g_network->getLocalAddress()), context((const uint8_t*)c, strlen(c)), version(v), mutation(m) {} @@ -1174,42 +1169,44 @@ bool addressInDbAndRemoteDc( extern bool isSimulatorProcessUnreliable(); -ACTOR template -Future ioTimeoutError(Future what, double time, const char* context = nullptr) { +template +Future ioTimeoutError(Future what, double time, const char* context = nullptr, ExplicitVoid = {}) { // Before simulation is sped up, IO operations can take a very long time so limit timeouts // to not end until at least time after simulation is sped up. - state double orig = now(); - state std::string trace = platform::get_backtrace(); + double orig = now(); + std::string trace = platform::get_backtrace(); if (g_network->isSimulated() && !g_simulator->speedUpSimulation) { time += std::max(0.0, FLOW_KNOBS->SIM_SPEEDUP_AFTER_SECONDS - now()); } Future end = lowPriorityDelay(time); - choose { - when(T t = wait(what)) { - return t; + auto res = co_await race(what, end); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + Error err = io_timeout(); + if (isSimulatorProcessUnreliable()) { + err = err.asInjectedFault(); } - when(wait(end)) { - Error err = io_timeout(); - if (isSimulatorProcessUnreliable()) { - err = err.asInjectedFault(); - } - TraceEvent e(SevError, "IoTimeoutError"); - e.error(err); - if (context != nullptr) { - e.detail("Context", context); - } - e.detail("OrigTime", orig).detail("OrigTrace", trace).log(); - throw err; + TraceEvent e(SevError, "IoTimeoutError"); + e.error(err); + if (context != nullptr) { + e.detail("Context", context); } + e.detail("OrigTime", orig).detail("OrigTrace", trace).log(); + throw err; + } else { + UNREACHABLE(); } } -ACTOR template +template Future ioDegradedOrTimeoutError(Future what, double errTime, Reference> degraded, double degradedTime, - const char* context = nullptr) { + const char* context = nullptr, + ExplicitVoid = {}) { // Before simulation is sped up, IO operations can take a very long time so limit timeouts // to not end until at least time after simulation is sped up. if (g_network->isSimulated() && !g_simulator->speedUpSimulation) { @@ -1220,39 +1217,39 @@ Future ioDegradedOrTimeoutError(Future what, if (degradedTime < errTime) { Future degradedEnd = lowPriorityDelay(degradedTime); - choose { - when(T t = wait(what)) { - return t; - } - when(wait(degradedEnd)) { - CODE_PROBE(true, "TLog degraded", probe::func::deduplicate); - TraceEvent(SevWarnAlways, "IoDegraded").log(); - degraded->set(true); - } + auto res = co_await race(what, degradedEnd); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + CODE_PROBE(true, "TLog degraded", probe::func::deduplicate); + TraceEvent(SevWarnAlways, "IoDegraded").log(); + degraded->set(true); + } else { + UNREACHABLE(); } } Future end = lowPriorityDelay(errTime - degradedTime); - choose { - when(T t = wait(what)) { - return t; + auto res = co_await race(what, end); + if (res.index() == 0) { + T t = std::get<0>(std::move(res)); + co_return t; + } else if (res.index() == 1) { + Error err = io_timeout(); + if (isSimulatorProcessUnreliable()) { + err = err.asInjectedFault(); } - when(wait(end)) { - Error err = io_timeout(); - if (isSimulatorProcessUnreliable()) { - err = err.asInjectedFault(); - } - TraceEvent e(SevError, "IoTimeoutError"); - e.error(err); - if (context != nullptr) { - e.detail("Context", context); - } - e.log(); - throw err; + TraceEvent e(SevError, "IoTimeoutError"); + e.error(err); + if (context != nullptr) { + e.detail("Context", context); } + e.log(); + throw err; + } else { + UNREACHABLE(); } } -#include "flow/unactorcompiler.h" #include "fdbserver/core/ServerDBInfo.h" -#endif diff --git a/fdbserver/datadistributor/DDRelocationQueue.cpp b/fdbserver/datadistributor/DDRelocationQueue.cpp index 1f912f52f2..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; @@ -3069,9 +3064,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); } @@ -3218,3 +3227,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; +} 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..f969c577c4 100644 --- a/fdbserver/fdbserver.cpp +++ b/fdbserver/fdbserver.cpp @@ -65,7 +65,7 @@ #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/TestEncryptionUtils.h" #include "fdbserver/tester/tester.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/worker/Worker.h" #include "fdbserver/mocks3/MockS3Server.h" #ifdef WITH_ROCKSDB @@ -410,68 +410,6 @@ Future metricsReport() { } } -void testSerializationSpeed() { - double tstart; - double build = 0, serialize = 0, deserialize = 0, copy = 0, deallocate = 0; - double bytes = 0; - double testBegin = timer(); - for (int a = 0; a < 10000; a++) { - { - tstart = timer(); - - Arena batchArena; - VectorRef batch; - batch.resize(batchArena, 1000); - for (int t = 0; t < batch.size(); t++) { - CommitTransactionRef& tr = batch[t]; - tr.read_snapshot = 0; - for (int i = 0; i < 2; i++) - tr.mutations.push_back_deep(batchArena, - MutationRef(MutationRef::SetValue, "KeyABCDE"_sr, "SomeValu"_sr)); - tr.mutations.push_back_deep(batchArena, - MutationRef(MutationRef::ClearRange, "BeginKey"_sr, "EndKeyAB"_sr)); - } - - build += timer() - tstart; - - tstart = timer(); - - BinaryWriter wr(IncludeVersion()); - wr << batch; - - bytes += wr.getLength(); - - serialize += timer() - tstart; - - for (int i = 0; i < 1; i++) { - tstart = timer(); - Arena arena; - StringRef data(arena, StringRef((const uint8_t*)wr.getData(), wr.getLength())); - copy += timer() - tstart; - - tstart = timer(); - ArenaReader rd(arena, data, IncludeVersion()); - VectorRef batch2; - rd >> arena >> batch2; - - deserialize += timer() - tstart; - } - - tstart = timer(); - } - deallocate += timer() - tstart; - } - double elapsed = (timer() - testBegin); - printf("Test speed: %0.1f MB/sec (%0.0f/sec)\n", bytes / 1e6 / elapsed, 1000000 / elapsed); - printf(" Build: %0.1f MB/sec\n", bytes / 1e6 / build); - printf(" Serialize: %0.1f MB/sec\n", bytes / 1e6 / serialize); - printf(" Copy: %0.1f MB/sec\n", bytes / 1e6 / copy); - printf(" Deserialize: %0.1f MB/sec\n", bytes / 1e6 / deserialize); - printf(" Deallocate: %0.1f MB/sec\n", bytes / 1e6 / deallocate); - printf(" Bytes: %0.1f MB\n", bytes / 1e6); - printf("\n"); -} - void memoryTest(); void skipListTest(); diff --git a/fdbserver/grvproxy/GrvProxyServer.cpp b/fdbserver/grvproxy/GrvProxyServer.cpp index 5bf8ed3a0f..32e1a713b4 100644 --- a/fdbserver/grvproxy/GrvProxyServer.cpp +++ b/fdbserver/grvproxy/GrvProxyServer.cpp @@ -35,7 +35,7 @@ #include "fdbserver/logsystem/LogSystemFactory.h" #include "fdbserver/logsystem/LogSystemDiskQueueAdapter.h" #include "fdbserver/core/WaitFailure.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbrpc/sim_validation.h" #include "flow/Buggify.h" #include "flow/IRandom.h" diff --git a/fdbserver/include/fdbserver/NetworkTest.h b/fdbserver/include/fdbserver/NetworkTest.h index 05ec966d69..ae51501c22 100644 --- a/fdbserver/include/fdbserver/NetworkTest.h +++ b/fdbserver/include/fdbserver/NetworkTest.h @@ -28,7 +28,6 @@ struct NetworkTestInterface { RequestStream test; - RequestStream testStream; NetworkTestInterface() = default; explicit NetworkTestInterface(NetworkAddress remote); explicit NetworkTestInterface(INetwork* local); @@ -58,29 +57,6 @@ struct NetworkTestRequest { } }; -struct NetworkTestStreamingReply : ReplyPromiseStreamReply { - constexpr static FileIdentifier file_identifier = 3726830; - - int index = 0; - NetworkTestStreamingReply() = default; - explicit NetworkTestStreamingReply(int index) : index(index) {} - size_t expectedSize() const { return 4e6; /*sizeof(*this);*/ } - - template - void serialize(Ar& ar) { - serializer(ar, ReplyPromiseStreamReply::acknowledgeToken, ReplyPromiseStreamReply::sequence, index); - } -}; - -struct NetworkTestStreamingRequest { - constexpr static FileIdentifier file_identifier = 2794452; - ReplyPromiseStream reply; - template - void serialize(Ar& ar) { - serializer(ar, reply); - } -}; - Future networkTestServer(); Future networkTestClient(std::string const& testServers); diff --git a/fdbserver/kvstore/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..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" @@ -63,7 +61,7 @@ void ExecCmdValueString::setCmdValueString(StringRef pCmdValueString) { } StringRef ExecCmdValueString::getCmdValueString() const { - return cmdValueString.toString(); + return cmdValueString; } StringRef ExecCmdValueString::getBinaryPath() const { @@ -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/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/kvstore/VersionedBTree.actor.cpp b/fdbserver/kvstore/VersionedBTree.actor.cpp index 3b6cd0f442..4d691100b1 100644 --- a/fdbserver/kvstore/VersionedBTree.actor.cpp +++ b/fdbserver/kvstore/VersionedBTree.actor.cpp @@ -30,7 +30,7 @@ #include "fdbserver/core/Knobs.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "VersionedBTreeDebug.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "flow/ActorCollection.h" #include "flow/CoroUtils.h" #include "flow/Error.h" @@ -1365,10 +1365,6 @@ public: } }; -int nextPowerOf2(uint32_t x) { - return 1 << (32 - clz(x - 1)); -} - struct RedwoodMetrics { constexpr static unsigned int btreeLevels = 5; static int maxRecordCount; diff --git a/fdbserver/logrouter/LogRouter.cpp b/fdbserver/logrouter/LogRouter.cpp index 10cc18e958..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/LogSystem.cpp b/fdbserver/logsystem/LogSystem.cpp index f59c263db7..7f55ad925f 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; } } @@ -530,16 +530,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(); @@ -1137,10 +1127,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; } @@ -1597,7 +1589,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/logsystem/include/fdbserver/logsystem/LogSystem.h b/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h index 5f1ac93279..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" @@ -338,9 +338,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; diff --git a/fdbserver/networktest.cpp b/fdbserver/networktest.cpp index 5cc52893a5..d8f6473cbc 100644 --- a/fdbserver/networktest.cpp +++ b/fdbserver/networktest.cpp @@ -108,60 +108,6 @@ Future networkTestServer() { co_await server.run(); } -class NetworkTestStreamingServer { -public: - NetworkTestStreamingServer() : interf(g_network) {} - - Future run() { co_await race(requests(), logging()); } - -private: - Future requests() { - while (true) { - try { - NetworkTestStreamingRequest req = co_await interf.testStream.getFuture(); - LatencyStats::sample sample = latency.tick(); - for (int i = 0; i < 100; ++i) { - co_await req.reply.onReady(); - req.reply.send(NetworkTestStreamingReply{ i }); - } - req.reply.sendError(end_of_stream()); - latency.tock(sample); - sent++; - } catch (Error& e) { - if (e.code() != error_code_operation_obsolete) { - throw e; - } - } - } - } - - Future logging() { - double lastTime = now(); - - while (true) { - co_await delay(1.0); - auto spd = sent / (now() - lastTime); - if (FLOW_KNOBS->NETWORK_TEST_SCRIPT_MODE) { - fprintf(stderr, "%f\t%.3f\t%.3f\n", spd, latency.mean() * 1e6, latency.stddev() * 1e6); - } else { - fprintf(stderr, "responses per second: %f (%f us)\n", spd, latency.mean() * 1e6); - } - latency.reset(); - lastTime = now(); - sent = 0; - } - } - - NetworkTestInterface interf; - int sent = 0; - LatencyStats latency; -}; - -Future networkTestStreamingServer() { - NetworkTestStreamingServer server; - co_await server.run(); -} - static bool moreRequestsPending(int count) { if (count == -1) { return false; @@ -193,31 +139,6 @@ Future testClient(std::vector interfs, int* sent, in } } -Future testClientStream(std::vector interfs, - int* sent, - int* completed, - LatencyStats* latency) { - while (moreRequestsPending(*sent)) { - (*sent)++; - LatencyStats::sample sample = latency->tick(); - ReplyPromiseStream stream = - interfs[deterministicRandom()->randomInt(0, interfs.size())].testStream.getReplyStream( - NetworkTestStreamingRequest{}); - int j = 0; - try { - while (true) { - NetworkTestStreamingReply rep = co_await stream.getFuture(); - ASSERT(rep.index == j++); - } - } catch (Error& e) { - ASSERT(e.code() == error_code_end_of_stream || e.code() == error_code_connection_failed || - e.code() == error_code_request_maybe_delivered); - } - latency->tock(sample); - (*completed)++; - } -} - Future logger(int* sent, int* completed, LatencyStats* latency) { double lastTime = now(); int logged = 0; diff --git a/fdbserver/ratekeeper/Ratekeeper.cpp b/fdbserver/ratekeeper/Ratekeeper.cpp index 4e0e0cd995..ba733c7b38 100644 --- a/fdbserver/ratekeeper/Ratekeeper.cpp +++ b/fdbserver/ratekeeper/Ratekeeper.cpp @@ -662,7 +662,18 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { // ratio for (auto i = storageQueueInfo.begin(); i != storageQueueInfo.end(); ++i) { auto const& ss = i->value; - if (!ss.valid || !ss.acceptingRequests || (remoteDC.present() && ss.locality.dcId() == remoteDC)) { + if (!ss.valid || !ss.acceptingRequests) { + continue; + } + + int64_t storageQueue = ss.getStorageQueueBytes(); + worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); + + int64_t storageDurabilityLag = ss.getDurabilityLag(); + worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); + + // Remote storage is reported in health metrics but is not used to rate-limit the primary region. + if (remoteDC.present() && ss.locality.dcId() == remoteDC) { continue; } ++sscount; @@ -697,12 +708,6 @@ void Ratekeeper::updateRate(RatekeeperLimits* limits) { } } - int64_t storageQueue = ss.getStorageQueueBytes(); - worstStorageQueueStorageServer = std::max(worstStorageQueueStorageServer, storageQueue); - - int64_t storageDurabilityLag = ss.getDurabilityLag(); - worstDurabilityLag = std::max(worstDurabilityLag, storageDurabilityLag); - storageDurabilityLagReverseIndex.insert(std::make_pair(-1 * storageDurabilityLag, &ss)); double targetRateRatio = std::min((storageQueue - targetBytes + springBytes) / (double)springBytes, 2.0); diff --git a/fdbserver/resolver/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/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 dbc8cb8adb..4397b008be 100644 --- a/fdbserver/storageserver/storageserver.cpp +++ b/fdbserver/storageserver/storageserver.cpp @@ -96,7 +96,7 @@ #include "fdbserver/core/ServerDBInfo.h" #include "fdbserver/core/DataMovement.h" #include "fdbserver/storageserver/StorageServer.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "StorageServerUtils.h" #include "flow/CoroUtils.h" #include "flow/TDMetric.h" @@ -1985,16 +1985,6 @@ Future waitForVersionActor(StorageServer* data, Version version, SpanCo } } -// If the latest commit version that mutated the shard(s) being served by the specified storage -// server is below the client specified read version then do a read at the latest commit version -// of the storage server. -Version getRealReadVersion(VersionVector& ssLatestCommitVersions, Tag& tag, Version specifiedReadVersion) { - Version realReadVersion = - ssLatestCommitVersions.hasVersion(tag) ? ssLatestCommitVersions.getVersion(tag) : specifiedReadVersion; - ASSERT(realReadVersion <= specifiedReadVersion); - return realReadVersion; -} - // Find the latest commit version of the given tag. Version getLatestCommitVersion(VersionVector& ssLatestCommitVersions, Tag& tag) { Version commitVersion = @@ -3496,7 +3486,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) { @@ -4266,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. @@ -6249,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/tester/ConsistencyChecker.cpp b/fdbserver/tester/ConsistencyChecker.cpp index d0cab73a65..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" @@ -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/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 f7e960751e..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" @@ -349,6 +349,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 @@ -541,7 +542,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; @@ -1278,6 +1283,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) { @@ -1432,8 +1598,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()) { @@ -1449,7 +1620,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(); @@ -1488,6 +1659,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 { @@ -2584,6 +2758,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) { @@ -4168,6 +4343,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/tlog/TestTLogServer.cpp b/fdbserver/tlog/TestTLogServer.cpp index 00ed45f1ca..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" @@ -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/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.actor.cpp b/fdbserver/worker/worker.cpp similarity index 67% rename from fdbserver/worker/worker.actor.cpp rename to fdbserver/worker/worker.cpp index 6bea87ca19..0320eceb3f 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 * @@ -60,7 +60,8 @@ #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" #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,45 @@ 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>(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; } + 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 + // 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 +225,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 @@ -361,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) { @@ -560,34 +577,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 +647,7 @@ ACTOR Future registrationClient(Referenceget().present(); + bool ccInterfacePresent = ccInterface->get().present(); if (ccInterfacePresent) { TraceEvent("WorkerRegister") .detail("CCID", ccInterface->get().get().id()) @@ -642,50 +655,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>(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 +1330,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 +1353,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 +1380,10 @@ ACTOR Future healthMonitor(ReferenceonChange())) {} - when(wait(dbInfo->onChange())) {} - when(wait(enablePrimaryTxnSystemHealthCheck->onChange())) {} - } + co_await race(nextHealthCheckDelay, + ccInterface->onChange(), + dbInfo->onChange(), + enablePrimaryTxnSystemHealthCheck->onChange()); } } @@ -1834,7 +1822,7 @@ Future chaosMetricsLogger() { if (!res) co_return; - ChaosMetrics* chaosMetrics = static_cast(res); + auto* chaosMetrics = static_cast(res); chaosMetrics->clear(); while (true) { @@ -1932,7 +1920,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; @@ -2003,81 +1991,1000 @@ 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( - new AsyncVar>()); - state Reference>> rkInterf(new AsyncVar>()); - state 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; +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>(res); + + TraceEvent("LoggingRateChange", interf.id()) + .detail("OldDelay", loggingDelay) + .detail("NewLogPS", req.metricsLogsPerSecond); + if (req.metricsLogsPerSecond != 0) { + loggingDelay = 1.0 / req.metricsLogsPerSecond; + loggingTrigger = Void(); + } + } else { + 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 (const 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(interf.clientInterface.reboot.getFuture(), + serveServerDBInfoUpdates(), + 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() == 0); + co_await handleRebootRequest(std::get<0>(res)); + } +}; + +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; + 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(); + 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(); + 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. - state std::map> sharedLogs; - state Reference> activeSharedTLog(new AsyncVar()); - state WorkerCache backupWorkerCache; - state WorkerCache rangePartitionedBackupWorkerCache; - state WorkerCache logRouterCache; + std::map> sharedLogs; + auto activeSharedTLog = makeReference>(); + 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"); @@ -2087,10 +2994,10 @@ ACTOR 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 { - state Database db = + Database db = Database::createDatabase(metricsConnFile, ApiVersion::LATEST_VERSION, IsInternal::True, locality); metricsLogger = runMetrics(db, KeyRef(metricsPrefix)); db->globalConfig->trigger(samplingFrequency, samplingProfilerUpdateFrequency); @@ -2098,7 +3005,7 @@ ACTOR 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); @@ -2156,24 +3063,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,733 +3272,68 @@ ACTOR Future workerServer(Reference connRecord, healthMonitor(ccInterface, interf, locality, dbInfo, enablePrimaryTxnSystemHealthCheck)); } - loop choose { - when(UpdateServerDBInfoRequest req = waitNext(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); - } - } - } - when(RebootRequest req = waitNext(interf.clientInterface.reboot.getFuture())) { - state 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 = - wait(IAsyncFileSystem::filesystem()->open(joinPath(folder, validationFilename), - IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, - 0600)); - wait(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); - } - } - when(SetFailureInjection req = waitNext(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()); - } - } - when(ProfilerRequest req = waitNext(interf.clientInterface.profiler.getFuture())) { - state 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); - } - } - when(RecruitMasterRequest req = waitNext(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); - } - when(InitializeDataDistributorRequest req = waitNext(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); - } - when(InitializeRatekeeperRequest req = waitNext(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); - } - when(InitializeConsistencyScanRequest req = waitNext(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); - } - when(InitializeBackupRequest req = waitNext(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)); - } - } - when(InitializeRangePartitionedBackupRequest req = waitNext(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)); - } - } - when(InitializeTLogRequest req = waitNext(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); - } - when(InitializeStorageRequest req = waitNext(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(); - })); - } - } - when(InitializeCommitProxyRequest req = waitNext(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); - } - when(InitializeGrvProxyRequest req = waitNext(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); - } - when(InitializeCDCProxyRequest req = waitNext(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); - } - when(InitializeResolverRequest req = waitNext(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); - } - when(InitializeLogRouterRequest req = waitNext(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)); - } - } - when(CoordinationPingMessage m = waitNext(interf.coordinationPing.getFuture())) { - TraceEvent("CoordinationPing", interf.id()) - .detail("CCID", m.clusterControllerId) - .detail("TimeStep", m.timeStep); - } - when(SetMetricsLogRateRequest req = waitNext(interf.setMetricsRate.getFuture())) { - TraceEvent("LoggingRateChange", interf.id()) - .detail("OldDelay", loggingDelay) - .detail("NewLogPS", req.metricsLogsPerSecond); - if (req.metricsLogsPerSecond != 0) { - loggingDelay = 1.0 / req.metricsLogsPerSecond; - loggingTrigger = Void(); - } - } - when(EventLogRequest req = waitNext(interf.eventLogRequest.getFuture())) { - 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())) { - g_traceBatch.dump(); - req.reply.send(Void()); - } - when(DiskStoreRequest req = waitNext(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); - } - when(wait(loggingTrigger)) { - systemMonitor(); - loggingTrigger = delay(loggingDelay, TaskPriority::FlushTrace); - } - when(state WorkerSnapRequest snapReq = waitNext(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(); - } - } - } - when(wait(errorForwarders.getResult())) {} - when(wait(handleErrors)) {} - } + 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) { - // 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 { @@ -3134,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( @@ -3308,34 +3551,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 +3584,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 +3613,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 +3630,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 +3659,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 +3689,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 +3707,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 +3726,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 +3736,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 @@ -3698,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)) { @@ -3737,17 +3959,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 +3991,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 +4014,33 @@ 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 { + 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); } } } @@ -3866,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)); } @@ -3887,8 +4115,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), 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/DiskFailureInjection.cpp b/fdbserver/workloads/DiskFailureInjection.cpp index af4996d86b..8326f98e20 100644 --- a/fdbserver/workloads/DiskFailureInjection.cpp +++ b/fdbserver/workloads/DiskFailureInjection.cpp @@ -22,7 +22,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/WorkerEvents.h" diff --git a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp index 4bc9df90b9..15be1e5a3b 100644 --- a/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp +++ b/fdbserver/workloads/ExcludeIncludeStorageServersWorkload.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/FDBSimulationPolicy.h" #include "fdbserver/tester/workloads.h" #include "fdbrpc/simulator.h" diff --git a/fdbserver/workloads/FailoverWithSSLag.cpp b/fdbserver/workloads/FailoverWithSSLag.cpp index 4d91a0c7b2..9cdd892db4 100644 --- a/fdbserver/workloads/FailoverWithSSLag.cpp +++ b/fdbserver/workloads/FailoverWithSSLag.cpp @@ -20,7 +20,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "fdbserver/core/Knobs.h" #include "fdbserver/core/FDBSimulationPolicy.h" diff --git a/fdbserver/workloads/GcGenerations.cpp b/fdbserver/workloads/GcGenerations.cpp index 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") 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/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/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/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/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/WorkerErrors.cpp b/fdbserver/workloads/WorkerErrors.cpp index 288574c3b0..e0ac5a5dbd 100644 --- a/fdbserver/workloads/WorkerErrors.cpp +++ b/fdbserver/workloads/WorkerErrors.cpp @@ -22,7 +22,7 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/core/QuietDatabase.h" #include "fdbserver/core/ServerDBInfo.h" diff --git a/fdbserver/workloads/WriteBandwidth.cpp b/fdbserver/workloads/WriteBandwidth.cpp index 9045cebda5..c46d7067d2 100644 --- a/fdbserver/workloads/WriteBandwidth.cpp +++ b/fdbserver/workloads/WriteBandwidth.cpp @@ -23,7 +23,7 @@ #include "fdbrpc/DDSketch.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbserver/core/TesterInterface.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" diff --git a/fdbserver/workloads/WriteTagThrottling.cpp b/fdbserver/workloads/WriteTagThrottling.cpp index 66fa6d5493..bb8a23f50a 100644 --- a/fdbserver/workloads/WriteTagThrottling.cpp +++ b/fdbserver/workloads/WriteTagThrottling.cpp @@ -21,7 +21,7 @@ #include "fdbserver/core/TesterInterface.h" #include "fdbserver/tester/workloads.h" #include "BulkSetup.h" -#include "fdbserver/core/WorkerInterface.actor.h" +#include "fdbserver/core/WorkerInterface.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/TagThrottle.h" diff --git a/fdbserver/workloads/pubsub.cpp b/fdbserver/workloads/pubsub.cpp index c813a5bed4..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)); } @@ -212,7 +209,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 +330,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 +385,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 +406,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/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") { 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/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/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(); } diff --git a/flow/include/flow/CoroUtils.h b/flow/include/flow/CoroUtils.h index 904e299155..8014899bcb 100644 --- a/flow/include/flow/CoroUtils.h +++ b/flow/include/flow/CoroUtils.h @@ -348,14 +348,21 @@ struct RaceImplActor final : Actor, template void finish(T&& value) { + Result result(std::in_place_index, std::forward(value)); this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); - this->SAV::sendAndDelPromiseRef(Result(std::in_place_index, std::forward(value))); + { + auto futuresToRelease = std::move(futures); + } + this->SAV::sendAndDelPromiseRef(std::move(result)); } void fail(Error e) { this->actor_wait_state = ACTOR_WAIT_STATE_NOT_WAITING; RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(e); } @@ -364,6 +371,9 @@ struct RaceImplActor final : Actor, this->actor_wait_state = ACTOR_WAIT_STATE_CANCELLED; if (actorWaitStateIsWaiting(waitState)) { RaceImplCallback, 0, Futures...>::removeCallbacks(); + { + auto futuresToRelease = std::move(futures); + } this->SAV::sendErrorAndDelPromiseRef(actor_cancelled()); } } diff --git a/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. 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