From 7e0df8663137dd5254f76c3ad2247b43553cc75e Mon Sep 17 00:00:00 2001 From: Mark Shabanov Date: Fri, 29 May 2026 15:16:38 +0300 Subject: [PATCH 1/6] XDB-546 merge from main upstream --- .clang-tidy | 21 + .github/workflows/stale.yml | 24 + .github/workflows/windows-boost-test.yml | 2 +- .gitignore | 4 +- CMakeLists.txt | 5 +- README.md | 11 +- bindings/c/CMakeLists.txt | 5 +- bindings/c/fdb_c.cpp | 4 +- .../c/test/apitester/TesterApiWorkload.cpp | 6 +- .../TesterAtomicOpsCorrectnessWorkload.cpp | 6 +- .../apitester/TesterCorrectnessWorkload.cpp | 4 +- .../c/test/apitester/TesterKeyValueStore.cpp | 10 +- bindings/c/test/apitester/TesterTestSpec.cpp | 2 +- .../apitester/TesterTransactionExecutor.cpp | 14 +- bindings/c/test/apitester/TesterUtil.h | 6 +- bindings/c/test/apitester/TesterWorkload.cpp | 12 +- .../c/test/apitester/fdb_c_api_tester.cpp | 2 +- bindings/c/test/mako/async.cpp | 11 +- bindings/c/test/mako/async.hpp | 2 +- bindings/c/test/mako/mako.cpp | 291 +- bindings/c/test/mako/mako.hpp | 10 +- bindings/c/test/mako/mako.rst | 9 + bindings/c/test/mako/operations.cpp | 14 + bindings/c/test/mako/stats.hpp | 18 + bindings/c/test/performance_test.c | 11 +- bindings/c/test/ryw_benchmark.c | 17 +- bindings/c/test/test.h | 17 +- bindings/c/test/workloads/workloads.cpp | 2 +- bindings/flow/CMakeLists.txt | 10 +- ...toryLayer.actor.cpp => DirectoryLayer.cpp} | 301 +- bindings/flow/DirectoryPartition.h | 12 +- ....actor.cpp => HighContentionAllocator.cpp} | 34 +- bindings/flow/{Node.actor.cpp => Node.cpp} | 10 +- bindings/flow/Subspace.cpp | 4 +- .../flow/{fdb_flow.actor.cpp => fdb_flow.cpp} | 49 +- bindings/flow/tester/CMakeLists.txt | 6 +- ...ryTester.actor.cpp => DirectoryTester.cpp} | 249 +- .../tester/{Tester.actor.cpp => Tester.cpp} | 846 ++- .../flow/tester/{Tester.actor.h => Tester.h} | 60 +- bindings/go/CMakeLists.txt | 37 +- bindings/go/README.md | 10 + bindings/go/src/fdb/database.go | 5 +- bindings/go/src/fdb/doc.go | 48 +- bindings/go/src/fdb/error_codes_generated.go | 677 ++ bindings/go/src/fdb/errors.go | 35 +- bindings/go/src/fdb/errors_test.go | 27 + bindings/go/src/fdb/fdb.go | 20 +- bindings/go/src/fdb/generated.go | 73 +- bindings/go/src/internal/gen_errors/main.go | 176 + .../translate_fdb_options.go | 18 +- bindings/java/JavaWorkload.cpp | 20 +- bindings/java/fdbJNI.cpp | 106 +- .../MappedRangeQueryIntegrationTest.java | 113 +- .../apple/foundationdb/FDBTransaction.java | 5 + .../com/apple/foundationdb/Transaction.java | 18 + .../test/ConcurrentGetSetGet.java | 1 - .../foundationdb/test/SerialIteration.java | 2 - .../apple/foundationdb/test/WatchTest.java | 1 - cmake/AddFdbServerLinkTest.cmake | 8 + cmake/CompileBoost.cmake | 42 +- cmake/CompileRocksDB.cmake | 13 +- cmake/ConfigureCompiler.cmake | 30 + cmake/FDBBenchmark.cmake | 61 + cmake/FDBComponents.cmake | 33 +- cmake/FindCoroutines.cmake | 2 +- cmake/GetFmt.cmake | 23 +- cmake/RocksDBVersion.cmake | 2 +- .../benchmark-download.cmake | 7 +- cmake/utils.cmake | 5 + contrib/Joshua/scripts/correctnessTest.sh | 11 + contrib/TestHarness2/README.md | 6 + contrib/TestHarness2/test_harness/config.py | 29 +- contrib/TestHarness2/test_harness/results.py | 19 +- contrib/TestHarness2/test_harness/run.py | 46 + .../TestHarness2/test_harness/summarize.py | 17 +- contrib/benchmark_comparison.py | 12 +- contrib/joshua_logtool.py | 2 +- contrib/linenoise/CMakeLists.txt | 1 + contrib/lsan.suppressions | 11 + contrib/mockkms/CMakeLists.txt | 18 - contrib/mockkms/fault_injection.go | 179 - contrib/mockkms/get_encryption_keys.go | 321 - contrib/mockkms/mock_kms.go | 75 - contrib/mockkms/mockkms_test.go | 302 - contrib/monitoring/actor_flamegraph.cpp | 4 +- contrib/pkg_tester/requirements.txt | 2 +- contrib/replay/CMakeLists.txt | 28 +- contrib/sqlite/CMakeLists.txt | 4 +- contrib/stacktrace/CMakeLists.txt | 1 + design/AI-generated/FDB_NETWORK_PROTOCOL.md | 2666 ++++++++ design/AI-generated/README.md | 63 + .../AI-generated/diagram_01_flow_runtime.md | 76 + .../AI-generated/diagram_02_rpc_transport.md | 109 + .../AI-generated/diagram_03_client_library.md | 104 + .../diagram_04_cluster_controller.md | 107 + .../diagram_05_commit_pipeline.md | 107 + .../AI-generated/diagram_06_tlog_logsystem.md | 139 + .../AI-generated/diagram_07_storage_server.md | 119 + .../diagram_08_data_distribution.md | 129 + .../diagram_09_cluster_recovery.md | 149 + design/AI-generated/diagram_10_ratekeeper.md | 91 + design/AI-generated/diagram_11_backup_dr.md | 114 + .../diagram_12_simulation_testing.md | 155 + design/AI-generated/diagram_overview.md | 163 + .../foundationdb_subsystem_map.md | 359 ++ .../AI-generated/subsystem_01_flow_runtime.md | 378 ++ .../subsystem_02_rpc_transport.md | 340 + .../subsystem_03_client_library.md | 288 + .../subsystem_04_cluster_controller.md | 319 + .../subsystem_05_commit_pipeline.md | 305 + .../subsystem_06_tlog_logsystem.md | 243 + .../subsystem_07_storage_server.md | 766 +++ .../subsystem_08_data_distribution.md | 238 + .../subsystem_09_cluster_recovery.md | 215 + .../AI-generated/subsystem_10_ratekeeper.md | 203 + design/AI-generated/subsystem_11_backup_dr.md | 290 + .../subsystem_12_simulation_testing.md | 345 + design/AcAC.md | 15 +- design/Commit/commit.sequence | 2 +- design/backup_v2_partitioned_logs.md | 4 +- design/cluster_health_metric.md | 171 + design/coroutines.md | 111 +- design/data-distributor-internals.md | 806 ++- design/fdb-coroutines-internals.md | 727 +++ design/file-level-encryption-backup.md | 370 ++ design/global-tag-throttling.md | 131 - design/mocks3server_chaos_design.md | 3 +- design/recovery-internals.md | 6 +- design/restore_v1.md | 2 +- design/transaction-state-store.md | 2 +- ...idating_restored_data_using_one_cluster.md | 2 - documentation/coro_tutorial/tutorial.cpp | 21 +- documentation/sphinx/source/api-c.rst | 4 +- documentation/sphinx/source/backups.rst | 4 +- documentation/sphinx/source/clang-format.rst | 68 + documentation/sphinx/source/clang-tidy.rst | 186 + .../sphinx/source/command-line-interface.rst | 2 +- .../sphinx/source/developer-tools.rst | 13 + .../sphinx/source/global-configuration.rst | 4 +- documentation/sphinx/source/ha-write-path.rst | 6 +- documentation/sphinx/source/index.rst | 5 +- .../source/mr-status-json-schemas.rst.inc | 4 +- .../sphinx/source/read-write-path.rst | 2 +- .../release-notes/release-notes-700.rst | 2 +- .../release-notes/release-notes-730.rst | 10 + .../tutorial/dining_philosophers.actor.cpp | 10 +- documentation/tutorial/make_h2o.actor.cpp | 2 +- documentation/tutorial/play.actor.cpp | 2 +- documentation/tutorial/play_network.actor.cpp | 2 +- .../tutorial/print_in_order.actor.cpp | 2 +- documentation/tutorial/tutorial.actor.cpp | 21 +- fdbbackup/CMakeLists.txt | 30 +- ...eConverter.actor.cpp => FileConverter.cpp} | 119 +- ...{FileDecoder.actor.cpp => FileDecoder.cpp} | 178 +- fdbbackup/{backup.actor.cpp => backup.cpp} | 1219 ++-- fdbbackup/include/fdbbackup/FileConverter.h | 4 +- fdbbackup/tests/backup_tests_common.sh | 114 +- ...up_test.sh => blob_backup_restore_test.sh} | 66 +- fdbbackup/tests/dir_backup_test.sh | 41 +- .../tests/s3_backup_bulkdump_bulkload.sh | 6 +- ...nd.actor.cpp => AdvanceVersionCommand.cpp} | 28 +- ...mand.actor.cpp => AuditStorageCommand.cpp} | 34 +- ...pCommand.actor.cpp => BulkDumpCommand.cpp} | 133 +- ...dCommand.actor.cpp => BulkLoadCommand.cpp} | 226 +- fdbcli/CMakeLists.txt | 2 +- ...Command.actor.cpp => ConfigureCommand.cpp} | 69 +- ....actor.cpp => ConsistencyCheckCommand.cpp} | 26 +- ...d.actor.cpp => ConsistencyScanCommand.cpp} | 52 +- ...mand.actor.cpp => CoordinatorsCommand.cpp} | 110 +- ....actor.cpp => DataDistributionCommand.cpp} | 75 +- ...ugCommands.actor.cpp => DebugCommands.cpp} | 189 +- ...deCommand.actor.cpp => ExcludeCommand.cpp} | 260 +- ...ctor.cpp => ExpensiveDataCheckCommand.cpp} | 27 +- ...and.actor.cpp => FileConfigureCommand.cpp} | 40 +- ...wLineNoise.actor.cpp => FlowLineNoise.cpp} | 12 +- ...p => ForceRecoveryWithDataLossCommand.cpp} | 14 +- ...nd.actor.cpp => GetAuditStatusCommand.cpp} | 155 +- ...eCommand.actor.cpp => HotRangeCommand.cpp} | 36 +- ...nd.actor.cpp => IdempotencyIdsCommand.cpp} | 28 +- ...deCommand.actor.cpp => IncludeCommand.cpp} | 78 +- ...{KillCommand.actor.cpp => KillCommand.cpp} | 37 +- ....actor.cpp => LocationMetadataCommand.cpp} | 155 +- ...{LockCommand.actor.cpp => LockCommand.cpp} | 78 +- ...mmand.actor.cpp => MaintenanceCommand.cpp} | 75 +- ...leCommand.actor.cpp => ProfileCommand.cpp} | 36 +- ...mmand.actor.cpp => RangeConfigCommand.cpp} | 56 +- ...sCommand.actor.cpp => SetClassCommand.cpp} | 74 +- ...tCommand.actor.cpp => SnapshotCommand.cpp} | 16 +- ...tusCommand.actor.cpp => StatusCommand.cpp} | 148 +- ...ndCommand.actor.cpp => SuspendCommand.cpp} | 34 +- ...eCommand.actor.cpp => ThrottleCommand.cpp} | 65 +- ...or.cpp => TriggerDDTeamInfoLogCommand.cpp} | 20 +- ...{TssqCommand.actor.cpp => TssqCommand.cpp} | 75 +- fdbcli/Util.actor.cpp | 193 - fdbcli/Util.cpp | 465 ++ ...mand.actor.cpp => VersionEpochCommand.cpp} | 91 +- fdbcli/{fdbcli.actor.cpp => fdbcli.cpp} | 357 +- fdbcli/include/fdbcli/fdbcli.actor.h | 272 - fdbcli/include/fdbcli/fdbcli.h | 342 + fdbcli/tests/fdbcli_tests.py | 137 +- fdbclient/ActorLineageProfiler.cpp | 4 +- ...Store.actor.cpp => AsyncFileBlobStore.cpp} | 41 +- fdbclient/AsyncTaskThread.actor.cpp | 120 - .../{AuditUtils.actor.cpp => AuditUtils.cpp} | 751 ++- ...gentBase.actor.cpp => BackupAgentBase.cpp} | 539 +- ...ontainer.actor.cpp => BackupContainer.cpp} | 165 +- ...actor.cpp => BackupContainerBlobStore.cpp} | 144 +- ...BlobStore.h => BackupContainerBlobStore.h} | 33 +- ...ctor.cpp => BackupContainerFileSystem.cpp} | 1021 ++- ....cpp => BackupContainerLocalDirectory.cpp} | 67 +- .../BackupContainerLocalDirectory.h | 4 +- fdbclient/BackupTLSConfig.cpp | 16 +- fdbclient/BlobStoreCommon.cpp | 1015 +++ fdbclient/BulkLoading.cpp | 10 +- fdbclient/CMakeLists.txt | 31 +- fdbclient/ClientKnobCollection.cpp | 60 - fdbclient/ClientKnobs.cpp | 112 +- fdbclient/ClientStatusReport.cpp | 4 +- ...le.actor.cpp => ClusterConnectionFile.cpp} | 9 +- ...Key.actor.cpp => ClusterConnectionKey.cpp} | 81 +- ...tionKey.actor.h => ClusterConnectionKey.h} | 23 +- ....cpp => ClusterConnectionMemoryRecord.cpp} | 6 +- fdbclient/CoordinationInterface.cpp | 3 - ...g.actor.cpp => DataDistributionConfig.cpp} | 5 +- ...gent.actor.cpp => DatabaseBackupAgent.cpp} | 2297 +++---- fdbclient/DatabaseConfiguration.cpp | 105 +- ...eContext.actor.cpp => DatabaseContext.cpp} | 337 +- fdbclient/FDBAWSCredentialsProvider.cpp | 2 +- .../FDBAWSCredentialsProvider.h | 0 fdbclient/FDBTypes.cpp | 7 +- ...kupAgent.actor.cpp => FileBackupAgent.cpp} | 5615 +++++++++-------- fdbclient/FluentDSampleIngestor.cpp | 4 +- fdbclient/GCSBlobStore.cpp | 289 + fdbclient/GCSBlobStore.h | 59 + ...lobalConfig.actor.cpp => GlobalConfig.cpp} | 154 +- fdbclient/IKnobCollection.cpp | 126 - ...mpotencyId.actor.cpp => IdempotencyId.cpp} | 105 +- fdbclient/JsonBuilder.cpp | 1 - ...{KeyRangeMap.actor.cpp => KeyRangeMap.cpp} | 107 +- fdbclient/KnobValue.cpp | 1 - fdbclient/LocalClientAPI.cpp | 4 +- .../{include/fdbclient => }/LocalClientAPI.h | 2 +- ...agementAPI.actor.cpp => ManagementAPI.cpp} | 2022 +++--- ...itorLeader.actor.cpp => MonitorLeader.cpp} | 274 +- ....actor.cpp => MultiVersionTransaction.cpp} | 231 +- ...Reader.actor.cpp => MutationLogReader.cpp} | 70 +- fdbclient/NameLineage.cpp | 2 +- .../{include/fdbclient => }/NameLineage.h | 0 fdbclient/NativeAPI.actor.cpp | 1259 ++-- fdbclient/ParallelStream.actor.cpp | 78 - .../fdbclient => }/PartitionedLogIterator.h | 2 +- fdbclient/ProxyLoadBalance.h | 95 + .../{RESTClient.actor.cpp => RESTClient.cpp} | 64 +- .../{include/fdbclient => }/RESTClient.h | 2 +- .../{RESTUtils.actor.cpp => RESTUtils.cpp} | 26 +- fdbclient/{include/fdbclient => }/RESTUtils.h | 4 +- fdbclient/ReadYourWrites.actor.cpp | 317 +- fdbclient/RestoreInterface.cpp | 2 +- .../fdbclient => }/RestoreInterface.h | 0 fdbclient/S3BlobStore.actor.cpp | 2600 -------- fdbclient/S3BlobStore.cpp | 1698 +++++ .../{S3Client.actor.cpp => S3Client.cpp} | 732 ++- ...3Client_cli.actor.cpp => S3Client_cli.cpp} | 54 +- fdbclient/Schemas.cpp | 29 +- fdbclient/ServerKnobCollection.cpp | 61 - ...KeySpace.actor.cpp => SpecialKeySpace.cpp} | 677 +- ...tatusClient.actor.cpp => StatusClient.cpp} | 184 +- fdbclient/StorageServerInterface.cpp | 69 + fdbclient/Subspace.cpp | 4 +- fdbclient/SystemData.cpp | 255 +- ...{TagThrottle.actor.cpp => TagThrottle.cpp} | 52 +- .../{TaskBucket.actor.cpp => TaskBucket.cpp} | 648 +- fdbclient/TestKnobCollection.cpp | 80 - fdbclient/ThreadSafeTransaction.cpp | 18 +- fdbclient/{Tracing.actor.cpp => Tracing.cpp} | 84 +- fdbclient/VersionedMap.cpp | 77 - fdbclient/WriteMap.cpp | 129 +- .../bench}/BenchIdempotencyIds.cpp | 0 .../bench}/BenchIterate.cpp | 2 +- .../bench/BenchMain.cpp | 12 +- .../bench}/BenchMetadataCheck.cpp | 0 .../bench}/BenchPopulate.cpp | 2 +- .../bench}/BenchTempTagMessages.cpp | 0 .../bench}/BenchVersionVector.cpp | 0 .../BenchVersionVectorSerialization.cpp | 0 fdbclient/bench/BenchVersionedMap.cpp | 564 ++ fdbclient/bench/CMakeLists.txt | 12 + {flowbench => fdbclient/bench}/GlobalData.cpp | 1 + .../bench}/GlobalData.h | 7 +- fdbclient/include/fdbclient/AnnotateActor.h | 2 +- ...BlobStore.actor.h => AsyncFileBlobStore.h} | 148 +- fdbclient/include/fdbclient/AsyncTaskThread.h | 100 - .../include/fdbclient/AuditUtils.actor.h | 122 - fdbclient/include/fdbclient/AuditUtils.h | 203 + .../{BackupAgent.actor.h => BackupAgent.h} | 216 +- fdbclient/include/fdbclient/BackupContainer.h | 49 +- .../fdbclient/BackupContainerFileSystem.h | 30 +- .../fdbclient/BuildIdempotencyIdMutations.h | 2 +- fdbclient/include/fdbclient/BulkDumping.h | 2 + fdbclient/include/fdbclient/BulkLoading.h | 21 +- .../include/fdbclient/ClientKnobCollection.h | 51 - fdbclient/include/fdbclient/ClientKnobs.h | 352 -- fdbclient/include/fdbclient/ClientVersion.h | 2 +- .../include/fdbclient/CommitProxyInterface.h | 172 +- .../include/fdbclient/CommitTransaction.h | 10 - ...ace.actor.h => ConsistencyScanInterface.h} | 73 +- .../include/fdbclient/CoordinationInterface.h | 12 +- ...onfig.actor.h => DataDistributionConfig.h} | 22 +- .../include/fdbclient/DatabaseConfiguration.h | 37 +- fdbclient/include/fdbclient/DatabaseContext.h | 40 +- .../fdbclient/EncryptKeyProxyInterface.h | 317 - fdbclient/include/fdbclient/EventTypes.h | 34 + fdbclient/include/fdbclient/FDBTypes.h | 41 +- ...mentAPI.actor.h => GenericManagementAPI.h} | 211 +- .../include/fdbclient/GlobalConfig.actor.h | 186 - fdbclient/include/fdbclient/GlobalConfig.h | 159 +- .../include/fdbclient/GrvProxyInterface.h | 148 + ...ctor.h => HighContentionPrefixAllocator.h} | 50 +- fdbclient/include/fdbclient/IBlobStore.h | 522 ++ fdbclient/include/fdbclient/IKnobCollection.h | 79 - ...{IdempotencyId.actor.h => IdempotencyId.h} | 22 +- fdbclient/include/fdbclient/JSONDoc.h | 8 +- ...edRangeMap.actor.h => KeyBackedRangeMap.h} | 74 +- ...eyBackedTypes.actor.h => KeyBackedTypes.h} | 141 +- fdbclient/include/fdbclient/KeyRangeMap.h | 49 +- fdbclient/include/fdbclient/Knobs.h | 338 +- .../include/fdbclient/ManagementAPI.actor.h | 296 - fdbclient/include/fdbclient/ManagementAPI.h | 419 ++ fdbclient/include/fdbclient/MonitorLeader.h | 33 +- .../fdbclient/MultiVersionTransaction.h | 4 +- ...nLogReader.actor.h => MutationLogReader.h} | 36 +- fdbclient/include/fdbclient/NativeAPI.actor.h | 72 +- fdbclient/include/fdbclient/Notified.h | 4 +- .../include/fdbclient/ParallelStream.actor.h | 131 - .../include/fdbclient/RandomKeyValueUtils.h | 30 +- fdbclient/include/fdbclient/ReadYourWrites.h | 7 +- .../fdbclient/RunRYWTransaction.actor.h | 135 - .../include/fdbclient/RunRYWTransaction.h | 137 + ...unTransaction.actor.h => RunTransaction.h} | 64 +- fdbclient/include/fdbclient/S3BlobStore.h | 449 +- .../{S3Client.actor.h => S3Client.h} | 34 +- .../include/fdbclient/ServerKnobCollection.h | 49 - fdbclient/include/fdbclient/SimpleIni.h | 54 +- fdbclient/include/fdbclient/SnapshotCache.h | 7 +- ...cialKeySpace.actor.h => SpecialKeySpace.h} | 72 +- fdbclient/include/fdbclient/Status.h | 6 +- fdbclient/include/fdbclient/StatusClient.h | 2 +- .../include/fdbclient/StorageCheckpoint.h | 2 +- .../fdbclient/StorageServerInterface.h | 87 +- ...Metrics.actor.h => StorageWiggleMetrics.h} | 33 +- fdbclient/include/fdbclient/Subspace.h | 6 +- fdbclient/include/fdbclient/SystemData.h | 159 +- .../{TagThrottle.actor.h => TagThrottle.h} | 198 +- fdbclient/include/fdbclient/TaskBucket.h | 26 +- .../include/fdbclient/TestKnobCollection.h | 66 - .../include/fdbclient/ThreadSafeTransaction.h | 4 +- fdbclient/include/fdbclient/Tracing.h | 4 +- .../include/fdbclient/TransactionLineage.h | 3 +- fdbclient/include/fdbclient/Tuple.h | 2 +- .../include/fdbclient/TupleVersionstamp.h | 2 +- fdbclient/include/fdbclient/VersionVector.h | 2 +- .../include/fdbclient/VersionedMap.actor.h | 54 - fdbclient/include/fdbclient/VersionedMap.h | 260 +- fdbclient/include/fdbclient/WriteMap.h | 50 +- .../json_spirit/json_spirit_reader_template.h | 8 +- .../fdbclient/json_spirit/json_spirit_value.h | 23 +- fdbclient/sha1/SHA1.cpp | 4 +- fdbclient/{include/fdbclient => }/sha1/SHA1.h | 0 fdbclient/tests/gcp_fixture.sh | 94 + fdbclient/tests/s3client_test.sh | 49 +- fdbclient/tests/tests_common.sh | 85 +- fdbclient/vexillographer/fdb.options | 5 +- fdbctl/CMakeLists.txt | 2 +- fdbctl/ControlCommands.cpp | 27 +- fdbctl/ExcludeCommand.cpp | 13 +- fdbctl/include/fdbctl/ControlService.h | 3 +- fdbctl/protos/control_service.proto | 2 +- fdbkubernetesmonitor/CMakeLists.txt | 43 + fdbkubernetesmonitor/README.md | 6 +- fdbkubernetesmonitor/api/version_test.go | 17 +- fdbkubernetesmonitor/go.mod | 63 +- fdbkubernetesmonitor/go.sum | 185 +- .../internal/certloader/certloader.go | 26 +- fdbkubernetesmonitor/kubernetes.go | 169 +- fdbkubernetesmonitor/kubernetes_test.go | 139 +- fdbkubernetesmonitor/main.go | 6 +- fdbkubernetesmonitor/monitor.go | 183 +- fdbkubernetesmonitor/monitor_test.go | 46 +- fdbmonitor/fdbmonitor.cpp | 17 +- fdbmonitor/fdbmonitor_lib.cpp | 22 +- ...leCached.actor.cpp => AsyncFileCached.cpp} | 4 +- fdbrpc/{include/fdbrpc => }/AsyncFileChaos.h | 0 .../AsyncFileEIO.actor.h => AsyncFileEIO.h} | 210 +- ...ypted.actor.cpp => AsyncFileEncrypted.cpp} | 163 +- .../AsyncFileKAIO.actor.h => AsyncFileKAIO.h} | 150 +- fdbrpc/AsyncFileNonDurable.actor.cpp | 226 - fdbrpc/AsyncFileNonDurable.cpp | 215 + ...FileWinASIO.actor.h => AsyncFileWinASIO.h} | 16 +- fdbrpc/AsyncFileWriteChecker.cpp | 12 +- ...hecker.actor.h => AsyncFileWriteChecker.h} | 33 +- fdbrpc/Base64Decode.cpp | 4 +- fdbrpc/{include/fdbrpc => }/Base64Decode.h | 0 fdbrpc/Base64Encode.cpp | 2 +- fdbrpc/{include/fdbrpc => }/Base64Encode.h | 0 fdbrpc/CMakeLists.txt | 25 +- .../{include/fdbrpc => }/ContinuousSample.h | 0 ...DSketchTest.actor.cpp => DDSketchTest.cpp} | 1 - ...reMonitor.actor.cpp => FailureMonitor.cpp} | 48 +- fdbrpc/FileTransfer.cpp | 2 +- fdbrpc/{include/fdbrpc => }/FileTransfer.h | 3 +- fdbrpc/FlowGrpc.cpp | 4 +- fdbrpc/FlowGrpcTests.actor.cpp | 2 +- fdbrpc/FlowGrpcTests.cpp | 6 +- fdbrpc/{include/fdbrpc => }/FlowGrpcTests.h | 2 +- fdbrpc/FlowTests.actor.cpp | 143 +- ...wTransport.actor.cpp => FlowTransport.cpp} | 478 +- fdbrpc/{HTTP.actor.cpp => HTTP.cpp} | 255 +- .../{HTTPServer.actor.cpp => HTTPServer.cpp} | 202 +- ...lthMonitor.actor.cpp => HealthMonitor.cpp} | 4 +- fdbrpc/IPAllowList.cpp | 4 +- fdbrpc/JsonWebKeySet.cpp | 6 +- fdbrpc/{include/fdbrpc => }/JsonWebKeySet.h | 4 +- fdbrpc/Locality.cpp | 182 + fdbrpc/Net2FileSystem.cpp | 111 +- fdbrpc/Net2FileSystemTests.cpp | 156 + fdbrpc/ReplicationPolicy.cpp | 29 +- fdbrpc/ReplicationUtils.cpp | 221 +- ...on.actor.cpp => SimExternalConnection.cpp} | 99 +- .../fdbrpc => }/SimExternalConnection.h | 4 +- fdbrpc/{Stats.actor.cpp => Stats.cpp} | 37 +- .../bench}/BenchAsyncFileWriteCheckerLRU.cpp | 9 +- .../bench}/BenchIONet2.actor.cpp | 2 +- fdbrpc/bench/BenchMain.cpp | 35 + {flowbench => fdbrpc/bench}/BenchSamples.cpp | 5 +- .../bench}/BenchSelectReplicas.cpp | 5 +- .../utils.go => fdbrpc/bench/BenchSupport.h | 53 +- fdbrpc/bench/CMakeLists.txt | 14 + fdbrpc/dsltest.actor.cpp | 19 +- ...ncFileCached.actor.h => AsyncFileCached.h} | 102 +- fdbrpc/include/fdbrpc/AsyncFileEncrypted.h | 18 +- ...nDurable.actor.h => AsyncFileNonDurable.h} | 241 +- ...ReadAhead.actor.h => AsyncFileReadAhead.h} | 50 +- fdbrpc/include/fdbrpc/DDSketch.h | 2 +- fdbrpc/include/fdbrpc/FlowGrpc.h | 8 +- fdbrpc/include/fdbrpc/FlowProcess.actor.h | 94 - fdbrpc/include/fdbrpc/LoadBalance.actor.h | 76 +- fdbrpc/include/fdbrpc/Locality.h | 125 +- fdbrpc/include/fdbrpc/MultiInterface.h | 6 +- fdbrpc/include/fdbrpc/Net2FileSystem.h | 2 +- fdbrpc/include/fdbrpc/PerfMetric.h | 4 +- fdbrpc/include/fdbrpc/RangeMap.h | 2 +- fdbrpc/include/fdbrpc/Replication.h | 10 +- fdbrpc/include/fdbrpc/ReplicationPolicy.h | 3 +- fdbrpc/include/fdbrpc/ReplicationTypes.h | 10 +- fdbrpc/include/fdbrpc/SimulatorKillType.h | 4 +- fdbrpc/include/fdbrpc/SimulatorProcessInfo.h | 2 +- fdbrpc/include/fdbrpc/Stats.h | 6 +- fdbrpc/include/fdbrpc/TimedRequest.h | 2 + fdbrpc/include/fdbrpc/fdbrpc.h | 33 +- ...{genericactors.actor.h => genericactors.h} | 371 +- fdbrpc/include/fdbrpc/grpc/AsyncGrpcClient.h | 6 +- .../include/fdbrpc/grpc/AsyncTaskExecutor.h | 7 +- fdbrpc/include/fdbrpc/grpc/Credentials.h | 4 +- ...{networksender.actor.h => networksender.h} | 25 +- fdbrpc/include/fdbrpc/simulator.h | 181 +- fdbrpc/libeio/eio.c | 3 +- fdbrpc/{include/fdbrpc => }/linux_kaio.h | 0 fdbrpc/{sim2.actor.cpp => sim2.cpp} | 679 +- fdbrpc/sim_validation.cpp | 38 +- fdbrpc/swift_sim2_hooks.cpp | 2 +- ...uthzTlsTest.actor.cpp => AuthzTlsTest.cpp} | 19 +- fdbrpc/tests/CMakeLists.txt | 6 +- ...dbrpc_bench.actor.cpp => fdbrpc_bench.cpp} | 100 +- .../BackupWorkerRangePartitioned.actor.cpp | 252 - fdbserver/CMakeLists.txt | 79 +- fdbserver/GlobalTagThrottler.actor.cpp | 1146 ---- fdbserver/GrvProxyTagThrottler.actor.cpp | 512 -- fdbserver/LatencyBandsMap.actor.cpp | 131 - fdbserver/RemoteIKeyValueStore.actor.cpp | 251 - fdbserver/RestoreApplier.actor.cpp | 825 --- fdbserver/RestoreCommon.actor.cpp | 367 -- fdbserver/RestoreController.actor.cpp | 1222 ---- fdbserver/RestoreLoader.actor.cpp | 1583 ----- fdbserver/RestoreRoleCommon.actor.cpp | 219 - fdbserver/RestoreUtil.actor.cpp | 79 - fdbserver/RestoreWorker.actor.cpp | 423 -- fdbserver/RestoreWorkerInterface.actor.cpp | 102 - fdbserver/ServerThroughputTracker.cpp | 192 - ...Cluster.actor.cpp => SimulatedCluster.cpp} | 640 +- fdbserver/TSSMappingUtil.actor.cpp | 76 - .../BackupPartitionMap.cpp} | 124 +- fdbserver/backupworker/BackupPartitionMap.h | 44 + .../BackupRangePartitionedProgress.cpp | 73 + .../BackupRangePartitionedProgress.h | 65 + .../BackupWorker.cpp} | 674 +- .../BackupWorkerRangePartitioned.cpp | 1024 +++ fdbserver/backupworker/CMakeLists.txt | 15 + .../fdbserver/backupworker/BackupWorker.h | 29 + fdbserver/clustercontroller/CMakeLists.txt | 15 + .../ClusterController.actor.cpp | 747 ++- .../ClusterController.h} | 214 +- .../ClusterHealthIFactor.cpp | 287 + .../clustercontroller/ClusterHealthIFactor.h | 94 + .../ClusterHealthMonitor.cpp | 274 + .../clustercontroller/ClusterHealthMonitor.h | 107 + .../ClusterHealthMonitorTesting.cpp | 407 ++ .../ClusterRecovery.cpp} | 859 +-- .../ClusterRecovery.h} | 80 +- .../clustercontroller/RatekeeperMonitor.cpp | 62 + .../clustercontroller/RatekeeperMonitor.h | 46 + .../SingletonRoles.h | 12 +- .../Status.cpp} | 1432 +++-- .../Status.h} | 48 +- .../clustercontroller/ClusterController.h | 34 + fdbserver/commitproxy/CMakeLists.txt | 17 + .../CommitProxyServer.cpp} | 1369 ++-- .../ProxyCommitData.h} | 88 +- .../fdbserver/commitproxy/CommitProxyServer.h | 32 + fdbserver/consistencyscan/CMakeLists.txt | 12 + .../ConsistencyScan.cpp} | 871 +-- .../consistencyscan/ConsistencyScan.h | 64 + fdbserver/coordinator/CMakeLists.txt | 16 + .../Coordination.cpp} | 737 ++- .../OnDemandStore.cpp} | 13 +- .../fdbserver => coordinator}/OnDemandStore.h | 2 +- .../coordinator/CoordinationServer.h | 30 + .../{ => core}/AccumulativeChecksumUtil.cpp | 6 +- .../BackupProgress.cpp} | 45 +- .../BulkDumpUtil.cpp} | 165 +- .../BulkLoadUtil.cpp} | 455 +- fdbserver/core/CMakeLists.txt | 34 + .../CoordinatedState.cpp} | 199 +- fdbserver/core/CoordinationInterface.cpp | 55 + fdbserver/core/DataMovement.cpp | 82 + fdbserver/core/FDBSimulationPolicy.cpp | 401 ++ fdbserver/{ => core}/LatencyBandConfig.cpp | 10 +- fdbserver/{ => core}/LeaderElection.actor.cpp | 68 +- fdbserver/{ => core}/LogSystemConfig.cpp | 6 +- fdbserver/core/MasterInterface.cpp | 4 + .../{MoveKeys.actor.cpp => core/MoveKeys.cpp} | 1705 ++--- fdbserver/{ => core}/MutationTracking.cpp | 24 +- fdbserver/core/OpenDatabase.cpp | 56 + .../QuietDatabase.cpp} | 638 +- fdbserver/core/RatekeeperLimitReasons.cpp | 55 + .../RocksDBCheckpointUtils.cpp} | 338 +- fdbserver/core/SeedShardServers.cpp | 101 + .../ServerCheckpoint.cpp} | 44 +- {fdbclient => fdbserver/core}/ServerKnobs.cpp | 261 +- fdbserver/core/ShardSizing.cpp | 102 + fdbserver/core/SimulatorSupport.cpp | 7 + .../StorageMetrics.cpp} | 196 +- fdbserver/core/TSSMappingUtil.cpp | 48 + .../WaitFailure.cpp} | 91 +- fdbserver/core/WorkerEvents.cpp | 55 + fdbserver/core/WorkerInterface.cpp | 33 + fdbserver/core/WorkerSupport.cpp | 243 + fdbserver/core/WorkloadKeys.cpp | 23 + .../core}/AccumulativeChecksumUtil.h | 2 +- .../include/fdbserver/core}/BackupInterface.h | 0 .../include/fdbserver/core/BackupProgress.h} | 22 +- .../fdbserver/core/BackupProgressTypes.h | 31 + .../include/fdbserver/core/BulkDumpUtil.h} | 55 +- .../include/fdbserver/core/BulkLoadUtil.h | 80 + .../include/fdbserver/core/ConflictBatch.h | 37 + .../fdbserver/core}/CoordinatedState.h | 4 +- .../fdbserver/core}/CoordinationInterface.h | 16 +- .../include/fdbserver/core}/DBCoreState.h | 11 +- .../core}/DataDistributorInterface.h | 23 +- .../include/fdbserver/core/DataMovement.h | 26 + .../fdbserver/core/FDBSimulationPolicy.h | 117 + .../include/fdbserver/core}/IDiskQueue.h | 10 +- .../core/include/fdbserver/core/Knobs.h | 180 +- .../fdbserver/core}/LatencyBandConfig.h | 0 .../include/fdbserver/core}/LeaderElection.h | 0 .../fdbserver/core}/LogProtocolMessage.h | 0 .../include/fdbserver/core}/LogSystemConfig.h | 11 +- .../include/fdbserver/core}/MasterInterface.h | 7 +- .../include/fdbserver/core/MoveKeys.h} | 48 +- .../fdbserver/core}/MutationTracking.h | 0 .../fdbserver/core}/OTELSpanContextMessage.h | 2 +- .../include/fdbserver/core/QuietDatabase.h | 61 + .../fdbserver/core}/RatekeeperInterface.h | 11 +- .../fdbserver/core/RatekeeperLimitReasons.h | 44 + .../include/fdbserver/core}/RecoveryState.h | 2 +- .../fdbserver/core}/ResolverInterface.h | 0 .../fdbserver/core/RocksDBCheckpointUtils.h} | 27 +- .../include/fdbserver/core/SeedShardServers.h | 32 + .../fdbserver/core/ServerCheckpoint.h} | 31 +- .../include/fdbserver/core/ServerDBInfo.h} | 38 +- .../include/fdbserver/core/ShardMetrics.h | 49 + .../core/include/fdbserver/core/ShardSizing.h | 41 + .../fdbserver/core}/SpanContextMessage.h | 2 +- .../include/fdbserver/core/StorageMetrics.h} | 105 +- .../include/fdbserver/core}/TLogInterface.h | 8 +- .../include/fdbserver/core/TSSMappingUtil.h | 35 + .../include/fdbserver/core/TesterInterface.h} | 38 +- .../core/include/fdbserver/core/WaitFailure.h | 42 + .../include/fdbserver/core/WorkerEvents.h | 34 + .../fdbserver/core}/WorkerInterface.actor.h | 192 +- .../include/fdbserver/core/WorkloadKeys.h | 32 + fdbserver/coroimpl/CoroFlow.actor.cpp | 2 +- fdbserver/coroimpl/CoroFlowCoro.actor.cpp | 4 +- fdbserver/datadistributor/AuditUtilsTests.cpp | 407 ++ fdbserver/datadistributor/CMakeLists.txt | 14 + .../DDRelocationQueue.actor.cpp | 725 +-- .../DDRelocationQueue.h | 16 +- .../DDShardTracker.cpp} | 574 +- fdbserver/datadistributor/DDSharedContext.cpp | 34 + .../DDTeamCollection.actor.cpp | 1075 ++-- .../DDTxnProcessor.cpp} | 480 +- .../DataDistribution.cpp} | 2490 ++++---- .../ExclusionTracker.h} | 80 +- .../MockGlobalState.cpp} | 125 +- .../MovingWindow.h | 4 +- .../ShardsAffectedByTeamFailure.cpp | 6 +- .../datadistributor/SimulatedCluster.cpp | 96 + .../TCInfo.cpp} | 112 +- .../datadistributor}/DDShardTracker.h | 4 +- .../datadistributor}/DDSharedContext.h | 23 +- .../datadistributor}/DDTeamCollection.h | 37 +- .../datadistributor}/DDTxnProcessor.h | 8 +- .../datadistributor/DataDistribution.h} | 126 +- .../datadistributor}/DataDistributionTeam.h | 4 +- .../datadistributor/DataDistributor.h | 30 + .../datadistributor}/MockDataDistributor.h | 6 +- .../datadistributor}/MockGlobalState.h | 15 +- .../ShardsAffectedByTeamFailure.h | 0 .../datadistributor}/SimulatedCluster.h | 0 .../fdbserver/datadistributor}/TCInfo.h | 4 +- .../{fdbserver.actor.cpp => fdbserver.cpp} | 384 +- fdbserver/grvproxy/CMakeLists.txt | 15 + .../GrvProxyServer.cpp} | 640 +- fdbserver/grvproxy/GrvQueueDelay.cpp | 91 + fdbserver/grvproxy/GrvQueueDelay.h | 54 + fdbserver/grvproxy/GrvQueueDelayTests.cpp | 181 + .../GrvTransactionRateInfo.cpp} | 34 +- .../GrvTransactionRateInfo.h | 4 + .../fdbserver/grvproxy/GrvProxyServer.h | 31 + .../include/fdbserver/BulkLoadUtil.actor.h | 91 - .../include/fdbserver/FDBExecHelper.actor.h | 91 - .../include/fdbserver/GrvProxyTagThrottler.h | 104 - fdbserver/include/fdbserver/IKeyValueStore.h | 82 - fdbserver/include/fdbserver/KmsConnector.h | 48 - fdbserver/include/fdbserver/LatencyBandsMap.h | 60 - fdbserver/include/fdbserver/LogSystem.h | 943 --- fdbserver/include/fdbserver/NetworkTest.h | 6 +- fdbserver/include/fdbserver/QuietDatabase.h | 65 - .../include/fdbserver/RESTKmsConnectorUtils.h | 116 - fdbserver/include/fdbserver/RESTSimKmsVault.h | 41 - .../fdbserver/RemoteIKeyValueStore.actor.h | 498 -- .../include/fdbserver/RestoreApplier.actor.h | 417 -- .../include/fdbserver/RestoreCommon.actor.h | 391 -- .../fdbserver/RestoreController.actor.h | 464 -- .../include/fdbserver/RestoreLoader.actor.h | 244 - .../fdbserver/RestoreRoleCommon.actor.h | 130 - fdbserver/include/fdbserver/RestoreUtil.h | 113 - .../include/fdbserver/RestoreWorker.actor.h | 74 - .../fdbserver/RestoreWorkerInterface.actor.h | 739 --- .../fdbserver/ServerThroughputTracker.h | 78 - .../fdbserver/SimEncryptKmsProxy.actor.h | 152 - .../include/fdbserver/TSSMappingUtil.actor.h | 50 - .../fdbserver/TagPartitionedLogSystem.actor.h | 451 -- fdbserver/include/fdbserver/WaitFailure.h | 50 - .../fdbserver => kvstore}/ArtMutationBuffer.h | 10 +- fdbserver/kvstore/CMakeLists.txt | 29 + .../fdbserver => kvstore}/DeltaTree.h | 10 +- .../DiskQueue.cpp} | 384 +- .../FDBExecHelper.cpp} | 237 +- .../IKeyValueContainer.h | 4 +- fdbserver/{ => kvstore}/IKeyValueStore.cpp | 20 +- fdbserver/{ => kvstore}/IPager.cpp | 6 +- .../{include/fdbserver => kvstore}/IPager.h | 51 + .../KeyValueStoreCompressTestData.cpp} | 49 +- .../KeyValueStoreMemory.cpp} | 183 +- .../KeyValueStoreRocksDB.actor.cpp | 186 +- .../KeyValueStoreSQLite.cpp} | 123 +- .../KeyValueStoreShardedRocksDB.actor.cpp | 159 +- .../fdbserver => kvstore}/RadixTree.h | 8 +- fdbserver/{ => kvstore}/RocksDBCommon.cpp | 4 +- .../fdbserver => kvstore}/RocksDBCommon.h | 0 .../RocksDBLogForwarder.cpp} | 15 +- .../RocksDBLogForwarder.h | 0 .../TransactionStoreMutationTracking.cpp | 8 +- .../TransactionStoreMutationTracking.h | 0 fdbserver/{ => kvstore}/VFSAsync.cpp | 34 +- .../{include/fdbserver => kvstore}/VFSAsync.h | 0 .../{ => kvstore}/VersionedBTree.actor.cpp | 1512 ++--- .../{ => kvstore}/VersionedBTreeDebug.cpp | 4 +- .../VersionedBTreeDebug.h | 0 .../{include/fdbserver => kvstore}/art.h | 6 +- .../{include/fdbserver => kvstore}/art_impl.h | 0 .../include/fdbserver/kvstore/FDBExecHelper.h | 57 + .../include/fdbserver/kvstore/IDiskQueue.h | 33 + .../fdbserver/kvstore/IKeyValueStore.h | 97 +- .../include/fdbserver/kvstore/KVFileUtils.h} | 18 +- .../fdbserver => kvstore}/template_fdb.h | 0 fdbserver/logrouter/CMakeLists.txt | 13 + fdbserver/{ => logrouter}/LogRouter.cpp | 44 +- .../include/fdbserver/logrouter/LogRouter.h | 31 + .../{ => logsystem}/ApplyMetadataMutation.cpp | 60 +- fdbserver/logsystem/CMakeLists.txt | 16 + .../{LogSystem.cpp => logsystem/LogSet.cpp} | 159 +- .../LogSystem.cpp} | 1455 +++-- .../LogSystemDiskQueueAdapter.cpp} | 106 +- fdbserver/logsystem/LogSystemFactory.cpp | 44 + .../LogSystemPeekCursor.cpp} | 762 +-- .../LogSystemRecoveryTests.cpp} | 30 +- .../logsystem}/ApplyMetadataMutation.h | 57 +- .../include/fdbserver/logsystem/LogSystem.h | 692 ++ .../logsystem}/LogSystemDiskQueueAdapter.h | 19 +- .../fdbserver/logsystem/LogSystemFactory.h | 52 + .../fdbserver/logsystem/LogSystemTypes.h | 358 ++ fdbserver/masterserver.swift | 494 -- fdbserver/mocks3/CMakeLists.txt | 11 + .../MockS3Server.cpp} | 481 +- .../MockS3ServerChaos.cpp} | 86 +- .../include/fdbserver/mocks3}/MockS3Server.h | 2 +- .../fdbserver/mocks3}/MockS3ServerChaos.h | 2 +- ...{networktest.actor.cpp => networktest.cpp} | 397 +- fdbserver/ratekeeper/CMakeLists.txt | 14 + .../Ratekeeper.cpp} | 1071 ++-- .../fdbserver => ratekeeper}/Ratekeeper.h | 64 +- fdbserver/ratekeeper/RatekeeperLimits.cpp | 41 + .../RkTagThrottleCollection.cpp | 4 +- .../RkTagThrottleCollection.h | 9 +- .../TagThrottler.cpp} | 49 +- .../fdbserver => ratekeeper}/TagThrottler.h | 52 +- .../fdbserver/ratekeeper/Ratekeeper.h} | 17 +- fdbserver/resolver/CMakeLists.txt | 15 + .../ConflictSet.cpp} | 53 +- .../fdbserver => resolver}/ConflictSet.h | 13 +- .../Resolver.cpp} | 258 +- fdbserver/{ => resolver}/ResolverBug.cpp | 2 +- .../include/fdbserver/resolver/Resolver.h | 31 + .../include/fdbserver/resolver}/ResolverBug.h | 6 +- fdbserver/sequencer/CMakeLists.txt | 14 + .../MasterData.h} | 92 +- .../ResolutionBalancer.cpp} | 50 +- .../ResolutionBalancer.h} | 25 +- .../fdbserver/sequencer/MasterServer.h | 35 + .../masterserver.cpp} | 467 +- fdbserver/storageserver/CMakeLists.txt | 17 + .../ReadLatencySamples.cpp | 4 +- .../ReadLatencySamples.h | 0 .../StorageServerUtils.cpp | 4 +- .../StorageServerUtils.h | 2 +- .../TransactionTagCounter.cpp} | 64 +- .../TransactionTagCounter.h | 2 +- .../storageserver}/StorageCorruptionBug.h | 8 +- .../fdbserver/storageserver/StorageServer.h | 49 + .../storageserver.actor.cpp | 5217 ++++++++------- fdbserver/test.cpp | 3324 ---------- fdbserver/tester/CMakeLists.txt | 17 + fdbserver/tester/ConsistencyChecker.cpp | 760 +++ fdbserver/tester/ConsistencyChecker.h | 47 + .../CustomShardConfigWorkload.cpp} | 57 +- fdbserver/tester/DatabaseMaintenance.cpp | 264 + fdbserver/tester/DatabaseMaintenance.h | 37 + .../{ => tester}/KnobProtectiveGroups.cpp | 9 +- fdbserver/tester/TestSpecParser.cpp | 413 ++ fdbserver/tester/TestSpecParser.h | 35 + fdbserver/tester/TesterServer.cpp | 623 ++ fdbserver/tester/TesterServer.h | 28 + fdbserver/tester/WorkloadUtils.cpp | 439 ++ .../fdbserver/tester}/KnobProtectiveGroups.h | 10 +- .../fdbserver/tester/TestEncryptionUtils.h | 29 + .../include/fdbserver/tester/WorkloadUtils.h | 129 + .../tester/include/fdbserver/tester/tester.h | 51 + .../include/fdbserver/tester/workloads.h} | 194 +- fdbserver/tester/test.cpp | 1085 ++++ fdbserver/tlog/CMakeLists.txt | 15 + .../TLogServer.cpp} | 1451 +++-- .../TestTLogServer.cpp} | 194 +- .../TestTLogServer.h} | 35 +- .../tlog/include/fdbserver/tlog/TLogServer.h | 49 + fdbserver/worker/CMakeLists.txt | 54 + .../MetricClient.cpp} | 25 +- .../fdbserver => worker}/MetricClient.h | 11 +- fdbserver/{ => worker}/MetricLogger.actor.cpp | 20 +- .../fdbserver => worker}/MetricLogger.actor.h | 18 +- .../RoleLineage.cpp} | 2 +- .../RoleLineage.h} | 31 +- .../worker/include/fdbserver/worker/Worker.h | 50 + fdbserver/{ => worker}/worker.actor.cpp | 923 ++- ...rrectness.actor.cpp => ApiCorrectness.cpp} | 314 +- ...{ApiWorkload.actor.cpp => ApiWorkload.cpp} | 132 +- .../fdbserver => }/workloads/ApiWorkload.h | 14 +- fdbserver/workloads/AsyncFile.cpp | 4 +- .../AsyncFile.h} | 52 +- ...ess.actor.cpp => AsyncFileCorrectness.cpp} | 77 +- ...ncFileRead.actor.cpp => AsyncFileRead.cpp} | 95 +- ...FileWrite.actor.cpp => AsyncFileWrite.cpp} | 43 +- .../{AtomicOps.actor.cpp => AtomicOps.cpp} | 175 +- ....actor.cpp => AtomicOpsApiCorrectness.cpp} | 398 +- ...micRestore.actor.cpp => AtomicRestore.cpp} | 105 +- ...tchover.actor.cpp => AtomicSwitchover.cpp} | 107 +- ...r.cpp => AutomaticIdempotencyWorkload.cpp} | 247 +- ...tors.actor.cpp => BackgroundSelectors.cpp} | 101 +- .../{Backup.actor.cpp => Backup.cpp} | 186 +- ...kupAndParallelRestoreCorrectness.actor.cpp | 807 --- .../BackupAndRestoreValidation.actor.cpp | 246 - .../workloads/BackupAndRestoreValidation.cpp | 238 + ...ctness.actor.cpp => BackupCorrectness.cpp} | 557 +- ...r.cpp => BackupCorrectnessPartitioned.cpp} | 398 +- ....actor.cpp => BackupS3BlobCorrectness.cpp} | 475 +- ...oDBAbort.actor.cpp => BackupToDBAbort.cpp} | 68 +- ...ss.actor.cpp => BackupToDBCorrectness.cpp} | 421 +- ...pgrade.actor.cpp => BackupToDBUpgrade.cpp} | 284 +- ...{BulkDumping.actor.cpp => BulkDumping.cpp} | 279 +- .../{BulkLoad.actor.cpp => BulkLoad.cpp} | 36 +- ...{BulkLoading.actor.cpp => BulkLoading.cpp} | 347 +- .../BulkSetup.h} | 201 +- fdbserver/workloads/CMakeLists.txt | 17 + fdbserver/workloads/ChangeConfig.actor.cpp | 231 - fdbserver/workloads/ChangeConfig.cpp | 229 + ...leRange.actor.cpp => ClearSingleRange.cpp} | 36 +- ...lientMetric.actor.cpp => ClientMetric.cpp} | 121 +- ...> ClientTransactionProfileCorrectness.cpp} | 79 +- ...tWorkload.actor.cpp => ClientWorkload.cpp} | 155 +- ...emoteTLog.actor.cpp => ClogRemoteTLog.cpp} | 125 +- ...ion.actor.cpp => ClogSingleConnection.cpp} | 9 +- .../{ClogTlog.actor.cpp => ClogTlog.cpp} | 82 +- ...tBugCheck.actor.cpp => CommitBugCheck.cpp} | 116 +- ...tabase.actor.cpp => ConfigureDatabase.cpp} | 159 +- ...flictRange.actor.cpp => ConflictRange.cpp} | 196 +- ...cyCheck.actor.cpp => ConsistencyCheck.cpp} | 454 +- ...t.actor.cpp => ConsistencyCheckUrgent.cpp} | 313 +- ...{CpuProfiler.actor.cpp => CpuProfiler.cpp} | 57 +- .../workloads/{Cycle.actor.cpp => Cycle.cpp} | 104 +- .../{DDBalance.actor.cpp => DDBalance.cpp} | 127 +- .../{DDMetrics.actor.cpp => DDMetrics.cpp} | 37 +- ...Exclude.actor.cpp => DDMetricsExclude.cpp} | 47 +- ....actor.cpp => DataDistributionMetrics.cpp} | 151 +- ...ecovery.actor.cpp => DataLossRecovery.cpp} | 170 +- .../workloads/{DcLag.actor.cpp => DcLag.cpp} | 65 +- ....actor.cpp => DifferentClustersSameRV.cpp} | 243 +- ...urability.actor.cpp => DiskDurability.cpp} | 109 +- ...yTest.actor.cpp => DiskDurabilityTest.cpp} | 92 +- ...ion.actor.cpp => DiskFailureInjection.cpp} | 152 +- ... ExcludeIncludeStorageServersWorkload.cpp} | 71 +- .../ExpectStableThroughput.actor.cpp | 133 - ...orkload.actor.cpp => ExternalWorkload.cpp} | 33 +- ...hSSLag.actor.cpp => FailoverWithSSLag.cpp} | 128 +- ...hes.actor.cpp => FastTriggeredWatches.cpp} | 101 +- .../{FileSystem.actor.cpp => FileSystem.cpp} | 160 +- ...tness.actor.cpp => FuzzApiCorrectness.cpp} | 262 +- fdbserver/workloads/GcGenerations.actor.cpp | 221 - fdbserver/workloads/GcGenerations.cpp | 267 + ...ze.actor.cpp => GetEstimatedRangeSize.cpp} | 49 +- ...ppedRange.actor.cpp => GetMappedRange.cpp} | 427 +- ...ngeStream.actor.cpp => GetRangeStream.cpp} | 98 +- ...eStore.actor.cpp => HTTPKeyValueStore.cpp} | 162 +- ...ricsApi.actor.cpp => HealthMetricsApi.cpp} | 71 +- ...HighContentionPrefixAllocatorWorkload.cpp} | 89 +- ....cpp => IDDTxnProcessorApiCorrectness.cpp} | 273 +- .../{Increment.actor.cpp => Increment.cpp} | 65 +- ...Backup.actor.cpp => IncrementalBackup.cpp} | 251 +- .../{IndexScan.actor.cpp => IndexScan.cpp} | 88 +- .../{Inventory.actor.cpp => Inventory.cpp} | 72 +- ...{KVStoreTest.actor.cpp => KVStoreTest.cpp} | 120 +- .../{KillRegion.actor.cpp => KillRegion.cpp} | 69 +- ...tekeeper.actor.cpp => LocalRatekeeper.cpp} | 80 +- ...ockDatabase.actor.cpp => LockDatabase.cpp} | 91 +- ...y.actor.cpp => LockDatabaseFrequently.cpp} | 40 +- .../{LogMetrics.actor.cpp => LogMetrics.cpp} | 50 +- .../{LowLatency.actor.cpp => LowLatency.cpp} | 62 +- ...trition.actor.cpp => MachineAttrition.cpp} | 228 +- .../workloads/{Mako.actor.cpp => Mako.cpp} | 482 +- fdbserver/workloads/MaxGrvQueueDelay.cpp | 208 + fdbserver/workloads/MemoryKeyValueStore.cpp | 14 +- .../workloads/MemoryKeyValueStore.h | 2 +- ...yLifetime.actor.cpp => MemoryLifetime.cpp} | 99 +- ...ricLogging.actor.cpp => MetricLogging.cpp} | 33 +- .../{MiniCycle.actor.cpp => MiniCycle.cpp} | 127 +- ...eadWrite.actor.cpp => MockDDReadWrite.cpp} | 11 +- .../{MockDDTest.actor.cpp => MockDDTest.cpp} | 7 +- .../fdbserver => }/workloads/MockDDTest.h | 8 +- ...or.cpp => MockDDTrackerShardEvaluator.cpp} | 41 +- ...r.cpp => MutationLogReaderCorrectness.cpp} | 46 +- fdbserver/workloads/ParallelRestore.actor.cpp | 61 - ...{Performance.actor.cpp => Performance.cpp} | 78 +- .../PerpetualWiggleStatsWorkload.actor.cpp | 241 - .../PerpetualWiggleStatsWorkload.cpp | 233 + ...rpetualWiggleStorageMigrationWorkload.cpp} | 117 +- ...rdMove.actor.cpp => PhysicalShardMove.cpp} | 448 +- .../workloads/{Ping.actor.cpp => Ping.cpp} | 111 +- ...dpoints.actor.cpp => PrivateEndpoints.cpp} | 48 +- ...lVersion.actor.cpp => ProtocolVersion.cpp} | 18 +- ...ultiples.actor.cpp => PubSubMultiples.cpp} | 54 +- .../{QueuePush.actor.cpp => QueuePush.cpp} | 50 +- .../{RYWDisable.actor.cpp => RYWDisable.cpp} | 44 +- fdbserver/workloads/RYWPerformance.actor.cpp | 314 - fdbserver/workloads/RYWPerformance.cpp | 318 + ...mClogging.actor.cpp => RandomClogging.cpp} | 74 +- ...mMoveKeys.actor.cpp => RandomMoveKeys.cpp} | 109 +- ...angeLock.actor.cpp => RandomRangeLock.cpp} | 57 +- ...mSelector.actor.cpp => RandomSelector.cpp} | 436 +- .../{RangeLock.actor.cpp => RangeLock.cpp} | 258 +- ...fterWrite.actor.cpp => ReadAfterWrite.cpp} | 72 +- ...tection.actor.cpp => ReadHotDetection.cpp} | 108 +- .../{ReadWrite.actor.cpp => ReadWrite.cpp} | 613 +- .../ReadWriteWorkload.h} | 62 +- ...fely.actor.cpp => RemoveServersSafely.cpp} | 247 +- ...ys.actor.cpp => ReportConflictingKeys.cpp} | 80 +- ...{ResolverBug.actor.cpp => ResolverBug.cpp} | 78 +- .../{Restore.actor.cpp => Restore.cpp} | 212 +- ...toreBackup.actor.cpp => RestoreBackup.cpp} | 109 +- ...anges.actor.cpp => RestoreMultiRanges.cpp} | 145 +- ...dation.actor.cpp => RestoreValidation.cpp} | 178 +- .../{Rollback.actor.cpp => Rollback.cpp} | 47 +- ...rectness.actor.cpp => RyowCorrectness.cpp} | 91 +- ...orkload.actor.cpp => S3ClientWorkload.cpp} | 96 +- ...{SaveAndKill.actor.cpp => SaveAndKill.cpp} | 43 +- ...ness.actor.cpp => SelectorCorrectness.cpp} | 101 +- ...zability.actor.cpp => Serializability.cpp} | 343 +- .../{Sideband.actor.cpp => Sideband.cpp} | 88 +- ...andSingle.actor.cpp => SidebandSingle.cpp} | 90 +- ...tomicAdd.actor.cpp => SimpleAtomicAdd.cpp} | 88 +- ...eadWrite.actor.cpp => SkewedReadWrite.cpp} | 172 +- ...orkload.actor.cpp => SlowTaskWorkload.cpp} | 13 +- .../{SnapTest.actor.cpp => SnapTest.cpp} | 157 +- ...tor.cpp => SpecialKeySpaceCorrectness.cpp} | 564 +- ...ctor.cpp => SpecialKeySpaceRobustness.cpp} | 446 +- ...sWorkload.actor.cpp => StatusWorkload.cpp} | 73 +- ...uption.actor.cpp => StorageCorruption.cpp} | 33 +- ...=> StorageServerCheckpointRestoreTest.cpp} | 160 +- .../{Storefront.actor.cpp => Storefront.cpp} | 90 +- ...eRead.actor.cpp => StreamingRangeRead.cpp} | 92 +- ...eamingRead.actor.cpp => StreamingRead.cpp} | 52 +- ...ubmitBackup.actor.cpp => SubmitBackup.cpp} | 52 +- ...ocesses.actor.cpp => SuspendProcesses.cpp} | 39 +- ...rottleApi.actor.cpp => TagThrottleApi.cpp} | 119 +- ...argetedKill.actor.cpp => TargetedKill.cpp} | 43 +- ...ss.actor.cpp => TaskBucketCorrectness.cpp} | 193 +- ...hreadSafety.actor.cpp => ThreadSafety.cpp} | 56 +- .../{Throttling.actor.cpp => Throttling.cpp} | 109 +- .../{Throughput.actor.cpp => Throughput.cpp} | 146 +- fdbserver/workloads/ThroughputQuota.actor.cpp | 69 - ...ss.actor.cpp => TimeKeeperCorrectness.cpp} | 66 +- ...tionCost.actor.cpp => TransactionCost.cpp} | 129 +- ...Recovery.actor.cpp => TriggerRecovery.cpp} | 93 +- .../{TxnTimeout.actor.cpp => TxnTimeout.cpp} | 205 +- fdbserver/workloads/UDPWorkload.actor.cpp | 269 - fdbserver/workloads/UDPWorkload.cpp | 266 + .../{UnitPerf.actor.cpp => UnitPerf.cpp} | 24 +- .../{UnitTests.actor.cpp => UnitTests.cpp} | 89 +- .../{Unreadable.actor.cpp => Unreadable.cpp} | 59 +- ...eStorage.actor.cpp => ValidateStorage.cpp} | 483 +- ...ersionStamp.actor.cpp => VersionStamp.cpp} | 276 +- ...atchAndWait.actor.cpp => WatchAndWait.cpp} | 51 +- .../{Watches.actor.cpp => Watches.cpp} | 42 +- ...ctor.cpp => WatchesSameKeyCorrectness.cpp} | 190 +- ...orkerErrors.actor.cpp => WorkerErrors.cpp} | 33 +- ...Bandwidth.actor.cpp => WriteBandwidth.cpp} | 60 +- ...ringRead.actor.cpp => WriteDuringRead.cpp} | 809 +-- ...tling.actor.cpp => WriteTagThrottling.cpp} | 111 +- .../pubsub.cpp} | 312 +- .../{include/fdbserver => workloads}/pubsub.h | 2 +- flow/ActorCollection.actor.cpp | 8 +- flow/Arena.cpp | 93 +- flow/CMakeLists.txt | 20 +- flow/CodeProbe.cpp | 57 +- ...pressedInt.actor.cpp => CompressedInt.cpp} | 10 +- flow/CompressionUtils.cpp | 2 +- flow/{include/flow => }/CompressionUtils.h | 0 {fdbrpc => flow}/CoroTests.cpp | 785 ++- flow/Deque.cpp | 2 +- flow/DeterministicRandom.cpp | 24 +- flow/Error.cpp | 3 - .../flow/EventTypes.actor.h => EventTypes.h} | 23 +- flow/FileTraceLogWriter.cpp | 6 +- flow/{include/flow => }/FileTraceLogWriter.h | 0 flow/FlowTest.cpp | 362 ++ flow/Histogram.cpp | 1 - flow/{Hostname.actor.cpp => Hostname.cpp} | 60 +- flow/IAsyncFile.actor.cpp | 203 - flow/IAsyncFile.cpp | 81 + flow/IThreadPool.cpp | 11 +- ...PoolTest.actor.cpp => IThreadPoolTest.cpp} | 103 +- flow/IndexedSet.cpp | 8 +- flow/JsonTraceLogFormatter.cpp | 3 +- .../flow => }/JsonTraceLogFormatter.h | 0 flow/Knobs.cpp | 47 +- flow/{include/flow => }/MetricSample.h | 0 flow/MkCert.cpp | 1 - flow/MkCertCli.cpp | 2 +- flow/{Net2.actor.cpp => Net2.cpp} | 482 +- flow/Net2Packet.cpp | 2 +- flow/{Platform.actor.cpp => Platform.cpp} | 161 +- flow/ProcessEvents.cpp | 2 +- flow/{Profiler.actor.cpp => Profiler.cpp} | 35 +- flow/SimBugInjector.cpp | 9 +- flow/SimpleCounter.cpp | 6 +- flow/StreamCipher.cpp | 10 +- flow/SystemMonitor.cpp | 48 +- flow/TDMetric.cpp | 8 +- flow/{TLSConfig.actor.cpp => TLSConfig.cpp} | 74 +- ...hreadHelper.actor.cpp => ThreadHelper.cpp} | 21 +- flow/ThreadPrimitives.cpp | 6 - flow/Trace.cpp | 75 +- flow/WipedString.cpp | 1 - ...riteOnlySet.actor.cpp => WriteOnlySet.cpp} | 17 +- flow/XmlTraceLogFormatter.cpp | 59 +- .../{include/flow => }/XmlTraceLogFormatter.h | 0 flow/actorcompiler/ActorCompiler.cs | 73 +- flow/actorcompiler/ActorParser.cs | 84 - flow/actorcompiler/ParseTree.cs | 14 - flow/actorcompiler/usertype.dat | 1 - flow/actorcompiler_py/actor_compiler.py | 74 +- flow/actorcompiler_py/actor_parser.py | 89 +- flow/actorcompiler_py/parse_tree.py | 14 - flow/bench/BenchAsyncResult.cpp | 96 + {flowbench => flow/bench}/BenchCallback.cpp | 0 .../bench/BenchCallbackActor.cpp | 21 +- .../bench}/BenchConflictSet.cpp | 0 flow/bench/BenchCoroChooseRace.cpp | 278 + flow/bench/BenchCoroutineOverhead.cpp | 96 + {flowbench => flow/bench}/BenchHash.cpp | 13 +- flow/bench/BenchMain.cpp | 25 + {flowbench => flow/bench}/BenchMem.cpp | 0 flow/bench/BenchMemcpy.cpp | 242 + {flowbench => flow/bench}/BenchNet2.cpp | 2 +- .../bench/BenchNet2Actor.cpp | 53 +- flow/bench/BenchNoThrowOnCancel.cpp | 158 + .../bench/BenchPriorityMultiLock.cpp | 29 +- {flowbench => flow/bench}/BenchRandom.cpp | 0 {flowbench => flow/bench}/BenchRef.cpp | 0 .../bench/BenchStream.cpp | 25 +- .../bench/BenchSupport.h | 36 +- flow/bench/BenchTimeout.cpp | 95 + {flowbench => flow/bench}/BenchTimer.cpp | 0 flow/bench/BenchWaitForAllReady.cpp | 100 + {flowbench => flow/bench}/BenchZstd.cpp | 2 +- flow/bench/CMakeLists.txt | 18 + {flowbench => flow/bench}/README.md | 21 +- flow/flat_buffers.cpp | 10 +- flow/flow.cpp | 83 +- flow/genericactors.actor.cpp | 140 +- flow/genericcoros.h | 215 + flow/include/flow/ActorContext.h | 4 +- flow/include/flow/Arena.h | 137 +- flow/include/flow/AsioReactor.h | 2 +- .../include/flow/BenchMain.h | 36 +- flow/include/flow/CodeProbe.h | 18 +- flow/include/flow/CompressedInt.h | 2 +- flow/include/flow/CoroUtils.h | 208 +- flow/include/flow/Coroutines.h | 248 +- flow/include/flow/CoroutinesImpl.h | 996 ++- flow/include/flow/Deque.h | 4 +- flow/include/flow/DeterministicRandom.h | 12 +- flow/include/flow/Error.h | 4 +- flow/include/flow/FastRef.h | 12 +- flow/include/flow/FlowThread.h | 8 +- flow/include/flow/Histogram.h | 12 +- flow/include/flow/IConnection.h | 17 +- flow/include/flow/IPAddress.h | 2 +- flow/include/flow/IRandom.h | 10 +- flow/include/flow/IThreadPool.h | 2 +- flow/include/flow/IndexedSet.actor.h | 75 - flow/include/flow/IndexedSet.h | 78 +- flow/include/flow/Knobs.h | 7 +- flow/include/flow/MkCert.h | 2 +- flow/include/flow/OTELMetrics.h | 9 +- flow/include/flow/ObjectSerializer.h | 4 +- flow/include/flow/ObjectSerializerTraits.h | 10 +- flow/include/flow/Optional.h | 37 +- flow/include/flow/OwningResource.h | 18 +- flow/include/flow/PKey.h | 4 +- flow/include/flow/Platform.actor.h | 51 - flow/include/flow/Platform.h | 10 +- ...yMultiLock.actor.h => PriorityMultiLock.h} | 80 +- flow/include/flow/ScopeExit.h | 2 +- flow/include/flow/SendBufferIterator.h | 2 +- flow/include/flow/SimpleCounter.h | 13 +- flow/include/flow/StreamCipher.h | 4 +- flow/include/flow/SystemMonitor.h | 45 +- .../flow/{TDMetric.actor.h => TDMetric.h} | 164 +- .../flow/{TLSConfig.actor.h => TLSConfig.h} | 15 +- flow/include/flow/TaskQueue.h | 4 +- flow/include/flow/ThreadHelper.actor.h | 55 +- flow/include/flow/ThreadPrimitives.h | 6 +- flow/include/flow/ThreadSafeQueue.h | 4 +- flow/include/flow/Trace.h | 44 +- flow/include/flow/Traceable.h | 6 +- flow/include/flow/TreeBenchmark.h | 2 +- flow/include/flow/TxnCounters.h | 42 + flow/include/flow/Util.h | 119 +- .../flow/{WatchFile.actor.h => WatchFile.h} | 39 +- flow/include/flow/WipedString.h | 4 +- flow/include/flow/actorcompiler.h | 1 - flow/include/flow/error_definitions.h | 9 +- flow/include/flow/flat_buffers.h | 79 +- flow/include/flow/flow.h | 94 +- flow/include/flow/genericactors.actor.h | 812 +-- flow/include/flow/network.h | 2 +- flow/include/flow/serialize.h | 20 +- flow/include/flow/singleton.h | 4 +- flow/include/flow/swift/ABI/MetadataValues.h | 2 +- flow/include/flow/swift/Basic/FlagSet.h | 2 +- flow/include/flow/swift_concurrency_hooks.h | 2 +- flow/include/flow/unactorcompiler.h | 1 - flow/network.cpp | 56 +- flow/{include/flow => }/rte_memcpy.h | 0 flow/swift_concurrency_hooks.cpp | 2 +- flow/swift_task_priority.cpp | 2 +- flow/test_memcpy.cpp | 2 +- flow/test_memcpy_perf.cpp | 341 - flowbench/CMakeLists.txt | 66 - packaging/docker/Dockerfile | 29 +- packaging/docker/build-images.sh | 7 + packaging/docker/kubernetes/test_config.yaml | 78 +- .../samples/python/app/requirements.txt | 2 +- packaging/docker/sidecar_test.py | 16 +- packaging/multiversion/server/postinst | 2 + packaging/osx/buildpkg.sh | 6 +- pull_request_template.md | 28 +- recipes/java-recipes/MicroSpatial.java | 2 +- tests/CMakeLists.txt | 26 +- ...llelRestoreApiCorrectnessAtomicRestore.txt | 36 - ...eOldBackupApiCorrectnessAtomicRestore.toml | 40 - .../TestRunner/fdb_test_runner/fdb_version.py | 4 + tests/TestRunner/requirements.txt | 5 +- tests/fast/BulkDumping.toml | 2 +- tests/fast/BulkLoading.toml | 2 +- tests/fast/EncryptionUnitTests.toml | 17 - tests/fast/MaxGrvQueueDelay.toml | 19 + tests/fast/RESTUnit.toml | 21 - tests/fast/StatusDuringOutage.toml | 26 + tests/fast/TxnStateStoreCycleTest.toml | 2 + tests/loopback_cluster/run_custom_cluster.sh | 1 + tests/rare/ExpectStableThroughput.toml | 20 - tests/rare/ThroughputQuota.toml | 13 - tests/slow/ApiCorrectnessAtomicRestore.toml | 2 +- tests/slow/BackupAndRestore.toml | 2 +- tests/slow/BackupNewAndOldRestore.toml | 4 +- tests/slow/BackupOldAndNewRestore.toml | 4 +- tests/slow/BackupS3BlobBulkLoadRestore.toml | 2 +- ...BackupS3BlobBulkLoadRestoreMultiRange.toml | 2 +- .../BackupS3BlobBulkLoadRestoreWithChaos.toml | 2 +- tests/slow/BulkDumpingS3.toml | 2 +- tests/slow/BulkDumpingS3WithChaos.toml | 2 +- tests/slow/DiskFailureCycle.toml | 3 +- ...elRestoreNewBackupCorrectnessAtomicOp.toml | 62 - ...allelRestoreNewBackupCorrectnessCycle.toml | 57 - ...estoreNewBackupCorrectnessMultiCycles.toml | 80 - ...NewBackupWriteDuringReadAtomicRestore.toml | 52 - ...elRestoreOldBackupCorrectnessAtomicOp.toml | 60 - ...allelRestoreOldBackupCorrectnessCycle.toml | 57 - ...estoreOldBackupCorrectnessMultiCycles.toml | 80 - ...OldBackupWriteDuringReadAtomicRestore.toml | 52 - tests/slow/WriteDuringReadAtomicRestore.toml | 2 +- 1151 files changed, 86684 insertions(+), 77932 deletions(-) create mode 100644 .clang-tidy create mode 100644 .github/workflows/stale.yml rename bindings/flow/{DirectoryLayer.actor.cpp => DirectoryLayer.cpp} (56%) rename bindings/flow/{HighContentionAllocator.actor.cpp => HighContentionAllocator.cpp} (73%) rename bindings/flow/{Node.actor.cpp => Node.cpp} (89%) rename bindings/flow/{fdb_flow.actor.cpp => fdb_flow.cpp} (95%) rename bindings/flow/tester/{DirectoryTester.actor.cpp => DirectoryTester.cpp} (64%) rename bindings/flow/tester/{Tester.actor.cpp => Tester.cpp} (66%) rename bindings/flow/tester/{Tester.actor.h => Tester.h} (82%) create mode 100644 bindings/go/src/fdb/error_codes_generated.go create mode 100644 bindings/go/src/internal/gen_errors/main.go rename bindings/go/src/{_util => internal}/translate_fdb_options.go (92%) create mode 100644 cmake/AddFdbServerLinkTest.cmake create mode 100644 cmake/FDBBenchmark.cmake rename flowbench/benchmark.cmake => cmake/benchmark-download.cmake (79%) delete mode 100644 contrib/mockkms/CMakeLists.txt delete mode 100644 contrib/mockkms/fault_injection.go delete mode 100644 contrib/mockkms/get_encryption_keys.go delete mode 100644 contrib/mockkms/mock_kms.go delete mode 100644 contrib/mockkms/mockkms_test.go create mode 100644 design/AI-generated/FDB_NETWORK_PROTOCOL.md create mode 100644 design/AI-generated/README.md create mode 100644 design/AI-generated/diagram_01_flow_runtime.md create mode 100644 design/AI-generated/diagram_02_rpc_transport.md create mode 100644 design/AI-generated/diagram_03_client_library.md create mode 100644 design/AI-generated/diagram_04_cluster_controller.md create mode 100644 design/AI-generated/diagram_05_commit_pipeline.md create mode 100644 design/AI-generated/diagram_06_tlog_logsystem.md create mode 100644 design/AI-generated/diagram_07_storage_server.md create mode 100644 design/AI-generated/diagram_08_data_distribution.md create mode 100644 design/AI-generated/diagram_09_cluster_recovery.md create mode 100644 design/AI-generated/diagram_10_ratekeeper.md create mode 100644 design/AI-generated/diagram_11_backup_dr.md create mode 100644 design/AI-generated/diagram_12_simulation_testing.md create mode 100644 design/AI-generated/diagram_overview.md create mode 100644 design/AI-generated/foundationdb_subsystem_map.md create mode 100644 design/AI-generated/subsystem_01_flow_runtime.md create mode 100644 design/AI-generated/subsystem_02_rpc_transport.md create mode 100644 design/AI-generated/subsystem_03_client_library.md create mode 100644 design/AI-generated/subsystem_04_cluster_controller.md create mode 100644 design/AI-generated/subsystem_05_commit_pipeline.md create mode 100644 design/AI-generated/subsystem_06_tlog_logsystem.md create mode 100644 design/AI-generated/subsystem_07_storage_server.md create mode 100644 design/AI-generated/subsystem_08_data_distribution.md create mode 100644 design/AI-generated/subsystem_09_cluster_recovery.md create mode 100644 design/AI-generated/subsystem_10_ratekeeper.md create mode 100644 design/AI-generated/subsystem_11_backup_dr.md create mode 100644 design/AI-generated/subsystem_12_simulation_testing.md create mode 100644 design/cluster_health_metric.md create mode 100644 design/fdb-coroutines-internals.md create mode 100644 design/file-level-encryption-backup.md delete mode 100644 design/global-tag-throttling.md create mode 100644 documentation/sphinx/source/clang-format.rst create mode 100644 documentation/sphinx/source/clang-tidy.rst create mode 100644 documentation/sphinx/source/developer-tools.rst rename fdbbackup/{FileConverter.actor.cpp => FileConverter.cpp} (85%) rename fdbbackup/{FileDecoder.actor.cpp => FileDecoder.cpp} (86%) rename fdbbackup/{backup.actor.cpp => backup.cpp} (82%) rename fdbbackup/tests/{s3_backup_test.sh => blob_backup_restore_test.sh} (85%) rename fdbcli/{AdvanceVersionCommand.actor.cpp => AdvanceVersionCommand.cpp} (77%) rename fdbcli/{AuditStorageCommand.actor.cpp => AuditStorageCommand.cpp} (89%) rename fdbcli/{BulkDumpCommand.actor.cpp => BulkDumpCommand.cpp} (60%) rename fdbcli/{BulkLoadCommand.actor.cpp => BulkLoadCommand.cpp} (68%) rename fdbcli/{ConfigureCommand.actor.cpp => ConfigureCommand.cpp} (90%) rename fdbcli/{ConsistencyCheckCommand.actor.cpp => ConsistencyCheckCommand.cpp} (72%) rename fdbcli/{ConsistencyScanCommand.actor.cpp => ConsistencyScanCommand.cpp} (77%) rename fdbcli/{CoordinatorsCommand.actor.cpp => CoordinatorsCommand.cpp} (70%) rename fdbcli/{DataDistributionCommand.actor.cpp => DataDistributionCommand.cpp} (72%) rename fdbcli/{DebugCommands.actor.cpp => DebugCommands.cpp} (77%) rename fdbcli/{ExcludeCommand.actor.cpp => ExcludeCommand.cpp} (69%) rename fdbcli/{ExpensiveDataCheckCommand.actor.cpp => ExpensiveDataCheckCommand.cpp} (84%) rename fdbcli/{FileConfigureCommand.actor.cpp => FileConfigureCommand.cpp} (87%) rename fdbcli/{FlowLineNoise.actor.cpp => FlowLineNoise.cpp} (93%) rename fdbcli/{ForceRecoveryWithDataLossCommand.actor.cpp => ForceRecoveryWithDataLossCommand.cpp} (82%) rename fdbcli/{GetAuditStatusCommand.actor.cpp => GetAuditStatusCommand.cpp} (66%) rename fdbcli/{HotRangeCommand.actor.cpp => HotRangeCommand.cpp} (83%) rename fdbcli/{IdempotencyIdsCommand.actor.cpp => IdempotencyIdsCommand.cpp} (80%) rename fdbcli/{IncludeCommand.actor.cpp => IncludeCommand.cpp} (72%) rename fdbcli/{KillCommand.actor.cpp => KillCommand.cpp} (80%) rename fdbcli/{LocationMetadataCommand.actor.cpp => LocationMetadataCommand.cpp} (63%) rename fdbcli/{LockCommand.actor.cpp => LockCommand.cpp} (59%) rename fdbcli/{MaintenanceCommand.actor.cpp => MaintenanceCommand.cpp} (76%) rename fdbcli/{ProfileCommand.actor.cpp => ProfileCommand.cpp} (85%) rename fdbcli/{RangeConfigCommand.actor.cpp => RangeConfigCommand.cpp} (78%) rename fdbcli/{SetClassCommand.actor.cpp => SetClassCommand.cpp} (70%) rename fdbcli/{SnapshotCommand.actor.cpp => SnapshotCommand.cpp} (79%) rename fdbcli/{StatusCommand.actor.cpp => StatusCommand.cpp} (92%) rename fdbcli/{SuspendCommand.actor.cpp => SuspendCommand.cpp} (78%) rename fdbcli/{ThrottleCommand.actor.cpp => ThrottleCommand.cpp} (90%) rename fdbcli/{TriggerDDTeamInfoLogCommand.actor.cpp => TriggerDDTeamInfoLogCommand.cpp} (79%) rename fdbcli/{TssqCommand.actor.cpp => TssqCommand.cpp} (67%) delete mode 100644 fdbcli/Util.actor.cpp create mode 100644 fdbcli/Util.cpp rename fdbcli/{VersionEpochCommand.actor.cpp => VersionEpochCommand.cpp} (68%) rename fdbcli/{fdbcli.actor.cpp => fdbcli.cpp} (87%) delete mode 100644 fdbcli/include/fdbcli/fdbcli.actor.h create mode 100644 fdbcli/include/fdbcli/fdbcli.h rename fdbclient/{AsyncFileS3BlobStore.actor.cpp => AsyncFileBlobStore.cpp} (66%) delete mode 100644 fdbclient/AsyncTaskThread.actor.cpp rename fdbclient/{AuditUtils.actor.cpp => AuditUtils.cpp} (64%) rename fdbclient/{BackupAgentBase.actor.cpp => BackupAgentBase.cpp} (74%) rename fdbclient/{BackupContainer.actor.cpp => BackupContainer.cpp} (78%) rename fdbclient/{BackupContainerS3BlobStore.actor.cpp => BackupContainerBlobStore.cpp} (54%) rename fdbclient/{include/fdbclient/BackupContainerS3BlobStore.h => BackupContainerBlobStore.h} (64%) rename fdbclient/{BackupContainerFileSystem.actor.cpp => BackupContainerFileSystem.cpp} (77%) rename fdbclient/{BackupContainerLocalDirectory.actor.cpp => BackupContainerLocalDirectory.cpp} (86%) rename fdbclient/{include/fdbclient => }/BackupContainerLocalDirectory.h (90%) create mode 100644 fdbclient/BlobStoreCommon.cpp delete mode 100644 fdbclient/ClientKnobCollection.cpp rename fdbclient/{ClusterConnectionFile.actor.cpp => ClusterConnectionFile.cpp} (97%) rename fdbclient/{ClusterConnectionKey.actor.cpp => ClusterConnectionKey.cpp} (70%) rename fdbclient/{include/fdbclient/ClusterConnectionKey.actor.h => ClusterConnectionKey.h} (76%) rename fdbclient/{ClusterConnectionMemoryRecord.actor.cpp => ClusterConnectionMemoryRecord.cpp} (94%) rename fdbclient/{DataDistributionConfig.actor.cpp => DataDistributionConfig.cpp} (91%) rename fdbclient/{DatabaseBackupAgent.actor.cpp => DatabaseBackupAgent.cpp} (59%) rename fdbclient/{DatabaseContext.actor.cpp => DatabaseContext.cpp} (87%) rename fdbclient/{include/fdbclient => }/FDBAWSCredentialsProvider.h (100%) rename fdbclient/{FileBackupAgent.actor.cpp => FileBackupAgent.cpp} (61%) create mode 100644 fdbclient/GCSBlobStore.cpp create mode 100644 fdbclient/GCSBlobStore.h rename fdbclient/{GlobalConfig.actor.cpp => GlobalConfig.cpp} (62%) delete mode 100644 fdbclient/IKnobCollection.cpp rename fdbclient/{IdempotencyId.actor.cpp => IdempotencyId.cpp} (81%) rename fdbclient/{KeyRangeMap.actor.cpp => KeyRangeMap.cpp} (80%) rename fdbclient/{include/fdbclient => }/LocalClientAPI.h (99%) rename fdbclient/{ManagementAPI.actor.cpp => ManagementAPI.cpp} (68%) rename fdbclient/{MonitorLeader.actor.cpp => MonitorLeader.cpp} (80%) rename fdbclient/{MultiVersionTransaction.actor.cpp => MultiVersionTransaction.cpp} (94%) rename fdbclient/{MutationLogReader.actor.cpp => MutationLogReader.cpp} (73%) rename fdbclient/{include/fdbclient => }/NameLineage.h (100%) delete mode 100644 fdbclient/ParallelStream.actor.cpp rename fdbclient/{include/fdbclient => }/PartitionedLogIterator.h (99%) create mode 100644 fdbclient/ProxyLoadBalance.h rename fdbclient/{RESTClient.actor.cpp => RESTClient.cpp} (88%) rename fdbclient/{include/fdbclient => }/RESTClient.h (99%) rename fdbclient/{RESTUtils.actor.cpp => RESTUtils.cpp} (93%) rename fdbclient/{include/fdbclient => }/RESTUtils.h (97%) rename fdbclient/{include/fdbclient => }/RestoreInterface.h (100%) delete mode 100644 fdbclient/S3BlobStore.actor.cpp create mode 100644 fdbclient/S3BlobStore.cpp rename fdbclient/{S3Client.actor.cpp => S3Client.cpp} (62%) rename fdbclient/{S3Client_cli.actor.cpp => S3Client_cli.cpp} (91%) delete mode 100644 fdbclient/ServerKnobCollection.cpp rename fdbclient/{SpecialKeySpace.actor.cpp => SpecialKeySpace.cpp} (85%) rename fdbclient/{StatusClient.actor.cpp => StatusClient.cpp} (80%) rename fdbclient/{TagThrottle.actor.cpp => TagThrottle.cpp} (73%) rename fdbclient/{TaskBucket.actor.cpp => TaskBucket.cpp} (68%) delete mode 100644 fdbclient/TestKnobCollection.cpp rename fdbclient/{Tracing.actor.cpp => Tracing.cpp} (91%) delete mode 100644 fdbclient/VersionedMap.cpp rename {flowbench => fdbclient/bench}/BenchIdempotencyIds.cpp (100%) rename {flowbench => fdbclient/bench}/BenchIterate.cpp (98%) rename fdbserver/include/fdbserver/ServerDBInfo.h => fdbclient/bench/BenchMain.cpp (82%) rename {flowbench => fdbclient/bench}/BenchMetadataCheck.cpp (100%) rename {flowbench => fdbclient/bench}/BenchPopulate.cpp (98%) rename {flowbench => fdbclient/bench}/BenchTempTagMessages.cpp (100%) rename {flowbench => fdbclient/bench}/BenchVersionVector.cpp (100%) rename {flowbench => fdbclient/bench}/BenchVersionVectorSerialization.cpp (100%) create mode 100644 fdbclient/bench/BenchVersionedMap.cpp create mode 100644 fdbclient/bench/CMakeLists.txt rename {flowbench => fdbclient/bench}/GlobalData.cpp (98%) rename {flowbench/include/flowbench => fdbclient/bench}/GlobalData.h (88%) rename fdbclient/include/fdbclient/{AsyncFileS3BlobStore.actor.h => AsyncFileBlobStore.h} (62%) delete mode 100644 fdbclient/include/fdbclient/AsyncTaskThread.h delete mode 100644 fdbclient/include/fdbclient/AuditUtils.actor.h create mode 100644 fdbclient/include/fdbclient/AuditUtils.h rename fdbclient/include/fdbclient/{BackupAgent.actor.h => BackupAgent.h} (86%) delete mode 100644 fdbclient/include/fdbclient/ClientKnobCollection.h delete mode 100644 fdbclient/include/fdbclient/ClientKnobs.h rename fdbclient/include/fdbclient/{ConsistencyScanInterface.actor.h => ConsistencyScanInterface.h} (76%) rename fdbclient/include/fdbclient/{DataDistributionConfig.actor.h => DataDistributionConfig.h} (81%) delete mode 100644 fdbclient/include/fdbclient/EncryptKeyProxyInterface.h create mode 100644 fdbclient/include/fdbclient/EventTypes.h rename fdbclient/include/fdbclient/{GenericManagementAPI.actor.h => GenericManagementAPI.h} (75%) delete mode 100644 fdbclient/include/fdbclient/GlobalConfig.actor.h rename fdbclient/include/fdbclient/{HighContentionPrefixAllocator.actor.h => HighContentionPrefixAllocator.h} (66%) create mode 100644 fdbclient/include/fdbclient/IBlobStore.h delete mode 100644 fdbclient/include/fdbclient/IKnobCollection.h rename fdbclient/include/fdbclient/{IdempotencyId.actor.h => IdempotencyId.h} (89%) rename fdbclient/include/fdbclient/{KeyBackedRangeMap.actor.h => KeyBackedRangeMap.h} (86%) rename fdbclient/include/fdbclient/{KeyBackedTypes.actor.h => KeyBackedTypes.h} (91%) delete mode 100644 fdbclient/include/fdbclient/ManagementAPI.actor.h create mode 100644 fdbclient/include/fdbclient/ManagementAPI.h rename fdbclient/include/fdbclient/{MutationLogReader.actor.h => MutationLogReader.h} (78%) delete mode 100644 fdbclient/include/fdbclient/ParallelStream.actor.h delete mode 100644 fdbclient/include/fdbclient/RunRYWTransaction.actor.h create mode 100644 fdbclient/include/fdbclient/RunRYWTransaction.h rename fdbclient/include/fdbclient/{RunTransaction.actor.h => RunTransaction.h} (69%) rename fdbclient/include/fdbclient/{S3Client.actor.h => S3Client.h} (81%) delete mode 100644 fdbclient/include/fdbclient/ServerKnobCollection.h rename fdbclient/include/fdbclient/{SpecialKeySpace.actor.h => SpecialKeySpace.h} (90%) rename fdbclient/include/fdbclient/{StorageWiggleMetrics.actor.h => StorageWiggleMetrics.h} (90%) rename fdbclient/include/fdbclient/{TagThrottle.actor.h => TagThrottle.h} (77%) delete mode 100644 fdbclient/include/fdbclient/TestKnobCollection.h delete mode 100644 fdbclient/include/fdbclient/VersionedMap.actor.h rename fdbclient/{include/fdbclient => }/sha1/SHA1.h (100%) create mode 100644 fdbclient/tests/gcp_fixture.sh create mode 100644 fdbkubernetesmonitor/CMakeLists.txt rename fdbrpc/{AsyncFileCached.actor.cpp => AsyncFileCached.cpp} (99%) rename fdbrpc/{include/fdbrpc => }/AsyncFileChaos.h (100%) rename fdbrpc/{include/fdbrpc/AsyncFileEIO.actor.h => AsyncFileEIO.h} (71%) rename fdbrpc/{AsyncFileEncrypted.actor.cpp => AsyncFileEncrypted.cpp} (57%) rename fdbrpc/{include/fdbrpc/AsyncFileKAIO.actor.h => AsyncFileKAIO.h} (86%) delete mode 100644 fdbrpc/AsyncFileNonDurable.actor.cpp create mode 100644 fdbrpc/AsyncFileNonDurable.cpp rename fdbrpc/{include/fdbrpc/AsyncFileWinASIO.actor.h => AsyncFileWinASIO.h} (93%) rename fdbrpc/{include/fdbrpc/AsyncFileWriteChecker.actor.h => AsyncFileWriteChecker.h} (92%) rename fdbrpc/{include/fdbrpc => }/Base64Decode.h (100%) rename fdbrpc/{include/fdbrpc => }/Base64Encode.h (100%) rename fdbrpc/{include/fdbrpc => }/ContinuousSample.h (100%) rename fdbrpc/{DDSketchTest.actor.cpp => DDSketchTest.cpp} (97%) rename fdbrpc/{FailureMonitor.actor.cpp => FailureMonitor.cpp} (89%) rename fdbrpc/{include/fdbrpc => }/FileTransfer.h (96%) rename fdbrpc/{include/fdbrpc => }/FlowGrpcTests.h (96%) rename fdbrpc/{FlowTransport.actor.cpp => FlowTransport.cpp} (87%) rename fdbrpc/{HTTP.actor.cpp => HTTP.cpp} (80%) rename fdbrpc/{HTTPServer.actor.cpp => HTTPServer.cpp} (67%) rename fdbrpc/{HealthMonitor.actor.cpp => HealthMonitor.cpp} (95%) rename fdbrpc/{include/fdbrpc => }/JsonWebKeySet.h (92%) create mode 100644 fdbrpc/Net2FileSystemTests.cpp rename fdbrpc/{SimExternalConnection.actor.cpp => SimExternalConnection.cpp} (74%) rename fdbrpc/{include/fdbrpc => }/SimExternalConnection.h (94%) rename fdbrpc/{Stats.actor.cpp => Stats.cpp} (92%) rename {flowbench => fdbrpc/bench}/BenchAsyncFileWriteCheckerLRU.cpp (86%) rename {flowbench => fdbrpc/bench}/BenchIONet2.actor.cpp (98%) create mode 100644 fdbrpc/bench/BenchMain.cpp rename {flowbench => fdbrpc/bench}/BenchSamples.cpp (98%) rename {flowbench => fdbrpc/bench}/BenchSelectReplicas.cpp (91%) rename contrib/mockkms/utils.go => fdbrpc/bench/BenchSupport.h (57%) create mode 100644 fdbrpc/bench/CMakeLists.txt rename fdbrpc/include/fdbrpc/{AsyncFileCached.actor.h => AsyncFileCached.h} (88%) rename fdbrpc/include/fdbrpc/{AsyncFileNonDurable.actor.h => AsyncFileNonDurable.h} (81%) rename fdbrpc/include/fdbrpc/{AsyncFileReadAhead.actor.h => AsyncFileReadAhead.h} (82%) delete mode 100644 fdbrpc/include/fdbrpc/FlowProcess.actor.h rename fdbrpc/include/fdbrpc/{genericactors.actor.h => genericactors.h} (53%) rename fdbrpc/include/fdbrpc/{networksender.actor.h => networksender.h} (62%) rename fdbrpc/{include/fdbrpc => }/linux_kaio.h (100%) rename fdbrpc/{sim2.actor.cpp => sim2.cpp} (80%) rename fdbrpc/tests/{AuthzTlsTest.actor.cpp => AuthzTlsTest.cpp} (98%) rename fdbrpc/tests/{fdbrpc_bench.actor.cpp => fdbrpc_bench.cpp} (77%) delete mode 100644 fdbserver/BackupWorkerRangePartitioned.actor.cpp delete mode 100644 fdbserver/GlobalTagThrottler.actor.cpp delete mode 100644 fdbserver/GrvProxyTagThrottler.actor.cpp delete mode 100644 fdbserver/LatencyBandsMap.actor.cpp delete mode 100644 fdbserver/RemoteIKeyValueStore.actor.cpp delete mode 100644 fdbserver/RestoreApplier.actor.cpp delete mode 100644 fdbserver/RestoreCommon.actor.cpp delete mode 100644 fdbserver/RestoreController.actor.cpp delete mode 100644 fdbserver/RestoreLoader.actor.cpp delete mode 100644 fdbserver/RestoreRoleCommon.actor.cpp delete mode 100644 fdbserver/RestoreUtil.actor.cpp delete mode 100644 fdbserver/RestoreWorker.actor.cpp delete mode 100644 fdbserver/RestoreWorkerInterface.actor.cpp delete mode 100644 fdbserver/ServerThroughputTracker.cpp rename fdbserver/{SimulatedCluster.actor.cpp => SimulatedCluster.cpp} (84%) delete mode 100644 fdbserver/TSSMappingUtil.actor.cpp rename fdbserver/{BackupPartitionMap.actor.cpp => backupworker/BackupPartitionMap.cpp} (69%) create mode 100644 fdbserver/backupworker/BackupPartitionMap.h create mode 100644 fdbserver/backupworker/BackupRangePartitionedProgress.cpp create mode 100644 fdbserver/backupworker/BackupRangePartitionedProgress.h rename fdbserver/{BackupWorker.actor.cpp => backupworker/BackupWorker.cpp} (63%) create mode 100644 fdbserver/backupworker/BackupWorkerRangePartitioned.cpp create mode 100644 fdbserver/backupworker/CMakeLists.txt create mode 100644 fdbserver/backupworker/include/fdbserver/backupworker/BackupWorker.h create mode 100644 fdbserver/clustercontroller/CMakeLists.txt rename fdbserver/{ => clustercontroller}/ClusterController.actor.cpp (87%) rename fdbserver/{include/fdbserver/ClusterController.actor.h => clustercontroller/ClusterController.h} (96%) create mode 100644 fdbserver/clustercontroller/ClusterHealthIFactor.cpp create mode 100644 fdbserver/clustercontroller/ClusterHealthIFactor.h create mode 100644 fdbserver/clustercontroller/ClusterHealthMonitor.cpp create mode 100644 fdbserver/clustercontroller/ClusterHealthMonitor.h create mode 100644 fdbserver/clustercontroller/ClusterHealthMonitorTesting.cpp rename fdbserver/{ClusterRecovery.actor.cpp => clustercontroller/ClusterRecovery.cpp} (75%) rename fdbserver/{include/fdbserver/ClusterRecovery.actor.h => clustercontroller/ClusterRecovery.h} (84%) create mode 100644 fdbserver/clustercontroller/RatekeeperMonitor.cpp create mode 100644 fdbserver/clustercontroller/RatekeeperMonitor.h rename fdbserver/{include/fdbserver => clustercontroller}/SingletonRoles.h (90%) rename fdbserver/{Status.actor.cpp => clustercontroller/Status.cpp} (77%) rename fdbserver/{include/fdbserver/Status.actor.h => clustercontroller/Status.h} (50%) create mode 100644 fdbserver/clustercontroller/include/fdbserver/clustercontroller/ClusterController.h create mode 100644 fdbserver/commitproxy/CMakeLists.txt rename fdbserver/{CommitProxyServer.actor.cpp => commitproxy/CommitProxyServer.cpp} (73%) rename fdbserver/{include/fdbserver/ProxyCommitData.actor.h => commitproxy/ProxyCommitData.h} (87%) create mode 100644 fdbserver/commitproxy/include/fdbserver/commitproxy/CommitProxyServer.h create mode 100644 fdbserver/consistencyscan/CMakeLists.txt rename fdbserver/{ConsistencyScan.actor.cpp => consistencyscan/ConsistencyScan.cpp} (75%) create mode 100644 fdbserver/consistencyscan/include/fdbserver/consistencyscan/ConsistencyScan.h create mode 100644 fdbserver/coordinator/CMakeLists.txt rename fdbserver/{Coordination.actor.cpp => coordinator/Coordination.cpp} (57%) rename fdbserver/{OnDemandStore.actor.cpp => coordinator/OnDemandStore.cpp} (88%) rename fdbserver/{include/fdbserver => coordinator}/OnDemandStore.h (96%) create mode 100644 fdbserver/coordinator/include/fdbserver/coordinator/CoordinationServer.h rename fdbserver/{ => core}/AccumulativeChecksumUtil.cpp (99%) rename fdbserver/{BackupProgress.actor.cpp => core/BackupProgress.cpp} (87%) rename fdbserver/{BulkDumpUtil.actor.cpp => core/BulkDumpUtil.cpp} (67%) rename fdbserver/{BulkLoadUtil.actor.cpp => core/BulkLoadUtil.cpp} (56%) create mode 100644 fdbserver/core/CMakeLists.txt rename fdbserver/{CoordinatedState.actor.cpp => core/CoordinatedState.cpp} (64%) create mode 100644 fdbserver/core/CoordinationInterface.cpp create mode 100644 fdbserver/core/DataMovement.cpp create mode 100644 fdbserver/core/FDBSimulationPolicy.cpp rename fdbserver/{ => core}/LatencyBandConfig.cpp (94%) rename fdbserver/{ => core}/LeaderElection.actor.cpp (87%) rename fdbserver/{ => core}/LogSystemConfig.cpp (98%) create mode 100644 fdbserver/core/MasterInterface.cpp rename fdbserver/{MoveKeys.actor.cpp => core/MoveKeys.cpp} (72%) rename fdbserver/{ => core}/MutationTracking.cpp (90%) create mode 100644 fdbserver/core/OpenDatabase.cpp rename fdbserver/{QuietDatabase.actor.cpp => core/QuietDatabase.cpp} (64%) create mode 100644 fdbserver/core/RatekeeperLimitReasons.cpp rename fdbserver/{RocksDBCheckpointUtils.actor.cpp => core/RocksDBCheckpointUtils.cpp} (84%) create mode 100644 fdbserver/core/SeedShardServers.cpp rename fdbserver/{ServerCheckpoint.actor.cpp => core/ServerCheckpoint.cpp} (76%) rename {fdbclient => fdbserver/core}/ServerKnobs.cpp (89%) create mode 100644 fdbserver/core/ShardSizing.cpp create mode 100644 fdbserver/core/SimulatorSupport.cpp rename fdbserver/{StorageMetrics.actor.cpp => core/StorageMetrics.cpp} (85%) create mode 100644 fdbserver/core/TSSMappingUtil.cpp rename fdbserver/{WaitFailure.actor.cpp => core/WaitFailure.cpp} (52%) create mode 100644 fdbserver/core/WorkerEvents.cpp create mode 100644 fdbserver/core/WorkerInterface.cpp create mode 100644 fdbserver/core/WorkerSupport.cpp create mode 100644 fdbserver/core/WorkloadKeys.cpp rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/AccumulativeChecksumUtil.h (98%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/BackupInterface.h (100%) rename fdbserver/{include/fdbserver/BackupProgress.actor.h => core/include/fdbserver/core/BackupProgress.h} (81%) create mode 100644 fdbserver/core/include/fdbserver/core/BackupProgressTypes.h rename fdbserver/{include/fdbserver/BulkDumpUtil.actor.h => core/include/fdbserver/core/BulkDumpUtil.h} (70%) create mode 100644 fdbserver/core/include/fdbserver/core/BulkLoadUtil.h create mode 100644 fdbserver/core/include/fdbserver/core/ConflictBatch.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/CoordinatedState.h (96%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/CoordinationInterface.h (92%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/DBCoreState.h (97%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/DataDistributorInterface.h (93%) create mode 100644 fdbserver/core/include/fdbserver/core/DataMovement.h create mode 100644 fdbserver/core/include/fdbserver/core/FDBSimulationPolicy.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/IDiskQueue.h (93%) rename fdbclient/include/fdbclient/ServerKnobs.h => fdbserver/core/include/fdbserver/core/Knobs.h (89%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/LatencyBandConfig.h (100%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/LeaderElection.h (100%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/LogProtocolMessage.h (100%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/LogSystemConfig.h (97%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/MasterInterface.h (98%) rename fdbserver/{include/fdbserver/MoveKeys.actor.h => core/include/fdbserver/core/MoveKeys.h} (83%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/MutationTracking.h (100%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/OTELSpanContextMessage.h (95%) create mode 100644 fdbserver/core/include/fdbserver/core/QuietDatabase.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/RatekeeperInterface.h (93%) create mode 100644 fdbserver/core/include/fdbserver/core/RatekeeperLimitReasons.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/RecoveryState.h (98%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/ResolverInterface.h (100%) rename fdbserver/{include/fdbserver/RocksDBCheckpointUtils.actor.h => core/include/fdbserver/core/RocksDBCheckpointUtils.h} (93%) create mode 100644 fdbserver/core/include/fdbserver/core/SeedShardServers.h rename fdbserver/{include/fdbserver/ServerCheckpoint.actor.h => core/include/fdbserver/core/ServerCheckpoint.h} (79%) rename fdbserver/{include/fdbserver/ServerDBInfo.actor.h => core/include/fdbserver/core/ServerDBInfo.h} (78%) create mode 100644 fdbserver/core/include/fdbserver/core/ShardMetrics.h create mode 100644 fdbserver/core/include/fdbserver/core/ShardSizing.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/SpanContextMessage.h (96%) rename fdbserver/{include/fdbserver/StorageMetrics.actor.h => core/include/fdbserver/core/StorageMetrics.h} (80%) rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/TLogInterface.h (98%) create mode 100644 fdbserver/core/include/fdbserver/core/TSSMappingUtil.h rename fdbserver/{include/fdbserver/TesterInterface.actor.h => core/include/fdbserver/core/TesterInterface.h} (66%) create mode 100644 fdbserver/core/include/fdbserver/core/WaitFailure.h create mode 100644 fdbserver/core/include/fdbserver/core/WorkerEvents.h rename fdbserver/{include/fdbserver => core/include/fdbserver/core}/WorkerInterface.actor.h (82%) create mode 100644 fdbserver/core/include/fdbserver/core/WorkloadKeys.h create mode 100644 fdbserver/datadistributor/AuditUtilsTests.cpp create mode 100644 fdbserver/datadistributor/CMakeLists.txt rename fdbserver/{ => datadistributor}/DDRelocationQueue.actor.cpp (85%) rename fdbserver/{include/fdbserver => datadistributor}/DDRelocationQueue.h (96%) rename fdbserver/{DDShardTracker.actor.cpp => datadistributor/DDShardTracker.cpp} (85%) create mode 100644 fdbserver/datadistributor/DDSharedContext.cpp rename fdbserver/{ => datadistributor}/DDTeamCollection.actor.cpp (91%) rename fdbserver/{DDTxnProcessor.actor.cpp => datadistributor/DDTxnProcessor.cpp} (74%) rename fdbserver/{DataDistribution.actor.cpp => datadistributor/DataDistribution.cpp} (74%) rename fdbserver/{include/fdbserver/ExclusionTracker.actor.h => datadistributor/ExclusionTracker.h} (68%) rename fdbserver/{MockGlobalState.actor.cpp => datadistributor/MockGlobalState.cpp} (93%) rename fdbserver/{include/fdbserver => datadistributor}/MovingWindow.h (97%) rename fdbserver/{ => datadistributor}/ShardsAffectedByTeamFailure.cpp (97%) create mode 100644 fdbserver/datadistributor/SimulatedCluster.cpp rename fdbserver/{TCInfo.actor.cpp => datadistributor/TCInfo.cpp} (86%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/DDShardTracker.h (97%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/DDSharedContext.h (80%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/DDTeamCollection.h (97%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/DDTxnProcessor.h (98%) rename fdbserver/{include/fdbserver/DataDistribution.actor.h => datadistributor/include/fdbserver/datadistributor/DataDistribution.h} (90%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/DataDistributionTeam.h (98%) create mode 100644 fdbserver/datadistributor/include/fdbserver/datadistributor/DataDistributor.h rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/MockDataDistributor.h (85%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/MockGlobalState.h (96%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/ShardsAffectedByTeamFailure.h (100%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/SimulatedCluster.h (100%) rename fdbserver/{include/fdbserver => datadistributor/include/fdbserver/datadistributor}/TCInfo.h (98%) rename fdbserver/{fdbserver.actor.cpp => fdbserver.cpp} (88%) create mode 100644 fdbserver/grvproxy/CMakeLists.txt rename fdbserver/{GrvProxyServer.actor.cpp => grvproxy/GrvProxyServer.cpp} (69%) create mode 100644 fdbserver/grvproxy/GrvQueueDelay.cpp create mode 100644 fdbserver/grvproxy/GrvQueueDelay.h create mode 100644 fdbserver/grvproxy/GrvQueueDelayTests.cpp rename fdbserver/{GrvTransactionRateInfo.actor.cpp => grvproxy/GrvTransactionRateInfo.cpp} (83%) rename fdbserver/{include/fdbserver => grvproxy}/GrvTransactionRateInfo.h (92%) create mode 100644 fdbserver/grvproxy/include/fdbserver/grvproxy/GrvProxyServer.h delete mode 100644 fdbserver/include/fdbserver/BulkLoadUtil.actor.h delete mode 100644 fdbserver/include/fdbserver/FDBExecHelper.actor.h delete mode 100644 fdbserver/include/fdbserver/GrvProxyTagThrottler.h delete mode 100644 fdbserver/include/fdbserver/IKeyValueStore.h delete mode 100644 fdbserver/include/fdbserver/KmsConnector.h delete mode 100644 fdbserver/include/fdbserver/LatencyBandsMap.h delete mode 100644 fdbserver/include/fdbserver/LogSystem.h delete mode 100644 fdbserver/include/fdbserver/QuietDatabase.h delete mode 100644 fdbserver/include/fdbserver/RESTKmsConnectorUtils.h delete mode 100644 fdbserver/include/fdbserver/RESTSimKmsVault.h delete mode 100644 fdbserver/include/fdbserver/RemoteIKeyValueStore.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreApplier.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreCommon.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreController.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreLoader.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreRoleCommon.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreUtil.h delete mode 100644 fdbserver/include/fdbserver/RestoreWorker.actor.h delete mode 100644 fdbserver/include/fdbserver/RestoreWorkerInterface.actor.h delete mode 100644 fdbserver/include/fdbserver/ServerThroughputTracker.h delete mode 100644 fdbserver/include/fdbserver/SimEncryptKmsProxy.actor.h delete mode 100644 fdbserver/include/fdbserver/TSSMappingUtil.actor.h delete mode 100644 fdbserver/include/fdbserver/TagPartitionedLogSystem.actor.h delete mode 100644 fdbserver/include/fdbserver/WaitFailure.h rename fdbserver/{include/fdbserver => kvstore}/ArtMutationBuffer.h (92%) create mode 100644 fdbserver/kvstore/CMakeLists.txt rename fdbserver/{include/fdbserver => kvstore}/DeltaTree.h (99%) rename fdbserver/{DiskQueue.actor.cpp => kvstore/DiskQueue.cpp} (84%) rename fdbserver/{FDBExecHelper.actor.cpp => kvstore/FDBExecHelper.cpp} (58%) rename fdbserver/{include/fdbserver => kvstore}/IKeyValueContainer.h (95%) rename fdbserver/{ => kvstore}/IKeyValueStore.cpp (76%) rename fdbserver/{ => kvstore}/IPager.cpp (93%) rename fdbserver/{include/fdbserver => kvstore}/IPager.h (92%) rename fdbserver/{KeyValueStoreCompressTestData.actor.cpp => kvstore/KeyValueStoreCompressTestData.cpp} (78%) rename fdbserver/{KeyValueStoreMemory.actor.cpp => kvstore/KeyValueStoreMemory.cpp} (86%) rename fdbserver/{ => kvstore}/KeyValueStoreRocksDB.actor.cpp (95%) rename fdbserver/{KeyValueStoreSQLite.actor.cpp => kvstore/KeyValueStoreSQLite.cpp} (96%) rename fdbserver/{ => kvstore}/KeyValueStoreShardedRocksDB.actor.cpp (97%) rename fdbserver/{include/fdbserver => kvstore}/RadixTree.h (99%) rename fdbserver/{ => kvstore}/RocksDBCommon.cpp (98%) rename fdbserver/{include/fdbserver => kvstore}/RocksDBCommon.h (100%) rename fdbserver/{RocksDBLogForwarder.actor.cpp => kvstore/RocksDBLogForwarder.cpp} (93%) rename fdbserver/{include/fdbserver => kvstore}/RocksDBLogForwarder.h (100%) rename fdbserver/{ => kvstore}/TransactionStoreMutationTracking.cpp (94%) rename fdbserver/{include/fdbserver => kvstore}/TransactionStoreMutationTracking.h (100%) rename fdbserver/{ => kvstore}/VFSAsync.cpp (97%) rename fdbserver/{include/fdbserver => kvstore}/VFSAsync.h (100%) rename fdbserver/{ => kvstore}/VersionedBTree.actor.cpp (90%) rename fdbserver/{ => kvstore}/VersionedBTreeDebug.cpp (96%) rename fdbserver/{include/fdbserver => kvstore}/VersionedBTreeDebug.h (100%) rename fdbserver/{include/fdbserver => kvstore}/art.h (98%) rename fdbserver/{include/fdbserver => kvstore}/art_impl.h (100%) create mode 100644 fdbserver/kvstore/include/fdbserver/kvstore/FDBExecHelper.h create mode 100644 fdbserver/kvstore/include/fdbserver/kvstore/IDiskQueue.h rename fdbclient/include/fdbclient/IKeyValueStore.actor.h => fdbserver/kvstore/include/fdbserver/kvstore/IKeyValueStore.h (67%) rename fdbserver/{include/fdbserver/RESTKmsConnector.h => kvstore/include/fdbserver/kvstore/KVFileUtils.h} (70%) rename fdbserver/{include/fdbserver => kvstore}/template_fdb.h (100%) create mode 100644 fdbserver/logrouter/CMakeLists.txt rename fdbserver/{ => logrouter}/LogRouter.cpp (96%) create mode 100644 fdbserver/logrouter/include/fdbserver/logrouter/LogRouter.h rename fdbserver/{ => logsystem}/ApplyMetadataMutation.cpp (95%) create mode 100644 fdbserver/logsystem/CMakeLists.txt rename fdbserver/{LogSystem.cpp => logsystem/LogSet.cpp} (54%) rename fdbserver/{TagPartitionedLogSystem.actor.cpp => logsystem/LogSystem.cpp} (73%) rename fdbserver/{LogSystemDiskQueueAdapter.actor.cpp => logsystem/LogSystemDiskQueueAdapter.cpp} (74%) create mode 100644 fdbserver/logsystem/LogSystemFactory.cpp rename fdbserver/{LogSystemPeekCursor.actor.cpp => logsystem/LogSystemPeekCursor.cpp} (67%) rename fdbserver/{TagPartitionedLogSystemRecoveryTests.actor.cpp => logsystem/LogSystemRecoveryTests.cpp} (94%) rename fdbserver/{include/fdbserver => logsystem/include/fdbserver/logsystem}/ApplyMetadataMutation.h (69%) create mode 100644 fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h rename fdbserver/{include/fdbserver => logsystem/include/fdbserver/logsystem}/LogSystemDiskQueueAdapter.h (89%) create mode 100644 fdbserver/logsystem/include/fdbserver/logsystem/LogSystemFactory.h create mode 100644 fdbserver/logsystem/include/fdbserver/logsystem/LogSystemTypes.h delete mode 100644 fdbserver/masterserver.swift create mode 100644 fdbserver/mocks3/CMakeLists.txt rename fdbserver/{MockS3Server.actor.cpp => mocks3/MockS3Server.cpp} (81%) rename fdbserver/{MockS3ServerChaos.actor.cpp => mocks3/MockS3ServerChaos.cpp} (83%) rename fdbserver/{include/fdbserver => mocks3/include/fdbserver/mocks3}/MockS3Server.h (98%) rename fdbserver/{include/fdbserver => mocks3/include/fdbserver/mocks3}/MockS3ServerChaos.h (98%) rename fdbserver/{networktest.actor.cpp => networktest.cpp} (65%) create mode 100644 fdbserver/ratekeeper/CMakeLists.txt rename fdbserver/{Ratekeeper.actor.cpp => ratekeeper/Ratekeeper.cpp} (54%) rename fdbserver/{include/fdbserver => ratekeeper}/Ratekeeper.h (81%) create mode 100644 fdbserver/ratekeeper/RatekeeperLimits.cpp rename fdbserver/{ => ratekeeper}/RkTagThrottleCollection.cpp (99%) rename fdbserver/{include/fdbserver => ratekeeper}/RkTagThrottleCollection.h (89%) rename fdbserver/{TagThrottler.actor.cpp => ratekeeper/TagThrottler.cpp} (88%) rename fdbserver/{include/fdbserver => ratekeeper}/TagThrottler.h (50%) rename fdbserver/{include/fdbserver/Knobs.h => ratekeeper/include/fdbserver/ratekeeper/Ratekeeper.h} (71%) create mode 100644 fdbserver/resolver/CMakeLists.txt rename fdbserver/{SkipList.cpp => resolver/ConflictSet.cpp} (96%) rename fdbserver/{include/fdbserver => resolver}/ConflictSet.h (92%) rename fdbserver/{Resolver.actor.cpp => resolver/Resolver.cpp} (81%) rename fdbserver/{ => resolver}/ResolverBug.cpp (94%) create mode 100644 fdbserver/resolver/include/fdbserver/resolver/Resolver.h rename fdbserver/{include/fdbserver => resolver/include/fdbserver/resolver}/ResolverBug.h (90%) create mode 100644 fdbserver/sequencer/CMakeLists.txt rename fdbserver/{include/fdbserver/MasterData.actor.h => sequencer/MasterData.h} (50%) rename fdbserver/{ResolutionBalancer.actor.cpp => sequencer/ResolutionBalancer.cpp} (81%) rename fdbserver/{include/fdbserver/ResolutionBalancer.actor.h => sequencer/ResolutionBalancer.h} (68%) create mode 100644 fdbserver/sequencer/include/fdbserver/sequencer/MasterServer.h rename fdbserver/{masterserver.actor.cpp => sequencer/masterserver.cpp} (52%) create mode 100644 fdbserver/storageserver/CMakeLists.txt rename fdbserver/{ => storageserver}/ReadLatencySamples.cpp (97%) rename fdbserver/{include/fdbserver => storageserver}/ReadLatencySamples.h (100%) rename fdbserver/{ => storageserver}/StorageServerUtils.cpp (98%) rename fdbserver/{include/fdbserver => storageserver}/StorageServerUtils.h (99%) rename fdbserver/{TransactionTagCounter.actor.cpp => storageserver/TransactionTagCounter.cpp} (76%) rename fdbserver/{include/fdbserver => storageserver}/TransactionTagCounter.h (97%) rename fdbserver/{include/fdbserver => storageserver/include/fdbserver/storageserver}/StorageCorruptionBug.h (85%) create mode 100644 fdbserver/storageserver/include/fdbserver/storageserver/StorageServer.h rename fdbserver/{ => storageserver}/storageserver.actor.cpp (78%) delete mode 100644 fdbserver/test.cpp create mode 100644 fdbserver/tester/CMakeLists.txt create mode 100644 fdbserver/tester/ConsistencyChecker.cpp create mode 100644 fdbserver/tester/ConsistencyChecker.h rename fdbserver/{workloads/workloads.actor.cpp => tester/CustomShardConfigWorkload.cpp} (67%) create mode 100644 fdbserver/tester/DatabaseMaintenance.cpp create mode 100644 fdbserver/tester/DatabaseMaintenance.h rename fdbserver/{ => tester}/KnobProtectiveGroups.cpp (88%) create mode 100644 fdbserver/tester/TestSpecParser.cpp create mode 100644 fdbserver/tester/TestSpecParser.h create mode 100644 fdbserver/tester/TesterServer.cpp create mode 100644 fdbserver/tester/TesterServer.h create mode 100644 fdbserver/tester/WorkloadUtils.cpp rename fdbserver/{include/fdbserver => tester/include/fdbserver/tester}/KnobProtectiveGroups.h (86%) create mode 100644 fdbserver/tester/include/fdbserver/tester/TestEncryptionUtils.h create mode 100644 fdbserver/tester/include/fdbserver/tester/WorkloadUtils.h create mode 100644 fdbserver/tester/include/fdbserver/tester/tester.h rename fdbserver/{include/fdbserver/workloads/workloads.actor.h => tester/include/fdbserver/tester/workloads.h} (61%) create mode 100644 fdbserver/tester/test.cpp create mode 100644 fdbserver/tlog/CMakeLists.txt rename fdbserver/{TLogServer.actor.cpp => tlog/TLogServer.cpp} (77%) rename fdbserver/{TestTLogServer.actor.cpp => tlog/TestTLogServer.cpp} (75%) rename fdbserver/{include/fdbserver/TestTLogServer.actor.h => tlog/TestTLogServer.h} (78%) create mode 100644 fdbserver/tlog/include/fdbserver/tlog/TLogServer.h create mode 100644 fdbserver/worker/CMakeLists.txt rename fdbserver/{MetricClient.actor.cpp => worker/MetricClient.cpp} (89%) rename fdbserver/{include/fdbserver => worker}/MetricClient.h (92%) rename fdbserver/{ => worker}/MetricLogger.actor.cpp (97%) rename fdbserver/{include/fdbserver => worker}/MetricLogger.actor.h (71%) rename fdbserver/{RoleLineage.actor.cpp => worker/RoleLineage.cpp} (95%) rename fdbserver/{include/fdbserver/RoleLineage.actor.h => worker/RoleLineage.h} (62%) create mode 100644 fdbserver/worker/include/fdbserver/worker/Worker.h rename fdbserver/{ => worker}/worker.actor.cpp (85%) rename fdbserver/workloads/{ApiCorrectness.actor.cpp => ApiCorrectness.cpp} (72%) rename fdbserver/workloads/{ApiWorkload.actor.cpp => ApiWorkload.cpp} (73%) rename fdbserver/{include/fdbserver => }/workloads/ApiWorkload.h (97%) rename fdbserver/{include/fdbserver/workloads/AsyncFile.actor.h => workloads/AsyncFile.h} (69%) rename fdbserver/workloads/{AsyncFileCorrectness.actor.cpp => AsyncFileCorrectness.cpp} (87%) rename fdbserver/workloads/{AsyncFileRead.actor.cpp => AsyncFileRead.cpp} (76%) rename fdbserver/workloads/{AsyncFileWrite.actor.cpp => AsyncFileWrite.cpp} (81%) rename fdbserver/workloads/{AtomicOps.actor.cpp => AtomicOps.cpp} (74%) rename fdbserver/workloads/{AtomicOpsApiCorrectness.actor.cpp => AtomicOpsApiCorrectness.cpp} (58%) rename fdbserver/workloads/{AtomicRestore.actor.cpp => AtomicRestore.cpp} (50%) rename fdbserver/workloads/{AtomicSwitchover.actor.cpp => AtomicSwitchover.cpp} (59%) rename fdbserver/workloads/{AutomaticIdempotencyWorkload.actor.cpp => AutomaticIdempotencyWorkload.cpp} (64%) rename fdbserver/workloads/{BackgroundSelectors.actor.cpp => BackgroundSelectors.cpp} (68%) rename fdbserver/workloads/{Backup.actor.cpp => Backup.cpp} (66%) delete mode 100644 fdbserver/workloads/BackupAndParallelRestoreCorrectness.actor.cpp delete mode 100644 fdbserver/workloads/BackupAndRestoreValidation.actor.cpp create mode 100644 fdbserver/workloads/BackupAndRestoreValidation.cpp rename fdbserver/workloads/{BackupCorrectness.actor.cpp => BackupCorrectness.cpp} (65%) rename fdbserver/workloads/{BackupCorrectnessPartitioned.actor.cpp => BackupCorrectnessPartitioned.cpp} (66%) rename fdbserver/workloads/{BackupS3BlobCorrectness.actor.cpp => BackupS3BlobCorrectness.cpp} (67%) rename fdbserver/workloads/{BackupToDBAbort.actor.cpp => BackupToDBAbort.cpp} (51%) rename fdbserver/workloads/{BackupToDBCorrectness.actor.cpp => BackupToDBCorrectness.cpp} (65%) rename fdbserver/workloads/{BackupToDBUpgrade.actor.cpp => BackupToDBUpgrade.cpp} (64%) rename fdbserver/workloads/{BulkDumping.actor.cpp => BulkDumping.cpp} (76%) rename fdbserver/workloads/{BulkLoad.actor.cpp => BulkLoad.cpp} (86%) rename fdbserver/workloads/{BulkLoading.actor.cpp => BulkLoading.cpp} (72%) rename fdbserver/{include/fdbserver/workloads/BulkSetup.actor.h => workloads/BulkSetup.h} (71%) create mode 100644 fdbserver/workloads/CMakeLists.txt delete mode 100644 fdbserver/workloads/ChangeConfig.actor.cpp create mode 100644 fdbserver/workloads/ChangeConfig.cpp rename fdbserver/workloads/{ClearSingleRange.actor.cpp => ClearSingleRange.cpp} (65%) rename fdbserver/workloads/{ClientMetric.actor.cpp => ClientMetric.cpp} (67%) rename fdbserver/workloads/{ClientTransactionProfileCorrectness.actor.cpp => ClientTransactionProfileCorrectness.cpp} (85%) rename fdbserver/workloads/{ClientWorkload.actor.cpp => ClientWorkload.cpp} (57%) rename fdbserver/workloads/{ClogRemoteTLog.actor.cpp => ClogRemoteTLog.cpp} (79%) rename fdbserver/workloads/{ClogSingleConnection.actor.cpp => ClogSingleConnection.cpp} (87%) rename fdbserver/workloads/{ClogTlog.actor.cpp => ClogTlog.cpp} (77%) rename fdbserver/workloads/{CommitBugCheck.actor.cpp => CommitBugCheck.cpp} (56%) rename fdbserver/workloads/{ConfigureDatabase.actor.cpp => ConfigureDatabase.cpp} (79%) rename fdbserver/workloads/{ConflictRange.actor.cpp => ConflictRange.cpp} (74%) rename fdbserver/workloads/{ConsistencyCheck.actor.cpp => ConsistencyCheck.cpp} (76%) rename fdbserver/workloads/{ConsistencyCheckUrgent.actor.cpp => ConsistencyCheckUrgent.cpp} (77%) rename fdbserver/workloads/{CpuProfiler.actor.cpp => CpuProfiler.cpp} (72%) rename fdbserver/workloads/{Cycle.actor.cpp => Cycle.cpp} (79%) rename fdbserver/workloads/{DDBalance.actor.cpp => DDBalance.cpp} (72%) rename fdbserver/workloads/{DDMetrics.actor.cpp => DDMetrics.cpp} (65%) rename fdbserver/workloads/{DDMetricsExclude.actor.cpp => DDMetricsExclude.cpp} (69%) rename fdbserver/workloads/{DataDistributionMetrics.actor.cpp => DataDistributionMetrics.cpp} (61%) rename fdbserver/workloads/{DataLossRecovery.actor.cpp => DataLossRecovery.cpp} (71%) rename fdbserver/workloads/{DcLag.actor.cpp => DcLag.cpp} (79%) rename fdbserver/workloads/{DifferentClustersSameRV.actor.cpp => DifferentClustersSameRV.cpp} (51%) rename fdbserver/workloads/{DiskDurability.actor.cpp => DiskDurability.cpp} (54%) rename fdbserver/workloads/{DiskDurabilityTest.actor.cpp => DiskDurabilityTest.cpp} (66%) rename fdbserver/workloads/{DiskFailureInjection.actor.cpp => DiskFailureInjection.cpp} (64%) rename fdbserver/workloads/{ExcludeIncludeStorageServersWorkload.actor.cpp => ExcludeIncludeStorageServersWorkload.cpp} (75%) delete mode 100644 fdbserver/workloads/ExpectStableThroughput.actor.cpp rename fdbserver/workloads/{ExternalWorkload.actor.cpp => ExternalWorkload.cpp} (93%) rename fdbserver/workloads/{FailoverWithSSLag.actor.cpp => FailoverWithSSLag.cpp} (69%) rename fdbserver/workloads/{FastTriggeredWatches.actor.cpp => FastTriggeredWatches.cpp} (71%) rename fdbserver/workloads/{FileSystem.actor.cpp => FileSystem.cpp} (70%) rename fdbserver/workloads/{FuzzApiCorrectness.actor.cpp => FuzzApiCorrectness.cpp} (89%) delete mode 100644 fdbserver/workloads/GcGenerations.actor.cpp create mode 100644 fdbserver/workloads/GcGenerations.cpp rename fdbserver/workloads/{GetEstimatedRangeSize.actor.cpp => GetEstimatedRangeSize.cpp} (70%) rename fdbserver/workloads/{GetMappedRange.actor.cpp => GetMappedRange.cpp} (60%) rename fdbserver/workloads/{GetRangeStream.actor.cpp => GetRangeStream.cpp} (50%) rename fdbserver/workloads/{HTTPKeyValueStore.actor.cpp => HTTPKeyValueStore.cpp} (74%) rename fdbserver/workloads/{HealthMetricsApi.actor.cpp => HealthMetricsApi.cpp} (74%) rename fdbserver/workloads/{HighContentionPrefixAllocatorWorkload.actor.cpp => HighContentionPrefixAllocatorWorkload.cpp} (58%) rename fdbserver/workloads/{IDDTxnProcessorApiCorrectness.actor.cpp => IDDTxnProcessorApiCorrectness.cpp} (61%) rename fdbserver/workloads/{Increment.actor.cpp => Increment.cpp} (80%) rename fdbserver/workloads/{IncrementalBackup.actor.cpp => IncrementalBackup.cpp} (52%) rename fdbserver/workloads/{IndexScan.actor.cpp => IndexScan.cpp} (62%) rename fdbserver/workloads/{Inventory.actor.cpp => Inventory.cpp} (82%) rename fdbserver/workloads/{KVStoreTest.actor.cpp => KVStoreTest.cpp} (81%) rename fdbserver/workloads/{KillRegion.actor.cpp => KillRegion.cpp} (65%) rename fdbserver/workloads/{LocalRatekeeper.actor.cpp => LocalRatekeeper.cpp} (69%) rename fdbserver/workloads/{LockDatabase.actor.cpp => LockDatabase.cpp} (57%) rename fdbserver/workloads/{LockDatabaseFrequently.actor.cpp => LockDatabaseFrequently.cpp} (55%) rename fdbserver/workloads/{LogMetrics.actor.cpp => LogMetrics.cpp} (67%) rename fdbserver/workloads/{LowLatency.actor.cpp => LowLatency.cpp} (68%) rename fdbserver/workloads/{MachineAttrition.actor.cpp => MachineAttrition.cpp} (74%) rename fdbserver/workloads/{Mako.actor.cpp => Mako.cpp} (63%) create mode 100644 fdbserver/workloads/MaxGrvQueueDelay.cpp rename fdbserver/{include/fdbserver => }/workloads/MemoryKeyValueStore.h (97%) rename fdbserver/workloads/{MemoryLifetime.actor.cpp => MemoryLifetime.cpp} (62%) rename fdbserver/workloads/{MetricLogging.actor.cpp => MetricLogging.cpp} (74%) rename fdbserver/workloads/{MiniCycle.actor.cpp => MiniCycle.cpp} (81%) rename fdbserver/workloads/{MockDDReadWrite.actor.cpp => MockDDReadWrite.cpp} (90%) rename fdbserver/workloads/{MockDDTest.actor.cpp => MockDDTest.cpp} (97%) rename fdbserver/{include/fdbserver => }/workloads/MockDDTest.h (93%) rename fdbserver/workloads/{MockDDTrackerShardEvaluator.actor.cpp => MockDDTrackerShardEvaluator.cpp} (83%) rename fdbserver/workloads/{MutationLogReaderCorrectness.actor.cpp => MutationLogReaderCorrectness.cpp} (80%) delete mode 100644 fdbserver/workloads/ParallelRestore.actor.cpp rename fdbserver/workloads/{Performance.actor.cpp => Performance.cpp} (79%) delete mode 100644 fdbserver/workloads/PerpetualWiggleStatsWorkload.actor.cpp create mode 100644 fdbserver/workloads/PerpetualWiggleStatsWorkload.cpp rename fdbserver/workloads/{PerpetualWiggleStorageMigrationWorkload.actor.cpp => PerpetualWiggleStorageMigrationWorkload.cpp} (67%) rename fdbserver/workloads/{PhysicalShardMove.actor.cpp => PhysicalShardMove.cpp} (58%) rename fdbserver/workloads/{Ping.actor.cpp => Ping.cpp} (76%) rename fdbserver/workloads/{PrivateEndpoints.actor.cpp => PrivateEndpoints.cpp} (83%) rename fdbserver/workloads/{ProtocolVersion.actor.cpp => ProtocolVersion.cpp} (70%) rename fdbserver/workloads/{PubSubMultiples.actor.cpp => PubSubMultiples.cpp} (69%) rename fdbserver/workloads/{QueuePush.actor.cpp => QueuePush.cpp} (80%) rename fdbserver/workloads/{RYWDisable.actor.cpp => RYWDisable.cpp} (72%) delete mode 100644 fdbserver/workloads/RYWPerformance.actor.cpp create mode 100644 fdbserver/workloads/RYWPerformance.cpp rename fdbserver/workloads/{RandomClogging.actor.cpp => RandomClogging.cpp} (67%) rename fdbserver/workloads/{RandomMoveKeys.actor.cpp => RandomMoveKeys.cpp} (80%) rename fdbserver/workloads/{RandomRangeLock.actor.cpp => RandomRangeLock.cpp} (79%) rename fdbserver/workloads/{RandomSelector.actor.cpp => RandomSelector.cpp} (65%) rename fdbserver/workloads/{RangeLock.actor.cpp => RangeLock.cpp} (72%) rename fdbserver/workloads/{ReadAfterWrite.actor.cpp => ReadAfterWrite.cpp} (60%) rename fdbserver/workloads/{ReadHotDetection.actor.cpp => ReadHotDetection.cpp} (70%) rename fdbserver/workloads/{ReadWrite.actor.cpp => ReadWrite.cpp} (53%) rename fdbserver/{include/fdbserver/workloads/ReadWriteWorkload.actor.h => workloads/ReadWriteWorkload.h} (76%) rename fdbserver/workloads/{RemoveServersSafely.actor.cpp => RemoveServersSafely.cpp} (81%) rename fdbserver/workloads/{ReportConflictingKeys.actor.cpp => ReportConflictingKeys.cpp} (85%) rename fdbserver/workloads/{ResolverBug.actor.cpp => ResolverBug.cpp} (66%) rename fdbserver/workloads/{Restore.actor.cpp => Restore.cpp} (63%) rename fdbserver/workloads/{RestoreBackup.actor.cpp => RestoreBackup.cpp} (59%) rename fdbserver/workloads/{RestoreMultiRanges.actor.cpp => RestoreMultiRanges.cpp} (52%) rename fdbserver/workloads/{RestoreValidation.actor.cpp => RestoreValidation.cpp} (71%) rename fdbserver/workloads/{Rollback.actor.cpp => Rollback.cpp} (78%) rename fdbserver/workloads/{RyowCorrectness.actor.cpp => RyowCorrectness.cpp} (79%) rename fdbserver/workloads/{S3ClientWorkload.actor.cpp => S3ClientWorkload.cpp} (83%) rename fdbserver/workloads/{SaveAndKill.actor.cpp => SaveAndKill.cpp} (83%) rename fdbserver/workloads/{SelectorCorrectness.actor.cpp => SelectorCorrectness.cpp} (79%) rename fdbserver/workloads/{Serializability.actor.cpp => Serializability.cpp} (57%) rename fdbserver/workloads/{Sideband.actor.cpp => Sideband.cpp} (70%) rename fdbserver/workloads/{SidebandSingle.actor.cpp => SidebandSingle.cpp} (75%) rename fdbserver/workloads/{SimpleAtomicAdd.actor.cpp => SimpleAtomicAdd.cpp} (53%) rename fdbserver/workloads/{SkewedReadWrite.actor.cpp => SkewedReadWrite.cpp} (72%) rename fdbserver/workloads/{SlowTaskWorkload.actor.cpp => SlowTaskWorkload.cpp} (89%) rename fdbserver/workloads/{SnapTest.actor.cpp => SnapTest.cpp} (69%) rename fdbserver/workloads/{SpecialKeySpaceCorrectness.actor.cpp => SpecialKeySpaceCorrectness.cpp} (73%) rename fdbserver/workloads/{SpecialKeySpaceRobustness.actor.cpp => SpecialKeySpaceRobustness.cpp} (61%) rename fdbserver/workloads/{StatusWorkload.actor.cpp => StatusWorkload.cpp} (79%) rename fdbserver/workloads/{StorageCorruption.actor.cpp => StorageCorruption.cpp} (74%) rename fdbserver/workloads/{StorageServerCheckpointRestoreTest.actor.cpp => StorageServerCheckpointRestoreTest.cpp} (60%) rename fdbserver/workloads/{Storefront.actor.cpp => Storefront.cpp} (78%) rename fdbserver/workloads/{StreamingRangeRead.actor.cpp => StreamingRangeRead.cpp} (57%) rename fdbserver/workloads/{StreamingRead.actor.cpp => StreamingRead.cpp} (78%) rename fdbserver/workloads/{SubmitBackup.actor.cpp => SubmitBackup.cpp} (61%) rename fdbserver/workloads/{SuspendProcesses.actor.cpp => SuspendProcesses.cpp} (73%) rename fdbserver/workloads/{TagThrottleApi.actor.cpp => TagThrottleApi.cpp} (65%) rename fdbserver/workloads/{TargetedKill.actor.cpp => TargetedKill.cpp} (85%) rename fdbserver/workloads/{TaskBucketCorrectness.actor.cpp => TaskBucketCorrectness.cpp} (72%) rename fdbserver/workloads/{ThreadSafety.actor.cpp => ThreadSafety.cpp} (83%) rename fdbserver/workloads/{Throttling.actor.cpp => Throttling.cpp} (63%) rename fdbserver/workloads/{Throughput.actor.cpp => Throughput.cpp} (76%) delete mode 100644 fdbserver/workloads/ThroughputQuota.actor.cpp rename fdbserver/workloads/{TimeKeeperCorrectness.actor.cpp => TimeKeeperCorrectness.cpp} (61%) rename fdbserver/workloads/{TransactionCost.actor.cpp => TransactionCost.cpp} (74%) rename fdbserver/workloads/{TriggerRecovery.actor.cpp => TriggerRecovery.cpp} (67%) rename fdbserver/workloads/{TxnTimeout.actor.cpp => TxnTimeout.cpp} (62%) delete mode 100644 fdbserver/workloads/UDPWorkload.actor.cpp create mode 100644 fdbserver/workloads/UDPWorkload.cpp rename fdbserver/workloads/{UnitPerf.actor.cpp => UnitPerf.cpp} (74%) rename fdbserver/workloads/{UnitTests.actor.cpp => UnitTests.cpp} (79%) rename fdbserver/workloads/{Unreadable.actor.cpp => Unreadable.cpp} (92%) rename fdbserver/workloads/{ValidateStorage.actor.cpp => ValidateStorage.cpp} (62%) rename fdbserver/workloads/{VersionStamp.actor.cpp => VersionStamp.cpp} (66%) rename fdbserver/workloads/{WatchAndWait.actor.cpp => WatchAndWait.cpp} (69%) rename fdbserver/workloads/{Watches.actor.cpp => Watches.cpp} (89%) rename fdbserver/workloads/{WatchesSameKeyCorrectness.actor.cpp => WatchesSameKeyCorrectness.cpp} (51%) rename fdbserver/workloads/{WorkerErrors.actor.cpp => WorkerErrors.cpp} (61%) rename fdbserver/workloads/{WriteBandwidth.actor.cpp => WriteBandwidth.cpp} (72%) rename fdbserver/workloads/{WriteDuringRead.actor.cpp => WriteDuringRead.cpp} (57%) rename fdbserver/workloads/{WriteTagThrottling.actor.cpp => WriteTagThrottling.cpp} (80%) rename fdbserver/{pubsub.actor.cpp => workloads/pubsub.cpp} (62%) rename fdbserver/{include/fdbserver => workloads}/pubsub.h (98%) rename flow/{CompressedInt.actor.cpp => CompressedInt.cpp} (93%) rename flow/{include/flow => }/CompressionUtils.h (100%) rename {fdbrpc => flow}/CoroTests.cpp (67%) rename flow/{include/flow/EventTypes.actor.h => EventTypes.h} (59%) rename flow/{include/flow => }/FileTraceLogWriter.h (100%) create mode 100644 flow/FlowTest.cpp rename flow/{Hostname.actor.cpp => Hostname.cpp} (75%) delete mode 100644 flow/IAsyncFile.actor.cpp create mode 100644 flow/IAsyncFile.cpp rename flow/{IThreadPoolTest.actor.cpp => IThreadPoolTest.cpp} (68%) rename flow/{include/flow => }/JsonTraceLogFormatter.h (100%) rename flow/{include/flow => }/MetricSample.h (100%) rename flow/{Net2.actor.cpp => Net2.cpp} (87%) rename flow/{Platform.actor.cpp => Platform.cpp} (97%) rename flow/{Profiler.actor.cpp => Profiler.cpp} (90%) rename flow/{TLSConfig.actor.cpp => TLSConfig.cpp} (95%) rename flow/{ThreadHelper.actor.cpp => ThreadHelper.cpp} (81%) rename flow/{WriteOnlySet.actor.cpp => WriteOnlySet.cpp} (96%) rename flow/{include/flow => }/XmlTraceLogFormatter.h (100%) create mode 100644 flow/bench/BenchAsyncResult.cpp rename {flowbench => flow/bench}/BenchCallback.cpp (100%) rename flowbench/BenchCallback.actor.cpp => flow/bench/BenchCallbackActor.cpp (84%) rename {flowbench => flow/bench}/BenchConflictSet.cpp (100%) create mode 100644 flow/bench/BenchCoroChooseRace.cpp create mode 100644 flow/bench/BenchCoroutineOverhead.cpp rename {flowbench => flow/bench}/BenchHash.cpp (84%) create mode 100644 flow/bench/BenchMain.cpp rename {flowbench => flow/bench}/BenchMem.cpp (100%) create mode 100644 flow/bench/BenchMemcpy.cpp rename {flowbench => flow/bench}/BenchNet2.cpp (98%) rename flowbench/BenchNet2.actor.cpp => flow/bench/BenchNet2Actor.cpp (67%) create mode 100644 flow/bench/BenchNoThrowOnCancel.cpp rename flowbench/BenchPriorityMultiLock.actor.cpp => flow/bench/BenchPriorityMultiLock.cpp (76%) rename {flowbench => flow/bench}/BenchRandom.cpp (100%) rename {flowbench => flow/bench}/BenchRef.cpp (100%) rename flowbench/BenchStream.actor.cpp => flow/bench/BenchStream.cpp (71%) rename fdbclient/include/fdbclient/EventTypes.actor.h => flow/bench/BenchSupport.h (56%) create mode 100644 flow/bench/BenchTimeout.cpp rename {flowbench => flow/bench}/BenchTimer.cpp (100%) create mode 100644 flow/bench/BenchWaitForAllReady.cpp rename {flowbench => flow/bench}/BenchZstd.cpp (99%) create mode 100644 flow/bench/CMakeLists.txt rename {flowbench => flow/bench}/README.md (70%) create mode 100644 flow/genericcoros.h rename flowbench/flowbench.actor.cpp => flow/include/flow/BenchMain.h (71%) delete mode 100644 flow/include/flow/IndexedSet.actor.h delete mode 100644 flow/include/flow/Platform.actor.h rename flow/include/flow/{PriorityMultiLock.actor.h => PriorityMultiLock.h} (87%) rename flow/include/flow/{TDMetric.actor.h => TDMetric.h} (91%) rename flow/include/flow/{TLSConfig.actor.h => TLSConfig.h} (95%) create mode 100644 flow/include/flow/TxnCounters.h rename flow/include/flow/{WatchFile.actor.h => WatchFile.h} (60%) rename flow/{include/flow => }/rte_memcpy.h (100%) delete mode 100644 flow/test_memcpy_perf.cpp delete mode 100644 flowbench/CMakeLists.txt delete mode 100644 tests/ParallelRestoreApiCorrectnessAtomicRestore.txt delete mode 100644 tests/ParallelRestoreOldBackupApiCorrectnessAtomicRestore.toml create mode 100644 tests/TestRunner/fdb_test_runner/fdb_version.py delete mode 100644 tests/fast/EncryptionUnitTests.toml create mode 100644 tests/fast/MaxGrvQueueDelay.toml create mode 100644 tests/fast/StatusDuringOutage.toml delete mode 100644 tests/rare/ExpectStableThroughput.toml delete mode 100644 tests/rare/ThroughputQuota.toml delete mode 100644 tests/slow/ParallelRestoreNewBackupCorrectnessAtomicOp.toml delete mode 100644 tests/slow/ParallelRestoreNewBackupCorrectnessCycle.toml delete mode 100644 tests/slow/ParallelRestoreNewBackupCorrectnessMultiCycles.toml delete mode 100644 tests/slow/ParallelRestoreNewBackupWriteDuringReadAtomicRestore.toml delete mode 100644 tests/slow/ParallelRestoreOldBackupCorrectnessAtomicOp.toml delete mode 100644 tests/slow/ParallelRestoreOldBackupCorrectnessCycle.toml delete mode 100644 tests/slow/ParallelRestoreOldBackupCorrectnessMultiCycles.toml delete mode 100644 tests/slow/ParallelRestoreOldBackupWriteDuringReadAtomicRestore.toml diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000000..5db9e83d08c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,21 @@ +--- +Checks: > + -*, + bugprone-sizeof-expression, + bugprone-suspicious-string-compare, + bugprone-use-after-move, + modernize-use-auto, + modernize-use-equals-default, + modernize-use-override, + modernize-use-using, + performance-for-range-copy, + readability-container-contains, + readability-const-return-type, + readability-container-size-empty, + readability-delete-null-pointer, + readability-duplicate-include, + readability-inconsistent-ifelse-braces +CheckOptions: + - key: modernize-use-auto.MinTypeNameLength + value: '11' +... diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000000..7b55968c90d --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,24 @@ +name: 'Close stale pull requests' +on: + schedule: + - cron: '15 20 * * *' # 20:15 UTC + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 + with: + stale-pr-message: > + This PR is stale because it has been open 150 days with no activity. + Remove stale label or add a comment to prevent automatic closure in 14 days. + days-before-pr-stale: 150 + days-before-close: 14 + days-before-issue-stale: 5475 # 15 years, basically disabled for issues + # throttled to 20 per day + operations-per-run: 20 + ascending: true diff --git a/.github/workflows/windows-boost-test.yml b/.github/workflows/windows-boost-test.yml index 8bf7f551a05..e19b8d5e59c 100644 --- a/.github/workflows/windows-boost-test.yml +++ b/.github/workflows/windows-boost-test.yml @@ -24,7 +24,7 @@ jobs: run: | vcpkg install boost-filesystem:x64-windows boost-iostreams:x64-windows boost-serialization:x64-windows boost-system:x64-windows boost-program-options:x64-windows boost-url:x64-windows boost-context:x64-windows lz4:x64-windows shell: cmd - + - name: Configure and Build FoundationDB (without Swift) run: | mkdir build diff --git a/.gitignore b/.gitignore index b608ae140fd..fae11ee2e97 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ cmake-build-*/ *.g.S *.g.asm *.pom +fdbserver/include/fdbserver/FDBRocksDBVersion.h bindings/java/pom*.xml bindings/java/src*/main/overview.html bindings/java/src*/main/com/apple/foundationdb/NetworkOptions.java @@ -113,4 +114,5 @@ temp/ # AI assistants *.aider* -*.fdq \ No newline at end of file +*.fdq +.claude/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 01314db22ce..33bd957d068 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,14 +314,11 @@ if(NOT WIN32) if(NOT FOUNDATIONDB_CROSS_COMPILING) # FIXME(swift): make this work when # x-compiling. add_subdirectory(fdbmonitor) + add_subdirectory(fdbkubernetesmonitor) endif() endif() add_subdirectory(fdbbackup) add_subdirectory(tests) -if(NOT FOUNDATIONDB_CROSS_COMPILING) # FIXME(swift): make this work when - # x-compiling. - add_subdirectory(flowbench EXCLUDE_FROM_ALL) -endif() if(WITH_PYTHON AND WITH_C_BINDING) if(NOT FOUNDATIONDB_CROSS_COMPILING) # FIXME(swift): make this work when # x-compiling. diff --git a/README.md b/README.md index 3a74c410803..dfa32e585f2 100755 --- a/README.md +++ b/README.md @@ -140,17 +140,18 @@ Building FoundationDB requires at least 8GB of memory. More memory is needed whe ### macOS -The build under macOS will work the same way as on Linux. [Homebrew](https://brew.sh/) can be used to install the `boost` library and the `ninja` build tool. +The build under macOS will work the same way as on Linux. [Homebrew](https://brew.sh/) can be used to install the `boost` library and the `ninja` build tool. Be carefull, curent main branch use boost 1.86, do install this version or just let cmake download one. Also, if swift binding is not interest, use -DBUILD_SWIFT_BINDING=OFF. ```sh -cmake -G Ninja +cmake -G Ninja -B +cd ninja ``` To generate an installable package, ```sh -/packaging/osx/buildpkg.sh . +/packaging/osx/buildpkg.sh ``` ### Windows @@ -185,6 +186,10 @@ CMake will not produce a `compile_commands.json` by default; you must pass `-DCM Note that if the building is done inside the `foundationdb/build` Docker image, the resulting paths will still be incorrect and require manual fixing. One will wish to re-run `cmake` with `-DCMAKE_EXPORT_COMPILE_COMMANDS=OFF` to prevent it from reverting the manual changes. +### Code Formatting and Static Analysis + +`clang-format` and `clang-tidy` run as part of CI on every pull request. See the [clang-format](https://apple.github.io/foundationdb/clang-format.html) and [clang-tidy](https://apple.github.io/foundationdb/clang-tidy.html) guides for how to run them locally before pushing. + ### Using IDEs CMake provides built-in support for several popular IDEs. However, most FoundationDB files are written in the `flow` language, which is an extension of the C++ programming language, for coroutine support (Note that when FoundationDB was being developed, C++20 was not available). The `flow` language will be transpiled into C++ code using `actorcompiler`, while preventing most IDEs from recognizing `flow`-specific syntax. diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index a6405a1ccf9..422bbe6e0a4 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -211,7 +211,7 @@ if(NOT WIN32) target_link_libraries(fdb_c_client_memory_test PRIVATE fdb_c Threads::Threads) target_include_directories(fdb_c_api_tester_impl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/foundationdb/ ${CMAKE_SOURCE_DIR}/flow/include ${CMAKE_BINARY_DIR}/flow/include) - target_link_libraries(fdb_c_api_tester_impl PRIVATE fdb_cpp toml11_target Threads::Threads fmt::fmt boost_target) + target_link_libraries(fdb_c_api_tester_impl PRIVATE fdb_cpp toml11::toml11 Threads::Threads fmt::fmt boost_target) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_link_libraries(fdb_c_api_tester_impl PRIVATE stdc++fs) endif() @@ -230,6 +230,9 @@ if(NOT WIN32) # do not set RPATH for mako set_property(TARGET mako PROPERTY SKIP_BUILD_RPATH TRUE) target_link_libraries(mako PRIVATE fdb_c fdbclient fmt::fmt Threads::Threads fdb_cpp boost_target rapidjson) + if(NOT OPEN_FOR_IDE) + strip_debug_symbols(mako) + endif() if(NOT OPEN_FOR_IDE) # Make sure that fdb_c.h is compatible with c90 diff --git a/bindings/c/fdb_c.cpp b/bindings/c/fdb_c.cpp index bdb2f90f84b..d904e50f760 100644 --- a/bindings/c/fdb_c.cpp +++ b/bindings/c/fdb_c.cpp @@ -86,7 +86,7 @@ extern "C" DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_erro code == error_code_commit_proxy_memory_limit_exceeded || code == error_code_transaction_throttled_hot_shard || code == error_code_batch_transaction_throttled || code == error_code_process_behind || code == error_code_tag_throttled || - code == error_code_proxy_tag_throttled || code == error_code_transaction_rejected_range_locked; + code == error_code_transaction_rejected_range_locked; } return false; } @@ -222,7 +222,7 @@ class CAPICallback final : public ThreadCallback { extern "C" DLLEXPORT fdb_error_t fdb_future_set_callback(FDBFuture* f, void (*callbackf)(FDBFuture*, void*), void* userdata) { - CAPICallback* cb = new CAPICallback(callbackf, f, userdata); + auto* cb = new CAPICallback(callbackf, f, userdata); int ignore; CATCH_AND_RETURN(TSAVB(f)->callOrSetAsCallback(cb, ignore, 0);); } diff --git a/bindings/c/test/apitester/TesterApiWorkload.cpp b/bindings/c/test/apitester/TesterApiWorkload.cpp index 7a607da357f..b295aeb7a84 100644 --- a/bindings/c/test/apitester/TesterApiWorkload.cpp +++ b/bindings/c/test/apitester/TesterApiWorkload.cpp @@ -171,7 +171,7 @@ fdb::KeyRange ApiWorkload::randomNonEmptyKeyRange() { } std::optional ApiWorkload::randomTenant() { - if (tenants.size() > 0) { + if (!tenants.empty()) { return Random::get().randomInt(0, tenants.size() - 1); } else { return {}; @@ -244,7 +244,7 @@ void ApiWorkload::populateTenantData(TTaskFct cont, std::optional tenantId) } void ApiWorkload::createTenantsIfNecessary(TTaskFct cont) { - if (tenants.size() > 0) { + if (!tenants.empty()) { ASSERT(false); } else { schedule(cont); @@ -252,7 +252,7 @@ void ApiWorkload::createTenantsIfNecessary(TTaskFct cont) { } void ApiWorkload::populateData(TTaskFct cont) { - if (tenants.size() > 0) { + if (!tenants.empty()) { populateTenantData(cont, std::make_optional(0)); } else { populateTenantData(cont, {}); diff --git a/bindings/c/test/apitester/TesterAtomicOpsCorrectnessWorkload.cpp b/bindings/c/test/apitester/TesterAtomicOpsCorrectnessWorkload.cpp index 3743bbab882..fa619e55536 100644 --- a/bindings/c/test/apitester/TesterAtomicOpsCorrectnessWorkload.cpp +++ b/bindings/c/test/apitester/TesterAtomicOpsCorrectnessWorkload.cpp @@ -37,8 +37,8 @@ class AtomicOpsCorrectnessWorkload : public ApiWorkload { AtomicOpsCorrectnessWorkload(const WorkloadConfig& config) : ApiWorkload(config) {} private: - typedef std::function IntAtomicOpFunction; - typedef std::function AtomicOpFunction; + using IntAtomicOpFunction = std::function; + using AtomicOpFunction = std::function; enum OpType { OP_ATOMIC_ADD, @@ -57,7 +57,7 @@ class AtomicOpsCorrectnessWorkload : public ApiWorkload { }; void randomOperation(TTaskFct cont) override { - OpType txType = (OpType)Random::get().randomInt(0, OP_LAST); + auto txType = OpType(Random::get().randomInt(0, OP_LAST)); switch (txType) { case OP_ATOMIC_ADD: diff --git a/bindings/c/test/apitester/TesterCorrectnessWorkload.cpp b/bindings/c/test/apitester/TesterCorrectnessWorkload.cpp index deb77e16e47..c58b75a874d 100644 --- a/bindings/c/test/apitester/TesterCorrectnessWorkload.cpp +++ b/bindings/c/test/apitester/TesterCorrectnessWorkload.cpp @@ -248,7 +248,7 @@ class ApiCorrectnessWorkload : public ApiWorkload { results->size())); } else { auto expected_kv = expected.begin(); - for (auto actual_kv : *results) { + for (const auto& actual_kv : *results) { if (actual_kv.key != expected_kv->key || actual_kv.value != expected_kv->value) { error(fmt::format( "randomGetRangeOp mismatch. expected key: {} actual key: {} expected value: " @@ -266,7 +266,7 @@ class ApiCorrectnessWorkload : public ApiWorkload { getTenant(tenantId)); } - void randomOperation(TTaskFct cont) { + void randomOperation(TTaskFct cont) override { std::optional tenantId = randomTenant(); OpType txType = (stores[tenantId].size() == 0) ? OP_INSERT : (OpType)Random::get().randomInt(0, OP_LAST); diff --git a/bindings/c/test/apitester/TesterKeyValueStore.cpp b/bindings/c/test/apitester/TesterKeyValueStore.cpp index af572d628bf..2a4e910b707 100644 --- a/bindings/c/test/apitester/TesterKeyValueStore.cpp +++ b/bindings/c/test/apitester/TesterKeyValueStore.cpp @@ -42,7 +42,7 @@ bool KeyValueStore::exists(fdb::KeyRef key) { fdb::Key KeyValueStore::getKey(fdb::KeyRef keyName, bool orEqual, int offset) const { std::unique_lock lock(mutex); // Begin by getting the start key referenced by the key selector - std::map::const_iterator mapItr = store.lower_bound(keyName); + auto mapItr = store.lower_bound(keyName); // Update the iterator position if necessary based on the value of orEqual int count = 0; @@ -92,16 +92,16 @@ std::vector KeyValueStore::getRange(fdb::KeyRef begin, fdb::KeyRe std::unique_lock lock(mutex); std::vector results; if (!reverse) { - std::map::const_iterator mapItr = store.lower_bound(begin); - - for (; mapItr != store.end() && mapItr->first < end && results.size() < limit; mapItr++) + for (auto mapItr = store.lower_bound(begin); + mapItr != store.end() && mapItr->first < end && results.size() < limit; + mapItr++) results.push_back(fdb::KeyValue{ mapItr->first, mapItr->second }); } // Support for reverse getRange queries is supported, but not tested at this time. This is because reverse range // queries have been disallowed by the database at the API level else { - std::map::const_iterator mapItr = store.lower_bound(end); + auto mapItr = store.lower_bound(end); if (mapItr == store.begin()) return results; diff --git a/bindings/c/test/apitester/TesterTestSpec.cpp b/bindings/c/test/apitester/TesterTestSpec.cpp index 2a8ab161691..f65784a7d29 100644 --- a/bindings/c/test/apitester/TesterTestSpec.cpp +++ b/bindings/c/test/apitester/TesterTestSpec.cpp @@ -137,7 +137,7 @@ TestSpec readTomlTestSpec(std::string fileName) { // Then parse each test const toml::array& tests = toml::find(conf, "test").as_array(); - if (tests.size() == 0) { + if (tests.empty()) { throw TesterError("Invalid test file. No [test] section found"); } else if (tests.size() > 1) { throw TesterError("Invalid test file. More than one [test] section found"); diff --git a/bindings/c/test/apitester/TesterTransactionExecutor.cpp b/bindings/c/test/apitester/TesterTransactionExecutor.cpp index 6bd4cf30d62..19cec9fe8fc 100644 --- a/bindings/c/test/apitester/TesterTransactionExecutor.cpp +++ b/bindings/c/test/apitester/TesterTransactionExecutor.cpp @@ -97,7 +97,7 @@ class TransactionContextBase : public ITransactionContext { } } - virtual ~TransactionContextBase() { ASSERT(txState == TxState::DONE); } + ~TransactionContextBase() override { ASSERT(txState == TxState::DONE); } // A state machine: // IN_PROGRESS -> (ON_ERROR -> IN_PROGRESS)* [-> ON_ERROR] -> DONE @@ -165,7 +165,7 @@ class TransactionContextBase : public ITransactionContext { } } - virtual void onError(fdb::Error err) override { + void onError(fdb::Error err) override { std::unique_lock lock(mutex); if (txState != TxState::IN_PROGRESS) { // Ignore further errors, if the transaction is in the error handing mode or completed @@ -438,7 +438,7 @@ class BlockingTransactionContext : public TransactionContextBase { onError(err); } - virtual void handleOnErrorFuture() override { + void handleOnErrorFuture() override { ASSERT(txState == TxState::ON_ERROR); auto start = timeNow(); @@ -503,7 +503,7 @@ class AsyncTransactionContext : public TransactionContextBase { static void futureReadyCallback(fdb::Future f, void* param) { try { - AsyncTransactionContext* txCtx = (AsyncTransactionContext*)param; + auto* txCtx = (AsyncTransactionContext*)param; txCtx->onFutureReady(f); } catch (std::exception& err) { fmt::print("Unexpected exception in callback {}\n", err.what()); @@ -550,7 +550,7 @@ class AsyncTransactionContext : public TransactionContextBase { onError(err); } - virtual void handleOnErrorFuture() override { + void handleOnErrorFuture() override { ASSERT(txState == TxState::ON_ERROR); onErrorCallTimePoint = timeNow(); @@ -565,7 +565,7 @@ class AsyncTransactionContext : public TransactionContextBase { static void onErrorReadyCallback(fdb::Future f, void* param) { try { - AsyncTransactionContext* txCtx = (AsyncTransactionContext*)param; + auto* txCtx = (AsyncTransactionContext*)param; txCtx->onErrorReady(f); } catch (std::exception& err) { fmt::print("Unexpected exception in callback {}\n", err.what()); @@ -642,7 +642,7 @@ class TransactionExecutorBase : public ITransactionExecutor { public: TransactionExecutorBase(const TransactionExecutorOptions& options) : options(options), scheduler(nullptr) {} - ~TransactionExecutorBase() { + ~TransactionExecutorBase() override { if (tamperClusterFileThread.joinable()) { tamperClusterFileThread.join(); } diff --git a/bindings/c/test/apitester/TesterUtil.h b/bindings/c/test/apitester/TesterUtil.h index 4b5e6d41926..f8c96f63892 100644 --- a/bindings/c/test/apitester/TesterUtil.h +++ b/bindings/c/test/apitester/TesterUtil.h @@ -136,7 +136,8 @@ KeyRangeArray copyKeyRangeArray(fdb::future_var::KeyRangeRefArray::Type array); static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "Do not support non-little-endian systems"); // Converts a little-endian encoded number into an integral type. -template ::value>> +template + requires(std::is_integral_v) static T toInteger(fdb::BytesRef value) { ASSERT(value.size() == sizeof(T)); T output; @@ -145,7 +146,8 @@ static T toInteger(fdb::BytesRef value) { } // Converts an integral type to a little-endian encoded byte string. -template ::value>> +template + requires(std::is_integral_v) static fdb::ByteString toByteString(T value) { fdb::ByteString output(sizeof(T), 0); memcpy(output.data(), (const uint8_t*)&value, sizeof(value)); diff --git a/bindings/c/test/apitester/TesterWorkload.cpp b/bindings/c/test/apitester/TesterWorkload.cpp index ec8ca8487d7..6faf51fc769 100644 --- a/bindings/c/test/apitester/TesterWorkload.cpp +++ b/bindings/c/test/apitester/TesterWorkload.cpp @@ -204,14 +204,14 @@ void WorkloadManager::run() { std::vector> initialWorkloads; { std::unique_lock lock(mutex); - for (auto iter : workloads) { + for (const auto& iter : workloads) { initialWorkloads.push_back(iter.second.ref); } } - for (auto iter : initialWorkloads) { + for (const auto& iter : initialWorkloads) { iter->init(this); } - for (auto iter : initialWorkloads) { + for (const auto& iter : initialWorkloads) { iter->start(); } scheduler->join(); @@ -265,7 +265,7 @@ void WorkloadManager::readControlInput(std::string pipeName) { // Open in binary mode and read char-by-char to avoid // any kind of buffering FILE* f = fopen(pipeName.c_str(), "rb"); - setbuf(f, NULL); + setbuf(f, nullptr); std::string line; while (true) { int ch = fgetc(f); @@ -291,7 +291,7 @@ void WorkloadManager::readControlInput(std::string pipeName) { void WorkloadManager::schedulePrintStatistics(int timeIntervalMs) { statsTimer = scheduler->scheduleWithDelay(timeIntervalMs, [this, timeIntervalMs]() { - for (auto workload : getActiveWorkloads()) { + for (const auto& workload : getActiveWorkloads()) { workload->printStats(); } this->schedulePrintStatistics(timeIntervalMs); @@ -301,7 +301,7 @@ void WorkloadManager::schedulePrintStatistics(int timeIntervalMs) { std::vector> WorkloadManager::getActiveWorkloads() { std::unique_lock lock(mutex); std::vector> res; - for (auto iter : workloads) { + for (const auto& iter : workloads) { res.push_back(iter.second.ref); } return res; diff --git a/bindings/c/test/apitester/fdb_c_api_tester.cpp b/bindings/c/test/apitester/fdb_c_api_tester.cpp index 4ae10f8fc00..35f38b93d8a 100644 --- a/bindings/c/test/apitester/fdb_c_api_tester.cpp +++ b/bindings/c/test/apitester/fdb_c_api_tester.cpp @@ -325,7 +325,7 @@ void applyNetworkOptions(TesterOptions& options) { fdb::network::setOption(FDBNetworkOption::FDB_NET_OPTION_RETAIN_CLIENT_LIBRARY_COPIES); } - for (auto knob : options.testSpec.knobs) { + for (const auto& knob : options.testSpec.knobs) { fmt::print(stderr, "Setting knob {}={}\n", knob.first.c_str(), knob.second.c_str()); fdb::network::setOption(FDBNetworkOption::FDB_NET_OPTION_KNOB, fmt::format("{}={}", knob.first.c_str(), knob.second.c_str())); diff --git a/bindings/c/test/mako/async.cpp b/bindings/c/test/mako/async.cpp index 5f9c1b058f5..25bd8640254 100644 --- a/bindings/c/test/mako/async.cpp +++ b/bindings/c/test/mako/async.cpp @@ -74,7 +74,7 @@ void ResumableStateForPopulate::runOneTick() { stats.incrOpCount(OP_COMMIT); stats.incrOpCount(OP_TRANSACTION); tx.reset(); - setTransactionTimeoutIfEnabled(args, tx); + setTransactionOptionsIfEnabled(args, tx); watch_tx.startFromStop(); key_checkpoint = i + 1; if (i != key_end) { @@ -165,7 +165,7 @@ void ResumableStateForRunWorkload::updateStepStats() { stats.addLatency(OP_COMMIT, step_latency); } tx.reset(); - setTransactionTimeoutIfEnabled(args, tx); + setTransactionOptionsIfEnabled(args, tx); stats.incrOpCount(OP_COMMIT); needs_commit = false; } @@ -211,7 +211,7 @@ void ResumableStateForRunWorkload::onTransactionSuccess() { stats.incrOpCount(OP_COMMIT); stats.incrOpCount(OP_TRANSACTION); tx.reset(); - setTransactionTimeoutIfEnabled(args, tx); + setTransactionOptionsIfEnabled(args, tx); watch_tx.startFromStop(); onIterationEnd(FutureRC::OK); } @@ -226,7 +226,7 @@ void ResumableStateForRunWorkload::onTransactionSuccess() { stats.incrOpCount(OP_TRANSACTION); watch_tx.startFromStop(); tx.reset(); - setTransactionTimeoutIfEnabled(args, tx); + setTransactionOptionsIfEnabled(args, tx); onIterationEnd(FutureRC::OK); } } @@ -238,6 +238,9 @@ void ResumableStateForRunWorkload::onIterationEnd(FutureRC rc) { if (ended()) { signalEnd(); } else { + if (rc == FutureRC::RETRY) { + setTransactionOptionsIfEnabled(args, tx); + } iter = getOpBegin(args); needs_commit = false; postNextTick(); diff --git a/bindings/c/test/mako/async.hpp b/bindings/c/test/mako/async.hpp index 26271b7aac7..66867f739d1 100644 --- a/bindings/c/test/mako/async.hpp +++ b/bindings/c/test/mako/async.hpp @@ -109,7 +109,7 @@ struct ResumableStateForRunWorkload : std::enable_shared_from_this= max_iters) || signal.load() == SIGNAL_RED; } diff --git a/bindings/c/test/mako/mako.cpp b/bindings/c/test/mako/mako.cpp index b628980bd48..01859e5300f 100644 --- a/bindings/c/test/mako/mako.cpp +++ b/bindings/c/test/mako/mako.cpp @@ -18,6 +18,7 @@ * limitations under the License. */ +#include #include #include #include @@ -38,6 +39,8 @@ #include #include +#include "flow/BooleanParam.h" + #include #include #include @@ -45,8 +48,6 @@ #include #include #include -#include -#include #include #include #include @@ -291,6 +292,9 @@ int runOneTransaction(Transaction& tx, if (future_rc == FutureRC::ABORT) { return -1; } + setTransactionOptionsIfEnabled(args, tx); + if (token) + tx.setOption(FDB_TR_OPTION_AUTHORIZATION_TOKEN, *token); // retry from first op op_iter = getOpBegin(args); needs_commit = false; @@ -304,6 +308,7 @@ int runOneTransaction(Transaction& tx, stats.addLatency(OP_COMMIT, step_latency); } tx.reset(); + setTransactionOptionsIfEnabled(args, tx); if (token) tx.setOption(FDB_TR_OPTION_AUTHORIZATION_TOKEN, *token); stats.incrOpCount(OP_COMMIT); @@ -331,8 +336,9 @@ int runOneTransaction(Transaction& tx, const auto rc = waitAndHandleError(tx, f, "COMMIT_AT_TX_END", args.isAnyTimeoutEnabled()); updateErrorStatsRunMode(stats, f.error(), OP_COMMIT); watch_commit.stop(); - auto tx_resetter = ExitGuard([&tx, &token]() { + auto tx_resetter = ExitGuard([&tx, &token, &args]() { tx.reset(); + setTransactionOptionsIfEnabled(args, tx); if (token) tx.setOption(FDB_TR_OPTION_AUTHORIZATION_TOKEN, *token); }); @@ -418,7 +424,7 @@ int runWorkload(Database db, if (current_tps > 0 || thread_tps == 0 /* throttling off */) { auto [tx, token] = createNewTransaction(db, args, -1); - setTransactionTimeoutIfEnabled(args, tx); + setTransactionOptionsIfEnabled(args, tx); /* enable transaction trace */ if (dotrace) { @@ -541,7 +547,7 @@ void runAsyncWorkload(Arguments const& args, while (shm.headerConst().signal.load() != SIGNAL_GREEN) usleep(1000); // launch [async_xacts] concurrent transactions - for (auto state : states) + for (const auto& state : states) state->postNextTick(); while (stopcount.load() != args.async_xacts) usleep(1000); @@ -573,7 +579,7 @@ void runAsyncWorkload(Arguments const& args, } while (shm.headerConst().signal.load() != SIGNAL_GREEN) usleep(1000); - for (auto state : states) + for (const auto& state : states) state->postNextTick(); logr.debug("Launched {} concurrent transactions", states.size()); while (stopcount.load() != args.async_xacts) @@ -787,6 +793,7 @@ Arguments::Arguments() { load_factor = 1.0; row_digits = digits(rows); seconds = 0; + warmup_seconds = 0; iteration = 0; tpsmax = 0; tpsmin = -1; @@ -823,6 +830,7 @@ Arguments::Arguments() { distributed_tracer_client = 0; transaction_timeout_db = 0; transaction_timeout_tx = 0; + max_grv_queue_delay_ms = -1; num_report_files = 0; } @@ -968,6 +976,9 @@ int parseTransaction(Arguments& args, char const* optarg) { } else if (strncmp(ptr, "g", 1) == 0) { op = OP_GET; ptr++; + } else if (strncmp(ptr, "sj", 2) == 0) { + op = OP_STATUSJSON; + ptr += 2; } else if (strncmp(ptr, "sgr", 3) == 0) { op = OP_SGETRANGE; rangeop = 1; @@ -1084,6 +1095,9 @@ void usage() { printf("%-24s %s\n", "-l, --load_factor=LOAD_FACTOR", "Specify load factor"); printf("%-24s %s\n", "-s, --seconds=SECONDS", "Specify the test duration in seconds\n"); printf("%-24s %s\n", "", "This option cannot be specified with --iteration."); + printf("%-24s %s\n", + " --warmup_seconds=SECONDS", + "Ignore the initial SECONDS of a run when computing aggregate throughput and count-based totals"); printf("%-24s %s\n", "-i, --iteration=ITERS", "Specify the number of iterations.\n"); printf("%-24s %s\n", "", "This option cannot be specified with --seconds."); printf("%-24s %s\n", " --keylen=LENGTH", "Specify the key lengths"); @@ -1135,6 +1149,9 @@ void usage() { printf("%-24s %s\n", " --transaction_timeout_tx=DURATION", "Duration in milliseconds after which a transaction times out in run mode. Set as transaction option"); + printf("%-24s %s\n", + " --max_grv_queue_delay=DURATION", + "Maximum estimated GRV proxy queue delay in milliseconds. Set as transaction option in run mode."); } /* parse benchmark parameters */ @@ -1147,59 +1164,64 @@ int parseArguments(int argc, char* argv[], Arguments& args) { static struct option long_options[] = { /* name, has_arg, flag, val */ /* options requiring an argument */ - { "api_version", required_argument, NULL, 'a' }, - { "cluster", required_argument, NULL, 'c' }, - { "num_databases", required_argument, NULL, 'd' }, - { "procs", required_argument, NULL, 'p' }, - { "threads", required_argument, NULL, 't' }, - { "async_xacts", required_argument, NULL, ARG_ASYNC }, - { "rows", required_argument, NULL, 'r' }, - { "load_factor", required_argument, NULL, 'l' }, - { "seconds", required_argument, NULL, 's' }, - { "iteration", required_argument, NULL, 'i' }, - { "keylen", required_argument, NULL, ARG_KEYLEN }, - { "vallen", required_argument, NULL, ARG_VALLEN }, - { "transaction", required_argument, NULL, 'x' }, - { "tps", required_argument, NULL, ARG_TPS }, - { "tpsmax", required_argument, NULL, ARG_TPSMAX }, - { "tpsmin", required_argument, NULL, ARG_TPSMIN }, - { "tpsinterval", required_argument, NULL, ARG_TPSINTERVAL }, - { "tpschange", required_argument, NULL, ARG_TPSCHANGE }, - { "sampling", required_argument, NULL, ARG_SAMPLING }, - { "verbose", required_argument, NULL, 'v' }, - { "mode", required_argument, NULL, 'm' }, - { "knobs", required_argument, NULL, ARG_KNOBS }, - { "loggroup", required_argument, NULL, ARG_LOGGROUP }, - { "tracepath", required_argument, NULL, ARG_TRACEPATH }, - { "trace_format", required_argument, NULL, ARG_TRACEFORMAT }, - { "streaming", required_argument, NULL, ARG_STREAMING_MODE }, - { "txntrace", required_argument, NULL, ARG_TXNTRACE }, - { "txntagging", required_argument, NULL, ARG_TXNTAGGING }, - { "txntagging_prefix", required_argument, NULL, ARG_TXNTAGGINGPREFIX }, - { "client_threads_per_version", required_argument, NULL, ARG_CLIENT_THREADS_PER_VERSION }, - { "bg_file_path", required_argument, NULL, ARG_BG_FILE_PATH }, - { "distributed_tracer_client", required_argument, NULL, ARG_DISTRIBUTED_TRACER_CLIENT }, - { "tls_certificate_file", required_argument, NULL, ARG_TLS_CERTIFICATE_FILE }, - { "tls_key_file", required_argument, NULL, ARG_TLS_KEY_FILE }, - { "tls_ca_file", required_argument, NULL, ARG_TLS_CA_FILE }, - { "authorization_keypair_id", required_argument, NULL, ARG_AUTHORIZATION_KEYPAIR_ID }, - { "authorization_private_key_pem_file", required_argument, NULL, ARG_AUTHORIZATION_PRIVATE_KEY_PEM_FILE }, - { "transaction_timeout_tx", required_argument, NULL, ARG_TRANSACTION_TIMEOUT_TX }, - { "transaction_timeout_db", required_argument, NULL, ARG_TRANSACTION_TIMEOUT_DB }, + { "api_version", required_argument, nullptr, 'a' }, + { "cluster", required_argument, nullptr, 'c' }, + { "num_databases", required_argument, nullptr, 'd' }, + { "procs", required_argument, nullptr, 'p' }, + { "threads", required_argument, nullptr, 't' }, + { "async_xacts", required_argument, nullptr, ARG_ASYNC }, + { "rows", required_argument, nullptr, 'r' }, + { "load_factor", required_argument, nullptr, 'l' }, + { "seconds", required_argument, nullptr, 's' }, + { "warmup_seconds", required_argument, nullptr, ARG_WARMUP_SECONDS }, + { "iteration", required_argument, nullptr, 'i' }, + { "keylen", required_argument, nullptr, ARG_KEYLEN }, + { "vallen", required_argument, nullptr, ARG_VALLEN }, + { "transaction", required_argument, nullptr, 'x' }, + { "tps", required_argument, nullptr, ARG_TPS }, + { "tpsmax", required_argument, nullptr, ARG_TPSMAX }, + { "tpsmin", required_argument, nullptr, ARG_TPSMIN }, + { "tpsinterval", required_argument, nullptr, ARG_TPSINTERVAL }, + { "tpschange", required_argument, nullptr, ARG_TPSCHANGE }, + { "sampling", required_argument, nullptr, ARG_SAMPLING }, + { "verbose", required_argument, nullptr, 'v' }, + { "mode", required_argument, nullptr, 'm' }, + { "knobs", required_argument, nullptr, ARG_KNOBS }, + { "loggroup", required_argument, nullptr, ARG_LOGGROUP }, + { "tracepath", required_argument, nullptr, ARG_TRACEPATH }, + { "trace_format", required_argument, nullptr, ARG_TRACEFORMAT }, + { "streaming", required_argument, nullptr, ARG_STREAMING_MODE }, + { "txntrace", required_argument, nullptr, ARG_TXNTRACE }, + { "txntagging", required_argument, nullptr, ARG_TXNTAGGING }, + { "txntagging_prefix", required_argument, nullptr, ARG_TXNTAGGINGPREFIX }, + { "client_threads_per_version", required_argument, nullptr, ARG_CLIENT_THREADS_PER_VERSION }, + { "bg_file_path", required_argument, nullptr, ARG_BG_FILE_PATH }, + { "distributed_tracer_client", required_argument, nullptr, ARG_DISTRIBUTED_TRACER_CLIENT }, + { "tls_certificate_file", required_argument, nullptr, ARG_TLS_CERTIFICATE_FILE }, + { "tls_key_file", required_argument, nullptr, ARG_TLS_KEY_FILE }, + { "tls_ca_file", required_argument, nullptr, ARG_TLS_CA_FILE }, + { "authorization_keypair_id", required_argument, nullptr, ARG_AUTHORIZATION_KEYPAIR_ID }, + { "authorization_private_key_pem_file", + required_argument, + nullptr, + ARG_AUTHORIZATION_PRIVATE_KEY_PEM_FILE }, + { "transaction_timeout_tx", required_argument, nullptr, ARG_TRANSACTION_TIMEOUT_TX }, + { "transaction_timeout_db", required_argument, nullptr, ARG_TRANSACTION_TIMEOUT_DB }, + { "max_grv_queue_delay", required_argument, nullptr, ARG_MAX_GRV_QUEUE_DELAY }, /* options which may or may not have an argument */ - { "json_report", optional_argument, NULL, ARG_JSON_REPORT }, - { "stats_export_path", optional_argument, NULL, ARG_EXPORT_PATH }, + { "json_report", optional_argument, nullptr, ARG_JSON_REPORT }, + { "stats_export_path", optional_argument, nullptr, ARG_EXPORT_PATH }, /* options without an argument */ - { "help", no_argument, NULL, 'h' }, - { "zipf", no_argument, NULL, 'z' }, - { "commitget", no_argument, NULL, ARG_COMMITGET }, - { "flatbuffers", no_argument, NULL, ARG_FLATBUFFERS }, - { "prefix_padding", no_argument, NULL, ARG_PREFIXPADDING }, - { "trace", no_argument, NULL, ARG_TRACE }, - { "version", no_argument, NULL, ARG_VERSION }, - { "disable_client_bypass", no_argument, NULL, ARG_DISABLE_CLIENT_BYPASS }, - { "disable_ryw", no_argument, NULL, ARG_DISABLE_RYW }, - { NULL, 0, NULL, 0 } + { "help", no_argument, nullptr, 'h' }, + { "zipf", no_argument, nullptr, 'z' }, + { "commitget", no_argument, nullptr, ARG_COMMITGET }, + { "flatbuffers", no_argument, nullptr, ARG_FLATBUFFERS }, + { "prefix_padding", no_argument, nullptr, ARG_PREFIXPADDING }, + { "trace", no_argument, nullptr, ARG_TRACE }, + { "version", no_argument, nullptr, ARG_VERSION }, + { "disable_client_bypass", no_argument, nullptr, ARG_DISABLE_CLIENT_BYPASS }, + { "disable_ryw", no_argument, nullptr, ARG_DISABLE_RYW }, + { nullptr, 0, nullptr, 0 } }; /* For optional arguments, optarg is only set when the argument is passed as "--option=[ARGUMENT]" but not as @@ -1228,9 +1250,9 @@ int parseArguments(int argc, char* argv[], Arguments& args) { case 'c': { const char delim[] = ","; char* cluster_file = strtok(optarg, delim); - while (cluster_file != NULL) { + while (cluster_file != nullptr) { strcpy(args.cluster_files[args.num_fdb_clusters++], cluster_file); - cluster_file = strtok(NULL, delim); + cluster_file = strtok(nullptr, delim); } break; } @@ -1276,8 +1298,7 @@ int parseArguments(int argc, char* argv[], Arguments& args) { args.mode = MODE_RUN; } else if (strcmp(optarg, "report") == 0) { args.mode = MODE_REPORT; - int i = optind; - for (; i < argc; i++) { + for (int i = optind; i < argc; i++) { if (argv[i][0] != '-') { const std::string report_file = argv[i]; strncpy(args.report_files[args.num_report_files], report_file.c_str(), report_file.size()); @@ -1461,6 +1482,12 @@ int parseArguments(int argc, char* argv[], Arguments& args) { case ARG_TRANSACTION_TIMEOUT_DB: args.transaction_timeout_db = atoi(optarg); break; + case ARG_MAX_GRV_QUEUE_DELAY: + args.max_grv_queue_delay_ms = atoi(optarg); + break; + case ARG_WARMUP_SECONDS: + args.warmup_seconds = atoi(optarg); + break; } } @@ -1555,12 +1582,36 @@ int Arguments::validate() { logr.error("--transaction_timeout_[tx|db] must be a non-negative integer"); return -1; } + if (max_grv_queue_delay_ms < -1 || max_grv_queue_delay_ms == 0) { + logr.error("--max_grv_queue_delay must be a positive integer"); + return -1; + } + if (warmup_seconds < 0) { + logr.error("--warmup_seconds must be a non-negative integer"); + return -1; + } + if (warmup_seconds > 0 && iteration > 0) { + logr.error("--warmup_seconds is only supported with --seconds"); + return -1; + } + if (seconds > 0 && warmup_seconds >= seconds) { + logr.error("--warmup_seconds must be smaller than --seconds"); + return -1; + } } if (mode != MODE_RUN && (transaction_timeout_db != 0 || transaction_timeout_tx != 0)) { logr.error("--transaction_timeout_[tx|db] only supported in run mode"); return -1; } + if (mode != MODE_RUN && max_grv_queue_delay_ms != -1) { + logr.error("--max_grv_queue_delay only supported in run mode"); + return -1; + } + if (mode != MODE_RUN && warmup_seconds != 0) { + logr.error("--warmup_seconds only supported in run mode"); + return -1; + } if (mode == MODE_RUN || mode == MODE_BUILD) { if (tpsmax > 0) { @@ -1654,7 +1705,37 @@ void printStats(Arguments const& args, WorkflowStatistics const* stats, double c prev = current; } -void printStatsHeader(Arguments const& args, bool show_commit, bool is_first_header_empty, bool show_op_stats) { +WorkflowStatistics aggregateWorkerStats(Arguments const& args, WorkflowStatistics const* worker_stats) { + auto aggregate = WorkflowStatistics{}; + const auto num_workers = args.async_xacts > 0 ? args.async_xacts : args.num_threads; + for (auto i = 0; i < args.num_processes * num_workers; i++) { + aggregate.combine(worker_stats[i]); + } + return aggregate; +} + +struct WarmupSnapshot { + WorkflowStatistics worker_stats; + double duration_sec; +}; + +std::optional maybeCaptureWarmupSnapshot(Arguments const& args, + WorkflowStatistics const* worker_stats, + double elapsed_sec) { + if (args.warmup_seconds == 0 || elapsed_sec < args.warmup_seconds) { + return std::nullopt; + } + return WarmupSnapshot{ aggregateWorkerStats(args, worker_stats), elapsed_sec }; +} + +FDB_BOOLEAN_PARAM(ShowCommit); +FDB_BOOLEAN_PARAM(IsFirstHeaderEmpty); +FDB_BOOLEAN_PARAM(ShowOpStats); + +void printStatsHeader(Arguments const& args, + ShowCommit show_commit, + IsFirstHeaderEmpty is_first_header_empty, + ShowOpStats show_op_stats) { /* header */ if (is_first_header_empty) putTitle(""); @@ -1709,7 +1790,7 @@ void printWorkerStats(WorkflowStatistics& final_stats, Arguments args, FILE* fp, } fmt::print("Latency (us)"); - printStatsHeader(args, true, false, true); + printStatsHeader(args, ShowCommit::True, IsFirstHeaderEmpty::False, ShowOpStats::True); /* Total Samples */ putTitle("Samples"); @@ -1944,16 +2025,18 @@ void printReport(Arguments const& args, WorkflowStatistics const* worker_stats, ThreadStatistics const* thread_stats, ProcessStatistics const* process_stats, - double const duration_sec, + double const run_duration_sec, + std::optional const& warmup_snapshot, pid_t pid_main, FILE* fp) { - auto final_worker_stats = WorkflowStatistics{}; - const auto num_workers = args.async_xacts > 0 ? args.async_xacts : args.num_threads; - - for (auto i = 0; i < args.num_processes * num_workers; i++) { - final_worker_stats.combine(worker_stats[i]); + auto final_worker_stats = aggregateWorkerStats(args, worker_stats); + auto measured_worker_stats = final_worker_stats; + if (warmup_snapshot.has_value()) { + measured_worker_stats.subtractCounters(warmup_snapshot->worker_stats); } + const auto warmup_duration_sec = warmup_snapshot.has_value() ? warmup_snapshot->duration_sec : 0.0; + const auto measurement_duration_sec = std::max(run_duration_sec - warmup_duration_sec, 1e-9); double cpu_time_worker_threads = std::accumulate(thread_stats, @@ -2002,7 +2085,9 @@ void printReport(Arguments const& args, (total_duration_local_fdb_networks); // assume that external networks have same total duration as local networks /* overall stats */ - fmt::printf("\n====== Total Duration %6.3f sec ======\n\n", duration_sec); + fmt::printf("\n====== Measured Duration %6.3f sec ======\n\n", measurement_duration_sec); + fmt::printf("Run Duration: %8.3f\n", run_duration_sec); + fmt::printf("Warmup Duration: %8.3f\n", warmup_duration_sec); fmt::printf("Total Processes: %8d\n", args.num_processes); fmt::printf("Total Threads: %8d\n", args.num_threads); fmt::printf("Total Async Xacts: %8d\n", args.async_xacts); @@ -2025,30 +2110,36 @@ void printReport(Arguments const& args, break; } } - const auto tps_f = final_worker_stats.getOpCount(OP_TRANSACTION) / duration_sec; + const auto tps_f = measured_worker_stats.getOpCount(OP_TRANSACTION) / measurement_duration_sec; const auto tps_i = static_cast(tps_f); - fmt::printf("Total Xacts: %8lu\n", final_worker_stats.getOpCount(OP_TRANSACTION)); - fmt::printf("Total Conflicts: %8lu\n", final_worker_stats.getConflictCount()); - fmt::printf("Total Errors: %8lu\n", final_worker_stats.getTotalErrorCount()); - fmt::printf("Total Timeouts: %8lu\n", final_worker_stats.getTotalTimeoutCount()); + fmt::printf("Total Xacts: %8lu\n", measured_worker_stats.getOpCount(OP_TRANSACTION)); + fmt::printf("Total Conflicts: %8lu\n", measured_worker_stats.getConflictCount()); + fmt::printf("Total Errors: %8lu\n", measured_worker_stats.getTotalErrorCount()); + fmt::printf("Total Timeouts: %8lu\n", measured_worker_stats.getTotalTimeoutCount()); fmt::printf("Overall TPS: %8lu\n\n", tps_i); fmt::printf("%%CPU Worker Processes: %6.2f \n", cpu_util_worker_processes); fmt::printf("%%CPU Worker Threads: %6.2f \n", cpu_util_worker_threads); fmt::printf("%%CPU Local Network Threads: %6.2f \n", cpu_util_local_fdb_networks); fmt::printf("%%CPU External Network Threads: %6.2f \n\n", cpu_util_external_fdb_networks); + if (warmup_duration_sec > 0) { + fmt::printf( + "Note: aggregate throughput and count-based totals exclude warmup; latency stats still include it.\n\n"); + } if (fp) { fmt::fprintf(fp, "\"results\": {"); - fmt::fprintf(fp, "\"totalDuration\": %6.3f,", duration_sec); + fmt::fprintf(fp, "\"runDuration\": %6.3f,", run_duration_sec); + fmt::fprintf(fp, "\"warmupDuration\": %6.3f,", warmup_duration_sec); + fmt::fprintf(fp, "\"totalDuration\": %6.3f,", measurement_duration_sec); fmt::fprintf(fp, "\"totalProcesses\": %d,", args.num_processes); fmt::fprintf(fp, "\"totalThreads\": %d,", args.num_threads); fmt::fprintf(fp, "\"totalAsyncXacts\": %d,", args.async_xacts); fmt::fprintf(fp, "\"targetTPS\": %d,", args.tpsmax); - fmt::fprintf(fp, "\"totalXacts\": %lu,", final_worker_stats.getOpCount(OP_TRANSACTION)); - fmt::fprintf(fp, "\"totalConflicts\": %lu,", final_worker_stats.getConflictCount()); - fmt::fprintf(fp, "\"totalErrors\": %lu,", final_worker_stats.getTotalErrorCount()); - fmt::fprintf(fp, "\"totalTimeouts\": %lu,", final_worker_stats.getTotalTimeoutCount()); + fmt::fprintf(fp, "\"totalXacts\": %lu,", measured_worker_stats.getOpCount(OP_TRANSACTION)); + fmt::fprintf(fp, "\"totalConflicts\": %lu,", measured_worker_stats.getConflictCount()); + fmt::fprintf(fp, "\"totalErrors\": %lu,", measured_worker_stats.getTotalErrorCount()); + fmt::fprintf(fp, "\"totalTimeouts\": %lu,", measured_worker_stats.getTotalTimeoutCount()); fmt::fprintf(fp, "\"overallTPS\": %lu,", tps_i); fmt::fprintf(fp, "\"workerProcesseCPU\": %.8f,", cpu_util_worker_processes); fmt::fprintf(fp, "\"workerThreadCPU\": %.8f,", cpu_util_worker_threads); @@ -2057,7 +2148,7 @@ void printReport(Arguments const& args, } /* per-op stats */ - printStatsHeader(args, true, true, false); + printStatsHeader(args, ShowCommit::True, IsFirstHeaderEmpty::True, ShowOpStats::False); /* OPS */ putTitle("Total OPS"); @@ -2067,24 +2158,24 @@ void printReport(Arguments const& args, auto first_op = true; for (auto op = 0; op < MAX_OP; op++) { if ((args.txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) || op == OP_COMMIT) { - putField(final_worker_stats.getOpCount(op)); + putField(measured_worker_stats.getOpCount(op)); if (fp) { if (first_op) { first_op = false; } else { fmt::fprintf(fp, ","); } - fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), final_worker_stats.getOpCount(op)); + fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), measured_worker_stats.getOpCount(op)); } } } /* TPS */ - const auto tps = final_worker_stats.getOpCount(OP_TRANSACTION) / duration_sec; + const auto tps = measured_worker_stats.getOpCount(OP_TRANSACTION) / measurement_duration_sec; putFieldFloat(tps, 2); /* Conflicts */ - const auto conflicts_rate = final_worker_stats.getConflictCount() / duration_sec; + const auto conflicts_rate = measured_worker_stats.getConflictCount() / measurement_duration_sec; putFieldFloat(conflicts_rate, 2); fmt::print("\n"); @@ -2097,14 +2188,14 @@ void printReport(Arguments const& args, first_op = true; for (auto op = 0; op < MAX_OP; op++) { if ((args.txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) || op == OP_COMMIT) { - putField(final_worker_stats.getErrorCount(op)); + putField(measured_worker_stats.getErrorCount(op)); if (fp) { if (first_op) { first_op = false; } else { fmt::fprintf(fp, ","); } - fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), final_worker_stats.getErrorCount(op)); + fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), measured_worker_stats.getErrorCount(op)); } } } @@ -2118,14 +2209,14 @@ void printReport(Arguments const& args, first_op = true; for (auto op = 0; op < MAX_OP; op++) { if ((args.txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) || op == OP_COMMIT) { - putField(final_worker_stats.getTimeoutCount(op)); + putField(measured_worker_stats.getTimeoutCount(op)); if (fp) { if (first_op) { first_op = false; } else { fmt::fprintf(fp, ","); } - fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), final_worker_stats.getTimeoutCount(op)); + fmt::fprintf(fp, "\"%s\": %lu", getOpName(op), measured_worker_stats.getTimeoutCount(op)); } } } @@ -2176,6 +2267,7 @@ int statsProcessMain(Arguments const& args, std::atomic const& stopcount, pid_t pid_main) { bool first_stats = true; + auto warmup_snapshot = std::optional{}; /* wait until the signal turn on */ while (signal.load() == SIGNAL_OFF) { @@ -2183,9 +2275,9 @@ int statsProcessMain(Arguments const& args, } if (args.verbose >= VERBOSE_DEFAULT) - printStatsHeader(args, false, true, false); + printStatsHeader(args, ShowCommit::False, IsFirstHeaderEmpty::True, ShowOpStats::False); - FILE* fp = NULL; + FILE* fp = nullptr; if (args.json_output_path[0] != '\0') { fp = fopen(args.json_output_path, "w"); fmt::fprintf(fp, "{\"makoArgs\": {"); @@ -2198,6 +2290,7 @@ int statsProcessMain(Arguments const& args, fmt::fprintf(fp, "\"rows\": %d,", args.rows); fmt::fprintf(fp, "\"load_factor\": %lf,", args.load_factor); fmt::fprintf(fp, "\"seconds\": %d,", args.seconds); + fmt::fprintf(fp, "\"warmup_seconds\": %d,", args.warmup_seconds); fmt::fprintf(fp, "\"iteration\": %d,", args.iteration); fmt::fprintf(fp, "\"tpsmax\": %d,", args.tpsmax); fmt::fprintf(fp, "\"tpsmin\": %d,", args.tpsmin); @@ -2223,6 +2316,7 @@ int statsProcessMain(Arguments const& args, fmt::fprintf(fp, "\"disable_ryw\": %d,", args.disable_ryw); fmt::fprintf(fp, "\"transaction_timeout_db\": %d,", args.transaction_timeout_db); fmt::fprintf(fp, "\"transaction_timeout_tx\": %d,", args.transaction_timeout_tx); + fmt::fprintf(fp, "\"max_grv_queue_delay_ms\": %d,", args.max_grv_queue_delay_ms); fmt::fprintf(fp, "\"json_output_path\": \"%s\"", args.json_output_path); fmt::fprintf(fp, "},\"samples\": ["); } @@ -2235,6 +2329,10 @@ int statsProcessMain(Arguments const& args, /* print stats every (roughly) 1 sec */ if (toDoubleSeconds(time_now - time_prev) >= 1.0) { + if (!warmup_snapshot.has_value()) { + warmup_snapshot = + maybeCaptureWarmupSnapshot(args, worker_stats, toDoubleSeconds(time_now - time_start)); + } /* adjust throttle rate if needed */ if (args.tpsmax != args.tpsmin) { @@ -2290,11 +2388,14 @@ int statsProcessMain(Arguments const& args, /* print report */ if (args.verbose >= VERBOSE_DEFAULT) { auto time_now = steady_clock::now(); + const auto run_duration_sec = toDoubleSeconds(time_now - time_start); + if (!warmup_snapshot.has_value()) { + warmup_snapshot = maybeCaptureWarmupSnapshot(args, worker_stats, run_duration_sec); + } while (stopcount.load() < args.num_threads * args.num_processes) { usleep(10000); /* 10ms */ } - printReport( - args, worker_stats, thread_stats, process_stats, toDoubleSeconds(time_now - time_start), pid_main, fp); + printReport(args, worker_stats, thread_stats, process_stats, run_duration_sec, warmup_snapshot, pid_main, fp); } if (fp) { @@ -2360,7 +2461,7 @@ int main(int argc, char* argv[]) { if (args.mode == MODE_REPORT) { WorkflowStatistics stats = mergeSketchReport(args); - printWorkerStats(stats, args, NULL, true); + printWorkerStats(stats, args, nullptr, true); return 0; } @@ -2391,7 +2492,7 @@ int main(int argc, char* argv[]) { } /* map it */ - shm = mmap(NULL, shmsize, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); + shm = mmap(nullptr, shmsize, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); if (shm == MAP_FAILED) { logr.error("mmap (fd:{} size:{}) failed", shmfd, shmsize); return -1; diff --git a/bindings/c/test/mako/mako.hpp b/bindings/c/test/mako/mako.hpp index f02e7b6adf3..e4a7bf8708f 100644 --- a/bindings/c/test/mako/mako.hpp +++ b/bindings/c/test/mako/mako.hpp @@ -88,6 +88,8 @@ enum ArgKind { ARG_ENABLE_TOKEN_BASED_AUTHORIZATION, ARG_TRANSACTION_TIMEOUT_TX, ARG_TRANSACTION_TIMEOUT_DB, + ARG_WARMUP_SECONDS, + ARG_MAX_GRV_QUEUE_DELAY, }; constexpr const int OP_COUNT = 0; @@ -101,6 +103,7 @@ enum OpKind { OP_GETRANGE, OP_SGET, OP_SGETRANGE, + OP_STATUSJSON, OP_UPDATE, OP_INSERT, OP_INSERTRANGE, @@ -159,6 +162,7 @@ struct Arguments { double load_factor; int row_digits; int seconds; + int warmup_seconds; int iteration; int tpsmax; int tpsmin; @@ -203,13 +207,17 @@ struct Arguments { std::optional private_key_pem; int transaction_timeout_db; int transaction_timeout_tx; + int max_grv_queue_delay_ms; }; // helper functions -inline void setTransactionTimeoutIfEnabled(const Arguments& args, fdb::Transaction& tx) { +inline void setTransactionOptionsIfEnabled(const Arguments& args, fdb::Transaction& tx) { if (args.transaction_timeout_tx > 0) { tx.setOption(FDB_TR_OPTION_TIMEOUT, args.transaction_timeout_tx); } + if (args.max_grv_queue_delay_ms > 0) { + tx.setOption(FDB_TR_OPTION_MAX_GRV_QUEUE_DELAY, args.max_grv_queue_delay_ms); + } } } // namespace mako diff --git a/bindings/c/test/mako/mako.rst b/bindings/c/test/mako/mako.rst index c978ec1b85d..99781bfb274 100644 --- a/bindings/c/test/mako/mako.rst +++ b/bindings/c/test/mako/mako.rst @@ -68,6 +68,11 @@ Arguments | Test duration in seconds (Default: 30) | This option cannot be set with ``--iteration``. +- | ``--warmup_seconds `` + | Ignore the initial ```` of a run when computing aggregate throughput and count-based totals. + | Raw per-second samples are still emitted for the full run. + | This option is only supported with ``--seconds`` and must be smaller than ``--seconds``. + - | ``-i | --iteration `` | Specify the number of operations to be executed. | This option cannot be set with ``--seconds``. @@ -143,6 +148,9 @@ Arguments - | ``--transaction_timeout_db `` | Duration in milliseconds after which a transaction times out in run mode. Set as database option. +- | ``--max_grv_queue_delay `` + | Maximum estimated GRV proxy queue delay in milliseconds. Set as transaction option in run mode. + Transaction Specification ========================= | A transaction may contain multiple operations of various types. @@ -156,6 +164,7 @@ Operation Types - ``gr`` – GET RANGE - ``sg`` – Snapshot GET - ``sgr`` – Snapshot GET RANGE +- ``sj`` – GET ``\xff\xff/status/json`` from the special key space - ``u`` – Update (= GET followed by SET) - ``i`` – Insert (= SET with a new key) - ``ir`` – Insert Range (Sequential) diff --git a/bindings/c/test/mako/operations.cpp b/bindings/c/test/mako/operations.cpp index 7f7cf6a6b79..5c75fa8a599 100644 --- a/bindings/c/test/mako/operations.cpp +++ b/bindings/c/test/mako/operations.cpp @@ -111,6 +111,20 @@ const std::array opTable{ { } } }, 1, false }, + { "STATUSJSON", + { { StepKind::READ, + [](Transaction& tx, Arguments const&, ByteString&, ByteString&, ByteString&) { + static constexpr auto status_json_key = + std::string_view{ "\xff\xff/status/json", sizeof("\xff\xff/status/json") - 1 }; + return tx.get(toBytesRef(status_json_key), false /*snapshot*/).eraseType(); + }, + [](Future& f, Transaction&, Arguments const&, ByteString&, ByteString&, ByteString&) { + if (f && !f.error()) { + f.get(); + } + } } }, + 1, + false }, { "UPDATE", { { StepKind::READ, [](Transaction& tx, Arguments const& args, ByteString& key, ByteString&, ByteString&) { diff --git a/bindings/c/test/mako/stats.hpp b/bindings/c/test/mako/stats.hpp index f2498047737..6348da9515b 100644 --- a/bindings/c/test/mako/stats.hpp +++ b/bindings/c/test/mako/stats.hpp @@ -22,6 +22,7 @@ #define MAKO_STATS_HPP #include +#include #include #include #include @@ -174,6 +175,23 @@ class alignas(64) WorkflowStatistics { latency_us_total[op] += latency_us; } + void subtractCounters(const WorkflowStatistics& baseline) noexcept { + assert(conflicts >= baseline.conflicts); + assert(total_errors >= baseline.total_errors); + assert(total_timeouts >= baseline.total_timeouts); + conflicts -= baseline.conflicts; + total_errors -= baseline.total_errors; + total_timeouts -= baseline.total_timeouts; + for (auto op = 0; op < MAX_OP; op++) { + assert(ops[op] >= baseline.ops[op]); + assert(errors[op] >= baseline.errors[op]); + assert(timeouts[op] >= baseline.timeouts[op]); + ops[op] -= baseline.ops[op]; + errors[op] -= baseline.errors[op]; + timeouts[op] -= baseline.timeouts[op]; + } + } + void writeToFile(const std::string& filename, int op) const { rapidjson::StringBuffer ss; rapidjson::Writer writer(ss); diff --git a/bindings/c/test/performance_test.c b/bindings/c/test/performance_test.c index 736f74c75d6..ba4e78d0d0f 100644 --- a/bindings/c/test/performance_test.c +++ b/bindings/c/test/performance_test.c @@ -81,8 +81,7 @@ int runTest(struct RunResult (*testFxn)(struct ResultSet*, FDBTransaction*), const char* kpiName) { int numRuns = 25; int* results = malloc(sizeof(int) * numRuns); - int i = 0; - for (; i < numRuns; ++i) { + for (int i = 0; i < numRuns; ++i) { struct RunResult res = run(rs, db, testFxn); if (res.e) { logError(res.e, kpiName, rs); @@ -110,8 +109,7 @@ int runTestDb(struct RunResult (*testFxn)(struct ResultSet*, FDBDatabase*), const char* kpiName) { int numRuns = 25; int* results = malloc(sizeof(int) * numRuns); - int i = 0; - for (; i < numRuns; ++i) { + for (int i = 0; i < numRuns; ++i) { struct RunResult res = testFxn(rs, db); if (res.e) { logError(res.e, kpiName, rs); @@ -465,8 +463,9 @@ struct RunResult getRange(struct ResultSet* rs, FDBTransaction* tr) { } if (totalOut != GET_RANGE_COUNT) { - char* msg = (char*)malloc((sizeof(char)) * 200); - sprintf(msg, "verifying out count (%d != %d)", totalOut, GET_RANGE_COUNT); + const size_t nbytes = 200; + char* msg = (char*)malloc(nbytes); + snprintf(msg, nbytes, "verifying out count (%d != %d)", totalOut, GET_RANGE_COUNT); logError(4100, msg, rs); free(msg); fdb_future_destroy(f); diff --git a/bindings/c/test/ryw_benchmark.c b/bindings/c/test/ryw_benchmark.c index f729f8949ff..2a20bcd46a8 100644 --- a/bindings/c/test/ryw_benchmark.c +++ b/bindings/c/test/ryw_benchmark.c @@ -50,8 +50,7 @@ int runTest(int (*testFxn)(FDBTransaction*, struct ResultSet*), const char* kpiName) { int numRuns = 25; int* results = malloc(sizeof(int) * numRuns); - int i = 0; - for (; i < numRuns; ++i) { + for (int i = 0; i < numRuns; ++i) { results[i] = testFxn(tr, rs); if (results[i] < 0) { free(results); @@ -231,24 +230,24 @@ int interleavedSetsGets(FDBTransaction* tr, struct ResultSet* rs) { int length; int i; - uint8_t* k = (uint8_t*)"foo"; - uint8_t v[10]; + char* k = "foo"; + char v[12]; int num = 1; double start = getTime(); - sprintf((char*)v, "%d", num); - fdb_transaction_set(tr, k, 3, v, strlen((char*)v)); + snprintf(v, sizeof(v), "%d", num); + fdb_transaction_set(tr, (uint8_t*)k, 3, (uint8_t*)v, strlen(v)); for (i = 0; i < 10000; ++i) { - FDBFuture* f = fdb_transaction_get(tr, k, 3, 0); + FDBFuture* f = fdb_transaction_get(tr, (uint8_t*)k, 3, 0); if (getError(fdb_future_block_until_ready(f), "InterleavedSetsGets (block for get)", rs)) return -1; if (getError(fdb_future_get_value(f, &present, &value, &length), "InterleavedSetsGets (get result)", rs)) return -1; fdb_future_destroy(f); - sprintf((char*)v, "%d", ++num); - fdb_transaction_set(tr, k, 3, v, strlen((char*)v)); + snprintf(v, sizeof(v), "%d", ++num); + fdb_transaction_set(tr, (uint8_t*)k, 3, (uint8_t*)v, strlen(v)); } double end = getTime(); diff --git a/bindings/c/test/test.h b/bindings/c/test/test.h index afb6e5010ac..3f314b885c5 100644 --- a/bindings/c/test/test.h +++ b/bindings/c/test/test.h @@ -42,8 +42,9 @@ double getTime() { } void writeKey(uint8_t** dest, int key, int keySize) { - *dest = (uint8_t*)malloc((sizeof(uint8_t)) * keySize); - sprintf((char*)*dest, "%0*d", keySize, key); + size_t bufsize = keySize + 1; + *dest = (uint8_t*)malloc(bufsize); + snprintf((char*)*dest, bufsize, "%0*d", keySize, key); } uint8_t** generateKeys(int numKeys, int keySize) { @@ -130,7 +131,7 @@ void addError(struct ResultSet* rs, const char* message) { void writeResultSet(struct ResultSet* rs) { uint64_t id = ((uint64_t)rand() << 32) + rand(); char name[100]; - sprintf(name, "fdb-c_result-%" SCNu64 ".json", id); + snprintf(name, sizeof(name), "fdb-c_result-%" SCNu64 ".json", id); FILE* fp = fopen(name, "w"); if (!fp) { fprintf(stderr, "Could not open results file %s\n", name); @@ -190,8 +191,9 @@ void freeResultSet(struct ResultSet* rs) { fdb_error_t getError(fdb_error_t err, const char* context, struct ResultSet* rs) { if (err) { - char* msg = (char*)malloc(strlen(context) + 100); - sprintf(msg, "Error in %s: %s", context, fdb_get_error(err)); + size_t nbytes = strlen(context) + 100; + char* msg = (char*)malloc(nbytes); + snprintf(msg, nbytes, "Error in %s: %s", context, fdb_get_error(err)); fprintf(stderr, "%s\n", msg); if (rs != NULL) { addError(rs, msg); @@ -214,8 +216,9 @@ void checkError(fdb_error_t err, const char* context, struct ResultSet* rs) { } fdb_error_t logError(fdb_error_t err, const char* context, struct ResultSet* rs) { - char* msg = (char*)malloc(strlen(context) + 100); - sprintf(msg, "Error in %s: %s", context, fdb_get_error(err)); + size_t nbytes = strlen(context) + 100; + char* msg = (char*)malloc(nbytes); + snprintf(msg, nbytes, "Error in %s: %s", context, fdb_get_error(err)); fprintf(stderr, "%s\n", msg); if (rs != NULL) { addError(rs, msg); diff --git a/bindings/c/test/workloads/workloads.cpp b/bindings/c/test/workloads/workloads.cpp index 5f53d87039d..5e49326fafc 100644 --- a/bindings/c/test/workloads/workloads.cpp +++ b/bindings/c/test/workloads/workloads.cpp @@ -20,7 +20,7 @@ #include "workloads.h" -FDBWorkloadFactoryImpl::~FDBWorkloadFactoryImpl() {} +FDBWorkloadFactoryImpl::~FDBWorkloadFactoryImpl() = default; std::map& FDBWorkloadFactoryImpl::factories() { static std::map _factories; diff --git a/bindings/flow/CMakeLists.txt b/bindings/flow/CMakeLists.txt index 5e5a31c6bd2..1a7bb07ce54 100644 --- a/bindings/flow/CMakeLists.txt +++ b/bindings/flow/CMakeLists.txt @@ -1,19 +1,19 @@ set(SRCS - DirectoryLayer.actor.cpp + DirectoryLayer.cpp DirectoryLayer.h DirectoryPartition.h DirectorySubspace.cpp DirectorySubspace.h FDBLoanerTypes.h - HighContentionAllocator.actor.cpp + HighContentionAllocator.cpp HighContentionAllocator.h IDirectory.h - Node.actor.cpp + Node.cpp Subspace.cpp Subspace.h Tuple.cpp Tuple.h - fdb_flow.actor.cpp + fdb_flow.cpp fdb_flow.h) add_flow_target(STATIC_LIBRARY NAME fdb_flow SRCS ${SRCS}) @@ -21,10 +21,8 @@ target_link_libraries(fdb_flow PUBLIC fdb_c) target_link_libraries(fdb_flow PUBLIC fdbclient) target_include_directories(fdb_flow PUBLIC "${CMAKE_SOURCE_DIR}" - "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/tester" - "${CMAKE_CURRENT_BINARY_DIR}/tester" ) add_subdirectory(tester) diff --git a/bindings/flow/DirectoryLayer.actor.cpp b/bindings/flow/DirectoryLayer.cpp similarity index 56% rename from bindings/flow/DirectoryLayer.actor.cpp rename to bindings/flow/DirectoryLayer.cpp index c9b8ef3d488..86e5378b8f6 100644 --- a/bindings/flow/DirectoryLayer.actor.cpp +++ b/bindings/flow/DirectoryLayer.cpp @@ -1,5 +1,5 @@ /* - * DirectoryLayer.actor.cpp + * DirectoryLayer.cpp * * This source file is part of the FoundationDB open source project * @@ -52,34 +52,31 @@ Optional DirectoryLayer::nodeWithPrefix(Optional const& prefix) con return nodeWithPrefix(prefix.get()); } -ACTOR Future find(Reference dirLayer, - Reference tr, - IDirectory::Path path) { - state int pathIndex = 0; - state DirectoryLayer::Node node = DirectoryLayer::Node(dirLayer, dirLayer->rootNode, IDirectory::Path(), path); +Future find(Reference dirLayer, + Reference tr, + IDirectory::Path path) { + DirectoryLayer::Node node(dirLayer, dirLayer->rootNode, IDirectory::Path(), path); - for (; pathIndex != path.size(); ++pathIndex) { + for (int pathIndex = 0; pathIndex != path.size(); ++pathIndex) { ASSERT(node.subspace.present()); Optional> val = - wait(tr->get(node.subspace.get().get(DirectoryLayer::SUB_DIR_KEY).get(path[pathIndex], true).key())); + co_await tr->get(node.subspace.get().get(DirectoryLayer::SUB_DIR_KEY).get(path[pathIndex], true).key()); node.path.push_back(path[pathIndex]); node = DirectoryLayer::Node(dirLayer, dirLayer->nodeWithPrefix(val), node.path, path); - DirectoryLayer::Node _node = wait(node.loadMetadata(tr)); - node = _node; + node = co_await node.loadMetadata(tr); if (!node.exists() || node.layer == DirectoryLayer::PARTITION_LAYER) { - return node; + co_return node; } } if (!node.loadedMetadata) { - DirectoryLayer::Node _node = wait(node.loadMetadata(tr)); - node = _node; + node = co_await node.loadMetadata(tr); } - return node; + co_return node; } IDirectory::Path DirectoryLayer::toAbsolutePath(IDirectory::Path const& subpath) const { @@ -98,11 +95,10 @@ Reference DirectoryLayer::contentsOfNode(Subspace const& node Standalone prefix = nodeSubspace.unpack(node.key()).getString(0); if (layer == PARTITION_LAYER) { - return Reference( - new DirectoryPartition(toAbsolutePath(path), prefix, Reference::addRef(this))); + return makeReference(toAbsolutePath(path), prefix, Reference::addRef(this)); } else { - return Reference( - new DirectorySubspace(toAbsolutePath(path), prefix, Reference::addRef(this), layer)); + return makeReference( + toAbsolutePath(path), prefix, Reference::addRef(this), layer); } } @@ -112,7 +108,7 @@ Reference DirectoryLayer::openInternal(Standalone if (!allowOpen) { throw directory_already_exists(); } - if (layer.size() > 0 && layer != existingNode.layer) { + if (!layer.empty() && layer != existingNode.layer) { throw mismatched_layer(); } @@ -129,15 +125,15 @@ void DirectoryLayer::initializeDirectory(Reference const& tr) const tr->set(rootNode.pack(VERSION_KEY), StringRef((uint8_t*)VERSION, 12)); } -ACTOR Future checkVersionInternal(const DirectoryLayer* dirLayer, Reference tr, bool writeAccess) { +Future checkVersionInternal(const DirectoryLayer* dirLayer, Reference tr, bool writeAccess) { Optional> versionBytes = - wait(tr->get(dirLayer->rootNode.pack(DirectoryLayer::VERSION_KEY))); + co_await tr->get(dirLayer->rootNode.pack(DirectoryLayer::VERSION_KEY)); if (!versionBytes.present()) { if (writeAccess) { dirLayer->initializeDirectory(tr); } - return Void(); + co_return; } else { if (versionBytes.get().size() != 12) { throw invalid_directory_layer_metadata(); @@ -148,151 +144,147 @@ ACTOR Future checkVersionInternal(const DirectoryLayer* dirLayer, Referenc throw incompatible_directory_version(); } } - - return Void(); } Future DirectoryLayer::checkVersion(Reference const& tr, bool writeAccess) const { return checkVersionInternal(this, tr, writeAccess); } -ACTOR Future> getPrefix(Reference dirLayer, - Reference tr, - Optional> prefix) { +Future> getPrefix(Reference dirLayer, + Reference tr, + Optional> prefix) { if (!prefix.present()) { - Standalone allocated = wait(dirLayer->allocator.allocate(tr)); - state Standalone finalPrefix = allocated.withPrefix(dirLayer->contentSubspace.key()); + Standalone allocated = co_await dirLayer->allocator.allocate(tr); + Standalone finalPrefix = allocated.withPrefix(dirLayer->contentSubspace.key()); - FDBStandalone result = wait(tr->getRange(KeyRangeRef(finalPrefix, strinc(finalPrefix)), 1)); + FDBStandalone result = co_await tr->getRange(KeyRangeRef(finalPrefix, strinc(finalPrefix)), 1); - if (result.size() > 0) { + if (!result.empty()) { throw directory_prefix_not_empty(); } - return finalPrefix; + co_return finalPrefix; } - return prefix.get(); + co_return prefix.get(); } -ACTOR Future> nodeContainingKey(Reference dirLayer, - Reference tr, - Standalone key, - bool snapshot) { +Future> nodeContainingKey(Reference dirLayer, + Reference tr, + Standalone key, + bool snapshot) { if (key.startsWith(dirLayer->nodeSubspace.key())) { - return dirLayer->rootNode; + co_return dirLayer->rootNode; } KeyRange range = KeyRangeRef(dirLayer->nodeSubspace.range().begin, keyAfter(dirLayer->nodeSubspace.pack(key))); - FDBStandalone result = wait(tr->getRange(range, 1, snapshot, true)); + FDBStandalone result = co_await tr->getRange(range, 1, snapshot, true); - if (result.size() > 0) { + if (!result.empty()) { Standalone prevPrefix = dirLayer->nodeSubspace.unpack(result[0].key).getString(0); if (key.startsWith(prevPrefix)) { - return dirLayer->nodeWithPrefix(prevPrefix); + co_return dirLayer->nodeWithPrefix(prevPrefix); } } - return Optional(); + co_return Optional(); } -ACTOR Future isPrefixFree(Reference dirLayer, - Reference tr, - Standalone prefix, - bool snapshot) { - if (!prefix.size()) { - return false; +Future isPrefixFree(Reference dirLayer, + Reference tr, + Standalone prefix, + bool snapshot) { + if (prefix.empty()) { + co_return false; } - Optional node = wait(nodeContainingKey(dirLayer, tr, prefix, snapshot)); + Optional node = co_await nodeContainingKey(dirLayer, tr, prefix, snapshot); if (node.present()) { - return false; + co_return false; } - FDBStandalone result = wait(tr->getRange( - KeyRangeRef(dirLayer->nodeSubspace.pack(prefix), dirLayer->nodeSubspace.pack(strinc(prefix))), 1, snapshot)); - return !result.size(); + FDBStandalone result = co_await tr->getRange( + KeyRangeRef(dirLayer->nodeSubspace.pack(prefix), dirLayer->nodeSubspace.pack(strinc(prefix))), 1, snapshot); + co_return result.empty(); } -ACTOR Future getParentNode(Reference dirLayer, - Reference tr, - IDirectory::Path path) { +Future getParentNode(Reference dirLayer, Reference tr, IDirectory::Path path) { if (path.size() > 1) { Reference parent = - wait(dirLayer->createOrOpenInternal(tr, - IDirectory::Path(path.begin(), path.end() - 1), - StringRef(), - Optional>(), - true, - true)); - return dirLayer->nodeWithPrefix(parent->key()); + co_await dirLayer->createOrOpenInternal(tr, + IDirectory::Path(path.begin(), path.end() - 1), + StringRef(), + Optional>(), + true, + true); + co_return dirLayer->nodeWithPrefix(parent->key()); } else { - return dirLayer->rootNode; + co_return dirLayer->rootNode; } } -ACTOR Future> createInternal(Reference dirLayer, - Reference tr, - IDirectory::Path path, - Standalone layer, - Optional> prefix, - bool allowCreate) { +Future> createInternal(Reference dirLayer, + Reference tr, + IDirectory::Path path, + Standalone layer, + Optional> prefix, + bool allowCreate) { if (!allowCreate) { throw directory_does_not_exist(); } - wait(dirLayer->checkVersion(tr, true)); + co_await dirLayer->checkVersion(tr, true); - state Standalone newPrefix = wait(getPrefix(dirLayer, tr, prefix)); - bool isFree = wait(isPrefixFree(dirLayer, tr, newPrefix, !prefix.present())); + Standalone newPrefix = co_await getPrefix(dirLayer, tr, prefix); + bool isFree = co_await isPrefixFree(dirLayer, tr, newPrefix, !prefix.present()); if (!isFree) { throw directory_prefix_in_use(); } - Subspace parentNode = wait(getParentNode(dirLayer, tr, path)); + Subspace parentNode = co_await getParentNode(dirLayer, tr, path); Subspace node = dirLayer->nodeWithPrefix(newPrefix); tr->set(parentNode.get(DirectoryLayer::SUB_DIR_KEY).get(path.back(), true).key(), newPrefix); tr->set(node.get(DirectoryLayer::LAYER_KEY).key(), layer); - return dirLayer->contentsOfNode(node, path, layer); + co_return dirLayer->contentsOfNode(node, path, layer); } -ACTOR Future> _createOrOpenInternal(Reference dirLayer, - Reference tr, - IDirectory::Path path, - Standalone layer, - Optional> prefix, - bool allowCreate, - bool allowOpen) { +Future> _createOrOpenInternal(Reference dirLayer, + Reference tr, + IDirectory::Path path, + Standalone layer, + Optional> prefix, + bool allowCreate, + bool allowOpen) { ASSERT(!prefix.present() || allowCreate); - wait(dirLayer->checkVersion(tr, false)); + co_await dirLayer->checkVersion(tr, false); if (prefix.present() && !dirLayer->allowManualPrefixes) { - if (!dirLayer->getPath().size()) { + if (dirLayer->getPath().empty()) { throw manual_prefixes_not_enabled(); } else { throw prefix_in_partition(); } } - if (!path.size()) { + if (path.empty()) { throw cannot_open_root_directory(); } - state DirectoryLayer::Node existingNode = wait(find(dirLayer, tr, path)); + DirectoryLayer::Node existingNode = co_await find(dirLayer, tr, path); if (existingNode.exists()) { if (existingNode.isInPartition()) { IDirectory::Path subpath = existingNode.getPartitionSubpath(); Reference dirSpace = - wait(existingNode.getContents()->getDirectoryLayer()->createOrOpenInternal( - tr, subpath, layer, prefix, allowCreate, allowOpen)); - return dirSpace; + co_await existingNode.getContents()->getDirectoryLayer()->createOrOpenInternal( + tr, subpath, layer, prefix, allowCreate, allowOpen); + co_return dirSpace; } - return dirLayer->openInternal(layer, existingNode, allowOpen); + co_return dirLayer->openInternal(layer, existingNode, allowOpen); } else { - Reference dirSpace = wait(createInternal(dirLayer, tr, path, layer, prefix, allowCreate)); - return dirSpace; + Reference dirSpace = co_await createInternal(dirLayer, tr, path, layer, prefix, allowCreate); + co_return dirSpace; } } @@ -319,35 +311,35 @@ Future> DirectoryLayer::createOrOpen(Reference>(), true, true); } -ACTOR Future>> listInternal(Reference dirLayer, - Reference tr, - IDirectory::Path path) { - wait(dirLayer->checkVersion(tr, false)); +Future>> listInternal(Reference dirLayer, + Reference tr, + IDirectory::Path path) { + co_await dirLayer->checkVersion(tr, false); - state DirectoryLayer::Node node = wait(find(dirLayer, tr, path)); + DirectoryLayer::Node node = co_await find(dirLayer, tr, path); if (!node.exists()) { throw directory_does_not_exist(); } if (node.isInPartition(true)) { Standalone> partitionList = - wait(node.getContents()->getDirectoryLayer()->list(tr, node.getPartitionSubpath())); - return partitionList; + co_await node.getContents()->getDirectoryLayer()->list(tr, node.getPartitionSubpath()); + co_return partitionList; } - state Subspace subdir = node.subspace.get().get(DirectoryLayer::SUB_DIR_KEY); - state Key begin = subdir.range().begin; - state Standalone> subdirectories; + Subspace subdir = node.subspace.get().get(DirectoryLayer::SUB_DIR_KEY); + Key begin = subdir.range().begin; + Standalone> subdirectories; - loop { - FDBStandalone subdirRange = wait(tr->getRange(KeyRangeRef(begin, subdir.range().end))); + while (true) { + FDBStandalone subdirRange = co_await tr->getRange(KeyRangeRef(begin, subdir.range().end)); for (int i = 0; i < subdirRange.size(); ++i) { subdirectories.push_back_deep(subdirectories.arena(), subdir.unpack(subdirRange[i].key).getString(0)); } if (!subdirRange.more) { - return subdirectories; + co_return subdirectories; } begin = keyAfter(subdirRange.back().key); @@ -373,23 +365,19 @@ bool pathsEqual(IDirectory::Path const& path1, return true; } -ACTOR Future removeFromParent(Reference dirLayer, - Reference tr, - IDirectory::Path path) { - ASSERT(path.size() >= 1); - DirectoryLayer::Node parentNode = wait(find(dirLayer, tr, IDirectory::Path(path.begin(), path.end() - 1))); +Future removeFromParent(Reference dirLayer, Reference tr, IDirectory::Path path) { + ASSERT(!path.empty()); + DirectoryLayer::Node parentNode = co_await find(dirLayer, tr, IDirectory::Path(path.begin(), path.end() - 1)); if (parentNode.subspace.present()) { tr->clear(parentNode.subspace.get().get(DirectoryLayer::SUB_DIR_KEY).get(path.back(), true).key()); } - - return Void(); } -ACTOR Future> moveInternal(Reference dirLayer, - Reference tr, - IDirectory::Path oldPath, - IDirectory::Path newPath) { - wait(dirLayer->checkVersion(tr, true)); +Future> moveInternal(Reference dirLayer, + Reference tr, + IDirectory::Path oldPath, + IDirectory::Path newPath) { + co_await dirLayer->checkVersion(tr, true); if (oldPath.size() <= newPath.size()) { if (pathsEqual(oldPath, newPath, oldPath.size())) { @@ -401,10 +389,10 @@ ACTOR Future> moveInternal(Reference nodes = wait(getAll(futures)); + std::vector nodes = co_await getAll(futures); - state DirectoryLayer::Node oldNode = nodes[0]; - state DirectoryLayer::Node newNode = nodes[1]; + DirectoryLayer::Node oldNode = nodes[0]; + DirectoryLayer::Node newNode = nodes[1]; if (!oldNode.exists()) { throw directory_does_not_exist(); @@ -416,24 +404,24 @@ ACTOR Future> moveInternal(Reference partitionMove = - wait(newNode.getContents()->move(tr, oldNode.getPartitionSubpath(), newNode.getPartitionSubpath())); - return partitionMove; + co_await newNode.getContents()->move(tr, oldNode.getPartitionSubpath(), newNode.getPartitionSubpath()); + co_return partitionMove; } if (newNode.exists() || newPath.empty()) { throw directory_already_exists(); } - DirectoryLayer::Node parentNode = wait(find(dirLayer, tr, IDirectory::Path(newPath.begin(), newPath.end() - 1))); + DirectoryLayer::Node parentNode = co_await find(dirLayer, tr, IDirectory::Path(newPath.begin(), newPath.end() - 1)); if (!parentNode.exists()) { throw parent_directory_does_not_exist(); } tr->set(parentNode.subspace.get().get(DirectoryLayer::SUB_DIR_KEY).get(newPath.back(), true).key(), dirLayer->nodeSubspace.unpack(oldNode.subspace.get().key()).getString(0)); - wait(removeFromParent(dirLayer, tr, oldPath)); + co_await removeFromParent(dirLayer, tr, oldPath); - return dirLayer->contentsOfNode(oldNode.subspace.get(), newPath, oldNode.layer); + co_return dirLayer->contentsOfNode(oldNode.subspace.get(), newPath, oldNode.layer); } Future> DirectoryLayer::move(Reference const& tr, @@ -447,14 +435,13 @@ Future> DirectoryLayer::moveTo(Reference removeRecursive(Reference const&, Reference const&, Subspace const&); -ACTOR Future removeRecursive(Reference dirLayer, Reference tr, Subspace nodeSub) { - state Subspace subdir = nodeSub.get(DirectoryLayer::SUB_DIR_KEY); - state Key begin = subdir.range().begin; - state std::vector> futures; +Future removeRecursive(Reference dirLayer, Reference tr, Subspace nodeSub) { + Subspace subdir = nodeSub.get(DirectoryLayer::SUB_DIR_KEY); + Key begin = subdir.range().begin; + std::vector> futures; - loop { - FDBStandalone range = wait(tr->getRange(KeyRangeRef(begin, subdir.range().end))); + while (true) { + FDBStandalone range = co_await tr->getRange(KeyRangeRef(begin, subdir.range().end)); for (int i = 0; i < range.size(); ++i) { Subspace subNode = dirLayer->nodeWithPrefix(range[i].value); futures.push_back(removeRecursive(dirLayer, tr, subNode)); @@ -468,53 +455,47 @@ ACTOR Future removeRecursive(Reference dirLayer, Reference } // waits are done concurrently - wait(waitForAll(futures)); + co_await waitForAll(futures); Standalone nodePrefix = dirLayer->nodeSubspace.unpack(nodeSub.key()).getString(0); tr->clear(KeyRangeRef(nodePrefix, strinc(nodePrefix))); tr->clear(nodeSub.range()); - - return Void(); } -Future removeInternal(Reference const&, - Reference const&, - IDirectory::Path const&, - bool const&); -ACTOR Future removeInternal(Reference dirLayer, - Reference tr, - IDirectory::Path path, - bool failOnNonexistent) { - wait(dirLayer->checkVersion(tr, true)); +Future removeInternal(Reference dirLayer, + Reference tr, + IDirectory::Path path, + bool failOnNonexistent) { + co_await dirLayer->checkVersion(tr, true); if (path.empty()) { throw cannot_modify_root_directory(); } - state DirectoryLayer::Node node = wait(find(dirLayer, tr, path)); + DirectoryLayer::Node node = co_await find(dirLayer, tr, path); if (!node.exists()) { if (failOnNonexistent) { throw directory_does_not_exist(); } else { - return false; + co_return false; } } if (node.isInPartition()) { - bool recurse = wait( - removeInternal(node.getContents()->getDirectoryLayer(), tr, node.getPartitionSubpath(), failOnNonexistent)); - return recurse; + bool recurse = co_await removeInternal( + node.getContents()->getDirectoryLayer(), tr, node.getPartitionSubpath(), failOnNonexistent); + co_return recurse; } - state std::vector> futures; + std::vector> futures; futures.push_back(removeRecursive(dirLayer, tr, node.subspace.get())); futures.push_back(removeFromParent(dirLayer, tr, path)); - wait(waitForAll(futures)); + co_await waitForAll(futures); - return true; + co_return true; } Future DirectoryLayer::remove(Reference const& tr, Path const& path) { @@ -525,23 +506,21 @@ Future DirectoryLayer::removeIfExists(Reference const& tr, Pa return removeInternal(Reference::addRef(this), tr, path, false); } -ACTOR Future existsInternal(Reference dirLayer, - Reference tr, - IDirectory::Path path) { - wait(dirLayer->checkVersion(tr, false)); +Future existsInternal(Reference dirLayer, Reference tr, IDirectory::Path path) { + co_await dirLayer->checkVersion(tr, false); - DirectoryLayer::Node node = wait(find(dirLayer, tr, path)); + DirectoryLayer::Node node = co_await find(dirLayer, tr, path); if (!node.exists()) { - return false; + co_return false; } if (node.isInPartition()) { - bool exists = wait(node.getContents()->getDirectoryLayer()->exists(tr, node.getPartitionSubpath())); - return exists; + bool exists = co_await node.getContents()->getDirectoryLayer()->exists(tr, node.getPartitionSubpath()); + co_return exists; } - return true; + co_return true; } Future DirectoryLayer::exists(Reference const& tr, Path const& path) { diff --git a/bindings/flow/DirectoryPartition.h b/bindings/flow/DirectoryPartition.h index df4073290f4..f046ef754f3 100644 --- a/bindings/flow/DirectoryPartition.h +++ b/bindings/flow/DirectoryPartition.h @@ -32,12 +32,12 @@ class DirectoryPartition : public DirectorySubspace { public: DirectoryPartition(Path const& path, StringRef const& prefix, Reference parentDirectoryLayer) - : DirectorySubspace(path, - prefix, - Reference(new DirectoryLayer( - Subspace(DirectoryLayer::DEFAULT_NODE_SUBSPACE_PREFIX.withPrefix(prefix)), - Subspace(prefix))), - DirectoryLayer::PARTITION_LAYER), + : DirectorySubspace( + path, + prefix, + makeReference(Subspace(DirectoryLayer::DEFAULT_NODE_SUBSPACE_PREFIX.withPrefix(prefix)), + Subspace(prefix)), + DirectoryLayer::PARTITION_LAYER), parentDirectoryLayer(parentDirectoryLayer) { this->directoryLayer->path = path; } diff --git a/bindings/flow/HighContentionAllocator.actor.cpp b/bindings/flow/HighContentionAllocator.cpp similarity index 73% rename from bindings/flow/HighContentionAllocator.actor.cpp rename to bindings/flow/HighContentionAllocator.cpp index eebbada0ae2..92f63a48c65 100644 --- a/bindings/flow/HighContentionAllocator.actor.cpp +++ b/bindings/flow/HighContentionAllocator.cpp @@ -1,5 +1,5 @@ /* - * HighContentionAllocator.actor.cpp + * HighContentionAllocator.cpp * * This source file is part of the FoundationDB open source project * @@ -21,19 +21,19 @@ #include "HighContentionAllocator.h" namespace FDB { -ACTOR Future> _allocate(Reference tr, Subspace counters, Subspace recent) { - state int64_t start = 0; - state int64_t window = 0; +Future> _allocate(Reference tr, Subspace counters, Subspace recent) { + int64_t start = 0; + int64_t window = 0; - loop { - FDBStandalone range = wait(tr->getRange(counters.range(), 1, true, true)); + while (true) { + FDBStandalone range = co_await tr->getRange(counters.range(), 1, true, true); - if (range.size() > 0) { + if (!range.empty()) { start = counters.unpack(range[0].key).getInt(0); } - state bool windowAdvanced = false; - loop { + bool windowAdvanced = false; + while (true) { // if thread safety is needed, this should be locked { if (windowAdvanced) { tr->clear(KeyRangeRef(counters.key(), counters.get(start).key())); @@ -46,7 +46,7 @@ ACTOR Future> _allocate(Reference tr, Subspac Future>> countFuture = tr->get(counters.get(start).key(), true); // } - Optional> countValue = wait(countFuture); + Optional> countValue = co_await countFuture; int64_t count = 0; if (countValue.present()) { @@ -65,19 +65,19 @@ ACTOR Future> _allocate(Reference tr, Subspac windowAdvanced = true; } - loop { - state int64_t candidate = deterministicRandom()->randomInt(start, start + window); + while (true) { + int64_t candidate = deterministicRandom()->randomInt(start, start + window); // if thread safety is needed, this should be locked { - state Future> latestCounter = tr->getRange(counters.range(), 1, true, true); - state Future>> candidateValue = tr->get(recent.get(candidate).key()); + Future> latestCounter = tr->getRange(counters.range(), 1, true, true); + Future>> candidateValue = tr->get(recent.get(candidate).key()); tr->setOption(FDBTransactionOption::FDB_TR_OPTION_NEXT_WRITE_NO_WRITE_CONFLICT_RANGE); tr->set(recent.get(candidate).key(), ValueRef()); // } - wait(success(latestCounter) && success(candidateValue)); + co_await (success(latestCounter) && success(candidateValue)); int64_t currentWindowStart = 0; - if (latestCounter.get().size() > 0) { + if (!latestCounter.get().empty()) { currentWindowStart = counters.unpack(latestCounter.get()[0].key).getInt(0); } @@ -87,7 +87,7 @@ ACTOR Future> _allocate(Reference tr, Subspac if (!candidateValue.get().present()) { tr->addWriteConflictKey(recent.get(candidate).key()); - return Tuple().append(candidate).pack(); + co_return Tuple().append(candidate).pack(); } } } diff --git a/bindings/flow/Node.actor.cpp b/bindings/flow/Node.cpp similarity index 89% rename from bindings/flow/Node.actor.cpp rename to bindings/flow/Node.cpp index 0be50d25532..55e13bd51bc 100644 --- a/bindings/flow/Node.actor.cpp +++ b/bindings/flow/Node.cpp @@ -1,5 +1,5 @@ /* - * Node.actor.cpp + * Node.cpp * * This source file is part of the FoundationDB open source project * @@ -31,18 +31,18 @@ bool DirectoryLayer::Node::exists() const { return subspace.present(); } -ACTOR Future loadMetadata(DirectoryLayer::Node* n, Reference tr) { +Future loadMetadata(DirectoryLayer::Node* n, Reference tr) { if (!n->exists()) { n->loadedMetadata = true; - return *n; + co_return *n; } - Optional> layer = wait(tr->get(n->subspace.get().pack(DirectoryLayer::LAYER_KEY))); + Optional> layer = co_await tr->get(n->subspace.get().pack(DirectoryLayer::LAYER_KEY)); n->layer = layer.present() ? layer.get() : Standalone(); n->loadedMetadata = true; - return *n; + co_return *n; } // Calls to loadMetadata must keep the Node alive while the future is outstanding diff --git a/bindings/flow/Subspace.cpp b/bindings/flow/Subspace.cpp index 4f09408b43a..1605fd7a2fd 100644 --- a/bindings/flow/Subspace.cpp +++ b/bindings/flow/Subspace.cpp @@ -39,7 +39,7 @@ Subspace::Subspace(StringRef const& rawPrefix) { this->rawPrefix.append(this->rawPrefix.arena(), rawPrefix.begin(), rawPrefix.size()); } -Subspace::~Subspace() {} +Subspace::~Subspace() = default; Key Subspace::key() const { return StringRef(rawPrefix.begin(), rawPrefix.size()); @@ -89,4 +89,4 @@ Subspace Subspace::subspace(Tuple const& tuple) const { Subspace Subspace::get(Tuple const& tuple) const { return subspace(tuple); } -} // namespace FDB \ No newline at end of file +} // namespace FDB diff --git a/bindings/flow/fdb_flow.actor.cpp b/bindings/flow/fdb_flow.cpp similarity index 95% rename from bindings/flow/fdb_flow.actor.cpp rename to bindings/flow/fdb_flow.cpp index f9df8f5266e..9599cbece25 100644 --- a/bindings/flow/fdb_flow.actor.cpp +++ b/bindings/flow/fdb_flow.cpp @@ -1,5 +1,5 @@ /* - * fdb_flow.actor.cpp + * fdb_flow.cpp * * This source file is part of the FoundationDB open source project * @@ -21,14 +21,14 @@ #include "fdb_flow.h" #include -#include #include +#include +#include #include "fmt/format.h" #include "flow/DeterministicRandom.h" #include "flow/SystemMonitor.h" -#include "flow/TLSConfig.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/TLSConfig.h" using namespace FDB; @@ -37,47 +37,45 @@ THREAD_FUNC networkThread(void* fdb) { THREAD_RETURN; } -ACTOR Future _test() { +Future _test() { API* fdb = FDB::API::selectAPIVersion(FDB_API_VERSION); auto db = fdb->createDatabase(); - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); // tr->setVersion(1); - Version ver = wait(tr->getReadVersion()); + Version ver = co_await tr->getReadVersion(); fmt::print("{}\n", ver); - state std::vector> versions; + std::vector> versions; - state double starttime = timer_monotonic(); - state int i; - // for (i = 0; i < 100000; i++) { + double starttime = timer_monotonic(); + // for (int i = 0; i < 100000; i++) { // Version v = wait( tr->getReadVersion() ); // } - for (i = 0; i < 100000; i++) { + for (int i = 0; i < 100000; i++) { versions.push_back(tr->getReadVersion()); } - for (i = 0; i < 100000; i++) { - Version v = wait(versions[i]); + for (int i = 0; i < 100000; i++) { + co_await versions[i]; } // wait( waitForAllReady( versions ) ); printf("Elapsed: %lf\n", timer_monotonic() - starttime); tr->set("foo"_sr, "bar"_sr); - Optional> v = wait(tr->get("foo"_sr)); + Optional> v = co_await tr->get("foo"_sr); if (v.present()) { printf("%s\n", v.get().toString().c_str()); } - FDBStandalone r = wait(tr->getRange(KeyRangeRef("a"_sr, "z"_sr), 100)); + FDBStandalone r = co_await tr->getRange(KeyRangeRef("a"_sr, "z"_sr), 100); for (auto kv : r) { printf("%s is %s\n", kv.key.toString().c_str(), kv.value.toString().c_str()); } g_network->stop(); - return Void(); } void fdb_flow_test() { @@ -100,7 +98,7 @@ void fdb_flow_test() { namespace FDB { class DatabaseImpl : public Database, NonCopyable { public: - virtual ~DatabaseImpl() { fdb_database_destroy(db); } + ~DatabaseImpl() override { fdb_database_destroy(db); } Reference createTransaction() override; void setDatabaseOption(FDBDatabaseOption option, Optional value = Optional()) override; @@ -119,7 +117,7 @@ class TransactionImpl : public Transaction, private NonCopyable, public FastAllo friend class DatabaseImpl; public: - virtual ~TransactionImpl() { + ~TransactionImpl() override { if (tr) { fdb_transaction_destroy(tr); } @@ -201,17 +199,22 @@ void backToFutureCallback(FDBFuture* f, void* data) { // Takes an FDBFuture (from the alien client world, with callbacks potentially firing on an alien thread) // and converts it into a Future (with callbacks working on this thread, cancellation etc). // You must pass as the second parameter a function which takes a ready FDBFuture* and returns a value of Type -ACTOR template +template static Future backToFuture(FDBFuture* _f, Function convertValue) { - state Reference f(new CFuture(_f)); + Reference f(new CFuture(_f)); Promise ready; Future onReady = ready.getFuture(); throw_on_error(fdb_future_set_callback(f->f, backToFutureCallback, ready.extractRawPointer())); - wait(onReady); + co_await onReady; - return convertValue(f); + if constexpr (std::is_same_v) { + convertValue(f); + co_return; + } else { + co_return convertValue(f); + } } void API::setNetworkOption(FDBNetworkOption option, Optional value) { diff --git a/bindings/flow/tester/CMakeLists.txt b/bindings/flow/tester/CMakeLists.txt index dd7f8988de4..c1e67628fdf 100644 --- a/bindings/flow/tester/CMakeLists.txt +++ b/bindings/flow/tester/CMakeLists.txt @@ -1,6 +1,6 @@ set(TEST_SRCS - DirectoryTester.actor.cpp - Tester.actor.cpp - Tester.actor.h) + DirectoryTester.cpp + Tester.cpp + Tester.h) add_flow_target(EXECUTABLE NAME fdb_flow_tester SRCS ${TEST_SRCS}) target_link_libraries(fdb_flow_tester fdb_flow) diff --git a/bindings/flow/tester/DirectoryTester.actor.cpp b/bindings/flow/tester/DirectoryTester.cpp similarity index 64% rename from bindings/flow/tester/DirectoryTester.actor.cpp rename to bindings/flow/tester/DirectoryTester.cpp index a915b6b4266..e2c065c3592 100644 --- a/bindings/flow/tester/DirectoryTester.actor.cpp +++ b/bindings/flow/tester/DirectoryTester.cpp @@ -1,5 +1,5 @@ /* - * DirectoryTester.actor.cpp + * DirectoryTester.cpp * * This source file is part of the FoundationDB open source project * @@ -18,40 +18,38 @@ * limitations under the License. */ -#include "Tester.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "Tester.h" using namespace FDB; -ACTOR Future> popTuples(Reference data, int count = 1) { - state std::vector tuples; +Future> popTuples(Reference data, int count = 1) { + std::vector tuples; while (tuples.size() < count) { - Standalone sizeStr = wait(data->stack.pop()[0].value); + Standalone sizeStr = co_await data->stack.pop()[0].value; int size = Tuple::unpack(sizeStr).getInt(0); - state std::vector tupleItems = data->stack.pop(size); - state Tuple tuple; + std::vector tupleItems = data->stack.pop(size); + Tuple tuple; - state int index; - for (index = 0; index < tupleItems.size(); ++index) { - Standalone itemStr = wait(tupleItems[index].value); + for (int index = 0; index < tupleItems.size(); ++index) { + Standalone itemStr = co_await tupleItems[index].value; tuple.append(Tuple::unpack(itemStr)); } tuples.push_back(tuple); } - return tuples; + co_return tuples; } -ACTOR Future popTuple(Reference data) { - std::vector tuples = wait(popTuples(data)); - return tuples[0]; +Future popTuple(Reference data) { + std::vector tuples = co_await popTuples(data); + co_return tuples[0]; } -ACTOR Future> popPaths(Reference data, int count = 1) { - std::vector tuples = wait(popTuples(data, count)); +Future> popPaths(Reference data, int count = 1) { + std::vector tuples = co_await popTuples(data, count); std::vector paths; for (auto& tuple : tuples) { @@ -63,12 +61,12 @@ ACTOR Future> popPaths(Reference d paths.push_back(path); } - return paths; + co_return paths; } -ACTOR Future popPath(Reference data) { - std::vector paths = wait(popPaths(data)); - return paths[0]; +Future popPath(Reference data) { + std::vector paths = co_await popPaths(data); + co_return paths[0]; } std::string pathToString(IDirectory::Path const& path) { @@ -86,7 +84,7 @@ std::string pathToString(IDirectory::Path const& path) { IDirectory::Path combinePaths(IDirectory::Path const& path1, IDirectory::Path const& path2) { IDirectory::Path outPath(path1.begin(), path1.end()); - for (auto p : path2) { + for (const auto& p : path2) { outPath.push_back(p); } @@ -104,14 +102,13 @@ void logOp(std::string message, bool force = false) { struct DirectoryCreateSubspaceFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state Tuple path = wait(popTuple(data)); - Tuple rawPrefix = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple path = co_await popTuple(data); + Tuple rawPrefix = co_await data->stack.waitAndPop(); logOp(format( "Created subspace at %s: %s", tupleToString(path).c_str(), rawPrefix.getString(0).printable().c_str())); data->directoryData.push(new Subspace(path, rawPrefix.getString(0))); - return Void(); } }; const char* DirectoryCreateSubspaceFunc::name = "DIRECTORY_CREATE_SUBSPACE"; @@ -121,8 +118,8 @@ REGISTER_INSTRUCTION_FUNC(DirectoryCreateSubspaceFunc); struct DirectoryCreateLayerFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - std::vector args = wait(data->stack.waitAndPop(3)); + static Future call(Reference data, Reference instruction) { + std::vector args = co_await data->stack.waitAndPop(3); int index1 = args[0].getInt(0); int index2 = args[1].getInt(0); @@ -141,11 +138,9 @@ struct DirectoryCreateLayerFunc : InstructionFunc { index2, nodeSubspace->key().printable().c_str(), allowManualPrefixes)); - data->directoryData.push( - Reference(new DirectoryLayer(*nodeSubspace, *contentSubspace, allowManualPrefixes))); + data->directoryData.push(Reference( + makeReference(*nodeSubspace, *contentSubspace, allowManualPrefixes))); } - - return Void(); } }; const char* DirectoryCreateLayerFunc::name = "DIRECTORY_CREATE_LAYER"; @@ -155,8 +150,8 @@ REGISTER_INSTRUCTION_FUNC(DirectoryCreateLayerFunc); struct DirectoryChangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple index = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple index = co_await data->stack.waitAndPop(); data->directoryData.directoryListIndex = index.getInt(0); ASSERT(data->directoryData.directoryListIndex < data->directoryData.directoryList.size()); @@ -173,8 +168,6 @@ struct DirectoryChangeFunc : InstructionFunc { : d.subspace.get()->key().printable().c_str()); fflush(stdout); } - - return Void(); } }; const char* DirectoryChangeFunc::name = "DIRECTORY_CHANGE"; @@ -184,11 +177,9 @@ REGISTER_INSTRUCTION_FUNC(DirectoryChangeFunc); struct DirectorySetErrorIndexFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple index = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple index = co_await data->stack.waitAndPop(); data->directoryData.directoryErrorIndex = index.getInt(0); - - return Void(); } }; const char* DirectorySetErrorIndexFunc::name = "DIRECTORY_SET_ERROR_INDEX"; @@ -198,9 +189,9 @@ REGISTER_INSTRUCTION_FUNC(DirectorySetErrorIndexFunc); struct DirectoryCreateOrOpenFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state IDirectory::Path path = wait(popPath(data)); - Tuple layerTuple = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + IDirectory::Path path = co_await popPath(data); + Tuple layerTuple = co_await data->stack.waitAndPop(); Standalone layer = layerTuple.getType(0) == Tuple::NULL_TYPE ? StringRef() : layerTuple.getString(0); Reference directory = data->directoryData.directory(); @@ -208,12 +199,12 @@ struct DirectoryCreateOrOpenFunc : InstructionFunc { pathToString(combinePaths(directory->getPath(), path)).c_str(), layer.printable().c_str())); - Reference dirSubspace = wait(executeMutation( - instruction, [this, directory, layer]() { return directory->createOrOpen(instruction->tr, path, layer); })); + Reference dirSubspace = + co_await executeMutation(instruction, [instruction, directory, path, layer]() { + return directory->createOrOpen(instruction->tr, path, layer); + }); data->directoryData.push(dirSubspace); - - return Void(); } }; const char* DirectoryCreateOrOpenFunc::name = "DIRECTORY_CREATE_OR_OPEN"; @@ -223,9 +214,9 @@ REGISTER_INSTRUCTION_FUNC(DirectoryCreateOrOpenFunc); struct DirectoryCreateFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state IDirectory::Path path = wait(popPath(data)); - std::vector args = wait(data->stack.waitAndPop(2)); + static Future call(Reference data, Reference instruction) { + IDirectory::Path path = co_await popPath(data); + std::vector args = co_await data->stack.waitAndPop(2); Standalone layer = args[0].getType(0) == Tuple::NULL_TYPE ? StringRef() : args[0].getString(0); Optional> prefix = args[1].getType(0) == Tuple::NULL_TYPE ? Optional>() : args[1].getString(0); @@ -237,13 +228,11 @@ struct DirectoryCreateFunc : InstructionFunc { prefix.present() ? prefix.get().printable().c_str() : "")); Reference dirSubspace = - wait(executeMutation(instruction, [this, directory, layer, prefix]() { + co_await executeMutation(instruction, [instruction, directory, path, layer, prefix]() { return directory->create(instruction->tr, path, layer, prefix); - })); + }); data->directoryData.push(dirSubspace); - - return Void(); } }; const char* DirectoryCreateFunc::name = "DIRECTORY_CREATE"; @@ -253,19 +242,17 @@ REGISTER_INSTRUCTION_FUNC(DirectoryCreateFunc); struct DirectoryOpenFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state IDirectory::Path path = wait(popPath(data)); - Tuple layerTuple = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + IDirectory::Path path = co_await popPath(data); + Tuple layerTuple = co_await data->stack.waitAndPop(); Standalone layer = layerTuple.getType(0) == Tuple::NULL_TYPE ? StringRef() : layerTuple.getString(0); Reference directory = data->directoryData.directory(); logOp(format("open %s: layer=%s", pathToString(combinePaths(directory->getPath(), path)).c_str(), layer.printable().c_str())); - Reference dirSubspace = wait(directory->open(instruction->tr, path, layer)); + Reference dirSubspace = co_await directory->open(instruction->tr, path, layer); data->directoryData.push(dirSubspace); - - return Void(); } }; const char* DirectoryOpenFunc::name = "DIRECTORY_OPEN"; @@ -275,20 +262,20 @@ REGISTER_INSTRUCTION_FUNC(DirectoryOpenFunc); struct DirectoryMoveFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - std::vector paths = wait(popPaths(data, 2)); + static Future call(Reference data, Reference instruction) { + std::vector paths = co_await popPaths(data, 2); Reference directory = data->directoryData.directory(); logOp(format("move %s to %s", pathToString(combinePaths(directory->getPath(), paths[0])).c_str(), pathToString(combinePaths(directory->getPath(), paths[1])).c_str())); - Reference dirSubspace = wait(executeMutation( - instruction, [this, directory, paths]() { return directory->move(instruction->tr, paths[0], paths[1]); })); + Reference dirSubspace = + co_await executeMutation(instruction, [instruction, directory, paths]() { + return directory->move(instruction->tr, paths[0], paths[1]); + }); data->directoryData.push(dirSubspace); - - return Void(); } }; const char* DirectoryMoveFunc::name = "DIRECTORY_MOVE"; @@ -298,18 +285,16 @@ REGISTER_INSTRUCTION_FUNC(DirectoryMoveFunc); struct DirectoryMoveToFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - IDirectory::Path path = wait(popPath(data)); + static Future call(Reference data, Reference instruction) { + IDirectory::Path path = co_await popPath(data); Reference directory = data->directoryData.directory(); logOp(format("move %s to %s", pathToString(directory->getPath()).c_str(), pathToString(path).c_str())); - Reference dirSubspace = wait(executeMutation( - instruction, [this, directory, path]() { return directory->moveTo(instruction->tr, path); })); + Reference dirSubspace = co_await executeMutation( + instruction, [instruction, directory, path]() { return directory->moveTo(instruction->tr, path); }); data->directoryData.push(dirSubspace); - - return Void(); } }; const char* DirectoryMoveToFunc::name = "DIRECTORY_MOVE_TO"; @@ -319,21 +304,21 @@ REGISTER_INSTRUCTION_FUNC(DirectoryMoveToFunc); struct DirectoryRemoveFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple count = wait(data->stack.waitAndPop()); - state Reference directory = data->directoryData.directory(); + static Future call(Reference data, Reference instruction) { + Tuple count = co_await data->stack.waitAndPop(); + Reference directory = data->directoryData.directory(); if (count.getInt(0) == 0) { logOp(format("remove %s", pathToString(directory->getPath()).c_str())); - wait(executeMutation(instruction, [this]() { return directory->remove(instruction->tr); })); + co_await executeMutation(instruction, + [instruction, directory]() { return directory->remove(instruction->tr); }); } else { - IDirectory::Path path = wait(popPath(data)); + IDirectory::Path path = co_await popPath(data); logOp(format("remove %s", pathToString(combinePaths(directory->getPath(), path)).c_str())); - wait(executeMutation(instruction, [this, path]() { return directory->remove(instruction->tr, path); })); + co_await executeMutation( + instruction, [instruction, directory, path]() { return directory->remove(instruction->tr, path); }); } - - return Void(); } }; const char* DirectoryRemoveFunc::name = "DIRECTORY_REMOVE"; @@ -343,23 +328,22 @@ REGISTER_INSTRUCTION_FUNC(DirectoryRemoveFunc); struct DirectoryRemoveIfExistsFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple count = wait(data->stack.waitAndPop()); - state Reference directory = data->directoryData.directory(); + static Future call(Reference data, Reference instruction) { + Tuple count = co_await data->stack.waitAndPop(); + Reference directory = data->directoryData.directory(); if (count.getInt(0) == 0) { logOp(format("remove_if_exists %s", pathToString(directory->getPath()).c_str())); - wait( - success(executeMutation(instruction, [this]() { return directory->removeIfExists(instruction->tr); }))); + co_await executeMutation(instruction, + [instruction, directory]() { return directory->removeIfExists(instruction->tr); }); } else { - IDirectory::Path path = wait(popPath(data)); + IDirectory::Path path = co_await popPath(data); logOp(format("remove_if_exists %s", pathToString(combinePaths(directory->getPath(), path)).c_str())); - wait(success(executeMutation(instruction, - [this, path]() { return directory->removeIfExists(instruction->tr, path); }))); + co_await executeMutation(instruction, [instruction, directory, path]() { + return directory->removeIfExists(instruction->tr, path); + }); } - - return Void(); } }; const char* DirectoryRemoveIfExistsFunc::name = "DIRECTORY_REMOVE_IF_EXISTS"; @@ -369,18 +353,18 @@ REGISTER_INSTRUCTION_FUNC(DirectoryRemoveIfExistsFunc); struct DirectoryListFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple count = wait(data->stack.waitAndPop()); - state Reference directory = data->directoryData.directory(); - state Standalone> subdirs; + static Future call(Reference data, Reference instruction) { + Tuple count = co_await data->stack.waitAndPop(); + Reference directory = data->directoryData.directory(); + Standalone> subdirs; if (count.getInt(0) == 0) { logOp(format("list %s", pathToString(directory->getPath()).c_str())); - Standalone> _subdirs = wait(directory->list(instruction->tr)); + Standalone> _subdirs = co_await directory->list(instruction->tr); subdirs = _subdirs; } else { - IDirectory::Path path = wait(popPath(data)); + IDirectory::Path path = co_await popPath(data); logOp(format("list %s", pathToString(combinePaths(directory->getPath(), path)).c_str())); - Standalone> _subdirs = wait(directory->list(instruction->tr, path)); + Standalone> _subdirs = co_await directory->list(instruction->tr, path); subdirs = _subdirs; } @@ -390,7 +374,6 @@ struct DirectoryListFunc : InstructionFunc { } data->stack.pushTuple(subdirTuple.pack()); - return Void(); } }; const char* DirectoryListFunc::name = "DIRECTORY_LIST"; @@ -400,23 +383,22 @@ REGISTER_INSTRUCTION_FUNC(DirectoryListFunc); struct DirectoryExistsFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple count = wait(data->stack.waitAndPop()); - state Reference directory = data->directoryData.directory(); - state bool result; + static Future call(Reference data, Reference instruction) { + Tuple count = co_await data->stack.waitAndPop(); + Reference directory = data->directoryData.directory(); + bool result{ false }; if (count.getInt(0) == 0) { - bool _result = wait(directory->exists(instruction->tr)); + bool _result = co_await directory->exists(instruction->tr); result = _result; logOp(format("exists %s: %d", pathToString(directory->getPath()).c_str(), result)); } else { - state IDirectory::Path path = wait(popPath(data)); - bool _result = wait(directory->exists(instruction->tr, path)); + IDirectory::Path path = co_await popPath(data); + bool _result = co_await directory->exists(instruction->tr, path); result = _result; logOp(format("exists %s: %d", pathToString(combinePaths(directory->getPath(), path)).c_str(), result)); } data->stack.push(Tuple().append(result ? 1 : 0).pack()); - return Void(); } }; const char* DirectoryExistsFunc::name = "DIRECTORY_EXISTS"; @@ -426,11 +408,9 @@ REGISTER_INSTRUCTION_FUNC(DirectoryExistsFunc); struct DirectoryPackKeyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple tuple = wait(popTuple(data)); + static Future call(Reference data, Reference instruction) { + Tuple tuple = co_await popTuple(data); data->stack.pushTuple(data->directoryData.subspace()->pack(tuple)); - - return Void(); } }; const char* DirectoryPackKeyFunc::name = "DIRECTORY_PACK_KEY"; @@ -440,8 +420,8 @@ REGISTER_INSTRUCTION_FUNC(DirectoryPackKeyFunc); struct DirectoryUnpackKeyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple key = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple key = co_await data->stack.waitAndPop(); Subspace* subspace = data->directoryData.subspace(); logOp(format("Unpack %s in subspace with prefix %s", key.getString(0).printable().c_str(), @@ -450,8 +430,6 @@ struct DirectoryUnpackKeyFunc : InstructionFunc { for (int i = 0; i < tuple.size(); ++i) { data->stack.push(tuple.subTuple(i, i + 1).pack()); } - - return Void(); } }; const char* DirectoryUnpackKeyFunc::name = "DIRECTORY_UNPACK_KEY"; @@ -461,13 +439,11 @@ REGISTER_INSTRUCTION_FUNC(DirectoryUnpackKeyFunc); struct DirectoryRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple tuple = wait(popTuple(data)); + static Future call(Reference data, Reference instruction) { + Tuple tuple = co_await popTuple(data); KeyRange range = data->directoryData.subspace()->range(tuple); data->stack.pushTuple(range.begin); data->stack.pushTuple(range.end); - - return Void(); } }; const char* DirectoryRangeFunc::name = "DIRECTORY_RANGE"; @@ -477,12 +453,10 @@ REGISTER_INSTRUCTION_FUNC(DirectoryRangeFunc); struct DirectoryContainsFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple key = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple key = co_await data->stack.waitAndPop(); bool result = data->directoryData.subspace()->contains(key.getString(0)); data->stack.push(Tuple().append(result ? 1 : 0).pack()); - - return Void(); } }; const char* DirectoryContainsFunc::name = "DIRECTORY_CONTAINS"; @@ -492,14 +466,12 @@ REGISTER_INSTRUCTION_FUNC(DirectoryContainsFunc); struct DirectoryOpenSubspaceFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple tuple = wait(popTuple(data)); + static Future call(Reference data, Reference instruction) { + Tuple tuple = co_await popTuple(data); Subspace* subspace = data->directoryData.subspace(); logOp(format("open_subspace %s (at %s)", tupleToString(tuple).c_str(), subspace->key().printable().c_str())); Subspace* child = new Subspace(subspace->subspace(tuple)); data->directoryData.push(child); - - return Void(); } }; const char* DirectoryOpenSubspaceFunc::name = "DIRECTORY_OPEN_SUBSPACE"; @@ -509,13 +481,11 @@ REGISTER_INSTRUCTION_FUNC(DirectoryOpenSubspaceFunc); struct DirectoryLogSubspaceFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple prefix = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple prefix = co_await data->stack.waitAndPop(); Tuple tuple; tuple.append(data->directoryData.directoryListIndex); instruction->tr->set(Subspace(tuple, prefix.getString(0)).key(), data->directoryData.subspace()->key()); - - return Void(); } }; const char* DirectoryLogSubspaceFunc::name = "DIRECTORY_LOG_SUBSPACE"; @@ -525,14 +495,14 @@ REGISTER_INSTRUCTION_FUNC(DirectoryLogSubspaceFunc); struct DirectoryLogDirectoryFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state Reference directory = data->directoryData.directory(); - state Tuple prefix = wait(data->stack.waitAndPop()); - state bool exists = wait(directory->exists(instruction->tr)); + static Future call(Reference data, Reference instruction) { + Reference directory = data->directoryData.directory(); + Tuple prefix = co_await data->stack.waitAndPop(); + bool exists = co_await directory->exists(instruction->tr); - state Tuple childrenTuple; + Tuple childrenTuple; if (exists) { - Standalone> children = wait(directory->list(instruction->tr)); + Standalone> children = co_await directory->list(instruction->tr); for (auto& c : children) { childrenTuple.append(c, true); } @@ -549,8 +519,6 @@ struct DirectoryLogDirectoryFunc : InstructionFunc { instruction->tr->set(logSubspace.pack("layer"_sr, true), Tuple().append(directory->getLayer()).pack()); instruction->tr->set(logSubspace.pack("exists"_sr, true), Tuple().append(exists ? 1 : 0).pack()); instruction->tr->set(logSubspace.pack("children"_sr, true), childrenTuple.pack()); - - return Void(); } }; const char* DirectoryLogDirectoryFunc::name = "DIRECTORY_LOG_DIRECTORY"; @@ -560,12 +528,11 @@ REGISTER_INSTRUCTION_FUNC(DirectoryLogDirectoryFunc); struct DirectoryStripPrefixFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Tuple str = wait(data->stack.waitAndPop()); + static Future call(Reference data, Reference instruction) { + Tuple str = co_await data->stack.waitAndPop(); Subspace* subspace = data->directoryData.subspace(); ASSERT(str.getString(0).startsWith(subspace->key())); data->stack.pushTuple(str.getString(0).substr(subspace->key().size())); - return Void(); } }; const char* DirectoryStripPrefixFunc::name = "DIRECTORY_STRIP_PREFIX"; diff --git a/bindings/flow/tester/Tester.actor.cpp b/bindings/flow/tester/Tester.cpp similarity index 66% rename from bindings/flow/tester/Tester.actor.cpp rename to bindings/flow/tester/Tester.cpp index f1aaf401cf6..331f4f3fc09 100644 --- a/bindings/flow/tester/Tester.actor.cpp +++ b/bindings/flow/tester/Tester.cpp @@ -1,5 +1,5 @@ /* - * Tester.actor.cpp + * Tester.cpp * * This source file is part of the FoundationDB open source project * @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "tester/Tester.actor.h" +#include "tester/Tester.h" #include #ifdef __linux__ #include @@ -28,8 +28,7 @@ #include "bindings/flow/FDBLoanerTypes.h" #include "fdbrpc/fdbrpc.h" #include "flow/DeterministicRandom.h" -#include "flow/TLSConfig.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/TLSConfig.h" // Otherwise we have to type setupNetwork(), FDB::open(), etc. using namespace FDB; @@ -62,26 +61,25 @@ bool hasEnding(std::string const& fullString, std::string const& ending) { } } -ACTOR Future> waitAndPop(FlowTesterStack* self, int count) { - state std::vector tuples; - state std::vector items = self->pop(count); +Future> waitAndPop(FlowTesterStack* self, int count) { + std::vector tuples; + std::vector items = self->pop(count); - state int index; - for (index = 0; index < items.size(); ++index) { - Standalone itemStr = wait(items[index].value); + for (int index = 0; index < items.size(); ++index) { + Standalone itemStr = co_await items[index].value; tuples.push_back(Tuple::unpack(itemStr)); } - return tuples; + co_return tuples; } Future> FlowTesterStack::waitAndPop(int count) { return ::waitAndPop(this, count); } -ACTOR Future waitAndPop(FlowTesterStack* self) { - std::vector tuples = wait(waitAndPop(self, 1)); - return tuples[0]; +Future waitAndPop(FlowTesterStack* self) { + std::vector tuples = co_await waitAndPop(self, 1); + co_return tuples[0]; } Future FlowTesterStack::waitAndPop() { @@ -131,34 +129,34 @@ std::string tupleToString(Tuple const& tuple) { return str; } -ACTOR Future> getRange(Reference tr, - KeySelectorRef begin, - KeySelectorRef end, - int limits = 0, - bool snapshot = false, - bool reverse = false, - FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) { - state KeySelector ks_begin(begin); - state KeySelector ks_end(end); - state Standalone results; - state int iteration = 1; - loop { +Future> getRange(Reference tr, + KeySelectorRef begin, + KeySelectorRef end, + int limits = 0, + bool snapshot = false, + bool reverse = false, + FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) { + KeySelector ks_begin(begin); + KeySelector ks_end(end); + Standalone results; + int iteration = 1; + while (true) { // printf("=====DB: begin:%s, end:%s, limits:%d\n", printable(begin.key).c_str(), printable(end.key).c_str(), // limits); - state FDBStandalone r; + FDBStandalone r; if (streamingMode == FDB_STREAMING_MODE_ITERATOR && iteration > 1) { int effective_iteration = std::min(iteration, MAX_ITERATION); int bytes_limit = ITERATION_PROGRESSION[effective_iteration - 1]; - FDBStandalone rTemp = wait(tr->getRange(ks_begin, - ks_end, - GetRangeLimits(limits, bytes_limit), - snapshot, - reverse, - (FDBStreamingMode)FDB_STREAMING_MODE_EXACT)); + FDBStandalone rTemp = co_await tr->getRange(ks_begin, + ks_end, + GetRangeLimits(limits, bytes_limit), + snapshot, + reverse, + (FDBStreamingMode)FDB_STREAMING_MODE_EXACT); r = rTemp; } else { FDBStandalone rTemp = - wait(tr->getRange(ks_begin, ks_end, limits, snapshot, reverse, streamingMode)); + co_await tr->getRange(ks_begin, ks_end, limits, snapshot, reverse, streamingMode); r = rTemp; } iteration += 1; @@ -177,7 +175,7 @@ ACTOR Future> getRange(Reference tr, ASSERT(limits == 0 || limits >= r.size()); if (!r.more || (limits > 0 && limits == r.size())) { - return results; + co_return results; } if (limits > 0) { @@ -186,32 +184,32 @@ ACTOR Future> getRange(Reference tr, } } -ACTOR Future> getRange(Reference tr, - KeyRange keys, - int limits = 0, - bool snapshot = false, - bool reverse = false, - FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) { - state Key begin(keys.begin); - state Key end(keys.end); - state Standalone results; - state int iteration = 1; - loop { +Future> getRange(Reference tr, + KeyRange keys, + int limits = 0, + bool snapshot = false, + bool reverse = false, + FDBStreamingMode streamingMode = FDB_STREAMING_MODE_SERIAL) { + Key begin(keys.begin); + Key end(keys.end); + Standalone results; + int iteration = 1; + while (true) { // printf("=====DB: begin:%s, limits:%d\n", printable(begin).c_str(), limits); KeyRange keyRange(KeyRangeRef(begin, end > begin ? end : begin)); - state FDBStandalone r; + FDBStandalone r; if (streamingMode == FDB_STREAMING_MODE_ITERATOR && iteration > 1) { int effective_iteration = std::min(iteration, MAX_ITERATION); int bytes_limit = ITERATION_PROGRESSION[effective_iteration - 1]; - FDBStandalone rTemp = wait(tr->getRange(keyRange, - GetRangeLimits(limits, bytes_limit), - snapshot, - reverse, - (FDBStreamingMode)FDB_STREAMING_MODE_EXACT)); + FDBStandalone rTemp = co_await tr->getRange(keyRange, + GetRangeLimits(limits, bytes_limit), + snapshot, + reverse, + (FDBStreamingMode)FDB_STREAMING_MODE_EXACT); r = rTemp; } else { FDBStandalone rTemp = - wait(tr->getRange(keyRange, limits, snapshot, reverse, streamingMode)); + co_await tr->getRange(keyRange, limits, snapshot, reverse, streamingMode); r = rTemp; } iteration += 1; @@ -230,7 +228,7 @@ ACTOR Future> getRange(Reference tr, ASSERT(limits == 0 || limits >= r.size()); if (!r.more || (limits > 0 && limits == r.size())) { - return results; + co_return results; } if (limits > 0) { @@ -253,54 +251,50 @@ ACTOR Future> getRange(Reference tr, // return Void(); //} -ACTOR Future stackSub(FlowTesterStack* stack) { +Future stackSub(FlowTesterStack* stack) { if (stack->data.size() < 2) - return Void(); + co_return; StackItem a = stack->data.back(); stack->data.pop_back(); - state Standalone sa = wait(a.value); + Standalone sa = co_await a.value; StackItem b = stack->data.back(); stack->data.pop_back(); - Standalone sb = wait(b.value); + Standalone sb = co_await b.value; int64_t c = Tuple::unpack(sa).getInt(0) - Tuple::unpack(sb).getInt(0); Tuple f; f.append(c); stack->push(f.pack()); - - return Void(); } -ACTOR Future stackConcat(FlowTesterStack* stack) { +Future stackConcat(FlowTesterStack* stack) { if (stack->data.size() < 2) - return Void(); + co_return; StackItem a = stack->data.back(); stack->data.pop_back(); - state Standalone sa = wait(a.value); - state Tuple ta = Tuple::unpack(sa); + Standalone sa = co_await a.value; + Tuple ta = Tuple::unpack(sa); StackItem b = stack->data.back(); stack->data.pop_back(); - Standalone sb = wait(b.value); - state Tuple tb = Tuple::unpack(sb); + Standalone sb = co_await b.value; + Tuple tb = Tuple::unpack(sb); ASSERT(ta.getType(0) == tb.getType(0)); stack->pushTuple(tb.getString(0).withPrefix(ta.getString(0)), ta.getType(0) == Tuple::ElementType::UTF8); - - return Void(); } -ACTOR Future stackSwap(FlowTesterStack* stack) { +Future stackSwap(FlowTesterStack* stack) { if (stack->data.size() < 3) - return Void(); + co_return; StackItem pop = stack->data.back(); stack->data.pop_back(); - Standalone sv = wait(pop.value); + Standalone sv = co_await pop.value; int64_t idx = stack->data.size() - 1; int64_t idx1 = idx - Tuple::unpack(sv).getInt(0); if (idx1 < idx) { @@ -309,18 +303,15 @@ ACTOR Future stackSwap(FlowTesterStack* stack) { stack->data[idx] = stack->data[idx1]; stack->data[idx1] = item; } - return Void(); } -ACTOR Future printFlowTesterStack(FlowTesterStack* stack) { +Future printFlowTesterStack(FlowTesterStack* stack) { // printf("====================stack item count:%ld\n", stack->data.size()); - state int idx; - for (idx = stack->data.size() - 1; idx >= 0; --idx) { - Standalone value = wait(stack->data[idx].value); + for (int idx = stack->data.size() - 1; idx >= 0; --idx) { + Standalone value = co_await stack->data[idx].value; // printf("==========stack item:%d, index:%d, value:%s\n", idx, stack->data[idx].index, // value.printable().c_str()); } - return Void(); } // @@ -367,9 +358,8 @@ REGISTER_INSTRUCTION_FUNC(EmptyStackFunc); struct SwapFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - wait(stackSwap(&(data->stack))); - return Void(); + static Future call(Reference data, Reference instruction) { + co_await stackSwap(&(data->stack)); } }; const char* SwapFunc::name = "SWAP"; @@ -378,12 +368,11 @@ REGISTER_INSTRUCTION_FUNC(SwapFunc); struct PopFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); - for (StackItem item : items) { - wait(success(item.value)); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); + for (const StackItem& item : items) { + co_await item.value; } - return Void(); } }; const char* PopFunc::name = "POP"; @@ -392,9 +381,8 @@ REGISTER_INSTRUCTION_FUNC(PopFunc); struct SubFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - wait(stackSub(&(data->stack))); - return Void(); + static Future call(Reference data, Reference instruction) { + co_await stackSub(&(data->stack)); } }; const char* SubFunc::name = "SUB"; @@ -403,9 +391,8 @@ REGISTER_INSTRUCTION_FUNC(SubFunc); struct ConcatFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - wait(stackConcat(&(data->stack))); - return Void(); + static Future call(Reference data, Reference instruction) { + co_await stackConcat(&(data->stack)); } }; const char* ConcatFunc::name = "CONCAT"; @@ -414,50 +401,50 @@ REGISTER_INSTRUCTION_FUNC(ConcatFunc); struct LogStackFunc : InstructionFunc { static const char* name; - ACTOR static Future logStack(Reference data, - std::map entries, - Standalone prefix) { - loop { - state Reference tr = data->db->createTransaction(); + static Future logStack(Reference data, + std::map entries, + Standalone prefix) { + while (true) { + Reference tr = data->db->createTransaction(); + Error err; try { - for (auto it : entries) { + for (const auto& it : entries) { Tuple tk; tk.append(it.first); tk.append((int64_t)it.second.index); - state Standalone pk = tk.pack().withPrefix(prefix); - Standalone pv = wait(it.second.value); + Standalone pk = tk.pack().withPrefix(prefix); + Standalone pv = co_await it.second.value; tr->set(pk, pv.substr(0, std::min(pv.size(), 40000))); } - wait(tr->commit()); - return Void(); + co_await tr->commit(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.empty()) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone prefix = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone prefix = Tuple::unpack(s1).getString(0); - state std::map entries; - while (data->stack.data.size() > 0) { - state std::vector it = data->stack.pop(); + std::map entries; + while (!data->stack.data.empty()) { + std::vector it = data->stack.pop(); ASSERT(it.size() == 1); entries[data->stack.data.size()] = it.front(); if (entries.size() == 100) { - wait(logStack(data, entries, prefix)); + co_await logStack(data, entries, prefix); entries.clear(); } } - wait(logStack(data, entries, prefix)); - - return Void(); + co_await logStack(data, entries, prefix); } }; const char* LogStackFunc::name = "LOG_STACK"; @@ -466,12 +453,12 @@ REGISTER_INSTRUCTION_FUNC(LogStackFunc); // // FoundationDB Operations // -ACTOR Future> waitForVoid(Future f) { +Future> waitForVoid(Future f) { try { - wait(f); + co_await f; Tuple t; t.append("RESULT_NOT_PRESENT"_sr); - return t.pack(); + co_return t.pack(); } catch (Error& e) { // printf("FDBError1:%d\n", e.code()); Tuple t; @@ -480,16 +467,16 @@ ACTOR Future> waitForVoid(Future f) { // pack above as error string into another tuple Tuple ret; ret.append(t.pack()); - return ret.pack(); + co_return ret.pack(); } } -ACTOR Future> waitForValue(Future> f) { +Future> waitForValue(Future> f) { try { - FDBStandalone value = wait(f); + FDBStandalone value = co_await f; Tuple t; t.append(value); - return t.pack(); + co_return t.pack(); } catch (Error& e) { // printf("FDBError2:%d\n", e.code()); Tuple t; @@ -498,13 +485,13 @@ ACTOR Future> waitForValue(Future> f // pack above as error string into another tuple Tuple ret; ret.append(t.pack()); - return ret.pack(); + co_return ret.pack(); } } -ACTOR Future> waitForValue(Future>> f) { +Future> waitForValue(Future>> f) { try { - Optional> value = wait(f); + Optional> value = co_await f; Standalone str; if (value.present()) str = value.get(); @@ -513,7 +500,7 @@ ACTOR Future> waitForValue(Future> waitForValue(Future> getKey(Future> f, Standalone prefixFilter) { +Future> getKey(Future> f, Standalone prefixFilter) { try { - FDBStandalone key = wait(f); + FDBStandalone key = co_await f; Tuple t; if (key.startsWith(prefixFilter)) { @@ -539,7 +526,7 @@ ACTOR Future> getKey(Future> f, Stan t.append(strinc(prefixFilter)); } - return t.pack(); + co_return t.pack(); } catch (Error& e) { // printf("FDBError4:%d\n", e.code()); Tuple t; @@ -548,7 +535,7 @@ ACTOR Future> getKey(Future> f, Stan // pack above as error string into another tuple Tuple ret; ret.append(t.pack()); - return ret.pack(); + co_return ret.pack(); } } @@ -566,15 +553,14 @@ REGISTER_INSTRUCTION_FUNC(NewTransactionFunc); struct UseTransactionFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); - Standalone name = wait(items[0].value); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); + Standalone name = co_await items[0].value; data->trName = name; - if (trMap.count(data->trName) == 0) { + if (!trMap.contains(data->trName)) { trMap[data->trName] = data->db->createTransaction(); } - return Void(); } }; const char* UseTransactionFunc::name = "USE_TRANSACTION"; @@ -583,17 +569,16 @@ REGISTER_INSTRUCTION_FUNC(UseTransactionFunc); struct OnErrorFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.empty()) - return Void(); + co_return; - Standalone value = wait(items[0].value); + Standalone value = co_await items[0].value; int err_code = Tuple::unpack(value).getInt(0); // printf("OnError:%d:%d:%s\n", err_code, items[0].index, printable(value).c_str()); data->stack.push(waitForVoid(instruction->tr->onError(Error(err_code)))); - return Void(); } }; const char* OnErrorFunc::name = "ON_ERROR"; @@ -602,16 +587,16 @@ REGISTER_INSTRUCTION_FUNC(OnErrorFunc); struct SetFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(2); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(2); if (items.size() != 2) - return Void(); + co_return; - Standalone sk = wait(items[0].value); - state Standalone key = Tuple::unpack(sk).getString(0); + Standalone sk = co_await items[0].value; + Standalone key = Tuple::unpack(sk).getString(0); // if (instruction->isDatabase) // printf("SetDatabase:%s, isDatabase:%d\n", printable(key).c_str(), instruction->isDatabase); - Standalone sv = wait(items[1].value); + Standalone sv = co_await items[1].value; Standalone value = Tuple::unpack(sv).getString(0); // printf("SetDatabase:%s:%s:%s\n", printable(key).c_str(), printable(sv).c_str(), printable(value).c_str()); @@ -626,10 +611,8 @@ struct SetFunc : InstructionFunc { if (instruction->isDatabase) { data->stack.push(waitForVoid(mutation)); } else { - wait(mutation); + co_await mutation; } - - return Void(); } }; const char* SetFunc::name = "SET"; @@ -638,18 +621,16 @@ REGISTER_INSTRUCTION_FUNC(SetFunc); struct GetFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone sk = wait(items[0].value); - state Standalone key = Tuple::unpack(sk).getString(0); + Standalone sk = co_await items[0].value; + Standalone key = Tuple::unpack(sk).getString(0); Future>> fk = instruction->tr->get(StringRef(key), instruction->isSnapshot); data->stack.push(waitForValue(holdWhile(instruction->tr, fk))); - - return Void(); } }; const char* GetFunc::name = "GET"; @@ -658,21 +639,19 @@ REGISTER_INSTRUCTION_FUNC(GetFunc); struct GetEstimatedRangeSize : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(2); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(2); if (items.size() != 2) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone beginKey = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone beginKey = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone endKey = Tuple::unpack(s2).getString(0); + Standalone s2 = co_await items[1].value; + Standalone endKey = Tuple::unpack(s2).getString(0); Future fsize = instruction->tr->getEstimatedRangeSizeBytes(KeyRangeRef(beginKey, endKey)); - int64_t size = wait(fsize); + co_await fsize; data->stack.pushTuple("GOT_ESTIMATED_RANGE_SIZE"_sr); - - return Void(); } }; const char* GetEstimatedRangeSize::name = "GET_ESTIMATED_RANGE_SIZE"; @@ -681,26 +660,24 @@ REGISTER_INSTRUCTION_FUNC(GetEstimatedRangeSize); struct GetRangeSplitPoints : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(3); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(3); if (items.size() != 3) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone beginKey = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone beginKey = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone endKey = Tuple::unpack(s2).getString(0); + Standalone s2 = co_await items[1].value; + Standalone endKey = Tuple::unpack(s2).getString(0); - Standalone s3 = wait(items[2].value); - state int64_t chunkSize = Tuple::unpack(s3).getInt(0); + Standalone s3 = co_await items[2].value; + int64_t chunkSize = Tuple::unpack(s3).getInt(0); Future>> fsplitPoints = instruction->tr->getRangeSplitPoints(KeyRangeRef(beginKey, endKey), chunkSize); - FDBStandalone> splitPoints = wait(fsplitPoints); + FDBStandalone> splitPoints = co_await fsplitPoints; data->stack.pushTuple("GOT_RANGE_SPLIT_POINTS"_sr); - - return Void(); } }; const char* GetRangeSplitPoints::name = "GET_RANGE_SPLIT_POINTS"; @@ -709,29 +686,27 @@ REGISTER_INSTRUCTION_FUNC(GetRangeSplitPoints); struct GetKeyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(4); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(4); if (items.size() != 4) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone key = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone key = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state int64_t or_equal = Tuple::unpack(s2).getInt(0); + Standalone s2 = co_await items[1].value; + int64_t or_equal = Tuple::unpack(s2).getInt(0); - Standalone s3 = wait(items[2].value); - state int64_t offset = Tuple::unpack(s3).getInt(0); + Standalone s3 = co_await items[2].value; + int64_t offset = Tuple::unpack(s3).getInt(0); - Standalone s4 = wait(items[3].value); + Standalone s4 = co_await items[3].value; Standalone prefix = Tuple::unpack(s4).getString(0); // printf("===================GET_KEY:%s, %ld, %ld\n", printable(key).c_str(), or_equal, offset); Future> fk = instruction->tr->getKey(KeySelector(KeySelectorRef(key, or_equal, offset)), instruction->isSnapshot); data->stack.push(getKey(holdWhile(instruction->tr, fk), prefix)); - - return Void(); } }; const char* GetKeyFunc::name = "GET_KEY"; @@ -740,11 +715,10 @@ REGISTER_INSTRUCTION_FUNC(GetKeyFunc); struct GetReadVersionFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - Version v = wait(instruction->tr->getReadVersion()); + static Future call(Reference data, Reference instruction) { + Version v = co_await instruction->tr->getReadVersion(); data->lastVersion = v; data->stack.pushTuple("GOT_READ_VERSION"_sr); - return Void(); } }; const char* GetReadVersionFunc::name = "GET_READ_VERSION"; @@ -778,11 +752,10 @@ REGISTER_INSTRUCTION_FUNC(GetCommittedVersionFunc); struct GetApproximateSizeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - int64_t _ = wait(instruction->tr->getApproximateSize()); + static Future call(Reference data, Reference instruction) { + int64_t _ = co_await instruction->tr->getApproximateSize(); (void)_; // disable unused variable warning data->stack.pushTuple("GOT_APPROXIMATE_SIZE"_sr); - return Void(); } }; const char* GetApproximateSizeFunc::name = "GET_APPROXIMATE_SIZE"; @@ -816,14 +789,13 @@ REGISTER_INSTRUCTION_FUNC(CommitFunc); struct WaitFutureFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone sk = wait(items[0].value); + Standalone sk = co_await items[0].value; data->stack.push(StackItem(items[0].index, sk)); - return Void(); } }; const char* WaitFutureFunc::name = "WAIT_FUTURE"; @@ -833,12 +805,12 @@ REGISTER_INSTRUCTION_FUNC(WaitFutureFunc); struct ClearFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone sk = wait(items[0].value); + Standalone sk = co_await items[0].value; Standalone key = Tuple::unpack(sk).getString(0); Reference instructionCopy = instruction; @@ -851,10 +823,8 @@ struct ClearFunc : InstructionFunc { if (instruction->isDatabase) { data->stack.push(waitForVoid(mutation)); } else { - wait(mutation); + co_await mutation; } - - return Void(); } }; const char* ClearFunc::name = "CLEAR"; @@ -887,35 +857,35 @@ REGISTER_INSTRUCTION_FUNC(CancelFunc); struct GetRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(5); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(5); if (items.size() != 5) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone begin = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone begin = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone end = Tuple::unpack(s2).getString(0); + Standalone s2 = co_await items[1].value; + Standalone end = Tuple::unpack(s2).getString(0); - Standalone s3 = wait(items[2].value); - state int limit = Tuple::unpack(s3).getInt(0); + Standalone s3 = co_await items[2].value; + int limit = Tuple::unpack(s3).getInt(0); - Standalone s4 = wait(items[3].value); - state int reverse = Tuple::unpack(s4).getInt(0); + Standalone s4 = co_await items[3].value; + int reverse = Tuple::unpack(s4).getInt(0); - Standalone s5 = wait(items[4].value); + Standalone s5 = co_await items[4].value; FDBStreamingMode mode = (FDBStreamingMode)Tuple::unpack(s5).getInt(0); // printf("================GetRange: %s, %s, %d, %d, %d, %d\n", printable(begin).c_str(), // printable(end).c_str(), limit, reverse, mode, instruction->isSnapshot); - Standalone results = wait(getRange(instruction->tr, - KeyRange(KeyRangeRef(begin, end > begin ? end : begin)), - limit, - instruction->isSnapshot, - reverse, - mode)); + Standalone results = co_await getRange(instruction->tr, + KeyRange(KeyRangeRef(begin, end > begin ? end : begin)), + limit, + instruction->isSnapshot, + reverse, + mode); Tuple t; for (auto& s : results) { t.append(s.key); @@ -926,7 +896,6 @@ struct GetRangeFunc : InstructionFunc { // printf("=====Results Count:%d, size:%d\n", results.size(), str.size()); data->stack.push(Tuple().append(t.pack()).pack()); - return Void(); } }; const char* GetRangeFunc::name = "GET_RANGE"; @@ -935,31 +904,31 @@ REGISTER_INSTRUCTION_FUNC(GetRangeFunc); struct GetRangeStartsWithFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(4); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(4); if (items.size() != 4) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone prefix = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone prefix = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state int limit = Tuple::unpack(s2).getInt(0); + Standalone s2 = co_await items[1].value; + int limit = Tuple::unpack(s2).getInt(0); - Standalone s3 = wait(items[2].value); - state int reverse = Tuple::unpack(s3).getInt(0); + Standalone s3 = co_await items[2].value; + int reverse = Tuple::unpack(s3).getInt(0); - Standalone s4 = wait(items[3].value); + Standalone s4 = co_await items[3].value; FDBStreamingMode mode = (FDBStreamingMode)Tuple::unpack(s4).getInt(0); // printf("================GetRangeStartsWithFunc: %s, %d, %d, %d, %d\n", printable(prefix).c_str(), limit, // reverse, mode, isSnapshot); - Standalone results = wait(getRange(instruction->tr, - KeyRange(KeyRangeRef(prefix, strinc(prefix))), - limit, - instruction->isSnapshot, - reverse, - mode)); + Standalone results = co_await getRange(instruction->tr, + KeyRange(KeyRangeRef(prefix, strinc(prefix))), + limit, + instruction->isSnapshot, + reverse, + mode); Tuple t; // printf("=====Results Count:%d\n", results.size()); for (auto& s : results) { @@ -970,7 +939,6 @@ struct GetRangeStartsWithFunc : InstructionFunc { } data->stack.push(Tuple().append(t.pack()).pack()); - return Void(); } }; const char* GetRangeStartsWithFunc::name = "GET_RANGE_STARTS_WITH"; @@ -979,15 +947,15 @@ REGISTER_INSTRUCTION_FUNC(GetRangeStartsWithFunc); struct ClearRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(2); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(2); if (items.size() != 2) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone begin = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone begin = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); + Standalone s2 = co_await items[1].value; Standalone end = Tuple::unpack(s2).getString(0); Reference instructionCopy = instruction; @@ -1001,10 +969,8 @@ struct ClearRangeFunc : InstructionFunc { if (instruction->isDatabase) { data->stack.push(waitForVoid(mutation)); } else { - wait(mutation); + co_await mutation; } - - return Void(); } }; const char* ClearRangeFunc::name = "CLEAR_RANGE"; @@ -1013,12 +979,12 @@ REGISTER_INSTRUCTION_FUNC(ClearRangeFunc); struct ClearRangeStartWithFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; Standalone begin = Tuple::unpack(s1).getString(0); Reference instructionCopy = instruction; @@ -1031,10 +997,8 @@ struct ClearRangeStartWithFunc : InstructionFunc { if (instruction->isDatabase) { data->stack.push(waitForVoid(mutation)); } else { - wait(mutation); + co_await mutation; } - - return Void(); } }; const char* ClearRangeStartWithFunc::name = "CLEAR_RANGE_STARTS_WITH"; @@ -1043,40 +1007,40 @@ REGISTER_INSTRUCTION_FUNC(ClearRangeStartWithFunc); struct GetRangeSelectorFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(10); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(10); if (items.size() != 10) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone begin = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone begin = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state bool begin_or_equal = Tuple::unpack(s2).getInt(0); + Standalone s2 = co_await items[1].value; + bool begin_or_equal = Tuple::unpack(s2).getInt(0); - Standalone s3 = wait(items[2].value); - state int64_t begin_offset = Tuple::unpack(s3).getInt(0); + Standalone s3 = co_await items[2].value; + int64_t begin_offset = Tuple::unpack(s3).getInt(0); - Standalone s4 = wait(items[3].value); - state Standalone end = Tuple::unpack(s4).getString(0); + Standalone s4 = co_await items[3].value; + Standalone end = Tuple::unpack(s4).getString(0); - Standalone s5 = wait(items[4].value); - state bool end_or_equal = Tuple::unpack(s5).getInt(0); + Standalone s5 = co_await items[4].value; + bool end_or_equal = Tuple::unpack(s5).getInt(0); - Standalone s6 = wait(items[5].value); - state int64_t end_offset = Tuple::unpack(s6).getInt(0); + Standalone s6 = co_await items[5].value; + int64_t end_offset = Tuple::unpack(s6).getInt(0); - Standalone s7 = wait(items[6].value); - state int limit = Tuple::unpack(s7).getInt(0); + Standalone s7 = co_await items[6].value; + int limit = Tuple::unpack(s7).getInt(0); - Standalone s8 = wait(items[7].value); - state int reverse = Tuple::unpack(s8).getInt(0); + Standalone s8 = co_await items[7].value; + int reverse = Tuple::unpack(s8).getInt(0); - Standalone s9 = wait(items[8].value); - state FDBStreamingMode mode = (FDBStreamingMode)Tuple::unpack(s9).getInt(0); + Standalone s9 = co_await items[8].value; + FDBStreamingMode mode = (FDBStreamingMode)Tuple::unpack(s9).getInt(0); - Standalone s10 = wait(items[9].value); - state Optional> prefix; + Standalone s10 = co_await items[9].value; + Optional> prefix; Tuple t10 = Tuple::unpack(s10); if (t10.getType(0) != Tuple::ElementType::NULL_TYPE) { prefix = t10.getString(0); @@ -1092,7 +1056,7 @@ struct GetRangeSelectorFunc : InstructionFunc { instruction->isSnapshot, reverse, mode); - Standalone results = wait(holdWhile(instruction->tr, f)); + Standalone results = co_await holdWhile(instruction->tr, f); Tuple t; // printf("=====Results Count:%d\n", results.size()); for (auto& s : results) { @@ -1105,7 +1069,6 @@ struct GetRangeSelectorFunc : InstructionFunc { } data->stack.push(Tuple().append(t.pack()).pack()); - return Void(); } }; const char* GetRangeSelectorFunc::name = "GET_RANGE_SELECTOR"; @@ -1116,22 +1079,21 @@ REGISTER_INSTRUCTION_FUNC(GetRangeSelectorFunc); struct TuplePackFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state int64_t count = Tuple::unpack(s1).getInt(0); + Standalone s1 = co_await items[0].value; + int64_t count = Tuple::unpack(s1).getInt(0); - state std::vector items1 = data->stack.pop(count); + std::vector items1 = data->stack.pop(count); if (items1.size() != count) - return Void(); + co_return; - state Tuple tuple; - state int i = 0; - for (; i < items1.size(); ++i) { - Standalone str = wait(items1[i].value); + Tuple tuple; + for (int i = 0; i < items1.size(); ++i) { + Standalone str = co_await items1[i].value; Tuple itemTuple = Tuple::unpack(str); if (deterministicRandom()->coinflip()) { Tuple::ElementType type = itemTuple.getType(0); @@ -1164,7 +1126,6 @@ struct TuplePackFunc : InstructionFunc { } data->stack.pushTuple(tuple.pack()); - return Void(); } }; const char* TuplePackFunc::name = "TUPLE_PACK"; @@ -1174,12 +1135,12 @@ REGISTER_INSTRUCTION_FUNC(TuplePackFunc); struct TupleUnpackFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; Tuple t = Tuple::unpack(Tuple::unpack(s1).getString(0)); for (int i = 0; i < t.size(); ++i) { @@ -1187,7 +1148,6 @@ struct TupleUnpackFunc : InstructionFunc { // printf("=====value:%s\n", printable(str).c_str()); data->stack.pushTuple(str); } - return Void(); } }; const char* TupleUnpackFunc::name = "TUPLE_UNPACK"; @@ -1197,22 +1157,21 @@ REGISTER_INSTRUCTION_FUNC(TupleUnpackFunc); struct TupleRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state int64_t count = Tuple::unpack(s1).getInt(0); + Standalone s1 = co_await items[0].value; + int64_t count = Tuple::unpack(s1).getInt(0); - state std::vector items1 = data->stack.pop(count); + std::vector items1 = data->stack.pop(count); if (items1.size() != count) - return Void(); + co_return; - state Tuple tuple; - state size_t i = 0; - for (; i < items1.size(); ++i) { - Standalone str = wait(items1[i].value); + Tuple tuple; + for (size_t i = 0; i < items1.size(); ++i) { + Standalone str = co_await items1[i].value; Tuple itemTuple = Tuple::unpack(str); if (deterministicRandom()->coinflip()) { Tuple::ElementType type = itemTuple.getType(0); @@ -1248,7 +1207,6 @@ struct TupleRangeFunc : InstructionFunc { data->stack.pushTuple(range.begin); data->stack.pushTuple(range.end); - return Void(); } }; const char* TupleRangeFunc::name = "TUPLE_RANGE"; @@ -1258,22 +1216,21 @@ REGISTER_INSTRUCTION_FUNC(TupleRangeFunc); struct TupleSortFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state int64_t count = Tuple::unpack(s1).getInt(0); + Standalone s1 = co_await items[0].value; + int64_t count = Tuple::unpack(s1).getInt(0); - state std::vector items1 = data->stack.pop(count); + std::vector items1 = data->stack.pop(count); if (items1.size() != count) - return Void(); + co_return; - state std::vector tuples; - state size_t i = 0; - for (; i < items1.size(); i++) { - Standalone value = wait(items1[i].value); + std::vector tuples; + for (size_t i = 0; i < items1.size(); i++) { + Standalone value = co_await items1[i].value; tuples.push_back(Tuple::unpack(value)); } @@ -1281,8 +1238,6 @@ struct TupleSortFunc : InstructionFunc { for (Tuple const& t : tuples) { data->stack.push(t.pack()); } - - return Void(); } }; const char* TupleSortFunc::name = "TUPLE_SORT"; @@ -1292,12 +1247,12 @@ REGISTER_INSTRUCTION_FUNC(TupleSortFunc); struct EncodeFloatFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { + static Future call(Reference data, Reference instruction) { std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; Standalone fBytes = Tuple::unpack(s1).getString(0); ASSERT(fBytes.size() == 4); @@ -1308,8 +1263,6 @@ struct EncodeFloatFunc : InstructionFunc { Tuple t; t.append(fVal); data->stack.push(t.pack()); - - return Void(); } }; const char* EncodeFloatFunc::name = "ENCODE_FLOAT"; @@ -1319,12 +1272,12 @@ REGISTER_INSTRUCTION_FUNC(EncodeFloatFunc); struct EncodeDoubleFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { + static Future call(Reference data, Reference instruction) { std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; Standalone dBytes = Tuple::unpack(s1).getString(0); ASSERT(dBytes.size() == 8); @@ -1335,8 +1288,6 @@ struct EncodeDoubleFunc : InstructionFunc { Tuple t; t.append(dVal); data->stack.push(t.pack()); - - return Void(); } }; const char* EncodeDoubleFunc::name = "ENCODE_DOUBLE"; @@ -1346,12 +1297,12 @@ REGISTER_INSTRUCTION_FUNC(EncodeDoubleFunc); struct DecodeFloatFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { + static Future call(Reference data, Reference instruction) { std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; float fVal = Tuple::unpack(s1).getFloat(0); int32_t intVal = *(int32_t*)&fVal; intVal = bigEndian32(intVal); @@ -1359,8 +1310,6 @@ struct DecodeFloatFunc : InstructionFunc { Tuple t; t.append(StringRef((uint8_t*)&intVal, 4), false); data->stack.push(t.pack()); - - return Void(); } }; const char* DecodeFloatFunc::name = "DECODE_FLOAT"; @@ -1370,12 +1319,12 @@ REGISTER_INSTRUCTION_FUNC(DecodeFloatFunc); struct DecodeDoubleFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { + static Future call(Reference data, Reference instruction) { std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; double dVal = Tuple::unpack(s1).getDouble(0); int64_t intVal = *(int64_t*)&dVal; intVal = bigEndian64(intVal); @@ -1383,7 +1332,6 @@ struct DecodeDoubleFunc : InstructionFunc { Tuple t; t.append(StringRef((uint8_t*)&intVal, 8), false); data->stack.push(t.pack()); - return Void(); } }; const char* DecodeDoubleFunc::name = "DECODE_DOUBLE"; @@ -1394,35 +1342,34 @@ REGISTER_INSTRUCTION_FUNC(DecodeDoubleFunc); struct StartThreadFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone prefix = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone prefix = Tuple::unpack(s1).getString(0); // printf("=========START_THREAD:%s\n", printable(prefix).c_str()); - Reference newData = Reference(new FlowTesterData(data->api)); + auto newData = makeReference(data->api); data->subThreads.push_back(runTest(newData, data->db, prefix)); - - return Void(); } }; const char* StartThreadFunc::name = "START_THREAD"; REGISTER_INSTRUCTION_FUNC(StartThreadFunc); -ACTOR template +template Future()(Reference()).getValue())> read(Reference db, Function func) { - state Reference tr = db->createTransaction(); - loop { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { - state decltype(std::declval()(Reference()).getValue()) result = wait(func(tr)); - return result; + co_return co_await func(tr); } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } @@ -1430,28 +1377,25 @@ Future()(Reference()).getValue( struct WaitEmptyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); + Standalone s1 = co_await items[0].value; Standalone prefix = Tuple::unpack(s1).getString(0); // printf("=========WAIT_EMPTY:%s\n", printable(prefix).c_str()); - wait(read(data->db, - [=](Reference tr) -> Future { return checkEmptyPrefix(tr, prefix); })); - - return Void(); + co_await read(data->db, + [=](Reference tr) -> Future { return checkEmptyPrefix(tr, prefix); }); } private: - ACTOR static Future checkEmptyPrefix(Reference tr, Standalone prefix) { - FDBStandalone results = wait(tr->getRange(KeyRangeRef(prefix, strinc(prefix)), 1)); - if (results.size() > 0) { + static Future checkEmptyPrefix(Reference tr, Standalone prefix) { + FDBStandalone results = co_await tr->getRange(KeyRangeRef(prefix, strinc(prefix)), 1); + if (!results.empty()) { throw not_committed(); } - return Void(); } }; const char* WaitEmptyFunc::name = "WAIT_EMPTY"; @@ -1475,18 +1419,17 @@ REGISTER_INSTRUCTION_FUNC(DisableWriteConflictFunc); struct ReadConflictKeyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone key = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone key = Tuple::unpack(s1).getString(0); // printf("=========READ_CONFLICT_KEY:%s\n", printable(key).c_str()); instruction->tr->addReadConflictKey(key); data->stack.pushTuple("SET_CONFLICT_KEY"_sr); - return Void(); } }; const char* ReadConflictKeyFunc::name = "READ_CONFLICT_KEY"; @@ -1496,18 +1439,17 @@ REGISTER_INSTRUCTION_FUNC(ReadConflictKeyFunc); struct WriteConflictKeyFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(); if (items.size() != 1) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone key = Tuple::unpack(s1).getString(0); + Standalone s1 = co_await items[0].value; + Standalone key = Tuple::unpack(s1).getString(0); // printf("=========WRITE_CONFLICT_KEY:%s\n", printable(key).c_str()); instruction->tr->addWriteConflictKey(key); data->stack.pushTuple("SET_CONFLICT_KEY"_sr); - return Void(); } }; const char* WriteConflictKeyFunc::name = "WRITE_CONFLICT_KEY"; @@ -1517,20 +1459,19 @@ REGISTER_INSTRUCTION_FUNC(WriteConflictKeyFunc); struct ReadConflictRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(2); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(2); if (items.size() != 2) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone begin = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone end = Tuple::unpack(s2).getString(0); + Standalone s1 = co_await items[0].value; + Standalone begin = Tuple::unpack(s1).getString(0); + Standalone s2 = co_await items[1].value; + Standalone end = Tuple::unpack(s2).getString(0); // printf("=========READ_CONFLICT_RANGE:%s:%s\n", printable(begin).c_str(), printable(end).c_str()); instruction->tr->addReadConflictRange(KeyRange(KeyRangeRef(begin, end))); data->stack.pushTuple("SET_CONFLICT_RANGE"_sr); - return Void(); } }; const char* ReadConflictRangeFunc::name = "READ_CONFLICT_RANGE"; @@ -1540,21 +1481,20 @@ REGISTER_INSTRUCTION_FUNC(ReadConflictRangeFunc); struct WriteConflictRangeFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(2); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(2); if (items.size() != 2) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone begin = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone end = Tuple::unpack(s2).getString(0); + Standalone s1 = co_await items[0].value; + Standalone begin = Tuple::unpack(s1).getString(0); + Standalone s2 = co_await items[1].value; + Standalone end = Tuple::unpack(s2).getString(0); // printf("=========WRITE_CONFLICT_RANGE:%s:%s\n", printable(begin).c_str(), printable(end).c_str()); instruction->tr->addWriteConflictRange(KeyRange(KeyRangeRef(begin, end))); data->stack.pushTuple("SET_CONFLICT_RANGE"_sr); - return Void(); } }; const char* WriteConflictRangeFunc::name = "WRITE_CONFLICT_RANGE"; @@ -1564,17 +1504,17 @@ REGISTER_INSTRUCTION_FUNC(WriteConflictRangeFunc); struct AtomicOPFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { - state std::vector items = data->stack.pop(3); + static Future call(Reference data, Reference instruction) { + std::vector items = data->stack.pop(3); if (items.size() != 3) - return Void(); + co_return; - Standalone s1 = wait(items[0].value); - state Standalone op = Tuple::unpack(s1).getString(0); - Standalone s2 = wait(items[1].value); - state Standalone key = Tuple::unpack(s2).getString(0); - Standalone s3 = wait(items[2].value); - state Standalone value = Tuple::unpack(s3).getString(0); + Standalone s1 = co_await items[0].value; + Standalone op = Tuple::unpack(s1).getString(0); + Standalone s2 = co_await items[1].value; + Standalone key = Tuple::unpack(s2).getString(0); + Standalone s3 = co_await items[2].value; + Standalone value = Tuple::unpack(s3).getString(0); ASSERT(optionInfo.find(op.toString()) != optionInfo.end()); @@ -1595,10 +1535,8 @@ struct AtomicOPFunc : InstructionFunc { if (instruction->isDatabase) { data->stack.push(waitForVoid(mutation)); } else { - wait(mutation); + co_await mutation; } - - return Void(); } }; const char* AtomicOPFunc::name = "ATOMIC_OP"; @@ -1608,12 +1546,12 @@ REGISTER_INSTRUCTION_FUNC(AtomicOPFunc); struct UnitTestsFunc : InstructionFunc { static const char* name; - ACTOR static Future call(Reference data, Reference instruction) { + static Future call(Reference data, Reference instruction) { ASSERT(data->api->evaluatePredicate(FDBErrorPredicate::FDB_ERROR_PREDICATE_RETRYABLE, Error(1020))); ASSERT(!data->api->evaluatePredicate(FDBErrorPredicate::FDB_ERROR_PREDICATE_RETRYABLE, Error(10))); ASSERT(API::isAPIVersionSelected()); - state API* fdb = API::getInstance(); + API* fdb = API::getInstance(); ASSERT(fdb->getAPIVersion() <= FDB_API_VERSION); try { API::selectAPIVersion(fdb->getAPIVersion() + 1); @@ -1664,7 +1602,7 @@ struct UnitTestsFunc : InstructionFunc { data->db->setDatabaseOption(FDBDatabaseOption::FDB_DB_OPTION_TRANSACTION_CAUSAL_READ_RISKY); data->db->setDatabaseOption(FDBDatabaseOption::FDB_DB_OPTION_TRANSACTION_INCLUDE_PORT_IN_ADDRESS); - state Reference tr = data->db->createTransaction(); + Reference tr = data->db->createTransaction(); tr->setOption(FDBTransactionOption::FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOption::FDB_TR_OPTION_PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOption::FDB_TR_OPTION_PRIORITY_BATCH); @@ -1689,42 +1627,41 @@ struct UnitTestsFunc : InstructionFunc { tr->setOption(FDBTransactionOption::FDB_TR_OPTION_INCLUDE_PORT_IN_ADDRESS); tr->setOption(FDBTransactionOption::FDB_TR_OPTION_REPORT_CONFLICTING_KEYS); - Optional> _ = wait(tr->get("\xff"_sr)); + Optional> _ = co_await tr->get("\xff"_sr); tr->cancel(); - - return Void(); } }; const char* UnitTestsFunc::name = "UNIT_TESTS"; REGISTER_INSTRUCTION_FUNC(UnitTestsFunc); -ACTOR static Future getInstructions(Reference data, StringRef prefix) { - state Reference tr = data->db->createTransaction(); +static Future getInstructions(Reference data, StringRef prefix) { + Reference tr = data->db->createTransaction(); // get test instructions - state Tuple testSpec; + Tuple testSpec; testSpec.append(prefix); - loop { + while (true) { + Error err; try { - Standalone results = wait(getRange(tr, testSpec.range())); + Standalone results = co_await getRange(tr, testSpec.range()); data->instructions = results; - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } -ACTOR static Future doInstructions(Reference data) { +static Future doInstructions(Reference data) { // printf("Total num instructions:%d\n", data->instructions.size()); - state size_t idx = 0; - for (; idx < data->instructions.size(); ++idx) { + for (size_t idx = 0; idx < data->instructions.size(); ++idx) { Tuple opTuple = Tuple::unpack(data->instructions[idx].value); - state Standalone op = opTuple.getString(0); + Standalone op = opTuple.getString(0); - state bool isDatabase = op.endsWith("_DATABASE"_sr); - state bool isSnapshot = op.endsWith("_SNAPSHOT"_sr); - state bool isDirectory = op.startsWith("DIRECTORY_"_sr); + bool isDatabase = op.endsWith("_DATABASE"_sr); + bool isSnapshot = op.endsWith("_SNAPSHOT"_sr); + bool isDirectory = op.startsWith("DIRECTORY_"_sr); try { if (LOG_INSTRUCTIONS) { @@ -1747,10 +1684,10 @@ ACTOR static Future doInstructions(Reference data) { // wait(printFlowTesterStack(&(data->stack))); // wait(debugPrintRange(instruction->tr, "\x01test_results", "")); - state Reference instruction = Reference( - new InstructionData(isDatabase, isSnapshot, data->instructions[idx].value, Reference())); + auto instruction = makeReference( + isDatabase, isSnapshot, data->instructions[idx].value, Reference()); if (isDatabase) { - state Reference tr = data->db->createTransaction(); + Reference tr = data->db->createTransaction(); instruction->tr = tr; } else { instruction->tr = trMap[data->trName]; @@ -1760,7 +1697,7 @@ ACTOR static Future doInstructions(Reference data) { ASSERT(!isDirectory || !isSnapshot); data->stack.index = idx; - wait(InstructionFunc::call(op.toString(), data, instruction)); + co_await InstructionFunc::call(op.toString(), data, instruction); } catch (Error& e) { if (LOG_ERRORS) { printf("Error: %s (%d)\n", e.name(), e.code()); @@ -1768,7 +1705,7 @@ ACTOR static Future doInstructions(Reference data) { } if (isDirectory) { - if (opsThatCreateDirectories.count(op.toString())) { + if (opsThatCreateDirectories.contains(op.toString())) { data->directoryData.directoryList.push_back(DirectoryOrSubspace()); } data->stack.pushTuple("DIRECTORY_ERROR"_sr); @@ -1778,21 +1715,20 @@ ACTOR static Future doInstructions(Reference data) { } } // printf("Total num instructions:%d\n", data->instructions.size()); - return Void(); } -ACTOR static Future runTest(Reference data, Reference db, StringRef prefix) { +static Future runTest(Reference const& data, + Reference const& db, + StringRef const& prefix) { ASSERT(data); try { data->db = db; - wait(getInstructions(data, prefix)); - wait(doInstructions(data)); - wait(waitForAll(data->subThreads)); + co_await getInstructions(data, prefix); + co_await doInstructions(data); + co_await waitForAll(data->subThreads); } catch (Error& e) { TraceEvent(SevError, "FlowTesterDataRunError").error(e); } - - return Void(); } void populateAtomicOpMap() { @@ -1823,7 +1759,7 @@ void populateOpsThatCreateDirectories() { opsThatCreateDirectories.insert("DIRECTORY_OPEN_SUBSPACE"); } -ACTOR void startTest(std::string clusterFilename, StringRef prefix, int apiVersion) { +Future startTest(Uncancellable, std::string clusterFilename, StringRef prefix, int apiVersion) { try { populateAtomicOpMap(); // FIXME: NOOOOOO! populateOpsThatCreateDirectories(); // FIXME @@ -1851,8 +1787,8 @@ ACTOR void startTest(std::string clusterFilename, StringRef prefix, int apiVersi // Connect to the default cluster/database, and create a transaction auto db = fdb->createDatabase(clusterFilename); - Reference data = Reference(new FlowTesterData(fdb)); - wait(runTest(data, db, prefix)); + auto data = makeReference(fdb); + co_await runTest(data, db, prefix); // Stopping the network returns from g_network->run() and allows // the program to terminate @@ -1867,7 +1803,7 @@ ACTOR void startTest(std::string clusterFilename, StringRef prefix, int apiVersi } } -ACTOR void _test_versionstamp() { +Future _test_versionstamp(Uncancellable) { try { g_network = newNet2(TLSConfig()); @@ -1877,20 +1813,20 @@ ACTOR void _test_versionstamp() { startThread(networkThread, fdb); auto db = fdb->createDatabase(); - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); - state Future> ftrVersion = tr->getVersionstamp(); + Future> ftrVersion = tr->getVersionstamp(); tr->atomicOp( "foo"_sr, "blahblahbl\x00\x00\x00\x00"_sr, FDBMutationType::FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE); - wait(tr->commit()); // should use retry loop + co_await tr->commit(); // should use retry loop tr->reset(); - Optional> optionalDbVersion = wait(tr->get("foo"_sr)); - state FDBStandalone dbVersion = optionalDbVersion.get(); - FDBStandalone trVersion = wait(ftrVersion); + Optional> optionalDbVersion = co_await tr->get("foo"_sr); + FDBStandalone dbVersion = optionalDbVersion.get(); + FDBStandalone trVersion = co_await ftrVersion; ASSERT(trVersion.compare(dbVersion) == 0); @@ -1932,7 +1868,7 @@ int main(int argc, char** argv) { } // start test - startTest(clusterFilename, prefix, apiVersion); + startTest(Uncancellable(), clusterFilename, prefix, apiVersion); // Run the network until someone tells us to stop g_network->run(); diff --git a/bindings/flow/tester/Tester.actor.h b/bindings/flow/tester/Tester.h similarity index 82% rename from bindings/flow/tester/Tester.actor.h rename to bindings/flow/tester/Tester.h index 96890bcfdcc..89bef41bafb 100644 --- a/bindings/flow/tester/Tester.actor.h +++ b/bindings/flow/tester/Tester.h @@ -1,5 +1,5 @@ /* - * Tester.actor.h + * Tester.h * * This source file is part of the FoundationDB open source project * @@ -18,16 +18,9 @@ * limitations under the License. */ -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDB_FLOW_TESTER_TESTER_ACTOR_G_H) -#define FDB_FLOW_TESTER_TESTER_ACTOR_G_H -#include "tester/Tester.actor.g.h" -#elif !defined(FDB_FLOW_TESTER_TESTER_ACTOR_H) -#define FDB_FLOW_TESTER_TESTER_ACTOR_H - #pragma once +#include #include #include "flow/IDispatched.h" @@ -35,7 +28,6 @@ #include "bindings/flow/IDirectory.h" #include "bindings/flow/Subspace.h" #include "bindings/flow/DirectoryLayer.h" -#include "flow/actorcompiler.h" // This must be the last #include. constexpr bool LOG_ALL = false; constexpr bool LOG_INSTRUCTIONS = LOG_ALL || false; @@ -135,9 +127,9 @@ struct DirectoryOrSubspace { Optional subspace; DirectoryOrSubspace() {} - DirectoryOrSubspace(Reference directory) : directory(directory) {} - DirectoryOrSubspace(FDB::Subspace* subspace) : subspace(subspace) {} - DirectoryOrSubspace(Reference dirSubspace) + explicit DirectoryOrSubspace(Reference directory) : directory(directory) {} + explicit DirectoryOrSubspace(FDB::Subspace* subspace) : subspace(subspace) {} + explicit DirectoryOrSubspace(Reference dirSubspace) : directory(dirSubspace), subspace(dirSubspace.getPtr()) {} bool valid() { return directory.present() || subspace.present(); } @@ -173,7 +165,7 @@ struct DirectoryTesterData { } DirectoryTesterData() : directoryListIndex(0), directoryErrorIndex(0) { - directoryList.push_back(Reference(new FDB::DirectoryLayer())); + directoryList.push_back(DirectoryOrSubspace(Reference(makeReference()))); } template @@ -204,29 +196,43 @@ struct FlowTesterData : public ReferenceCounted { instruction->instruction.toString(), Reference::addRef(this), instruction); } - FlowTesterData(FDB::API* api) { this->api = api; } + explicit FlowTesterData(FDB::API* api) { this->api = api; } }; std::string tupleToString(FDB::Tuple const& tuple); -ACTOR template -Future()().getValue())> executeMutation(Reference instruction, F func) { - loop { +template +using ExecuteMutationResult = decltype(std::declval()().getValue()); + +template +Future> executeMutation(Reference instruction, F func) { + while (true) { + Error err; + bool hasErr = false; try { - state decltype(std::declval()().getValue()) result = wait(func()); - if (instruction->isDatabase) { - wait(instruction->tr->commit()); + if constexpr (std::is_same_v, Void>) { + co_await func(); + if (instruction->isDatabase) { + co_await instruction->tr->commit(); + } + co_return; + } else { + ExecuteMutationResult result = co_await func(); + if (instruction->isDatabase) { + co_await instruction->tr->commit(); + } + co_return result; } - return result; } catch (Error& e) { + err = e; + hasErr = true; + } + if (hasErr) { if (instruction->isDatabase) { - wait(instruction->tr->onError(e)); + co_await instruction->tr->onError(err); } else { - throw; + throw err; } } } } - -#include "flow/unactorcompiler.h" -#endif diff --git a/bindings/go/CMakeLists.txt b/bindings/go/CMakeLists.txt index cae3ca3dc03..d4670e94967 100644 --- a/bindings/go/CMakeLists.txt +++ b/bindings/go/CMakeLists.txt @@ -22,7 +22,6 @@ set(SRCS src/fdb/directory/directory_subspace.go src/fdb/fdb_test.go src/fdb/snapshot.go - go.mod) set(GOPATH ${CMAKE_CURRENT_BINARY_DIR}) @@ -43,6 +42,7 @@ set(GO_PACKAGE_OUTDIR ${GOPATH}/pkg/${GOPLATFORM}/${GO_IMPORT_PATH}) file(MAKE_DIRECTORY ${GOPATH} ${GO_DEST}) set(go_options_file ${GO_DEST}/src/fdb/generated.go) +set(go_error_file ${GO_DEST}/src/fdb/error_codes_generated.go) set(go_env GOPATH=${GOPATH} CGO_CFLAGS="-I${CMAKE_BINARY_DIR}/bindings/c/foundationdb;-I${CMAKE_SOURCE_DIR}/bindings/c" @@ -62,14 +62,23 @@ foreach(src_file IN LISTS SRCS) endforeach() add_custom_target(copy_go_sources DEPENDS ${SRCS_OUT}) add_custom_command(OUTPUT ${go_options_file} - COMMAND ${GO_EXECUTABLE} run ${CMAKE_CURRENT_SOURCE_DIR}/src/_util/translate_fdb_options.go + COMMAND ${GO_EXECUTABLE} run ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/translate_fdb_options.go -in ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options -out ${go_options_file} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/_util/translate_fdb_options.go + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/translate_fdb_options.go ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options COMMENT "Generate FDBOptions for GO") add_custom_target(go_options_file DEPENDS ${go_options_file}) add_dependencies(go_options_file copy_go_sources) +add_custom_command(OUTPUT ${go_error_file} + COMMAND ${GO_EXECUTABLE} run ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/gen_errors/main.go + -in ${CMAKE_SOURCE_DIR}/flow/include/flow/error_definitions.h + -out ${go_error_file} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/gen_errors/main.go + ${CMAKE_SOURCE_DIR}/flow/include/flow/error_definitions.h + COMMENT "Generate FDB errors for GO") +add_custom_target(go_error_file DEPENDS ${go_error_file}) +add_dependencies(go_error_file copy_go_sources) function(build_go_package) set(options LIBRARY EXECUTABLE INCLUDE_TEST) @@ -128,7 +137,7 @@ function(build_go_package) endfunction() build_go_package(LIBRARY NAME fdb_go PATH fdb INCLUDE_TEST) -add_dependencies(fdb_go fdb_c go_options_file) +add_dependencies(fdb_go fdb_c go_options_file go_error_file) build_go_package(LIBRARY NAME tuple_go PATH fdb/tuple INCLUDE_TEST) add_dependencies(tuple_go fdb_go) @@ -149,3 +158,23 @@ add_test( NAME update_bindings_go_src_fdb_generated_go COMMAND ${CMAKE_COMMAND} -E compare_files ${go_options_file} ${CMAKE_CURRENT_SOURCE_DIR}/src/fdb/generated.go ) + +# If this fails, then you need to update bindings/go/src/fdb/error_codes_generated.go +# Ideally this wouldn't be necessary, but it looks like we distribute the go +# bindings directly from the source on github. +add_test( + NAME update_generated_errors + COMMAND ${CMAKE_COMMAND} -E compare_files ${go_error_file} ${CMAKE_CURRENT_SOURCE_DIR}/src/fdb/error_codes_generated.go +) + +# gofmt should be installed with go, but it's better to check if the binary is available. +find_program(GOFMT_EXECUTABLE gofmt HINTS /usr/local/go/bin/) +if(GOFMT_EXECUTABLE) + add_test( + NAME fdb-go-fmt + # gofmt -d doesn't return a non-zero exit code, so we have to check if the output is empty. + COMMAND bash -c "out=$(\"${GOFMT_EXECUTABLE}\" -d \"${CMAKE_CURRENT_SOURCE_DIR}\"); test -z \"$out\" || { echo \"$out\"; exit 1; }" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +else() + message(WARNING "gofmt not found. The 'fdb-go-fmt' test will not be available.") +endif() diff --git a/bindings/go/README.md b/bindings/go/README.md index 884b5f1ffae..13e35307c04 100644 --- a/bindings/go/README.md +++ b/bindings/go/README.md @@ -45,3 +45,13 @@ make fdb_go ``` This will create binary packages for the appropriate platform within the "build" subdirectory of this folder. + +Generating options file +---------- + +The [generated.go](./src/fdb/generated.go) is generated based on the [fdb.options](../../fdbclient/vexillographer/fdb.options) file. +If you change the `fdb.options` file you can update the `generated.go` by running the following command from the root of this reporsitory: + +```bash +go run bindings/go/src/_util/translate_fdb_options.go < fdbclient/vexillographer/fdb.options > bindings/go/src/fdb/generated.go +``` diff --git a/bindings/go/src/fdb/database.go b/bindings/go/src/fdb/database.go index 610d51e9db3..47e8cdc21f4 100644 --- a/bindings/go/src/fdb/database.go +++ b/bindings/go/src/fdb/database.go @@ -115,6 +115,9 @@ func (d Database) CreateTransaction() (Transaction, error) { // process address is the form of IP:Port pair without the :tls suffix if the cluster is running // with TLS enabled. The address can also be multiple processes addresses concated by a comma, e.g. // "IP1:Port,IP2:port", in this case the RebootWorker will reboot all provided addresses concurrently. +// It's not recommened to close the Database directly after calling the RebootWorker method as the +// reboot commands will be queue and executed asynchronous. If the Database context is closed immediately +// some of the commands might not be delievered, even if the RebootWorker method returned successfully. func (d Database) RebootWorker(address string, checkFile bool, suspendDuration int) error { t := &futureInt64{ future: newFutureWithDb( @@ -144,7 +147,7 @@ func (d Database) RebootWorker(address string, checkFile bool, suspendDuration i // NOTE: ErrMultiVersionClientUnavailable will be returned if the Multi-Version client API was not enabled. func (d Database) GetClientStatus() ([]byte, error) { if apiVersion == 0 { - return nil, errAPIVersionUnset + return nil, ErrAPIVersionUnset } st := &futureByteSlice{ diff --git a/bindings/go/src/fdb/doc.go b/bindings/go/src/fdb/doc.go index 2ca154cea75..6601573df31 100644 --- a/bindings/go/src/fdb/doc.go +++ b/bindings/go/src/fdb/doc.go @@ -39,31 +39,33 @@ A basic interaction with the FoundationDB API is demonstrated below: package main import ( - "github.com/apple/foundationdb/bindings/go/src/fdb" - "log" - "fmt" + + "github.com/apple/foundationdb/bindings/go/src/fdb" + "log" + "fmt" + ) -func main() { - // Different API versions may expose different runtime behaviors. - fdb.MustAPIVersion(800) - - // Open the default database from the system cluster - db := fdb.MustOpenDefault() - - // Database reads and writes happen inside transactions - ret, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) { - tr.Set(fdb.Key("hello"), []byte("world")) - return tr.Get(fdb.Key("foo")).MustGet(), nil - // db.Transact automatically commits (and if necessary, - // retries) the transaction - }) - if err != nil { - log.Fatalf("Unable to perform FDB transaction (%v)", err) - } - - fmt.Printf("hello is now world, foo was: %s\n", string(ret.([]byte))) -} + func main() { + // Different API versions may expose different runtime behaviors. + fdb.MustAPIVersion(800) + + // Open the default database from the system cluster + db := fdb.MustOpenDefault() + + // Database reads and writes happen inside transactions + ret, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) { + tr.Set(fdb.Key("hello"), []byte("world")) + return tr.Get(fdb.Key("foo")).MustGet(), nil + // db.Transact automatically commits (and if necessary, + // retries) the transaction + }) + if err != nil { + log.Fatalf("Unable to perform FDB transaction (%v)", err) + } + + fmt.Printf("hello is now world, foo was: %s\n", string(ret.([]byte))) + } # Futures diff --git a/bindings/go/src/fdb/error_codes_generated.go b/bindings/go/src/fdb/error_codes_generated.go new file mode 100644 index 00000000000..69546fe6178 --- /dev/null +++ b/bindings/go/src/fdb/error_codes_generated.go @@ -0,0 +1,677 @@ +// Code generated by gen_errors. DO NOT EDIT. + +/* + * error_codes_generated.go + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fdb + +// To regenerate this file run this command: +// go run ./bindings/go/src/internal/gen_errors/main.go -in ./flow/include/flow/error_definitions.h -out ./bindings/go/src/fdb/error_codes_generated.go +// +// Error sentinel variables derived from flow/include/flow/error_definitions.h. +// Use with errors.Is to match a specific FoundationDB error code: +// +// errors.Is(err, fdb.ErrTransactionTooOld) +var ( + // Success + ErrSuccess = Error{Code: 0} + // End of stream + ErrEndOfStream = Error{Code: 1} + // Operation failed + ErrOperationFailed = Error{Code: 1000} + // Shard is not available from this server + ErrWrongShardServer = Error{Code: 1001} + // Operation result no longer necessary + ErrOperationObsolete = Error{Code: 1002} + // Cache server is not warm for this range + ErrColdCacheServer = Error{Code: 1003} + // Operation timed out + ErrTimedOut = Error{Code: 1004} + // Conflict occurred while changing coordination information + ErrCoordinatedStateConflict = Error{Code: 1005} + // All alternatives failed + ErrAllAlternativesFailed = Error{Code: 1006} + // Transaction is too old to perform reads or be committed + ErrTransactionTooOld = Error{Code: 1007} + // Not enough physical servers available + ErrNoMoreServers = Error{Code: 1008} + // Request for future version + ErrFutureVersion = Error{Code: 1009} + // Conflicting attempts to change data distribution + ErrMovekeysConflict = Error{Code: 1010} + // TLog stopped + ErrTLogStopped = Error{Code: 1011} + // Server request queue is full + ErrServerRequestQueueFull = Error{Code: 1012} + // Transaction not committed due to conflict with another transaction + ErrNotCommitted = Error{Code: 1020} + // Transaction may or may not have committed + ErrCommitUnknownResult = Error{Code: 1021} + // Idempotency id for transaction may have expired, so the commit status of the transaction cannot be determined + ErrCommitUnknownResultFatal = Error{Code: 1022} + // Operation aborted because the transaction was cancelled + ErrTransactionCancelled = Error{Code: 1025} + // Network connection failed + ErrConnectionFailed = Error{Code: 1026} + // Coordination servers have changed + ErrCoordinatorsChanged = Error{Code: 1027} + // New coordination servers did not respond in a timely way + ErrNewCoordinatorsTimedOut = Error{Code: 1028} + // Watch cancelled because storage server watch limit exceeded + ErrWatchCancelled = Error{Code: 1029} + // Request may or may not have been delivered + ErrRequestMaybeDelivered = Error{Code: 1030} + // Operation aborted because the transaction timed out + ErrTransactionTimedOut = Error{Code: 1031} + // Too many watches currently set + ErrTooManyWatches = Error{Code: 1032} + // Locality information not available + ErrLocalityInformationUnavailable = Error{Code: 1033} + // Watches cannot be set if read your writes is disabled + ErrWatchesDisabled = Error{Code: 1034} + // Default error for an ErrorOr object + ErrDefaultErrorOr = Error{Code: 1035} + // Read or wrote an unreadable key + ErrAccessedUnreadable = Error{Code: 1036} + // Storage process does not have recent mutations + ErrProcessBehind = Error{Code: 1037} + // Database is locked + ErrDatabaseLocked = Error{Code: 1038} + // The protocol version of the cluster has changed + ErrClusterVersionChanged = Error{Code: 1039} + // External client has already been loaded + ErrExternalClientAlreadyLoaded = Error{Code: 1040} + // DNS lookup failed + ErrLookupFailed = Error{Code: 1041} + // CommitProxy commit memory limit exceeded + ErrCommitProxyMemoryLimitExceeded = Error{Code: 1042} + // Operation no longer supported due to shutdown + ErrShutdownInProgress = Error{Code: 1043} + // Failed to deserialize an object + ErrSerializationFailed = Error{Code: 1044} + // No peer references for connection + ErrConnectionUnreferenced = Error{Code: 1048} + // Connection closed after idle timeout + ErrConnectionIdle = Error{Code: 1049} + // The disk queue adapter reset + ErrDiskAdapterReset = Error{Code: 1050} + // Batch GRV request rate limit exceeded + ErrBatchTransactionThrottled = Error{Code: 1051} + // Data distribution components cancelled + ErrDdCancelled = Error{Code: 1052} + // Data distributor not found + ErrDdNotFound = Error{Code: 1053} + // Connection file mismatch + ErrWrongConnectionFile = Error{Code: 1054} + // The requested changes have been compacted away + ErrVersionAlreadyCompacted = Error{Code: 1055} + // Local configuration file has changed. Restart and apply these changes + ErrLocalConfigChanged = Error{Code: 1056} + // Format version not supported + ErrUnsupportedFormatVersion = Error{Code: 1058} + // Change feed not found + ErrUnknownChangeFeed = Error{Code: 1059} + // Change feed not registered + ErrChangeFeedNotRegistered = Error{Code: 1060} + // Change feed was cancelled + ErrChangeFeedCancelled = Error{Code: 1062} + // Tried to read a version older than what has been popped from the change feed + ErrChangeFeedPopped = Error{Code: 1066} + // Page header does not match location on disk + ErrPageHeaderWrongPageID = Error{Code: 1068} + // Page header checksum failed + ErrPageHeaderChecksumFailed = Error{Code: 1069} + // Page header version is not supported + ErrPageHeaderVersionNotSupported = Error{Code: 1070} + // Page encoding type is not supported or not valid + ErrPageEncodingNotSupported = Error{Code: 1071} + // Page content decoding failed + ErrPageDecodingFailed = Error{Code: 1072} + // Page content decoding failed + ErrUnexpectedEncodingType = Error{Code: 1073} + // Encryption key not found + ErrEncryptionKeyNotFound = Error{Code: 1074} + // Data move was cancelled + ErrDataMoveCancelled = Error{Code: 1075} + // Dest team was not found for data move + ErrDataMoveDestTeamNotFound = Error{Code: 1076} + // GetReadVersion proxy memory limit exceeded + ErrGRVProxyMemoryLimitExceeded = Error{Code: 1078} + // Too many feed streams to a single storage server + ErrStorageTooManyFeedStreams = Error{Code: 1080} + // Storage engine was never successfully initialized. + ErrStorageEngineNotInitialized = Error{Code: 1081} + // Storage engine type is not recognized. + ErrUnknownStorageEngine = Error{Code: 1082} + // A duplicate snapshot request has been sent, the old request is discarded. + ErrDuplicateSnapshotRequest = Error{Code: 1083} + // DataDistribution configuration changed. + ErrDdConfigChanged = Error{Code: 1084} + // Consistency check urgent task is failed + ErrConsistencyCheckUrgentTaskFailed = Error{Code: 1085} + // Data move conflict in SS + ErrDataMoveConflict = Error{Code: 1086} + // Consistency check urgent got a duplicate request + ErrConsistencyCheckUrgentDuplicateRequest = Error{Code: 1087} + // Broken promise + ErrBrokenPromise = Error{Code: 1100} + // Asynchronous operation cancelled + ErrOperationCancelled = Error{Code: 1101} + // Future has been released + ErrFutureReleased = Error{Code: 1102} + // Connection object leaked + ErrConnectionLeaked = Error{Code: 1103} + // Never reply to the request + ErrNeverReply = Error{Code: 1104} + // Retry operation + ErrRetry = Error{Code: 1105} + // Recruitment of a server failed + ErrRecruitmentFailed = Error{Code: 1200} + // Attempt to move keys to a storage server that was removed + ErrMoveToRemovedServer = Error{Code: 1201} + // Normal worker shut down + ErrWorkerRemoved = Error{Code: 1202} + // Cluster recovery failed + ErrClusterRecoveryFailed = Error{Code: 1203} + // Master hit maximum number of versions in flight + ErrMasterMaxVersionsInFlight = Error{Code: 1204} + // Cluster recovery terminating because a TLog failed + ErrTLogFailed = Error{Code: 1205} + // Recovery of a worker process failed + ErrWorkerRecoveryFailed = Error{Code: 1206} + // Reboot of server process requested + ErrPleaseReboot = Error{Code: 1207} + // Reboot of server process requested, with deletion of state + ErrPleaseRebootDelete = Error{Code: 1208} + // Master terminating because a CommitProxy failed + ErrCommitProxyFailed = Error{Code: 1209} + // Cluster recovery terminating because a Resolver failed + ErrResolverFailed = Error{Code: 1210} + // Server is under too much load and cannot respond + ErrServerOverloaded = Error{Code: 1211} + // Cluster recovery terminating because a backup worker failed + ErrBackupWorkerFailed = Error{Code: 1212} + // Transaction tag is being throttled + ErrTagThrottled = Error{Code: 1213} + // Cluster recovery terminating because a GRVProxy failed + ErrGRVProxyFailed = Error{Code: 1214} + // The data distribution tracker has been cancelled + ErrDdTrackerCancelled = Error{Code: 1215} + // Process has failed to make sufficient progress + ErrFailedToProgress = Error{Code: 1216} + // Attempted to join cluster with a different cluster ID + ErrInvalidClusterID = Error{Code: 1217} + // Restart cluster controller process + ErrRestartClusterController = Error{Code: 1218} + // Need to reboot the storage engine + ErrPleaseRebootKVStore = Error{Code: 1219} + // Current software does not support database format + ErrIncompatibleSoftwareVersion = Error{Code: 1220} + // Validate storage consistency operation failed + ErrAuditStorageFailed = Error{Code: 1221} + // Exceeded the max number of allowed concurrent audit storage requests + ErrAuditStorageExceededRequestLimit = Error{Code: 1222} + // Exceeded maximum time allowed to read or write. + ErrKeyValueStoreDeadlineExceeded = Error{Code: 1224} + // Found data corruption + ErrAuditStorageError = Error{Code: 1226} + // Cluster recovery terminating because master has failed + ErrMasterFailed = Error{Code: 1227} + // Test failed + ErrTestFailed = Error{Code: 1228} + // Need background datamove cleanup + ErrRetryCleanUpDatamoveTombstoneAdded = Error{Code: 1229} + // Persist new audit metadata error + ErrPersistNewAuditMetadataError = Error{Code: 1230} + // Failed to cancel an audit + ErrCancelAuditStorageFailed = Error{Code: 1231} + // Audit has been cancelled + ErrAuditStorageCancelled = Error{Code: 1232} + // Found location metadata corruption + ErrLocationMetadataCorruption = Error{Code: 1233} + // Audit task is scheduled by an outdated DD + ErrAuditStorageTaskOutdated = Error{Code: 1234} + // Transaction throttled due to hot shard + ErrTransactionThrottledHotShard = Error{Code: 1235} + // Storage replicas not consistent + ErrStorageReplicaComparisonError = Error{Code: 1236} + // Storage replica cannot be reached + ErrUnreachableStorageReplica = Error{Code: 1237} + // Bulk loading task failed + ErrBulkloadTaskFailed = Error{Code: 1238} + // Bulk loading task outdated + ErrBulkloadTaskOutdated = Error{Code: 1239} + // Lock range failed + ErrRangeLockFailed = Error{Code: 1241} + // Transaction rejected due to range lock + ErrTransactionRejectedRangeLocked = Error{Code: 1242} + // Bulk dumping task failed + ErrBulkdumpTaskFailed = Error{Code: 1243} + // Bulk dumping task outdated + ErrBulkdumpTaskOutdated = Error{Code: 1244} + // Bulkload fileset provides invalid filepath + ErrBulkloadFilesetInvalidFilepath = Error{Code: 1245} + // Bulkload manifest string is failed to decode + ErrBulkloadManifestDecodeError = Error{Code: 1246} + // Range lock is rejected + ErrRangeLockReject = Error{Code: 1247} + // Range unlock is rejected + ErrRangeUnlockReject = Error{Code: 1248} + // Bulkload dataset does not cover the required range + ErrBulkloadDatasetNotCoverRequiredRange = Error{Code: 1249} + // BulkLoad requires cluster configuration with both shard_encode_location_metadata=1 and enable_read_lock_on_range=1 + ErrBulkloadInvalidConfiguration = Error{Code: 1250} + // GRV request rejected because estimated queue wait exceeds transaction limit + ErrTransactionGRVQueueRejected = Error{Code: 1251} + // finishMoveKeys exceeded retry limit + ErrFinishMoveKeysTooManyRetries = Error{Code: 1252} + // Platform error + ErrPlatformError = Error{Code: 1500} + // Large block allocation failed + ErrLargeAllocFailed = Error{Code: 1501} + // QueryPerformanceCounter error + ErrPerformanceCounterError = Error{Code: 1502} + // Null allocator was used to allocate memory + ErrBadAllocator = Error{Code: 1503} + // Disk i/o operation failed + ErrIOError = Error{Code: 1510} + // File not found + ErrFileNotFound = Error{Code: 1511} + // Unable to bind to network + ErrBindFailed = Error{Code: 1512} + // File could not be read + ErrFileNotReadable = Error{Code: 1513} + // File could not be written + ErrFileNotWritable = Error{Code: 1514} + // No cluster file found in current directory or default location + ErrNoClusterFileFound = Error{Code: 1515} + // File too large to be read + ErrFileTooLarge = Error{Code: 1516} + // Non sequential file operation not allowed + ErrNonSequentialOp = Error{Code: 1517} + // HTTP response was badly formed + ErrHTTPBadResponse = Error{Code: 1518} + // HTTP request not accepted + ErrHTTPNotAccepted = Error{Code: 1519} + // A data checksum failed + ErrChecksumFailed = Error{Code: 1520} + // A disk IO operation failed to complete in a timely manner + ErrIOTimeout = Error{Code: 1521} + // A structurally corrupt data file was detected + ErrFileCorrupt = Error{Code: 1522} + // HTTP response code not received or indicated failure + ErrHTTPRequestFailed = Error{Code: 1523} + // HTTP request failed due to bad credentials + ErrHTTPAuthFailed = Error{Code: 1524} + // HTTP response contained an unexpected X-Request-ID header + ErrHTTPBadRequestID = Error{Code: 1525} + // Invalid REST URI + ErrRestInvalidURI = Error{Code: 1526} + // Invalid RESTClient knob + ErrRestInvalidRestClientKnob = Error{Code: 1527} + // ConnectKey not found in connection pool + ErrRestConnectpoolKeyNotFound = Error{Code: 1528} + // Unable to lock the file + ErrLockFileFailure = Error{Code: 1529} + // Unsupported REST protocol + ErrRestUnsupportedProtocol = Error{Code: 1530} + // Malformed REST response + ErrRestMalformedResponse = Error{Code: 1531} + // Max BaseCipher length violation + ErrRestMaxBaseCipherLen = Error{Code: 1532} + // Requested resource was not found + ErrResourceNotFound = Error{Code: 1533} + // Invalid API call + ErrClientInvalidOperation = Error{Code: 2000} + // Commit with incomplete read + ErrCommitReadIncomplete = Error{Code: 2002} + // Invalid test specification + ErrTestSpecificationInvalid = Error{Code: 2003} + // Key outside legal range + ErrKeyOutsideLegalRange = Error{Code: 2004} + // Range begin key larger than end key + ErrInvertedRange = Error{Code: 2005} + // Option set with an invalid value + ErrInvalidOptionValue = Error{Code: 2006} + // Option not valid in this context + ErrInvalidOption = Error{Code: 2007} + // Action not possible before the network is configured + ErrNetworkNotSetup = Error{Code: 2008} + // Network can be configured only once + ErrNetworkAlreadySetup = Error{Code: 2009} + // Transaction already has a read version set + ErrReadVersionAlreadySet = Error{Code: 2010} + // Version not valid + ErrVersionInvalid = Error{Code: 2011} + // Range limits not valid + ErrRangeLimitsInvalid = Error{Code: 2012} + // Database name must be 'DB' + ErrInvalidDatabaseName = Error{Code: 2013} + // Attribute not found + ErrAttributeNotFound = Error{Code: 2014} + // Future not ready + ErrFutureNotSet = Error{Code: 2015} + // Future not an error + ErrFutureNotError = Error{Code: 2016} + // Operation issued while a commit was outstanding + ErrUsedDuringCommit = Error{Code: 2017} + // Unrecognized atomic mutation type + ErrInvalidMutationType = Error{Code: 2018} + // Attribute too large for type int + ErrAttributeTooLarge = Error{Code: 2019} + // Transaction does not have a valid commit version + ErrTransactionInvalidVersion = Error{Code: 2020} + // Transaction is read-only and therefore does not have a commit version + ErrNoCommitVersion = Error{Code: 2021} + // Environment variable network option could not be set + ErrEnvironmentVariableNetworkOptionFailed = Error{Code: 2022} + // Attempted to commit a transaction specified as read-only + ErrTransactionReadOnly = Error{Code: 2023} + // Invalid cache eviction policy, only random and lru are supported + ErrInvalidCacheEvictionPolicy = Error{Code: 2024} + // Network can only be started once + ErrNetworkCannotBeRestarted = Error{Code: 2025} + // Detected a deadlock in a callback called from the network thread + ErrBlockedFromNetworkThread = Error{Code: 2026} + // The index in K[] or V[] is not a valid number or out of range + ErrMapperBadIndex = Error{Code: 2030} + // A mapped key is not set in database + ErrMapperNoSuchKey = Error{Code: 2031} + // One of the mapped range queries is too large + ErrQuickGetKeyValuesHasMore = Error{Code: 2033} + // Found a mapped key that is not served in the same SS + ErrQuickGetValueMiss = Error{Code: 2034} + // Found a mapped range that is not served in the same SS + ErrQuickGetKeyValuesMiss = Error{Code: 2035} + // getMappedRange does not support continuation for now + ErrGetMappedKeyValuesHasMore = Error{Code: 2038} + // getMappedRange tries to read data that were previously written in the transaction + ErrGetMappedRangeReadsYourWrites = Error{Code: 2039} + // Checkpoint not found + ErrCheckpointNotFound = Error{Code: 2040} + // The key cannot be parsed as a tuple + ErrKeyNotTuple = Error{Code: 2041} + // The value cannot be parsed as a tuple + ErrValueNotTuple = Error{Code: 2042} + // The mapper cannot be parsed as a tuple + ErrMapperNotTuple = Error{Code: 2043} + // Invalid checkpoint format + ErrInvalidCheckpointFormat = Error{Code: 2044} + // Failed to create a checkpoint + ErrFailedToCreateCheckpoint = Error{Code: 2046} + // Failed to restore a checkpoint + ErrFailedToRestoreCheckpoint = Error{Code: 2047} + // Failed to dump shard metadata for a checkpoint to a sst file + ErrFailedToCreateCheckpointShardMetadata = Error{Code: 2048} + // Failed to parse address + ErrAddressParseError = Error{Code: 2049} + // Incompatible protocol version + ErrIncompatibleProtocolVersion = Error{Code: 2100} + // Transaction exceeds byte limit + ErrTransactionTooLarge = Error{Code: 2101} + // Key length exceeds limit + ErrKeyTooLarge = Error{Code: 2102} + // Value length exceeds limit + ErrValueTooLarge = Error{Code: 2103} + // Connection string invalid + ErrConnectionStringInvalid = Error{Code: 2104} + // Local address in use + ErrAddressInUse = Error{Code: 2105} + // Invalid local address + ErrInvalidLocalAddress = Error{Code: 2106} + // TLS error + ErrTLSError = Error{Code: 2107} + // Operation is not supported + ErrUnsupportedOperation = Error{Code: 2108} + // Too many tags set on transaction + ErrTooManyTags = Error{Code: 2109} + // Tag set on transaction is too long + ErrTagTooLong = Error{Code: 2110} + // Too many tag throttles have been created + ErrTooManyTagThrottles = Error{Code: 2111} + // Special key space range read crosses modules. Refer to the `special_key_space_relaxed' transaction option for more details. + ErrSpecialKeysCrossModuleRead = Error{Code: 2112} + // Special key space range read does not intersect a module. Refer to the `special_key_space_relaxed' transaction option for more details. + ErrSpecialKeysNoModuleFound = Error{Code: 2113} + // Special Key space is not allowed to write by default. Refer to the `special_key_space_enable_writes` transaction option for more details. + ErrSpecialKeysWriteDisabled = Error{Code: 2114} + // Special key space key or keyrange in set or clear does not intersect a module + ErrSpecialKeysNoWriteModuleFound = Error{Code: 2115} + // Special key space clear crosses modules + ErrSpecialKeysCrossModuleClear = Error{Code: 2116} + // Api call through special keys failed. For more information, call get on special key 0xff0xff/error_message to get a json string of the error message. + ErrSpecialKeysAPIFailure = Error{Code: 2117} + // Invalid client library metadata. + ErrClientLibInvalidMetadata = Error{Code: 2118} + // Client library with same identifier already exists on the cluster. + ErrClientLibAlreadyExists = Error{Code: 2119} + // Client library for the given identifier not found. + ErrClientLibNotFound = Error{Code: 2120} + // Client library exists, but is not available for download. + ErrClientLibNotAvailable = Error{Code: 2121} + // Invalid client library binary. + ErrClientLibInvalidBinary = Error{Code: 2122} + // No external client library provided. + ErrNoExternalClientProvided = Error{Code: 2123} + // All external clients have failed. + ErrAllExternalClientsFailed = Error{Code: 2124} + // None of the available clients match the protocol version of the cluster. + ErrIncompatibleClient = Error{Code: 2125} + // API version is not set + ErrAPIVersionUnset = Error{Code: 2200} + // API version may be set only once + ErrAPIVersionAlreadySet = Error{Code: 2201} + // API version not valid + ErrAPIVersionInvalid = Error{Code: 2202} + // API version not supported + ErrAPIVersionNotSupported = Error{Code: 2203} + // Failed to load a required FDB API function. + ErrAPIFunctionMissing = Error{Code: 2204} + // EXACT streaming mode requires limits, but none were given + ErrExactModeWithoutLimits = Error{Code: 2210} + // Unrecognized data type in packed tuple + ErrInvalidTupleDataType = Error{Code: 2250} + // Tuple does not have element at specified index + ErrInvalidTupleIndex = Error{Code: 2251} + // Cannot unpack key that is not in subspace + ErrKeyNotInSubspace = Error{Code: 2252} + // Cannot specify a prefix unless manual prefixes are enabled + ErrManualPrefixesNotEnabled = Error{Code: 2253} + // Cannot specify a prefix in a partition + ErrPrefixInPartition = Error{Code: 2254} + // Root directory cannot be opened + ErrCannotOpenRootDirectory = Error{Code: 2255} + // Directory already exists + ErrDirectoryAlreadyExists = Error{Code: 2256} + // Directory does not exist + ErrDirectoryDoesNotExist = Error{Code: 2257} + // Directory's parent does not exist + ErrParentDirectoryDoesNotExist = Error{Code: 2258} + // Directory has already been created with a different layer string + ErrMismatchedLayer = Error{Code: 2259} + // Invalid directory layer metadata + ErrInvalidDirectoryLayerMetadata = Error{Code: 2260} + // Directory cannot be moved between partitions + ErrCannotMoveDirectoryBetweenPartitions = Error{Code: 2261} + // Directory partition cannot be used as subspace + ErrCannotUsePartitionAsSubspace = Error{Code: 2262} + // Directory layer was created with an incompatible version + ErrIncompatibleDirectoryVersion = Error{Code: 2263} + // Database has keys stored at the prefix chosen by the automatic prefix allocator + ErrDirectoryPrefixNotEmpty = Error{Code: 2264} + // Directory layer already has a conflicting prefix + ErrDirectoryPrefixInUse = Error{Code: 2265} + // Target directory is invalid + ErrInvalidDestinationDirectory = Error{Code: 2266} + // Root directory cannot be modified + ErrCannotModifyRootDirectory = Error{Code: 2267} + // UUID is not sixteen bytes + ErrInvalidUUIDSize = Error{Code: 2268} + // Versionstamp is not exactly twelve bytes + ErrInvalidVersionstampSize = Error{Code: 2269} + // Backup error + ErrBackupError = Error{Code: 2300} + // Restore error + ErrRestoreError = Error{Code: 2301} + // Backup duplicate request + ErrBackupDuplicate = Error{Code: 2311} + // Backup unneeded request + ErrBackupUnneeded = Error{Code: 2312} + // Backup file block size too small + ErrBackupBadBlockSize = Error{Code: 2313} + // Backup Container URL invalid + ErrBackupInvalidURL = Error{Code: 2314} + // Backup Container info invalid + ErrBackupInvalidInfo = Error{Code: 2315} + // Cannot expire requested data from backup without violating minimum restorability + ErrBackupCannotExpire = Error{Code: 2316} + // Cannot find authentication details (such as a password or secret key) for the specified Backup Container URL + ErrBackupAuthMissing = Error{Code: 2317} + // Cannot read or parse one or more sources of authentication information for Backup Container URLs + ErrBackupAuthUnreadable = Error{Code: 2318} + // Backup does not exist + ErrBackupDoesNotExist = Error{Code: 2319} + // Backup before 6.3 cannot be filtered with key ranges + ErrBackupNotFilterableWithKeyRanges = Error{Code: 2320} + // Backup key ranges doesn't overlap with key ranges filter + ErrBackupNotOverlappedWithKeysFilter = Error{Code: 2321} + // bucket is not in the URL for backup + ErrBucketNotInURL = Error{Code: 2322} + // Invalid restore version + ErrRestoreInvalidVersion = Error{Code: 2361} + // Corrupted backup data + ErrRestoreCorruptedData = Error{Code: 2362} + // Missing backup data + ErrRestoreMissingData = Error{Code: 2363} + // Restore duplicate request + ErrRestoreDuplicateTag = Error{Code: 2364} + // Restore tag does not exist + ErrRestoreUnknownTag = Error{Code: 2365} + // Unknown backup/restore file type + ErrRestoreUnknownFileType = Error{Code: 2366} + // Unsupported backup file version + ErrRestoreUnsupportedFileVersion = Error{Code: 2367} + // Unexpected number of bytes read + ErrRestoreBadRead = Error{Code: 2368} + // Backup file has unexpected padding bytes + ErrRestoreCorruptedDataPadding = Error{Code: 2369} + // Attempted to restore into a non-empty destination database + ErrRestoreDestinationNotEmpty = Error{Code: 2370} + // Attempted to restore using a UID that had been used for an aborted restore + ErrRestoreDuplicateUID = Error{Code: 2371} + // Invalid task version + ErrTaskInvalidVersion = Error{Code: 2381} + // Task execution stopped due to timeout, abort, or completion by another worker + ErrTaskInterrupted = Error{Code: 2382} + // The provided encryption key file has invalid contents + ErrInvalidEncryptionKeyFile = Error{Code: 2383} + // Missing mutation logs + ErrBlobRestoreMissingLogs = Error{Code: 2384} + // Corrupted mutation logs + ErrBlobRestoreCorruptedLogs = Error{Code: 2385} + // Invalid manifest URL + ErrBlobRestoreInvalidManifestURL = Error{Code: 2386} + // Corrupted manifest + ErrBlobRestoreCorruptedManifest = Error{Code: 2387} + // Missing manifest + ErrBlobRestoreMissingManifest = Error{Code: 2388} + // Blob migrator is replaced + ErrBlobMigratorReplaced = Error{Code: 2389} + // BulkDump dataset incomplete. Use --rangefile flag + ErrRestoreBulkloadDatasetIncomplete = Error{Code: 2390} + // BulkLoad operation failed + ErrRestoreBulkloadFailed = Error{Code: 2391} + // BulkDump operation timed out + ErrBackupBulkdumpTimeout = Error{Code: 2392} + // BulkDump operation failed + ErrBackupBulkdumpFailed = Error{Code: 2393} + // Expected key is missing + ErrKeyNotFound = Error{Code: 2400} + // JSON string was malformed + ErrJSONMalformed = Error{Code: 2401} + // JSON string did not terminate where expected + ErrJSONEofExpected = Error{Code: 2402} + // Failed to disable tlog pops + ErrSnapDisableTLogPopFailed = Error{Code: 2500} + // Failed to snapshot storage nodes + ErrSnapStorageFailed = Error{Code: 2501} + // Failed to snapshot TLog nodes + ErrSnapTLogFailed = Error{Code: 2502} + // Failed to snapshot coordinator nodes + ErrSnapCoordFailed = Error{Code: 2503} + // Failed to enable tlog pops + ErrSnapEnableTLogPopFailed = Error{Code: 2504} + // Snapshot create binary path not whitelisted + ErrSnapPathNotWhitelisted = Error{Code: 2505} + // Unsupported when the cluster is not fully recovered + ErrSnapNotFullyRecoveredUnsupported = Error{Code: 2506} + // Unsupported when log anti quorum is configured + ErrSnapLogAntiQuorumUnsupported = Error{Code: 2507} + // Cluster recovery during snapshot operation not supported + ErrSnapWithRecoveryUnsupported = Error{Code: 2508} + // The given uid string is not a 32-length hex string + ErrSnapInvalidUIDString = Error{Code: 2509} + // Encryption operation error + ErrEncryptOpsError = Error{Code: 2700} + // Encryption header metadata mismatch + ErrEncryptHeaderMetadataMismatch = Error{Code: 2701} + // Expected encryption key is missing + ErrEncryptKeyNotFound = Error{Code: 2702} + // Expected encryption key TTL has expired + ErrEncryptKeyTTLExpired = Error{Code: 2703} + // Encryption header authentication token mismatch + ErrEncryptHeaderAuthtokenMismatch = Error{Code: 2704} + // Attempt to update encryption cipher key + ErrEncryptUpdateCipher = Error{Code: 2705} + // Invalid encryption cipher details + ErrEncryptInvalidID = Error{Code: 2706} + // Encryption keys fetch from external KMS failed + ErrEncryptKeysFetchFailed = Error{Code: 2707} + // Invalid encryption/kms configuration: discovery-url, validation-token, endpoint etc. + ErrEncryptInvalidKMSConfig = Error{Code: 2708} + // Encryption not supported + ErrEncryptUnsupported = Error{Code: 2709} + // Encryption mode mismatch with configuration + ErrEncryptModeMismatch = Error{Code: 2710} + // Encryption key-check-value mismatch + ErrEncryptKeyCheckValueMismatch = Error{Code: 2711} + // Max BaseCipher buffer length violation + ErrEncryptMaxBaseCipherLen = Error{Code: 2712} + // An unknown error occurred + ErrUnknownError = Error{Code: 4000} + // An internal error occurred + ErrInternalError = Error{Code: 4100} + // Not implemented yet + ErrNotImplemented = Error{Code: 4200} + // Client tried to access unauthorized data + ErrPermissionDenied = Error{Code: 6000} + // A untrusted client tried to send a message to a private endpoint + ErrUnauthorizedAttempt = Error{Code: 6001} + // Digital signature operation error + ErrDigitalSignatureOpsError = Error{Code: 6002} + // Failed to verify authorization token + ErrAuthorizationTokenVerifyFailed = Error{Code: 6003} + // Failed to decode public/private key + ErrPkeyDecodeError = Error{Code: 6004} + // Failed to encode public/private key + ErrPkeyEncodeError = Error{Code: 6005} + // gRPC Error + ErrGRPCError = Error{Code: 7000} +) diff --git a/bindings/go/src/fdb/errors.go b/bindings/go/src/fdb/errors.go index c40063f2f42..b72bc901e59 100644 --- a/bindings/go/src/fdb/errors.go +++ b/bindings/go/src/fdb/errors.go @@ -20,6 +20,8 @@ // FoundationDB Go API +//go:generate go run ./internal/gen_errors/main.go -in ../../../flow/include/flow/error_definitions.h -out fdb/error_codes_generated.go + package fdb // #define FDB_API_VERSION 800 @@ -34,10 +36,12 @@ import ( // Error may be returned by any FoundationDB API function that returns error, or // as a panic from any FoundationDB API function whose name ends with OrPanic. // -// You may compare the Code field of an Error against the list of FoundationDB -// error codes at https://apple.github.io/foundationdb/api-error-codes.html, -// but generally an Error should be passed to (Transaction).OnError. When using -// (Database).Transact, non-fatal errors will be retried automatically. +// Use errors.Is with the sentinel variables defined in error_codes_generated.go +// to check for specific error codes: +// +// errors.Is(err, fdb.ErrTransactionTooOld) +// +// When using (Database).Transact, non-fatal errors will be retried automatically. type Error struct { Code int } @@ -46,14 +50,15 @@ func (e Error) Error() string { return fmt.Sprintf("FoundationDB error code %d (%s)", e.Code, C.GoString(C.fdb_get_error(C.fdb_error_t(e.Code)))) } -// SOMEDAY: these (along with others) should be coming from fdb.options? - -var ( - errNetworkNotSetup = Error{2008} - errNetworkAlreadySetup = Error{2009} // currently unused - errNetworkCannotBeRestarted = Error{2025} // currently unused - - errAPIVersionUnset = Error{2200} - errAPIVersionAlreadySet = Error{2201} - errAPIVersionNotSupported = Error{2203} -) +// Is implements the errors.Is contract. It matches target if target is an Error +// or *Error with the same Code, enabling errors.Is(err, fdb.ErrTransactionTooOld). +func (e Error) Is(target error) bool { + switch t := target.(type) { + case Error: + return e.Code == t.Code + case *Error: + return t != nil && e.Code == t.Code + default: + return false + } +} diff --git a/bindings/go/src/fdb/errors_test.go b/bindings/go/src/fdb/errors_test.go index 15babf7f0d6..a6e054a7271 100644 --- a/bindings/go/src/fdb/errors_test.go +++ b/bindings/go/src/fdb/errors_test.go @@ -60,3 +60,30 @@ func TestErrorWrapping(t *testing.T) { } } } + +func TestErrorIs(t *testing.T) { + // value sentinel — direct match + if !errors.Is(Error{Code: 1007}, ErrTransactionTooOld) { + t.Error("expected match: Error{1007} vs ErrTransactionTooOld") + } + // pointer target (user takes address of sentinel var) + if !errors.Is(Error{Code: 1007}, &ErrTransactionTooOld) { + t.Error("expected match: Error{1007} vs &ErrTransactionTooOld") + } + // wrapped value + if !errors.Is(fmt.Errorf("wrap: %w", Error{Code: 1007}), ErrTransactionTooOld) { + t.Error("expected match through wrapping") + } + // wrapped pointer + if !errors.Is(fmt.Errorf("wrap: %w", &Error{Code: 1007}), ErrTransactionTooOld) { + t.Error("expected match through wrapped *Error") + } + // no false positive: wrong code + if errors.Is(Error{Code: 1008}, ErrTransactionTooOld) { + t.Error("expected no match: Error{1008} vs ErrTransactionTooOld") + } + // no false positive: unrelated error + if errors.Is(errors.New("other"), ErrTransactionTooOld) { + t.Error("expected no match: non-fdb error vs ErrTransactionTooOld") + } +} diff --git a/bindings/go/src/fdb/fdb.go b/bindings/go/src/fdb/fdb.go index 340a1874105..da86bc3c0aa 100644 --- a/bindings/go/src/fdb/fdb.go +++ b/bindings/go/src/fdb/fdb.go @@ -43,7 +43,7 @@ var ( // with the network thread while the network thread is no more running. ErrNetworkIsStopped = errors.New("network is stopped") - // ErrNetworkAlreadyStopped for a too early call to StopNetwork(). + // ErrNetworkNotStarted for a too early call to StopNetwork(). ErrNetworkNotStarted = errors.New("network has not been started") ) @@ -107,7 +107,7 @@ func (opt NetworkOptions) setOpt(code int, param []byte) error { defer networkMutex.Unlock() if apiVersion == 0 { - return errAPIVersionUnset + return ErrAPIVersionUnset } return setOpt(func(p *C.uint8_t, pl C.int) C.fdb_error_t { @@ -138,11 +138,11 @@ func APIVersion(version int) error { if apiVersion == version { return nil } - return errAPIVersionAlreadySet + return ErrAPIVersionAlreadySet } if version < 200 || version > headerVersion { - return errAPIVersionNotSupported + return ErrAPIVersionNotSupported } if e := C.fdb_select_api_version_impl(C.int(version), C.int(headerVersion)); e != 0 { @@ -181,7 +181,7 @@ func GetAPIVersion() (int, error) { if IsAPIVersionSelected() { return apiVersion, nil } - return 0, errAPIVersionUnset + return 0, ErrAPIVersionUnset } // MustAPIVersion is like APIVersion but panics if the API version is not @@ -266,7 +266,7 @@ func executeWithRunningNetworkThread(f func()) error { // StartNetwork does nothing, but it will ensure that the API version is set and return an error otherwise. func StartNetwork() error { if apiVersion == 0 { - return errAPIVersionUnset + return ErrAPIVersionUnset } return nil @@ -378,7 +378,7 @@ func MustOpen(clusterFile string, dbName []byte) Database { // Caller must call Close() to release resources. func createDatabase(clusterFile string) (Database, error) { if apiVersion == 0 { - return Database{}, errAPIVersionUnset + return Database{}, ErrAPIVersionUnset } var cf *C.char @@ -412,7 +412,7 @@ func createDatabase(clusterFile string) (Database, error) { // Caller must call Close() to release resources. func OpenWithConnectionString(connectionString string) (Database, error) { if apiVersion == 0 { - return Database{}, errAPIVersionUnset + return Database{}, ErrAPIVersionUnset } var cf *C.char @@ -451,11 +451,11 @@ func CreateCluster(clusterFile string) (Cluster, error) { defer networkMutex.Unlock() if apiVersion == 0 { - return Cluster{}, errAPIVersionUnset + return Cluster{}, ErrAPIVersionUnset } if !networkStarted { - return Cluster{}, errNetworkNotSetup + return Cluster{}, ErrNetworkNotSetup } if networkStopped { diff --git a/bindings/go/src/fdb/generated.go b/bindings/go/src/fdb/generated.go index c33ffc7feaf..76e39cca157 100644 --- a/bindings/go/src/fdb/generated.go +++ b/bindings/go/src/fdb/generated.go @@ -60,14 +60,14 @@ func (o NetworkOptions) SetTraceEnable(param string) error { return o.setOpt(30, []byte(param)) } -// Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. +// Sets the maximum size in bytes of a single trace output file. This value should be in the range "[0, INT64_MAX]". If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes. // // Parameter: max size of a single trace output file func (o NetworkOptions) SetTraceRollSize(param int64) error { return o.setOpt(31, int64ToBytes(param)) } -// Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. +// Sets the maximum size of all the trace output files put together. This value should be in the range "[0, INT64_MAX]". If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. // // Parameter: max total size of trace files func (o NetworkOptions) SetTraceMaxLogsSize(param int64) error { @@ -336,7 +336,7 @@ func (o NetworkOptions) SetDistributedClientTracer(param string) error { // Sets the directory for storing temporary files created by FDB client, such as temporary copies of client libraries. Defaults to /tmp // -// Parameter: Client directory for temporary files. +// Parameter: Client directory for temporary files. func (o NetworkOptions) SetClientTmpDir(param string) error { return o.setOpt(91, []byte(param)) } @@ -379,35 +379,35 @@ func (o DatabaseOptions) SetSnapshotRywDisable() error { return o.setOpt(27, nil) } -// Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the ``transaction_logging_max_field_length`` option of each transaction created by this database. See the transaction option description for more information. +// Sets the maximum escaped length of key and value fields to be logged to the trace file via the LOG_TRANSACTION option. This sets the "transaction_logging_max_field_length" option of each transaction created by this database. See the transaction option description for more information. // // Parameter: Maximum length of escaped key and value fields. func (o DatabaseOptions) SetTransactionLoggingMaxFieldLength(param int64) error { return o.setOpt(405, int64ToBytes(param)) } -// Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the ``timeout`` option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. +// Set a timeout in milliseconds which, when elapsed, will cause each transaction automatically to be cancelled. This sets the "timeout" option of each transaction created by this database. See the transaction option description for more information. Using this option requires that the API version is 610 or higher. // // Parameter: value in milliseconds of timeout func (o DatabaseOptions) SetTransactionTimeout(param int64) error { return o.setOpt(500, int64ToBytes(param)) } -// Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. +// Set a maximum number of retries after which additional calls to "onError" will throw the most recently seen error code. This sets the "retry_limit" option of each transaction created by this database. See the transaction option description for more information. // // Parameter: number of times to retry func (o DatabaseOptions) SetTransactionRetryLimit(param int64) error { return o.setOpt(501, int64ToBytes(param)) } -// Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. +// Set the maximum amount of backoff delay incurred in the call to "onError" if the error is retryable. This sets the "max_retry_delay" option of each transaction created by this database. See the transaction option description for more information. // // Parameter: value in milliseconds of maximum delay func (o DatabaseOptions) SetTransactionMaxRetryDelay(param int64) error { return o.setOpt(502, int64ToBytes(param)) } -// Set the maximum transaction size in bytes. This sets the ``size_limit`` option on each transaction created by this database. See the transaction option description for more information. +// Set the maximum transaction size in bytes. This sets the "size_limit" option on each transaction created by this database. See the transaction option description for more information. // // Parameter: value in bytes func (o DatabaseOptions) SetTransactionSizeLimit(param int64) error { @@ -429,7 +429,7 @@ func (o DatabaseOptions) SetTransactionAutomaticIdempotency() error { return o.setOpt(506, nil) } -// Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the ``bypass_unreadable`` option of each transaction created by this database. See the transaction option description for more information. +// Allows "get" operations to read from sections of keyspace that have become unreadable because of versionstamp operations. This sets the "bypass_unreadable" option of each transaction created by this database. See the transaction option description for more information. func (o DatabaseOptions) SetTransactionBypassUnreadable() error { return o.setOpt(700, nil) } @@ -597,21 +597,21 @@ func (o TransactionOptions) SetServerRequestTracing() error { return o.setOpt(406, nil) } -// Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to ``onError``. If the API version is 610 or greater, the timeout is not reset after an ``onError`` call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the ``transaction_timeout`` database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. +// Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are "[0, INT_MAX]". If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Prior to API version 610, like all other transaction options, the timeout must be reset after a call to "onError". If the API version is 610 or greater, the timeout is not reset after an "onError" call. This allows the user to specify a longer timeout on specific transactions than the default timeout specified through the "transaction_timeout" database option without the shorter database timeout cancelling transactions that encounter a retryable error. Note that at all API versions, it is safe and legal to set the timeout each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. // // Parameter: value in milliseconds of timeout func (o TransactionOptions) SetTimeout(param int64) error { return o.setOpt(500, int64ToBytes(param)) } -// Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. +// Set a maximum number of retries after which additional calls to "onError" will throw the most recently seen error code. Valid parameter values are "[-1, INT_MAX]". If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to "onError". If the API version is 610 or greater, the retry limit is not reset after an "onError" call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. // // Parameter: number of times to retry func (o TransactionOptions) SetRetryLimit(param int64) error { return o.setOpt(501, int64ToBytes(param)) } -// Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. +// Set the maximum amount of backoff delay incurred in the call to "onError" if the error is retryable. Defaults to 1000 ms. Valid parameter values are "[0, INT_MAX]". If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to "onError". If the API version is 610 or greater, the retry limit is not reset after an "onError" call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. // // Parameter: value in milliseconds of maximum delay func (o TransactionOptions) SetMaxRetryDelay(param int64) error { @@ -625,7 +625,7 @@ func (o TransactionOptions) SetSizeLimit(param int64) error { return o.setOpt(503, int64ToBytes(param)) } -// Automatically assign a random 16 byte idempotency id for this transaction. Prevents commits from failing with ``commit_unknown_result``. WARNING: If you are also using the multiversion client or transaction timeouts, if either cluster_version_changed or transaction_timed_out was thrown during a commit, then that commit may have already succeeded or may succeed in the future. This feature is in development and not ready for general use. +// Automatically assign a random 16 byte idempotency id for this transaction. Prevents commits from failing with "commit_unknown_result". WARNING: If you are also using the multiversion client or transaction timeouts, if either cluster_version_changed or transaction_timed_out was thrown during a commit, then that commit may have already succeeded or may succeed in the future. This feature is in development and not ready for general use. func (o TransactionOptions) SetAutomaticIdempotency() error { return o.setOpt(505, nil) } @@ -650,7 +650,7 @@ func (o TransactionOptions) SetUsedDuringCommitProtectionDisable() error { return o.setOpt(701, nil) } -// The transaction can read from locked databases. +// The transaction can read from locked databases. This option will make the transaction read-only if the lock_aware option is not set. func (o TransactionOptions) SetReadLockAware() error { return o.setOpt(702, nil) } @@ -689,7 +689,7 @@ func (o TransactionOptions) SetAutoThrottleTag(param string) error { return o.setOpt(801, []byte(param)) } -// Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with a 33 bytes serialized binary format which consists of: 8 bytes protocol version, e.g. ``0x0FDB00B073000000LL`` in little-endian format, 16 bytes trace id, 8 bytes span id, 1 byte set to 1 if sampling is enabled +// Adds a parent to the Span of this transaction. Used for transaction tracing. A span can be identified with a 33 bytes serialized binary format which consists of: 8 bytes protocol version, e.g. "0x0FDB00B073000000LL" in little-endian format, 16 bytes trace id, 8 bytes span id, 1 byte set to 1 if sampling is enabled // // Parameter: A serialized binary byte string of length 33 used to associate the span of this transaction with a parent func (o TransactionOptions) SetSpanParent(param []byte) error { @@ -701,7 +701,7 @@ func (o TransactionOptions) SetExpensiveClearCostEstimationEnable() error { return o.setOpt(1000, nil) } -// Allows ``get`` operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. +// Allows "get" operations to read from sections of keyspace that have become unreadable because of versionstamp operations. These reads will view versionstamp operations as if they were set operations that did not fill in the versionstamp. func (o TransactionOptions) SetBypassUnreadable() error { return o.setOpt(1100, nil) } @@ -711,6 +711,13 @@ func (o TransactionOptions) SetUseGrvCache() error { return o.setOpt(1101, nil) } +// Rejects a GRV request with "transaction_grv_queue_rejected" if the GRV proxy estimates that ratekeeper throttling will keep the request queued longer than this limit. Valid parameter values are "[0, INT_MAX]". The estimate is advisory and only accounts for time spent queued at the GRV proxy due to ratekeeper throttling. +// +// Parameter: value in milliseconds of maximum estimated GRV queue delay +func (o TransactionOptions) SetMaxGrvQueueDelay(param int64) error { + return o.setOpt(1103, int64ToBytes(param)) +} + // Attach given authorization token to the transaction such that subsequent tenant-aware requests are authorized // // Parameter: A JSON Web Token authorized to access data belonging to one or more tenants, indicated by 'tenants' claim of the token's payload. @@ -744,13 +751,13 @@ const ( // order to minimize costs if the client doesn't read the entire range), and // as the caller iterates over more items in the range larger batches will // be transferred in order to minimize latency. After enough iterations, - // the iterator mode will eventually reach the same byte limit as “WANT_ALL“ + // the iterator mode will eventually reach the same byte limit as "WANT_ALL" StreamingModeIterator StreamingMode = 0 // Infrequently used. The client has passed a specific row limit and wants // that many rows delivered in a single batch. Because of iterator operation // in client drivers make request batches transparent to the user, consider - // “WANT_ALL“ StreamingMode instead. A row limit must be specified if this + // "WANT_ALL" StreamingMode instead. A row limit must be specified if this // mode is used. StreamingModeExact StreamingMode = 1 @@ -777,7 +784,7 @@ const ( StreamingModeSerial StreamingMode = 5 ) -// Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. +// Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to "param" and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow. func (t Transaction) Add(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 2) } @@ -787,7 +794,7 @@ func (t Transaction) And(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 6) } -// Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. +// Performs a bitwise "and" operation. If the existing value in the database is not present, then "param" is stored in the database. If the existing value in the database is shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". func (t Transaction) BitAnd(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 6) } @@ -797,7 +804,7 @@ func (t Transaction) Or(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 7) } -// Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. +// Performs a bitwise "or" operation. If the existing value in the database is not present or shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". func (t Transaction) BitOr(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 7) } @@ -807,47 +814,47 @@ func (t Transaction) Xor(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 8) } -// Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. +// Performs a bitwise "xor" operation. If the existing value in the database is not present or shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". func (t Transaction) BitXor(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 8) } -// Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. +// Appends "param" to the end of the existing value already in the database at the given key (or creates the key and sets the value to "param" if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size. func (t Transaction) AppendIfFits(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 9) } -// Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database. +// Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". The larger of the two values is then stored in the database. func (t Transaction) Max(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 12) } -// Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database. +// Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then "param" is stored in the database. If the existing value in the database is shorter than "param", it is first extended to the length of "param" with zero bytes. If "param" is shorter than the existing value in the database, the existing value is truncated to match the length of "param". The smaller of the two values is then stored in the database. func (t Transaction) Min(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 13) } -// Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. +// Transforms "key" using a versionstamp for the transaction. Sets the transformed key in the database to "param". The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position "pos". The 10 bytes of the key from "pos" to "pos + 10" are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes. func (t Transaction) SetVersionstampedKey(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 14) } -// Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. +// Transforms "param" using a versionstamp for the transaction. Sets the "key" given to the transformed "param". The parameter is transformed by removing the final four bytes from "param" and reading those as a little-Endian 32-bit integer to get a position "pos". The 10 bytes of the parameter from "pos" to "pos + 10" are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java, Python, and Go bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset. func (t Transaction) SetVersionstampedValue(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 15) } -// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database. +// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then "param" is stored. Otherwise the smaller of the two values is then stored in the database. func (t Transaction) ByteMin(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 16) } -// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database. +// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then "param" is stored. Otherwise the larger of the two values is then stored in the database. func (t Transaction) ByteMax(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 17) } -// Performs an atomic ``compare and clear`` operation. If the existing value in the database is equal to the given value, then given key is cleared. +// Performs an atomic "compare and clear" operation. If the existing value in the database is equal to the given value, then given key is cleared. func (t Transaction) CompareAndClear(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, 20) } @@ -867,15 +874,15 @@ type ErrorPredicate int const ( - // Returns “true“ if the error indicates the operations in the transactions + // Returns "true" if the error indicates the operations in the transactions // should be retried because of transient error. ErrorPredicateRetryable ErrorPredicate = 50000 - // Returns “true“ if the error indicates the transaction may have succeeded, + // Returns "true" if the error indicates the transaction may have succeeded, // though not in a way the system can verify. ErrorPredicateMaybeCommitted ErrorPredicate = 50001 - // Returns “true“ if the error indicates the transaction has not committed, + // Returns "true" if the error indicates the transaction has not committed, // though in a way that can be retried. ErrorPredicateRetryableNotCommitted ErrorPredicate = 50002 ) diff --git a/bindings/go/src/internal/gen_errors/main.go b/bindings/go/src/internal/gen_errors/main.go new file mode 100644 index 00000000000..429403a52b6 --- /dev/null +++ b/bindings/go/src/internal/gen_errors/main.go @@ -0,0 +1,176 @@ +//go:build ignore +// +build ignore + +/* + * main.go + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// gen_errors reads flow/include/flow/error_definitions.h and generates a Go +// source file containing integer constants for every FDB error code. +// +// Usage: +// +// go run internal/gen_errors/main.go -in -out +package main + +import ( + "bufio" + "flag" + "fmt" + "os" + "regexp" + "strings" + "text/template" + "unicode" +) + +var errorRe = regexp.MustCompile(`^\s*ERROR\(\s*(\w+)\s*,\s*(\d+)\s*,\s*"([^"]+)"\s*\)`) + +// initialism maps lower-case words to their expected go words. Most of them will be upper-case with TLog being an exception. +var initialism = map[string]string{ + "api": "API", + "db": "DB", + "dns": "DNS", + "grpc": "GRPC", + "grv": "GRV", + "http": "HTTP", + "id": "ID", + "ids": "IDs", + "io": "IO", + "ip": "IP", + "json": "JSON", + "kms": "KMS", + "kv": "KV", + "ssl": "SSL", + "tlog": "TLog", + "tls": "TLS", + "ttl": "TTL", + "uid": "UID", + "uri": "URI", + "url": "URL", + "uuid": "UUID", +} + +type errorDef struct { + Name string // exported Go constant name, e.g. ErrCodeTransactionTooOld + Code string // numeric string, e.g. "1007" + Description string // human-readable description from the macro +} + +func toPascalCase(s string) string { + parts := strings.Split(s, "_") + var b strings.Builder + for _, p := range parts { + if len(p) == 0 { + continue + } + if upper, ok := initialism[strings.ToLower(p)]; ok { + b.WriteString(upper) + continue + } + runes := []rune(p) + b.WriteRune(unicode.ToUpper(runes[0])) + b.WriteString(string(runes[1:])) + } + return b.String() +} + +func main() { + var inFile string + var outFile string + flag.StringVar(&inFile, "in", "stdin", "Input file") + flag.StringVar(&outFile, "out", "stdout", "Output file") + flag.Parse() + + f, err := os.Open(inFile) + if err != nil { + fmt.Fprintf(os.Stderr, "open %s: %v\n", inFile, err) + os.Exit(1) + } + defer f.Close() + + var defs []errorDef + scanner := bufio.NewScanner(f) + for scanner.Scan() { + m := errorRe.FindStringSubmatch(scanner.Text()) + if m == nil { + continue + } + defs = append(defs, errorDef{ + Name: "Err" + toPascalCase(m[1]), + Code: m[2], + Description: m[3], + }) + } + if err := scanner.Err(); err != nil { + fmt.Fprintf(os.Stderr, "scan: %v\n", err) + os.Exit(1) + } + + out, err := os.Create(outFile) + if err != nil { + fmt.Fprintf(os.Stderr, "create %s: %v\n", outFile, err) + os.Exit(1) + } + defer out.Close() + + if err := tmpl.Execute(out, defs); err != nil { + fmt.Fprintf(os.Stderr, "template: %v\n", err) + os.Exit(1) + } +} + +var tmpl = template.Must(template.New("").Parse(`// Code generated by gen_errors. DO NOT EDIT. + +/* + * error_codes_generated.go + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fdb + +// To regenerate this file run this command: +// go run ./bindings/go/src/internal/gen_errors/main.go -in ./flow/include/flow/error_definitions.h -out ./bindings/go/src/fdb/error_codes_generated.go +// +// Error sentinel variables derived from flow/include/flow/error_definitions.h. +// Use with errors.Is to match a specific FoundationDB error code: +// +// errors.Is(err, fdb.ErrTransactionTooOld) +var ( +{{- range .}} + // {{.Description}} + {{.Name}} = Error{Code: {{.Code}}} +{{- end}} +) +`)) diff --git a/bindings/go/src/_util/translate_fdb_options.go b/bindings/go/src/internal/translate_fdb_options.go similarity index 92% rename from bindings/go/src/_util/translate_fdb_options.go rename to bindings/go/src/internal/translate_fdb_options.go index f62d9493aec..58b6687837c 100644 --- a/bindings/go/src/_util/translate_fdb_options.go +++ b/bindings/go/src/internal/translate_fdb_options.go @@ -42,14 +42,23 @@ type Option struct { Description string `xml:"description,attr"` Hidden bool `xml:"hidden,attr"` } + type Scope struct { Name string `xml:"name,attr"` Option []Option } + type Options struct { Scope []Scope } +// sanitize normalizes description text to be gofmt-compliant: replaces RST +// double-backtick spans with Go-style double-quoted strings and removes +// trailing whitespace. +func sanitize(s string) string { + return strings.TrimRight(strings.ReplaceAll(s, "``", "\""), " \t") +} + func writeOptString(w io.Writer, receiver string, function string, opt Option) { fmt.Fprintf(w, `func (o %s) %s(param string) error { return o.setOpt(%d, []byte(param)) @@ -84,9 +93,9 @@ func writeOpt(w io.Writer, receiver string, opt Option) { fmt.Fprintln(w) if opt.Description != "" { - fmt.Fprintf(w, "// %s\n", opt.Description) + fmt.Fprintf(w, "// %s\n", sanitize(opt.Description)) if opt.ParamDesc != "" { - fmt.Fprintf(w, "//\n// Parameter: %s\n", opt.ParamDesc) + fmt.Fprintf(w, "//\n// Parameter: %s\n", sanitize(opt.ParamDesc)) } } else { fmt.Fprintf(w, "// Not yet implemented.\n") @@ -117,14 +126,13 @@ func writeMutation(w io.Writer, opt Option) { func (t Transaction) %s(key KeyConvertible, param []byte) { t.atomicOp(key.FDBKey(), param, %d) } -`, opt.Description, tname, opt.Code) +`, sanitize(opt.Description), tname, opt.Code) } func writeEnum(w io.Writer, scope Scope, opt Option, delta int) { fmt.Fprintln(w) if opt.Description != "" { - doc.ToText(w, opt.Description, "\t// ", "", 73) - // fmt.Printf(" // %s\n", opt.Description) + doc.ToText(w, sanitize(opt.Description), "\t// ", "", 73) } fmt.Fprintf(w, " %s %s = %d\n", scope.Name+translateName(opt.Name), scope.Name, opt.Code+delta) } diff --git a/bindings/java/JavaWorkload.cpp b/bindings/java/JavaWorkload.cpp index c14d6e2ee66..a10a8d0c0bc 100644 --- a/bindings/java/JavaWorkload.cpp +++ b/bindings/java/JavaWorkload.cpp @@ -90,17 +90,17 @@ void printTrace(JNIEnv* env, jclass, jlong logger, jint severity, jstring messag } jlong getProcessID(JNIEnv* env, jclass, jlong self) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); return jlong(context->getProcessID()); } void setProcessID(JNIEnv* env, jclass, jlong self, jlong processID) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); context->setProcessID(processID); } jboolean getOptionBool(JNIEnv* env, jclass, jlong self, jstring name, jboolean defaultValue) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); jboolean isCopy = true; const char* utf = env->GetStringUTFChars(name, &isCopy); auto res = jboolean(context->getOption(utf, bool(defaultValue))); @@ -111,7 +111,7 @@ jboolean getOptionBool(JNIEnv* env, jclass, jlong self, jstring name, jboolean d } jlong getOptionLong(JNIEnv* env, jclass, jlong self, jstring name, jlong defaultValue) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); jboolean isCopy = true; const char* utf = env->GetStringUTFChars(name, &isCopy); auto res = jlong(context->getOption(utf, long(defaultValue))); @@ -122,7 +122,7 @@ jlong getOptionLong(JNIEnv* env, jclass, jlong self, jstring name, jlong default } jdouble getOptionDouble(JNIEnv* env, jclass, jlong self, jstring name, jdouble defaultValue) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); jboolean isCopy = true; const char* utf = env->GetStringUTFChars(name, &isCopy); auto res = jdouble(context->getOption(utf, double(defaultValue))); @@ -133,7 +133,7 @@ jdouble getOptionDouble(JNIEnv* env, jclass, jlong self, jstring name, jdouble d } jstring getOptionString(JNIEnv* env, jclass, jlong self, jstring name, jstring defaultValue) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); jboolean isCopy; jboolean defIsCopy; const char* nameStr = env->GetStringUTFChars(name, &isCopy); @@ -149,17 +149,17 @@ jstring getOptionString(JNIEnv* env, jclass, jlong self, jstring name, jstring d } jint getClientID(JNIEnv* env, jclass, jlong self) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); return jint(context->clientId()); } jint getClientCount(JNIEnv* env, jclass, jlong self) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); return jint(context->clientCount()); } jlong getSharedRandomNumber(JNIEnv* env, jclass, jlong self) { - FDBWorkloadContext* context = reinterpret_cast(self); + auto* context = reinterpret_cast(self); return jlong(context->sharedRandomNumber()); } @@ -324,7 +324,7 @@ struct JVM { if (!env) { throw JNIError{}; } - if (classPath.count(path) > 0) { + if (classPath.contains(path)) { // already added return; } diff --git a/bindings/java/fdbJNI.cpp b/bindings/java/fdbJNI.cpp index 81e4f391161..451cf8d1555 100644 --- a/bindings/java/fdbJNI.cpp +++ b/bindings/java/fdbJNI.cpp @@ -448,7 +448,7 @@ JNIEXPORT jobject JNICALL Java_com_apple_foundationdb_FutureKeyRangeArray_Future return JNI_NULL; } - jobjectArray kr_values = jenv->NewObjectArray(count, keyrange_class, NULL); + jobjectArray kr_values = jenv->NewObjectArray(count, keyrange_class, nullptr); if (!kr_values) { if (!jenv->ExceptionOccurred()) throwOutOfMem(jenv); @@ -600,7 +600,7 @@ JNIEXPORT jobject JNICALL Java_com_apple_foundationdb_FutureMappedResults_Future return JNI_NULL; } - jobjectArray mrr_values = jenv->NewObjectArray(count, mapped_key_value_class, NULL); + jobjectArray mrr_values = jenv->NewObjectArray(count, mapped_key_value_class, nullptr); if (!mrr_values) { if (!jenv->ExceptionOccurred()) throwOutOfMem(jenv); @@ -753,7 +753,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBDatabase_Database_1create throwParamNotNull(jenv); return 0; } - FDBDatabase* database = (FDBDatabase*)dbPtr; + auto* database = (FDBDatabase*)dbPtr; FDBTransaction* tr; fdb_error_t err = fdb_database_create_transaction(database, &tr); if (err) { @@ -780,12 +780,12 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBDatabase_Database_1setOpti throwParamNotNull(jenv); return; } - FDBDatabase* c = (FDBDatabase*)dPtr; + auto* c = (FDBDatabase*)dPtr; uint8_t* barr = nullptr; int size = 0; if (value != JNI_NULL) { - barr = (uint8_t*)jenv->GetByteArrayElements(value, JNI_NULL); + barr = (decltype(barr))jenv->GetByteArrayElements(value, JNI_NULL); if (!barr) { throwRuntimeEx(jenv, "Error getting handle to native resources"); return; @@ -810,7 +810,7 @@ JNIEXPORT jdouble JNICALL Java_com_apple_foundationdb_FDBDatabase_Database_1getM throwParamNotNull(jenv); return 0; } - FDBDatabase* database = (FDBDatabase*)dbPtr; + auto* database = (FDBDatabase*)dbPtr; return (jdouble)fdb_database_get_main_thread_busyness(database); } @@ -822,7 +822,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBDatabase_Database_1getCli return 0; } - FDBDatabase* database = (FDBDatabase*)dbPtr; + auto* database = (FDBDatabase*)dbPtr; FDBFuture* f = fdb_database_get_client_status(database); return (jlong)f; @@ -869,7 +869,7 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1s throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; fdb_transaction_set_read_version(tr, version); } @@ -880,7 +880,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; FDBFuture* f = fdb_transaction_get_read_version(tr); return (jlong)f; } @@ -894,9 +894,9 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); + auto* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -919,9 +919,9 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); + auto* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -953,16 +953,16 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barrBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); + auto* barrBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); if (!barrBegin) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return 0; } - uint8_t* barrEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); + auto* barrEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); if (!barrEnd) { jenv->ReleaseByteArrayElements(keyBeginBytes, (jbyte*)barrBegin, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1010,16 +1010,16 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barrBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); + auto* barrBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); if (!barrBegin) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return 0; } - uint8_t* barrEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); + auto* barrEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); if (!barrEnd) { jenv->ReleaseByteArrayElements(keyBeginBytes, (jbyte*)barrBegin, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1027,7 +1027,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 return 0; } - uint8_t* barrMapper = (uint8_t*)jenv->GetByteArrayElements(mapperBytes, JNI_NULL); + auto* barrMapper = (uint8_t*)jenv->GetByteArrayElements(mapperBytes, JNI_NULL); if (!barrMapper) { jenv->ReleaseByteArrayElements(keyBeginBytes, (jbyte*)barrBegin, JNI_ABORT); jenv->ReleaseByteArrayElements(keyEndBytes, (jbyte*)barrEnd, JNI_ABORT); @@ -1076,7 +1076,7 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FutureResults_FutureResults_1 return; } - FDBFuture* f = (FDBFuture*)future; + auto* f = (FDBFuture*)future; const FDBKeyValue* kvs; int count; fdb_bool_t more; @@ -1153,7 +1153,7 @@ Java_com_apple_foundationdb_FutureMappedResults_FutureMappedResults_1getDirect(J return; } - FDBFuture* f = (FDBFuture*)future; + auto* f = (FDBFuture*)future; const FDBMappedKeyValue* kvms; int count; fdb_bool_t more; @@ -1219,16 +1219,16 @@ Java_com_apple_foundationdb_FDBTransaction_Transaction_1getEstimatedRangeSizeByt throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* startKey = (uint8_t*)jenv->GetByteArrayElements(beginKeyBytes, JNI_NULL); + auto* startKey = (uint8_t*)jenv->GetByteArrayElements(beginKeyBytes, JNI_NULL); if (!startKey) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return 0; } - uint8_t* endKey = (uint8_t*)jenv->GetByteArrayElements(endKeyBytes, JNI_NULL); + auto* endKey = (uint8_t*)jenv->GetByteArrayElements(endKeyBytes, JNI_NULL); if (!endKey) { jenv->ReleaseByteArrayElements(beginKeyBytes, (jbyte*)startKey, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1254,16 +1254,16 @@ Java_com_apple_foundationdb_FDBTransaction_Transaction_1getRangeSplitPoints(JNIE throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* startKey = (uint8_t*)jenv->GetByteArrayElements(beginKeyBytes, JNI_NULL); + auto* startKey = (uint8_t*)jenv->GetByteArrayElements(beginKeyBytes, JNI_NULL); if (!startKey) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return 0; } - uint8_t* endKey = (uint8_t*)jenv->GetByteArrayElements(endKeyBytes, JNI_NULL); + auto* endKey = (uint8_t*)jenv->GetByteArrayElements(endKeyBytes, JNI_NULL); if (!endKey) { jenv->ReleaseByteArrayElements(beginKeyBytes, (jbyte*)startKey, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1287,16 +1287,16 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1s throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barrKey = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); + auto* barrKey = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); if (!barrKey) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return; } - uint8_t* barrValue = (uint8_t*)jenv->GetByteArrayElements(valueBytes, JNI_NULL); + auto* barrValue = (uint8_t*)jenv->GetByteArrayElements(valueBytes, JNI_NULL); if (!barrValue) { jenv->ReleaseByteArrayElements(keyBytes, (jbyte*)barrKey, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1317,9 +1317,9 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1c throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); + auto* barr = (uint8_t*)jenv->GetByteArrayElements(keyBytes, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -1339,16 +1339,16 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1c throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barrKeyBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); + auto* barrKeyBegin = (uint8_t*)jenv->GetByteArrayElements(keyBeginBytes, JNI_NULL); if (!barrKeyBegin) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return; } - uint8_t* barrKeyEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); + auto* barrKeyEnd = (uint8_t*)jenv->GetByteArrayElements(keyEndBytes, JNI_NULL); if (!barrKeyEnd) { jenv->ReleaseByteArrayElements(keyBeginBytes, (jbyte*)barrKeyBegin, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1372,16 +1372,16 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1m throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barrKey = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); + auto* barrKey = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); if (!barrKey) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); return; } - uint8_t* barrValue = (uint8_t*)jenv->GetByteArrayElements(value, JNI_NULL); + auto* barrValue = (uint8_t*)jenv->GetByteArrayElements(value, JNI_NULL); if (!barrValue) { jenv->ReleaseByteArrayElements(key, (jbyte*)barrKey, JNI_ABORT); if (!jenv->ExceptionOccurred()) @@ -1403,7 +1403,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; FDBFuture* f = fdb_transaction_commit(tr); return (jlong)f; } @@ -1417,12 +1417,12 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1s throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; uint8_t* barr = nullptr; int size = 0; if (value != JNI_NULL) { - barr = (uint8_t*)jenv->GetByteArrayElements(value, JNI_NULL); + barr = (decltype(barr))jenv->GetByteArrayElements(value, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -1445,7 +1445,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; int64_t version; fdb_error_t err = fdb_transaction_get_committed_version(tr, &version); if (err) { @@ -1462,7 +1462,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBFuture* f = fdb_transaction_get_approximate_size((FDBTransaction*)tPtr); + auto* f = fdb_transaction_get_approximate_size((FDBTransaction*)tPtr); return (jlong)f; } @@ -1473,7 +1473,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; FDBFuture* f = fdb_transaction_get_versionstamp(tr); return (jlong)f; } @@ -1486,9 +1486,9 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barr = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); + auto* barr = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -1510,7 +1510,7 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; FDBFuture* f = fdb_transaction_on_error(tr, (fdb_error_t)errorCode); return (jlong)f; } @@ -1543,9 +1543,9 @@ JNIEXPORT jlong JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1 throwParamNotNull(jenv); return 0; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* barr = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); + auto* barr = (uint8_t*)jenv->GetByteArrayElements(key, JNI_NULL); if (!barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -1578,9 +1578,9 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1a throwParamNotNull(jenv); return; } - FDBTransaction* tr = (FDBTransaction*)tPtr; + auto* tr = (FDBTransaction*)tPtr; - uint8_t* begin_barr = (uint8_t*)jenv->GetByteArrayElements(keyBegin, JNI_NULL); + auto* begin_barr = (uint8_t*)jenv->GetByteArrayElements(keyBegin, JNI_NULL); if (!begin_barr) { if (!jenv->ExceptionOccurred()) throwRuntimeEx(jenv, "Error getting handle to native resources"); @@ -1588,7 +1588,7 @@ JNIEXPORT void JNICALL Java_com_apple_foundationdb_FDBTransaction_Transaction_1a } int begin_size = jenv->GetArrayLength(keyBegin); - uint8_t* end_barr = (uint8_t*)jenv->GetByteArrayElements(keyEnd, JNI_NULL); + auto* end_barr = (uint8_t*)jenv->GetByteArrayElements(keyEnd, JNI_NULL); if (!end_barr) { jenv->ReleaseByteArrayElements(keyBegin, (jbyte*)begin_barr, JNI_ABORT); if (!jenv->ExceptionOccurred()) diff --git a/bindings/java/src/integration/com/apple/foundationdb/MappedRangeQueryIntegrationTest.java b/bindings/java/src/integration/com/apple/foundationdb/MappedRangeQueryIntegrationTest.java index 437a194ce13..e9881719e3f 100644 --- a/bindings/java/src/integration/com/apple/foundationdb/MappedRangeQueryIntegrationTest.java +++ b/bindings/java/src/integration/com/apple/foundationdb/MappedRangeQueryIntegrationTest.java @@ -152,74 +152,67 @@ public interface RangeQueryWithIndex { } RangeQueryWithIndex rangeQueryAndThenRangeQueries = (int begin, int end, Database db) -> db.run(tr -> { - try { - List kvs = tr.getRange(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)), - KeySelector.firstGreaterOrEqual(indexEntryKey(end)), - ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL) - .asList() - .get(); - Assertions.assertEquals(end - begin, kvs.size()); - - // Get the records of each index entry IN PARALLEL. - List>> resultFutures = new ArrayList<>(); - // In reality, we need to get the record key by parsing the index entry key. But considering this is a - // performance test, we just ignore the returned key and simply generate it from recordKey. + // Let Database.run retry transient FDB errors instead of turning them into assertion failures. + List kvs = tr.getRange(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)), + KeySelector.firstGreaterOrEqual(indexEntryKey(end)), + ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL) + .asList() + .join(); + Assertions.assertEquals(end - begin, kvs.size()); + + // Get the records of each index entry IN PARALLEL. + List>> resultFutures = new ArrayList<>(); + // In reality, we need to get the record key by parsing the index entry key. But considering this is a + // performance test, we just ignore the returned key and simply generate it from recordKey. + for (int id = begin; id < end; id++) { + resultFutures.add(tr.getRange(Range.startsWith(recordKeyPrefix(id)), + ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL).asList()); + } + AsyncUtil.whenAll(resultFutures).join(); + + if (validate) { + final Iterator indexes = kvs.iterator(); + final Iterator>> records = resultFutures.iterator(); for (int id = begin; id < end; id++) { - resultFutures.add(tr.getRange(Range.startsWith(recordKeyPrefix(id)), - ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL).asList()); - } - AsyncUtil.whenAll(resultFutures).get(); - - if (validate) { - final Iterator indexes = kvs.iterator(); - final Iterator>> records = resultFutures.iterator(); - for (int id = begin; id < end; id++) { - Assertions.assertTrue(indexes.hasNext()); - assertByteArrayEquals(indexEntryKey(id), indexes.next().getKey()); - - Assertions.assertTrue(records.hasNext()); - List rangeResult = records.next().get(); - validateRangeResult(id, rangeResult); - } - Assertions.assertFalse(indexes.hasNext()); - Assertions.assertFalse(records.hasNext()); + Assertions.assertTrue(indexes.hasNext()); + assertByteArrayEquals(indexEntryKey(id), indexes.next().getKey()); + + Assertions.assertTrue(records.hasNext()); + List rangeResult = records.next().join(); + validateRangeResult(id, rangeResult); } - } catch (Exception e) { - Assertions.fail("Unexpected exception", e); + Assertions.assertFalse(indexes.hasNext()); + Assertions.assertFalse(records.hasNext()); } return null; }); RangeQueryWithIndex mappedRangeQuery = (int begin, int end, Database db) -> db.run(tr -> { - try { - List kvs = - tr.getMappedRange(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)), - KeySelector.firstGreaterOrEqual(indexEntryKey(end)), MAPPER, - ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL) - .asList() - .get(); - Assertions.assertEquals(end - begin, kvs.size()); - - if (validate) { - final Iterator results = kvs.iterator(); - for (int id = begin; id < end; id++) { - Assertions.assertTrue(results.hasNext()); - MappedKeyValue mappedKeyValue = results.next(); - assertByteArrayEquals(indexEntryKey(id), mappedKeyValue.getKey()); - assertByteArrayEquals(EMPTY, mappedKeyValue.getValue()); - assertByteArrayEquals(indexEntryKey(id), mappedKeyValue.getKey()); - byte[] prefix = recordKeyPrefix(id); - assertByteArrayEquals(prefix, mappedKeyValue.getRangeBegin()); - prefix[prefix.length - 1] = (byte)0x01; - assertByteArrayEquals(prefix, mappedKeyValue.getRangeEnd()); - - List rangeResult = mappedKeyValue.getRangeResult(); - validateRangeResult(id, rangeResult); - } - Assertions.assertFalse(results.hasNext()); + List kvs = + tr.getMappedRange(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)), + KeySelector.firstGreaterOrEqual(indexEntryKey(end)), MAPPER, + ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL) + .asList() + .join(); + Assertions.assertEquals(end - begin, kvs.size()); + + if (validate) { + final Iterator results = kvs.iterator(); + for (int id = begin; id < end; id++) { + Assertions.assertTrue(results.hasNext()); + MappedKeyValue mappedKeyValue = results.next(); + assertByteArrayEquals(indexEntryKey(id), mappedKeyValue.getKey()); + assertByteArrayEquals(EMPTY, mappedKeyValue.getValue()); + assertByteArrayEquals(indexEntryKey(id), mappedKeyValue.getKey()); + byte[] prefix = recordKeyPrefix(id); + assertByteArrayEquals(prefix, mappedKeyValue.getRangeBegin()); + prefix[prefix.length - 1] = (byte)0x01; + assertByteArrayEquals(prefix, mappedKeyValue.getRangeEnd()); + + List rangeResult = mappedKeyValue.getRangeResult(); + validateRangeResult(id, rangeResult); } - } catch (Exception e) { - Assertions.fail("Unexpected exception", e); + Assertions.assertFalse(results.hasNext()); } return null; }); diff --git a/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java b/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java index 8cdecd172c2..28068d9c4de 100644 --- a/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java +++ b/bindings/java/src/main/com/apple/foundationdb/FDBTransaction.java @@ -640,6 +640,11 @@ public CompletableFuture commit() { @Override public Long getCommittedVersion() { + return getCommittedVersionAsPrimitive(); + } + + @Override + public long getCommittedVersionAsPrimitive() { if (eventKeeper != null) { eventKeeper.increment(Events.JNI_CALL); } diff --git a/bindings/java/src/main/com/apple/foundationdb/Transaction.java b/bindings/java/src/main/com/apple/foundationdb/Transaction.java index 3e07779cf99..a194e521fea 100644 --- a/bindings/java/src/main/com/apple/foundationdb/Transaction.java +++ b/bindings/java/src/main/com/apple/foundationdb/Transaction.java @@ -253,6 +253,24 @@ public interface Transaction extends AutoCloseable, ReadTransaction, Transaction */ Long getCommittedVersion(); + /** + * Gets the version number at which a successful commit modified the database, + * as a primitive {@code long} to avoid the overhead of autoboxing. + * This must be called only after the successful (non-error) completion of a call + * to {@link #commit()} on this {@code Transaction}, or the behavior is undefined. + * Read-only transactions do not modify the database when committed and will have + * a committed version of -1. Keep in mind that a transaction which reads keys and + * then sets them to their current values may be optimized to a read-only transaction. + * + *

This method is equivalent to {@link #getCommittedVersion()} but avoids the + * allocation of a {@link Long} object on every call.

+ * + * @return the database version at which the commit succeeded, as a primitive {@code long} + */ + default long getCommittedVersionAsPrimitive() { + return getCommittedVersion(); + } + /** * Returns a future which will contain the versionstamp which was used by any versionstamp * operations in this transaction. The future will be ready only after the successful diff --git a/bindings/java/src/test/com/apple/foundationdb/test/ConcurrentGetSetGet.java b/bindings/java/src/test/com/apple/foundationdb/test/ConcurrentGetSetGet.java index 2eb9d674f98..438a26cb474 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/ConcurrentGetSetGet.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/ConcurrentGetSetGet.java @@ -65,7 +65,6 @@ public void apply(Database db) { System.out.println(" sem: " + semaphore); System.out.println(); } catch (InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } diff --git a/bindings/java/src/test/com/apple/foundationdb/test/SerialIteration.java b/bindings/java/src/test/com/apple/foundationdb/test/SerialIteration.java index 6a53f871b9b..20f7890add7 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/SerialIteration.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/SerialIteration.java @@ -62,7 +62,6 @@ private static double runThreadedTest(Database database, int threadCount) { t.join(); rowsPerSecond += t.getAverageRowsPerSecond(); } catch (InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } @@ -90,7 +89,6 @@ public void run() { try { Thread.sleep(r.nextInt(1000)); } catch (InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } long totalTime = 0; diff --git a/bindings/java/src/test/com/apple/foundationdb/test/WatchTest.java b/bindings/java/src/test/com/apple/foundationdb/test/WatchTest.java index a28b78a86fa..23dcca2c0af 100644 --- a/bindings/java/src/test/com/apple/foundationdb/test/WatchTest.java +++ b/bindings/java/src/test/com/apple/foundationdb/test/WatchTest.java @@ -104,7 +104,6 @@ public static void raceTest(Database db) { Thread.sleep(1); } catch(InterruptedException e1) { - // TODO Auto-generated catch block e1.printStackTrace(); } } diff --git a/cmake/AddFdbServerLinkTest.cmake b/cmake/AddFdbServerLinkTest.cmake new file mode 100644 index 00000000000..71ab3b81558 --- /dev/null +++ b/cmake/AddFdbServerLinkTest.cmake @@ -0,0 +1,8 @@ +function(add_fdbserver_link_test target_name) + set(link_libs ${ARGN}) + list(GET link_libs 0 primary_lib) + list(REMOVE_AT link_libs 0) + file(RELATIVE_PATH link_test_src "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/fdbclient/LinkTest.cpp") + add_flow_target(LINK_TEST NAME ${target_name} SRCS ${link_test_src}) + target_link_libraries(${target_name} PRIVATE "$" ${link_libs} rapidxml) +endfunction() diff --git a/cmake/CompileBoost.cmake b/cmake/CompileBoost.cmake index 925bae405f8..98b3b3084e2 100644 --- a/cmake/CompileBoost.cmake +++ b/cmake/CompileBoost.cmake @@ -38,10 +38,6 @@ function(compile_boost) set(BOOST_LINK_FLAGS "") if(APPLE OR ICX OR USE_LIBCXX) list(APPEND BOOST_COMPILER_FLAGS -stdlib=libc++ -nostdlib++) - if (APPLE) - # Remove this after boost 1.81 or above is used - list(APPEND BOOST_COMPILER_FLAGS -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION) - endif() list(APPEND BOOST_LINK_FLAGS -lc++ -lc++abi) if (NOT APPLE) list(APPEND BOOST_LINK_FLAGS -static-libgcc) @@ -164,11 +160,10 @@ set(Boost_USE_STATIC_LIBS ON) if (UNIX AND CMAKE_CXX_COMPILER_ID MATCHES "Clang$" AND USE_LIBCXX) list(APPEND CMAKE_PREFIX_PATH /opt/boost_1_86_0_clang) set(BOOST_HINT_PATHS /opt/boost_1_86_0_clang) - message(STATUS "Using Clang version of boost") + message(STATUS "Preferring _clang build of boost ...") else () list(APPEND CMAKE_PREFIX_PATH /opt/boost_1_86_0) set(BOOST_HINT_PATHS /opt/boost_1_86_0) - message(STATUS "Using g++ version of boost") endif () if(BOOST_ROOT) @@ -178,10 +173,18 @@ endif() if(WIN32) # Use CONFIG mode to prefer Boost's BoostConfig.cmake over deprecated FindBoost module # This is required for CMake 3.30+ compatibility (policy CMP0167) - find_package(Boost 1.86.0 QUIET COMPONENTS filesystem iostreams serialization system program_options url CONFIG PATHS ${BOOST_HINT_PATHS}) + find_package(Boost 1.86.0 QUIET + COMPONENTS filesystem iostreams serialization system program_options url + CONFIG PATHS ${BOOST_HINT_PATHS}) add_library(boost_target INTERFACE) - target_link_libraries(boost_target INTERFACE Boost::boost Boost::filesystem Boost::iostreams Boost::serialization Boost::system Boost::url) - + target_link_libraries(boost_target + INTERFACE Boost::boost + Boost::filesystem + Boost::iostreams + Boost::serialization + Boost::system + Boost::url + ) # Add zstd library since boost::iostreams may depend on it find_package(PkgConfig QUIET) if(PkgConfig_FOUND) @@ -191,15 +194,17 @@ if(WIN32) target_link_directories(boost_target INTERFACE ${ZSTD_LIBRARY_DIRS}) endif() endif() - add_library(boost_target_program_options INTERFACE) target_link_libraries(boost_target_program_options INTERFACE Boost::boost Boost::program_options) return() endif() -find_package(Boost 1.86.0 EXACT QUIET COMPONENTS context filesystem iostreams program_options serialization system url CONFIG PATHS ${BOOST_HINT_PATHS}) -set(FORCE_BOOST_BUILD OFF CACHE BOOL "Forces cmake to build boost and ignores any installed boost") +find_package(Boost 1.86.0 EXACT QUIET + COMPONENTS context filesystem iostreams program_options serialization system url + CONFIG PATHS ${BOOST_HINT_PATHS}) + +set(FORCE_BOOST_BUILD OFF CACHE BOOL "Forces cmake to build boost and ignores any installed boost") # The precompiled boost silently broke in CI. While investigating, I considered extending # the old check with something like this, so that it would fail loudly if it found a bad # pre-existing boost. It turns out the error messages we get from CMake explain what is @@ -214,9 +219,17 @@ set(FORCE_BOOST_BUILD OFF CACHE BOOL "Forces cmake to build boost and ignores an # message(FATAL_ERROR "Unacceptable precompiled boost found") # if(Boost_FOUND AND NOT FORCE_BOOST_BUILD) + message(STATUS "Found Boost: ${Boost_DIR}") add_library(boost_target INTERFACE) - target_link_libraries(boost_target INTERFACE Boost::boost Boost::context Boost::filesystem Boost::iostreams Boost::serialization Boost::system Boost::url) - + target_link_libraries(boost_target + INTERFACE Boost::boost + Boost::context + Boost::filesystem + Boost::iostreams + Boost::serialization + Boost::system + Boost::url + ) # Add zstd library since boost::iostreams may depend on it when compiled with homebrew on macOS if(APPLE) find_package(PkgConfig QUIET) @@ -228,7 +241,6 @@ if(Boost_FOUND AND NOT FORCE_BOOST_BUILD) endif() endif() endif() - add_library(boost_target_program_options INTERFACE) target_link_libraries(boost_target_program_options INTERFACE Boost::boost Boost::program_options) elseif(WIN32) diff --git a/cmake/CompileRocksDB.cmake b/cmake/CompileRocksDB.cmake index 99ae2581a3c..c50b1dacef6 100644 --- a/cmake/CompileRocksDB.cmake +++ b/cmake/CompileRocksDB.cmake @@ -64,18 +64,25 @@ else() set(FDB_ROCKSDB_GIT_HASH "") endif() -# Generate FDBRocksDBVersion.h from template into build directory +# Generate FDBRocksDBVersion.h from template into the core build include tree # This file is NOT source-controlled - it's regenerated at cmake configure time -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/fdbserver/include/fdbserver) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/fdbserver/core/include/fdbserver/core) configure_file( ${CMAKE_CURRENT_LIST_DIR}/FDBRocksDBVersion.h.in - ${CMAKE_BINARY_DIR}/fdbserver/include/fdbserver/FDBRocksDBVersion.h + ${CMAKE_BINARY_DIR}/fdbserver/core/include/fdbserver/core/FDBRocksDBVersion.h @ONLY) message(STATUS "Generated FDBRocksDBVersion.h with version ${FDB_ROCKSDB_MAJOR}.${FDB_ROCKSDB_MINOR}.${FDB_ROCKSDB_PATCH}") # Only try to find system RocksDB if not using a specific commit hash if(NOT ROCKSDB_GIT_HASH) + # Save the configured version - FindRocksDB.cmake may overwrite ROCKSDB_VERSION + # with the system version even when find_package fails + set(_FDB_ROCKSDB_VERSION_CONFIGURED ${ROCKSDB_VERSION}) find_package(RocksDB ${ROCKSDB_VERSION}) + if(NOT ROCKSDB_FOUND) + # Restore configured version since FindRocksDB overwrote it + set(ROCKSDB_VERSION ${_FDB_ROCKSDB_VERSION_CONFIGURED}) + endif() endif() include(ExternalProject) diff --git a/cmake/ConfigureCompiler.cmake b/cmake/ConfigureCompiler.cmake index 321ac4a22a9..11a1d0b3062 100644 --- a/cmake/ConfigureCompiler.cmake +++ b/cmake/ConfigureCompiler.cmake @@ -13,6 +13,9 @@ env_set(USE_UBSAN OFF BOOL "Compile with undefined behavior sanitizer") env_set(FDB_RELEASE_CANDIDATE OFF BOOL "This is a building of a release candidate") env_set(FDB_RELEASE OFF BOOL "This is a building of a final release") env_set(USE_CCACHE OFF BOOL "Use ccache for compilation if available") +env_set(USE_CLANG_TIDY OFF BOOL "Run clang-tidy during C/C++ compilation") +env_set(CLANG_TIDY "" STRING "Path to clang-tidy executable (empty to auto-detect)") +env_set(CLANG_TIDY_EXTRA_ARGS "" STRING "Additional clang-tidy arguments (space-separated)") env_set(RELATIVE_DEBUG_PATHS OFF BOOL "Use relative file paths in debug info") env_set(USE_WERROR OFF BOOL "Compile with -Werror. Recommended for local development and CI.") default_linker(_use_ld) @@ -102,6 +105,33 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) +if(USE_CLANG_TIDY) + if(CLANG_TIDY) + set(_clang_tidy_program "${CLANG_TIDY}") + else() + find_program(_clang_tidy_program NAMES clang-tidy) + endif() + + if(NOT _clang_tidy_program) + message( + FATAL_ERROR + "USE_CLANG_TIDY is enabled, but clang-tidy was not found. Install clang-tidy or set CLANG_TIDY=/path/to/clang-tidy.") + endif() + + set(_clang_tidy_command "${_clang_tidy_program}") + list(APPEND _clang_tidy_command "--config-file=${CMAKE_SOURCE_DIR}/.clang-tidy") + if(CLANG_TIDY_EXTRA_ARGS) + separate_arguments(_clang_tidy_extra_args NATIVE_COMMAND "${CLANG_TIDY_EXTRA_ARGS}") + list(APPEND _clang_tidy_command ${_clang_tidy_extra_args}) + endif() + + set(CMAKE_C_CLANG_TIDY ${_clang_tidy_command}) + set(CMAKE_CXX_CLANG_TIDY ${_clang_tidy_command}) + + string(REPLACE ";" " " _clang_tidy_command_display "${_clang_tidy_command}") + message(STATUS "clang-tidy enabled for C/C++ compilation: ${_clang_tidy_command_display}") +endif() + if(NOT OPEN_FOR_IDE) add_compile_definitions(NO_INTELLISENSE) endif() diff --git a/cmake/FDBBenchmark.cmake b/cmake/FDBBenchmark.cmake new file mode 100644 index 00000000000..505ca64892b --- /dev/null +++ b/cmake/FDBBenchmark.cmake @@ -0,0 +1,61 @@ +function(fdb_setup_googlebenchmark) + if(TARGET fdb_google_benchmark) + return() + endif() + + if(NOT benchmark_ROOT) + if(EXISTS /opt/googlebenchmark-f91b6b AND CLANG AND USE_LIBCXX) + set(benchmark_ROOT /opt/googlebenchmark-f91b6b) + elseif(EXISTS /opt/googlebenchmark-f91b6b-g++ AND NOT USE_LIBCXX) + set(benchmark_ROOT /opt/googlebenchmark-f91b6b-g++) + endif() + endif() + + find_package(benchmark) + + add_library(fdb_google_benchmark INTERFACE) + + if(benchmark_FOUND) + target_link_libraries(fdb_google_benchmark INTERFACE benchmark::benchmark) + target_include_directories( + fdb_google_benchmark + INTERFACE $) + return() + endif() + + if(NOT TARGET benchmark) + set(googlebenchmark_root ${CMAKE_BINARY_DIR}/googlebenchmark-download) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/benchmark-download.cmake + ${googlebenchmark_root}/CMakeLists.txt + COPYONLY) + + execute_process( + COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE results + WORKING_DIRECTORY ${googlebenchmark_root}) + if(results) + message(FATAL_ERROR "Configuration step for Benchmark has Failed. ${results}") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} --build . --config Release + RESULT_VARIABLE results + WORKING_DIRECTORY ${googlebenchmark_root}) + if(results) + message(FATAL_ERROR "Build step for Benchmark has Failed. ${results}") + endif() + + set(BENCHMARK_ENABLE_TESTING OFF) + add_subdirectory( + ${googlebenchmark_root}/googlebenchmark-src + ${googlebenchmark_root}/googlebenchmark-build + EXCLUDE_FROM_ALL) + endif() + + target_include_directories( + fdb_google_benchmark + INTERFACE ${CMAKE_BINARY_DIR}/googlebenchmark-download/googlebenchmark-src/include) + target_link_libraries(fdb_google_benchmark INTERFACE benchmark) +endfunction() diff --git a/cmake/FDBComponents.cmake b/cmake/FDBComponents.cmake index 59023fc03f4..8a2c7ea1bc1 100644 --- a/cmake/FDBComponents.cmake +++ b/cmake/FDBComponents.cmake @@ -159,7 +159,7 @@ if(BUILD_SWIFT_BINDING AND NOT WITH_C_BINDING) message(WARNING "Swift binding depends on C binding, but C binding is not enabled") endif() -if(NOT BUILD_SWIFT_BINDING OR NOT BUILD_C_BINDING OR OPEN_FOR_IDE) +if(NOT BUILD_SWIFT_BINDING OR NOT BUILD_C_BINDING OR OPEN_FOR_IDE OR NOT WITH_SWIFT) set(WITH_SWIFT_BINDING OFF) else() find_program(SWIFT_EXECUTABLE swift) @@ -262,28 +262,15 @@ set(WITH_LIBURING OFF CACHE BOOL "Build with liburing enabled") # Set this to ON # TOML11 ################################################################################ -# TOML can download and install itself into the binary directory, so it should -# always be available. -find_package(toml11 3.4.0) -if(toml11_FOUND) - message(STATUS "Using TOML11 from system") - add_library(toml11_target INTERFACE) - target_link_libraries(toml11_target INTERFACE toml11) -else() - include(ExternalProject) - ExternalProject_add(toml11Project - URL "https://github.com/ToruNiina/toml11/archive/v3.4.0.tar.gz" - URL_HASH SHA256=bc6d733efd9216af8c119d8ac64a805578c79cc82b813e4d1d880ca128bd154d - CMAKE_CACHE_ARGS - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} - -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} - -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/toml11 - -Dtoml11_BUILD_TEST:BOOL=OFF - BUILD_ALWAYS ON) - add_library(toml11_target INTERFACE) - add_dependencies(toml11_target toml11Project) - target_include_directories(toml11_target SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/toml11/include) +find_package(toml11 3.8.1 EXACT CONFIG) +if(NOT toml11_FOUND) + include(FetchContent) + FetchContent_Declare( + toml11 + URL "https://github.com/ToruNiina/toml11/archive/v3.8.1.tar.gz" + URL_HASH SHA256=6a3d20080ecca5ea42102c078d3415bef80920f6c4ea2258e87572876af77849 + ) + FetchContent_MakeAvailable(toml11) endif() ################################################################################ diff --git a/cmake/FindCoroutines.cmake b/cmake/FindCoroutines.cmake index 9391ddcaed9..b7411ed2f79 100644 --- a/cmake/FindCoroutines.cmake +++ b/cmake/FindCoroutines.cmake @@ -249,7 +249,7 @@ if(CXX_COROUTINES_HAVE_COROUTINES) # Try to compile a simple coroutines program without any compiler flags set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) - + check_cxx_source_compiles("${code}" CXX_COROUTINES_NO_AWAIT_NEEDED) set(can_link ${CXX_COROUTINES_NO_AWAIT_NEEDED}) diff --git a/cmake/GetFmt.cmake b/cmake/GetFmt.cmake index ecb890896af..d197dd8765d 100644 --- a/cmake/GetFmt.cmake +++ b/cmake/GetFmt.cmake @@ -1,30 +1,11 @@ -# Try to find fmt using pkg-config first (for homebrew installations) -find_package(PkgConfig QUIET) -if(PkgConfig_FOUND) - pkg_check_modules(FMT QUIET fmt) - if(FMT_FOUND) - # Create an imported target for fmt - add_library(fmt::fmt INTERFACE IMPORTED) - target_link_libraries(fmt::fmt INTERFACE ${FMT_LIBRARIES}) - target_link_directories(fmt::fmt INTERFACE ${FMT_LIBRARY_DIRS}) - target_include_directories(fmt::fmt INTERFACE ${FMT_INCLUDE_DIRS}) - target_compile_options(fmt::fmt INTERFACE ${FMT_CFLAGS_OTHER}) - set(fmt_FOUND TRUE) - endif() -endif() - -# Fall back to standard find_package -if(NOT fmt_FOUND) - find_package(fmt 11.0.2 EXACT QUIET CONFIG) -endif() +find_package(fmt 11.1.4 EXACT CONFIG) -# Fall back to FetchContent if still not found if(NOT fmt_FOUND) include(FetchContent) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt - GIT_TAG 11.0.2 + GIT_TAG 11.1.4 ) FetchContent_MakeAvailable(fmt) endif() diff --git a/cmake/RocksDBVersion.cmake b/cmake/RocksDBVersion.cmake index 3b0c349640f..51f5e5d3512 100644 --- a/cmake/RocksDBVersion.cmake +++ b/cmake/RocksDBVersion.cmake @@ -5,7 +5,7 @@ # Choose ONE of the two options below by uncommenting the appropriate section. # Do NOT set both - CMake will error if both are configured. # -# CMake will automatically generate fdbserver/include/fdbserver/FDBRocksDBVersion.h +# CMake will automatically generate fdbserver/core/include/fdbserver/core/FDBRocksDBVersion.h # based on the version specified here. Do NOT edit that file manually. # # To get SHA256: diff --git a/flowbench/benchmark.cmake b/cmake/benchmark-download.cmake similarity index 79% rename from flowbench/benchmark.cmake rename to cmake/benchmark-download.cmake index 3fa69e8066b..410842e2cfd 100644 --- a/flowbench/benchmark.cmake +++ b/cmake/benchmark-download.cmake @@ -4,8 +4,8 @@ project(googlebenchmark-download NONE) include(ExternalProject) ExternalProject_Add(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git - # If you change this, then be sure to also update the directory name (which contains this SHA) in flowbench/CMakeLists.txt, - # and the fdb-build-support Dockerfile (which downloads + compiles the prebuilt version) + # If you change this, then be sure to also update the directory name (which contains this SHA) + # in FDB's build environment and the prebuilt googlebenchmark package. GIT_TAG f91b6b42b1b9854772a90ae9501464a161707d1e # v1.6.0 GIT_SHALLOW ON GIT_CONFIG advice.detachedHead=false @@ -21,8 +21,7 @@ ExternalProject_Add(googlebenchmark include(ExternalProject) ExternalProject_Add(googletest DEPENDS googlebenchmark GIT_REPOSITORY https://github.com/google/googletest.git - # If you change this, be sure to update the perl one-liner in the part of fdb-build-support Dockerfile that downloads - # googlebenchmark. That one-liner determines which SHA will be used in the pre-built version of googlebenchmark. + # If you change this, be sure to update the prebuilt googlebenchmark package as well. GIT_TAG 2fe3bd994b3189899d93f1d5a881e725e046fdc2 # release-1.8.1 GIT_SHALLOW ON GIT_CONFIG advice.detachedHead=false diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 6a907285028..e90811ec16b 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -110,4 +110,9 @@ function(generate_grpc_protobuf pkg_name) APPEND_PATH ${out_rel_path} PROTOS ${proto_files} ) + + get_target_property(_generated_proto_sources ${target_name} SOURCES) + if(_generated_proto_sources) + set_source_files_properties(${_generated_proto_sources} PROPERTIES SKIP_LINTING ON) + endif() endfunction() diff --git a/contrib/Joshua/scripts/correctnessTest.sh b/contrib/Joshua/scripts/correctnessTest.sh index 32f5cd8b3c1..54c9ad7be58 100755 --- a/contrib/Joshua/scripts/correctnessTest.sh +++ b/contrib/Joshua/scripts/correctnessTest.sh @@ -99,10 +99,21 @@ # Maps to: --long-running # Values: true/false (default: false) # +# TH_DISABLE_CODE_PROBES Disable code probe collection and ensemble coverage checks +# Maps to: --disable-code-probes +# Values: true/false (default: false) +# # TH_RANDOM_SEED Force specific random seed for debugging # Maps to: --random-seed # Default: auto-generated # +# TH_FDBSERVER_MEMORY Pass --memory SIZE to fdbserver +# Maps to: --fdbserver-memory +# Example: 12288MiB +# Default: unset +# Note: if unset, TestHarness2 automatically uses +# 12288MiB for ASAN/UBSAN-instrumented binaries +# # TH_OUTPUT_FORMAT Output format for test results # Maps to: --output-format # Values: xml/json (default: xml) diff --git a/contrib/TestHarness2/README.md b/contrib/TestHarness2/README.md index f9a180558e7..5e82f8bb624 100644 --- a/contrib/TestHarness2/README.md +++ b/contrib/TestHarness2/README.md @@ -76,11 +76,17 @@ TestHarness2 supports environment variables for configuration. Most variables fo - **`TH_BUGGIFY`**: Buggify mode (`on`, `off`, or `random`, default: `random`) - **`TH_USE_VALGRIND`**: Run tests under valgrind (`true`/`false`, default: `false`) - **`TH_LONG_RUNNING`**: Enable long-running test mode (`true`/`false`, default: `false`) +- **`TH_DISABLE_CODE_PROBES`**: Disable code probe collection and ensemble coverage checks (`true`/`false`, default: `false`) +- **`TH_FDBSERVER_MEMORY`**: Pass `--memory SIZE` to `fdbserver` (for example `12288MiB`) **Optional Configuration:** - **`TH_RANDOM_SEED`**: Force specific random seed for debugging - **`TH_OUTPUT_FORMAT`**: Output format (`xml` or `json`, default: `xml`) +When `TH_FDBSERVER_MEMORY` is unset, TestHarness2 automatically runs sanitizer-instrumented +`fdbserver` binaries with `--memory 12288MiB` so ASAN/UBSAN correctness jobs do not stay +pinned to the default 8 GiB simulator limit. + For a complete list of all variables, run: `python3 -m test_harness.app --help` ## Output Format diff --git a/contrib/TestHarness2/test_harness/config.py b/contrib/TestHarness2/test_harness/config.py index 54c80c0cbb7..7f92d0ce6fe 100644 --- a/contrib/TestHarness2/test_harness/config.py +++ b/contrib/TestHarness2/test_harness/config.py @@ -216,10 +216,21 @@ def __init__(self): } self.print_coverage = False self.print_coverage_args = {"action": "store_true"} + self.disable_code_probes: bool = False + self.disable_code_probes_args = { + "action": "store_true", + "help": "Disable code probe collection and ensemble coverage checks", + } self.binary = Path("bin") / ( "fdbserver.exe" if os.name == "nt" else "fdbserver" ) self.binary_args = {"help": "Path to executable"} + self.fdbserver_memory: str | None = None + self.fdbserver_memory_args = { + "type": str, + "required": False, + "help": "Pass --memory SIZE to fdbserver (for example 12288MiB)", + } self.hit_per_runs_ratio: int = 20000 self.hit_per_runs_ratio_args = { "help": "Maximum test runs before each code probe hit at least once" @@ -352,7 +363,23 @@ def _read_env(self): # Use the env var to supply the default value, so that if the # environment variable is set and the corresponding command line # flag is not, the environment variable has an effect. - self._config_map[attr].kwargs["default"] = attr_type(e) + self._config_map[attr].kwargs["default"] = self._parse_env_value( + attr_type, env_name, e + ) + + def _parse_env_value(self, attr_type: type, env_name: str, value: str): + if attr_type is bool: + normalized = value.lower() + if normalized in ("true", "1", "yes", "on"): + return True + if normalized in ("false", "0", "no", "off"): + return False + raise ValueError( + "Invalid boolean value {} for {} -- use true or false".format( + value, env_name + ) + ) + return attr_type(value) def build_arguments(self, parser: argparse.ArgumentParser): for val in self._config_map.values(): diff --git a/contrib/TestHarness2/test_harness/results.py b/contrib/TestHarness2/test_harness/results.py index c0124ce080c..4d31ddbafae 100644 --- a/contrib/TestHarness2/test_harness/results.py +++ b/contrib/TestHarness2/test_harness/results.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import collections import io import json import re @@ -28,9 +29,14 @@ def __init__(self, cluster_file: str | None, ensemble_id: str): self.fdb_path = ("joshua", "ensembles", "results", "application", ensemble_id) self.coverage_path = self.fdb_path + ("coverage",) self.statistics = test_harness.fdb.Statistics(cluster_file, self.fdb_path) - coverage_dict: OrderedDict[Coverage, int] = test_harness.fdb.read_coverage( - cluster_file, self.coverage_path - ) + self.code_probe_tracking_enabled = not config.disable_code_probes + coverage_dict: OrderedDict[Coverage, int] + if self.code_probe_tracking_enabled: + coverage_dict = test_harness.fdb.read_coverage( + cluster_file, self.coverage_path + ) + else: + coverage_dict = collections.OrderedDict() self.coverage: List[Tuple[Coverage, int]] = [] self.min_coverage_hit: int | None = None self.ratio = self.global_statistics.total_test_runs / config.hit_per_runs_ratio @@ -54,7 +60,9 @@ def __init__(self, cluster_file: str | None, ensemble_id: str): self.global_statistics.total_cpu_time += v.runtime self.stats.append((k, v.runtime, v.run_count)) self.stats.sort(key=lambda x: x[1], reverse=True) - if self.min_coverage_hit is not None: + if not self.code_probe_tracking_enabled: + self.coverage_ok = True + elif self.min_coverage_hit is not None: self.coverage_ok = self.min_coverage_hit > self.ratio else: self.coverage_ok = False @@ -64,6 +72,9 @@ def dump(self, prefix: str): out = SummaryTree("EnsembleResults") out.attributes["TotalRuntime"] = str(self.global_statistics.total_cpu_time) out.attributes["TotalTestRuns"] = str(self.global_statistics.total_test_runs) + out.attributes["CodeProbeTrackingEnabled"] = ( + "1" if self.code_probe_tracking_enabled else "0" + ) out.attributes["TotalProbesHit"] = str(self.global_statistics.total_probes_hit) out.attributes["MinProbeHit"] = str(self.min_coverage_hit) out.attributes["TotalProbes"] = str(len(self.coverage)) diff --git a/contrib/TestHarness2/test_harness/run.py b/contrib/TestHarness2/test_harness/run.py index d0133e588ab..90b5eed9dd1 100644 --- a/contrib/TestHarness2/test_harness/run.py +++ b/contrib/TestHarness2/test_harness/run.py @@ -22,6 +22,10 @@ from test_harness.summarize import Summary, SummaryTree +DEFAULT_SANITIZER_MEMORY = "12288MiB" +SANITIZER_MARKERS = (b"__asan_init", b"__ubsan_handle_") +_SANITIZER_BINARY_CACHE: Dict[Path, bool] = {} + def parse_test_args_file(args_file: Path) -> tuple[Path, int, bool, List[str]]: """ @@ -85,6 +89,44 @@ def parse_test_args_file(args_file: Path) -> tuple[Path, int, bool, List[str]]: return test_file, random_seed, buggify_enabled, extra_args +def extra_args_include_memory(extra_args: List[str]) -> bool: + return any(arg in ("-m", "--memory") for arg in extra_args) + + +def binary_uses_sanitizers(binary: Path) -> bool: + cached = _SANITIZER_BINARY_CACHE.get(binary) + if cached is not None: + return cached + + found = False + max_marker_len = max(len(marker) for marker in SANITIZER_MARKERS) + overlap = max_marker_len - 1 + with binary.open("rb") as infile: + tail = b"" + while True: + chunk = infile.read(1024 * 1024) + if not chunk: + break + haystack = tail + chunk + if any(marker in haystack for marker in SANITIZER_MARKERS): + found = True + break + tail = haystack[-overlap:] if overlap else b"" + + _SANITIZER_BINARY_CACHE[binary] = found + return found + + +def resolve_fdbserver_memory(binary: Path, extra_args: List[str]) -> str | None: + if extra_args_include_memory(extra_args): + return None + if config.fdbserver_memory is not None: + return config.fdbserver_memory + if binary_uses_sanitizers(binary): + return DEFAULT_SANITIZER_MEMORY + return None + + @total_ordering class TestDescription: def __init__(self, path: Path, name: str, priority: float): @@ -432,6 +474,7 @@ def __init__( extra_args: List[str] | None = None, ): self.binary = binary + self.is_old_binary = False self.test_file = test_file self.random_seed = random_seed self.uid = uid @@ -553,6 +596,9 @@ def run(self): "-s", str(self.random_seed), ] + memory_limit = resolve_fdbserver_memory(self.binary, self.extra_args) + if memory_limit is not None: + command += ["--memory", memory_limit] if self.trace_format is not None: command += ["--trace_format", self.trace_format] if self.use_tls_plugin: diff --git a/contrib/TestHarness2/test_harness/summarize.py b/contrib/TestHarness2/test_harness/summarize.py index 94526018637..d8e11329134 100644 --- a/contrib/TestHarness2/test_harness/summarize.py +++ b/contrib/TestHarness2/test_harness/summarize.py @@ -390,7 +390,11 @@ def summarize(self, trace_dir: Path, command: str): return self.summarize_files(trace_files[0]) # Skip write_coverage for old binaries in restarting tests - if config.joshua_dir is not None and not self.is_old_binary: + if ( + config.joshua_dir is not None + and not self.is_old_binary + and not config.disable_code_probes + ): import test_harness.fdb test_harness.fdb.write_coverage( @@ -424,7 +428,7 @@ def ok(self): return (not self.error) != self.is_negative_test def done(self): - if config.print_coverage: + if config.print_coverage and not config.disable_code_probes: for k, v in self.coverage.items(): child = SummaryTree("CodeCoverage") child.attributes["File"] = k.file @@ -500,6 +504,13 @@ def done(self): ): # When running ASAN we expect to see this message. Boost coroutine should be using the correct asan annotations so that it shouldn't produce any false positives. continue + if "WARNING: ASan is ignoring requested __asan_handle_no_return: stack type:" in line: + # ASAN emits this with makecontext/swapcontext coroutine stacks. + continue + if line == "False positive error reports may follow": + continue + if line == "For details see https://github.com/google/sanitizers/issues/189": + continue if line.endswith("Warning: unimplemented fcntl command: 1036"): # Valgrind produces this warning when F_SET_RW_HINT is used continue @@ -695,6 +706,8 @@ def parse_error(attrs: Dict[str, str]): self.handler.add_handler(("Severity", "40"), parse_error) def coverage(attrs: Dict[str, str]): + if config.disable_code_probes: + return covered = True if "Covered" in attrs: covered = int(attrs["Covered"]) != 0 diff --git a/contrib/benchmark_comparison.py b/contrib/benchmark_comparison.py index ada345f691c..b5776ad558e 100644 --- a/contrib/benchmark_comparison.py +++ b/contrib/benchmark_comparison.py @@ -3,11 +3,11 @@ Generate comprehensive actor vs coroutine benchmark comparison report. USAGE: - cd build_output # or your build directory with bin/flowbench + cd build_output # or your build directory with bin/flow_bench python3 ../contrib/benchmark_comparison.py REQUIREMENTS: - - Working bin/flowbench executable + - Working bin/flow_bench executable - Both actor and coroutine benchmarks available: * bench_net2, coroutine_net2 * bench_delay.*DELAY.*, coroutine_delay_bench @@ -33,7 +33,7 @@ def geomean(values): def get_benchmark_data(filter_name): """Get benchmark data and parse results""" - result = subprocess.run(['./bin/flowbench', f'--benchmark_filter={filter_name}', '--benchmark_format=json'], + result = subprocess.run(['./bin/flow_bench', f'--benchmark_filter={filter_name}', '--benchmark_format=json'], capture_output=True, text=True, cwd='/root/build_output') if result.returncode == 0 and result.stdout.strip(): return json.loads(result.stdout)['benchmarks'] @@ -87,7 +87,7 @@ def main(): print(f"OVERALL_GEOMEAN {geom:+7.4f} {geom:+7.4f} 0 0 0 0") print("") - print("Comparing bench_net2 to coroutine_net2 (from ./build_output/bin/flowbench)") + print("Comparing bench_net2 to coroutine_net2 (from ./build_output/bin/flow_bench)") print("Benchmark Time CPU Time Old Time New CPU Old CPU New") print("----------------------------------------------------------------------------------------------------------------------------") @@ -114,7 +114,7 @@ def main(): print(f"OVERALL_GEOMEAN {geom:+7.4f} {geom:+7.4f} 0 0 0 0") print("") - print("Comparing bench_callback to coroutine_callback (from ./build_output/bin/flowbench)") + print("Comparing bench_callback to coroutine_callback (from ./build_output/bin/flow_bench)") print("Benchmark Time CPU Time Old Time New CPU Old CPU New") print("---------------------------------------------------------------------------------------------------------------------------------------------") @@ -147,4 +147,4 @@ def main(): print(f"OVERALL_GEOMEAN {geom:+7.4f} {geom:+7.4f} 0 0 0 0") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/contrib/joshua_logtool.py b/contrib/joshua_logtool.py index 87d579ab3f5..8f296de9998 100755 --- a/contrib/joshua_logtool.py +++ b/contrib/joshua_logtool.py @@ -20,7 +20,7 @@ from typing import List, Union -# Defined in SimulatedCluster.actor.cpp:SimulationConfig::setStorageEngine +# Defined in SimulatedCluster.cpp:SimulationConfig::setStorageEngine ROCKSDB_TRACEEVENT_STRING = ["RocksDBNonDeterminism", "ShardedRocksDBNonDeterminism"] # e.g. /var/joshua/ensembles/20230221-051349-xiaogesu-c9fc5b230dcd91cf diff --git a/contrib/linenoise/CMakeLists.txt b/contrib/linenoise/CMakeLists.txt index 52487a6f228..9191ea44432 100644 --- a/contrib/linenoise/CMakeLists.txt +++ b/contrib/linenoise/CMakeLists.txt @@ -1,2 +1,3 @@ add_library(linenoise STATIC linenoise.c) target_include_directories(linenoise PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +set_target_properties(linenoise PROPERTIES C_CLANG_TIDY "") diff --git a/contrib/lsan.suppressions b/contrib/lsan.suppressions index 3d3d40e59eb..70fd84f298d 100644 --- a/contrib/lsan.suppressions +++ b/contrib/lsan.suppressions @@ -3,3 +3,14 @@ # Not all incoming connections are cleanly shut down in client API tests leak:ConnectionReaderActorState + +# Peer objects in TransportData::peers are intentionally not freed at shutdown. +# FlowTransport is allocated into a global slot and never deleted because its +# destructor triggers actor cancellation cascades that access freed state. +# Peer::Peer covers allocations during Peer construction (DDSketch, AsyncTrigger). +# connectionKeeper and connectionMonitor cover Promise allocations made during +# the Peer's lifetime that are leaked along with the Peer. +leak:TransportData::getOrOpenPeer +leak:Peer::Peer +leak:connectionKeeper +leak:connectionMonitor diff --git a/contrib/mockkms/CMakeLists.txt b/contrib/mockkms/CMakeLists.txt deleted file mode 100644 index 6a0b75639c2..00000000000 --- a/contrib/mockkms/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -if(WITH_GO_BINDING) - set(MOCK_KMS_SRC fault_injection.go get_encryption_keys.go mock_kms.go utils.go) - set(MOCK_KMS_TEST_SRC ${MOCK_KMS_SRC} mockkms_test.go) - add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/bin/mockkms - COMMAND ${GO_EXECUTABLE} build -o ${CMAKE_BINARY_DIR}/bin/mockkms ${MOCK_KMS_SRC} - DEPENDS ${MOCK_KMS_SRC} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_custom_target(mockkms ALL DEPENDS ${CMAKE_BINARY_DIR}/bin/mockkms) - fdb_install(PROGRAMS ${CMAKE_BINARY_DIR}/bin/mockkms DESTINATION bin COMPONENT server) - - add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/bin/mockkms_test - COMMAND ${GO_EXECUTABLE} test -c -o ${CMAKE_BINARY_DIR}/bin/mockkms_test ${MOCK_KMS_TEST_SRC} - DEPENDS ${MOCK_KMS_TEST_SRC} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_custom_target(mockkms_test ALL DEPENDS ${CMAKE_BINARY_DIR}/bin/mockkms_test) - add_test(NAME mockkms COMMAND ${CMAKE_BINARY_DIR}/bin/mockkms_test) - -endif() diff --git a/contrib/mockkms/fault_injection.go b/contrib/mockkms/fault_injection.go deleted file mode 100644 index 8b5ff01b194..00000000000 --- a/contrib/mockkms/fault_injection.go +++ /dev/null @@ -1,179 +0,0 @@ -/* - * fault_injection.go - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Interface supports client to inject fault(s) -// Module enables a client to update { FaultLocation -> FaultStatus } mapping in a -// thread-safe manner, however, client is responsible to synchronize fault status -// updates across 'getEncryptionKeys' REST requests to obtain predictable results. - -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "sync" -) - -type Fault struct { - Location int `json:"fault_location"` - Enable bool `json:"enable_fault"` -} - -type FaultInjectionRequest struct { - Faults []Fault `json:"faults"` -} - -type FaultInjectionResponse struct { - Faults []Fault `json:"faults"` -} - -type faultLocMap struct { - locMap map[int]bool - rwLock sync.RWMutex -} - -var ( - faultLocMapInstance *faultLocMap // Singleton mapping of { FaultLocation -> FaultStatus } -) - -// Caller is responsible for thread synchronization. Recommended to be invoked during package::init() -func NewFaultLocMap() *faultLocMap { - if faultLocMapInstance == nil { - faultLocMapInstance = &faultLocMap{} - - faultLocMapInstance.rwLock = sync.RWMutex{} - faultLocMapInstance.locMap = map[int]bool { - READ_HTTP_REQUEST_BODY : false, - UNMARSHAL_REQUEST_BODY_JSON : false, - UNSUPPORTED_QUERY_MODE : false, - PARSE_HTTP_REQUEST : false, - MARSHAL_RESPONSE : false, - } - } - return faultLocMapInstance -} - -func getLocFaultStatus(loc int) (val bool, found bool) { - if faultLocMapInstance == nil { - panic("FaultLocMap not intialized") - os.Exit(1) - } - - faultLocMapInstance.rwLock.RLock() - defer faultLocMapInstance.rwLock.RUnlock() - val, found = faultLocMapInstance.locMap[loc] - if !found { - return - } - return -} - -func updateLocFaultStatuses(faults []Fault) (updated []Fault, err error) { - if faultLocMapInstance == nil { - panic("FaultLocMap not intialized") - os.Exit(1) - } - - updated = []Fault{} - err = nil - - faultLocMapInstance.rwLock.Lock() - defer faultLocMapInstance.rwLock.Unlock() - for i := 0; i < len(faults); i++ { - fault := faults[i] - - oldVal, found := faultLocMapInstance.locMap[fault.Location] - if !found { - err = fmt.Errorf("Unknown fault_location '%d'", fault.Location) - return - } - faultLocMapInstance.locMap[fault.Location] = fault.Enable - log.Printf("Update Location '%d' oldVal '%t' newVal '%t'", fault.Location, oldVal, fault.Enable) - } - - // return the updated faultLocMap - for loc, enable := range faultLocMapInstance.locMap { - var f Fault - f.Location = loc - f.Enable = enable - updated = append(updated, f) - } - return -} - -func jsonifyFaultArr(w http.ResponseWriter, faults []Fault) (jResp string) { - resp := FaultInjectionResponse{ - Faults: faults, - } - - mResp, err := json.Marshal(resp) - if err != nil { - log.Printf("Error marshaling response '%s'", err.Error()) - sendErrorResponse(w, err) - return - } - jResp = string(mResp) - return -} - -func updateFaultLocMap(w http.ResponseWriter, faults []Fault) { - updated , err := updateLocFaultStatuses(faults) - if err != nil { - sendErrorResponse(w, err) - return - } - - fmt.Fprintf(w, jsonifyFaultArr(w, updated)) -} - -func shouldInjectFault(loc int) bool { - status, found := getLocFaultStatus(loc) - if !found { - log.Printf("Unknown fault_location '%d'", loc) - return false - } - return status -} - -func handleUpdateFaultInjection(w http.ResponseWriter, r *http.Request) { - byteArr, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Printf("Http request body read error '%s'", err.Error()) - sendErrorResponse(w, err) - return - } - - req := FaultInjectionRequest{} - err = json.Unmarshal(byteArr, &req) - if err != nil { - log.Printf("Error parsing FaultInjectionRequest '%s'", string(byteArr)) - sendErrorResponse(w, err) - } - updateFaultLocMap(w, req.Faults) -} - -func initFaultLocMap() { - faultLocMapInstance = NewFaultLocMap() - log.Printf("FaultLocMap int done") -} \ No newline at end of file diff --git a/contrib/mockkms/get_encryption_keys.go b/contrib/mockkms/get_encryption_keys.go deleted file mode 100644 index 1f48e0f371d..00000000000 --- a/contrib/mockkms/get_encryption_keys.go +++ /dev/null @@ -1,321 +0,0 @@ -/* - * get_encryption_keys.go - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// GetEncryptionKeys handler -// Handler is resposible for the following: -// 1. Parse the incoming HttpRequest and validate JSON request structural sanity -// 2. Ability to handle getEncryptionKeys by 'KeyId' or 'DomainId' as requested -// 3. Ability to inject faults if requested - -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "math/rand" - "net/http" -) - -type CipherDetailRes struct { - BaseCipherId uint64 `json:"base_cipher_id"` - EncryptDomainId int64 `json:"encrypt_domain_id"` - BaseCipher string `json:"base_cipher"` -} - -type ValidationToken struct { - TokenName string `json:"token_name"` - TokenValue string `json:"token_value"` -} - -type CipherDetailReq struct { - BaseCipherId uint64 `json:"base_cipher_id"` - EncryptDomainId int64 `json:"encrypt_domain_id"` -} - -type GetEncryptKeysResponse struct { - CipherDetails []CipherDetailRes `json:"cipher_key_details"` - KmsUrls []string `json:"kms_urls"` -} - -type GetEncryptKeysRequest struct { - QueryMode string `json:"query_mode"` - CipherDetails []CipherDetailReq `json:"cipher_key_details"` - ValidationTokens []ValidationToken `json:"validation_tokens"` - RefreshKmsUrls bool `json:"refresh_kms_urls"` -} - -type cipherMapInstanceSingleton map[uint64][]byte - -const ( - READ_HTTP_REQUEST_BODY = iota - UNMARSHAL_REQUEST_BODY_JSON - UNSUPPORTED_QUERY_MODE - PARSE_HTTP_REQUEST - MARSHAL_RESPONSE -) - -const ( - maxCipherKeys = uint64(1024 * 1024) // Max cipher keys - maxCipherSize = 16 // Max cipher buffer size -) - -var ( - cipherMapInstance cipherMapInstanceSingleton // Singleton mapping of { baseCipherId -> baseCipher } -) - -// const mapping of { Location -> errorString } -func errStrMap() func(int) string { - _errStrMap := map[int]string{ - READ_HTTP_REQUEST_BODY: "Http request body read error", - UNMARSHAL_REQUEST_BODY_JSON: "Http request body unmarshal error", - UNSUPPORTED_QUERY_MODE: "Unsupported query_mode", - PARSE_HTTP_REQUEST: "Error parsing GetEncryptionKeys request", - MARSHAL_RESPONSE: "Error marshaling response", - } - - return func(key int) string { - return _errStrMap[key] - } -} - -// Caller is responsible for thread synchronization. Recommended to be invoked during package::init() -func NewCipherMap(maxKeys uint64, cipherSize int) cipherMapInstanceSingleton { - if cipherMapInstance == nil { - cipherMapInstance = make(map[uint64][]byte) - - for i := uint64(1); i <= maxKeys; i++ { - cipher := make([]byte, cipherSize) - rand.Read(cipher) - cipherMapInstance[i] = cipher - } - log.Printf("KMS cipher map populate done, maxCiphers '%d'", maxCipherKeys) - } - return cipherMapInstance -} - -func getKmsUrls() (urls []string) { - urlCount := rand.Intn(5) + 1 - for i := 1; i <= urlCount; i++ { - url := fmt.Sprintf("https://KMS/%d:%d:%d:%d", i, i, i, i) - urls = append(urls, url) - } - return -} - -func isEncryptDomainIdValid(id int64) bool { - if id > 0 || id == -1 || id == -2 { - return true - } - return false -} - -func abs(x int64) int64 { - if x < 0 { - return -x - } - return x -} - -func getBaseCipherIdFromDomainId(domainId int64) (baseCipherId uint64) { - baseCipherId = uint64(1) + uint64(abs(domainId))%maxCipherKeys - return -} - -func getEncryptionKeysByKeyIds(w http.ResponseWriter, byteArr []byte) { - req := GetEncryptKeysRequest{} - err := json.Unmarshal(byteArr, &req) - if err != nil || shouldInjectFault(PARSE_HTTP_REQUEST) { - var err error - if shouldInjectFault(PARSE_HTTP_REQUEST) { - err = fmt.Errorf("[FAULT] %s %s'", errStrMap()(PARSE_HTTP_REQUEST), string(byteArr)) - } else { - err = fmt.Errorf("%s %s' err '%v'", errStrMap()(PARSE_HTTP_REQUEST), string(byteArr), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - var details []CipherDetailRes - for i := 0; i < len(req.CipherDetails); i++ { - var baseCipherId = uint64(req.CipherDetails[i].BaseCipherId) - - var encryptDomainId = int64(req.CipherDetails[i].EncryptDomainId) - if !isEncryptDomainIdValid(encryptDomainId) { - err := fmt.Errorf("EncryptDomainId not valid '%d'", encryptDomainId) - sendErrorResponse(w, err) - return - } - - cipher, found := cipherMapInstance[baseCipherId] - if !found { - err := fmt.Errorf("BaseCipherId not found '%d'", baseCipherId) - sendErrorResponse(w, err) - return - } - - var detail = CipherDetailRes{ - BaseCipherId: baseCipherId, - EncryptDomainId: encryptDomainId, - BaseCipher: string(cipher), - } - details = append(details, detail) - } - - var urls []string - if req.RefreshKmsUrls { - urls = getKmsUrls() - } - - resp := GetEncryptKeysResponse{ - CipherDetails: details, - KmsUrls: urls, - } - - mResp, err := json.Marshal(resp) - if err != nil || shouldInjectFault(MARSHAL_RESPONSE) { - var err error - if shouldInjectFault(MARSHAL_RESPONSE) { - err = fmt.Errorf("[FAULT] %s", errStrMap()(MARSHAL_RESPONSE)) - } else { - err = fmt.Errorf("%s err '%v'", errStrMap()(MARSHAL_RESPONSE), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - fmt.Fprintf(w, string(mResp)) -} - -func getEncryptionKeysByDomainIds(w http.ResponseWriter, byteArr []byte) { - req := GetEncryptKeysRequest{} - err := json.Unmarshal(byteArr, &req) - if err != nil || shouldInjectFault(PARSE_HTTP_REQUEST) { - var err error - if shouldInjectFault(PARSE_HTTP_REQUEST) { - err = fmt.Errorf("[FAULT] %s '%s'", errStrMap()(PARSE_HTTP_REQUEST), string(byteArr)) - } else { - err = fmt.Errorf("%s '%s' err '%v'", errStrMap()(PARSE_HTTP_REQUEST), string(byteArr), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - var details []CipherDetailRes - for i := 0; i < len(req.CipherDetails); i++ { - var encryptDomainId = int64(req.CipherDetails[i].EncryptDomainId) - if !isEncryptDomainIdValid(encryptDomainId) { - err := fmt.Errorf("EncryptDomainId not valid '%d'", encryptDomainId) - sendErrorResponse(w, err) - return - } - - var baseCipherId = getBaseCipherIdFromDomainId(encryptDomainId) - cipher, found := cipherMapInstance[baseCipherId] - if !found { - err := fmt.Errorf("BaseCipherId not found '%d'", baseCipherId) - sendErrorResponse(w, err) - return - } - - var detail = CipherDetailRes{ - BaseCipherId: baseCipherId, - EncryptDomainId: encryptDomainId, - BaseCipher: string(cipher), - } - details = append(details, detail) - } - - var urls []string - if req.RefreshKmsUrls { - urls = getKmsUrls() - } - - resp := GetEncryptKeysResponse{ - CipherDetails: details, - KmsUrls: urls, - } - - mResp, err := json.Marshal(resp) - if err != nil || shouldInjectFault(MARSHAL_RESPONSE) { - var err error - if shouldInjectFault(MARSHAL_RESPONSE) { - err = fmt.Errorf("[FAULT] %s", errStrMap()(MARSHAL_RESPONSE)) - } else { - err = fmt.Errorf("%s err '%v'", errStrMap()(MARSHAL_RESPONSE), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - fmt.Fprintf(w, string(mResp)) -} - -func handleGetEncryptionKeys(w http.ResponseWriter, r *http.Request) { - byteArr, err := ioutil.ReadAll(r.Body) - if err != nil || shouldInjectFault(READ_HTTP_REQUEST_BODY) { - var err error - if shouldInjectFault(READ_HTTP_REQUEST_BODY) { - err = fmt.Errorf("[FAULT] %s", errStrMap()(READ_HTTP_REQUEST_BODY)) - } else { - err = fmt.Errorf("%s err '%v'", errStrMap()(READ_HTTP_REQUEST_BODY), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - var arbitrary_json map[string]interface{} - err = json.Unmarshal(byteArr, &arbitrary_json) - if err != nil || shouldInjectFault(UNMARSHAL_REQUEST_BODY_JSON) { - var err error - if shouldInjectFault(UNMARSHAL_REQUEST_BODY_JSON) { - err = fmt.Errorf("[FAULT] %s", errStrMap()(UNMARSHAL_REQUEST_BODY_JSON)) - } else { - err = fmt.Errorf("%s err '%v'", errStrMap()(UNMARSHAL_REQUEST_BODY_JSON), err) - } - log.Println(err.Error()) - sendErrorResponse(w, err) - return - } - - if shouldInjectFault(UNSUPPORTED_QUERY_MODE) { - err = fmt.Errorf("[FAULT] %s '%s'", errStrMap()(UNSUPPORTED_QUERY_MODE), arbitrary_json["query_mode"]) - sendErrorResponse(w, err) - return - } else if arbitrary_json["query_mode"] == "lookupByKeyId" { - getEncryptionKeysByKeyIds(w, byteArr) - } else if arbitrary_json["query_mode"] == "lookupByDomainId" { - getEncryptionKeysByDomainIds(w, byteArr) - } else { - err = fmt.Errorf("%s '%s'", errStrMap()(UNSUPPORTED_QUERY_MODE), arbitrary_json["query_mode"]) - sendErrorResponse(w, err) - return - } -} - -func initEncryptCipherMap() { - cipherMapInstance = NewCipherMap(maxCipherKeys, maxCipherSize) -} diff --git a/contrib/mockkms/mock_kms.go b/contrib/mockkms/mock_kms.go deleted file mode 100644 index 52d72539f72..00000000000 --- a/contrib/mockkms/mock_kms.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * mock_kms.go - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// FoundationDB Mock KMS (Key Management Solution/Service) interface -// Interface runs an HTTP server handling REST calls simulating FDB communications -// with an external KMS. - -package main - -// Importing module enable .note.gnu.build-id insertion in the ELF executable. -// Few drawbacks of the scheme are: -// 1. Potentially slower builds -// 2. No cross-compilation support -// 3. Limited `go tools` availability. -// TODO: Replace with a better scheme if possible. - -import "C" - -import ( - "log" - "math/rand" - "net/http" - "sync" - "time" -) - -// KMS supported endpoints -const ( - getEncryptionKeysEndpoint = "/getEncryptionKeys" - updateFaultInjectionEndpoint = "/updateFaultInjection" -) - -// Routine is responsible to instantiate data-structures necessary for MockKMS functioning -func init () { - var wg sync.WaitGroup - - wg.Add(2) - go func(){ - initEncryptCipherMap() - wg.Done() - }() - go func(){ - initFaultLocMap() - wg.Done() - }() - - wg.Wait() - - rand.Seed(time.Now().UTC().UnixNano()) -} - -func main() { - http.NewServeMux() - http.HandleFunc(getEncryptionKeysEndpoint, handleGetEncryptionKeys) - http.HandleFunc(updateFaultInjectionEndpoint, handleUpdateFaultInjection) - - log.Fatal(http.ListenAndServe(":5001", nil)) -} \ No newline at end of file diff --git a/contrib/mockkms/mockkms_test.go b/contrib/mockkms/mockkms_test.go deleted file mode 100644 index 38b901bd8cc..00000000000 --- a/contrib/mockkms/mockkms_test.go +++ /dev/null @@ -1,302 +0,0 @@ -/* - * mockkms_test.go - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// MockKMS unit tests, the coverage includes: -// 1. Mock HttpRequest creation and HttpResponse writer. -// 2. Construct fake request to validate the following scenarios: -// 2.1. Request with "unsupported query mode" -// 2.2. Get encryption keys by KeyIds; with and without 'RefreshKmsUrls' flag. -// 2.2. Get encryption keys by DomainIds; with and without 'RefreshKmsUrls' flag. -// 2.3. Random fault injection and response validation - -package main - -import ( - "encoding/json" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -const ( - ByKeyIdReqWithRefreshUrls = `{ - "query_mode": "lookupByKeyId", - "cipher_key_details": [ - { - "base_cipher_id": 77, - "encrypt_domain_id": 76 - }, - { - "base_cipher_id": 2, - "encrypt_domain_id": -1 - } - ], - "validation_tokens": [ - { - "token_name": "1", - "token_value":"12344" - }, - { - "token_name": "2", - "token_value":"12334" - } - ], - "refresh_kms_urls": true - }` - ByKeyIdReqWithoutRefreshUrls = `{ - "query_mode": "lookupByKeyId", - "cipher_key_details": [ - { - "base_cipher_id": 77, - "encrypt_domain_id": 76 - }, - { - "base_cipher_id": 2, - "encrypt_domain_id": -1 - } - ], - "validation_tokens": [ - { - "token_name": "1", - "token_value":"12344" - }, - { - "token_name": "2", - "token_value":"12334" - } - ], - "refresh_kms_urls": false - }` - ByDomainIdReqWithRefreshUrls = `{ - "query_mode": "lookupByDomainId", - "cipher_key_details": [ - { - "encrypt_domain_id": 76 - }, - { - "encrypt_domain_id": -1 - } - ], - "validation_tokens": [ - { - "token_name": "1", - "token_value":"12344" - }, - { - "token_name": "2", - "token_value":"12334" - } - ], - "refresh_kms_urls": true - }` - ByDomainIdReqWithoutRefreshUrls = `{ - "query_mode": "lookupByDomainId", - "cipher_key_details": [ - { - "encrypt_domain_id": 76 - }, - { - "encrypt_domain_id": -1 - } - ], - "validation_tokens": [ - { - "token_name": "1", - "token_value":"12344" - }, - { - "token_name": "2", - "token_value":"12334" - } - ], - "refresh_kms_urls": false - }` - UnsupportedQueryMode= `{ - "query_mode": "foo_mode", - "cipher_key_details": [ - { - "encrypt_domain_id": 76 - }, - { - "encrypt_domain_id": -1 - } - ], - "validation_tokens": [ - { - "token_name": "1", - "token_value":"12344" - }, - { - "token_name": "2", - "token_value":"12334" - } - ], - "refresh_kms_urls": false - }` -) - -func unmarshalValidResponse(data []byte, t *testing.T) (resp GetEncryptKeysResponse) { - resp = GetEncryptKeysResponse{} - err := json.Unmarshal(data, &resp) - if err != nil { - t.Errorf("Error unmarshaling valid response '%s' error '%v'", string(data), err) - t.Fail() - } - return -} - -func unmarshalErrorResponse(data []byte, t *testing.T) (resp ErrorResponse) { - resp = ErrorResponse{} - err := json.Unmarshal(data, &resp) - if err != nil { - t.Errorf("Error unmarshaling error response resp '%s' error '%v'", string(data), err) - t.Fail() - } - return -} - -func checkGetEncyptKeysResponseValidity(resp GetEncryptKeysResponse, t *testing.T) { - if len(resp.CipherDetails) != 2 { - t.Errorf("Unexpected CipherDetails count, expected '%d' actual '%d'", 2, len(resp.CipherDetails)) - t.Fail() - } - - baseCipherIds := [...]uint64 {uint64(77), uint64(2)} - encryptDomainIds := [...]int64 {int64(76), int64(-1)} - - for i := 0; i < len(resp.CipherDetails); i++ { - if resp.CipherDetails[i].BaseCipherId != baseCipherIds[i] { - t.Errorf("Mismatch BaseCipherId, expected '%d' actual '%d'", baseCipherIds[i], resp.CipherDetails[i].BaseCipherId) - t.Fail() - } - if resp.CipherDetails[i].EncryptDomainId != encryptDomainIds[i] { - t.Errorf("Mismatch EncryptDomainId, expected '%d' actual '%d'", encryptDomainIds[i], resp.CipherDetails[i].EncryptDomainId) - t.Fail() - } - if len(resp.CipherDetails[i].BaseCipher) == 0 { - t.Error("Empty BaseCipher") - t.Fail() - } - } -} - -func runQueryExpectingErrorResponse(payload string, url string, errSubStr string, t *testing.T) { - body := strings.NewReader(payload) - req := httptest.NewRequest(http.MethodPost, url, body) - w := httptest.NewRecorder() - handleGetEncryptionKeys(w, req) - res := w.Result() - defer res.Body.Close() - data, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Errorf("Error %v", err) - } - - resp := unmarshalErrorResponse(data, t) - if !strings.Contains(resp.Err.Detail, errSubStr) { - t.Errorf("Unexpected error response '%s'", resp.Err.Detail) - t.Fail() - } -} - -func runQueryExpectingValidResponse(payload string, url string, t *testing.T) { - body := strings.NewReader(payload) - req := httptest.NewRequest(http.MethodPost, url, body) - w := httptest.NewRecorder() - handleGetEncryptionKeys(w, req) - res := w.Result() - defer res.Body.Close() - data, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Errorf("Error %v", err) - } - - resp := unmarshalValidResponse(data, t) - checkGetEncyptKeysResponseValidity(resp, t) -} - -func TestUnsupportedQueryMode(t *testing.T) { - runQueryExpectingErrorResponse(UnsupportedQueryMode, getEncryptionKeysEndpoint, errStrMap()(UNSUPPORTED_QUERY_MODE), t) -} - -func TestGetEncryptionKeysByKeyIdsWithRefreshUrls(t *testing.T) { - runQueryExpectingValidResponse(ByKeyIdReqWithRefreshUrls, getEncryptionKeysEndpoint, t) -} - -func TestGetEncryptionKeysByKeyIdsWithoutRefreshUrls(t *testing.T) { - runQueryExpectingValidResponse(ByKeyIdReqWithoutRefreshUrls, getEncryptionKeysEndpoint, t) -} - -func TestGetEncryptionKeysByDomainIdsWithRefreshUrls(t *testing.T) { - runQueryExpectingValidResponse(ByDomainIdReqWithRefreshUrls, getEncryptionKeysEndpoint, t) -} - -func TestGetEncryptionKeysByDomainIdsWithoutRefreshUrls(t *testing.T) { - runQueryExpectingValidResponse(ByDomainIdReqWithoutRefreshUrls, getEncryptionKeysEndpoint, t) -} - -func TestFaultInjection(t *testing.T) { - numIterations := rand.Intn(701) + 86 - - for i := 0; i < numIterations; i++ { - loc := rand.Intn(MARSHAL_RESPONSE + 1) - f := Fault{} - f.Location = loc - f.Enable = true - - var faults []Fault - faults = append(faults, f) - fW := httptest.NewRecorder() - body := strings.NewReader(jsonifyFaultArr(fW, faults)) - fReq := httptest.NewRequest(http.MethodPost, updateFaultInjectionEndpoint, body) - handleUpdateFaultInjection(fW, fReq) - if !shouldInjectFault(loc) { - t.Errorf("Expected fault enabled for loc '%d'", loc) - t.Fail() - } - - var payload string - lottery := rand.Intn(100) - if lottery < 25 { - payload = ByKeyIdReqWithRefreshUrls - } else if lottery >= 25 && lottery < 50 { - payload = ByKeyIdReqWithoutRefreshUrls - } else if lottery >= 50 && lottery < 75 { - payload = ByDomainIdReqWithRefreshUrls - } else { - payload = ByDomainIdReqWithoutRefreshUrls - } - runQueryExpectingErrorResponse(payload, getEncryptionKeysEndpoint, errStrMap()(loc), t) - - // reset Fault - faults[0].Enable = false - fW = httptest.NewRecorder() - body = strings.NewReader(jsonifyFaultArr(fW, faults)) - fReq = httptest.NewRequest(http.MethodPost, updateFaultInjectionEndpoint, body) - handleUpdateFaultInjection(fW, fReq) - if shouldInjectFault(loc) { - t.Errorf("Expected fault disabled for loc '%d'", loc) - t.Fail() - } - } -} \ No newline at end of file diff --git a/contrib/monitoring/actor_flamegraph.cpp b/contrib/monitoring/actor_flamegraph.cpp index 58bd0d2c862..b97aaa360b8 100644 --- a/contrib/monitoring/actor_flamegraph.cpp +++ b/contrib/monitoring/actor_flamegraph.cpp @@ -113,11 +113,11 @@ class Traces { actor->stack.push_back(currentStack.top()->name); } } else if (op == OP_DESTROY) { - if (actors.count(id)) { + if (actors.contains(id)) { actors.erase(id); } } else if (op == OP_ENTER) { - if (actors.count(id) == 0) { + if (!actors.contains(id)) { actors[id] = std::make_shared(results, id, name); } currentStack.push(actors[id]); diff --git a/contrib/pkg_tester/requirements.txt b/contrib/pkg_tester/requirements.txt index af94b20328e..d56e3045ff4 100644 --- a/contrib/pkg_tester/requirements.txt +++ b/contrib/pkg_tester/requirements.txt @@ -6,7 +6,7 @@ packaging==20.9 pluggy==0.13.1 py==1.10.0 pyparsing==2.4.7 -pytest==6.2.4 +pytest==9.0.3 syrupy==1.2.3 toml==0.10.2 typing-extensions==3.10.0.0 diff --git a/contrib/replay/CMakeLists.txt b/contrib/replay/CMakeLists.txt index 85e393c707d..7a113685ced 100644 --- a/contrib/replay/CMakeLists.txt +++ b/contrib/replay/CMakeLists.txt @@ -25,27 +25,23 @@ file(MAKE_DIRECTORY ${REPLAY_OUTPUT_DIR}) # Get all Go source files for dependency tracking file(GLOB REPLAY_GO_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.go") +set(REPLAY_GO_MOD "${CMAKE_CURRENT_SOURCE_DIR}/go.mod") +set(REPLAY_GO_SUM "${CMAKE_CURRENT_SOURCE_DIR}/go.sum") -# Custom target to build replay -# This handles all Go dependencies automatically - users just need Go installed -# ALL means it builds by default (but only if Go is available - see check above) -add_custom_target(replay ALL - COMMAND ${CMAKE_COMMAND} -E echo "Downloading Go dependencies..." - COMMAND ${GO_EXECUTABLE} mod download - COMMAND ${CMAKE_COMMAND} -E echo "Tidying Go modules..." - COMMAND ${GO_EXECUTABLE} mod tidy - COMMAND ${CMAKE_COMMAND} -E echo "Building replay..." +# Custom command to build replay - only rebuilds when source files change +# go build handles dependencies automatically (downloads if needed) +add_custom_command( + OUTPUT ${REPLAY_BINARY} COMMAND ${GO_EXECUTABLE} build -o ${REPLAY_BINARY} . WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Building FDB trace replay tool (all dependencies handled automatically)" - BYPRODUCTS ${REPLAY_BINARY} - SOURCES ${REPLAY_GO_SOURCES} + DEPENDS ${REPLAY_GO_SOURCES} ${REPLAY_GO_MOD} ${REPLAY_GO_SUM} + COMMENT "Building FDB trace replay tool" ) -# Make sure the binary is executable -add_custom_command(TARGET replay POST_BUILD - COMMAND chmod +x ${REPLAY_BINARY} - COMMENT "Making replay executable" +# Target that depends on the binary - only triggers rebuild when deps change +add_custom_target(replay ALL + DEPENDS ${REPLAY_BINARY} + SOURCES ${REPLAY_GO_SOURCES} ) message(STATUS "replay will be built by default (Go found)") diff --git a/contrib/sqlite/CMakeLists.txt b/contrib/sqlite/CMakeLists.txt index 71ca7e6666c..41ae5a42781 100644 --- a/contrib/sqlite/CMakeLists.txt +++ b/contrib/sqlite/CMakeLists.txt @@ -7,6 +7,8 @@ add_library(sqlite STATIC sqliteLimit.h sqlite3.amalgamation.c) +set_target_properties(sqlite PROPERTIES C_CLANG_TIDY "") + target_include_directories(sqlite PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # Suppress warnings in sqlite since it's third party @@ -18,4 +20,4 @@ endif() # SQLite won't build with -ffast-math if(ICX) target_compile_options(sqlite PRIVATE -fno-fast-math) -endif() \ No newline at end of file +endif() diff --git a/contrib/stacktrace/CMakeLists.txt b/contrib/stacktrace/CMakeLists.txt index f82b4b6a6c3..6d958db451f 100644 --- a/contrib/stacktrace/CMakeLists.txt +++ b/contrib/stacktrace/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(stacktrace STATIC stacktrace.amalgamation.cpp) +set_target_properties(stacktrace PROPERTIES CXX_CLANG_TIDY "") target_include_directories(stacktrace PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") if (USE_ASAN) target_compile_definitions(stacktrace PRIVATE ADDRESS_SANITIZER) diff --git a/design/AI-generated/FDB_NETWORK_PROTOCOL.md b/design/AI-generated/FDB_NETWORK_PROTOCOL.md new file mode 100644 index 00000000000..6e4ca2c82bd --- /dev/null +++ b/design/AI-generated/FDB_NETWORK_PROTOCOL.md @@ -0,0 +1,2666 @@ +# FoundationDB Network Protocol Message Reference + +This document is a comprehensive enumeration of every serialized message type in the +FoundationDB network protocol. These types are not documented in any single place in +the source; they are defined implicitly by `serialize`/`serializer` methods on C++ +structs throughout the codebase. + +*NOTE: By design FDB protocols can change between minor versions. Information in +this file is very detailed and is subject to being out of date. However it is +probably straightforward to ask state of the art models to update the documentation +for you if needed.* + +**Conventions in this document:** +- Every struct referenced in any message is fully defined with all serialized fields. +- Types are resolved recursively down to C/C++ base types. +- "→ base" annotations after a type show what it resolves to at the wire level. +- Fields marked "(not serialized)" are present in the C++ struct but do not appear on the wire. +- `Optional` is serialized as: `bool present` + (if true) the value of type `T`. +- `std::vector` / `VectorRef` are serialized as: `int32_t count` + count × T. +- `Arena` fields provide memory backing for `Ref` types; they are serialized last. + +--- + +## Table of Contents + +- [1. Base Types and Aliases](#1-base-types-and-aliases) +- [2. Foundational Struct Definitions](#2-foundational-struct-definitions) +- [3. Coordination Protocol](#3-coordination-protocol) +- [4. Cluster Controller Protocol](#4-cluster-controller-protocol) +- [5. Master Protocol](#5-master-protocol) +- [6. GRV Proxy Protocol](#6-grv-proxy-protocol) +- [7. Commit Proxy Protocol](#7-commit-proxy-protocol) +- [8. Resolver Protocol](#8-resolver-protocol) +- [9. Transaction Log (TLog) Protocol](#9-transaction-log-tlog-protocol) +- [10. Storage Server Protocol](#10-storage-server-protocol) +- [11. Data Distributor Protocol](#11-data-distributor-protocol) +- [12. Ratekeeper Protocol](#12-ratekeeper-protocol) +- [13. Worker Protocol](#13-worker-protocol) +- [14. Backup / Consistency Scan / Test Protocols](#14-backup--consistency-scan--test-protocols) +- [15. Client Worker / Debug / Process Protocols](#15-client-worker--debug--process-protocols) +- [Appendix A: Log System Configuration Types](#appendix-a-log-system-configuration-types) +- [Appendix B: In-Band Log Messages](#appendix-b-in-band-log-messages) +- [Appendix C: Backpressure Protocol](#appendix-c-backpressure-protocol) +- [Appendix D: Client Library Protocol Flows](#appendix-d-client-library-protocol-flows) +- [Appendix E: fdbcli Protocol Usage](#appendix-e-fdbcli-protocol-usage) +- [Appendix F: Server-Side Protocol Flows](#appendix-f-server-side-protocol-flows) + +--- + +## Architecture Overview + +FDB uses an RPC framework built on its "Flow" actor model. Each server role exposes +an **interface** struct containing `RequestStream` fields — typed endpoints. +Request messages carry a `ReplyPromise` (or `ReplyPromiseStream` for streaming) +that the receiver fulfills with the reply. + +- **Endpoint multiplexing**: many interfaces serialize only one `RequestStream` and + reconstruct the others via `getAdjustedEndpoint(offset)` during deserialization. +- **Wire format**: `ProtocolVersion` (uint64) header, then fields serialized + sequentially via `serializer(ar, field1, field2, ...)` with no inter-field framing. + +--- + +## 1. Base Types and Aliases + +These are the leaf types that all struct fields ultimately resolve to. + +| Wire Type | C++ Type | Size | Notes | +|-----------|----------|------|-------| +| `bool` | `bool` | 1 byte | | +| `int8_t` / `uint8_t` | integer | 1 byte | | +| `int16_t` / `uint16_t` | integer | 2 bytes | little-endian | +| `int32_t` / `uint32_t` / `int` | integer | 4 bytes | little-endian | +| `int64_t` / `uint64_t` | integer | 8 bytes | little-endian | +| `double` | IEEE 754 | 8 bytes | little-endian | +| `StringRef` | length-prefixed bytes | `int32_t` len + bytes | | +| `std::string` | length-prefixed bytes | `int32_t` len + bytes | | +| `ProtocolVersion` | `uint64_t` | 8 bytes | version with feature flags | +| enums | underlying integer type | varies | typically `uint8_t` or `uint32_t` | + +**Type aliases** used throughout the protocol (all are simple typedefs): + +| Alias | Resolves To | Purpose | +|-------|-------------|---------| +| `Version` | `int64_t` | MVCC version number | +| `LogEpoch` | `uint64_t` | TLog generation epoch | +| `Generation` | `int64_t` | Cluster controller generation | +| `Sequence` | `uint64_t` | Sequence number | +| `DBRecoveryCount` | `uint64_t` | Sequential recovery counter | +| `Key` | `Standalone` → `StringRef` | Key bytes (arena-owned) | +| `KeyRef` | `StringRef` | Key bytes (ref) | +| `Value` | `Standalone` → `StringRef` | Value bytes (arena-owned) | +| `ValueRef` | `StringRef` | Value bytes (ref) | +| `TransactionTag` | `Standalone` → `StringRef` | Transaction tag string | +| `TransactionTagRef` | `StringRef` | Transaction tag ref | +| `SpanID` | `UID` | Tracing span identifier | + +**Generic containers** (serialization is count + elements): + +| Container | Wire Format | +|-----------|-------------| +| `std::vector` | `int32_t` count, then count × T | +| `VectorRef` | `int32_t` count, then count × T | +| `std::set` | `int32_t` count, then count × T | +| `std::map` | `int32_t` count, then count × (K, V) pairs | +| `std::unordered_map` | `int32_t` count, then count × (K, V) pairs | +| `std::deque` | `int32_t` count, then count × T | +| `Optional` | `bool` present, then (if true) T | +| `std::variant` | `uint8_t` index, then the active variant | +| `Standalone` | same as T (Arena is implicit) | + +--- + +## 2. Foundational Struct Definitions + +Every struct used as a field in protocol messages is defined here, in dependency +order. Each definition shows all serialized fields and their fully-resolved types. + +### UID +Universally unique identifier. Serialized as two `uint64_t` values. + +| Field | Type | Purpose | +|-------|------|---------| +| part[0] | `uint64_t` | First 8 bytes | +| part[1] | `uint64_t` | Second 8 bytes | + +### IPAddress +IP address supporting both IPv4 and IPv6. + +Serialized as: `bool isV6`, then either `uint32_t` (v4) or `std::array` (v6). + +### NetworkAddress +A network endpoint address. + +| Field | Type | Purpose | +|-------|------|---------| +| ip | `IPAddress` | IP address (see above) | +| port | `uint16_t` | Port number | +| flags | `uint16_t` | Flags (TLS, public, etc.) | +| fromHostname | `bool` | Whether resolved from hostname (conditional on protocol version) | + +### NetworkAddressList +A primary + optional secondary address for multi-homed processes. + +| Field | Type | Purpose | +|-------|------|---------| +| address | `NetworkAddress` | Primary address | +| secondaryAddress | `Optional` | Secondary address | + +### Hostname + +| Field | Type | Purpose | +|-------|------|---------| +| host | `std::string` | Hostname string | +| service | `std::string` | Service/port string | +| isTLS | `bool` | Whether TLS is enabled | + +### AddressExclusion +An IP address + optional port for excluding servers. + +| Field | Type | Purpose | +|-------|------|---------| +| ip | `IPAddress` | IP address | +| port | `int` (int32_t) | Port (0 = all ports on this IP) | + +### Endpoint +A network-addressable RPC endpoint. + +| Field | Type | Purpose | +|-------|------|---------| +| addresses | `NetworkAddressList` | Network addresses | +| token | `UID` | Endpoint token (two `uint64_t`) | + +### Tag +Identifies which storage server(s) should receive a mutation. + +| Field | Type | Purpose | +|-------|------|---------| +| locality | `int8_t` | Locality group (-1 = special, 0+ = DC index) | +| id | `uint16_t` | Tag ID within the locality | + +### KeyRangeRef +A contiguous range of keys [begin, end). + +| Field | Type | Purpose | +|-------|------|---------| +| begin | `KeyRef` → `StringRef` | Start key (inclusive) | +| end | `KeyRef` → `StringRef` | End key (exclusive) | + +**Note:** `KeyRange` is `Standalone` (same wire format, arena-owned). + +### KeyValueRef +A single key-value pair. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `KeyRef` → `StringRef` | Key bytes | +| value | `ValueRef` → `StringRef` | Value bytes | + +**Note:** `KeyValue` is `Standalone`. + +### KeySelectorRef +A relative key reference: "the first/last key ≥/≤ `key`, plus `offset`." + +| Field | Type | Purpose | +|-------|------|---------| +| key | `KeyRef` → `StringRef` | Anchor key | +| orEqual | `bool` | Whether to include the anchor key | +| offset | `int` (int32_t) | Offset from anchor | + +### RangeResultRef +Result of a range read. Inherits from `VectorRef` (vector of KV pairs). + +| Field | Type | Purpose | +|-------|------|---------| +| (inherited) | `VectorRef` | Key-value pairs | +| more | `bool` | Whether more results exist | +| readThrough | `Optional` | Read-through key | +| readToBegin | `bool` | Read reached beginning | +| readThroughEnd | `bool` | Read reached end | + +### MutationRef +A single database mutation. + +| Field | Type | Purpose | +|-------|------|---------| +| type | `uint8_t` | Mutation type (SetValue=0, ClearRange=1, AddValue=2, ...) | +| param1 | `StringRef` | First parameter (key or range start) | +| param2 | `StringRef` | Second parameter (value or range end) | +| checksum | `Optional` | Optional checksum (conditional on protocol version) | +| accumulativeChecksumIndex | `Optional` | Checksum index (conditional on protocol version) | + +### CommitTransactionRef +A complete transaction to be committed. + +| Field | Type | Purpose | +|-------|------|---------| +| read_conflict_ranges | `VectorRef` | Read conflict ranges | +| write_conflict_ranges | `VectorRef` | Write conflict ranges | +| mutations | `VectorRef` | Mutations to apply | +| read_snapshot | `Version` → `int64_t` | Read snapshot version | +| report_conflicting_keys | `bool` | Whether to report conflicts | +| lock_aware | `bool` | Whether lock-aware | +| spanContext | `Optional` | Tracing context | +| tenantIds | `Optional>` | Tenant IDs (conditional on protocol version) | + +### MutationsAndVersionRef +A set of mutations at a particular version. + +| Field | Type | Purpose | +|-------|------|---------| +| mutations | `VectorRef` | Mutations | +| version | `Version` → `int64_t` | Version | +| knownCommittedVersion | `Version` → `int64_t` | Known committed version | + +### SpanContext +OpenTelemetry-compatible distributed tracing context. + +| Field | Type | Purpose | +|-------|------|---------| +| traceID | `UID` (two `uint64_t`) | Trace identifier | +| spanID | `uint64_t` | Span identifier | +| m_Flags | `uint8_t` | Trace flags | + +### FailureStatus + +| Field | Type | Purpose | +|-------|------|---------| +| failed | `bool` | Whether the process is failed | + +### SystemFailureStatus + +| Field | Type | Purpose | +|-------|------|---------| +| addresses | `NetworkAddressList` | Process addresses | +| status | `FailureStatus` | Failure status (→ `bool failed`) | + +### LocalityData +Locality key-value map for a process. Serialized as a `uint64_t` map size followed +by (key, value) pairs. + +| Field | Type | Purpose | +|-------|------|---------| +| _data | `std::map, Optional>>` | Locality key-value pairs | + +### ProcessClass +Classification of a process (role fitness). + +| Field | Type | Purpose | +|-------|------|---------| +| _class | `int16_t` | Class type (unset, storage, transaction, resolution, ...) | +| _source | `int16_t` | Source (command line, configure, ...) | + +### ClusterControllerPriorityInfo + +| Field | Type | Purpose | +|-------|------|---------| +| processClassFitness | `uint8_t` | Fitness for cluster controller role | +| isExcluded | `bool` | Whether the process is excluded | +| dcFitness | `uint8_t` | Data center fitness | + +### StorageMetrics +Load metrics for a storage range. + +| Field | Type | Purpose | +|-------|------|---------| +| bytes | `int64_t` | Bytes stored | +| bytesWrittenPerKSecond | `int64_t` | Write rate | +| iosPerKSecond | `int64_t` | I/O rate | +| bytesReadPerKSecond | `int64_t` | Read rate | +| opsReadPerKSecond | `int64_t` | Read operation rate | + +### StorageBytes +Disk space information. + +| Field | Type | Purpose | +|-------|------|---------| +| free | `int64_t` | Free bytes | +| total | `int64_t` | Total bytes | +| used | `int64_t` | Used bytes | +| available | `int64_t` | Available bytes | + +### HealthMetrics +Cluster-wide health metrics. + +| Field | Type | Purpose | +|-------|------|---------| +| worstStorageQueue | `int64_t` | Worst SS queue size | +| limitingStorageQueue | `int64_t` | Limiting SS queue size | +| worstStorageDurabilityLag | `int64_t` | Worst SS durability lag | +| limitingStorageDurabilityLag | `int64_t` | Limiting SS durability lag | +| worstTLogQueue | `int64_t` | Worst TLog queue size | +| tpsLimit | `double` | Current TPS limit | +| batchLimited | `bool` | Whether batch is limited | +| storageStats | `std::map` | Per-SS stats | +| tLogQueue | `std::map` | Per-TLog queue sizes | + +#### HealthMetrics::StorageStats + +| Field | Type | Purpose | +|-------|------|---------| +| storageQueue | `int64_t` | Queue size | +| storageDurabilityLag | `int64_t` | Durability lag | +| diskUsage | `double` | Disk usage fraction | +| cpuUsage | `double` | CPU usage fraction | + +### BusyTagInfo +Information about a busy transaction tag on a storage server. + +| Field | Type | Purpose | +|-------|------|---------| +| tag | `TransactionTag` → `StringRef` | Tag string | +| rate | `double` | Tag rate | +| fractionalBusyness | `double` | Fraction of busyness | + +### DDMetricsRef +Data distribution shard metrics. + +| Field | Type | Purpose | +|-------|------|---------| +| shardBytes | `int64_t` | Bytes in shard | +| shardBytesPerKSecond | `int64_t` | Write rate | +| beginKey | `KeyRef` → `StringRef` | Shard begin key | + +### LifetimeToken +Identifies a master's lifetime for fencing. + +| Field | Type | Purpose | +|-------|------|---------| +| ccID | `UID` (two `uint64_t`) | Cluster controller ID | +| count | `int64_t` | Monotonic counter | + +### UniqueGeneration +Paxos generation identifier. + +| Field | Type | Purpose | +|-------|------|---------| +| generation | `uint64_t` | Generation number | +| uid | `UID` (two `uint64_t`) | Unique ID for tie-breaking | + +### LeaderInfo +Information about the current cluster controller leader. + +| Field | Type | Purpose | +|-------|------|---------| +| changeID | `UID` (two `uint64_t`) | Unique ID for this leader epoch | +| serializedInfo | `Value` → `StringRef` | Serialized `ClusterControllerClientInterface` | +| forward | `bool` | If true, serializedInfo is a forwarding address | + +### ClusterConnectionString + +| Field | Type | Purpose | +|-------|------|---------| +| coords | `std::vector` | Coordinator addresses | +| hostnames | `std::vector` | Coordinator hostnames | +| key | `Key` → `StringRef` | Cluster description key | +| keyDesc | `Key` → `StringRef` | Cluster description | + +### ClientVersionRef + +| Field | Type | Purpose | +|-------|------|---------| +| clientVersion | `StringRef` | Client version string | +| sourceVersion | `StringRef` | Source version hash | +| protocolVersion | `StringRef` | Protocol version string | + +### VersionHistory +Global configuration change record. + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Version of this config change | +| mutations | `Standalone>` | Config mutations | + +### ReadOptions +Options for read operations. + +| Field | Type | Purpose | +|-------|------|---------| +| type | `ReadType` → `uint8_t` | Read type (NORMAL, EAGER, FETCH) | +| cacheResult | `bool` | Whether to cache the result | +| debugID | `Optional` | Debug trace ID | +| consistencyCheckStartVersion | `Optional` | Consistency check version | +| lockAware | `bool` | Whether lock-aware | + +### TagSet +A set of transaction tags. Custom serialization. + +Serialized as a vector of `TransactionTagRef` (`StringRef`) entries. + +### ClientTagThrottleLimits + +| Field | Type | Purpose | +|-------|------|---------| +| tpsRate | `double` | Transactions per second rate limit | +| duration | `double` | Duration of throttle | + +### TransactionCommitCostEstimation + +| Field | Type | Purpose | +|-------|------|---------| +| opsSum | `int` (int32_t) | Total operation count | +| costSum | `uint64_t` | Total cost | + +### IdempotencyIdRef +A 16-byte idempotency identifier. Serialized as raw bytes with custom encoding. + +| Field | Type | Purpose | +|-------|------|---------| +| first | `uint64_t` | First 8 bytes (or size indicator) | +| second | `uint64_t` | Second 8 bytes (or pointer to data) | + +### KeyValueStoreType +Storage engine type enum. + +| Field | Type | Purpose | +|-------|------|---------| +| type | `uint32_t` | Engine type (SSD=1, MEMORY=2, SSD_ROCKSDB=4, ...) | + +### TLogVersion + +| Field | Type | Purpose | +|-------|------|---------| +| version | `uint32_t` | TLog version (V2=2, ..., V8=8) | + +### TLogSpillType + +| Field | Type | Purpose | +|-------|------|---------| +| type | `uint32_t` | Spill type (DEFAULT=0, VALUE=1, REFERENCE=2) | + +### EncryptionAtRestModeDeprecated + +| Field | Type | Purpose | +|-------|------|---------| +| mode | `uint32_t` | Encryption mode (DISABLED=0, DOMAIN_AWARE=1, CLUSTER_AWARE=2) | + +### MoveKeysLock + +| Field | Type | Purpose | +|-------|------|---------| +| prevOwner | `UID` (two `uint64_t`) | Previous lock owner | +| myOwner | `UID` (two `uint64_t`) | Current lock owner | +| prevWrite | `UID` (two `uint64_t`) | Previous write | + +### LogMessageVersion + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Version | +| sub | `uint32_t` | Sub-version within the version | + +### Versionstamp +Serialized in big-endian for ordered comparison. + +| Field | Type | Purpose | +|-------|------|---------| +| version | `int64_t` | Version (big-endian on wire) | +| batchNumber | `int16_t` | Batch number (big-endian on wire) | + +### StorageMetadataType + +| Field | Type | Purpose | +|-------|------|---------| +| createdTime | `double` | Creation timestamp | +| storeType | `KeyValueStoreType` → `uint32_t` | Storage engine type | + +### RecoveryState (enum) +Serialized as its underlying integer type. + +| Value | Name | Meaning | +|-------|------|---------| +| 0 | UNINITIALIZED | Not started | +| 1 | READING_CSTATE | Reading coordinated state | +| 2 | LOCKING_CSTATE | Locking coordinated state | +| 3 | RECRUITING | Recruiting transaction system | +| 4 | RECOVERY_TRANSACTION | Writing recovery transaction | +| 5 | WRITING_CSTATE | Writing coordinated state | +| 6 | ACCEPTING_COMMITS | Accepting commits | +| 7 | ALL_LOGS_RECRUITED | All logs recruited | +| 8 | STORAGE_RECOVERED | Storage servers recovered | +| 9 | FULLY_RECOVERED | Fully recovered | + +### ClusterType (enum) + +| Value | Name | +|-------|------| +| 0 | STANDALONE | +| 1 | LEGACY_UNUSED_METACLUSTER_MANAGEMENT | +| 2 | LEGACY_UNUSED_METACLUSTER_DATA | + +### LogSystemType (enum) + +| Value | Name | +|-------|------| +| -1 | unset | +| 0 | empty | +| 2 | tagPartitioned | + +### ResolverMoveRef +Describes a shard reassignment between resolvers. + +| Field | Type | Purpose | +|-------|------|---------| +| range | `KeyRangeRef` | Key range being moved | +| dest | `int` (int32_t) | Destination resolver index | + +### StateTransactionRef +A state transaction with its commit status. + +| Field | Type | Purpose | +|-------|------|---------| +| committed | `bool` | Whether committed | +| mutations | `VectorRef` | State mutations | + +### UnknownCommittedVersions +Tracks version ranges whose commit status is unknown during recovery. + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Version | +| prev | `Version` → `int64_t` | Previous version | +| tLogLocIds | `std::vector` | TLog locality IDs | + +### StorageServerShard +Physical shard metadata on a storage server. + +| Field | Type | Purpose | +|-------|------|---------| +| range | `KeyRange` | Shard key range | +| version | `Version` → `int64_t` | Shard version | +| id | `uint64_t` | Shard ID | +| desiredId | `uint64_t` | Desired shard ID | +| shardState | `int8_t` | Shard state (ReadWrite, Adding, etc.) | +| moveInShardId | `Optional` | Move-in shard ID if being moved | + +### CheckpointMetaData +Metadata describing a storage checkpoint. + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Checkpoint version | +| ranges | `std::vector` | Key ranges in checkpoint | +| format | `int16_t` | Checkpoint format | +| state | `int16_t` | Checkpoint state | +| checkpointID | `UID` (two `uint64_t`) | Checkpoint identifier | +| src | `std::vector` | Source servers | +| serializedCheckpoint | `Standalone` | Serialized checkpoint data | +| actionId | `Optional` | Action ID | +| bytesSampleFile | `Optional` | Sample file path | +| dir | `std::string` | Directory path | + +### OverlappingChangeFeedEntry +An entry describing a change feed that overlaps a key range. + +| Field | Type | Purpose | +|-------|------|---------| +| feedId | `KeyRef` → `StringRef` | Change feed identifier | +| range | `KeyRangeRef` | Feed's key range | +| emptyVersion | `Version` → `int64_t` | Version at which feed was empty | +| stopVersion | `Version` → `int64_t` | Version at which feed was stopped | +| feedMetadataVersion | `Version` → `int64_t` | Feed metadata version | + +### ReadHotRangeWithMetrics + +| Field | Type | Purpose | +|-------|------|---------| +| keys | `KeyRangeRef` | Hot key range | +| density | `double` | Read density | +| readBandwidthSec | `double` | Read bandwidth (bytes/sec) | +| bytes | `int64_t` | Bytes in range | +| readOpsSec | `double` | Read operations per second | + +### CheckSumMetaData + +| Field | Type | Purpose | +|-------|------|---------| +| range | `KeyRange` | Key range | +| version | `Version` → `int64_t` | Version | +| checkSumValue | `StringRef` | Checksum bytes | + +### BulkDumpState +State of a bulk dump job. + +| Field | Type | Purpose | +|-------|------|---------| +| jobId | `UID` (two `uint64_t`) | Job identifier | +| jobRange | `KeyRange` | Key range being dumped | +| phase | `uint8_t` | Dump phase | +| taskId | `Optional` | Task identifier | +| manifest | `BulkLoadManifest` | Manifest data (complex, serialized as bytes) | + +### AuditStorageState + +| Field | Type | Purpose | +|-------|------|---------| +| id | `UID` (two `uint64_t`) | Audit ID | +| auditServerId | `UID` (two `uint64_t`) | Server performing audit | +| range | `KeyRange` | Audited key range | +| type | `uint8_t` | Audit type | +| phase | `uint8_t` | Audit phase | +| error | `std::string` | Error message | +| ddId | `UID` (two `uint64_t`) | Data distributor ID | +| engineType | `KeyValueStoreType` → `uint32_t` | Storage engine type | + +### MappedKeyValueRef +A key-value pair with a secondary index lookup result. +Inherits from `KeyValueRef`. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `KeyRef` → `StringRef` | Primary key (inherited) | +| value | `ValueRef` → `StringRef` | Primary value (inherited) | +| reqAndResult | `std::variant` | Mapped result | + +#### GetValueReqAndResultRef + +| Field | Type | Purpose | +|-------|------|---------| +| key | `KeyRef` → `StringRef` | Looked-up key | +| result | `Optional` | Looked-up value | + +#### GetRangeReqAndResultRef + +| Field | Type | Purpose | +|-------|------|---------| +| begin | `KeySelectorRef` | Range begin selector | +| end | `KeySelectorRef` | Range end selector | +| result | `RangeResultRef` | Range result | + +### DatabaseConfiguration +Database configuration. Serialized as a key-value list. + +| Field | Type | Purpose | +|-------|------|---------| +| rawConfiguration | `Standalone>` | Configuration key-value pairs | + +The key-value pairs encode redundancy mode, storage engine, proxy/resolver/log counts, +region configuration, and other tuning parameters. + +### OpenDatabaseRequest::Samples +Sampling data for client issue/version tracking. + +| Field | Type | Purpose | +|-------|------|---------| +| count | `int` (int32_t) | Total count | +| samples | `std::set>` | Sampled (address, key) pairs | + +### DiskFailureCommand (nested in SetFailureInjection) + +| Field | Type | Purpose | +|-------|------|---------| +| stallInterval | `double` | Interval between stalls | +| stallPeriod | `double` | Duration of each stall | +| throttlePeriod | `double` | Throttle period | + +### FlipBitsCommand (nested in SetFailureInjection) + +| Field | Type | Purpose | +|-------|------|---------| +| percentBitFlips | `double` | Percentage of bits to flip | + +### SerializedSample +An actor lineage sample. + +| Field | Type | Purpose | +|-------|------|---------| +| time | `double` | Sample timestamp | +| data | `std::unordered_map` | Wait state → data map | + +`WaitState` is serialized as its underlying integer type. + +### PerfMetric + +| Field | Type | Purpose | +|-------|------|---------| +| m_name | `std::string` | Metric name | +| m_format_code | `std::string` | Format code | +| m_value | `double` | Metric value | +| m_averaged | `bool` | Whether averaged | + +--- + +## 3. Coordination Protocol + +**Source:** `fdbclient/include/fdbclient/CoordinationInterface.h`, +`fdbserver/core/include/fdbserver/core/CoordinationInterface.h` + +Coordinators run Paxos-based leader election and store the `DBCoreState`. + +### ProtocolInfoRequest +Handshake message to discover a peer's protocol version. + +| Field | Type | Purpose | +|-------|------|---------| +| reply | `ReplyPromise` → `UID` token | Where to send reply | + +**Reply: ProtocolInfoReply** + +| Field | Type | Purpose | +|-------|------|---------| +| version | `ProtocolVersion` → `uint64_t` | Protocol version of the responder | + +### GetLeaderRequest +Ask a coordinator who the current leader (cluster controller) is. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Cluster description key | +| knownLeader | `UID` | Last known leader change ID (long-poll) | +| reply | `ReplyPromise>` | Where to send reply | + +**Reply:** `Optional` — see [LeaderInfo](#leaderinfo) definition. + +### OpenDatabaseCoordRequest +Client requests current `ClientDBInfo` from coordinators. + +| Field | Type | Purpose | +|-------|------|---------| +| traceLogGroup | `Key` → `StringRef` | Client's trace log group | +| issues | `Standalone>` | Client-reported issues | +| supportedVersions | `Standalone>` | Client library versions | +| knownClientInfoID | `UID` | Last known DB info ID (long-poll) | +| clusterKey | `Key` → `StringRef` | Cluster connection key | +| hostnames | `std::vector` | Coordinator hostnames | +| coordinators | `std::vector` | Coordinator addresses | +| internal | `bool` | Whether this is an internal request | +| reply | `ReplyPromise>` | Where to send reply | + +**Reply:** `ClientDBInfo` — see [Section 4](#4-cluster-controller-protocol). + +### CheckDescriptorMutableRequest + +| Field | Type | Purpose | +|-------|------|---------| +| reply | `ReplyPromise` | Where to send reply | + +**Reply: CheckDescriptorMutableReply** — `bool isMutable`. + +### CandidacyRequest +Propose a candidate for cluster controller during leader election. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Cluster description key | +| myInfo | `LeaderInfo` | Candidate's leader info | +| knownLeader | `UID` | Currently known leader | +| prevChangeID | `UID` | Previous change ID | +| reply | `ReplyPromise>` | Election result | + +### ElectionResultRequest +Wait for the result of an election. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Cluster description key | +| hostnames | `std::vector` | Coordinator hostnames | +| coordinators | `std::vector` | Coordinator addresses | +| knownLeader | `UID` | Currently known leader | +| reply | `ReplyPromise>` | Election result | + +### LeaderHeartbeatRequest +Leader sends heartbeats to maintain leadership. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Cluster description key | +| myInfo | `LeaderInfo` | Leader's info | +| prevChangeID | `UID` | Previous change ID | +| reply | `ReplyPromise` | Heartbeat ack | + +**Reply: LeaderHeartbeatReply** — `bool value` (accepted or not). + +### ForwardRequest +Tell a coordinator to forward clients to a new connection string. + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Cluster description key | +| conn | `Value` → `StringRef` | New cluster connection string | +| reply | `ReplyPromise` | Acknowledgment | + +### GenerationRegReadRequest +Read from the generation register (Paxos storage layer). + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Register key | +| gen | `UniqueGeneration` | Generation to read at | +| reply | `ReplyPromise` | Register value | + +**Reply: GenerationRegReadReply** + +| Field | Type | Purpose | +|-------|------|---------| +| value | `Optional` | Stored value | +| gen | `UniqueGeneration` | Write generation | +| rgen | `UniqueGeneration` | Read generation | + +### GenerationRegWriteRequest +Write to the generation register. + +| Field | Type | Purpose | +|-------|------|---------| +| kv | `KeyValue` (→ `StringRef` key + `StringRef` value) | Key-value to write | +| gen | `UniqueGeneration` | Generation for this write | +| reply | `ReplyPromise` | Actual generation after write | + +--- + +## 4. Cluster Controller Protocol + +**Source:** `fdbclient/include/fdbclient/ClusterInterface.h` + +The cluster controller manages failure detection, role recruiting, and distributes +`ClientDBInfo` to clients. + +### Interface: ClusterInterface + +| Endpoint | Request Type | +|----------|-------------| +| openDatabase | `OpenDatabaseRequest` | +| failureMonitoring | `FailureMonitoringRequest` | +| databaseStatus | `StatusRequest` | +| ping | `ReplyPromise` | +| getClientWorkers | `GetClientWorkersRequest` | +| forceRecovery | `ForceRecoveryRequest` | +| moveShard | `MoveShardRequest` | +| repairSystemData | `RepairSystemDataRequest` | +| splitShard | `SplitShardRequest` | +| triggerAudit | `TriggerAuditRequest` | + +### OpenDatabaseRequest + +| Field | Type | Purpose | +|-------|------|---------| +| clientCount | `int` (int32_t) | Number of clients | +| issues | `std::map` | Client-reported issues | +| supportedVersions | `std::map, Samples>` | Client versions | +| maxProtocolSupported | `std::map` | Max protocol per client | +| knownClientInfoID | `UID` | Last known info ID (long-poll) | +| reply | `ReplyPromise` | Current database info | + +### ClientDBInfo +Returned to clients; contains everything needed to reach proxies. + +| Field | Type | Purpose | +|-------|------|---------| +| grvProxies | `std::vector` | GRV proxy interfaces | +| commitProxies | `std::vector` | Commit proxy interfaces | +| id | `UID` | Unique ID for this info snapshot | +| forward | `Optional` → `Optional` | Forwarding connection string | +| history | `std::vector` | Configuration version history | +| clusterId | `UID` | Cluster ID | +| clusterType | `ClusterType` → enum (int) | Cluster type | + +Note: the struct also contains `firstCommitProxy` (`Optional`) +but this field is **not serialized** — it is reconstructed locally from `commitProxies`. + +### FailureMonitoringRequest + +| Field | Type | Purpose | +|-------|------|---------| +| senderStatus | `Optional` | Sender's own status | +| failureInformationVersion | `Version` → `int64_t` | Last known failure info version | +| addresses | `NetworkAddressList` | Sender's addresses | +| reply | `ReplyPromise` | Failure updates | + +**Reply: FailureMonitoringReply** + +| Field | Type | Purpose | +|-------|------|---------| +| changes | `VectorRef` | Changed failure statuses | +| failureInformationVersion | `Version` → `int64_t` | Updated version | +| allOthersFailed | `bool` | Whether all other processes are failed | +| clientRequestIntervalMS | `int` (int32_t) | Suggested poll interval | +| considerServerFailedTimeoutMS | `int` (int32_t) | Failure timeout threshold | +| arena | `Arena` | Memory arena | + +### StatusRequest + +| Field | Type | Purpose | +|-------|------|---------| +| statusField | `std::string` | Optional field filter | +| reply | `ReplyPromise` | JSON status | + +**Reply: StatusReply** — serializes `statusStr` (`std::string`, JSON); `statusObj` is reconstructed on deserialization. + +### TriggerAuditRequest + +| Field | Type | Purpose | +|-------|------|---------| +| type | `uint8_t` | Audit type | +| range | `KeyRange` | Range to audit | +| id | `UID` | Audit ID | +| cancel | `bool` | Whether to cancel | +| engineType | `KeyValueStoreType` → `uint32_t` | Storage engine type | +| reply | `ReplyPromise` | Audit ID | + +### Other Cluster Controller Requests +- **GetClientWorkersRequest**: `reply` → `std::vector` +- **ForceRecoveryRequest**: `Key dcId`, `reply` → `Void` +- **MoveShardRequest**: `KeyRange shard`, `std::vector addresses`, `reply` → `Void` +- **RepairSystemDataRequest**: `reply` → `Void` +- **SplitShardRequest**: `KeyRange shard`, `int num`, `reply` → **SplitShardReply** {`std::vector shards`} + +--- + +## 5. Master Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/MasterInterface.h` + +The master coordinates recovery and hands out commit versions to proxies. + +### Interface: MasterInterface +Serializes `locality` and `waitFailure`; other endpoints reconstructed via offsets. + +| Endpoint | Request Type | +|----------|-------------| +| waitFailure | `ReplyPromise` | +| getCommitVersion | `GetCommitVersionRequest` | +| getLiveCommittedVersion | `GetRawCommittedVersionRequest` | +| reportLiveCommittedVersion | `ReportRawCommittedVersionRequest` | +| updateRecoveryData | `UpdateRecoveryDataRequest` | + +### GetCommitVersionRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| requestNum | `uint64_t` | Monotonic request number | +| mostRecentProcessedRequestNum | `uint64_t` | Most recent processed request | +| requestingProxy | `UID` | Proxy ID | +| reply | `ReplyPromise` | Assigned version | + +**Reply: GetCommitVersionReply** + +| Field | Type | Purpose | +|-------|------|---------| +| resolverChanges | `Standalone>` | Resolver shard reassignments | +| resolverChangesVersion | `Version` → `int64_t` | Version of changes | +| version | `Version` → `int64_t` | Assigned commit version | +| prevVersion | `Version` → `int64_t` | Previous commit version | +| requestNum | `uint64_t` | Echoed request number | + +### ReportRawCommittedVersionRequest + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Committed version | +| locked | `bool` | Database locked | +| metadataVersion | `Optional` | Metadata version | +| minKnownCommittedVersion | `Version` → `int64_t` | Min known committed | +| prevVersion | `Optional` | Previous version | +| writtenTags | `Optional>` | Tags written | +| reply | `ReplyPromise` | Acknowledgment | + +### UpdateRecoveryDataRequest + +| Field | Type | Purpose | +|-------|------|---------| +| recoveryTransactionVersion | `Version` → `int64_t` | Recovery txn version | +| lastEpochEnd | `Version` → `int64_t` | End of last epoch | +| commitProxies | `std::vector` | Proxy interfaces | +| resolvers | `std::vector` | Resolver interfaces | +| versionEpoch | `Optional` | Version epoch | +| primaryLocality | `int8_t` | Primary DC locality | +| reply | `ReplyPromise` | Acknowledgment | + +### ChangeCoordinatorsRequest + +| Field | Type | Purpose | +|-------|------|---------| +| newConnectionString | `Standalone` | New connection string | +| masterId | `UID` | Master ID | +| reply | `ReplyPromise` | Acknowledgment | + +--- + +## 6. GRV Proxy Protocol + +**Source:** `fdbclient/include/fdbclient/GrvProxyInterface.h` + +### Interface: GrvProxyInterface +Serializes `processId` (`Optional`), `provisional` (`bool`), and +`getConsistentReadVersion`; other endpoints reconstructed via offsets. + +### GetReadVersionRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| transactionCount | `uint32_t` | Transactions in batch | +| flags | `uint32_t` | Priority flags | +| tags | `TransactionTagMap` → `std::unordered_map` | Tag counts | +| debugID | `Optional` | Debug trace ID | +| maxVersion | `Version` → `int64_t` | Max acceptable version | +| reply | `ReplyPromise` | Read version | + +**Reply: GetReadVersionReply** + +| Field | Type | Purpose | +|-------|------|---------| +| processBusyTime | `double` | Proxy busy time | +| version | `Version` → `int64_t` | Assigned read version | +| locked | `bool` | Database locked | +| metadataVersion | `Optional` | Metadata version | +| tagThrottleInfo | `TransactionTagMap` | Per-tag throttle limits | +| midShardSize | `int64_t` | Median shard size | +| rkDefaultThrottled | `bool` | Ratekeeper throttling default | +| rkBatchThrottled | `bool` | Ratekeeper throttling batch | +| ssVersionVectorDelta | `VersionVector` | SS version vector delta (custom serialization) | +| proxyId | `UID` | Proxy ID | + +### GlobalConfigRefreshRequest + +| Field | Type | Purpose | +|-------|------|---------| +| lastKnown | `Version` → `int64_t` | Last known config version | +| reply | `ReplyPromise` | Updated config | + +**Reply: GlobalConfigRefreshReply**: `Version version`, `RangeResultRef result`, `Arena arena`. + +--- + +## 7. Commit Proxy Protocol + +**Source:** `fdbclient/include/fdbclient/CommitProxyInterface.h` + +### Interface: CommitProxyInterface +Serializes `processId` (`Optional`), `provisional` (`bool`), and `commit`; +11 other endpoints reconstructed via offsets 1–10 and 13 (offsets 11–12 are +reserved/unused). + +### CommitTransactionRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| transaction | `CommitTransactionRef` | Transaction to commit | +| flags | `uint32_t` | Commit flags | +| debugID | `Optional` | Debug trace ID | +| commitCostEstimation | `Optional` | Cost estimate (see below) | +| tagSet | `Optional` | Transaction tags | +| idempotencyId | `IdempotencyIdRef` | Idempotency identifier | +| arena | `Arena` | Memory arena | +| reply | `ReplyPromise` | Commit result | + +#### ClientTrCommitCostEstimation + +| Field | Type | Purpose | +|-------|------|---------| +| opsCount | `int` (int32_t) | Operation count | +| writeCosts | `uint64_t` | Write cost estimate | +| clearIdxCosts | `std::deque>` | Clear index costs | +| expensiveCostEstCount | `uint32_t` | Expensive estimate count | + +**Reply: CommitID** + +| Field | Type | Purpose | +|-------|------|---------| +| version | `Version` → `int64_t` | Committed version | +| txnBatchId | `uint16_t` | Batch identifier | +| metadataVersion | `Optional` | Metadata version | +| conflictingKRIndices | `Optional>>` | Conflicting key range indices | + +### GetKeyServerLocationsRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| begin | `KeyRef` → `StringRef` | Range begin key | +| end | `Optional` | Range end key | +| limit | `int` (int32_t) | Max results | +| reverse | `bool` | Reverse iteration | +| legacyVersion | `Version` → `int64_t` | Legacy version | +| arena | `Arena` | Memory arena | +| reply | `ReplyPromise` | Locations | + +**Reply: GetKeyServerLocationsReply** + +| Field | Type | Purpose | +|-------|------|---------| +| results | `std::vector>>` | Range→SS mapping | +| resultsTssMapping | `std::vector>` | TSS pairs | +| resultsTagMapping | `std::vector>` | SS→Tag mapping | +| arena | `Arena` | Memory arena | + +### Other Commit Proxy Messages +- **GetRawCommittedVersionRequest**: `SpanContext`, `Optional debugID`, `Version maxVersion`, `reply` → **GetRawCommittedVersionReply** {`Optional debugID`, `Version version`, `bool locked`, `Optional metadataVersion`, `Version minKnownCommittedVersion`, `VersionVector ssVersionVectorDelta`} +- **GetStorageServerRejoinInfoRequest**: `UID id`, `Optional dcId`, `reply` → **GetStorageServerRejoinInfoReply** {`Version version`, `Tag tag`, `Optional newTag`, `bool newLocality`, `std::vector> history`, `EncryptionAtRestModeDeprecated encryptMode`} +- **TxnStateRequest**: `VectorRef data`, `Sequence sequence` (uint64_t), `bool last`, `std::vector broadcastInfo`, `Arena arena`, `reply` → `Void` +- **GetHealthMetricsRequest**: `bool detailed`, `reply` → **GetHealthMetricsReply** {`Standalone serialized`} +- **GetDDMetricsRequest**: `KeyRange keys`, `int shardLimit`, `reply` → **GetDDMetricsReply** {`Standalone> storageMetricsList`} +- **ProxySnapRequest**: `StringRef snapPayload`, `UID snapUID`, `Optional debugID`, `Arena arena`, `reply` → `Void` +- **ExclusionSafetyCheckRequest**: `std::vector exclusions`, `reply` → **ExclusionSafetyCheckReply** {`bool safe`} +- **SetThrottledShardRequest**: `std::vector throttledShards`, `double expirationTime`, `reply` → **SetThrottledShardReply** {} +- **ExpireIdempotencyIdRequest** (fire-and-forget): `Version commitVersion`, `uint8_t batchIndexHighByte` +- **GlobalConfigRefreshRequest**: `Version lastKnown`, `reply` → **GlobalConfigRefreshReply** {`Version version`, `RangeResultRef result`, `Arena arena`} + +--- + +## 8. Resolver Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/ResolverInterface.h` + +### Interface: ResolverInterface +All endpoints serialized directly: `uniqueID` (`UID`), `locality` (`LocalityData`), +`resolve`, `metrics`, `split`, `waitFailure`, `txnState`. + +### ResolveTransactionBatchRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| prevVersion | `Version` → `int64_t` | Previous batch version | +| version | `Version` → `int64_t` | This batch's version | +| lastReceivedVersion | `Version` → `int64_t` | Last received by this resolver | +| transactions | `VectorRef` | Transactions to check | +| txnStateTransactions | `VectorRef` | Indices of state transactions | +| debugID | `Optional` | Debug trace ID | +| writtenTags | `std::set` | Tags written | +| lastShardMove | `Version` → `int64_t` | Last shard move version | +| arena | `Arena` | Memory arena | +| reply | `ReplyPromise` | Results | + +**Reply: ResolveTransactionBatchReply** + +| Field | Type | Purpose | +|-------|------|---------| +| committed | `VectorRef` | Per-txn status (0=ok, 1=conflict, ...) | +| stateMutations | `VectorRef>` | State mutations | +| debugID | `Optional` | Debug trace ID | +| conflictingKeyRangeMap | `std::map>` | Conflict details | +| privateMutations | `VectorRef` | Private mutations | +| privateMutationCount | `uint32_t` | Count | +| tpcvMap | `std::unordered_map` | Per-proxy committed versions | +| writtenTags | `std::set` | Tags written | +| lastShardMove | `Version` → `int64_t` | Last shard move | +| arena | `Arena` | Memory arena | + +### Other Resolver Messages +- **ResolutionMetricsRequest**: `reply` → **ResolutionMetricsReply** {`int64_t value`} +- **ResolutionSplitRequest**: `KeyRange range`, `int64_t offset`, `bool front`, `reply` → **ResolutionSplitReply** {`Key key`, `int64_t used`} + +--- + +## 9. Transaction Log (TLog) Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/TLogInterface.h` + +### Interface: TLogInterface +Serializes `uniqueID` (`UID`), `sharedTLogID` (`UID`), `filteredLocality` (`LocalityData`), +and `peekMessages`; 12 other endpoints reconstructed via offsets. + +### TLogCommitRequest + +| Field | Type | Purpose | +|-------|------|---------| +| spanContext | `SpanContext` | Tracing context | +| prevVersion | `Version` → `int64_t` | Previous version | +| version | `Version` → `int64_t` | Commit version | +| knownCommittedVersion | `Version` → `int64_t` | Known committed version | +| minKnownCommittedVersion | `Version` → `int64_t` | Min known committed | +| seqPrevVersion | `Version` → `int64_t` | Sequential previous version | +| messages | `StringRef` | Serialized mutation messages | +| tLogCount | `uint16_t` | Number of TLogs | +| tLogLocIds | `std::vector` | TLog locality IDs | +| debugID | `Optional` | Debug trace ID | +| arena | `Arena` | Memory arena | +| reply | `ReplyPromise` | Acknowledgment | + +**Reply: TLogCommitReply** — `Version version` (int64_t). + +### TLogPeekRequest + +| Field | Type | Purpose | +|-------|------|---------| +| begin | `Version` → `int64_t` | Start version | +| tag | `Tag` | Tag to peek | +| returnIfBlocked | `bool` | Return immediately if blocked | +| onlySpilled | `bool` | Only spilled data | +| sequence | `Optional>` | Ordering sequence | +| end | `Optional` | End version | +| returnEmptyIfStopped | `Optional` | Return empty if stopped | +| reply | `ReplyPromise` | Mutations | + +**Reply: TLogPeekReply** + +| Field | Type | Purpose | +|-------|------|---------| +| messages | `StringRef` | Serialized mutations | +| end | `Version` → `int64_t` | End version | +| popped | `Optional` | Popped version | +| maxKnownVersion | `Version` → `int64_t` | Max known version | +| minKnownCommittedVersion | `Version` → `int64_t` | Min known committed | +| begin | `Optional` | Actual begin | +| onlySpilled | `bool` | Only from spill | +| arena | `Arena` | Memory arena | + +### TLogPeekStreamRequest + +| Field | Type | Purpose | +|-------|------|---------| +| begin | `Version` → `int64_t` | Start version | +| tag | `Tag` | Tag to peek | +| returnIfBlocked | `bool` | Return immediately | +| limitBytes | `int` (int32_t) | Byte limit per reply | +| end | `Optional` | End version | +| returnEmptyIfStopped | `Optional` | Return empty if stopped | +| reply | `ReplyPromiseStream` | Streaming reply | + +**Reply: TLogPeekStreamReply** (extends ReplyPromiseStreamReply) + +| Field | Type | Purpose | +|-------|------|---------| +| acknowledgeToken | `Optional` | Backpressure token | +| sequence | `uint16_t` | Sequence number | +| rep | `TLogPeekReply` | Peek data (see above) | + +### Other TLog Messages +- **TLogPopRequest**: `Version to`, `Version durableKnownCommittedVersion`, `Tag tag`, `reply` → `Void` +- **TLogQueuingMetricsRequest**: `reply` → **TLogQueuingMetricsReply** {`double localTime`, `int64_t instanceID`, `int64_t bytesDurable`, `int64_t bytesInput`, `StorageBytes storageBytes`, `Version v`} +- **TLogConfirmRunningRequest**: `Optional debugID`, `reply` → `Void` +- **TLogRecoveryFinishedRequest**: `reply` → `Void` +- **TLogDisablePopRequest**: `UID snapUID`, `Optional debugID`, `reply` → `Void` +- **TLogEnablePopRequest**: `UID snapUID`, `Optional debugID`, `reply` → `Void` +- **TLogSnapRequest**: `StringRef snapPayload`, `UID snapUID`, `StringRef role`, `Arena arena`, `reply` → `Void` +- **TrackTLogRecoveryRequest**: `Version oldestGenRecoverAtVersion`, `reply` → **TrackTLogRecoveryReply** {`Version oldestUnrecoveredStartVersion`} +- **TLogLockResult** (reply to `lock` endpoint): `Version end`, `Version knownCommittedVersion`, `std::deque unknownCommittedVersions`, `UID id`, `UID logId` + +--- + +## 10. Storage Server Protocol + +**Source:** `fdbclient/include/fdbclient/StorageServerInterface.h` + +### Interface: StorageServerInterface +Serializes `uniqueID` (`UID`), `locality` (`LocalityData`), `getValue`, +`tssPairID` (`Optional`), and `acceptingRequests` (`bool`); 30+ other +endpoints reconstructed via offsets. + +### GetValueRequest + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Key to read | +| version | `Version` → `int64_t` | Read version | +| tags | `Optional` | Transaction tags | +| reply | `ReplyPromise` | Value | +| spanContext | `SpanContext` | Tracing context | +| options | `Optional` | Read options | +| ssLatestCommitVersions | `VersionVector` | SS version vector | + +**Reply: GetValueReply** + +| Field | Type | Purpose | +|-------|------|---------| +| penalty | `double` | Load balancing penalty | +| error | `Optional` | Error (serialized as error code) | +| value | `Optional` → `Optional` | The value | +| cached | `bool` | From cache | + +### GetKeyRequest + +| Field | Type | Purpose | +|-------|------|---------| +| sel | `KeySelectorRef` | Key selector | +| version | `Version` → `int64_t` | Read version | +| tags | `Optional` | Transaction tags | +| reply | `ReplyPromise` | Resolved key | +| spanContext | `SpanContext` | Tracing context | +| options | `Optional` | Read options | +| ssLatestCommitVersions | `VersionVector` | SS version vector | +| arena | `Arena` | Memory arena | + +**Reply: GetKeyReply** — `double penalty`, `Optional error`, `KeySelector sel`, `bool cached`. + +### GetKeyValuesRequest + +| Field | Type | Purpose | +|-------|------|---------| +| begin | `KeySelectorRef` | Range begin | +| end | `KeySelectorRef` | Range end | +| version | `Version` → `int64_t` | Read version | +| limit | `int` (int32_t) | Row limit | +| limitBytes | `int` (int32_t) | Byte limit | +| tags | `Optional` | Transaction tags | +| reply | `ReplyPromise` | Results | +| spanContext | `SpanContext` | Tracing context | +| options | `Optional` | Read options | +| ssLatestCommitVersions | `VersionVector` | SS version vector | +| taskID | `Optional` → `Optional` | Task priority | +| arena | `Arena` | Memory arena | + +**Reply: GetKeyValuesReply** — `double penalty`, `Optional error`, `VectorRef data`, `Version version`, `bool more`, `bool cached`, `Arena arena`. + +### GetMappedKeyValuesRequest +Same as `GetKeyValuesRequest` plus `KeyRef mapper`. Reply: `VectorRef data`, `Version version`, `bool more`, `bool cached`, `Arena arena`. + +### GetKeyValuesStreamRequest +Same fields as `GetKeyValuesRequest` (without `taskID`) but with `ReplyPromiseStream reply`. + +**Reply: GetKeyValuesStreamReply** — `Optional acknowledgeToken`, `uint16_t sequence`, `VectorRef data`, `Version version`, `bool more`, `bool cached`, `Arena arena`. + +### WatchValueRequest + +| Field | Type | Purpose | +|-------|------|---------| +| key | `Key` → `StringRef` | Key to watch | +| value | `Optional` | Expected current value | +| version | `Version` → `int64_t` | Version of expected value | +| tags | `Optional` | Transaction tags | +| debugID | `Optional` | Debug trace ID | +| reply | `ReplyPromise` | Watch trigger | +| spanContext | `SpanContext` | Tracing context | + +**Reply: WatchValueReply** — `Version version`, `bool cached`. + +### ChangeFeedStreamRequest + +| Field | Type | Purpose | +|-------|------|---------| +| rangeID | `Key` → `StringRef` | Change feed ID | +| begin | `Version` → `int64_t` | Start version | +| end | `Version` → `int64_t` | End version | +| range | `KeyRange` | Key range | +| reply | `ReplyPromiseStream` | Streaming reply | +| spanContext | `SpanContext` | Tracing context | +| replyBufferSize | `int` (int32_t) | Buffer size | +| canReadPopped | `bool` | Can read popped data | +| id | `UID` | Request ID | +| options | `Optional` | Read options | +| encrypted | `bool` | Whether to encrypt | +| arena | `Arena` | Memory arena | + +**Reply: ChangeFeedStreamReply** — `Optional acknowledgeToken`, `uint16_t sequence`, `VectorRef mutations`, `bool atLatestVersion`, `Version minStreamVersion`, `Version popVersion`, `Arena arena`. + +### Other Storage Server Messages +- **GetShardStateRequest**: `KeyRange keys`, `int32_t mode`, `bool includePhysicalShard`, `reply` → **GetShardStateReply** {`Version first`, `Version second`, `std::vector shards`} +- **WaitMetricsRequest**: `KeyRangeRef keys`, `StorageMetrics min`, `StorageMetrics max`, `Version legacyVersion`, `Arena arena`, `reply` → `StorageMetrics` +- **SplitMetricsRequest**: `KeyRangeRef keys`, `StorageMetrics limits/used/estimated`, `bool isLastShard`, `Optional minSplitBytes`, `Arena arena`, `reply` → **SplitMetricsReply** {`Standalone> splits`, `StorageMetrics used`, `bool more`} +- **GetStorageMetricsRequest**: `reply` → **GetStorageMetricsReply** {`StorageMetrics load/available/capacity`, `double bytesInputRate`, `int64_t versionLag`, `double lastUpdate`, `int64_t bytesDurable`, `int64_t bytesInput`, `int ongoingBulkLoadTaskCount`} +- **StorageQueuingMetricsRequest**: `reply` → **StorageQueuingMetricsReply** {`double localTime`, `int64_t instanceID/bytesDurable/bytesInput`, `StorageBytes storageBytes`, `Version version/durableVersion`, `double cpuUsage/diskUsage/localRateLimit`, `std::vector busiestTags`} +- **OverlappingChangeFeedsRequest**: `KeyRange range`, `Version minVersion`, `reply` → **OverlappingChangeFeedsReply** {`VectorRef feeds`, `Version feedMetadataVersion`, `Arena arena`} +- **ChangeFeedPopRequest**: `Key rangeID`, `Version version`, `KeyRange range`, `reply` → `Void` +- **ChangeFeedVersionUpdateRequest**: `Version minVersion`, `reply` → **ChangeFeedVersionUpdateReply** {`Version version`} +- **GetCheckpointRequest**: `Version version`, `std::vector ranges`, `int16_t format`, `Optional actionId`, `reply` → `CheckpointMetaData` +- **FetchCheckpointRequest**: `UID checkpointID`, `Standalone token`, `reply` (stream) → **FetchCheckpointReply** {`Optional acknowledgeToken`, `uint16_t sequence`, `Standalone token`, `Standalone data`} +- **FetchCheckpointKeyValuesRequest**: `UID checkpointID`, `KeyRange range`, `reply` (stream) → **FetchCheckpointKeyValuesStreamReply** {`Optional acknowledgeToken`, `uint16_t sequence`, `VectorRef data`, `Arena arena`} +- **ReadHotSubRangeRequest**: `KeyRangeRef keys`, `uint8_t type`, `int chunkCount`, `Arena arena`, `reply` → **ReadHotSubRangeReply** {`Standalone> readHotRanges`} +- **SplitRangeRequest**: `KeyRangeRef keys`, `int64_t chunkSize`, `Arena arena`, `reply` → **SplitRangeReply** {`Standalone> splitPoints`} +- **UpdateCommitCostRequest**: `UID ratekeeperID`, `double postTime/elapsed`, `TransactionTag busiestTag`, `int opsSum`, `uint64_t costSum/totalWriteCosts`, `bool reported`, `reply` → `Void` +- **GetHotShardsRequest**: `reply` → **GetHotShardsReply** {`std::vector hotShards`} +- **GetStorageCheckSumRequest**: `std::vector>> ranges`, `Optional actionId`, `uint8_t checkSumMethod`, `reply` → **GetStorageCheckSumReply** {`std::vector checkSums`, `uint8_t checkSumMethod`} +- **BulkDumpRequest**: `std::vector checksumServers`, `BulkDumpState bulkDumpState`, `reply` → `BulkDumpState` +- **AuditStorageRequest**: `UID id/ddId`, `KeyRange range`, `uint8_t type`, `std::vector targetServers`, `reply` → `Void` + +--- + +## 11. Data Distributor Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/DataDistributorInterface.h` + +### Interface: DataDistributorInterface +Serializes all endpoints directly: `waitFailure`, `haltDataDistributor`, +`locality` (`LocalityData`), `myId` (`UID`), and 7 additional `RequestStream`s. + +- **HaltDataDistributorRequest**: `UID requesterID`, `reply` → `Void` +- **GetDataDistributorMetricsRequest**: `KeyRange keys`, `int shardLimit`, `bool midOnly`, `reply` → **GetDataDistributorMetricsReply** {`Standalone> storageMetricsList`, `Optional midShardSize`} +- **DistributorSnapRequest**: `StringRef snapPayload`, `UID snapUID`, `Optional debugID`, `Arena arena`, `reply` → `Void` +- **DistributorExclusionSafetyCheckRequest**: `std::vector exclusions`, `reply` → **DistributorExclusionSafetyCheckReply** {`bool safe`} +- **DistributorSplitRangeRequest**: `std::vector splitPoints`, `reply` → **SplitShardReply** {`std::vector shards`} +- **GetStorageWigglerStateRequest**: `reply` → **GetStorageWigglerStateReply** {`uint8_t primary`, `uint8_t remote`} +- **PrepareBlobRestoreRequest**: `UID requesterID`, `StorageServerInterface ssi`, `KeyRange keys`, `reply` → **PrepareBlobRestoreReply** {`int8_t res`} + +--- + +## 12. Ratekeeper Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/RatekeeperInterface.h` + +### Interface: RatekeeperInterface +Serializes all endpoints directly: `waitFailure`, `getRateInfo`, `haltRatekeeper`, +`reportCommitCostEstimation`, `getSSVersionLag`, `locality` (`LocalityData`), `myId` (`UID`). + +### GetRateInfoRequest + +| Field | Type | Purpose | +|-------|------|---------| +| requesterID | `UID` | Proxy ID | +| totalReleasedTransactions | `int64_t` | Total released | +| batchReleasedTransactions | `int64_t` | Batch released | +| version | `Version` → `int64_t` | Current version | +| throttledTagCounts | `TransactionTagMap` | Throttled tag counts | +| detailed | `bool` | Include detailed metrics | +| reply | `ReplyPromise` | Rate limits | + +**Reply: GetRateInfoReply** + +| Field | Type | Purpose | +|-------|------|---------| +| transactionRate | `double` | Default TPS limit | +| batchTransactionRate | `double` | Batch TPS limit | +| leaseDuration | `double` | Rate lease duration | +| healthMetrics | `HealthMetrics` | Cluster health | +| clientThrottledTags | `Optional>` | Client throttles | + +`PrioritizedTransactionTagMap` is `std::map>` where `TransactionPriority` is a `uint8_t` enum. + +### Other Ratekeeper Messages +- **HaltRatekeeperRequest**: `UID requesterID`, `reply` → `Void` +- **ReportCommitCostEstimationRequest**: `UIDTransactionTagMap ssTrTagCommitCost`, `reply` → `Void` +- **GetSSVersionLagRequest**: `reply` → **GetSSVersionLagReply** {`Version maxPrimarySSVersion`, `Version maxRemoteSSVersion`} + +--- + +## 13. Worker Protocol + +**Source:** `fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h` + +Workers host server roles. The cluster controller sends initialization requests. + +### Interface: WorkerInterface +Serializes all 24 `RequestStream` endpoints and `TesterInterface` directly. + +### RegisterWorkerRequest + +| Field | Type | Purpose | +|-------|------|---------| +| wi | `WorkerInterface` | Worker's interface | +| initialClass | `ProcessClass` | Initial class | +| processClass | `ProcessClass` | Current class | +| priorityInfo | `ClusterControllerPriorityInfo` | Priority info | +| generation | `Generation` → `int64_t` | Cluster generation | +| distributorInterf | `Optional` | DD interface | +| ratekeeperInterf | `Optional` | RK interface | +| consistencyScanInterf | `Optional` | CS interface | +| issues | `Standalone>` | Worker issues | +| incompatiblePeers | `std::vector` | Incompatible peers | +| degraded | `bool` | Worker degraded | +| recoveredDiskFiles | `bool` | Disk files recovered | +| clusterId | `Optional` | Cluster ID | +| reply | `ReplyPromise` | Registration result | + +**Reply: RegisterWorkerReply** — `ProcessClass processClass`, `ClusterControllerPriorityInfo priorityInfo`. + +### Role Initialization Requests + +All follow the pattern: fields describing the role configuration + `ReplyPromise`. + +- **RecruitMasterRequest**: `LifetimeToken lifetime`, `bool forceRecovery`, `reply` → `MasterInterface` +- **InitializeTLogRequest**: `UID recruitmentID`, `LogSystemConfig recoverFrom`, `Version recoverAt/knownCommittedVersion`, `LogEpoch epoch`, `std::vector recoverTags/allTags`, `TLogVersion logVersion`, `KeyValueStoreType storeType`, `TLogSpillType spillType`, `Tag remoteTag`, `int8_t locality`, `bool isPrimary`, `Version startVersion`, `int logRouterTags/txsTags`, `Version recoveryTransactionVersion`, `std::vector oldGenerationRecoverAtVersions`, `reply` → `TLogInterface` +- **InitializeCommitProxyRequest**: `MasterInterface master`, `LifetimeToken masterLifetime`, `uint64_t recoveryCount`, `Version recoveryTransactionVersion`, `bool firstProxy`, `EncryptionAtRestModeDeprecated encryptMode`, `uint16_t commitProxyIndex`, `reply` → `CommitProxyInterface` +- **InitializeGrvProxyRequest**: `MasterInterface master`, `LifetimeToken masterLifetime`, `uint64_t recoveryCount`, `reply` → `GrvProxyInterface` +- **InitializeResolverRequest**: `LifetimeToken masterLifetime`, `uint64_t recoveryCount`, `int commitProxyCount/resolverCount`, `UID masterId`, `EncryptionAtRestModeDeprecated encryptMode`, `reply` → `ResolverInterface` +- **InitializeStorageRequest**: `Tag seedTag`, `UID reqId/interfaceId`, `KeyValueStoreType storeType`, `Optional> tssPairIDAndVersion`, `Version initialClusterVersion`, `EncryptionAtRestModeDeprecated encryptMode`, `reply` → **InitializeStorageReply** {`StorageServerInterface interf`, `Version addedVersion`} +- **InitializeDataDistributorRequest**: `UID reqId`, `reply` → `DataDistributorInterface` +- **InitializeRatekeeperRequest**: `UID reqId`, `reply` → `RatekeeperInterface` +- **InitializeConsistencyScanRequest**: `UID reqId`, `reply` → `ConsistencyScanInterface` +- **InitializeLogRouterRequest**: `uint64_t recoveryCount`, `Tag routerTag`, `Version startVersion`, `std::vector tLogLocalities`, `Reference tLogPolicy`, `int8_t locality`, `Optional recoverAt`, `Optional>> knownLockedTLogIds`, `bool allowDropInSim/isReplacement`, `UID reqId`, `reply` → `TLogInterface` +- **InitializeBackupRequest**: `UID reqId`, `LogEpoch recruitedEpoch/backupEpoch`, `Tag routerTag`, `int totalTags`, `Version startVersion`, `Optional endVersion`, `reply` → **InitializeBackupReply** {`BackupInterface interf`, `LogEpoch backupEpoch`} + +### Recruitment Requests +- **RecruitFromConfigurationRequest**: `DatabaseConfiguration configuration`, `bool recruitSeedServers`, `int maxOldLogRouters`, `reply` → **RecruitFromConfigurationReply** {`std::vector` for: tLogs, satelliteTLogs, commitProxies, grvProxies, resolvers, storageServers, oldLogRouters, backupWorkers; `Optional dcId`, `bool satelliteFallback`} +- **RecruitRemoteFromConfigurationRequest**: `DatabaseConfiguration configuration`, `Optional dcId`, `int logRouterCount`, `std::vector exclusionWorkerIds`, `Optional dbgId`, `reply` → **RecruitRemoteFromConfigurationReply** {`std::vector remoteTLogs/logRouters`, `Optional dbgId`} +- **RecruitStorageRequest**: `std::vector>> excludeMachines/includeDCs`, `std::vector excludeAddresses`, `bool criticalRecruitment`, `reply` → **RecruitStorageReply** {`WorkerInterface worker`, `ProcessClass processClass`} + +### Other Worker Messages +- **RegisterMasterRequest**: `UID id`, `LocalityData mi`, `LogSystemConfig logSystemConfig`, `std::vector commitProxies`, `std::vector grvProxies`, `std::vector resolvers`, `DBRecoveryCount recoveryCount`, `int64_t registrationCount`, `Optional configuration`, `std::vector priorCommittedLogServers`, `RecoveryState recoveryState`, `bool recoveryStalled` (no reply) +- **GetWorkersRequest**: `int flags`, `reply` → `std::vector` where **WorkerDetails** = {`WorkerInterface interf`, `ProcessClass processClass`, `bool degraded`, `bool recoveredDiskFiles`} +- **TLogRejoinRequest**: `TLogInterface myInterface`, `reply` → **TLogRejoinReply** {`bool masterIsRecovered`} +- **BackupWorkerDoneRequest**: `UID workerUID`, `LogEpoch backupEpoch`, `reply` → `Void` +- **GetEncryptionAtRestModeRequest**: `UID tlogId`, `reply` → **GetEncryptionAtRestModeResponse** {`uint32_t mode`} +- **CoordinationPingMessage** (fire-and-forget): `UID clusterControllerId`, `int64_t timeStep` +- **SetMetricsLogRateRequest** (fire-and-forget): `uint32_t metricsLogsPerSecond` +- **UpdateWorkerHealthRequest** (fire-and-forget): `NetworkAddress address`, `std::vector degradedPeers/disconnectedPeers/recoveredPeers` +- **LoadedPingRequest**: `UID id`, `bool loadReply`, `Standalone payload`, `reply` → **LoadedReply** {`Standalone payload`, `UID id`} +- **EventLogRequest**: `bool getLastError`, `Standalone eventName`, `reply` → `TraceEventFields` +- **TraceBatchDumpRequest**: `reply` → `Void` +- **ExecuteRequest**: `StringRef execPayload`, `Arena arena`, `reply` → `Void` +- **WorkerSnapRequest**: `StringRef snapPayload`, `UID snapUID`, `StringRef role`, `Arena arena`, `reply` → `Void` +- **DiskStoreRequest**: `bool includePartialStores`, `reply` → `Standalone>` + +--- + +## 14. Backup / Consistency Scan / Test Protocols + +### BackupInterface +`RequestStream> waitFailure`, `LocalityData locality`. + +### ConsistencyScanInterface +`RequestStream> waitFailure`, `RequestStream haltConsistencyScan`, `LocalityData locality`, `UID myId`. + +**HaltConsistencyScanRequest**: `UID requesterID`, `reply` → `Void`. + +### TesterInterface +`RequestStream recruitments`. + +**WorkloadRequest**: `StringRef title`, `int timeout`, `double databasePingDelay`, `int64_t sharedRandomNumber`, `bool useDatabase/runFailureWorkloads`, `VectorRef> options`, `std::vector rangesToCheck`, `int clientId/clientCount`, `std::vector disabledFailureInjectionWorkloads`, `Arena arena`, `reply` → **WorkloadInterface** {`RequestStream` endpoints for setup/start/check/metrics/stop}. + +### 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 + +### ClientWorkerInterface +`RequestStream reboot`, `RequestStream profiler`, `RequestStream setFailureInjection`, `Optional grpcAddress`. + +- **RebootRequest** (fire-and-forget): `bool deleteData`, `bool checkData`, `uint32_t waitForDuration` +- **ProfilerRequest**: `Type` (enum/int), `Action` (enum/int), `int duration`, `Standalone outputFile`, `reply` → `Void` +- **SetFailureInjection**: `Optional diskFailure`, `Optional flipBits`, `reply` → `Void` + +### ProcessInterface +`RequestStream getInterface`, `RequestStream actorLineage`. + +- **GetProcessInterfaceRequest**: `reply` → `ProcessInterface` +- **ActorLineageRequest**: `WaitState waitStateStart/waitStateEnd` (enum/int), `time_t timeStart/timeEnd` (int64_t), `reply` → **ActorLineageReply** {`std::vector samples`} + +--- + +## Appendix A: Log System Configuration Types + +**Source:** `fdbserver/core/include/fdbserver/core/LogSystemConfig.h`, +`fdbserver/core/include/fdbserver/core/DBCoreState.h`, +`fdbserver/core/include/fdbserver/core/ServerDBInfo.h` + +### OptionalInterface\ +Conditionally serializes a full interface or just its UID. + +| Field | Type | Purpose | +|-------|------|---------| +| iface | `Optional` | Full interface (if present) | +| ident | `UID` | Interface ID (serialized only if iface is absent) | + +### TLogSet + +| Field | Type | Purpose | +|-------|------|---------| +| tLogs | `std::vector>` | TLog interfaces | +| logRouters | `std::vector>` | Log router interfaces | +| backupWorkers | `std::vector>` | Backup worker interfaces | +| tLogWriteAntiQuorum | `int32_t` | Write anti-quorum | +| tLogReplicationFactor | `int32_t` | Replication factor | +| tLogPolicy | `Reference` | Replication policy (custom serialization) | +| tLogLocalities | `std::vector` | TLog localities | +| isLocal | `bool` | Whether this is the local DC | +| locality | `int8_t` | DC locality | +| startVersion | `Version` → `int64_t` | Start version | +| satelliteTagLocations | `std::vector>` | Satellite tag locations | +| tLogVersion | `TLogVersion` → `uint32_t` | TLog version | + +### OldTLogConf + +| Field | Type | Purpose | +|-------|------|---------| +| tLogs | `std::vector` | TLog sets | +| epochBegin | `Version` → `int64_t` | Epoch begin version | +| epochEnd | `Version` → `int64_t` | Epoch end version | +| recoverAt | `Version` → `int64_t` | Recovery version | +| logRouterTags | `int32_t` | Number of log router tags | +| txsTags | `int32_t` | Number of txs tags | +| pseudoLocalities | `std::set` | Pseudo-localities | +| epoch | `LogEpoch` → `uint64_t` | Epoch number | + +### LogSystemConfig + +| Field | Type | Purpose | +|-------|------|---------| +| logSystemType | `LogSystemType` → enum (int) | Always `tagPartitioned` (2) | +| tLogs | `std::vector` | Current TLog sets | +| logRouterTags | `int32_t` | Log router tag count | +| txsTags | `int32_t` | Txs tag count | +| oldTLogs | `std::vector` | Previous generation configs | +| expectedLogSets | `int32_t` | Expected log set count | +| recruitmentID | `UID` | Recruitment ID | +| stopped | `bool` | Whether stopped | +| recoveredAt | `Optional` | Recovery version | +| pseudoLocalities | `std::set` | Pseudo-localities | +| epoch | `LogEpoch` → `uint64_t` | Current epoch | +| oldestBackupEpoch | `LogEpoch` → `uint64_t` | Oldest backup epoch | +| knownLockedTLogIds | `std::map>` | Known locked TLog IDs | + +### CoreTLogSet +Persisted on coordinators (UIDs only, no full interfaces). + +| Field | Type | Purpose | +|-------|------|---------| +| tLogs | `std::vector` | TLog UIDs | +| tLogWriteAntiQuorum | `int32_t` | Write anti-quorum | +| tLogReplicationFactor | `int32_t` | Replication factor | +| tLogPolicy | `Reference` | Replication policy | +| tLogLocalities | `std::vector` | TLog localities | +| isLocal | `bool` | Local DC | +| locality | `int8_t` | DC locality | +| startVersion | `Version` → `int64_t` | Start version | +| satelliteTagLocations | `std::vector>` | Satellite locations | +| tLogVersion | `TLogVersion` → `uint32_t` | TLog version | + +### OldTLogCoreData + +| Field | Type | Purpose | +|-------|------|---------| +| tLogs | `std::vector` | Core TLog sets | +| logRouterTags | `int32_t` | Log router tags | +| txsTags | `int32_t` | Txs tags | +| epochBegin | `Version` → `int64_t` | Epoch begin | +| epochEnd | `Version` → `int64_t` | Epoch end | +| recoverAt | `Version` → `int64_t` | Recovery version (conditional on protocol version) | +| pseudoLocalities | `std::set` | Pseudo-localities | +| epoch | `LogEpoch` → `uint64_t` | Epoch | + +### DBCoreState +Persisted on coordinators — the ground truth for recovery. + +| Field | Type | Purpose | +|-------|------|---------| +| tLogs | `std::vector` | Current TLog sets | +| logRouterTags | `int32_t` | Log router tags | +| txsTags | `int32_t` | Txs tags | +| oldTLogData | `std::vector` | Previous generations | +| recoveryCount | `DBRecoveryCount` → `uint64_t` | Recovery counter | +| logSystemType | `LogSystemType` → enum (int) | Log system type | +| pseudoLocalities | `std::set` | Pseudo-localities | +| newestProtocolVersion | `ProtocolVersion` → `uint64_t` | Newest protocol (conditional) | +| lowestCompatibleProtocolVersion | `ProtocolVersion` → `uint64_t` | Lowest compatible (conditional) | +| encryptionAtRestModeDeprecated | `EncryptionAtRestModeDeprecated` → `uint32_t` | Encryption mode (conditional) | + +### ServerDBInfo +Distributed to all server processes. + +| Field | Type | Purpose | +|-------|------|---------| +| id | `UID` | Info snapshot ID | +| clusterInterface | `ClusterControllerFullInterface` | CC interface | +| client | `ClientDBInfo` | Client-facing info | +| distributor | `Optional` | DD interface | +| master | `MasterInterface` | Master interface | +| ratekeeper | `Optional` | RK interface | +| consistencyScan | `Optional` | CS interface | +| resolvers | `std::vector` | Resolver interfaces | +| recoveryCount | `DBRecoveryCount` → `uint64_t` | Recovery counter | +| recoveryState | `RecoveryState` → enum (int) | Recovery state | +| masterLifetime | `LifetimeToken` | Master lifetime token | +| logSystemConfig | `LogSystemConfig` | Log system config | +| priorCommittedLogServers | `std::vector` | Prior committed log servers | +| latencyBandConfig | `Optional` | Latency band config | +| infoGeneration | `int64_t` | Info generation counter | + +### LatencyBandConfig + +| Field | Type | Purpose | +|-------|------|---------| +| grvConfig | `GrvConfig` | GRV latency bands | +| readConfig | `ReadConfig` | Read latency bands | +| commitConfig | `CommitConfig` | Commit latency bands | + +Each config inherits from **RequestConfig** {`std::set bands`} and adds: +- **ReadConfig**: `Optional maxReadBytes`, `Optional maxKeySelectorOffset` +- **CommitConfig**: `Optional maxCommitBytes` + +### UpdateServerDBInfoRequest + +| Field | Type | Purpose | +|-------|------|---------| +| serializedDbInfo | `Standalone` | Serialized `ServerDBInfo` | +| broadcastInfo | `std::vector` | Endpoints to broadcast to | +| reply | `ReplyPromise>` | Updated endpoints | + +### GetServerDBInfoRequest + +| Field | Type | Purpose | +|-------|------|---------| +| knownServerInfoID | `UID` | Last known info ID (long-poll) | +| reply | `ReplyPromise` | Server DB info | + +--- + +## Appendix B: In-Band Log Messages + +**Source:** `fdbserver/core/include/fdbserver/core/LogProtocolMessage.h`, +`fdbserver/core/include/fdbserver/core/SpanContextMessage.h`, +`fdbserver/core/include/fdbserver/core/OTELSpanContextMessage.h` + +These messages are embedded in the TLog mutation stream (`messages` field of +`TLogCommitRequest` / `TLogPeekReply`). They use reserved `MutationRef` type codes. + +### LogProtocolMessage +Marks a protocol version change in the log stream. + +| Field | Type | Purpose | +|-------|------|---------| +| (type code) | `uint8_t` | `MutationRef::Reserved_For_LogProtocolMessage` | +| (version) | `ProtocolVersion` → `uint64_t` | New protocol version | + +### SpanContextMessage + +| Field | Type | Purpose | +|-------|------|---------| +| (type code) | `uint8_t` | `MutationRef::Reserved_For_SpanContextMessage` | +| spanContext | `SpanID` → `UID` (two `uint64_t`) | Tracing span context | + +### OTELSpanContextMessage + +| Field | Type | Purpose | +|-------|------|---------| +| (type code) | `uint8_t` | `MutationRef::Reserved_For_OTELSpanContextMessage` | +| spanContext | `SpanContext` | OpenTelemetry span context | + +--- + +## Appendix C: Backpressure Protocol + +**Source:** `fdbrpc/include/fdbrpc/fdbrpc.h` + +For streaming replies (`ReplyPromiseStream`), FDB implements flow control via +acknowledgments: + +### AcknowledgementReply +Sent from client to server to acknowledge received bytes. + +| Field | Type | Purpose | +|-------|------|---------| +| bytes | `int64_t` | Total bytes acknowledged | + +The server tracks `bytesSent` vs `bytesAcknowledged` and pauses when the +difference exceeds `bytesLimit`. On cancellation, the client sends +`operation_obsolete` on the acknowledgment stream. + +### ReplyPromiseStreamReply (base for all streaming replies) + +| Field | Type | Purpose | +|-------|------|---------| +| acknowledgeToken | `Optional` | Ack endpoint token (sent in first reply) | +| sequence | `uint16_t` | Monotonic sequence number | + +--- + +## Appendix D: Client Library Protocol Flows + +**Source:** `fdbclient/NativeAPI.actor.cpp`, `fdbclient/MonitorLeader.cpp`, +`fdbclient/GlobalConfig.cpp` + +This appendix describes the exact sequence of protocol messages the FDB client +library sends during each operation, as implemented in the NativeAPI layer. + +### D.1 Cluster Discovery and Connection + +The client's first task is to discover the cluster controller and obtain the +current proxy list. This happens continuously in the background. + +**Step 1: Parse connection file.** +The client reads the cluster file (e.g., `fdb.cluster`) containing +`description:id@coordinator1:port,coordinator2:port,...`. This yields a +`ClusterConnectionString`. + +**Step 2: Contact coordinators (monitorProxies loop).** +The client enters an infinite loop that monitors proxy changes. For each +coordinator, it creates a `ClientLeaderRegInterface` and sends: + +``` +Client → Coordinator: OpenDatabaseCoordRequest + clusterKey: Key (from connection string) + hostnames: std::vector + coordinators: std::vector + knownClientInfoID: UID (last known ClientDBInfo.id, for long-poll) + supportedVersions: std::vector + traceLogGroup: Key + internal: bool +``` + +The coordinator responds with `CachedSerialization`, which the +client deserializes to obtain: + +``` +Coordinator → Client: ClientDBInfo (serialization order) + grvProxies: std::vector + commitProxies: std::vector + id: UID + forward: Optional (if set, reconnect to new coordinators) + history: std::vector + clusterId: UID + clusterType: ClusterType +``` + +Note: `firstCommitProxy` is a struct member but is NOT serialized — it is +reconstructed locally. + +If `forward` is set, the client switches to the new connection string and +restarts discovery. Otherwise, it stores the proxy lists and long-polls +(blocks until `id` changes). + +**Step 3: Proxy list management.** +The client randomly shuffles the proxy lists and truncates them to +`MAX_COMMIT_PROXY_CONNECTIONS` and `MAX_GRV_PROXY_CONNECTIONS` to limit +connection fan-out. It selects `firstCommitProxy` for operations that must +go to a single proxy. + +### D.2 Get Read Version (GRV) + +Every transaction begins by acquiring a read version. + +**Step 1: Check GRV cache.** +If GRV caching is enabled and a cached version is recent enough (within +`MAX_VERSION_CACHE_LAG`), the client returns the cached version immediately +with no network round-trip. + +**Step 2: Batch GRV requests.** +Multiple concurrent transactions' GRV needs are batched together by the +`readVersionBatcher` actor. It collects requests until `MAX_BATCH_SIZE` is +reached or `GRV_BATCH_TIMEOUT` expires, then sends a single request. + +**Step 3: Send batched request to GRV proxy.** + +``` +Client → GrvProxy: GetReadVersionRequest + spanContext: SpanContext + transactionCount: uint32_t (number of transactions in batch) + flags: uint32_t (PRIORITY_SYSTEM_IMMEDIATE | PRIORITY_DEFAULT | PRIORITY_BATCH) + tags: TransactionTagMap (tag counts for throttling) + debugID: Optional + maxVersion: Version (max version in client's version vector cache) +``` + +Proxy selection: `basicLoadBalance()` across `grvProxies[]` with round-robin. + +**Step 4: Process reply.** + +``` +GrvProxy → Client: GetReadVersionReply + processBusyTime: double + version: Version (the assigned read version) + locked: bool + metadataVersion: Optional + midShardSize: int64_t + rkDefaultThrottled: bool + rkBatchThrottled: bool + tagThrottleInfo: TransactionTagMap + ssVersionVectorDelta: VersionVector + proxyId: UID +``` + +The client caches the version, updates its tag throttle table, and applies +the storage server version vector delta for read-your-writes consistency. + +### D.3 Key Location Discovery + +Before reading, the client must know which storage servers hold the target keys. + +**Step 1: Check location cache.** +The client maintains a `KeyRangeMap` mapping key ranges → storage server +interfaces. If the target range is cached, skip to the read. + +**Step 2: Request locations from commit proxy.** + +``` +Client → CommitProxy: GetKeyServerLocationsRequest + spanContext: SpanContext + begin: KeyRef (start of range) + end: Optional (end of range, if range query) + limit: int (max shard locations, typically 100) + reverse: bool + legacyVersion: Version + arena: Arena +``` + +Proxy selection: `commitProxyLoadBalance()` across `commitProxies[]`. + +**Step 3: Cache response.** + +``` +CommitProxy → Client: GetKeyServerLocationsReply + results: std::vector>> + resultsTssMapping: std::vector> + resultsTagMapping: std::vector> + arena: Arena +``` + +Each result maps a key range to the set of storage server replicas hosting it. +The client caches these mappings. TSS (Testing Storage Server) pairs are stored +separately for read validation. + +### D.4 Reading Data + +#### Single Key Read (getValue) + +``` +Client → StorageServer: GetValueRequest (serialization order) + key: Key + version: Version (transaction's read version) + tags: Optional + reply: ReplyPromise + spanContext: SpanContext + options: Optional + ssLatestCommitVersions: VersionVector +``` + +Server selection: `loadBalance()` across replicas from the location cache, +with locality-aware preference for same-DC servers. + +``` +StorageServer → Client: GetValueReply + penalty: double (load balancing hint) + error: Optional + value: Optional (None if key not found) + cached: bool +``` + +#### Range Read (getRange / getKeyValues) + +``` +Client → StorageServer: GetKeyValuesRequest (serialization order) + begin: KeySelectorRef + end: KeySelectorRef + version: Version + limit: int (row limit; negative for reverse) + limitBytes: int (byte limit) + tags: Optional + reply: ReplyPromise + spanContext: SpanContext + options: Optional + ssLatestCommitVersions: VersionVector + taskID: Optional + arena: Arena +``` + +``` +StorageServer → Client: GetKeyValuesReply + penalty: double + error: Optional + data: VectorRef (key-value pairs) + version: Version + more: bool (true if more data available) + cached: bool + arena: Arena +``` + +If `more` is true, the client adjusts the key selectors and sends another +request. If the range spans multiple shards, the client sends parallel +requests to different storage servers and merges results. + +**Error handling during reads:** +- `wrong_shard_server`: Shard moved; invalidate location cache, re-lookup, retry. +- `all_alternatives_failed`: All replicas failed; same as wrong_shard_server. +- `future_version`: Requested version not yet available; wait and retry. +- `transaction_too_old`: Version expired; restart transaction with new GRV. + +#### Streaming Range Read (getKeyValuesStream) + +For large range reads, the client can use the streaming variant: + +``` +Client → StorageServer: GetKeyValuesStreamRequest + (same fields as GetKeyValuesRequest, without taskID) + reply: ReplyPromiseStream +``` + +This returns a stream of `GetKeyValuesStreamReply` messages with backpressure +(see Appendix C). Each reply carries `acknowledgeToken` and `sequence` for +flow control. + +### D.5 Committing a Transaction + +**Step 1: Build commit request.** +The client accumulates mutations (set, clear, atomic operations) and conflict +ranges during the transaction. When `commit()` is called: + +``` +Client → CommitProxy: CommitTransactionRequest + spanContext: SpanContext + transaction: CommitTransactionRef + read_conflict_ranges: VectorRef + write_conflict_ranges: VectorRef + mutations: VectorRef + read_snapshot: Version (the read version) + report_conflicting_keys: bool + lock_aware: bool + spanContext: Optional + tenantIds: Optional> + flags: uint32_t (LOCK_AWARE, FIRST_IN_BATCH, etc.) + debugID: Optional + commitCostEstimation: Optional + tagSet: Optional + idempotencyId: IdempotencyIdRef + arena: Arena +``` + +Proxy selection: `basicLoadBalance()` across `commitProxies[]`, or +`firstCommitProxy` if `COMMIT_ON_FIRST_PROXY` option is set. + +**Step 2: Wait for reply.** + +``` +CommitProxy → Client: CommitID + version: Version (committed version, or invalidVersion on failure) + txnBatchId: uint16_t (for constructing versionstamps) + metadataVersion: Optional + conflictingKRIndices: Optional>> +``` + +**Step 3: Handle result.** +- **Success** (`version != invalidVersion`): Transaction is committed. The + client constructs a `Versionstamp` from `version` and `txnBatchId`, updates + its committed version, and sends `ExpireIdempotencyIdRequest` if using + idempotency. +- **Conflict** (`not_committed`): Transaction conflicted; throw to caller. +- **Unknown** (`commit_unknown_result`): Proxy may have crashed after committing. + The client can retry with idempotency to determine the outcome. + +### D.6 Watching a Key + +``` +Client → StorageServer: WatchValueRequest + spanContext: SpanContext + key: Key + value: Optional (expected current value) + version: Version (version at which value was read) + tags: Optional + debugID: Optional +``` + +This is a long-poll: the storage server holds the request until the key's value +changes, then replies: + +``` +StorageServer → Client: WatchValueReply + version: Version (version at which value changed) + cached: bool +``` + +The client then waits for `waitForCommittedVersion(version)` to ensure the +change is durable before triggering the watch future. If the watch is cancelled +by a `watch_cancelled` error (too many concurrent watches), the client retries +with backoff. + +### D.7 Change Feed Streaming + +``` +Client → StorageServer: ChangeFeedStreamRequest + spanContext: SpanContext + rangeID: Key (change feed identifier) + begin: Version (start version) + end: Version (end version) + range: KeyRange + replyBufferSize: int + canReadPopped: bool + id: UID + options: Optional + encrypted: bool + arena: Arena + reply: ReplyPromiseStream +``` + +Returns a stream of `ChangeFeedStreamReply` messages, each containing: +``` + mutations: VectorRef + atLatestVersion: bool + minStreamVersion: Version + popVersion: Version +``` + +### D.8 Global Configuration Refresh + +Runs periodically in the background: + +``` +Client → GrvProxy: GlobalConfigRefreshRequest + lastKnown: Version (last known config version) +``` + +``` +GrvProxy → Client: GlobalConfigRefreshReply + version: Version + result: RangeResultRef (config key-value pairs) + arena: Arena +``` + +### D.9 Error Handling and Retry Strategy + +| Error | Meaning | Client Action | +|-------|---------|---------------| +| `wrong_shard_server` | Key range relocated | Invalidate cache, retry | +| `all_alternatives_failed` | All replicas unreachable | Same as wrong_shard_server | +| `transaction_too_old` | Read version expired | Get new GRV, restart transaction | +| `future_version` | Version not yet available | Wait `FUTURE_VERSION_RETRY_DELAY`, retry | +| `not_committed` | Transaction conflicted | Throw conflict error to caller | +| `commit_unknown_result` | Proxy crash mid-commit | Verify via idempotency or retry | +| `commit_proxy_memory_limit_exceeded` | Proxy overloaded | Exponential backoff | +| `batch_transaction_throttled` | Batch priority throttled | Retry after delay | + +Backoff: starts at `CLIENT_KNOBS->BACKOFF_DELAY`, multiplied by +`BACKOFF_GROWTH_RATE` on each failure, capped at +`RESOURCE_CONSTRAINED_MAX_BACKOFF`. + +--- + +## Appendix E: fdbcli Protocol Usage + +**Source:** `fdbcli/*.actor.cpp`, `fdbclient/include/fdbclient/ManagementAPI.h` + +The `fdbcli` command-line tool uses three protocol layers: +1. **Normal transactions** via the NativeAPI (same as any client). +2. **Special Key Space writes** — transactions that write to `\xff\xff/management/` + keys, which the commit proxy intercepts and translates into cluster operations. +3. **Direct RPC** to ClusterInterface or worker endpoints. + +### E.1 Direct RPC Commands + +These commands send protocol messages directly to cluster endpoints, bypassing +the transaction layer. + +#### `status` Command +``` +fdbcli → ClusterController: StatusRequest + statusField: std::string (optional filter like "json") + reply: ReplyPromise +``` +Reply: `StatusReply` containing `statusStr` (JSON string). + +#### `force_recovery_with_data_loss ` +``` +fdbcli → ClusterController: ForceRecoveryRequest + dcId: Key (datacenter ID) + reply: ReplyPromise +``` + +#### `kill` / `suspend` / `expensive_data_check` +First discovers workers via: +``` +fdbcli → ClusterController: GetClientWorkersRequest + reply: ReplyPromise> +``` + +Then sends to each target worker: +``` +fdbcli → Worker: RebootRequest (fire-and-forget) + deleteData: bool (false for kill/suspend) + checkData: bool (true for expensive_data_check) + waitForDuration: uint32_t (0 for kill, >0 for suspend seconds) +``` + +#### `profile` Command (flow/heap profiling) +``` +fdbcli → Worker: ProfilerRequest + type: Type enum (GPROF, FLOW, GPROF_HEAP) + action: Action enum (ENABLE, DISABLE, RUN) + duration: int + outputFile: Standalone + reply: ReplyPromise +``` + +#### `audit_storage` Command +``` +fdbcli → ClusterController: TriggerAuditRequest + type: uint8_t (ValidateHA, ValidateReplica, ValidateLocationMetadata, ...) + range: KeyRange + id: UID + cancel: bool + engineType: KeyValueStoreType (uint32_t) + reply: ReplyPromise +``` + +#### `snapshot` Command +Uses `IDatabase::createSnapshot()` which sends `ProxySnapRequest` to a commit +proxy. The proxy then forwards a `DistributorSnapRequest` to the data distributor, +which orchestrates the actual snapshot across TLogs and storage servers. + +### E.2 Special Key Space Commands + +These commands write to `\xff\xff/management/` keys within a transaction with +`SPECIAL_KEY_SPACE_ENABLE_WRITES` option. The commit proxy intercepts these +writes and performs the corresponding cluster operations. + +#### `exclude` / `include` Commands +``` +Write to: \xff\xff/management/excluded/
= "" +Write to: \xff\xff/management/failed/
= "" +Write to: \xff\xff/management/excluded_locality/ = "" +Write to: \xff\xff/management/failed_locality/ = "" + +Options: +Write to: \xff\xff/management/options/excluded/force = "" (skip safety check) + +Read from: \xff\xff/management/in_progress_exclusion/
(check progress) +``` + +#### `configure` Command +Uses `ManagementAPI::changeConfig()` which: +1. Reads current configuration from `\xff/conf/` system keys. +2. Writes updated configuration via a normal transaction. +3. The commit proxy detects system key changes and triggers reconfiguration. + +#### `coordinators` Command +``` +Write to: \xff\xff/configuration/coordinators/processes = ",,..." +Write to: \xff\xff/configuration/coordinators/cluster_description = "" + +Read from: \xff\xff/management/auto_coordinators (get auto-selected coordinators) +``` + +#### `lock` / `unlock` Commands +``` +Write to: \xff\xff/management/db_locked = (lock) +Clear: \xff\xff/management/db_locked (unlock, requires matching UID) +``` + +#### `maintenance` Command +``` +Write to: \xff\xff/management/maintenance/ = +Write to: \xff\xff/management/maintenance/IgnoreSSFailures = "" (disable DD for SS failures) +Clear: \xff\xff/management/maintenance/ (end maintenance) +``` + +#### `datadistribution` Command +``` +Write to: \xff\xff/management/data_distribution/mode = "0" or "1" (off/on) +Write to: \xff\xff/management/data_distribution/rebalance_ignored = +``` + +#### `advanceversion` Command +``` +Write to: \xff\xff/management/min_required_commit_version = +``` + +#### `throttle` Command +Uses `ThrottleApi` functions which read/write: +``` +\xff\x02/throttledTags/tag/// = TagThrottleValue +\xff\x02/throttledTags/autoThrottledTags/// = TagThrottleValue +``` + +#### `setclass` Command +Uses `ManagementAPI::setClass()` which writes to system keys to change a +worker's process class assignment. + +### E.3 Backup / Restore CLI Commands + +Backup and restore commands use the `FileBackupAgent` and `DatabaseBackupAgent` +classes, which operate through normal transactions reading/writing system keys: + +``` +\xff\x02/fdbbackup/ — backup task state +\xff\x02/fdbrestore/ — restore task state +\xff\x02/backupProgress/ — backup progress markers +\xff\x02/backupStarted/ — backup start markers +``` + +These are standard key-value operations — the backup agents are implemented as +Taskbucket workflows that run as normal transactions within the cluster. + +### E.4 Transaction Options Used by fdbcli + +| Option | Purpose | +|--------|---------| +| `SPECIAL_KEY_SPACE_ENABLE_WRITES` | Enable writes to `\xff\xff/` keys | +| `PRIORITY_SYSTEM_IMMEDIATE` | Highest priority (bypass throttling) | +| `READ_SYSTEM_KEYS` | Allow reading `\xff/` system keys | +| `LOCK_AWARE` | Operate while database is locked | +| `ACCESS_SYSTEM_KEYS` | Full access to system keyspace | + +--- + +## Appendix F: Server-Side Protocol Flows + +**Source:** `fdbserver/commitproxy/CommitProxyServer.actor.cpp`, +`fdbserver/clustercontroller/ClusterRecovery.cpp`, +`fdbserver/storageserver/storageserver.actor.cpp`, +`fdbserver/sequencer/masterserver.cpp` + +### F.1 Transaction Commit Path + +This is the critical write path: how a client's `CommitTransactionRequest` +becomes durable. The commit proxy orchestrates a multi-phase protocol. + +``` +┌────────┐ ┌──────────────┐ ┌────────┐ ┌──────────┐ ┌───────┐ +│ Client │────▶│ Commit Proxy │────▶│ Master │ │ Resolver │ │ TLog │ +└────────┘ └──────────────┘ └────────┘ └──────────┘ └───────┘ + │ │ │ │ + │ GetCommitVersion │ │ │ + │─────────────────▶│ │ │ + │ Reply(version) │ │ │ + │◀─────────────────│ │ │ + │ │ │ + │ ResolveTransactionBatchRequest │ │ + │─────────────────────────────────▶│ │ + │ Reply(committed[], conflicts) │ │ + │◀─────────────────────────────────│ │ + │ │ + │ TLogCommitRequest │ + │─────────────────────────────────────────────────▶│ + │ TLogCommitReply │ + │◀─────────────────────────────────────────────────│ + │ │ │ + │ ReportRawCommittedVersion │ + │─────────────────▶│ │ +``` + +**Phase 1: Get Commit Version** + +The commit proxy batches incoming `CommitTransactionRequest` messages and +requests a commit version from the master: + +``` +CommitProxy → Master: GetCommitVersionRequest + spanContext: SpanContext + requestNum: uint64_t (monotonic per-proxy) + mostRecentProcessedRequestNum: uint64_t + requestingProxy: UID +``` + +``` +Master → CommitProxy: GetCommitVersionReply + resolverChanges: Standalone> + resolverChangesVersion: Version + version: Version (assigned commit version) + prevVersion: Version + requestNum: uint64_t +``` + +The master assigns monotonically increasing versions. If resolver topology has +changed, it includes `resolverChanges` so the proxy knows which resolver +handles which key ranges. + +**Phase 2: Conflict Resolution** + +The proxy sends the transaction batch to each resolver. Each resolver is +responsible for a range of keys. Transactions whose read/write conflict ranges +span multiple resolvers are sent to all relevant resolvers. + +``` +CommitProxy → Resolver[i]: ResolveTransactionBatchRequest + spanContext: SpanContext + prevVersion: Version + version: Version + lastReceivedVersion: Version + transactions: VectorRef + txnStateTransactions: VectorRef + debugID: Optional + writtenTags: std::set + lastShardMove: Version + arena: Arena +``` + +``` +Resolver[i] → CommitProxy: ResolveTransactionBatchReply + committed: VectorRef (per-txn: 0=ok, 1=conflict) + stateMutations: VectorRef> + conflictingKeyRangeMap: std::map> + privateMutations: VectorRef + privateMutationCount: uint32_t + tpcvMap: std::unordered_map + writtenTags: std::set + lastShardMove: Version + arena: Arena +``` + +The proxy merges results from all resolvers. A transaction is committed only +if all resolvers agree it has no conflicts. + +**Phase 3: TLog Push** + +For committed transactions, the proxy serializes mutations into a `LogPushData` +object. Each mutation is tagged with the `Tag` of the storage server(s) that +should receive it. The proxy then pushes to all TLog replicas: + +``` +CommitProxy → TLog[j]: TLogCommitRequest + spanContext: SpanContext + prevVersion: Version + version: Version + knownCommittedVersion: Version + minKnownCommittedVersion: Version + seqPrevVersion: Version + messages: StringRef (packed, tagged mutations) + tLogCount: uint16_t + tLogLocIds: std::vector + debugID: Optional + arena: Arena +``` + +The commit is durable when a quorum of TLog replicas acknowledge: + +``` +TLog[j] → CommitProxy: TLogCommitReply + version: Version (durable version) +``` + +**Phase 4: Report Committed Version** + +``` +CommitProxy → Master: ReportRawCommittedVersionRequest + version: Version + locked: bool + metadataVersion: Optional + minKnownCommittedVersion: Version + prevVersion: Optional + writtenTags: Optional> +``` + +The master updates its committed version. The proxy then replies to the +original client with `CommitID`. + +### F.2 Recovery Protocol + +Recovery happens when the cluster controller detects that the master has +failed, or during initial cluster startup. It is a complex multi-phase +protocol that establishes a new transaction system generation. + +``` +Phase 0: CC recruits new roles +Phase 1: Lock old TLogs to freeze previous generation +Phase 2: Determine recovery point (last committed version) +Phase 3: Recruit and initialize new TLogs +Phase 4: Initialize resolvers and proxies with transaction state +Phase 5: Write new DBCoreState to coordinators +Phase 6: Begin accepting commits +``` + +**Phase 0: Role Recruitment** + +``` +ClusterController → Workers: RecruitFromConfigurationRequest + configuration: DatabaseConfiguration + recruitSeedServers: bool + maxOldLogRouters: int +``` + +``` +Workers → ClusterController: RecruitFromConfigurationReply + tLogs: std::vector + satelliteTLogs: std::vector + commitProxies: std::vector + grvProxies: std::vector + resolvers: std::vector + storageServers: std::vector + oldLogRouters: std::vector + backupWorkers: std::vector + dcId: Optional + satelliteFallback: bool +``` + +**Phase 1: Lock Old TLogs** + +The master sends a lock request to every TLog from the previous generation: + +``` +Master → OldTLog[i]: TLogInterface::lock (ReplyPromise) +``` + +``` +OldTLog[i] → Master: TLogLockResult + end: Version (last version on this TLog) + knownCommittedVersion: Version + unknownCommittedVersions: std::deque + id: UID + logId: UID +``` + +The master gathers all lock results to determine the recovery point: the +highest version that reached a quorum of TLogs. + +**Phase 2: Initialize New TLogs** + +``` +ClusterController → NewTLog[j]: InitializeTLogRequest + recruitmentID: UID + recoverFrom: LogSystemConfig (previous generation's config) + recoverAt: Version + knownCommittedVersion: Version + epoch: LogEpoch + recoverTags: std::vector + allTags: std::vector + logVersion: TLogVersion + storeType: KeyValueStoreType + spillType: TLogSpillType + remoteTag: Tag + locality: int8_t + isPrimary: bool + startVersion: Version + logRouterTags: int + txsTags: int + recoveryTransactionVersion: Version + oldGenerationRecoverAtVersions: std::vector +``` + +``` +NewTLog[j] → ClusterController: TLogInterface + (the new TLog's interface, with all its RequestStream endpoints) +``` + +**Phase 3: Initialize Proxies and Resolvers** + +``` +CC → NewProxy: InitializeCommitProxyRequest + master: MasterInterface + masterLifetime: LifetimeToken + recoveryCount: uint64_t + recoveryTransactionVersion: Version + firstProxy: bool + encryptMode: EncryptionAtRestModeDeprecated + commitProxyIndex: uint16_t +``` + +``` +CC → NewResolver: InitializeResolverRequest + masterLifetime: LifetimeToken + recoveryCount: uint64_t + commitProxyCount: int + resolverCount: int + masterId: UID + encryptMode: EncryptionAtRestModeDeprecated +``` + +**Phase 4: Broadcast Transaction State** + +The master broadcasts the transaction system state (metadata about shards, +server assignments, etc.) to all resolvers: + +``` +Master → Resolver[i]: TxnStateRequest (sent in chunks) + data: VectorRef (transaction state KV pairs) + sequence: Sequence (chunk sequence number) + last: bool (true for final chunk) + broadcastInfo: std::vector +``` + +**Phase 5: Write to Coordinators** + +The master writes the new `DBCoreState` to the coordination service using +`GenerationRegWriteRequest`. This is the atomic commit point — once this +write succeeds, the new generation is official. + +**Phase 6: Accept Commits** + +The master updates `ServerDBInfo` and broadcasts it to all workers: + +``` +CC → Workers: UpdateServerDBInfoRequest + serializedDbInfo: Standalone (serialized ServerDBInfo) + broadcastInfo: std::vector +``` + +The master then sets `recoveryReadyForCommits`, allowing commit proxies to +begin processing client transactions. + +### F.3 Storage Server Data Flow + +Storage servers pull mutations from TLogs using a continuous peek cursor. +The storage server calls `logSystem->peekSingle()` which creates a cursor +for its assigned tag. By default (knob `PEEK_USING_STREAMING = false`), this +uses regular `TLogPeekRequest` messages. If `PEEK_USING_STREAMING` is enabled, +it uses the streaming `TLogPeekStreamRequest` variant instead. + +**Steady-State Mutation Pull (default, non-streaming):** + +``` +StorageServer → TLog: TLogPeekRequest + begin: Version + tag: Tag (this SS's tag) + returnIfBlocked: bool + onlySpilled: bool + sequence: Optional> + end: Optional + returnEmptyIfStopped: Optional +``` + +``` +TLog → StorageServer: TLogPeekReply + messages: StringRef (packed mutations) + end: Version + popped: Optional + maxKnownVersion: Version + minKnownCommittedVersion: Version + begin: Optional + onlySpilled: bool + arena: Arena +``` + +**Streaming variant (when `PEEK_USING_STREAMING = true`):** + +``` +StorageServer → TLog: TLogPeekStreamRequest + begin: Version + tag: Tag + returnIfBlocked: bool + limitBytes: int + end: Optional + returnEmptyIfStopped: Optional + reply: ReplyPromiseStream +``` + +The storage server deserializes mutations from `messages`, applies them to its +in-memory versioned data structure, and periodically makes them durable. + +**Pop (Garbage Collection):** + +Once a storage server has durably written mutations, it tells the TLog to +discard them: + +``` +StorageServer → TLog: TLogPopRequest + to: Version (pop up to this version) + durableKnownCommittedVersion: Version + tag: Tag +``` + +**Shard Movement (Data Distribution):** + +When data distribution moves a shard to a new storage server: + +1. The new SS begins peeking the TLog for the shard's tag at the transfer version. +2. The new SS uses `Transaction::getRange()` (or `getRangeStream()` if + `FETCH_USING_STREAMING` is enabled) to read the shard's data. This goes through + the normal client path: commit proxy for location lookup, then storage server + reads via `GetKeyValuesRequest` (or `GetKeyValuesStreamRequest`). The reads + target the old storage servers that still hold the shard. +3. Once caught up, the new SS transitions the shard to `ReadWrite` state. +4. Data distribution updates the shard map (via system key transactions through + the commit proxy), and future `GetKeyServerLocationsReply` messages will + direct clients to the new SS. diff --git a/design/AI-generated/README.md b/design/AI-generated/README.md new file mode 100644 index 00000000000..d7e35407c0a --- /dev/null +++ b/design/AI-generated/README.md @@ -0,0 +1,63 @@ +# AI-generated FDB code overview + +This directory contains an AI-generated code overview of the current `main` code base as of April, 2026. + +This was generated using a commercially available coding assistant in widespread use. We are not disclosing +the specific model used. It took about an hour and cost about $20 in API usage. + +Prompts: + +*Ok I want you to study the foundationdb code in the current + directory. It should be unmodified main. I am not interested in + specific diffs or PRs. I am interested in understanding the entire + system. What are the major modules? Do not rely too much on + filenames. Instead try to figure out the flow of data through the + system and the dynamic relationships more than just the static + arrangements. I would like some assessment of the code in terms of + a partition of subsystems that covers materially the entire code + base, but not too granular. I am thinking of 10-15 subsystems at + most (or fewer if that is justified by the structure of the code). + Tell me the major subsystems, the principle files in each, what they + are responsible for, and so on. Then we can do deep dives on each + one. But for now I want an overview of the whole system.* + +This generated the file [`foundationdb_subsystem_map.md`](foundationdb_subsystem_map.md). + +Subsequently: + +*for each of the 12 subsystems identified above, please study the code + in depth and generate a module-specific + architecture/design/implementation overview, focusing on the key data + structures, methods, and data flow in the system. Please save each + subsystem-specific overview to its own .md file* + +This generated the 12 files whose names have a `subsystem_` prefix. + +## Rationale and relationship to existing documentation + +Basically we did this on a Friday afternoon to see what the AI would +come up with. On cursory examination it seems pretty reasonable. We +were pleased to note that the first thing mentioned in the first file +we reviewed is `SAV`, which is of central importance to the system but +is buried in `flow.h` and is hardly mentioned in ../*.md. + +In any case, we hope this is usable as a starting point for people new +to the system or new to specific areas. It is meant to supplement, +not replace, the existing documentation in the parent directory. + +Note that the industry standard for developer-maintained documentation +of software systems (not just FDB) is generally considered to be +*widely uneven*. Some areas have in-depth, well-written deep dives. +Some areas are out of date. Some areas are not covered at all. This +is just the nature of things. AI-generated documentation, by +contrast, will be more comprehensive in coverage, but not quite as +in-depth. It might have random errors and omissions, but +human-maintained documentation certainly suffers from that too. + +## Caveats + +We have not reviewed this in detail. Errors/omissions will be +incrementally addressed. Feel free to send PRs to make corrections. +At some point in a few quarters it may be appropriate to completely +regenerate this documentation with more advanced AI models or +more creative prompts. diff --git a/design/AI-generated/diagram_01_flow_runtime.md b/design/AI-generated/diagram_01_flow_runtime.md new file mode 100644 index 00000000000..3fc47498cc6 --- /dev/null +++ b/design/AI-generated/diagram_01_flow_runtime.md @@ -0,0 +1,76 @@ +# Flow Runtime — Internal Architecture + +```mermaid +graph TB + subgraph ActorSystem["Actor System"] + Actor["ACTOR function\n(.actor.cpp source)"] + Compiler["Actor Compiler\n(Python preprocessor)"] + StateMachine["Generated State Machine\n(.actor.g.h)"] + Coro["C++20 Coroutine\n(co_await migration)"] + end + + subgraph AsyncPrimitives["Async Primitives"] + Promise["Promise<T>"] + Future["Future<T>"] + SAV["SAV\n(SingleAssignmentVar)"] + PS["PromiseStream<T>"] + FS["FutureStream<T>"] + end + + subgraph EventLoop["Event Loop (Net2)"] + Scheduler["Priority Task Scheduler"] + ASIO["Boost.ASIO\n(I/O multiplex)"] + Timers["Timer Queue"] + Yields["Yield Points"] + end + + subgraph Memory["Memory Management"] + Arena["Arena"] + StringRef["StringRef / KeyRef\n(non-owning view)"] + Standalone["Standalone<T>\n(owning)"] + RefCounted["Reference<T>\n(intrusive refcount)"] + end + + subgraph Diagnostics["Diagnostics"] + Trace["TraceEvent\n(.detail() chains)"] + DRandom["deterministicRandom()\n(seedable PRNG)"] + CodeProbe["CODE_PROBE\n(coverage marks)"] + Buggify["BUGGIFY\n(fault injection)"] + end + + Actor --> Compiler --> StateMachine + Actor -.->|migrating to| Coro + StateMachine --> Future + Promise --> SAV --> Future + PS --> FS + + Future --> Scheduler + Timers --> Scheduler + Scheduler --> ASIO + + Arena --> StringRef + Arena --> Standalone + + style ActorSystem fill:#e1f0ff,stroke:#4a90d9 + style AsyncPrimitives fill:#fff3e0,stroke:#f5a623 + style EventLoop fill:#e8f5e9,stroke:#4caf50 + style Memory fill:#fce4ec,stroke:#e91e63 + style Diagnostics fill:#f3e5f5,stroke:#9c27b0 +``` +## Future/Promise Lifecycle + +```mermaid +sequenceDiagram + participant Producer as Producer Actor + participant SAV as SAV (SharedState) + participant Consumer as Consumer Actor + participant Scheduler as Net2 Scheduler + + Producer->>SAV: Promise created (SAV allocated) + Consumer->>SAV: Future obtained (refcount++) + Consumer->>SAV: wait(future) — register callback + Note over Consumer: Suspended + Producer->>SAV: promise.send(value) + SAV->>Scheduler: enqueue callback + Scheduler->>Consumer: Resume with value +``` diff --git a/design/AI-generated/diagram_02_rpc_transport.md b/design/AI-generated/diagram_02_rpc_transport.md new file mode 100644 index 00000000000..17371aa7fcc --- /dev/null +++ b/design/AI-generated/diagram_02_rpc_transport.md @@ -0,0 +1,109 @@ +# RPC & Transport — Internal Architecture + +```mermaid +graph TB + subgraph Transport["FlowTransport (singleton)"] + SendR["sendReliable()"] + SendU["sendUnreliable()"] + Listen["listen()"] + end + + subgraph Peers["Peer Management"] + Peer["Peer\n(per-destination)"] + UnsentQ["Unsent Queue"] + ReliableQ["Reliable Packet List"] + ConnFuture["Connection Future\n(lazy connect)"] + end + + subgraph Addressing["Addressing"] + Endpoint["Endpoint\n= (NetworkAddressList, Token)"] + EndpointMap["EndpointMap\n(Token → Receiver)"] + WellKnown["Well-Known Tokens\n(system services)"] + end + + subgraph RequestReply["Request/Reply Pattern"] + ReqStream["RequestStream<Req>"] + RP["ReplyPromise<Rep>\n(self-addressed envelope)"] + Interface["Server Interface\n(struct of RequestStreams)"] + end + + subgraph Monitoring["Failure Monitoring"] + FM["FailureMonitor"] + OnFailed["onFailed(Endpoint)\n→ Future<Void>"] + OnStateChange["onStateChanged(Endpoint)"] + end + + subgraph SimNetwork["Simulation Network (Sim2)"] + VirtProcess["Virtual Processes\n(in-thread)"] + VirtTime["Deterministic Time"] + Clogging["Network Clogging"] + Partitions["Network Partitions"] + FaultInj["Fault Injection\n(kill process/machine/DC)"] + end + + Interface --> ReqStream + ReqStream --> SendR + SendR --> Peer --> UnsentQ + Peer --> ReliableQ + Peer --> ConnFuture + + Listen --> EndpointMap --> Endpoint + RP --> SendR + + FM --> OnFailed + FM --> OnStateChange + + SimNetwork -.->|replaces| Transport + + style Transport fill:#e1f0ff,stroke:#4a90d9 + style Peers fill:#fff3e0,stroke:#f5a623 + style Addressing fill:#e8f5e9,stroke:#4caf50 + style RequestReply fill:#fce4ec,stroke:#e91e63 + style Monitoring fill:#f3e5f5,stroke:#9c27b0 + style SimNetwork fill:#fffde7,stroke:#fbc02d +``` + +## Message Flow Between Processes + +```mermaid +sequenceDiagram + participant A as Actor A (Process 1) + participant FT1 as FlowTransport (P1) + participant Net as Network / Sim2 + participant FT2 as FlowTransport (P2) + participant EM as EndpointMap (P2) + participant B as Actor B (Process 2) + + A->>FT1: send(Endpoint, Request{ReplyPromise}) + FT1->>FT1: serialize(Request) + FT1->>Net: TCP send / sim route + Net->>FT2: deliver packet + FT2->>EM: lookup(Token) + EM->>B: receive(Request) + B->>B: process request + B->>FT2: ReplyPromise.send(Reply) + FT2->>Net: TCP send / sim route + Net->>FT1: deliver reply + FT1->>A: Future resolved with Reply +``` + +## Locality & Placement + +```mermaid +graph LR + subgraph LocalityData + Zone["zoneId"] + Machine["machineId"] + DC["dcId"] + DataHall["data_hall"] + end + + subgraph ProcessClass + Fitness["Fitness for Role"] + ClassType["ClassType\n(storage, transaction,\nresolution, stateless)"] + end + + LocalityData --> ReplicationPolicy["Replication Policy\n(e.g. 3 zones)"] + ProcessClass --> Recruitment["CC Role Recruitment"] + ReplicationPolicy --> TeamBuilding["DD Team Building"] +``` diff --git a/design/AI-generated/diagram_03_client_library.md b/design/AI-generated/diagram_03_client_library.md new file mode 100644 index 00000000000..944e6f80b51 --- /dev/null +++ b/design/AI-generated/diagram_03_client_library.md @@ -0,0 +1,104 @@ +# Client Library — Internal Architecture + +```mermaid +graph TB + subgraph Layers["Transaction Layers (outside → in)"] + App["Application Code"] + CAPI["C API (fdb_c.h)\n/ Language Bindings"] + MV["MultiVersionTransaction\n(protocol negotiation)"] + RYW["ReadYourWritesTransaction\n(local write map + cache)"] + Native["NativeAPI / Transaction\n(raw operations)"] + end + + subgraph ReadPath["Read Operations"] + Get["get(key)"] + GetRange["getRange(begin, end)"] + LocCache["Location Cache\n(KeyRange → SS list)"] + SS["Storage Server"] + end + + subgraph WritePath["Write Operations"] + Set["set(key, value)"] + Clear["clear(key/range)"] + WriteMap["Local Write Map"] + Commit["commit()"] + CP["Commit Proxy"] + end + + subgraph VersionMgmt["Version Management"] + GRV["getReadVersion()"] + GRVProxy["GRV Proxy"] + Watch["watch(key)"] + end + + subgraph Context["DatabaseContext"] + ProxyLB["Proxy Load Balancer\n(round-robin + failover)"] + RetryLoop["Retry Loop\n(onError → backoff)"] + MonLeader["monitorLeader()\n(cluster file → CC)"] + end + + App --> CAPI --> MV --> RYW --> Native + + Native --> Get + Native --> GetRange + Get --> LocCache + GetRange --> LocCache + LocCache -->|cache hit| SS + LocCache -->|cache miss| CP + + Native --> Set --> WriteMap + Native --> Clear --> WriteMap + Native --> Commit --> CP + + RYW -->|reads check| WriteMap + RYW -->|then merges| SS + + Native --> GRV --> GRVProxy + Native --> Watch --> SS + + ProxyLB --> CP + ProxyLB --> GRVProxy + RetryLoop --> Native + + style Layers fill:#e1f0ff,stroke:#4a90d9 + style ReadPath fill:#e8f5e9,stroke:#4caf50 + style WritePath fill:#fff3e0,stroke:#f5a623 + style VersionMgmt fill:#f3e5f5,stroke:#9c27b0 + style Context fill:#fffde7,stroke:#fbc02d +``` + +## Read-Your-Writes Merge + +```mermaid +graph LR + subgraph RYW["ReadYourWritesTransaction"] + Read["read(key)"] + WM["Write Map\n(local mutations)"] + SC["Snapshot Cache\n(previous reads)"] + end + + Read --> WM + WM -->|hit: locally set| Result["Return local value"] + WM -->|hit: locally cleared| Cleared["Return not found"] + WM -->|miss| SC + SC -->|hit| Result2["Return cached"] + SC -->|miss| SS["Storage Server read"] + SS --> Merge["Merge SS result\nwith write map"] + Merge --> Result3["Return merged"] +``` + +## Transaction Retry Loop + +```mermaid +stateDiagram-v2 + [*] --> GetReadVersion + GetReadVersion --> Execute: version V + Execute --> Commit: mutations ready + Commit --> Success: committed + Commit --> OnError: not_committed / too_old + Execute --> OnError: error during read + OnError --> GetReadVersion: retryable (reset + backoff) + OnError --> Failed: non-retryable + Success --> [*] + Failed --> [*] +``` diff --git a/design/AI-generated/diagram_04_cluster_controller.md b/design/AI-generated/diagram_04_cluster_controller.md new file mode 100644 index 00000000000..bbaef2f44e7 --- /dev/null +++ b/design/AI-generated/diagram_04_cluster_controller.md @@ -0,0 +1,107 @@ +# Cluster Controller & Coordination — Internal Architecture + +```mermaid +graph TB + subgraph Election["Leader Election"] + Coord1["Coordinator 1"] + Coord2["Coordinator 2"] + Coord3["Coordinator 3"] + GenReg["Generation Register\n(monotonic gen numbers)"] + Cand["CC Candidates\n(CandidacyRequest)"] + end + + subgraph CC["Cluster Controller"] + WorkerPool["Worker Registry\n(ProcessClass + Locality)"] + Recruit["Role Recruitment"] + Monitor["Health Monitoring"] + ServerDBInfo["ServerDBInfo\n(cluster-wide broadcast)"] + end + + subgraph Roles["Recruited Roles"] + Master["Master / Sequencer"] + CPr["Commit Proxies"] + GRVPr["GRV Proxies"] + Resolvers["Resolvers"] + TLogs["TLogs"] + DDist["Data Distributor"] + RK["Ratekeeper"] + CS["Consistency Scan"] + end + + subgraph Workers["Worker Processes"] + W1["Worker 1\n(transaction class)"] + W2["Worker 2\n(storage class)"] + W3["Worker 3\n(stateless class)"] + Wn["Worker N"] + end + + Cand --> Coord1 + Cand --> Coord2 + Cand --> Coord3 + Coord1 --> GenReg + Coord2 --> GenReg + Coord3 --> GenReg + GenReg -->|majority agrees| CC + + W1 -->|registrationClient()| WorkerPool + W2 -->|registrationClient()| WorkerPool + W3 -->|registrationClient()| WorkerPool + Wn -->|registrationClient()| WorkerPool + WorkerPool --> Recruit + Recruit -->|fitness + locality| Roles + + Monitor -->|process failure| Recruit + CC --> ServerDBInfo -->|broadcast| Workers + + style Election fill:#f3e5f5,stroke:#9c27b0 + style CC fill:#e1f0ff,stroke:#4a90d9 + style Roles fill:#fff3e0,stroke:#f5a623 + style Workers fill:#e8f5e9,stroke:#4caf50 +``` + +## Leader Election Protocol + +```mermaid +sequenceDiagram + participant C1 as CC Candidate 1 (better fitness) + participant C2 as CC Candidate 2 + participant Coord as Coordinators (majority) + + C1->>Coord: CandidacyRequest (fitness in changeID) + C2->>Coord: CandidacyRequest (fitness in changeID) + Coord->>Coord: Compare fitness (top bits of changeID) + Coord-->>C1: Nominated as leader + Coord-->>C2: Not nominated + C1->>C1: Become CC + C2->>C2: monitorLeader() — defer to C1 + Note over C1: Begins recruiting roles +``` + +## Role Recruitment Decision + +```mermaid +graph TD + Need["Need to fill role\n(e.g., CommitProxy)"] + Need --> Filter["Filter workers by\nProcessClass fitness"] + Filter --> Locality["Apply locality constraints\n(DC, zone, machine)"] + Locality --> Exclude["Exclude failed /\nexcluded processes"] + Exclude --> Best["Select best candidate"] + Best --> Recruit["Send recruitment request"] + Recruit --> Running["Role running on worker"] +``` + +## ServerDBInfo Broadcast + +```mermaid +graph LR + CC["Cluster Controller"] -->|update| Info["ServerDBInfo"] + + Info --> |contains| M["Master interface"] + Info --> |contains| PL["Proxy list"] + Info --> |contains| LSC["Log system config"] + Info --> |contains| RS["Recovery state"] + Info --> |contains| LB["Latency band config"] + + Info -->|broadcast to all| W["All Workers"] + W -->|react to changes| Roles["Update connections\nto new proxies, etc."] +``` diff --git a/design/AI-generated/diagram_05_commit_pipeline.md b/design/AI-generated/diagram_05_commit_pipeline.md new file mode 100644 index 00000000000..b962555c469 --- /dev/null +++ b/design/AI-generated/diagram_05_commit_pipeline.md @@ -0,0 +1,107 @@ +# Transaction Commit Pipeline — Internal Architecture + +```mermaid +graph TB + subgraph GRVPath["GRV Path (Read Versions)"] + Client1["Client"] + GRVProxy["GRV Proxy"] + RK["Ratekeeper"] + end + + subgraph CommitPath["Commit Path (4 Phases)"] + Client2["Client"] + Batcher["commitBatcher\n(batches by byte size)"] + CP["Commit Proxy"] + Seq["Master / Sequencer"] + Res["Resolver(s)\n(sharded by key range)"] + TLog["TLog (quorum write)"] + end + + Client1 -- "GetReadVersionRequest" --> GRVProxy + RK -- "rate limit" --> GRVProxy + GRVProxy -- "read version V" --> Client1 + + Client2 -- "CommitTransactionRequest" --> Batcher --> CP + CP -- "① GetCommitVersion" --> Seq + Seq -- "version V + prevVersion" --> CP + CP -- "② ResolveTransactionBatch" --> Res + Res -- "conflict/no-conflict per txn" --> CP + CP -- "③ LogSystem::push()" --> TLog + TLog -- "quorum ack" --> CP + CP -- "④ CommitTransactionReply" --> Client2 + + style GRVPath fill:#e1f0ff,stroke:#4a90d9 + style CommitPath fill:#fff3e0,stroke:#f5a623 +``` + +## Commit Proxy: Batch Processing Pipeline + +```mermaid +graph LR + subgraph Intake["Intake"] + Requests["Incoming\nCommitRequests"] + Batcher["Batcher\n(group by size/count)"] + end + + subgraph Phase1["Phase 1: Version"] + GetVer["GetCommitVersion\nfrom Sequencer"] + end + + subgraph Phase2["Phase 2: Resolve"] + Split["Split batch by\nresolver key range"] + R1["Resolver 1"] + R2["Resolver 2"] + Merge["Merge conflict\nresults"] + end + + subgraph Phase3["Phase 3: Log"] + TagMut["Tag mutations\n(key → shard → SS tag)"] + Push["LogSystem::push()\nto TLog quorum"] + end + + subgraph Phase4["Phase 4: Reply"] + Reply["Send replies\n(committed / conflicted)"] + end + + Requests --> Batcher --> GetVer --> Split + Split --> R1 --> Merge + Split --> R2 --> Merge + Merge --> TagMut --> Push --> Reply +``` + +## Resolver: Conflict Detection + +```mermaid +graph TB + subgraph Window["Sliding Conflict Window"] + Committed["Committed write ranges\n(version-ordered)"] + Incoming["Incoming txn batch\n(read version + read ranges)"] + end + + Incoming --> Check{"Any read range overlaps\nwith writes at versions\n> txn's read version?"} + Check -->|yes| Conflict["CONFLICT\n(txn rejected)"] + Check -->|no| NoConflict["NO CONFLICT\n(txn proceeds)"] + NoConflict --> Update["Add txn's write ranges\nto committed window"] + Update --> Expire["Expire old entries\nbeyond window"] + + style Conflict fill:#fce4ec,stroke:#e91e63 + style NoConflict fill:#e8f5e9,stroke:#4caf50 +``` + +## Master/Sequencer: Version Assignment + +```mermaid +sequenceDiagram + participant CP1 as Commit Proxy 1 + participant CP2 as Commit Proxy 2 + participant Seq as Sequencer + + Note over Seq: Maintains: nextVersion, prevVersion + CP1->>Seq: GetCommitVersion(lastVersion=100) + Seq->>Seq: nextVersion = max(wall_clock, prev+1) + Seq-->>CP1: version=150, prevVersion=100 + CP2->>Seq: GetCommitVersion(lastVersion=120) + Seq->>Seq: nextVersion = max(wall_clock, prev+1) + Seq-->>CP2: version=200, prevVersion=150 + Note over Seq: Monotonic, roughly tracks real time +``` diff --git a/design/AI-generated/diagram_06_tlog_logsystem.md b/design/AI-generated/diagram_06_tlog_logsystem.md new file mode 100644 index 00000000000..1e6939b1351 --- /dev/null +++ b/design/AI-generated/diagram_06_tlog_logsystem.md @@ -0,0 +1,139 @@ +# TLog & Log System — Internal Architecture + +```mermaid +graph TB + subgraph LogSystem["Log System Abstraction"] + LogSystemType["LogSystem"] + Push["push(mutations, version)\n→ quorum write"] + Peek["peek(tag, fromVersion)\n→ IPeekCursor"] + end + + subgraph TLogInstance["TLog Server Instance"] + Receive["Receive push\nfrom Commit Proxy"] + DiskQ["DiskQueue\n(append-only log)"] + MemIdx["In-Memory Index\n(tag → version → data)"] + PeekServe["Serve peek requests\nfrom Storage Servers"] + end + + subgraph Replication["Replication"] + TLog1["TLog 1"] + TLog2["TLog 2"] + TLog3["TLog 3"] + Quorum["Quorum: f+1 of 2f+1"] + end + + subgraph MultiRegion["Multi-Region"] + PrimaryTLogs["Primary TLogs"] + LogRouter["Log Router"] + RemoteTLogs["Remote TLogs"] + SatTLogs["Satellite TLogs"] + end + + subgraph Consumers["Log Consumers"] + SS["Storage Servers\n(peek by tag)"] + BW["Backup Worker\n(peek mutations)"] + end + + LogSystemType --> Push + LogSystemType --> Peek + + Push --> TLog1 + Push --> TLog2 + Push --> TLog3 + TLog1 --> Quorum + TLog2 --> Quorum + TLog3 --> Quorum + + Receive --> DiskQ + Receive --> MemIdx + MemIdx --> PeekServe + + PrimaryTLogs --> LogRouter --> RemoteTLogs + PrimaryTLogs --> SatTLogs + + SS --> Peek + BW --> Peek + + style LogSystem fill:#e1f0ff,stroke:#4a90d9 + style TLogInstance fill:#fff3e0,stroke:#f5a623 + style Replication fill:#e8f5e9,stroke:#4caf50 + style MultiRegion fill:#f3e5f5,stroke:#9c27b0 + style Consumers fill:#fffde7,stroke:#fbc02d +``` + +## Tag-Partitioned Mutation Flow + +```mermaid +sequenceDiagram + participant CP as Commit Proxy + participant LS as LogSystem + participant T1 as TLog 1 + participant T2 as TLog 2 + participant T3 as TLog 3 + participant SS_A as Storage Server A (tag=2) + participant SS_B as Storage Server B (tag=5) + + CP->>CP: Tag mutations by destination SS + Note over CP: set("apple","1") → tag=2 (SS_A's shard)
set("zebra","2") → tag=5 (SS_B's shard) + CP->>LS: push(tagged mutations, version V) + LS->>T1: replicate (all tags) + LS->>T2: replicate (all tags) + LS->>T3: replicate (all tags) + T1-->>LS: ack + T2-->>LS: ack + Note over LS: Quorum (2 of 3) — committed + + SS_A->>T1: peek(tag=2, fromVersion) + T1-->>SS_A: set("apple","1") at V + SS_B->>T2: peek(tag=5, fromVersion) + T2-->>SS_B: set("zebra","2") at V +``` + +## Log Epochs and Recovery + +```mermaid +graph LR + subgraph Epoch1["Epoch 1 (old)"] + OT1["TLog A"] + OT2["TLog B"] + OT3["TLog C"] + end + + subgraph Epoch2["Epoch 2 (old)"] + OT4["TLog D"] + OT5["TLog E"] + OT6["TLog F"] + end + + subgraph Epoch3["Epoch 3 (current)"] + NT1["TLog G"] + NT2["TLog H"] + NT3["TLog I"] + end + + Epoch1 -->|"recovery: lock + peek"| Epoch2 + Epoch2 -->|"recovery: lock + peek"| Epoch3 + + SS["Storage Server"] -->|"peek across epochs\n(merged cursor)"| Epoch1 + SS -->|"peek across epochs\n(merged cursor)"| Epoch2 + SS -->|"peek across epochs\n(merged cursor)"| Epoch3 + + Note1["Old epochs kept until\nall SS have consumed\ntheir data, then GC'd"] + + style Epoch1 fill:#fffde7,stroke:#fbc02d + style Epoch2 fill:#fff3e0,stroke:#f5a623 + style Epoch3 fill:#e8f5e9,stroke:#4caf50 +``` + +## TLog Internal Write Path + +```mermaid +graph TD + Push["push(mutations, version)"] + Push --> Deser["Deserialize mutations"] + Deser --> TagIndex["Index by tag\nin memory"] + Deser --> DiskWrite["Append to DiskQueue\n(sequential I/O)"] + DiskWrite --> Fsync["fsync / fdatasync"] + Fsync --> Ack["Acknowledge to\nCommit Proxy"] + TagIndex --> ServePeek["Serve peek(tag)\nfrom memory or disk"] +``` diff --git a/design/AI-generated/diagram_07_storage_server.md b/design/AI-generated/diagram_07_storage_server.md new file mode 100644 index 00000000000..7a789480fe9 --- /dev/null +++ b/design/AI-generated/diagram_07_storage_server.md @@ -0,0 +1,119 @@ +# Storage Server & Engines — Internal Architecture + +```mermaid +graph TB + subgraph UpdateLoop["Update Loop (pull from log)"] + Peek["peek(tag, fromVersion)\nfrom TLog"] + Deser["Deserialize mutations"] + Apply["Apply to KVStore"] + AdvVer["Advance durable version"] + end + + subgraph ReadServing["Read Serving"] + GetVal["GetValueRequest"] + GetKey["GetKeyRequest"] + GetRange["GetKeyValuesRequest"] + VerCheck["Version check\n(oldest ≤ V ≤ current)"] + ShardCheck["Shard ownership check"] + end + + subgraph Versions["Version Management"] + StorageVer["storageVersion\n(latest applied)"] + DurableVer["durableVersion\n(latest fsynced)"] + OldestVer["desiredOldestVersion\n(GC boundary)"] + end + + subgraph Engine["KV Engine (pluggable)"] + IKV["IKeyValueStore interface"] + RocksDB["RocksDB\n(primary production)"] + ShardedRocks["Sharded RocksDB\n(per-shard column families)"] + SQLite["SQLite\n(legacy)"] + MemEngine["Memory Engine\n(testing / txnStateStore)"] + end + + subgraph ShardMgmt["Shard Management"] + Shards["Assigned Shards\n(key ranges)"] + AddShard["addShard\n(from data movement)"] + RemShard["removeShard"] + FetchKeys["fetchKeys\n(bulk copy from source SS)"] + end + + Peek --> Deser --> Apply --> AdvVer + Apply --> IKV + + GetVal --> VerCheck + GetKey --> VerCheck + GetRange --> VerCheck + VerCheck --> ShardCheck --> IKV + IKV --> RocksDB + IKV --> ShardedRocks + IKV --> SQLite + IKV --> MemEngine + + StorageVer --> VerCheck + DurableVer --> VerCheck + OldestVer --> VerCheck + + AddShard --> FetchKeys --> Shards + RemShard --> Shards + + style UpdateLoop fill:#e1f0ff,stroke:#4a90d9 + style ReadServing fill:#e8f5e9,stroke:#4caf50 + style Versions fill:#fff3e0,stroke:#f5a623 + style Engine fill:#fce4ec,stroke:#e91e63 + style ShardMgmt fill:#f3e5f5,stroke:#9c27b0 +``` + +## Storage Server Lifecycle + +```mermaid +stateDiagram-v2 + [*] --> Recruited: DD assigns shard + Recruited --> FetchingKeys: fetchKeys from source SS + FetchingKeys --> WaitingForLog: data fetched, start peeking log + WaitingForLog --> Serving: caught up to current version + Serving --> Serving: continuous peek + apply + serve reads + Serving --> Removed: DD moves shard away + Removed --> [*] +``` + +## Read Request Processing + +```mermaid +sequenceDiagram + participant Client as Client + participant SS as Storage Server + participant KV as KV Engine + + Client->>SS: GetKeyValuesRequest(range, version V) + SS->>SS: Check: oldest ≤ V ≤ storageVersion? + alt Version too old + SS-->>Client: Error: transaction_too_old + else Version too new + SS->>SS: Wait until storageVersion ≥ V + end + SS->>SS: Check: owns shard for range? + alt Shard not owned + SS-->>Client: Error: wrong_shard_server + end + SS->>KV: readRange(range, version V) + KV-->>SS: results + SS-->>Client: GetKeyValuesReply(results) +``` + +## Storage Engine Comparison + +```mermaid +graph LR + subgraph Engines + R["RocksDB"] + SR["Sharded RocksDB"] + SQ["SQLite"] + M["Memory"] + end + + R --- R_desc["LSM-tree, production default\nGood compression, range perf"] + SR --- SR_desc["Per-shard column families\nBetter isolation between shards"] + SQ --- SQ_desc["B-tree, legacy\nStill supported"] + M --- M_desc["In-memory only\nFor txnStateStore, testing"] +``` diff --git a/design/AI-generated/diagram_08_data_distribution.md b/design/AI-generated/diagram_08_data_distribution.md new file mode 100644 index 00000000000..776ee0aeb39 --- /dev/null +++ b/design/AI-generated/diagram_08_data_distribution.md @@ -0,0 +1,129 @@ +# Data Distribution — Internal Architecture + +```mermaid +graph TB + subgraph DD["Data Distributor (singleton)"] + DDMain["DD Main Loop"] + end + + subgraph Tracking["Shard Tracking"] + ShardTracker["DDShardTracker\n(monitors size + load)"] + Split["Split decision\n(shard too large)"] + Merge["Merge decision\n(adjacent shards too small)"] + end + + subgraph Teams["Team Management"] + TeamColl["DDTeamCollection"] + BuildTeams["buildTeams()\n(satisfy replication policy)"] + HealthyTeams["Healthy Teams"] + UnhealthyTeams["Unhealthy Teams\n(missing replicas)"] + SATF["ShardsAffectedByTeamFailure\n(tracks which shards need moves)"] + end + + subgraph Relocation["Relocation Queue"] + RelocQ["DDRelocationQueue\n(prioritized)"] + Priority["Priorities:\n• Emergency (replica lost)\n• High (rebalance unhealthy)\n• Normal (load balance)\n• Low (merge)"] + Parallel["Concurrent moves\n(parallelism limit)"] + end + + subgraph MoveKeys["MoveKeys Protocol"] + Lock["moveKeysLock\n(serialize concurrent moves)"] + Fetch["Dest SS: fetchKeys\nfrom source SS"] + LogPeek["Dest SS: start\npeeking TLog"] + AtomicXfer["Atomic ownership\ntransfer (system key txn)"] + end + + DDMain --> ShardTracker + DDMain --> TeamColl + DDMain --> RelocQ + + ShardTracker --> Split + ShardTracker --> Merge + Split --> RelocQ + Merge --> RelocQ + TeamColl --> BuildTeams --> HealthyTeams + BuildTeams --> UnhealthyTeams + UnhealthyTeams --> SATF --> RelocQ + + RelocQ --> Priority --> Parallel --> MoveKeys + Lock --> Fetch --> LogPeek --> AtomicXfer + + style DD fill:#e1f0ff,stroke:#4a90d9 + style Tracking fill:#e8f5e9,stroke:#4caf50 + style Teams fill:#fff3e0,stroke:#f5a623 + style Relocation fill:#fce4ec,stroke:#e91e63 + style MoveKeys fill:#f3e5f5,stroke:#9c27b0 +``` + +## Shard Move Sequence + +```mermaid +sequenceDiagram + participant DD as Data Distributor + participant RQ as Relocation Queue + participant MK as MoveKeys + participant Src as Source SS + participant Dst as Destination SS + participant TLog as TLog + participant SysKey as System Key Space + + DD->>RQ: RelocateShard(range, priority) + RQ->>MK: execute(range, srcTeam, dstTeam) + MK->>MK: Acquire moveKeysLock + + MK->>Dst: Assign shard (begin fetch) + Dst->>Src: fetchKeys(range) + Src-->>Dst: key-value data (bulk) + + Dst->>TLog: peek(tag, version) — start consuming + TLog-->>Dst: mutations (catch up) + Note over Dst: Catching up to current version... + + Dst-->>MK: Ready (caught up) + + MK->>SysKey: Transaction: update keyServers + serverKeys + Note over MK: Atomic ownership transfer + SysKey-->>MK: committed + + MK->>Src: Remove shard assignment + MK-->>RQ: Move complete + MK->>MK: Release moveKeysLock +``` + +## Team Building + +```mermaid +graph TD + Policy["Replication Policy\ne.g., triple redundancy\nacross 3 zones"] + Policy --> Candidates["Available Storage Servers\n(with LocalityData)"] + Candidates --> Build["Build teams of N servers\nsatisfying zone/DC/rack diversity"] + Build --> Team1["Team {SS1-zoneA, SS2-zoneB, SS3-zoneC}"] + Build --> Team2["Team {SS1-zoneA, SS4-zoneD, SS5-zoneE}"] + Build --> TeamN["..."] + + Team1 --> Health{"All members\nhealthy?"} + Health -->|yes| Healthy["Healthy Team List"] + Health -->|no| Unhealthy["Unhealthy Team List\n→ trigger re-replication"] +``` + +## Reasons for Data Movement + +```mermaid +graph LR + subgraph Triggers + T1["Shard too large\n→ split"] + T2["SS failure\n→ re-replicate"] + T3["Load imbalance\n→ rebalance"] + T4["Config change\n(replication factor)"] + T5["Server exclusion\n(maintenance)"] + T6["Storage wiggle\n(proactive)"] + end + + T1 --> RQ["Relocation Queue"] + T2 --> RQ + T3 --> RQ + T4 --> RQ + T5 --> RQ + T6 --> RQ + RQ --> Move["Execute MoveKeys"] +``` diff --git a/design/AI-generated/diagram_09_cluster_recovery.md b/design/AI-generated/diagram_09_cluster_recovery.md new file mode 100644 index 00000000000..6b86b6e7cae --- /dev/null +++ b/design/AI-generated/diagram_09_cluster_recovery.md @@ -0,0 +1,149 @@ +# Cluster Recovery — Internal Architecture + +```mermaid +stateDiagram-v2 + [*] --> READING_CSTATE: Master failure detected + + state READING_CSTATE { + [*] --> ReadCoord: Read from coordinators + ReadCoord --> ParseDBState: Parse DBCoreState + ParseDBState --> LearnOldEpoch: Learn previous epoch's TLogs + } + + READING_CSTATE --> LOCKING_CSTATE + + state LOCKING_CSTATE { + [*] --> LockOldTLogs: Prevent split-brain + LockOldTLogs --> EndOldEpoch: Old TLogs stop accepting writes + } + + LOCKING_CSTATE --> RECRUITING + + state RECRUITING { + [*] --> RecruitTLogs: New TLog set + RecruitTLogs --> RecruitProxies: CommitProxies + GrvProxies + RecruitProxies --> RecruitResolvers: Resolvers + } + + RECRUITING --> RECOVERY_TRANSACTION + + state RECOVERY_TRANSACTION { + [*] --> PeekOldLogs: Read uncommitted mutations + PeekOldLogs --> ReplayTxnState: Reconstruct txnStateStore + ReplayTxnState --> SetRecoveryVer: Establish recovery version + } + + RECOVERY_TRANSACTION --> WRITING_CSTATE + + state WRITING_CSTATE { + [*] --> WriteCoord: Write new epoch to coordinators + WriteCoord --> [*]: New epoch official + } + + WRITING_CSTATE --> ACCEPTING_COMMITS: Cluster is live + + ACCEPTING_COMMITS --> ALL_LOGS_RECRUITED: Remote TLogs recruited + + ALL_LOGS_RECRUITED --> FULLY_RECOVERED: Old generations consumed + GC'd +``` + +## Recovery Data Flow + +```mermaid +graph TB + subgraph OldEpoch["Old Epoch"] + OldTLogs["Old TLogs\n(locked)"] + OldCState["Old Coordinated State\n(stored on coordinators)"] + end + + subgraph Recovery["Recovery Process"] + ReadCState["① Read coordinated state"] + LockTLogs["② Lock old TLogs"] + PeekReplay["③ Peek + replay\ntxnStateStore"] + RecVer["④ Set recovery version"] + WriteCState["⑤ Write new coordinated\nstate to coordinators"] + end + + subgraph NewEpoch["New Epoch"] + NewTLogs["New TLogs"] + NewProxies["New Proxies"] + NewResolvers["New Resolvers"] + NewMaster["New Master/Sequencer"] + end + + OldCState --> ReadCState + ReadCState --> LockTLogs --> OldTLogs + OldTLogs --> PeekReplay + PeekReplay --> RecVer + RecVer --> WriteCState + + WriteCState --> NewTLogs + WriteCState --> NewProxies + WriteCState --> NewResolvers + WriteCState --> NewMaster + + NewTLogs -.->|"SS peek across epochs"| OldTLogs + + style OldEpoch fill:#fffde7,stroke:#fbc02d + style Recovery fill:#e1f0ff,stroke:#4a90d9 + style NewEpoch fill:#e8f5e9,stroke:#4caf50 +``` + +## Generation GC (trackTlogRecovery) + +```mermaid +sequenceDiagram + participant TR as trackTlogRecovery + participant TLogs as TLog replicas + participant CState as Coordinated State + + loop Poll TLog recovery progress + TR->>TLogs: trackRecovery.getReply() + TLogs-->>TR: oldestUnrecoveredStartVersion + TR->>TR: recoveredVersion = min(all TLogs) + end + + TR->>TR: For each old generation:
if recoverAt < recoveredVersion
AND epoch < oldestBackupEpoch + TR->>CState: Purge old generation data + Note over CState: oldTLogData.resize(i)
removes consumed generations + TR->>CState: Write updated state +``` + +## Multi-Region Recovery + +```mermaid +graph TB + subgraph Primary["Primary DC"] + PCC["Cluster Controller"] + PTLogs["Primary TLogs"] + PProxies["Proxies + Resolvers"] + end + + subgraph Satellite["Satellite DCs"] + STLogs1["Satellite TLogs (DC2)"] + STLogs2["Satellite TLogs (DC4)"] + end + + subgraph Remote["Remote DC"] + RTLogs["Remote TLogs"] + RLogRouter["Log Router"] + end + + PCC -->|recruit| PTLogs + PCC -->|recruit| PProxies + PCC -->|recruit| STLogs1 + PCC -->|recruit| STLogs2 + PCC -->|recruit| RTLogs + PCC -->|recruit| RLogRouter + + PProxies -->|push| PTLogs + PTLogs -->|replicate| STLogs1 + PTLogs -->|replicate| STLogs2 + PTLogs -->|route via| RLogRouter -->|push| RTLogs + + Note1["Recovery states:\nACCEPTING_COMMITS = primary ready\nALL_LOGS_RECRUITED = remote ready\nFULLY_RECOVERED = all generations GC'd"] + + style Primary fill:#e8f5e9,stroke:#4caf50 + style Satellite fill:#fff3e0,stroke:#f5a623 + style Remote fill:#e1f0ff,stroke:#4a90d9 +``` diff --git a/design/AI-generated/diagram_10_ratekeeper.md b/design/AI-generated/diagram_10_ratekeeper.md new file mode 100644 index 00000000000..6e8189d4533 --- /dev/null +++ b/design/AI-generated/diagram_10_ratekeeper.md @@ -0,0 +1,91 @@ +# Rate Keeping & Throttling — Internal Architecture + +```mermaid +graph TB + subgraph Monitoring["What Ratekeeper Monitors"] + SSQueue["Storage Server\nqueue depth"] + TLogQueue["TLog\nqueue depth"] + DiskIO["Storage Server\ndisk throughput / IOPS"] + TagRates["Per-tag\ntransaction rates"] + end + + subgraph RK["Ratekeeper"] + Calc["Rate Calculator"] + TT["TagThrottler\n(per-tag limits)"] + end + + subgraph Control["What Ratekeeper Controls"] + TxnRate["Transaction rate limit\n(to GRV Proxies)"] + BatchRate["Batch priority rate\n(lower priority)"] + TagThrottle["Per-tag throttle\n(slow/reject specific tags)"] + end + + subgraph Enforcement["Enforcement"] + GRVProxy["GRV Proxy"] + Delay["Delay GetReadVersion\nresponses"] + Reject["Reject when\noverthrottled"] + end + + SSQueue --> Calc + TLogQueue --> Calc + DiskIO --> Calc + TagRates --> Calc + Calc --> TxnRate + Calc --> BatchRate + TagRates --> TT --> TagThrottle + + TxnRate --> GRVProxy + BatchRate --> GRVProxy + TagThrottle --> GRVProxy + GRVProxy --> Delay + GRVProxy --> Reject + + style Monitoring fill:#e1f0ff,stroke:#4a90d9 + style RK fill:#fff3e0,stroke:#f5a623 + style Control fill:#e8f5e9,stroke:#4caf50 + style Enforcement fill:#fce4ec,stroke:#e91e63 +``` + +## Back-Pressure Feedback Loop + +```mermaid +sequenceDiagram + participant SS as Storage Servers + participant TLog as TLogs + participant RK as Ratekeeper + participant GRV as GRV Proxy + participant Client as Clients + + SS->>RK: Report queue depth, disk stats + TLog->>RK: Report queue depth + RK->>RK: Calculate safe transaction rate + RK->>GRV: UpdateRateInfo(txnRate, batchRate) + + Client->>GRV: GetReadVersion + alt Under limit + GRV-->>Client: version (immediately) + else Over limit + GRV->>GRV: Delay response + GRV-->>Client: version (delayed) + end + Note over RK: Continuous feedback loop:
SS falls behind → lower rate →
fewer commits → SS catches up →
raise rate +``` + +## Tag-Based Throttling + +```mermaid +graph TD + Txn["Transaction with tag='team-A'"] + Txn --> GRV["GRV Proxy"] + GRV --> CheckTag{"Tag throttled?"} + CheckTag -->|no| Proceed["Assign read version"] + CheckTag -->|yes| CheckRate{"Under tag's\nrate limit?"} + CheckRate -->|yes| Proceed + CheckRate -->|no| ThrottleResp["Reject or delay\n(tag_throttled error)"] + + TT["TagThrottler"] -->|"per-tag limits"| GRV + RK["Ratekeeper"] -->|"tag usage stats"| TT + + style ThrottleResp fill:#fce4ec,stroke:#e91e63 + style Proceed fill:#e8f5e9,stroke:#4caf50 +``` diff --git a/design/AI-generated/diagram_11_backup_dr.md b/design/AI-generated/diagram_11_backup_dr.md new file mode 100644 index 00000000000..b98a49a0df1 --- /dev/null +++ b/design/AI-generated/diagram_11_backup_dr.md @@ -0,0 +1,114 @@ +# Backup, Restore & DR — Internal Architecture + +```mermaid +graph TB + subgraph BackupPipeline["Backup Pipeline"] + BW["Backup Worker\n(server-side role)"] + TLog["TLog"] + FBA["FileBackupAgent\n(orchestrator)"] + TaskBucket["TaskBucket\n(reliable task queue)"] + end + + subgraph FileTypes["Backup File Types"] + RangeFile["Range Files\n(KV snapshot at version)"] + LogFile["Log Files\n(mutation stream\nbetween versions)"] + end + + subgraph Storage["Backup Storage"] + BC["BackupContainer\n(abstract interface)"] + LocalDir["Local Directory"] + S3["S3 Blob Store"] + end + + subgraph Restore["Restore"] + RA["Restore Agent"] + ReadRange["Read range files\n(base state)"] + ApplyLog["Apply log files\n(to target version)"] + PrefixXform["Key prefix\ntransformation"] + end + + subgraph DR["Disaster Recovery"] + DBA["DatabaseBackupAgent"] + SrcCluster["Source Cluster"] + DstCluster["Standby Cluster"] + end + + BW -->|"peek mutations"| TLog + FBA --> TaskBucket + BW --> RangeFile + BW --> LogFile + RangeFile --> BC + LogFile --> BC + BC --> LocalDir + BC --> S3 + + RA --> ReadRange --> BC + RA --> ApplyLog --> BC + RA --> PrefixXform + + DBA -->|"stream mutations"| SrcCluster + DBA -->|"apply mutations"| DstCluster + + style BackupPipeline fill:#e1f0ff,stroke:#4a90d9 + style FileTypes fill:#fff3e0,stroke:#f5a623 + style Storage fill:#e8f5e9,stroke:#4caf50 + style Restore fill:#fce4ec,stroke:#e91e63 + style DR fill:#f3e5f5,stroke:#9c27b0 +``` + +## Backup Lifecycle + +```mermaid +sequenceDiagram + participant User as User / fdbbackup + participant FBA as FileBackupAgent + participant TB as TaskBucket + participant BW as Backup Worker + participant TLog as TLogs + participant BC as BackupContainer (S3) + + User->>FBA: Start backup + FBA->>TB: Create backup tasks + + loop Continuous + TB->>BW: Snapshot task + BW->>BW: Read key ranges from SS + BW->>BC: Write range files + + TB->>BW: Log task + BW->>TLog: peek(logRouterTag) + TLog-->>BW: mutation stream + BW->>BC: Write log files + end + + User->>FBA: Stop backup + FBA->>TB: Finalize + Note over BC: Backup complete:
range files + log files
= point-in-time restorable +``` + +## Point-in-Time Restore + +```mermaid +graph LR + subgraph BackupData["Backup Data"] + R1["Range File @ V100\n(full snapshot)"] + L1["Log File V100→V200"] + L2["Log File V200→V300"] + L3["Log File V300→V400"] + end + + subgraph Restore["Restore to V350"] + Step1["① Load range file @ V100\n(base state)"] + Step2["② Apply logs V100→V200"] + Step3["③ Apply logs V200→V300"] + Step4["④ Apply logs V300→V350\n(partial)"] + end + + R1 --> Step1 + L1 --> Step2 + L2 --> Step3 + L3 --> Step4 + Step4 --> Result["Restored cluster\nat version 350"] + + style Result fill:#e8f5e9,stroke:#4caf50 +``` diff --git a/design/AI-generated/diagram_12_simulation_testing.md b/design/AI-generated/diagram_12_simulation_testing.md new file mode 100644 index 00000000000..b40a6ef7e18 --- /dev/null +++ b/design/AI-generated/diagram_12_simulation_testing.md @@ -0,0 +1,155 @@ +# Simulation & Testing — Internal Architecture + +```mermaid +graph TB + subgraph Sim2["Sim2 (replaces Net2)"] + VirtProcs["Virtual Processes\n(many in one OS thread)"] + DetTime["Deterministic Time\n(virtual clock)"] + DetRand["deterministicRandom()\n(seedable PRNG)"] + SimNet["Simulated Network"] + end + + subgraph FaultInjection["Fault Injection"] + Buggify["BUGGIFY\n(probabilistic code paths)"] + NetFaults["Network Faults"] + ProcFaults["Process Faults"] + DiskFaults["Disk Faults"] + end + + subgraph NetFaultTypes["Network Fault Types"] + Clog["Clogging\n(delay packets)"] + Partition["Partitions\n(drop packets)"] + Latency["Latency injection"] + end + + subgraph ProcFaultTypes["Process Fault Types"] + KillProc["Kill process"] + KillMachine["Kill machine\n(all processes)"] + KillZone["Kill zone"] + KillDC["Kill datacenter"] + Reboot["Reboot process"] + end + + subgraph TestFramework["Test Framework"] + TOML["Test Config\n(.toml files)"] + SimCluster["SimulatedCluster\n(virtual topology)"] + Tester["Test Runner\n(fdbserver -r simulation)"] + end + + subgraph Workloads["Workloads (~160)"] + WBase["TestWorkload base class"] + Setup["setup()"] + Start["start()"] + Check["check()"] + Metrics["getMetrics()"] + end + + Tester --> TOML --> SimCluster --> VirtProcs + VirtProcs --> DetTime + VirtProcs --> DetRand + VirtProcs --> SimNet + SimNet --> NetFaults --> NetFaultTypes + ProcFaults --> ProcFaultTypes + DiskFaults --> SimNet + + Buggify --> VirtProcs + + Tester --> WBase + WBase --> Setup --> Start --> Check --> Metrics + + style Sim2 fill:#e1f0ff,stroke:#4a90d9 + style FaultInjection fill:#fce4ec,stroke:#e91e63 + style NetFaultTypes fill:#fff3e0,stroke:#f5a623 + style ProcFaultTypes fill:#fff3e0,stroke:#f5a623 + style TestFramework fill:#e8f5e9,stroke:#4caf50 + style Workloads fill:#f3e5f5,stroke:#9c27b0 +``` + +## Deterministic Simulation Flow + +```mermaid +sequenceDiagram + participant User as User + participant Tester as fdbserver -r simulation + participant Sim as Sim2 + participant Cluster as Virtual Cluster + + User->>Tester: seed=12345, test=Fast/Cycle.toml + Tester->>Sim: Initialize with seed + Sim->>Sim: deterministicRandom(12345) + Sim->>Cluster: Create virtual topology
(machines, DCs, processes) + + loop Simulation Loop + Sim->>Sim: Advance virtual clock
to next event + Sim->>Cluster: Deliver network messages + Sim->>Cluster: Fire timers + Sim->>Cluster: Inject faults (BUGGIFY) + Cluster->>Cluster: Execute actors + end + + Tester->>Tester: Run workload check() + Tester->>User: Pass/Fail + Unseed + Note over User: Same seed → same unseed
(on identical binary) +``` + +## BUGGIFY System + +```mermaid +graph TD + subgraph BuggifyDecision["At Startup (per BUGGIFY site)"] + Site["BUGGIFY at file:line"] + Coin["deterministicRandom()\ncoin flip"] + Coin -->|"~10% chance"| Enabled["Site ENABLED\nfor this run"] + Coin -->|"~90% chance"| Disabled["Site DISABLED\nfor this run"] + end + + subgraph Runtime["At Runtime"] + Enabled --> Hit{"Code reaches\nBUGGIFY site"} + Hit -->|"second coin flip\n(~10%)"| Fire["Fault injected"] + Hit -->|"~90%"| Skip["Normal path"] + end + + subgraph Examples["Example BUGGIFY Effects"] + E1["Short timeouts\n(trigger timeouts)"] + E2["Extra delays\n(expose races)"] + E3["Small buffer sizes\n(exercise edge cases)"] + E4["Wrong shard responses\n(test retry logic)"] + E5["Knob value changes\n(test parameter sensitivity)"] + end + + Fire --> E1 + Fire --> E2 + Fire --> E3 + Fire --> E4 + Fire --> E5 + + style BuggifyDecision fill:#e1f0ff,stroke:#4a90d9 + style Runtime fill:#fff3e0,stroke:#f5a623 + style Examples fill:#fce4ec,stroke:#e91e63 +``` + +## Test Organization + +```mermaid +graph LR + subgraph Tests["tests/"] + Fast["fast/\n(quick correctness)"] + Rare["rare/\n(failure scenarios:\nrollbacks, clogging,\nattrition)"] + Slow["slow/\n(longer, complex\nscenarios)"] + Negative["negative/\n(error conditions)"] + NoSim["noSim/\n(live cluster\nintegration tests)"] + end + + subgraph WorkloadTypes["Workload Categories"] + Correctness["Correctness\n(Cycle, AtomicOps,\nWriteDuringRead)"] + Config["Configuration\n(ConfigureDatabase,\nChangeConfig)"] + Failure["Failure\n(Attrition, Rollback,\nMachineAttrition)"] + Perf["Performance\n(ReadWrite,\nThroughput)"] + Feature["Feature-specific\n(Backup, BulkLoad,\nGcGenerations)"] + end + + Fast --> TOML[".toml config\n(workload list +\nparameters)"] + Rare --> TOML + Slow --> TOML + TOML --> WorkloadTypes +``` diff --git a/design/AI-generated/diagram_overview.md b/design/AI-generated/diagram_overview.md new file mode 100644 index 00000000000..b2118616e83 --- /dev/null +++ b/design/AI-generated/diagram_overview.md @@ -0,0 +1,163 @@ +# FoundationDB Architecture Overview + +## System-Level Architecture + +```mermaid +graph TB + subgraph Client["Client Layer"] + App["Application"] + CAPI["C API / Bindings"] + MV["MultiVersionTransaction"] + RYW["ReadYourWritesTransaction"] + Native["NativeAPI / Transaction"] + end + + subgraph Coordination["Coordination"] + Coord["Coordinators"] + CC["Cluster Controller"] + end + + subgraph CommitPipeline["Transaction Commit Pipeline"] + GRV["GRV Proxy"] + CP["Commit Proxy"] + Seq["Master / Sequencer"] + Res["Resolver"] + end + + subgraph LogSystem["Log System"] + TLog["TLog Replicas"] + LR["Log Router"] + TLogRemote["Remote TLogs"] + end + + subgraph Storage["Storage Layer"] + SS["Storage Server"] + KV["KV Engine\n(RocksDB / SQLite / Memory)"] + end + + subgraph Background["Background Services"] + DD["Data Distributor"] + RK["Ratekeeper"] + BW["Backup Worker"] + end + + App --> CAPI --> MV --> RYW --> Native + + Native -->|GetReadVersion| GRV + Native -->|"get / getRange"| SS + Native -->|commit| CP + + GRV -.->|rate limit check| RK + CP -->|GetCommitVersion| Seq + CP -->|resolve conflicts| Res + CP -->|push mutations| TLog + + SS -->|"peek (pull)"| TLog + SS --> KV + + TLog -->|route to remote| LR --> TLogRemote + + CC -->|recruit| CP + CC -->|recruit| GRV + CC -->|recruit| Res + CC -->|recruit| Seq + CC -->|recruit| TLog + CC -->|recruit| SS + CC -->|recruit| DD + CC -->|recruit| RK + CC -->|leader election| Coord + + DD -->|move shards| SS + RK -->|throttle signals| GRV + BW -->|peek mutations| TLog + + style Client fill:#e1f0ff,stroke:#4a90d9 + style CommitPipeline fill:#fff3e0,stroke:#f5a623 + style LogSystem fill:#e8f5e9,stroke:#4caf50 + style Storage fill:#fce4ec,stroke:#e91e63 + style Coordination fill:#f3e5f5,stroke:#9c27b0 + style Background fill:#fffde7,stroke:#fbc02d +``` + +## Write Path + +```mermaid +sequenceDiagram + participant App as Application + participant CP as Commit Proxy + participant Seq as Master/Sequencer + participant Res as Resolver + participant TLog as TLog (quorum) + participant SS as Storage Server + + App->>CP: CommitTransactionRequest + CP->>Seq: GetCommitVersion + Seq-->>CP: version V (monotonic) + CP->>Res: ResolveTransactionBatch + Res-->>CP: conflict/no-conflict + Note over CP: Drop conflicting txns + CP->>TLog: push(mutations, version V) + TLog-->>CP: ack (quorum) + CP-->>App: committed at version V + Note over TLog,SS: Async (decoupled) + SS->>TLog: peek(tag, fromVersion) + TLog-->>SS: mutations + SS->>SS: apply to KVStore +``` + +## Read Path + +```mermaid +sequenceDiagram + participant App as Application + participant GRV as GRV Proxy + participant RK as Ratekeeper + participant SS as Storage Server + participant KV as KV Engine + + App->>GRV: GetReadVersion + Note over GRV,RK: Rate-limited + GRV-->>App: read version V + App->>SS: get/getRange at version V + SS->>KV: read(key, version V) + KV-->>SS: value + SS-->>App: result +``` + +## Recovery Path + +```mermaid +stateDiagram-v2 + [*] --> READING_CSTATE: CC detects failure + READING_CSTATE --> LOCKING_CSTATE: Read coordinated state + LOCKING_CSTATE --> RECRUITING: Lock old TLogs + RECRUITING --> RECOVERY_TRANSACTION: Recruit new roles + RECOVERY_TRANSACTION --> WRITING_CSTATE: Replay last epoch + WRITING_CSTATE --> ACCEPTING_COMMITS: Write new coordinated state + ACCEPTING_COMMITS --> ALL_LOGS_RECRUITED: Primary + remote TLogs ready + ALL_LOGS_RECRUITED --> FULLY_RECOVERED: Old TLog data consumed + FULLY_RECOVERED --> [*] +``` + +## Data Movement Path + +```mermaid +sequenceDiagram + participant DDT as DD Shard Tracker + participant DDQ as DD Relocation Queue + participant MK as MoveKeys Protocol + participant Src as Source SS + participant Dst as Destination SS + participant TLog as TLog + + DDT->>DDQ: RelocateShard (priority) + DDQ->>MK: execute move + MK->>Dst: fetch data from source + Dst->>Src: bulk fetch key range + Src-->>Dst: data + Dst->>TLog: start peeking tag + TLog-->>Dst: mutations (catch up) + Note over MK: Atomic ownership transfer + MK->>MK: update keyServers system key + MK-->>DDQ: move complete +``` diff --git a/design/AI-generated/foundationdb_subsystem_map.md b/design/AI-generated/foundationdb_subsystem_map.md new file mode 100644 index 00000000000..e714626692e --- /dev/null +++ b/design/AI-generated/foundationdb_subsystem_map.md @@ -0,0 +1,359 @@ +# FoundationDB Codebase: Major Subsystem Map + +This is a research document, not an implementation plan. It partitions the entire FoundationDB codebase into 12 major subsystems based on data flow and runtime relationships. + +--- + +## The 12 Subsystems at a Glance + +| # | Subsystem | Primary Location | ~LOC | One-line summary | +|---|-----------|-----------------|------|------------------| +| 1 | [**Flow Runtime**](subsystem_01_flow_runtime.md) | [`flow/`](https://github.com/apple/foundationdb/tree/main/flow) | 31K impl + headers | Async actor framework, event loop, deterministic time, memory arenas | +| 2 | [**RPC & Transport**](subsystem_02_rpc_transport.md) | [`fdbrpc/`](https://github.com/apple/foundationdb/tree/main/fdbrpc) | 28K impl + headers | Endpoint-addressed messaging, peer management, failure monitoring, simulation network | +| 3 | [**Client Library**](subsystem_03_client_library.md) | [`fdbclient/`](https://github.com/apple/foundationdb/tree/main/fdbclient) | 75K impl, 34K headers | Transaction API, read-your-writes, location caching, multi-version client | +| 4 | [**Cluster Controller & Coordination**](subsystem_04_cluster_controller.md) | [`fdbserver/clustercontroller/`](https://github.com/apple/foundationdb/tree/main/fdbserver/clustercontroller), [`fdbserver/coordinator/`](https://github.com/apple/foundationdb/tree/main/fdbserver/coordinator), [`fdbserver/core/LeaderElection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.actor.cpp) | 13K + coordination | Leader election, role recruitment, process registration, cluster-wide decision making | +| 5 | [**Transaction Commit Pipeline**](subsystem_05_commit_pipeline.md) | [`fdbserver/commitproxy/`](https://github.com/apple/foundationdb/tree/main/fdbserver/commitproxy), [`fdbserver/grvproxy/`](https://github.com/apple/foundationdb/tree/main/fdbserver/grvproxy), [`fdbserver/resolver/`](https://github.com/apple/foundationdb/tree/main/fdbserver/resolver), [`fdbserver/sequencer/`](https://github.com/apple/foundationdb/tree/main/fdbserver/sequencer) | ~12K | Version assignment, conflict detection, commit batching — the write-path orchestration | +| 6 | [**Transaction Log (TLog) & Log System**](subsystem_06_tlog_logsystem.md) | [`fdbserver/tlog/`](https://github.com/apple/foundationdb/tree/main/fdbserver/tlog), [`fdbserver/logsystem/`](https://github.com/apple/foundationdb/tree/main/fdbserver/logsystem), [`fdbserver/logrouter/`](https://github.com/apple/foundationdb/tree/main/fdbserver/logrouter) | 17K | Durable mutation logging, tag-partitioned replication, peek cursors for storage servers | +| 7 | [**Storage Server & Engines**](subsystem_07_storage_server.md) | [`fdbserver/storageserver/`](https://github.com/apple/foundationdb/tree/main/fdbserver/storageserver), [`fdbserver/kvstore/`](https://github.com/apple/foundationdb/tree/main/fdbserver/kvstore) | 63K | Serves reads, applies mutations from log, pluggable storage backends (RocksDB, SQLite, memory) | +| 8 | [**Data Distribution**](subsystem_08_data_distribution.md) | [`fdbserver/datadistributor/`](https://github.com/apple/foundationdb/tree/main/fdbserver/datadistributor) | 22K | Shard management, team building, rebalancing, MoveKeys protocol | +| 9 | [**Cluster Recovery**](subsystem_09_cluster_recovery.md) | [`fdbserver/clustercontroller/ClusterRecovery*`](https://github.com/apple/foundationdb/tree/main/fdbserver/clustercontroller), [`fdbserver/core/RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h) | (part of #4 files) | 10-state recovery state machine to reconstitute the transaction system after failures | +| 10 | [**Rate Keeping & Throttling**](subsystem_10_ratekeeper.md) | [`fdbserver/ratekeeper/`](https://github.com/apple/foundationdb/tree/main/fdbserver/ratekeeper) | 4K | Back-pressure on commits and reads, tag-based throttling, throughput tracking | +| 11 | [**Backup, Restore & DR**](subsystem_11_backup_dr.md) | [`fdbserver/backupworker/`](https://github.com/apple/foundationdb/tree/main/fdbserver/backupworker), `fdbclient/FileBackupAgent*`, `fdbclient/BackupContainer*`, [`fdbbackup/`](https://github.com/apple/foundationdb/tree/main/fdbbackup) | ~10K server, ~20K client | Continuous backup to external storage, point-in-time restore, cross-cluster DR | +| 12 | [**Simulation & Testing**](subsystem_12_simulation_testing.md) | [`fdbrpc/sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp), [`fdbserver/workloads/`](https://github.com/apple/foundationdb/tree/main/fdbserver/workloads), [`fdbserver/tester/`](https://github.com/apple/foundationdb/tree/main/fdbserver/tester), [`tests/`](https://github.com/apple/foundationdb/tree/main/tests) | 51K workloads + sim | Deterministic simulation (Sim2), fault injection (Buggify), workload-based integration tests | + +Plus supporting code: [`fdbserver/worker/`](https://github.com/apple/foundationdb/tree/main/fdbserver/worker) (process startup, ~4K), [`fdbserver/core/`](https://github.com/apple/foundationdb/tree/main/fdbserver/core) (shared interfaces/utilities, ~14K), [`fdbcli/`](https://github.com/apple/foundationdb/tree/main/fdbcli) (CLI, ~8K), [`bindings/`](https://github.com/apple/foundationdb/tree/main/bindings) (language FFI), [`fdbserver/consistencyscan/`](https://github.com/apple/foundationdb/tree/main/fdbserver/consistencyscan) (~2K). + +--- + +## Detailed Subsystem Descriptions + +### 1. [Flow Runtime](subsystem_01_flow_runtime.md) ([`flow/`](https://github.com/apple/foundationdb/tree/main/flow)) + +**What it is:** A custom async programming framework that underpins every line of FDB code. It is *not* a library you can opt out of — it *is* the execution model. + +**Key abstractions:** +- **Future / Promise** — single-assignment async values. A Promise delivers once; a Future receives. Connected via reference-counted `SingleAssignmentVar` (SAV). +- **FutureStream / PromiseStream** — multi-value async channels. The primary request/reply pattern: send a struct containing a `ReplyPromise` into a `RequestStream`, and the receiver sends the reply back through it (self-addressed envelope). +- **ACTOR functions** — compiled by a custom C# actor compiler (`actorcompiler.exe`) into state machines. `wait(expr)` suspends; `state` variables persist across suspensions. Being migrated to C++20 coroutines (`co_await`). +- **Arena / StringRef / Standalone** — zero-copy memory management. `StringRef` is a non-owning pointer+length; `Arena` owns the backing memory; `Standalone` bundles a T with its Arena. +- **Net2** ([`Net2.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Net2.cpp)) — the real event loop. Single-threaded, priority-based task scheduling over Boost.ASIO. Handles timers, yields, I/O multiplexing. +- **Deterministic random** ([`IRandom.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/IRandom.h)) — `deterministicRandom()` provides a seedable PRNG used everywhere, enabling reproducible simulation. +- **Tracing** ([`Trace.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Trace.cpp)) — structured event logging with `.detail()` chains, used for diagnostics and simulation analysis. + +**Key dynamic behavior:** Every server-side component is a collection of ACTOR coroutines scheduled on Net2's run loop. There is no threading within a process (except for disk I/O thread pools). All concurrency is cooperative, driven by Future resolution. + +**Principal files:** [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h), [`Arena.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Arena.h), [`Error.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Error.h), [`genericactors.actor.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/genericactors.actor.h), [`Net2.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Net2.cpp), [`Trace.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Trace.cpp), [`serialize.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/serialize.h), [`CoroutinesImpl.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/CoroutinesImpl.h) + +--- + +### 2. [RPC & Transport](subsystem_02_rpc_transport.md) ([`fdbrpc/`](https://github.com/apple/foundationdb/tree/main/fdbrpc)) + +**What it is:** The layer that makes Flow's Future/Promise work *across process boundaries*. It maps the actor model onto a network. + +**Key abstractions:** +- **Endpoint** = `(NetworkAddressList, Token)` — a globally-unique address for a message receiver. Tokens are 128-bit UIDs. Well-known tokens exist for system services. +- **FlowTransport** — singleton that owns all peer connections. `sendReliable()` guarantees delivery (retransmit on reconnect); `sendUnreliable()` is fire-and-forget. +- **Peer** — per-destination state: unsent queue, reliable packet list, connection future, latency histogram. Connection is lazy (established on first send). +- **EndpointMap** — maps Token → `NetworkMessageReceiver*`. When a packet arrives, its token is looked up and the receiver's `receive()` method is called to deserialize and deliver. +- **FailureMonitor** — tracks endpoint/process liveness. Components register interest via `onFailed(Endpoint)` futures. +- **Locality** ([`Locality.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/Locality.h)) — `LocalityData` (zone, machine, datacenter, data hall) and `ProcessClass` (fitness for roles). Drives replication policy enforcement. +- **Sim2** ([`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp)) — *replaces* Net2 during simulation. Runs many virtual processes in one real thread with deterministic time, simulated network (clogging, latency, partitions), and fault injection (kill process/machine/zone/datacenter). + +**Key dynamic behavior:** When Actor A on Process 1 sends a request to Actor B on Process 2, FlowTransport serializes the message, TCP delivers it, the receiver's EndpointMap dispatches it, and the reply flows back the same way. In simulation, this all happens in-process with virtual routing. The abstraction is seamless — actors don't know whether they're simulated or real. + +**Principal files:** [`fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h), [`FlowTransport.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FlowTransport.h), [`FlowTransport.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/FlowTransport.cpp), [`FailureMonitor.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FailureMonitor.h), [`Locality.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/Locality.h), [`ReplicationPolicy.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/ReplicationPolicy.h), [`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp), [`simulator.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/simulator.h) + +--- + +### 3. [Client Library](subsystem_03_client_library.md) ([`fdbclient/`](https://github.com/apple/foundationdb/tree/main/fdbclient)) + +**What it is:** Everything an application (or internal FDB component acting as a client) uses to read and write data. + +**Transaction layers (outside-in):** +1. **C API / Language Bindings** — FFI boundary. All external languages go through [`fdb_c.h`](https://github.com/apple/foundationdb/blob/main/bindings/c/foundationdb/fdb_c.h). Multi-version support loads different client `.so` files for protocol compatibility. +2. **MultiVersionTransaction** — version-negotiating wrapper. +3. **ReadYourWritesTransaction** (RYW) — the layer most internal code uses. Maintains a local write map and snapshot read cache. Reads check the write map first, then merge with storage server results. This provides read-your-own-writes semantics within a transaction. +4. **NativeAPI / Transaction** — the raw transaction. `getReadVersion()` contacts a GRV proxy; `get()`/`getRange()` contact storage servers; `commit()` sends mutations + conflict ranges to a commit proxy. + +**Key dynamic behavior:** +- **Location cache**: `DatabaseContext` caches key-range → storage-server mappings. On cache miss or stale entry, re-resolves via a commit proxy's `getKeyServerLocations`. +- **Proxy load balancing**: `commitProxyLoadBalance()` / `grvProxyLoadBalance()` — round-robin with failover and automatic retry on proxy set changes. +- **Retry loop**: `Transaction::onError()` handles `not_committed`, `transaction_too_old`, etc. by resetting and retrying with backoff. The canonical usage pattern is `loop { try { ... tr.commit(); break; } catch(Error& e) { wait(tr.onError(e)); } }`. +- **Watches**: `tr.watch(key)` registers interest; the storage server notifies when the key changes. + +**Principal files:** [`NativeAPI.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/NativeAPI.actor.cpp), [`ReadYourWrites.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/ReadYourWrites.actor.cpp), [`DatabaseContext.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/DatabaseContext.cpp), [`MultiVersionTransaction.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/MultiVersionTransaction.cpp), [`CommitTransaction.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/CommitTransaction.h), [`SystemData.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/SystemData.h), [`MonitorLeader.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/MonitorLeader.h) + +--- + +### 4. [Cluster Controller & Coordination](subsystem_04_cluster_controller.md) + +**What it is:** The "brain" that decides who does what. One process is elected Cluster Controller (CC); it recruits and monitors all server roles. + +**Leader election:** +- Coordinators (typically 3 or 5, addresses in the cluster file) run a `leaderRegister` actor. +- CC candidates send `CandidacyRequest`s. Coordinators track candidates and nominate based on process class fitness (encoded in top bits of `LeaderInfo.changeID`). +- The winner becomes CC; losers detect this via `monitorLeader()` and defer. +- Uses a generation register (`localGenerationReg`) for safe state transitions — reads and writes carry generation numbers to prevent stale updates. This protocol is isomorphic to single-decree Paxos (generation = ballot, read = prepare, write = accept), with quorum voting in `CoordinatedState`. +- 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)). +- 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. +- CC also recruits singleton roles: DataDistributor, Ratekeeper, ConsistencyScan. + +**Cluster-wide state:** +- `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.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.actor.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) + +--- + +### 5. [Transaction Commit Pipeline](subsystem_05_commit_pipeline.md) + +**What it is:** The assembly line that takes a client's commit request and makes it durable. Four server roles collaborate in a strict pipeline. + +**The pipeline (a single commit's journey):** + +``` +Client ──CommitTransactionRequest──▶ CommitProxy + │ +Phase 1: GET VERSION │ GetCommitVersionRequest + ▼ + Master/Sequencer + │ assigns monotonic version + │ (tracks prevVersion for ordering) + ▼ +Phase 2: RESOLVE CommitProxy + │ ResolveTransactionBatchRequest + ▼ + Resolver(s) [parallel, sharded by key range] + │ checks read-ranges vs committed write-ranges + │ returns conflict/no-conflict per txn + ▼ +Phase 3: LOG CommitProxy + │ pushes non-conflicting mutations + │ via LogSystem::push() + ▼ + TLog replicas (quorum write) + │ +Phase 4: REPLY CommitProxy + │ CommitTransactionReply + ▼ + Client (committed!) +``` + +**Master/Sequencer** ([`masterserver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/sequencer/masterserver.cpp)): Assigns commit versions via `getVersion()`. Ensures versions are monotonically increasing and roughly track wall-clock time. Also tracks the "live committed version" for GRV. + +**GRV Proxy** ([`GrvProxyServer.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/grvproxy/GrvProxyServer.cpp)): Handles `GetReadVersionRequest` from clients. Assigns read versions that are guaranteed to see all previously committed transactions. Enforces cluster-wide flow control: receives TPS limits from Ratekeeper and gates transaction starts using a smoothed token bucket (`GrvTransactionRateInfo`). Batch-priority transactions are throttled more aggressively and dropped first under pressure. + +**Commit Proxy** ([`CommitProxyServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/commitproxy/CommitProxyServer.actor.cpp)): The workhorse. Batches commits (`commitBatcher`), drives the 4-phase pipeline, handles metadata mutations (system key writes), and updates the key-server location map. + +**Resolver** ([`Resolver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/Resolver.cpp), [`ConflictSet.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/ConflictSet.cpp)): Maintains a sliding window of committed write ranges. For each incoming batch, checks if any transaction's read ranges overlap with committed writes at versions newer than the transaction's read version. Pure conflict detection — no side effects. + +**Principal files:** [`CommitProxyServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/commitproxy/CommitProxyServer.actor.cpp), [`GrvProxyServer.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/grvproxy/GrvProxyServer.cpp), [`masterserver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/sequencer/masterserver.cpp), [`Resolver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/Resolver.cpp), [`ConflictSet.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/ConflictSet.cpp), [`LogSystem.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h) + +--- + +### 6. [Transaction Log (TLog) & Log System](subsystem_06_tlog_logsystem.md) + +**What it is:** The durable write-ahead log. All committed mutations flow through TLogs before being applied to storage servers. This is FDB's durability guarantee. + +**TLog** ([`TLogServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/tlog/TLogServer.actor.cpp)): +- Receives mutation batches from commit proxies via `push()`. +- Writes to a `DiskQueue` (append-only on-disk log) and an in-memory index. +- Mutations are **tagged** — each mutation carries one or more `Tag`s indicating which storage server(s) it's relevant to. Tags are assigned by the commit proxy based on the key-to-shard mapping. +- Provides `peek()` interface: storage servers pull mutations by tag and version range. +- Quorum replication: a mutation is committed when `f+1` of `2f+1` TLog replicas acknowledge. + +**Log System** ([`LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp)): +- Abstraction over the set of TLog replicas. +- `LogSystem::push()` — sends mutations to all relevant TLog replicas, waits for quorum. +- `LogSystem::peek()` → `IPeekCursor` — merges results from multiple TLogs for a given tag, handling multi-generation log sets during recovery. +- Manages log epochs: each recovery creates a new epoch of TLogs. Old epochs are kept until storage servers have consumed all their data. + +**Log Router** ([`LogRouter.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logrouter/LogRouter.cpp)): +- In multi-region configurations, routes mutation data from the primary's TLogs to the remote region's TLogs. +- Bridges the tag-partitioned log system across datacenters. + +**Key dynamic behavior:** Storage servers are *consumers* of the log. They continuously peek their assigned tag, pulling committed mutations and applying them to local storage. This decoupling means commits don't wait for storage servers — only for TLog quorum. + +**Principal files:** [`TLogServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/tlog/TLogServer.actor.cpp), [`LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp), [`LogRouter.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logrouter/LogRouter.cpp), [`LogSystem.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h), [`LogSystemConfig.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/LogSystemConfig.h), [`IDiskQueue.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/IDiskQueue.h) + +--- + +### 7. [Storage Server & Engines](subsystem_07_storage_server.md) + +**What it is:** The read path and durable key-value store. Storage servers serve client reads and maintain the materialized state of their assigned key ranges (shards). + +**Storage Server** ([`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp)): +- Each SS owns a set of key ranges (shards) and a version. +- **Update loop**: continuously peeks its tag from the log system, deserializes mutations, applies them to the local KVStore, and advances its durable version. +- **Read serving**: handles `GetKeyValuesRequest`, `GetKeyRequest`, `GetValueRequest`. Checks that the requested version is available (between oldest readable version and current version), that the shard is owned, and reads from the KVStore. +- **Version management**: tracks `storageVersion` (latest durable), `durableVersion`, and `desiredOldestVersion`. Advertises its version to the cluster for GRV decisions. + +**Storage Engines** ([`fdbserver/kvstore/`](https://github.com/apple/foundationdb/tree/main/fdbserver/kvstore)): +- **IKeyValueStore** interface: `readValue()`, `readRange()`, `set()`, `clear()`, `commit()`. All operations are on a versioned key-value store. +- **RocksDB** ([`KeyValueStoreRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreRocksDB.actor.cpp)) — primary production engine. Also a sharded variant ([`KeyValueStoreShardedRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreShardedRocksDB.actor.cpp)) that uses per-shard column families. +- **SQLite** ([`KeyValueStoreSQLite.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreSQLite.cpp)) — legacy engine, still supported. +- **Memory** ([`KeyValueStoreMemory.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreMemory.cpp)) — for testing and special uses (e.g., txnStateStore during recovery). + +**Key dynamic behavior:** The storage server is *pull-based*. It pulls from the log system at its own pace. If a storage server falls behind, it catches up by reading more from TLogs. If it falls too far behind, it may be removed from its team and re-replicated. Reads at a given version are served from a snapshot — the SS maintains enough history to serve reads at any version between its oldest and current. + +**Principal files:** [`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp), [`KeyValueStoreRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreRocksDB.actor.cpp), [`KeyValueStoreShardedRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreShardedRocksDB.actor.cpp), [`KeyValueStoreSQLite.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreSQLite.cpp), [`KeyValueStoreMemory.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreMemory.cpp), [`IKeyValueStore.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/include/fdbserver/kvstore/IKeyValueStore.h), [`StorageMetrics.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/StorageMetrics.cpp) + +--- + +### 8. [Data Distribution](subsystem_08_data_distribution.md) + +**What it is:** The subsystem that decides which storage servers hold which key ranges, and moves data when the assignment needs to change. + +**Why data moves:** +- A shard grows too large → split +- A storage server fails → re-replicate its shards to healthy servers +- Load imbalance → move shards from hot to cold servers +- Configuration change (e.g., replication factor increase) +- Exclusion of a server/machine/datacenter + +**Components:** +- **DDShardTracker** ([`DDShardTracker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDShardTracker.cpp)) — monitors shard sizes and read/write rates. Triggers splits, merges, and moves. +- **DDTeamCollection** ([`DDTeamCollection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDTeamCollection.actor.cpp)) — builds "teams" of storage servers that satisfy the replication policy (e.g., 3 servers in 3 different zones). Maintains healthy/unhealthy team lists. +- **DDRelocationQueue** ([`DDRelocationQueue.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDRelocationQueue.actor.cpp)) — prioritized queue of shard movements. Executes moves concurrently up to a parallelism limit. +- **MoveKeys** ([`fdbserver/core/MoveKeys.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/MoveKeys.cpp)) — the protocol for atomically transferring shard ownership. Uses a "move keys lock" to serialize concurrent moves. Updates the `keyServers` and `serverKeys` system key spaces. +- **DataMovement** ([`fdbserver/core/DataMovement.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/DataMovement.cpp)) — data move metadata and physical shard ID tracking. + +**Key dynamic behavior:** Data distribution is a continuous background process. The DD singleton (recruited by CC) watches for changes in shard metrics, server health, and configuration. It produces `RelocateShard` requests that flow through the relocation queue, which executes the MoveKeys protocol. During a move, the destination SS fetches data from the source SS and begins consuming from the log system. When caught up, ownership transfers atomically via a system key transaction. + +**Principal files:** [`DataDistribution.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DataDistribution.cpp), [`DDTeamCollection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDTeamCollection.actor.cpp), [`DDRelocationQueue.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDRelocationQueue.actor.cpp), [`DDShardTracker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDShardTracker.cpp), [`MoveKeys.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/MoveKeys.cpp), [`DataMovement.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/DataMovement.cpp), [`ShardsAffectedByTeamFailure.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp) + +--- + +### 9. [Cluster Recovery](subsystem_09_cluster_recovery.md) + +**What it is:** The state machine that reconstitutes the entire transaction system after a failure (master crash, network partition, coordinator change, etc.). + +**The 10 recovery states** (from [`RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h)): +0. **UNINITIALIZED** — starting +1. **READING_CSTATE** — read coordinated state from coordinators (learn about previous epoch) +2. **LOCKING_CSTATE** — lock old TLogs to prevent split-brain +3. **RECRUITING** — recruit new TLogs, CommitProxies, GrvProxies, Resolvers +4. **RECOVERY_TRANSACTION** — replay the last epoch's committed-but-unapplied mutations; establish the recovery version +5. **WRITING_CSTATE** — write the new epoch's coordinated state to coordinators +6. **ACCEPTING_COMMITS** — new transaction system is live, accepting commits +7. **ALL_LOGS_RECRUITED** — all TLog sets (including remote region) are populated +8. **STORAGE_RECOVERED** — storage servers have recovered their data from the new TLogs +9. **FULLY_RECOVERED** — old TLog data fully consumed; old generations can be discarded + +**Key operations during recovery:** +- `readTransactionSystemState()` — peeks old TLogs to reconstruct the txnStateStore (key-server mappings, configuration, etc.) +- `recruitEverything()` — recruits the full transaction system in parallel +- `trackTlogRecovery()` — monitors TLog set completeness and writes coordinated state updates +- `provisionalMaster()` — handles the gap between old and new transaction systems + +**Principal files:** [`ClusterRecovery.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.cpp), [`ClusterRecovery.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.h), [`RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h), [`DBCoreState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/DBCoreState.h), [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp), [`LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp) (newEpoch) + +--- + +### 10. [Rate Keeping & Throttling](subsystem_10_ratekeeper.md) ([`fdbserver/ratekeeper/`](https://github.com/apple/foundationdb/tree/main/fdbserver/ratekeeper)) + +**What it is:** Back-pressure mechanism that prevents the cluster from being overwhelmed. + +**What it monitors:** +- Storage server queue depths (how far behind are they from the log?) +- TLog queue depths +- Storage server disk throughput and IOPS +- Per-tag transaction rates (for tenant/workload isolation) + +**What it controls:** +- Transaction rate limits (`tpsLimit`) computed separately for normal (SYSTEM+DEFAULT) and batch priorities, sent to GRV proxies via `GetRateInfoReply`. Each proxy receives `tpsLimit / numProxies`. See [Subsystem 5](subsystem_05_commit_pipeline.md) for how GRV proxies enforce these limits. +- Per-tag throttling via `TagThrottler` — can slow down or reject transactions from specific tags +- Batch priority vs. default priority differentiation (batch uses more aggressive queue depth thresholds) + +**Principal files:** [`Ratekeeper.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.cpp), [`Ratekeeper.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.h), [`TagThrottler.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/TagThrottler.cpp), [`RkTagThrottleCollection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/RkTagThrottleCollection.cpp) + +--- + +### 11. [Backup, Restore & DR](subsystem_11_backup_dr.md) + +**What it is:** Continuous backup of the mutation stream to external storage, with point-in-time restore capability. + +**Backup architecture:** +- **BackupWorker** ([`fdbserver/backupworker/`](https://github.com/apple/foundationdb/tree/main/fdbserver/backupworker)) — server-side role that reads mutations from TLogs (like a storage server, but writes to backup storage instead of a local KVStore). Tag: `tagLocalityLogRouter`. +- **FileBackupAgent** ([`FileBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/FileBackupAgent.cpp)) — orchestrates backup lifecycle. Uses a TaskBucket pattern for reliable, resumable tasks. +- **BackupContainer** (`fdbclient/BackupContainer*`) — abstraction over backup storage destinations. Implementations: local directory, S3 blob store. +- **Two file types**: Range files (snapshot of key-value pairs at a version) and Log files (mutation stream between versions). Together they enable point-in-time restore. + +**Restore:** +- Restore agent reads range files to reconstruct base state, then applies log files to reach target version. +- Supports key prefix transformation during restore (add/remove prefix). + +**DR (Disaster Recovery):** +- `DatabaseBackupAgent` — streams mutations from one cluster to another in near-real-time. +- Enables warm standby clusters. + +**Principal files:** [`BackupWorker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/backupworker/BackupWorker.cpp), [`FileBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/FileBackupAgent.cpp), [`DatabaseBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/DatabaseBackupAgent.cpp), [`BackupContainer.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupContainer.h), [`BackupContainerFileSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/BackupContainerFileSystem.cpp), [`BackupAgent.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupAgent.h), [`backup.cpp`](https://github.com/apple/foundationdb/blob/main/fdbbackup/backup.cpp) + +--- + +### 12. [Simulation & Testing](subsystem_12_simulation_testing.md) + +**What it is:** FDB's most distinctive engineering practice — deterministic simulation testing that can find bugs that would take years to manifest in production. + +**Sim2** ([`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp)): +- Replaces Net2 as the network implementation during tests. +- Runs the entire cluster (multiple "processes") in a single OS thread with virtual time. +- Deterministic: given the same random seed, produces identical execution. +- Simulates: network latency/clogging/partitions, disk I/O errors, process crashes, machine failures, datacenter failures. + +**Buggify** (throughout the codebase): +- `BUGGIFY` / `BUGGIFY_WITH_PROB(p)` — randomly enables rare code paths in simulation. +- Examples: short timeouts, extra delays, buffer size changes, early returns. +- Dramatically increases code path coverage during testing. +- Disabled in production builds. + +**Workloads** ([`fdbserver/workloads/`](https://github.com/apple/foundationdb/tree/main/fdbserver/workloads), ~51K LOC): +- `TestWorkload` base class with `setup()`, `start()`, `check()`, `getMetrics()`. +- ~60+ workload types covering: API correctness, backup/restore, bulk operations, configuration changes, failure injection, performance benchmarks. +- Configured via TOML files in [`tests/`](https://github.com/apple/foundationdb/tree/main/tests). +- Multiple workloads can run concurrently in a single simulation. + +**Test runner** ([`fdbserver/tester/`](https://github.com/apple/foundationdb/tree/main/fdbserver/tester)): +- [`SimulatedCluster.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/SimulatedCluster.cpp) — sets up simulated cluster topology (machines, processes, datacenter layout). +- `TestConfig` — reads TOML test configuration. +- Orchestrates workload execution and result checking. + +**Principal files:** [`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp), [`simulator.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/simulator.h), [`SimulatedCluster.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/SimulatedCluster.cpp), [`fdbserver/workloads/`](https://github.com/apple/foundationdb/tree/main/fdbserver/workloads), [`tests/`](https://github.com/apple/foundationdb/tree/main/tests) + +--- + +## Data Flow Summary + +### Write Path (end to end) +``` +App → C API → NativeAPI.commit() → CommitProxy → Master (version) → Resolver (conflicts) + → CommitProxy → LogSystem.push() → TLog quorum (durable) → reply to client + ~~async~~> StorageServer.peek() → apply mutations to KVStore +``` + +### Read Path (end to end) +``` +App → C API → NativeAPI.getReadVersion() → GrvProxy (rate-limited by Ratekeeper TPS budget) +App → C API → NativeAPI.get()/getRange() → [location cache lookup] → StorageServer + → KVStore.read() at requested version → reply to client +``` + +### Recovery Path +``` +CC detects master failure → elect new CC (if needed) → read coordinated state + → lock old TLogs → recruit new {Master, Proxies, Resolvers, TLogs} + → replay old epoch → write new coordinated state → accept commits +``` + +### Data Movement Path +``` +DDShardTracker detects imbalance → RelocateShard request → DDRelocationQueue + → MoveKeys protocol: dest SS fetches data + starts consuming log + → atomic ownership transfer via system key transaction +``` diff --git a/design/AI-generated/subsystem_01_flow_runtime.md b/design/AI-generated/subsystem_01_flow_runtime.md new file mode 100644 index 00000000000..60688cee2e1 --- /dev/null +++ b/design/AI-generated/subsystem_01_flow_runtime.md @@ -0,0 +1,378 @@ +# Subsystem 1: Flow Runtime + +**[Diagrams](diagram_01_flow_runtime.md)** + +**Location:** [`flow/`](https://github.com/apple/foundationdb/tree/main/flow) +**Size:** ~31K implementation + headers +**Role:** The custom async programming framework that is the execution model for all FoundationDB code. + +--- + +## Overview + +Flow is not a library you can opt out of -- it **is** the execution model. Every server component, every client operation, every test runs as a collection of cooperatively-scheduled coroutines (called "actors") on a single-threaded event loop. There are no application-level threads; all concurrency comes from Future resolution and callback dispatch. + +--- + +## Key Data Structures + +### SAV (SingleAssignmentVar) -- [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h)`:730-914` + +The core primitive underlying all async communication. A reference-counted cell that holds either a value or an error, set exactly once. + +``` +struct SAV : Callback, FastAllocated> { + int promises; // Promise reference count + int futures; // Future reference count + aligned_storage value_storage; + Error error_state; // UNSET_ERROR_CODE (-3), SET_ERROR_CODE, NEVER_ERROR_CODE, or actual error +}; +``` + +**Key methods:** +- `send(U&& value)` -- sets value, fires all callbacks in the chain +- `sendError(Error)` -- sets error, fires error callbacks +- `isSet()` / `canBeSet()` / `isError()` -- state queries +- `addCallbackAndDelFutureRef()` -- registers a continuation +- `delPromiseRef()` / `delFutureRef()` -- reference counting; destroys when both reach 0 + +**Callback chain:** SAV inherits from `Callback`, which forms a **doubly-linked circular list**. Multiple waiters can register on one SAV. When the SAV fires, it walks the chain calling `fire(value)` or `error(err)` on each callback, then unlinks them. + +### Future -- [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h)`:973-1068` + +Read-only handle to a SAV. Cannot set the value, only observe it. + +``` +Future f; // default: points to nothing +Future f(value); // immediately ready +Future f(Never()); // never completes +Future f(error); // immediately errored +``` + +**Key methods:** +- `get()` -- returns value or throws error (must be ready) +- `isReady()` -- true if set (value or error) +- `canGet()` -- ready and not error +- `cancel()` -- calls `sav->cancel()`, used by actor system +- `addCallbackAndClear(cb)` -- transfers ownership of callback to SAV + +### Promise -- [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h)`:1091-1157` + +Write-only handle to a SAV. Delivers the value exactly once. + +**Key methods:** +- `send(value)` -- delivers value +- `sendError(error)` -- delivers error +- `getFuture()` -- returns the associated Future +- `isSet()` / `canBeSet()` -- state queries + +**Lifecycle:** Creating a Promise creates a SAV with `promises=1, futures=0`. Calling `getFuture()` increments `futures`. When the Promise is destroyed without sending, it sends `broken_promise()` error. + +### FutureStream / PromiseStream -- [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h)`:1159-1456` + +Multi-value async channels for request/reply patterns. + +**NotifiedQueue** (backing structure): +- `std::queue> queue` -- buffered values +- Single callback chain (not doubly-linked) +- `send(value)` -- if callback waiting, delivers directly; else queues +- `pop()` -- dequeues or throws error + +**PromiseStream** key pattern -- self-addressed envelope: +```cpp +struct MyRequest { + int param; + ReplyPromise reply; // sender embeds a reply channel +}; +PromiseStream stream; +stream.send(MyRequest{42, ReplyPromise()}); +// receiver calls req.reply.send(result) +``` + +No explicit backpressure -- values accumulate in the queue. Implicit pressure: if the consumer doesn't drain, memory grows. + +### Error / ErrorOr -- [`Error.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Error.h)`:44-81`, [`flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h)`:124-317` + +**Error class:** +- `uint16_t error_code` + `uint16_t flags` (includes `FLAG_INJECTED_FAULT`) +- Propagates through Futures: `SAV::sendError()` stores the error; `Future::get()` throws it +- Defined via macros in `error_definitions.h`: `ERROR(name, number, description)` + +**ErrorOr:** +- `std::variant` -- holds either an error or a value +- `present()` / `get()` / `getError()` -- access methods +- Used by `tryGetReply()` to return errors without throwing + +--- + +## The Actor System + +### ACTOR Compiler + +FDB uses a custom **C# actor compiler** (`actorcompiler.exe`) that transforms `.actor.cpp` files into standard C++. The `ACTOR` keyword marks a function as a coroutine: + +```cpp +ACTOR Future myActor(Future input) { + state int x = 42; // persists across waits + int val = wait(input); // suspension point + return val + x; +} +``` + +The compiler: +1. Scans for `wait()` and `waitNext()` calls +2. Breaks the function into callback states at each suspension point +3. Generates a C++ class inheriting from `Actor` with a SAV +4. Each `wait()` becomes a callback registration + return +5. `state` variables are promoted to class members (persist across suspensions) + +**Key macros** (`actorcompiler.h`): +- `ACTOR` -- marks coroutine function +- `state` -- marks variables that survive across `wait()` +- `wait(Future)` -- suspend until Future ready, return T +- `waitNext(FutureStream)` -- suspend until next stream value +- `choose { when(wait(a)) {...} when(wait(b)) {...} }` -- race two futures +- `loop` -- `while(true)` alias + +### C++20 Coroutine Integration -- [`CoroutinesImpl.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/CoroutinesImpl.h) + +The codebase is being migrated from the custom actor compiler to C++20 native coroutines: + +**CoroPromise** ([`CoroutinesImpl.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/CoroutinesImpl.h)`:737-844`): +- The `promise_type` for `Future` coroutines +- Embeds a `CoroActor` which inherits from `Actor` (which inherits from SAV) +- `get_return_object()` returns `Future` backed by the coroutine's SAV +- `initial_suspend()` returns `suspend_never` (eager start) +- `await_transform()` overloads convert `Future` into `AwaitableFuture` + +**AwaitableFuture** ([`CoroutinesImpl.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/CoroutinesImpl.h)`:453-534`): +- The awaiter type for `co_await Future` +- `await_ready()` -- checks if future is ready or coroutine cancelled +- `await_suspend()` -- registers callback with the future's SAV +- `await_resume()` -- extracts value or throws error +- Inherits from `Callback` so it can be inserted into SAV's callback chain + +**Cancellation:** `CoroActor::cancel()` sets `ACTOR_WAIT_STATE_CANCELLED` and resumes the coroutine, which checks the flag in `await_ready()` and throws `actor_cancelled()`. + +--- + +## Net2 Event Loop -- [`Net2.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Net2.cpp) + +The real (non-simulated) event loop. Single-threaded, priority-based task scheduling over Boost.ASIO. + +### Net2 Class (line 139) + +``` +class Net2 : public INetwork, public INetworkConnections { + ASIOReactor reactor; // Boost.ASIO wrapper + TaskQueue taskQueue; // Priority queue of pending tasks + std::atomic currentTime; + std::atomic stopped; +}; +``` + +### Run Loop + +The `run()` method: +1. Polls ASIO for I/O events (socket readable/writable, timers) +2. Processes ready tasks from the priority queue (highest priority first) +3. Tracks starvation metrics per priority level +4. Yields back to ASIO periodically + +### Task Priority + +`TaskPriority` enum defines ~30 priority levels from `Min` to `Max`. Examples: +- `RunLoop` (highest) -- event loop housekeeping +- `ReadSocket` -- incoming network data +- `DefaultPromiseEndpoint` -- RPC message delivery +- `DefaultYield` -- cooperative yielding +- `DefaultDelay` -- timer-based delays +- `DiskWrite` / `DiskRead` -- I/O completions + +### Key Methods + +- `delay(seconds, taskId)` -- returns `Future` that fires after `seconds` at priority `taskId` +- `yield(taskId)` -- suspends current actor, re-enqueues at `taskId` priority +- `check_yield(taskId)` -- returns true if the current task has been running too long +- `onMainThread(promise, taskId)` -- schedules work from a thread pool thread back onto the main thread + +### Boost.ASIO Integration + +- TCP/TLS connections via `boost::asio::ip::tcp::socket` +- UDP sockets for connectionless messaging +- Timer operations for `delay()` implementation +- Thread pool integration for async file I/O + +--- + +## Arena Memory System -- [`Arena.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Arena.h) + +### Arena + +A bump-pointer allocator. Memory is allocated in blocks (`ArenaBlock`), never freed individually -- the entire Arena is freed at once. + +``` +class Arena { + Reference impl; // first block in chain + + Arena(); // empty + Arena(size_t reservedSize); // pre-allocate + void dependsOn(const Arena&); // link arenas (for reference counting) + void* operator new(size_t, Arena&); // allocate in arena +}; +``` + +**ArenaBlock** ([`Arena.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Arena.h)`:171-212`): +- Fixed block sizes: `SMALL=64`, `LARGE=8193` +- Bump pointer allocation: `bigUsed` tracks next free offset +- Blocks chain together; all freed when Arena destroyed +- `secure` flag for zero-on-dealloc (sensitive data) + +### StringRef / KeyRef / ValueRef + +Non-owning pointer+length references. Do not own memory. + +``` +struct StringRef { + const uint8_t* data; + int length; + + StringRef(Arena& a, const StringRef& toCopy); // copy into arena + StringRef substr(int start, int size); + int compare(StringRef const& other); // lexicographic +}; + +using KeyRef = StringRef; +using ValueRef = StringRef; +``` + +### Standalone + +Bundles a T with its own Arena, creating an owning wrapper: + +``` +template +class Standalone : private Arena, public T { + Arena& arena(); // access the arena + T& contents(); // access the value +}; +// Example: Standalone owns the bytes it points to +``` + +### VectorRef + +Non-owning reference to an array of T. Like StringRef but for structured data. + +--- + +## FastAlloc -- `FastAlloc.h` + +Thread-local pool allocator for common sizes (16, 32, 64, 96, 128, 256 bytes). + +- `FastAllocator::allocate()` -- O(1) from thread-local free list +- `FastAllocator::release()` -- O(1) return to free list +- Magazine system: when free list empty, swap with a full magazine from global pool +- `FastAllocated` trait: overloads `operator new/delete` to use FastAllocator +- No locks for allocation when free list has items +- 128KB magazines exchanged between thread-local and global pools + +--- + +## Serialization -- [`serialize.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/serialize.h) + +Unified framework for binary serialization: + +- `is_binary_serializable` trait for fixed-size types (int, double, UID, etc.) +- `Serializer` template: calls `t.serialize(ar)` by default +- Specializations for containers: `std::vector`, `std::map`, `std::variant`, etc. +- `BinaryWriter` / `BinaryReader` archives +- Protocol version support: `_IncludeVersion`, `_AssumeVersion`, `_Unversioned` +- Flatbuffers integration via `ObjectSerializer` for network messages + +--- + +## Tracing -- `Trace.h`, [`Trace.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Trace.cpp) + +Structured event logging used everywhere: + +```cpp +TraceEvent("MyEvent", self->dbgid) + .detail("Key", key) + .detail("Version", version) + .detail("Duration", timer() - startTime); +``` + +- Fluent `.detail()` chain builds key-value fields +- Severity levels: `SevVerbose(0)`, `SevDebug(5)`, `SevInfo(10)`, `SevWarn(20)`, `SevError(40)` +- `AuditedEvent` for security-critical events that bypass suppression +- `.suppressFor(duration)` -- rate-limit repeated events +- `.trackLatest(key)` -- keep latest value of a recurring event + +--- + +## Generic Actors -- [`genericactors.actor.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/genericactors.actor.h) + +Key utility actors: + +| Actor | Purpose | +|-------|---------| +| `timeout(Future, seconds, defaultValue)` | Race future against timer | +| `timeoutError(Future, seconds)` | Timeout throws `timed_out()` | +| `delayed(Future, seconds)` | Add delay after completion | +| `errorOr(Future)` | Catch error into `ErrorOr` | +| `store(X& out, Future)` | Store result in variable | +| `waitForAll(vector>)` | Wait for all futures | +| `waitForAllReady(vector>)` | Wait ignoring errors | +| `map(Future, func)` | Transform value | +| `mapAsync(Future, actorFunc)` | Async transform | +| `holdWhile(object, Future)` | Keep object alive until future resolves | +| `uncancellable(Future)` | Prevent cancellation | + +**AsyncVar** ([`genericactors.actor.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/genericactors.actor.h)`:753-785`): +- Reactive variable: holds a value, notifies on change +- `get()` -- current value +- `onChange()` -- returns `Future` that fires on next change +- `set(value)` -- updates value and triggers onChange if different +- Used extensively for cluster state (e.g., `AsyncVar`) + +--- + +## Data Flow Summary + +### Actor Execution Cycle + +``` +1. Actor created → SAV allocated, initial code runs until first wait() +2. wait(future) → registers callback on future's SAV, returns to event loop +3. Event fires (network data, timer, another actor sends) → SAV::send(value) +4. Callback chain walked → actor's callback called +5. Actor resumes from wait point, runs until next wait() or return +6. return value → SAV::send(value) on actor's own SAV → caller's callback fires +``` + +### Memory Flow + +``` +1. Arena allocated (bump pointer in ArenaBlock) +2. StringRef/KeyRef/ValueRef created referencing arena memory +3. Standalone bundles T + Arena for ownership transfer +4. When Standalone destroyed, Arena freed, all StringRefs invalidated +5. No per-object deallocation -- whole Arena freed at once +``` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`flow/include/flow/flow.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/flow.h) | Master include: Future, Promise, SAV, Callback, ErrorOr | +| [`flow/include/flow/Arena.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Arena.h) | Arena, StringRef, Standalone, VectorRef | +| [`flow/include/flow/Error.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/Error.h) | Error class and error code definitions | +| [`flow/include/flow/CoroutinesImpl.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/CoroutinesImpl.h) | C++20 coroutine integration (CoroPromise, AwaitableFuture) | +| [`flow/include/flow/genericactors.actor.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/genericactors.actor.h) | Utility actors (delay, timeout, waitForAll, AsyncVar) | +| [`flow/include/flow/serialize.h`](https://github.com/apple/foundationdb/blob/main/flow/include/flow/serialize.h) | Serialization framework | +| `flow/include/flow/FastAlloc.h` | Thread-local pool allocator | +| [`flow/Net2.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Net2.cpp) | Real event loop (Boost.ASIO based) | +| [`flow/Trace.cpp`](https://github.com/apple/foundationdb/blob/main/flow/Trace.cpp) | Structured event tracing | +| `flow/include/flow/actorcompiler.h` | ACTOR/wait/state macro definitions | diff --git a/design/AI-generated/subsystem_02_rpc_transport.md b/design/AI-generated/subsystem_02_rpc_transport.md new file mode 100644 index 00000000000..f00fcf71315 --- /dev/null +++ b/design/AI-generated/subsystem_02_rpc_transport.md @@ -0,0 +1,340 @@ +# Subsystem 2: RPC & Transport + +**[Diagrams](diagram_02_rpc_transport.md)** + +**Location:** [`fdbrpc/`](https://github.com/apple/foundationdb/tree/main/fdbrpc) +**Size:** ~28K implementation + headers +**Role:** Maps Flow's actor model onto a network -- makes Future/Promise work across process boundaries. + +--- + +## Overview + +FlowTransport is the layer that lets actors on different processes (or simulated processes) communicate transparently. It provides endpoint-addressed messaging with both reliable (at-least-once) and unreliable (at-most-once) delivery semantics. In simulation, the same code runs in-process with virtual routing via Sim2. + +--- + +## Key Data Structures + +### Endpoint -- [`FlowTransport.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FlowTransport.h)`:43-120` + +A globally unique address for a message receiver: + +``` +class Endpoint { + NetworkAddressList addresses; // primary + optional secondary (TLS/non-TLS) + Token token; // UID: 128-bit unique identifier +}; +``` + +- **Token** = `UID` (pair of `uint64_t`). Lower 32 bits encode an index into the EndpointMap; upper 32 bits encode task priority. +- **Well-known tokens**: `wellKnownToken(int id)` returns `UID(-1, id)`. Reserved IDs: `WLTOKEN_ENDPOINT_NOT_FOUND(0)`, `WLTOKEN_PING_PACKET`, `WLTOKEN_UNAUTHORIZED_ENDPOINT`, plus system services (leader election, config transactions, etc.) +- **Address selection**: `choosePrimaryAddress()` swaps primary/secondary based on local TLS preference. + +### Peer -- [`FlowTransport.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FlowTransport.h)`:149-193` + +Per-destination connection state: + +``` +struct Peer : ReferenceCounted { + NetworkAddress destination; + UnsentPacketQueue unsent; // buffered outgoing packets + ReliablePacketList reliable; // retransmit list (circular linked) + AsyncTrigger dataToSend; // signal: unsent queue non-empty + Future connect; // connectionKeeper actor + bool compatible, connected; + double reconnectionDelay; // exponential backoff + int peerReferences; // stream reference count (-1 = no streams) + int outstandingReplies; + DDSketch pingLatencies; // latency distribution + AsyncVar> protocolVersion; + Promise disconnect; // notified on connection loss +}; +``` + +**Connection lifecycle:** +1. First send to address creates Peer and starts `connectionKeeper()` actor +2. `connectionKeeper()` calls `INetworkConnections::connect()`, performs TLS handshake +3. Sends `ConnectPacket` (protocol version, local address, connection ID) +4. Spawns `connectionWriter()` (async write loop) and `connectionReader()` (async read loop) +5. `connectionMonitor()` sends periodic pings, detects timeouts +6. On failure: exponential backoff (INITIAL_RECONNECTION_TIME to MAX_RECONNECTION_TIME, growth factor 1.5x) +7. `discardUnreliablePackets()` on disconnect; reliable packets resent after reconnect + +### EndpointMap -- [`FlowTransport.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/FlowTransport.cpp)`:90-230` + +Maps tokens to local message receivers: + +``` +struct Entry { + union { uint64_t uid[2]; uint32_t nextFree; }; + NetworkMessageReceiver* receiver; +}; +``` + +- Pre-allocates slots for well-known endpoints (indices 0 to wellKnownEndpointCount-1) +- Dynamic endpoints allocated from free list; doubles table size when full +- `get(token)` -- O(1) lookup by token's lower 32 bits +- `insert()` -- allocates from free list, encodes priority in upper 32 bits +- `remove()` -- returns slot to free list + +### FlowTransport -- [`FlowTransport.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FlowTransport.h)`:199-315` + +Singleton managing all network communication: + +**Key methods:** +- `bind(publicAddress, listenAddress)` -- start listening +- `sendReliable(data, endpoint)` -- guaranteed delivery (retransmit on reconnect) +- `sendUnreliable(data, endpoint, openConnection)` -- fire-and-forget +- `addEndpoint(endpoint, receiver, priority)` -- register local endpoint +- `addWellKnownEndpoint(endpoint, receiver, priority)` -- register system service +- `removeEndpoint(endpoint, receiver)` -- unregister +- `resetConnection(address)` -- force reconnect +- `getDegraded()` -- `AsyncVar` for network degradation status + +--- + +## Request/Reply Pattern -- [`fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h) + +The fundamental communication pattern in FDB: + +### RequestStream ([`fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h)`:728-949`) + +A typed channel for sending requests to a service. Wraps a `NetNotifiedQueue`: + +```cpp +RequestStream getValue; // on StorageServerInterface + +// Sending a request: +GetValueRequest req; +req.key = myKey; +req.reply = ReplyPromise(); // self-addressed envelope +stream.send(req); +GetValueReply reply = wait(req.reply.getFuture()); +``` + +### ReplyPromise ([`fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h)`:131-204`) + +Single-value reply channel embedded in requests: + +- Wraps a `NetSAV` (SAV + FlowReceiver + FastAllocated) +- Serialization only stores the endpoint token (16 bytes on wire) +- On deserialize at receiver: creates local endpoint to receive reply + +### Complete Request-Reply Lifecycle + +``` +CLIENT: +1. Create request with embedded ReplyPromise +2. stream.send(request) → serialize → FlowTransport.sendReliable() +3. Transport finds/creates Peer → queues in unsent buffer +4. connectionWriter() sends packet: [length][checksum][token][serialized data] +5. Client waits on reply.getFuture() + +SERVER: +6. connectionReader() receives packet → scanPackets() +7. Extract token → EndpointMap lookup → find receiver +8. deliver() actor: yield to correct TaskPriority, call receiver.receive() +9. NetNotifiedQueue deserializes request, queues for application +10. Application calls request.reply.send(response) +11. sendUnreliable() back to client's reply endpoint + +CLIENT: +12. connectionReader() receives reply packet +13. Token lookup → NetSAV::receive() → deserialize → SAV::send(value) +14. Client's wait() resolves +``` + +### ReplyPromiseStream ([`fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h)`:460-640`) + +Stream of replies with flow control: +- Server sends multiple responses +- Client acknowledges consumed bytes +- `setByteLimit()` configures backpressure threshold +- `onReady()` blocks server when too much data in-flight + +--- + +## Wire Protocol + +### Packet Format + +``` +┌─────────────────┬────────────┬───────────────┬──────────────┐ +│ Packet Length │ Checksum │ Token (UID) │ Message Data │ +│ (4 bytes) │ (8 bytes)* │ (16 bytes) │ (variable) │ +└─────────────────┴────────────┴───────────────┴──────────────┘ +* Checksum only for non-TLS connections (XXH3_64bits) +``` + +### ConnectPacket (sent at connection start) + +```cpp +struct ConnectPacket { + uint32_t connectPacketLength; + ProtocolVersion protocolVersion; + uint16_t canonicalRemotePort; + uint64_t connectionId; // multi-version client ID + uint32_t canonicalRemoteIp4; // or 0 for IPv6 + uint16_t flags; // FLAG_IPV6 + uint8_t canonicalRemoteIp6[16]; +}; +``` + +--- + +## Failure Monitoring -- [`FailureMonitor.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FailureMonitor.h) + +Tracks endpoint/process availability: + +``` +struct FailureStatus { bool failed; }; + +class SimpleFailureMonitor : public IFailureMonitor { + std::unordered_map addressStatus; + YieldedAsyncMap endpointKnownFailed; + AsyncMap disconnectTriggers; +}; +``` + +**Key methods:** +- `getState(endpoint)` -- current failure status +- `onStateChanged(endpoint)` -- future that fires on status change +- `onDisconnectOrFailure(endpoint)` -- wait for either event +- `onlyEndpointFailed(endpoint)` -- endpoint failed but address still up +- `setStatus(address, status)` -- update status + +**Detection mechanism** (`connectionMonitor()` actor): +1. Sends periodic ping to `WLTOKEN_PING_PACKET` +2. Waits for reply with `CONNECTION_MONITOR_TIMEOUT` (1s default) +3. On timeout: increment count, eventually throw `connection_failed()` +4. Records ping latency in `DDSketch` histogram +5. After `FAILURE_DETECTION_DELAY` (5s): mark address as failed + +--- + +## Locality & Replication -- [`Locality.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/Locality.h), [`ReplicationPolicy.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/ReplicationPolicy.h) + +### LocalityData + +Key-value map of location attributes for each process: + +| Key | Example | Purpose | +|-----|---------|---------| +| `keyProcessId` | UUID | Unique process identifier | +| `keyZoneId` | "zone-a" | Failure zone | +| `keyMachineId` | "machine-1" | Physical machine | +| `keyDcId` | "dc-east" | Datacenter | +| `keyDataHallId` | "hall-1" | Data hall within DC | + +### ProcessClass ([`Locality.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/Locality.h)`:27-154`) + +Classifies process suitability for roles: +- **ClassType**: `StorageClass`, `TransactionClass`, `LogClass`, `CommitProxyClass`, `GrvProxyClass`, `MasterClass`, `ResolverClass`, etc. +- **Fitness**: `BestFit` > `GoodFit` > `UnsetFit` > `OkayFit` > `WorstFit` > `ExcludeFit` > `NeverAssign` + +### Replication Policies ([`ReplicationPolicy.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/ReplicationPolicy.h)) + +Composable constraint system: +- **PolicyOne** -- select 1 replica anywhere +- **PolicyAcross(count, attribute, subpolicy)** -- "N replicas across distinct values of attribute" + - Example: `PolicyAcross(3, "dcId", PolicyOne())` = 3 datacenters +- **PolicyAnd(policies...)** -- intersection of multiple policies + +Used by team building (Data Distribution) to ensure failure domain separation. + +### Load Balance Distance + +``` +enum LBDistance { SAME_MACHINE=0, SAME_DC=1, DISTANT=2 }; +``` + +Computed from locality attributes; used for preferring nearby replicas. + +--- + +## Sim2 -- Deterministic Simulator -- [`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp), [`simulator.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/simulator.h) + +Replaces Net2 during testing. Runs the entire cluster in a single OS thread with virtual time. + +### Key Properties + +- **Deterministic**: Given same random seed → identical execution +- **Virtual time**: No wall-clock dependency; time advances via task scheduling +- **Multi-process**: Many virtual processes in one real thread via `ProcessInfo` +- **Fault injection**: Kill processes, machines, zones, datacenters; inject network clogging, disconnection, disk errors + +### ProcessInfo (`SimulatorProcessInfo.h:42-170`) + +State per simulated process: +- `name`, `dataFolder`, `coordinationFolder` +- `machine` -- `MachineInfo*` for shared machine state +- `addresses` -- virtual network addresses +- `locality` -- zone/machine/DC placement +- `failed`, `excluded`, `rebooting`, `failedDisk` -- fault state +- `fault_injection_p1`, `fault_injection_p2` -- injection probabilities +- `globals` -- per-process global variables +- `shutdownSignal` -- Promise for clean shutdown + +### SimClogging ([`sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp)`:198-297`) + +Simulates network impairment: +- `getSendDelay()` / `getRecvDelay()` -- adds latency between addresses +- `clogPairFor(addr1, addr2, seconds)` -- delay packets between pair +- `disconnectPairFor(addr1, addr2, seconds)` -- sever connection between pair +- Latency model: 99.9% fast (`FAST_NETWORK_LATENCY`), 0.1% slow (`SLOW_NETWORK_LATENCY`) + +### Kill Operations + +| Method | Scope | Effect | +|--------|-------|--------| +| `killProcess(pid, kt)` | Single process | Fail/fault/reboot process | +| `killMachine(machineId, kt)` | All processes on machine | Checks replication safety first | +| `killZone(zoneId, kt)` | All machines in zone | | +| `killDataCenter(dcId, kt)` | All machines in DC | | + +**KillTypes**: `KillInstantly`, `InjectFaults`, `FailDisk`, `Reboot`, `RebootProcess`, `RebootAndDelete`, `RebootProcessAndDelete` + +--- + +## Load Balancing -- `LoadBalance.actor.h` + +Client-side request distribution: + +- **QueueModel**: Estimates response latency based on pending requests per server +- **Round-robin**: Distributes across replicas +- **Failure routing**: Routes around failed/degraded servers +- **Locality-aware**: Prefers geographically proximate servers (SAME_MACHINE > SAME_DC > DISTANT) +- **TSS comparison**: Can shadow requests to Testing Storage Servers for correctness validation + +--- + +## Key Constants + +| Knob | Default | Purpose | +|------|---------|---------| +| `INITIAL_RECONNECTION_TIME` | 0.2s | First reconnect delay | +| `MAX_RECONNECTION_TIME` | 30s | Max backoff | +| `RECONNECTION_TIME_GROWTH_RATE` | 1.5x | Backoff multiplier | +| `CONNECTION_MONITOR_TIMEOUT` | 1s | Ping timeout | +| `FAILURE_DETECTION_DELAY` | 5s | Before marking failed | +| `PACKET_LIMIT` | 512MB | Max packet size | +| `MIN_COALESCE_DELAY` | 0.5ms | Min buffer wait | +| `MAX_COALESCE_DELAY` | 2ms | Max buffer wait | + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbrpc/include/fdbrpc/fdbrpc.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/fdbrpc.h) | RequestStream, ReplyPromise, NetSAV, request/reply pattern | +| [`fdbrpc/include/fdbrpc/FlowTransport.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FlowTransport.h) | Endpoint, Peer, FlowTransport API | +| [`fdbrpc/FlowTransport.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/FlowTransport.cpp) | Transport implementation, EndpointMap, connection actors | +| [`fdbrpc/include/fdbrpc/FailureMonitor.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/FailureMonitor.h) | Failure detection interface and implementation | +| [`fdbrpc/include/fdbrpc/Locality.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/Locality.h) | LocalityData, ProcessClass, LBDistance | +| [`fdbrpc/include/fdbrpc/ReplicationPolicy.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/ReplicationPolicy.h) | PolicyOne, PolicyAcross, PolicyAnd | +| `fdbrpc/include/fdbrpc/LoadBalance.actor.h` | Client-side load balancing | +| [`fdbrpc/sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp) | Sim2 deterministic simulator | +| [`fdbrpc/include/fdbrpc/simulator.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/simulator.h) | ISimulator interface, ProcessInfo | +| `flow/include/flow/IConnection.h` | IConnection, IListener interfaces | diff --git a/design/AI-generated/subsystem_03_client_library.md b/design/AI-generated/subsystem_03_client_library.md new file mode 100644 index 00000000000..e3bb2071b82 --- /dev/null +++ b/design/AI-generated/subsystem_03_client_library.md @@ -0,0 +1,288 @@ +# Subsystem 3: Client Library + +**[Diagrams](diagram_03_client_library.md)** + +**Location:** [`fdbclient/`](https://github.com/apple/foundationdb/tree/main/fdbclient) +**Size:** ~75K implementation, ~34K headers +**Role:** Everything an application (or internal FDB component) uses to read and write data. + +--- + +## Overview + +The client library provides a layered transaction API. From outside in: C API / language bindings -> MultiVersionTransaction -> ReadYourWritesTransaction -> NativeAPI Transaction. Most internal FDB code uses ReadYourWritesTransaction. External applications go through the C API. + +--- + +## Transaction Layers + +### Layer 1: NativeAPI / Transaction -- `NativeAPI.actor.h:311-525` + +The raw transaction. Directly communicates with proxies and storage servers. + +**TransactionState** (`NativeAPI.actor.h:255-309`): +``` +- Database cx // DatabaseContext reference +- Future readVersionFuture +- TransactionOptions options +- int64_t totalCost // accumulated operation cost for throttling +- Version committedVersion // set after successful commit +``` + +**Key methods:** +- `getReadVersion()` -- fetches version from GRV proxy (batched via versionBatcher) +- `get(key, Snapshot)` -- point read from storage server, adds read conflict range if !snapshot +- `getRange(begin, end, limits, Snapshot, Reverse)` -- range scan from storage server(s) +- `set(key, value)` -- appends `MutationRef(SetValue, key, value)` to mutations vector +- `clear(range)` -- appends `MutationRef(ClearRange, begin, end)` +- `commit()` -- sends `CommitTransactionRequest` to commit proxy via `tryCommit()` +- `onError(error)` -- handles retryable errors, resets transaction, implements backoff + +### Layer 2: ReadYourWritesTransaction -- `ReadYourWrites.h:69-268` + +Adds read-your-own-writes semantics on top of NativeAPI: + +``` +class ReadYourWritesTransaction { + Arena arena; + Transaction tr; // underlying native transaction + SnapshotCache cache; // cached reads from storage servers + WriteMap writes; // pending local writes + CoalescedKeyRefRangeMap readConflicts; +}; +``` + +**RYW read flow:** +1. Check `writes` (local write map) for the key +2. If found: return local value (or "cleared" indicator) +3. If not found: check `cache` (snapshot cache) for previously-read value +4. If not cached: issue read to storage server, cache result +5. Merge results using `RYWIterator` which walks both maps in parallel + +**RYW write flow:** +- `set()`/`clear()` buffer in local `WriteMap` +- On `commit()`: flush all writes to underlying `Transaction`, then commit + +### Layer 3: MultiVersionTransaction -- [`MultiVersionTransaction.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/MultiVersionTransaction.cpp) + +Supports running against clusters with different protocol versions: + +- **DLTransaction**: Wraps C API function pointers from dynamically-loaded client library +- **MultiVersionApi**: Loads multiple client `.so` files for protocol compatibility +- Converts between `ThreadFuture` (cross-thread) and internal `Future` +- Enables rolling upgrades with mixed-version clusters + +### Layer 4: C API / Language Bindings -- [`fdb_c.h`](https://github.com/apple/foundationdb/blob/main/bindings/c/foundationdb/fdb_c.h) + +FFI boundary for external languages: +- `fdb_setup_network()` / `fdb_run_network()` / `fdb_stop_network()` -- lifecycle +- `fdb_database_create_transaction()` -- create transaction +- `fdb_transaction_get()` / `fdb_transaction_set()` / `fdb_transaction_commit()` -- operations +- All async operations return `FDBFuture*` handles + +--- + +## DatabaseContext -- `DatabaseContext.h`, [`DatabaseContext.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/DatabaseContext.cpp) + +Central state for a database connection. Shared by all transactions on this connection. + +### Location Cache (`DatabaseContext.h:410`) + +``` +CoalescedKeyRangeMap> locationCache; +``` + +Maps key ranges to storage server locations. Used to route reads directly to the right SS without asking a proxy every time. + +- `getCachedLocation(key)` -- O(log n) lookup in range map +- `setCachedLocation(range, servers)` -- insert/update, evicts old entries when cache full +- `invalidateCache(key)` / `invalidateCache(range)` -- clear on shard movement + +### Proxy Management + +``` +Reference commitProxies; // for commits and location queries +Reference grvProxies; // for read version assignment +AsyncTrigger onProxiesChanged; // fires when proxy set updates +``` + +Updated by `monitorProxies()` actor which watches for cluster configuration changes. + +### GRV Cache + +``` +double lastGrvTime; +Version cachedReadVersion; +``` + +Caches recent read versions to reduce GRV proxy load. Background updater via `backgroundGrvUpdater()`. + +--- + +## Commit Flow -- [`NativeAPI.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/NativeAPI.actor.cpp)`:4369+` + +### tryCommit() Actor + +1. **Estimate cost**: If tagged transaction, calculate cost for throttling +2. **Build CommitTransactionRequest**: + - `read_snapshot` = read version + - `mutations[]` = all set/clear/atomic operations + - `read_conflict_ranges[]` = keys that were read + - `write_conflict_ranges[]` = keys that were written +3. **Select proxy**: `basicLoadBalance()` across commit proxies, or first proxy if `commitOnFirstProxy` +4. **Send and wait**: Retry on proxy set change +5. **Handle reply**: + - **Success** (version != invalidVersion): update `committedVersion`, send versionstamp, update metadata cache + - **Conflict** (version == invalidVersion): extract conflicting key info, throw `not_committed()` + - **Unknown result**: use idempotency ID to determine final status + +### CommitTransactionRequest structure + +``` +struct CommitTransactionRequest { + SpanContext spanContext; + TransactionRef transaction { + VectorRef mutations; + VectorRef read_conflict_ranges; + VectorRef write_conflict_ranges; + Version read_snapshot; + }; + bool report_conflicting_keys; + // ... +}; +``` + +### MutationRef -- [`CommitTransaction.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/CommitTransaction.h)`:64-150` + +``` +struct MutationRef { + enum Type : uint8_t { + SetValue=0, ClearRange, AddValue, CompareAndClear, + SetVersionstampedKey, SetVersionstampedValue, + // atomic ops: And, Or, Xor, AppendIfFits, Max, Min, ByteMin, ByteMax, ... + }; + uint8_t type; + StringRef param1; // key (or begin key for ClearRange) + StringRef param2; // value (or end key for ClearRange) +}; +``` + +--- + +## Read Flow + +### Point Read -- `get(key)` + +``` +1. getReadVersion() → GRV proxy (batched) +2. getKeyLocation(key) → location cache or commit proxy +3. getValue(key, version) → storage server via loadBalance() +4. Return value (or Optional() if not found) +``` + +### Range Read -- `getRange(begin, end, limits)` + +``` +1. getReadVersion() +2. Resolve KeySelectors to concrete keys +3. For each shard in range: + a. getKeyLocation() → find storage server + b. Send GetKeyValuesRequest to SS + c. Accumulate results respecting row/byte limits + d. If more data and limits not reached, continue to next shard +4. Return RangeResult +``` + +### KeySelector Resolution + +``` +struct KeySelectorRef { + KeyRef key; + bool orEqual; + int offset; +}; + +firstGreaterOrEqual(k) = KeySelector(k, false, +1) +lastLessOrEqual(k) = KeySelector(k, true, 0) +lastLessThan(k) = KeySelector(k, false, 0) +firstGreaterThan(k) = KeySelector(k, true, +1) +``` + +### Proxy Load Balancing -- `ProxyLoadBalance.h` + +```cpp +// Pattern: send request, race against proxy set change +while (true) { + auto reply = basicLoadBalance(cx->getCommitProxies(), channel, req); + auto result = co_await race(reply, cx->onProxiesChanged()); + if (result.index() == 0) co_return std::get<0>(result); + // proxy set changed, retry with new proxies +} +``` + +--- + +## Cluster Discovery -- [`MonitorLeader.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/MonitorLeader.h) + +### Connection Flow + +1. Parse cluster file: `description:id@coord1,coord2,coord3` +2. `monitorLeader()` contacts coordinators, tracks current cluster controller +3. `monitorProxies()` fetches commit/GRV proxy lists from cluster controller +4. `DatabaseContext` updated with new proxy interfaces +5. Transactions use proxies for all operations + +### Watch Operations + +```cpp +Future watch = tr.watch(key); // register interest +wait(tr.commit()); // commit first +wait(watch); // then wait for change +``` + +- Watch metadata tracked in `DatabaseContext::watchMap` +- Storage server monitors key, notifies on value change +- Watch survives transaction commit + +--- + +## System Keys -- [`SystemData.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/SystemData.h) + +Special key ranges for cluster metadata (prefix `\xff`): + +| Range | Purpose | +|-------|---------| +| `keyServersKeys` (`\xff/keyServers/`) | key range → storage server mapping | +| `serverKeysRange` (`\xff/serverKeys/`) | storage server → owned key ranges (reverse map) | +| `serverListKeys` (`\xff/serverList/`) | storage server interfaces | +| `configKeys` (`\xff/conf/`) | database configuration | +| `tagLocalityListKeys` | datacenter locality assignments | +| `serverTagKeys` | storage server tag assignments | +| `moveKeysLockOwnerKey` | data distribution lock | +| `backupStartedKey` | active backup state | + +--- + +## Special Key Space (`\xff\xff` prefix) + +Management API exposed as key-value operations: + +- Read `\xff\xff/status/json` for cluster status +- Write to `\xff\xff/management/` for configuration changes +- `SpecialKeyRangeReadImpl` / `SpecialKeyRangeRWImpl` abstract handlers + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbclient/NativeAPI.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/NativeAPI.actor.cpp) | Core transaction implementation, tryCommit, getValue, getRange | +| `fdbclient/include/fdbclient/NativeAPI.actor.h` | Transaction class, TransactionState | +| [`fdbclient/ReadYourWrites.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/ReadYourWrites.actor.cpp) | RYW layer, write map, snapshot cache merging | +| `fdbclient/include/fdbclient/DatabaseContext.h` | DatabaseContext: location cache, proxy tracking, watches | +| [`fdbclient/include/fdbclient/CommitTransaction.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/CommitTransaction.h) | MutationRef, CommitTransactionRef | +| [`fdbclient/MultiVersionTransaction.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/MultiVersionTransaction.cpp) | Multi-version client, DLTransaction | +| `fdbclient/MonitorLeader.cpp` | Cluster discovery, leader tracking | +| [`fdbclient/include/fdbclient/SystemData.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/SystemData.h) | System key range definitions | +| `fdbclient/ProxyLoadBalance.h` | Proxy load balancing templates | diff --git a/design/AI-generated/subsystem_04_cluster_controller.md b/design/AI-generated/subsystem_04_cluster_controller.md new file mode 100644 index 00000000000..8d29c2e8d5b --- /dev/null +++ b/design/AI-generated/subsystem_04_cluster_controller.md @@ -0,0 +1,319 @@ +# Subsystem 4: Cluster Controller & Coordination + +**[Diagrams](diagram_04_cluster_controller.md)** + +**Location:** [`fdbserver/clustercontroller/`](https://github.com/apple/foundationdb/tree/main/fdbserver/clustercontroller), [`fdbserver/coordinator/`](https://github.com/apple/foundationdb/tree/main/fdbserver/coordinator), [`fdbserver/core/LeaderElection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.actor.cpp) +**Size:** ~13K + coordination code +**Role:** Leader election, role recruitment, process registration, cluster-wide decision making. + +--- + +## Overview + +One process is elected Cluster Controller (CC). It is the "brain" of the cluster: it recruits all server roles, monitors health, broadcasts configuration, and initiates recovery. Leadership is determined by a coordination quorum running on designated coordinator processes. + +--- + +## Leader Election + +### Coordinators + +Typically 3 or 5 processes (addresses hardcoded in cluster file) that run a `leaderRegister` actor. They don't store user data -- only coordination metadata. Coordinators are not involved in committing transactions; they hold ~1KB of state that changes only during recovery. + +### Generation Register (Paxos Acceptor) -- [`Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp)`:121-173` + +The fundamental primitive: a register with generation numbers that prevents stale writes. Each coordinator runs a `localGenerationReg()` actor that implements a single Paxos acceptor. The generation register protocol is isomorphic to single-decree Paxos: + +| Paxos concept | FDB equivalent | +|---|---| +| Ballot number | `UniqueGeneration` (uint64 generation + UID tiebreaker) | +| Prepare(n) | `GenerationRegReadRequest(key, gen)` | +| Promise(n, v) | `GenerationRegReadReply(value, writeGen, readGen)` | +| Accept(n, v) | `GenerationRegWriteRequest(kv, gen)` | +| Quorum of acceptors | Majority of coordinators (n/2 + 1) | + +``` +struct GenerationRegVal { + UniqueGeneration readGen; // highest generation promised (Paxos: highest prepare seen) + UniqueGeneration writeGen; // highest generation accepted (Paxos: highest accept seen) + Optional val; // the accepted value +}; +``` + +**Read protocol** (`GenerationRegReadRequest`) -- equivalent to Paxos **prepare**: +- If client's generation > stored readGen: update readGen (promise not to accept lower) +- Persist to disk (`store->commit()`) +- Return (value, writeGen, readGen) + +**Write protocol** (`GenerationRegWriteRequest`) -- equivalent to Paxos **accept**: +- Check: `readGen <= requestGen AND writeGen < requestGen` +- If valid: update writeGen and value, persist, return writeGen +- If invalid: return `max(readGen, writeGen)` so client can retry with a higher generation + +The core consensus logic in `localGenerationReg()` is ~50 lines of code. + +### Election Algorithm -- [`LeaderElection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.actor.cpp) + +**`tryBecomeLeaderInternal()`** (lines 107-300): + +1. **Nomination phase** (lines 136-227): + - Generate `changeID` and `LeaderInfo` (encodes process class fitness in top bits) + - Submit `CandidacyRequest` to all coordinators + - Each coordinator nominates the candidate with best fitness + - Loop: check if quorum nominates us + - If quorum: become leader. If not: retry with updated info + +2. **Heartbeat phase** (lines 233-294): + - Send `LeaderHeartbeatRequest` to all coordinators at `HEARTBEAT_FREQUENCY` + - Expect quorum to respond `true` + - If quorum responds `false`: leadership lost, restart nomination + - Update `changeID` when priority info changes + +**LeaderInfo structure:** +- `serializedInfo` -- serialized cluster interface +- `changeID` -- unique ID for this leadership claim (top 7 bits = process class fitness) +- `forward` flag -- signals connection string update + +### Coordinated State (Paxos Proposer) -- [`CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp)`:71-210` + +Higher-level abstraction that implements the Paxos proposer role over generation registers. This is where the quorum logic lives -- individual coordinators are acceptors that know nothing about quorums; `CoordinatedStateImpl` drives the two-phase protocol across the quorum. + +``` +struct CoordinatedStateImpl { + ServerCoordinators coordinators; + UniqueGeneration gen; + uint64_t conflictGen; + bool doomed; + bool initial; // true if no prior value has been written +}; +``` + +**`read()`** -- Paxos prepare phase (two rounds): +1. **Round 1**: Send `GenerationRegReadRequest(key, UniqueGeneration())` to all coordinators. Wait for majority. Learns the highest existing generation. +2. **Compute ballot**: `conflictGen = max(all seen generations) + 1`. Create `gen = UniqueGeneration(conflictGen, randomUID)`. +3. **Round 2**: Send `GenerationRegReadRequest(key, gen)` to all coordinators. Wait for majority. Each acceptor promises not to accept writes below `gen`. Returns the value with the highest `writeGen`. + +**`setExclusive()`** -- Paxos accept phase: +- Send `GenerationRegWriteRequest(key, value, gen)` to all coordinators. +- For initial state (no prior writes): wait for **all** replicas. Otherwise: wait for **majority** (n/2 + 1). +- If any coordinator returns a generation > `gen`, throw `coordinated_state_conflict` (another proposer won). + +**Quorum sizes** (from `replicatedRead` / `replicatedWrite`): +- Read quorum: `replicas.size() / 2 + 1` (majority) +- Write quorum: `replicas.size() / 2 + 1` (majority), except initial writes require all replicas + +**`replicatedRead()`** has a subtlety: it races two sets of futures -- replies with a value vs. replies without. If a majority report empty (no value), it returns the empty reply with the highest `rgen`. Otherwise it returns the non-empty reply with the highest `writeGen`. This handles the bootstrap case where some coordinators have been written and others haven't. + +Used by recovery to store/update `DBCoreState` (log system configuration). This is the only mutable state managed by coordinators. + +### Coordinator Storage -- [`OnDemandStore`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/OnDemandStore.cpp), [`DiskQueue`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/DiskQueue.cpp) + +Each coordinator's Paxos state (generation numbers and the coordinated value) is persisted via `KeyValueStoreMemory` -- an in-memory key-value store backed by a `DiskQueue` for durability. + +**On-disk format:** +- Two files per coordinator: `coordination-0.fdq` and `coordination-1.fdq` in the process's data directory +- `.fdq` = **F**oundation**D**B **Q**ueue -- an append-only circular log that alternates between the two files +- Entries are checksummed (xxhash3 in DiskQueueVersion::V2) +- `KeyValueStoreMemory` periodically snapshots the full in-memory state into the log, after which older entries are reclaimed + +**Access pattern:** +- `OnDemandStore` is a lazy wrapper: opens the `KeyValueStoreMemory` only on first access (coordinator processes that are never contacted don't create files) +- `localGenerationReg()` calls `store->readValue(key)` on each request and `store->set()` + `store->commit()` to persist generation updates +- `store->commit()` flushes to the `.fdq` log, making Paxos promises and accepts crash-safe +- The memory budget is 500MB (`keyValueStoreMemory(path, id, 500e6)`) -- vastly more than needed for the ~1KB of coordination state, but it's the standard `KeyValueStoreMemory` interface + +This is the same storage engine used by transaction logs for their mutation queues and by `txnStateStore` during recovery. + +--- + +## ClusterControllerData -- `ClusterController.h:122-3412` + +The state maintained by the elected CC. + +### DBInfo (lines 124-215) + +``` +struct DBInfo { + Reference> clientInfo; // proxy list for clients + Reference> serverInfo; // full cluster config for servers + AsyncTrigger forceMasterFailure; + DatabaseConfiguration config, fullyRecoveredConfig; + int unfinishedRecoveries; + Database db; +}; +``` + +### Worker Pool + +``` +std::map>, WorkerInfo> id_worker; // processId → WorkerInfo +std::map>, ProcessClass> id_class; // processId → configured class +``` + +### Recruitment State + +``` +std::vector> outstandingRecruitmentRequests; +std::vector> outstandingRemoteRecruitmentRequests; +std::vector> outstandingStorageRequests; + +// Singleton role tracking +AsyncVar recruitDistributor, recruitRatekeeper, recruitConsistencyScan; +Optional recruitingDistributorID, recruitingRatekeeperID, recruitingConsistencyScanID; +``` + +### Health Monitoring + +``` +std::unordered_map workerHealth; +struct WorkerHealth { + std::unordered_map degradedPeers; + std::unordered_map disconnectedPeers; +}; +``` + +--- + +## Worker Registration -- [`ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp)`:1252-1434` + +### RegisterWorkerRequest + +``` +struct RegisterWorkerRequest { + WorkerInterface wi; + ProcessClass initialClass, processClass; + ClusterControllerPriorityInfo priorityInfo; + Generation generation; + Optional distributorInterf; + Optional ratekeeperInterf; + Optional consistencyScanInterf; + bool degraded, recoveredDiskFiles; +}; +``` + +### Registration Flow + +1. **Cluster ID validation** -- reject workers from different clusters +2. **Priority calculation** -- compute CC fitness and DC fitness for this worker +3. **Process class resolution** -- use DB-configured class or initial class +4. **Worker add/update** -- create `WorkerInfo`, start watcher actor, add to `id_worker` +5. **Singleton handling** -- if worker reports DD/Ratekeeper/ConsistencyScan interface, update DBInfo +6. **Trigger recruitment check** -- call `checkOutstandingRequests()` to re-evaluate pending recruitment + +--- + +## Role Recruitment + +### Fitness Calculation -- `ClusterController.h:1443-1643` + +**RoleFitness structure:** +``` +struct RoleFitness { + ProcessClass::Fitness bestFit, worstFit; + ProcessClass::ClusterRole role; + int count, worstUsed; + bool degraded; +}; +``` + +**Comparison priority** (for `operator<`): +1. Worst fitness (lower is better) +2. Worst used count (fewer concurrent roles is better) +3. Total count (more is better) +4. Prefer non-degraded +5. For non-TLog: compare best fitness + +**Worker selection methods:** +- `getWorkerForRoleInDatacenter()` -- single best worker for role in DC +- `getWorkersForRoleInDatacenter()` -- multiple workers, prioritized by fitness +- `getWorkersForTlogsSimple()` -- TLog recruitment respecting zone distribution +- `getWorkersForTlogsComplex()` -- TLog recruitment for cross-zone policies + +### Recruitment Functions -- `ClusterRecovery.cpp` + +| Function | Role Recruited | +|----------|---------------| +| `recruitNewMaster()` | Master/Sequencer (must be same DC as CC) | +| `newCommitProxies()` | Commit proxies (via `InitializeCommitProxyRequest`) | +| `newGrvProxies()` | GRV proxies (via `InitializeGrvProxyRequest`) | +| `newResolvers()` | Resolvers (via `InitializeResolverRequest`) | +| `newTLogServers()` | TLogs, including remote region and log routers | +| `newSeedServers()` | Initial storage servers (version 0 only) | + +--- + +## ServerDBInfo Broadcasting -- `ServerDBInfo.h:34-88` + +### ServerDBInfo Contents + +``` +struct ServerDBInfo { + UID id; + ClusterControllerFullInterface clusterInterface; + ClientDBInfo client; // proxy lists + MasterInterface master; + Optional distributor; + Optional ratekeeper; + std::vector resolvers; + DBRecoveryCount recoveryCount; + RecoveryState recoveryState; + LifetimeToken masterLifetime; + LogSystemConfig logSystemConfig; + int64_t infoGeneration; // increment on each change +}; +``` + +### Broadcasting + +`dbInfoUpdater()` actor: +1. Waits for `updateDBInfo` trigger or `serverInfo` change +2. Batches changes with `DBINFO_BATCH_DELAY` +3. Serializes and sends `UpdateServerDBInfoRequest` to all workers +4. Workers store in their local `AsyncVar` and react to changes + +--- + +## ClusterController Main Loop -- `clusterControllerCore()` + +**Core actors spawned:** +- `clusterWatchDatabase()` -- monitors master, triggers recovery +- `statusServer()` -- handles status requests from fdbcli +- `timeKeeper()` -- periodic version tracking +- `monitorProcessClasses()` -- watches DB for class changes +- `monitorDataDistributor()` / `monitorRatekeeper()` / `monitorConsistencyScan()` -- singleton lifecycles +- `dbInfoUpdater()` -- broadcasts ServerDBInfo +- `workerHealthMonitor()` -- optional health tracking + +**Event loop handles:** +- `RegisterWorkerRequest` -- worker self-registration +- `RecruitStorageRequest` -- on-demand storage server recruitment +- `OpenDatabaseRequest` -- client database access +- `GetWorkersRequest` / `GetClientWorkersRequest` -- worker queries +- Coordination pings at `WORKER_COORDINATION_PING_DELAY` intervals +- Leader failure detection + +--- + +## WorkerInterface -- [`WorkerInterface.actor.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/WorkerInterface.actor.h)`:45-126` + +RPCs exposed by every worker process: + +**Initialization:** +`tLog`, `master`, `commitProxy`, `grvProxy`, `dataDistributor`, `ratekeeper`, `consistencyScan`, `resolver`, `storage`, `logRouter`, `backup` + +**Monitoring:** +`debugPing`, `coordinationPing`, `waitFailure`, `setMetricsRate`, `eventLogRequest`, `updateServerDBInfo` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/clustercontroller/ClusterController.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterController.actor.cpp) | CC main loop, worker registration, event handling | +| `fdbserver/clustercontroller/ClusterController.h` | ClusterControllerData, fitness calculation, recruitment | +| [`fdbserver/coordinator/Coordination.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/coordinator/Coordination.cpp) | leaderRegister, generation register, coordination | +| [`fdbserver/core/LeaderElection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/LeaderElection.actor.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/ServerDBInfo.h` | ServerDBInfo structure and broadcasting | diff --git a/design/AI-generated/subsystem_05_commit_pipeline.md b/design/AI-generated/subsystem_05_commit_pipeline.md new file mode 100644 index 00000000000..93d62016b7c --- /dev/null +++ b/design/AI-generated/subsystem_05_commit_pipeline.md @@ -0,0 +1,305 @@ +# Subsystem 5: Transaction Commit Pipeline + +**[Diagrams](diagram_05_commit_pipeline.md)** + +**Location:** [`fdbserver/commitproxy/`](https://github.com/apple/foundationdb/tree/main/fdbserver/commitproxy), [`fdbserver/grvproxy/`](https://github.com/apple/foundationdb/tree/main/fdbserver/grvproxy), [`fdbserver/resolver/`](https://github.com/apple/foundationdb/tree/main/fdbserver/resolver), [`fdbserver/sequencer/`](https://github.com/apple/foundationdb/tree/main/fdbserver/sequencer) +**Size:** ~12K +**Role:** Version assignment, conflict detection, commit batching -- the write-path orchestration. + +--- + +## Overview + +Four server roles collaborate in a strict pipeline to commit transactions: + +``` +Client ──CommitTransactionRequest──▶ CommitProxy + │ +Phase 1: GET VERSION ▼ + Master/Sequencer (monotonic version) +Phase 2: RESOLVE ▼ + Resolver(s) (conflict detection) +Phase 3: LOG ▼ + TLog replicas (durable write) +Phase 4: REPLY ▼ + Client (committed!) +``` + +--- + +## Commit Proxy -- [`CommitProxyServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/commitproxy/CommitProxyServer.actor.cpp) + +The workhorse of the commit pipeline. Batches client commits and drives them through all phases. + +### ProxyCommitData -- `ProxyCommitData.h:183` + +``` +struct ProxyCommitData { + MasterInterface master; // for version assignment + std::vector resolvers; // for conflict detection + Reference logSystem; // for TLog push + IKeyValueStore* txnStateStore; // persistent metadata + + NotifiedVersion committedVersion; // largest durable version + NotifiedVersion version; // current proxy version + + KeyRangeMap keyInfo; // key range → storage servers + tags + + const std::vector& tagsForKey(StringRef key); // tag lookup for mutations +}; +``` + +### commitBatch() -- 5-Phase Pipeline + +**CommitBatchContext** (`CommitProxyServer.actor.cpp:491`): +``` +struct CommitBatchContext { + std::vector trs; // batch of transactions + LogPushData toCommit; // serialized mutations for TLogs + Version commitVersion, prevVersion; + std::vector resolution; // conflict results + std::set writtenTags; +}; +``` + +**Phase 1: Pre-Resolution** (`preresolutionProcessing`): +- Validate transaction sizes +- Compute read/write conflict ranges +- Determine storage server tags via `tagsForKey()` for each mutation +- Check hot shard throttling + +**Phase 2: Resolution** (`getResolution`): +- Send `ResolveTransactionBatchRequest` to all resolvers in parallel +- Each resolver handles a partition of the key space +- Wait for all responses + +**Phase 3: Post-Resolution** (`postResolution`): +- Process conflict results: mark conflicting transactions +- Apply metadata mutations (`applyMetadataToCommittedTransactions()`) +- Update storage server mappings +- Process idempotency keys + +**Phase 4: Transaction Logging** (`transactionLogging`): +- Call `logSystem->push()` with tagged mutations +- Block until mutations are durable (TLog quorum ack) +- Update `queueCommittedVersion` + +**Phase 5: Reply** (`reply`): +- Send `CommitTransactionReply` to each client +- Conflicting transactions get `not_committed` error +- Successful transactions get their commit version + +--- + +## GRV Proxy -- [`GrvProxyServer.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/grvproxy/GrvProxyServer.cpp) + +Assigns read versions to client transactions. The GRV proxy is the primary enforcement point for cluster-wide flow control: it is where ratekeeper's back-pressure decisions become concrete delays and rejections applied to client requests. + +### GrvProxyData (line 202) + +``` +struct GrvProxyData { + MasterInterface master; // version source + VersionVector ssVersionVectorCache; // storage server version tracking + Version version; +}; +``` + +### End-to-End Flow Control: Ratekeeper → GRV Proxy → Client + +Flow control is the mechanism that prevents the cluster from being overwhelmed when storage servers or TLogs fall behind. It works as a feedback loop: + +**1. Ratekeeper computes global TPS limits** (`Ratekeeper::updateRate()`, `Ratekeeper.cpp:642`): +- Runs periodically (every `METRIC_UPDATE_RATE` seconds, default 0.1s). +- Polls every storage server and TLog for queue depths, durable bytes rates, and free disk space. +- Computes a `tpsLimit` for both `normalLimits` (DEFAULT + SYSTEM priority) and `batchLimits` (BATCH priority) independently. +- The core formula scales `actualTps` (smoothed observed transaction rate) by the ratio of durable-bytes throughput to input-bytes rate, further modulated by a `targetRateRatio` derived from how full the storage/TLog queue is relative to target and spring thresholds. The result is: if queues are within target, `tpsLimit ≈ infinity`; as queues grow past the target, the limit ramps down toward zero. +- Batch limits use more aggressive thresholds (larger `storageTargetBytes`, `logTargetBytes`) so batch transactions are throttled first. + +**2. Ratekeeper sends per-proxy rate limits** (`handleGetRateInfoReqs()`, `Ratekeeper.cpp:347`): +- Each GRV proxy periodically sends a `GetRateInfoRequest` to ratekeeper (every `leaseDuration/2` seconds, jittered). +- Ratekeeper replies with `transactionRate = normalLimits.tpsLimit / numProxies` and `batchTransactionRate = batchLimits.tpsLimit / numProxies` — the global limit divided evenly across all GRV proxies. +- The reply also includes a `leaseDuration` (default `METRIC_UPDATE_RATE` = 0.1s). If the proxy doesn't hear back within the lease, it disables rate limiting (sets rate to 0 via `GrvTransactionRateInfo::disable()`), which smoothly ramps the allowed rate to zero and stops releasing transactions. + +**3. GRV proxy enforces the rate via a token bucket** (`GrvTransactionRateInfo`, `GrvTransactionRateInfo.h`): +- Each proxy maintains two `GrvTransactionRateInfo` objects: `normalRateInfo` (for SYSTEM + DEFAULT) and `batchRateInfo` (for BATCH). +- Each object is a smoothed token bucket: `setRate()` feeds the ratekeeper-provided rate through a `Smoother`, and `startReleaseWindow()` computes a `limit` from the smoothed difference between the allowed rate and the actual release rate, times the rate window. +- `canStart(numAlreadyStarted, count)` checks if `numAlreadyStarted + count <= limit + budget`. The `budget` accumulates unused capacity across release windows, bounded by `maxEmptyQueueBudget` when queues are empty (to avoid stale budget causing bursts). +- The smoothing prevents sudden rate changes from causing oscillation. + +**4. The `transactionStarter` loop releases batches** (`transactionStarter()`, line 877): +- On each GRVTimer tick, it drains the three priority queues in strict order: SYSTEM first, then DEFAULT, then BATCH. +- SYSTEM transactions bypass rate limiting entirely (`canStart` is never checked for them). +- DEFAULT transactions are gated by `normalRateInfo.canStart()`. +- BATCH transactions are gated by `batchRateInfo.canStart()`, which uses the more aggressive batch rate limit. +- Transactions that pass rate-limiting are batched into a single `getLiveCommittedVersion()` call to the master, and replies are sent to all clients in the batch simultaneously. + +### Dynamic Batching Based on Reply Latency + +The GRV batch interval is adaptively tuned based on observed round-trip latency (`queueGetReadVersionRequests`, line 518): + +- After each batch of non-risky-read requests completes, `timeReply()` measures the round-trip time and feeds it into a `normalGRVLatency` stream. +- The `queueGetReadVersionRequests` actor receives this latency and computes: + ``` + target_latency = reply_latency * START_TRANSACTION_BATCH_INTERVAL_LATENCY_FRACTION + GRVBatchTime = α * target_latency + (1 - α) * GRVBatchTime + ``` + clamped to `[START_TRANSACTION_BATCH_INTERVAL_MIN, START_TRANSACTION_BATCH_INTERVAL_MAX]`. +- When the first request arrives into empty queues, a GRVTimer is scheduled with delay `max(0, GRVBatchTime - timeSinceLastGRV)`. This means: when the system is fast, batching intervals shrink (lower latency for clients); when the system is slow, batches grow larger (better amortization of the master round-trip). + +### Lower Priorities Dropped Under Pressure + +When the total GRV queue depth exceeds `START_TRANSACTION_MAX_QUEUE_SIZE`, the proxy drops lower-priority requests to make room (`queueGetReadVersionRequests`, line 541): + +- A new **BATCH** request is rejected immediately with `grv_proxy_memory_limit_exceeded`. +- A new **DEFAULT** request evicts one BATCH request from the front of its queue (if any); if none exist, the DEFAULT request itself is rejected. +- A new **SYSTEM** request evicts one BATCH request, or failing that one DEFAULT request; only if both queues are empty is the SYSTEM request itself rejected. +- Additionally, when the batch rate from ratekeeper drops to effectively zero (`batchRateInfo.getRate() <= 1/numProxies`), BATCH requests are immediately rejected with `batch_transaction_throttled` before even being queued (line 605). + +### GetReadVersion Flow + +1. **Queuing** (`queueGetReadVersionRequests`, line 518): + - Three priority queues: SYSTEM, DEFAULT, BATCH + - Dynamic batching as described above + +2. **Version fetch** (`getLiveCommittedVersion`, line 670): + - Request to master: `master.getLiveCommittedVersion.getReply()` + - Causal read check (unless `CAUSAL_READ_RISKY`): `updateLastCommit()` confirms epoch is still live by calling `logSystem->confirmEpochLive()`. This ensures the version returned actually represents committed state. + - Version vector integration if enabled: applies delta from master to local SS version cache + +3. **Reply** (`sendGrvReplies`, line 747): + - Sends the version to all requests in the batch + - Attaches per-tag throttle information so clients can self-throttle + - Sets `rkBatchThrottled` / `rkDefaultThrottled` flags if sustained throttling detected (sustained for `GRV_SUSTAINED_THROTTLING_THRESHOLD` seconds), signaling clients to back off + +--- + +## Master/Sequencer -- [`masterserver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/sequencer/masterserver.cpp) + +Assigns monotonically increasing commit versions. + +### MasterData -- `MasterData.h:50` + +``` +struct MasterData { + Version lastEpochEnd; // last version from prior epoch + Version recoveryTransactionVersion; // first version in this epoch + NotifiedVersionValue liveCommittedVersion; // largest live-committed version + Version version; // last assigned version + double lastVersionTime; // timestamp of last version + Optional referenceVersion; // for wall-clock alignment + + std::map lastCommitProxyVersionReplies; + VersionVector ssVersionVector; // per-SS commit versions + ResolutionBalancer resolutionBalancer; +}; +``` + +### Version Assignment -- `getVersion()` (lines 74-178) + +1. **Proxy validation**: Check proxy is known +2. **Request ordering**: Wait for prior request (`latestRequestNum.whenAtLeast(requestNum - 1)`) +3. **Deduplication**: Return cached reply if already processed +4. **Version calculation**: + ``` + toAdd = max(1, min(MAX_READ_TRANSACTION_LIFE_VERSIONS, + VERSIONS_PER_SECOND * (now - lastVersionTime))) + ``` + If `referenceVersion` set, align to wall clock via `figureVersion()` +5. **Reply**: `GetCommitVersionReply` with `prevVersion` and `version` + +### figureVersion() -- Wall-Clock Alignment (lines 43-72) + +``` +expectedVersion = now * VERSIONS_PER_SECOND - referenceVersion +version = clamp(version + toAdd, expectedVersion ± scaled_bounds) +``` + +Keeps versions roughly aligned with wall-clock microseconds while maintaining monotonicity. + +### Live Committed Version Tracking + +- `serveLiveCommittedVersion()`: Responds to GRV proxy and proxy reports +- `updateLiveCommittedVersion()`: Updates when proxy reports new committed version +- If Version Vectors enabled: tracks per-tag commit info + +--- + +## Resolver -- [`Resolver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/Resolver.cpp), [`ConflictSet.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/ConflictSet.cpp) + +Pure conflict detection. Maintains a sliding window of committed write ranges. + +### Resolver Structure (line 128) + +``` +struct Resolver { + int commitProxyCount, resolverCount; + NotifiedVersion version; + ConflictSet* conflictSet; // SkipList-based conflict tracking + IKeyValueStore* txnStateStore; // metadata KVS + std::map proxyInfoMap; + KeyRangeMap keyInfo; +}; +``` + +### ConflictSet + +``` +struct ConflictSet { + SkipList versionHistory; // version → conflict data + Version oldestVersion; +}; +``` + +### resolveBatch() (line 261) + +1. **Memory check**: Block if `totalStateBytes > RESOLVER_STATE_MEMORY_LIMIT` +2. **Version ordering** (`versionReady()`): Wait for resolver to reach `prevVersion` +3. **Conflict detection**: + ```cpp + ConflictBatch conflictBatch(self->conflictSet, &reply.conflictingKeyRangeMap); + for (auto& txn : req.transactions) + conflictBatch.addTransaction(txn, newOldestVersion); + conflictBatch.detectConflicts(req.version, newOldestVersion, commitList, &tooOldList); + ``` + - For each transaction: check if read ranges overlap with committed writes at newer versions + - Populates `reply.committed[]` with per-transaction conflict status +4. **State transaction processing**: Apply metadata mutations if enabled +5. **Version cleanup**: Erase old state transactions to bound memory + +### ConflictBatch::detectConflicts() + +``` +void detectConflicts(Version now, Version newOldestVersion, + std::vector& nonConflicting, + std::vector* tooOldTransactions) +``` + +Algorithm: For each transaction's read ranges, check overlap with committed write ranges in the SkipList at versions newer than the transaction's read version. + +--- + +## Tag Assignment + +Tags connect mutations to storage servers: + +1. **During pre-resolution**: For each mutation, `tagsForKey(mutation.key)` returns tags +2. **Tag source**: `KeyRangeMap keyInfo` maps key ranges → storage servers → tags +3. **Tags added to LogPushData**: `toCommit.addTags(tags)` for each mutation +4. **In TLog**: Messages routed to `TagData[tag.locality][tag.id]` queues +5. **Storage servers**: Peek their assigned tag to pull relevant mutations + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/commitproxy/CommitProxyServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/commitproxy/CommitProxyServer.actor.cpp) | 5-phase commit batch pipeline | +| `fdbserver/commitproxy/ProxyCommitData.h` | ProxyCommitData, tag lookup | +| [`fdbserver/grvproxy/GrvProxyServer.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/grvproxy/GrvProxyServer.cpp) | Read version assignment, rate limiting enforcement | +| `fdbserver/grvproxy/GrvTransactionRateInfo.h` | Token-bucket rate limiter driven by ratekeeper | +| [`fdbserver/sequencer/masterserver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/sequencer/masterserver.cpp) | Version assignment, wall-clock alignment | +| `fdbserver/sequencer/MasterData.h` | MasterData, version tracking | +| [`fdbserver/resolver/Resolver.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/Resolver.cpp) | Conflict detection, batch resolution | +| [`fdbserver/resolver/ConflictSet.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/resolver/ConflictSet.cpp) | SkipList-based conflict range tracking | diff --git a/design/AI-generated/subsystem_06_tlog_logsystem.md b/design/AI-generated/subsystem_06_tlog_logsystem.md new file mode 100644 index 00000000000..429c613b510 --- /dev/null +++ b/design/AI-generated/subsystem_06_tlog_logsystem.md @@ -0,0 +1,243 @@ +# Subsystem 6: Transaction Log (TLog) & Log System + +**[Diagrams](diagram_06_tlog_logsystem.md)** + +**Location:** [`fdbserver/tlog/`](https://github.com/apple/foundationdb/tree/main/fdbserver/tlog), [`fdbserver/logsystem/`](https://github.com/apple/foundationdb/tree/main/fdbserver/logsystem), [`fdbserver/logrouter/`](https://github.com/apple/foundationdb/tree/main/fdbserver/logrouter) +**Size:** ~17K +**Role:** Durable mutation logging, tag-partitioned replication, peek cursors for storage servers. + +--- + +## Overview + +The TLog subsystem is FDB's durability guarantee. All committed mutations flow through TLogs before being applied to storage servers. A mutation is committed when a quorum of TLog replicas acknowledge. Storage servers then pull (peek) mutations asynchronously. + +--- + +## TLog Server -- [`TLogServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/tlog/TLogServer.actor.cpp) + +### TLogData (lines 295-420) + +Top-level container for all TLog data in a process: + +``` +struct TLogData { + Deque popOrder; // pop ordering across generations + Deque spillOrder; // spill ordering + std::map> id_data; // all log generations by UID + + IKeyValueStore* persistentData; // spilled data on disk (KVStore) + IDiskQueue* rawPersistentQueue; // physical disk queue + TLogQueue* persistentQueue; // logical queue wrapper + + int64_t diskQueueCommitBytes; + int64_t bytesInput, bytesDurable; + int64_t targetVolatileBytes; // spill threshold + + NotifiedVersion queueCommitEnd; // durable commit location + Version queueCommitBegin; +}; +``` + +### LogData (lines 422-810) + +Per-epoch/generation log container: + +``` +struct LogData { + struct TagData : ReferenceCounted { + std::deque> versionMessages; + bool nothingPersistent; + Version popped; // consumed up to this version + Version persistentPopped; // durable pop point + Tag tag; + }; + + Map> versionLocation; + Deque>>> messageBlocks; + std::vector>> tag_data; // [locality][id] + + VersionMetricHandle persistentDataVersion; + VersionMetricHandle persistentDataDurableVersion; + NotifiedVersion version; // current log version + NotifiedVersion queueCommittedVersion; // disk queue durability point + Version knownCommittedVersion; +}; +``` + +### Push Flow (`tLogPush`) + +1. Receive mutation batch from commit proxy +2. Deserialize `TLogQueueEntry` containing tagged mutations +3. Route each message by tag to appropriate `TagData` queue +4. Append to `versionMessages` deque (in-memory) +5. Write to `persistentQueue` (DiskQueue) for durability +6. Update `versionLocation` map +7. Reply when disk write confirmed + +### Peek Flow (`tLogPeekStream`) + +1. Storage server requests mutations for tag starting at version +2. Iterate `TagData::versionMessages` from requested version +3. For versions not in memory: read from `persistentData` (KVStore) +4. Send results in `TLogPeekStreamReply` chunks +5. Respect `limitBytes` and `peekMemoryLimiter` for backpressure +6. Track popped version and cursor position + +### DiskQueue -- `TLogQueue` (lines 112-285) + +Append-only durable queue: +- `push(entry)` -- add entry with tag metadata +- `pop(location)` -- remove committed entries up to location +- Entries prefixed with length + protocol version +- Recovery-safe: CRC checks for partial writes +- Linked to version boundaries for efficient cleanup + +### Spilling + +When in-memory data exceeds `targetVolatileBytes`: +- Oldest data spilled from memory to `persistentData` (KVStore on disk) +- `spillOrder` determines which generation spills first +- Peek reads transparently merge in-memory and spilled data + +--- + +## Log System -- [`LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp) + +Abstraction over the set of TLog replicas. + +### LogSystem Interface -- [`LogSystem.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h)`:260-601` + +**Core methods:** + +| Method | Purpose | +|--------|---------| +| `push(versionSet, data, spanContext)` | Send mutations to TLog replicas, wait for quorum | +| `peek(dbgid, begin, end, tag)` | Create cursor for reading mutations by tag | +| `peekSingle(dbgid, begin, tag, history)` | Peek from preferred TLog | +| `peekLogRouter(dbgid, begin, tag)` | Peek from same-generation logs only | +| `peekTxs(dbgid, begin, tag, locality, isLong)` | Peek transaction state mutations | +| `pop(upTo, tag, knownCommittedVersion)` | Allow TLog to discard messages before version | +| `onCoreStateChanged()` | Future that fires when log set changes | +| `newEpoch(recr, fRemoteWorkers, config, ...)` | Create new log epoch during recovery | + +### IPeekCursor Interface (lines 60-141) + +Lazy iterator over log messages: + +``` +struct IPeekCursor { + bool hasMessage() const; // current message exists? + VectorRef getTags() const; // tags for current message + Arena& arena(); // arena for message data + ArenaReader* reader(); // deserialize current message + void nextMessage(); // advance to next + Future getMore(TaskPriority); // wait for more messages + LogMessageVersion version(); // current message version + Version popped(); // earliest available version + bool isActive() / isExhausted(); // cursor status +}; +``` + +### Push Implementation + +1. **Tag distribution**: Partition mutations by tag (from commit proxy's `tagsForKey()`) +2. **TLog selection**: `getPushLocations()` determines which TLogs handle each tag +3. **Message formatting**: Build `LogPushData` with serialized mutations +4. **Parallel push**: Send to all relevant TLog replicas +5. **Quorum wait**: Block until `f+1` of `2f+1` TLogs acknowledge +6. **Return**: Promise resolves to next pushable version + +### Epoch Management + +Each recovery creates a new "epoch" of TLogs: +- `newEpoch()` recruits new TLogs and creates new log generation +- Old epochs kept until all storage servers have consumed their data +- `purgeOldRecoveredGenerations()` cleans up fully-consumed epochs +- Peek cursors can span multiple epochs during recovery + +--- + +## Log Router -- [`LogRouter.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logrouter/LogRouter.cpp) + +Routes mutations across regions in multi-datacenter deployments: + +- Reads mutations from primary TLogs via peek cursor +- Forwards to remote region's TLogs +- Reduces cross-region traffic by batching +- Uses `tagLocalityLogRouter` tags +- Bridges tag-partitioned log system across datacenters + +--- + +## Tag System + +### Tag Structure + +``` +struct Tag { + int8_t locality; // identifies the shard location + uint16_t id; // identifies the specific storage server +}; +``` + +### Special Tag Localities + +| Locality | Purpose | +|----------|---------| +| `tagLocalitySpecial` | Special/system tags | +| `tagLocalityLogRouter` | Log router tags for cross-region | +| `tagLocalityRemoteLog` | Remote region log tags | +| `tagLocalityUpgraded` | Tags from older versions | +| `tagLocalitySS` | Standard storage server tags | + +### Tag Assignment Flow + +1. **Cluster controller** assigns tags to storage servers during recruitment +2. **Tags stored** in `serverTagKeys` system key range +3. **Commit proxy** maintains `keyInfo` map: key range → storage servers → tags +4. **During commit**: `tagsForKey(key)` looks up tags for each mutation's key +5. **TLog routing**: Tags attached to mutations; TLog stores per-tag queues +6. **Storage server**: Peeks its assigned tag to pull only relevant mutations + +### Version Vector + +Optional per-storage-server version tracking: +- Master maintains `VersionVector ssVersionVector` +- GRV proxy caches and sends delta in `GetReadVersionReply` +- Enables per-shard causal consistency without global ordering + +--- + +## Data Flow: Mutation Lifecycle + +``` +1. Client commits transaction with mutations +2. CommitProxy assigns tags: mutation.key → tagsForKey() → [Tag1, Tag2, ...] +3. CommitProxy calls logSystem->push(mutations_with_tags) +4. LogSystem sends to all relevant TLog replicas +5. Each TLog: + a. Routes mutation to TagData[tag.locality][tag.id].versionMessages + b. Writes to DiskQueue for durability + c. Acks to LogSystem +6. LogSystem waits for quorum → commit confirmed +7. Storage server (background): + a. logSystem->peek(myTag, myVersion) → IPeekCursor + b. cursor.getMore() → new mutations + c. Apply mutations to local KVStore + d. Advance version + e. logSystem->pop(myVersion, myTag) → allow TLog cleanup +``` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/tlog/TLogServer.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/tlog/TLogServer.actor.cpp) | TLog server: push, peek, spill, DiskQueue | +| [`fdbserver/logsystem/LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp) | LogSystem: push/peek/pop across TLog replicas | +| [`fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/include/fdbserver/logsystem/LogSystem.h) | LogSystem, IPeekCursor interfaces | +| [`fdbserver/core/include/fdbserver/core/LogSystemConfig.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/LogSystemConfig.h) | Log system configuration and epoch tracking | +| [`fdbserver/core/include/fdbserver/core/IDiskQueue.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/IDiskQueue.h) | IDiskQueue interface for TLog persistence | +| [`fdbserver/logrouter/LogRouter.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logrouter/LogRouter.cpp) | Cross-region mutation routing | diff --git a/design/AI-generated/subsystem_07_storage_server.md b/design/AI-generated/subsystem_07_storage_server.md new file mode 100644 index 00000000000..8df3bc998ff --- /dev/null +++ b/design/AI-generated/subsystem_07_storage_server.md @@ -0,0 +1,766 @@ +# Subsystem 7: Storage Server & Engines + +**[Diagrams](diagram_07_storage_server.md)** + +**Location:** [`fdbserver/storageserver/`](https://github.com/apple/foundationdb/tree/main/fdbserver/storageserver), [`fdbserver/kvstore/`](https://github.com/apple/foundationdb/tree/main/fdbserver/kvstore) +**Size:** ~63K +**Role:** Serves reads, applies mutations from log, pluggable storage backends. + +--- + +## Overview + +Storage servers are the read path and the materialized state of the database. Each SS owns a set of key ranges (shards), continuously pulls committed mutations from the TLog via tagged peek cursors, applies them to a local key-value store, and serves client reads at specific versions. + +--- + +## StorageServer Structure -- [`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp)`:850-1528` + +``` +struct StorageServer : IStorageMetricsService { + // MVCC data + VersionedData versionedData; // versioned sets/clears + std::map> mutationLog; // (durableVersion, version] + + // Shard ownership + KeyRangeMap> shards; // key range → shard state + KeyRangeMap newestAvailableVersion; // key availability per version + + // Version tracking + NotifiedVersion version; // current SS version + NotifiedVersion desiredOldestVersion; // target for advancing durable + NotifiedVersion oldestVersion; // minimum readable version + NotifiedVersion durableVersion; // committed to disk + + // Log system connection + Reference logSystem; + Reference logCursor; + + // Storage engine + StorageServerDisk storage; // wraps IKeyValueStore + + // Move-in tracking + std::unordered_map> moveInShards; +}; +``` + +--- + +## StorageServerDisk Wrapper -- [`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp)`:602-719` + +`StorageServerDisk` is the thin layer between the storage server logic and the `IKeyValueStore` interface. It delegates all I/O to the underlying engine and adds metrics counters (`kvGets`, `kvScans`, `kvCommits`, `kvCommitLogicalBytes`, `kvClearRanges`, `kvClearSingleKey`). + +Key methods: +- **`readValue(key)`** / **`readValuePrefix(key, maxLength)`** -- point reads delegated to `storage->readValue()`, incrementing `kvGets` +- **`readRange(keys, rowLimit, byteLimit)`** -- range reads delegated to `storage->readRange()`, incrementing `kvScans` +- **`readNextKeyInclusive(key)`** -- helper that does a `readRange(key, allKeys.end, rowLimit=1)` and returns the first key found (or `allKeys.end`) +- **`makeVersionMutationsDurable(prevVersion, newVersion, bytesLeft, ...)`** -- iterates the in-memory `mutationLog` and writes `set()`/`clear()` calls to the storage engine for each mutation +- **`commit()`** -- calls `storage->commit()` to make buffered writes durable +- **Checkpoint operations** -- `checkpoint()`, `restore()`, `deleteCheckpoint()` -- forwarded for physical shard movement + +--- + +## Update Loop -- [`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp)`:9562+` + +The `update()` function (a coroutine) pulls mutations from the log system and applies them locally. + +### Flow + +1. **Emergency brake** (lines 9576-9610): + - If `queueSize >= STORAGE_HARD_LIMIT_BYTES` and `durableVersion < desiredOldestVersion`: block + - Prevents OOM from unbounded mutation accumulation + - Tracks `lastBytesInputEBrake` and `lastDurableVersionEBrake` to allow progress between braking periods + +2. **Log cursor read** (lines 9636-9651): + - `cursor->getMore()` -- wait for new mutations from TLog + - Validate cursor hasn't been popped (would mean SS removed via `worker_removed()`) + +3. **Eager read collection** (lines 9674+): + - Clone cursor, extract mutations to identify keys needing eager reads + - `UpdateEagerReadInfo` collects keys that need pre-reading (for atomic ops like `AppendIfFits`, `ByteMin`/`ByteMax`, `CompareAndClear`, and `ClearRange` endpoint resolution) + - `doEagerReads()` prefetches values from the storage engine + - Handles shard changes during read, retries if needed + +4. **Mutation application** (version loop): + - Create `StorageUpdater` for version transitions + - For each mutation at each version: + - `applyMutation(data, mutation, version)` -- update MVCC data + - Advance `data->version` on new version boundary + - Yield if accumulated bytes exceed `DESIRED_UPDATE_BYTES` + +### Version Management + +| Version | Meaning | +|---------|---------| +| `version` | Latest version SS knows about (from TLog stream) | +| `durableVersion` | Latest version committed to disk | +| `oldestVersion` | Minimum version readable from storage | +| `desiredOldestVersion` | Target set by data distributor for MVCC cleanup | + +**Gap `(durableVersion, version]`**: Held in MVCC `mutationLog` (memory). +**Gap `(oldestVersion, durableVersion]`**: On disk but not yet MVCC-freeable. + +--- + +## Durability Loop -- `updateStorage()` (line 10274) + +The `updateStorage` actor runs continuously and flushes in-memory mutations to the storage engine. + +### Flow + +1. **Wait** until `desiredOldestVersion > storageVersion` (i.e., the data distributor has indicated it's safe to advance) +2. **Check pending operations** -- inspect `pendingCheckpoints` and clamp `desiredVersion` accordingly +3. **Write mutations** via `storage.makeVersionMutationsDurable()` which iterates the `mutationLog` from `storageVersion` to `desiredVersion`, calling `storage.set()` and `storage.clear()` for each mutation. This is bounded by `STORAGE_COMMIT_BYTES` (bytes limit) and for RocksDB also by `ROCKSDB_CLEARRANGES_LIMIT_PER_COMMIT` +4. **Advance `oldestVersion`** and asynchronously free MVCC data for versions before `newOldestVersion` +5. **Checkpoint handling** -- if a pending checkpoint version was reached, create the checkpoint via `storage.checkpoint()` +6. **`storage.commit()`** -- atomically make all the above mutations durable +7. **Advance `durableVersion`** and signal `durableInProgress` to unblock the update loop +8. **Pop** old TLog data via `data->logSystem->pop()` now that mutations are durable + +### Persistent Version Tracking + +Each storage engine stores a special key `\xff\xffVersion` (`persistVersion`) that records the durable version. This is written by `makeVersionDurable()` on every commit and read during `restoreDurableState()` on startup. On recovery, the storage server reads `persistVersion` from the engine to learn how far it had committed, then resumes pulling mutations from the TLog starting from that version. If `persistVersion` is absent, the database was never initialized and the storage server self-destructs. + +### RocksDB-Specific Tuning + +For RocksDB backends, `updateStorage()` has additional controls: +- **`ROCKSDB_CLEARRANGES_LIMIT_PER_COMMIT`** -- limits the number of `DeleteRange` operations per commit to avoid excessive compaction pressure +- **`ROCKSDB_ENABLE_CLEAR_RANGE_EAGER_READS`** -- enables pre-reading clear range endpoints for optimization +- **`canCommit()` backpressure** -- before committing, the RocksDB engine checks pending compaction bytes and immutable memtable count; if overloaded, it delays the commit + +--- + +## Read Serving + +### Point Read -- `getValueQ()` (line 2148) + +1. **Priority management**: Acquire `PriorityMultiLock` with request priority +2. **Version wait**: `waitForVersion(data, commitVersion, req.version)` -- block until SS version >= requested +3. **Shard check**: `data->shards[key]->isReadable()` -- throw `wrong_shard_server()` if not owned +4. **Value read**: + - Try MVCC data at version: `data->data().at(version).lastLessOrEqual(key)` + - If not in MVCC: read from storage engine: `data->storage.readValue(key)` + - Validate version hasn't become too old, shard ownership hasn't changed +5. **Response**: Return value with penalty metric + +### Range Read -- `getKeyValuesQ()` (line 3314) + +1. **Shard validation**: Ensure begin and end resolve within single shard +2. **Key selector resolution**: Resolve begin/end `KeySelector` to concrete keys via `findKey()` +3. **Range read**: `readRange(data, version, range, limit, &remainingBytes)` +4. **Shard change detection**: Double-check ownership via change counters before returning + +--- + +## Shard Management + +### ShardInfo (lines 448-600) + +Each shard is in one of four states: + +| State | Meaning | +|-------|---------| +| `readWrite` | Assigned, readable at storageVersion and later | +| `adding` | Being fetched from another SS (fetchKeys in progress) | +| `moveInShard` | Physical shard being moved via checkpoint | +| `notAssigned` | SS has no data for this range | + +### AddingShard (lines 386-446) + +Active data transfer state: +``` +struct AddingShard { + KeyRange keys; + Future fetchClient; // fetchKeys() actor + Promise fetchComplete, readWrite; + std::deque> updates; // mutations during fetch + enum Phase { WaitPrevious, Fetching, FetchingCF, Waiting }; + Version transferredVersion, fetchVersion; +}; +``` + +### fetchKeys() Actor (line 6850) + +Transfers shard data from source to destination SS: + +1. **Setup**: Wait for `coreStarted`, version advancement, overlapping ranges durable +2. **Fetch preparation**: Acquire `fetchKeysParallelismLock`, set phase to `Fetching` +3. **Data fetch loop**: + - Create transaction at `fetchVersion` + - Read key-value blocks from source SS via `tryGetRange()` + - Apply deferred mutations from `updates` deque + - Write to storage: `data->storage.set()` for each KV pair +4. **Completion**: Set phase to `Waiting`, fire `fetchComplete`, update `newestAvailableVersion` + +### changeServerKeys + +Modifies shard map to move ranges between states: +- Updates `serverKeys` system namespace reflecting ownership +- Contexts: `CSK_UPDATE` (normal), `CSK_RESTORE` (recovery), `CSK_ASSIGN_EMPTY` (new), `CSK_FALL_BACK` (fallback) + +--- + +## Storage Engines -- [`fdbserver/kvstore/`](https://github.com/apple/foundationdb/tree/main/fdbserver/kvstore) + +### IKeyValueStore Interface -- [`IKeyValueStore.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/include/fdbserver/kvstore/IKeyValueStore.h) + +All storage engines implement the `IKeyValueStore` interface (which extends `IClosable`). This is the contract between the storage server and the underlying storage mechanism. + +``` +class IKeyValueStore : public IClosable { +public: + // === Core mutation operations (buffered, not immediately durable) === + virtual void set(KeyValueRef keyValue, const Arena* arena = nullptr) = 0; + virtual void clear(KeyRangeRef range, const Arena* arena = nullptr) = 0; + + // === Durability === + virtual Future canCommit() { return Void(); } // backpressure hook + virtual Future commit(bool sequential = false) = 0; // atomically make prior set/clear durable + + // === Read operations (async) === + virtual Future> readValue(KeyRef key, Optional) = 0; + virtual Future> readValuePrefix(KeyRef key, int maxLength, Optional) = 0; + virtual Future readRange(KeyRangeRef keys, int rowLimit, int byteLimit, Optional) = 0; + + // === Metadata === + virtual KeyValueStoreType getType() const = 0; + virtual StorageBytes getStorageBytes() const = 0; + + // === Checkpoints (physical shard movement) === + virtual Future checkpoint(const CheckpointRequest& request) { throw not_implemented(); } + virtual Future restore(const std::vector& checkpoints) { throw not_implemented(); } + virtual Future deleteCheckpoint(const CheckpointMetaData& checkpoint) { throw not_implemented(); } + + // === Bulk operations === + virtual Future compactRange(KeyRangeRef range) { throw not_implemented(); } + virtual Future replaceRange(KeyRange range, Standalone> data); + virtual Future ingestSSTFiles(std::shared_ptr localFileSets) { throw not_implemented(); } + + // === Lifecycle === + virtual Future init() { return Void(); } // MUST be idempotent (may be called again on rollback) +}; +``` + +#### Concurrency Contract + +From the header comment: + +> **Causal consistency**: A read which begins after a commit ends sees the effects of the commit. A read which ends before a commit begins does not see the effects of the commit. + +In practice: reads return data as of a version established by the most recent `commit()` that completed before the read ends, such that no subsequent commit also completed before the read began. This means reads and writes can overlap, but reads always see a consistent committed snapshot. + +#### ReadOptions + +``` +struct ReadOptions { + ReadType type = ReadType::NORMAL; // NORMAL or FETCH + Optional debugID; +}; +``` + +`ReadType::FETCH` is used during shard data movement (fetchKeys). The engine may apply different throttling or priority to fetch reads vs. normal client reads. + +#### Factory -- [`IKeyValueStore.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/IKeyValueStore.cpp) + +``` +IKeyValueStore* openKVStore(KeyValueStoreType storeType, std::string filename, UID logID, ...) { + switch (storeType) { + case SSD_BTREE_V1: return keyValueStoreSQLite(...); + case SSD_BTREE_V2: return keyValueStoreSQLite(...); + case MEMORY: return keyValueStoreMemory(...); + case SSD_REDWOOD_V1: return keyValueStoreRedwoodV1(...); + case SSD_ROCKSDB_V1: return keyValueStoreRocksDB(...); + case MEMORY_RADIXTREE: return keyValueStoreMemory(..., "fdr", MEMORY_RADIXTREE); + } +} +``` + +--- + +### RocksDB Engine -- [`KeyValueStoreRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreRocksDB.actor.cpp) (~3K lines) + +The primary production storage engine. Wraps Facebook's RocksDB LSM-tree engine, adapting it to the `IKeyValueStore` interface via thread pools for non-blocking I/O. + +#### Architecture + +``` +RocksDBKeyValueStore (IKeyValueStore) +├── Writer (IThreadPoolReceiver, single thread) +│ ├── OpenAction -- opens/creates the RocksDB database +│ ├── CommitAction -- writes accumulated WriteBatch to RocksDB +│ ├── CloseAction -- closes the database +│ ├── CheckpointAction -- creates a RocksDB checkpoint +│ ├── RestoreAction -- restores from checkpoint +│ ├── CompactRangeAction +│ └── IngestSSTFilesAction +├── Reader (IThreadPoolReceiver, N parallel threads) +│ ├── ReadValueAction +│ ├── ReadValuePrefixAction +│ └── ReadRangeAction +├── SharedRocksDBState -- holds RocksDB options configured from knobs +├── ReadIteratorPool -- recycles rocksdb::Iterator objects across reads +└── FlowLock semaphores -- readSemaphore, fetchSemaphore for backpressure +``` + +In simulation, the reader/writer threads use `CoroThreadPool` (running on the network thread) to avoid time-advancement issues. In production, they use real OS threads. + +#### Write Path + +**`set(KeyValueRef kv)`** (line 2183): +1. Lazy-initializes a `rocksdb::WriteBatch` (with configurable per-key protection bytes) +2. Calls `writeBatch->Put(columnFamily, key, value)` +3. If `ROCKSDB_SINGLEKEY_DELETES_ON_CLEARRANGE` is enabled, tracks the key in `keysSet` for later delete optimization + +**`clear(KeyRangeRef range)`** (line 2202): +Three strategies depending on knobs and range shape: +1. **Single-key delete** -- if `range.singleKeyRange()` and not `ROCKSDB_FORCE_DELETERANGE_FOR_CLEARRANGE`: `writeBatch->Delete(key)`. This is preferred because RocksDB handles point tombstones more efficiently than range tombstones. +2. **Converted deletes** -- if `ROCKSDB_SINGLEKEY_DELETES_ON_CLEARRANGE` is enabled and the delete budget (`maxDeletes`) hasn't been exhausted: iterate the range using a RocksDB iterator and emit individual `Delete()` calls for each key found. Also deletes any keys in `keysSet` and `previousCommitKeysSet` that fall within the range. Falls back to `DeleteRange()` if iteration fails or budget exhausted. +3. **Native `DeleteRange()`** -- `writeBatch->DeleteRange(begin, end)`. Used as the default when conversion is disabled. + +**`commit()`** (line 2308, via `commitInRocksDB`): +1. If `writeBatch` is null (no mutations), returns immediately +2. Creates a `Writer::CommitAction` and transfers ownership of the `WriteBatch` +3. Records delete histograms, moves `keysSet` to `previousCommitKeysSet` +4. Posts the action to `writeThread` +5. The writer thread calls `rocksdb::DB::Write(writeOptions, writeBatch)` -- this atomically persists all buffered mutations + +**`canCommit()` backpressure** (line 2282): +Before each commit, the storage server calls `canCommit()` which invokes `checkRocksdbState()`. This checks: +- `EstimatePendingCompactionBytes` > `ROCKSDB_CAN_COMMIT_COMPACT_BYTES_LIMIT` +- `NumImmutableMemTable` >= `ROCKSDB_CAN_COMMIT_IMMUTABLE_MEMTABLES_LIMIT` + +If overloaded, it delays up to `ROCKSDB_CAN_COMMIT_DELAY_TIMES_ON_OVERLOAD * ROCKSDB_CAN_COMMIT_DELAY_ON_OVERLOAD` seconds, rechecking each iteration. + +#### Read Path + +**`readValue(KeyRef key, ReadOptions)`** (line 2365): +1. Extract `ReadType` (NORMAL or FETCH) from options +2. If the key is a system key or an eager read, skip throttling and post directly to `readThreads` +3. Otherwise, check `FlowLock` waiter count; throw `server_overloaded()` if too many waiters +4. Acquire `FlowLock` semaphore with timeout (`ROCKSDB_READ_QUEUE_WAIT`) +5. Post `ReadValueAction` to `readThreads`; the reader thread calls `rocksdb::DB::Get()` +6. Return the result future + +**`readRange(KeyRangeRef keys, rowLimit, byteLimit, ReadOptions)`** (line 2433): +Similar throttling path. The reader thread: +1. Gets an iterator from `ReadIteratorPool` (reuse if configured, or create new with bounded key range) +2. For forward reads: `Seek(begin)`, iterate with `Next()` until `end`/limit +3. For reverse reads: `SeekForPrev(end)`, iterate with `Prev()` until `begin`/limit +4. Enforces read timeouts (`ROCKSDB_SET_READ_TIMEOUT`) and returns `transaction_too_old` if exceeded +5. Returns the iterator to the pool for potential reuse + +#### ReadIteratorPool (line 539) + +Manages a pool of `rocksdb::Iterator` objects to avoid the cost of creating/destroying them for every read: +- **On read**: provides an unused iterator if available (matching key range for bounded iterators), or creates a new one +- **On commit**: marks all iterators as stale (increments `deletedUptoIndex`), since the underlying data has changed +- **Periodic refresh**: a background actor (`refreshReadIteratorPool`) periodically removes stale and expired iterators +- **Bounded iterators**: when `ROCKSDB_READ_RANGE_REUSE_BOUNDED_ITERATORS` is enabled, iterators are created with `iterate_lower_bound`/`iterate_upper_bound` set, and can only be reused for sub-ranges +- **Pool size limit**: `ROCKSDB_READ_RANGE_BOUNDED_ITERATORS_MAX_LIMIT` caps the number of cached iterators to avoid memory pressure + +#### Error Handling + +`RocksDBErrorListener` (an `rocksdb::EventListener`) catches background errors from compaction and flush: +- IO errors → `io_error()` +- Corruption → `file_corrupt()` +- Other → `unknown_error()` + +These errors are forwarded to the storage server via a promise, causing the SS to shut down and be re-recruited. + +#### Configuration (Knobs) + +`SharedRocksDBState` constructs RocksDB options from server knobs. Key settings: + +| Category | Knobs | +|----------|-------| +| **Write buffers** | `ROCKSDB_MEMTABLE_BYTES`, `ROCKSDB_WRITE_BUFFER_SIZE`, `ROCKSDB_MAX_WRITE_BUFFER_NUMBER`, `ROCKSDB_MIN_WRITE_BUFFER_NUMBER_TO_MERGE` | +| **Compaction** | `ROCKSDB_LEVEL0_FILENUM_COMPACTION_TRIGGER`, `ROCKSDB_LEVEL0_SLOWDOWN_WRITES_TRIGGER`, `ROCKSDB_LEVEL0_STOP_WRITES_TRIGGER`, `ROCKSDB_PERIODIC_COMPACTION_SECONDS` (with random 2-day jitter), `ROCKSDB_MAX_COMPACTION_BYTES`, `ROCKSDB_MAX_BYTES_FOR_LEVEL_MULTIPLIER`, `ROCKSDB_COMPACTION_PRI` | +| **Block cache** | `ROCKSDB_BLOCK_CACHE_SIZE` (LRU cache), `ROCKSDB_CACHE_HIGH_PRI_POOL_RATIO`, `ROCKSDB_CACHE_INDEX_AND_FILTER_BLOCKS` | +| **Bloom filter** | `ROCKSDB_BLOOM_BITS_PER_KEY` (typically 10, ~1% false positive rate), `ROCKSDB_PREFIX_LEN` (fixed prefix for prefix bloom), `ROCKSDB_BLOOM_WHOLE_KEY_FILTERING` | +| **I/O** | `ROCKSDB_USE_DIRECT_READS`, `ROCKSDB_USE_DIRECT_IO_FLUSH_COMPACTION`, `ROCKSDB_BACKGROUND_PARALLELISM`, `ROCKSDB_MAX_SUBCOMPACTIONS`, `ROCKSDB_MAX_OPEN_FILES` | +| **Protection** | `ROCKSDB_MEMTABLE_PROTECTION_BYTES_PER_KEY`, `ROCKSDB_BLOCK_PROTECTION_BYTES_PER_KEY`, `ROCKSDB_PARANOID_FILE_CHECKS`, `ROCKSDB_FULLFILE_CHECKSUM` (CRC32c) | +| **Write rate** | `ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC`, `ROCKSDB_WRITE_RATE_LIMITER_FAIRNESS`, `ROCKSDB_WRITE_RATE_LIMITER_AUTO_TUNE` | +| **Deletion optimization** | `ROCKSDB_ENABLE_COMPACT_ON_DELETION` (triggers compaction when tombstone density is high in a sliding window: `ROCKSDB_CDCF_SLIDING_WINDOW_SIZE`, `ROCKSDB_CDCF_DELETION_TRIGGER`, `ROCKSDB_CDCF_DELETION_RATIO`), `ROCKSDB_SINGLEKEY_DELETES_ON_CLEARRANGE`, `ROCKSDB_SINGLEKEY_DELETES_MAX` | +| **Read throttling** | `ROCKSDB_READ_QUEUE_SOFT_MAX`, `ROCKSDB_READ_QUEUE_HARD_MAX`, `ROCKSDB_FETCH_QUEUE_SOFT_MAX`, `ROCKSDB_FETCH_QUEUE_HARD_MAX`, `ROCKSDB_READ_QUEUE_WAIT` | +| **Read parallelism** | `ROCKSDB_READ_PARALLELISM` (number of reader threads), `ROCKSDB_WRITER_THREAD_PRIORITY`, `ROCKSDB_READER_THREAD_PRIORITY` | + +#### Observability + +Comprehensive metrics are emitted via `rocksDBMetricLogger` (line 1008): +- **RocksDB ticker stats**: block cache hits/misses, bloom filter stats, bytes read/written, compaction bytes, WAL syncs, memtable hits, iterator counts +- **RocksDB histogram stats** (when `ROCKSDB_STATS_LEVEL > kExceptHistogramOrTimers`): compaction time, compression time, write stall duration +- **RocksDB property stats**: live SST size, pending compaction bytes, memtable counts, snapshot counts, estimated key count +- **FDB-side histograms**: commit latency, read value/range latency, queue wait times, deletes per commit +- **PerfContext metrics** (when `ROCKSDB_PERFCONTEXT_ENABLE`): per-thread breakdown of block reads, seeks, memtable operations, environment operations + +--- + +### Redwood (VersionedBTree) Engine -- [`VersionedBTree.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/VersionedBTree.actor.cpp) (~11K lines) + +A custom versioned copy-on-write B-tree storage engine built from scratch for FoundationDB. Named "Redwood," it is designed for high space efficiency through delta compression and for tight integration with FDB's versioned storage model. + +#### Architecture + +``` +KeyValueStoreRedwood (IKeyValueStore) +└── VersionedBTree + ├── IPager2* m_pager -- DWALPager instance (physical page I/O) + ├── MutationBuffer* m_pBuffer -- in-memory pending mutations (ART-based) + ├── BTreeCommitHeader m_header -- persistent root pointer + metadata + ├── LazyClearQueue -- deferred page deletions (FIFO) + └── BTreeCursor -- read cursor for tree traversal + +DWALPager (IPager2) +├── PageCacheT pageCache -- LRU page cache (ObjectCache) +├── PageToVersionedMapT remappedPages -- logical→physical page remapping for COW +├── DelayedFreePageQueueT -- pages freed at a version (deferred reclamation) +├── RemapQueueT -- remap cleanup queue +├── PriorityMultiLock ioLock -- I/O prioritization +└── IAsyncFile* pageFile -- the underlying data file + +BTreePage (in-memory representation of a disk page) +├── height (1 = leaf, >1 = internal) +├── kvBytes (total uncompressed KV bytes) +└── DeltaTree2 tree -- delta-compressed sorted record storage +``` + +#### KeyValueStoreRedwood (line 7609) + +The `IKeyValueStore` adapter. Constructor creates a `DWALPager` and wraps it in a `VersionedBTree`. + +Note: Although `VersionedBTree` supports multi-version reads via the pager's snapshot mechanism, `KeyValueStoreRedwood` currently only keeps one version -- each `commit()` immediately sets `oldestReadableVersion` to the commit version, discarding all prior versions. All reads go against `getLastCommittedVersion()`. This means the storage server's MVCC layer (in-memory `versionedData`) provides version history, not the storage engine itself. + +```cpp +KeyValueStoreRedwood(filename, logID, db, encodingType, pageCacheBytes) { + IPager2* pager = new DWALPager(pageSize, extentSize, filename, pageCacheBytes, + remapCleanupWindowBytes, concurrentExtentReads, false); + m_tree = new VersionedBTree(pager, filename, logID, db); +} +``` + +Key parameters: +- **`pageSize`**: `REDWOOD_DEFAULT_PAGE_SIZE` (default ~4096), BUGGIFied in simulation to random values 1000-16384 +- **`extentSize`**: `REDWOOD_DEFAULT_EXTENT_SIZE` (multiple pages per I/O for large sequential reads) +- **`pageCacheBytes`**: configurable via `PAGE_CACHE_4K` knob (or `SIM_PAGE_CACHE_4K` in simulation) +- **`remapCleanupWindowBytes`**: `REDWOOD_REMAP_CLEANUP_WINDOW_BYTES` (100MB default), controls how much remap history to keep before cleaning up; BUGGIFied in simulation + +#### Write Path + +**`set(KeyValueRef kv)`** / **`clear(KeyRangeRef range)`** -- delegated directly to `VersionedBTree`: + +```cpp +// VersionedBTree::set() (line 4916) +void set(KeyValueRef keyValue) { + ++m_mutationCount; + m_pBuffer->insert(keyValue.key) + .mutation().setBoundaryValue(m_pBuffer->copyToArena(keyValue.value)); +} + +// VersionedBTree::clear() (line 4924) +void clear(KeyRangeRef clearedRange) { + ++m_mutationCount; + if (clearedRange.singleKeyRange()) { + m_pBuffer->insert(clearedRange.begin).mutation().clearBoundary(); + } else { + auto iBegin = m_pBuffer->insert(clearedRange.begin); + auto iEnd = m_pBuffer->insert(clearedRange.end); + iBegin.mutation().clearAll(); + ++iBegin; + m_pBuffer->erase(iBegin, iEnd); + } +} +``` + +Mutations are buffered in `MutationBuffer` (either an ART (Adaptive Radix Tree) implementation for performance, or `std::map`-based fallback). The buffer represents pending mutations as a sorted map from key boundaries to `RangeMutation` structures, which track whether the boundary is set/cleared and whether the range after it is cleared. + +**`commit(Version v)`** (line 5168): +1. Swaps the current `MutationBuffer` with a fresh one (so new mutations can arrive during commit) +2. Creates a `CommitBatch` with the mutations, read version (from pager), and write version +3. Takes a snapshot for reading the current tree state +4. Walks the B-tree, merging buffered mutations with existing pages: + - Reads existing leaf pages + - Applies mutations (sets, clears) to produce updated records + - Rebuilds modified pages using `PageToBuild` to calculate optimal page splits + - Writes modified pages via `m_pager->updatePage()` or `atomicUpdatePage()` (copy-on-write) + - Propagates changes up through internal pages as needed +5. Runs lazy clear to free pages from deleted subtrees +6. Updates `BTreeCommitHeader` with new root pointer and lazy delete queue state +7. Calls `m_pager->commit(version, commitRecord)` to make everything durable + +#### Read Path + +**`readValue(key)`** (line 7879): +1. Initialize a `BTreeCursor` at the last committed version +2. `cursor.seekGTE(key)` -- traverse the B-tree from root to leaf +3. If the cursor lands on the exact key, return the value (with arena dependency on the page buffer) +4. Otherwise return `Optional()` + +**`readRange(keys, rowLimit, byteLimit)`** (line 7738): +1. Initialize `BTreeCursor` at last committed version +2. For forward reads: `seekGTE(keys.begin)`, optionally `prefetch(keys.end, ...)` +3. Walk through leaf pages using the `BTreePage::BinaryTree::Cursor` directly (no async waits needed within a leaf page) +4. For each leaf: check whether the entire leaf is within the query range to skip per-key bounds checking +5. Accumulate results until `rowLimit` or `byteLimit` is reached +6. When a leaf is exhausted, `popPath()` and `moveNext()` to traverse to the next leaf (this may require async page reads) +7. For reverse reads: `seekLT(keys.end)`, traverse with `movePrev()` + +The `prefetch()` mechanism pre-reads pages along the expected traversal path, reducing latency for sequential scans. + +#### DWALPager (Double Write-Ahead Log Pager) (line 1934) + +The physical page storage layer for Redwood. Manages pages on disk with copy-on-write versioning. + +**Key concepts:** +- **Logical vs. Physical pages**: The B-tree operates on logical page IDs. The pager maintains a mapping (`PageToVersionedMapT`) from logical to physical page IDs, which changes when a page is updated (copy-on-write). Old physical pages are freed at the version they were superseded. +- **Page cache**: An LRU `ObjectCache` that caches pages in memory. Each entry tracks read and write futures and is evictable when both are complete. +- **Header pages**: Two header pages (primary at physical page 0, backup at page 1) store the pager's metadata. They are alternately updated for crash safety. +- **Recovery**: On startup, `recover()` reads header pages, validates checksums, and reconstructs the page mapping from the remap queue. + +**Key operations:** +- `updatePage(logicalPageIDs, data)` -- writes a page, potentially to a new physical location (copy-on-write) +- `atomicUpdatePage(logicalPageID, data, version)` -- creates a new physical page and remaps the logical ID to it at the given version +- `readPage(physicalPageID)` -- reads a page from cache or disk +- `newPageID()` -- allocates a new logical page ID from the free list +- `freePage(logicalPageID, version)` -- schedules a page for deferred deletion at the given version +- `commit(version, commitRecord)` -- flushes all pending writes, updates the header, and syncs to disk + +**Queues:** +- `DelayedFreePageQueueT` -- pages whose physical storage can be reclaimed after `oldestReadableVersion` advances past them +- `RemapQueueT` -- tracks logical→physical remappings that need cleanup; entries have types: `REMAP` (logical moved to new physical), `FREE` (logical page freed), `DETACH` (logical page detached from remap tracking) +- `LazyClearQueueT` (in VersionedBTree) -- subtrees that need their pages freed; processed incrementally between commits by `incrementalLazyClear()` + +#### BTreePage and DeltaTree Compression (line 4566) + +Each B-tree page stores its records in a `DeltaTree2` -- a sorted, delta-compressed structure optimized for space efficiency. + +**RedwoodRecordRef** (line 4106): +``` +struct RedwoodRecordRef { + KeyRef key; + Optional value; // absent = tombstone (cleared key) + // For internal pages, value = child page IDs (BTreeNodeLinkRef) +}; +``` + +**Delta encoding**: Each record is stored as a delta relative to a reference record (typically the previous sibling in the tree). The `Delta` structure uses a flags byte and variable-length fields: +- **Prefix borrowing**: Keys share a common prefix with a reference record (left or right neighbor). The delta stores only the differing suffix. +- **4 length formats** (encoded in 2 bits of the flags byte): 3, 4, 6, or 7 bytes of length fields, supporting keys and values from small (<256 byte) to large (multi-GB) +- **Flags**: `PREFIX_SOURCE_PREV` (borrow from left vs. right ancestor), `IS_DELETED` (tombstone), `HAS_VALUE` (value present) + +This typically achieves 50-70% space savings over raw key-value storage for workloads with key locality. + +**Page structure:** +- `height`: 1 = leaf (stores user key-value pairs), >1 = internal (stores separator keys → child page ID pointers) +- `kvBytes`: total uncompressed KV bytes on the page (for metrics) +- The rest of the page is the `DeltaTree2` binary data + +**Page building** (`PageToBuild`, line 5469): During commits, records are packed into pages with awareness of delta sizes, block boundaries, and page utilization. The `shiftItem()` method rebalances records between adjacent pages to improve space efficiency. + +#### Lazy Clear Queue + +When a subtree is deleted (e.g., via a range clear that removes an entire internal page's worth of data), the internal page is pushed onto the `LazyClearQueue` rather than immediately freeing all descendant pages. The `incrementalLazyClear()` actor (line 4974) processes this queue between commits: +1. Pop entries from the queue in batches of `REDWOOD_LAZY_CLEAR_BATCH_SIZE_PAGES` +2. For each entry, read the page and iterate its children: + - Height 2 pages: free leaf children directly (`freeBTreePage`) + - Height >2 pages: re-queue children for later processing +3. Free the page itself +4. Stop when the queue is exhausted or `REDWOOD_LAZY_CLEAR_MAX_PAGES` have been freed + +This amortizes the cost of large deletions across multiple commits. + +#### BTreeCommitHeader (line 4862) + +Persistent metadata stored in the pager's commit record: +``` +struct BTreeCommitHeader { + uint32_t formatVersion; // currently FORMAT_VERSION = 17 + EncodingType encodingType; // page encoding (XXHash64, etc.) + uint8_t height; // tree height + LazyClearQueueT::QueueState lazyDeleteQueue; // lazy clear queue state + BTreeNodeLink root; // root page ID(s) -- may be multi-page for oversized root +}; +``` + +--- + +### SQLite Engine -- [`KeyValueStoreSQLite.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreSQLite.cpp) (~2.4K lines) + +The original FDB storage engine, using a modified embedded SQLite B-tree. Available as `SSD_BTREE_V1` (`.fdb` files) and `SSD_BTREE_V2` (`.sqlite` files, with checksums and an improved page format). This is a legacy engine; RocksDB and Redwood are the active production engines. + +#### Architecture + +``` +KeyValueStoreSQLite (IKeyValueStore) +├── Writer (IThreadPoolReceiver, single CoroThreadPool thread) +│ ├── InitAction -- opens SQLite DB, runs checkpoint, optional integrity check +│ ├── SetAction -- cursor->set(kv) +│ ├── ClearAction -- cursor->fastClear(range) + cursor->clear(range) +│ ├── CommitAction -- cursor->commit(), fullCheckpoint() +│ └── SpringCleaningAction -- lazy page deletion + vacuuming +├── Reader (IThreadPoolReceiver, SQLITE_READER_THREADS CoroThreadPool threads) +│ ├── ReadValueAction +│ ├── ReadValuePrefixAction +│ └── ReadRangeAction +├── ReadCursor[] -- per-reader cursor, recycled up to SQLITE_CURSOR_MAX_LIFETIME_BYTES +├── PageChecksumCodec -- per-page CRC32c checksums (V2 only) +└── VFSAsync -- async VFS layer bridging SQLite to FDB's IAsyncFile +``` + +Like RocksDB, all I/O runs on `CoroThreadPool` threads (coroutine-based, running on the network thread). The writer thread handles all mutations and commits; multiple reader threads handle reads in parallel. + +#### Write Path + +**`set(KeyValueRef kv)`** (line 2231): +Posts a `Writer::SetAction` to the write thread. The writer calls `cursor->set(kv)` on the SQLite B-tree cursor. Before each set, `checkFreePages()` runs lazy deletion if the free list is below `CHECK_FREE_PAGE_AMOUNT`. + +**`clear(KeyRangeRef range)`** (line 2235): +Posts a `Writer::ClearAction`. The writer calls both: +1. `cursor->fastClear(range)` -- marks B-tree pages as pending lazy deletion (added to the "free table") without immediately reclaiming them +2. `cursor->clear(range)` -- removes the key range from the B-tree index + +**`commit()`** (line 2239): +Posts a `Writer::CommitAction`. The writer: +1. Calls `cursor->commit()` to finalize the SQLite transaction +2. Destroys the cursor (releasing the read transaction) +3. Calls `fullCheckpoint()` -- resets all reader cursors, then runs `conn.checkpoint()` twice (once to flush WAL to the main DB file, once to reset the WAL). This ensures the WAL file is truncated after every commit. +4. Creates a new write cursor for the next batch of mutations + +#### Read Path + +Reads are posted directly to the reader thread pool without throttling (unlike RocksDB): +- **`readValue(key)`** -- `getCursor()->get(key)` on the SQLite B-tree +- **`readValuePrefix(key, maxLength)`** -- `getCursor()->getPrefix(key, maxLength)` +- **`readRange(keys, rowLimit, byteLimit)`** -- `getCursor()->getRange(keys, rowLimit, byteLimit)` + +Each reader thread holds a `ReadCursor` that is recycled after `SQLITE_CURSOR_MAX_LIFETIME_BYTES` of data read. All reader cursors are invalidated on each commit via `resetReaders()` in `fullCheckpoint()`. + +#### Spring Cleaning + +A background `cleanPeriodically()` actor runs `SpringCleaningAction` on the writer thread to reclaim space: +1. **Lazy deletion** -- processes the "free table" (B-tree pages marked for deletion by `fastClear`), freeing them in batches of `SPRING_CLEANING_LAZY_DELETE_BATCH_SIZE`. Bounded by time (`SPRING_CLEANING_LAZY_DELETE_TIME_ESTIMATE`) and page count (`SPRING_CLEANING_MIN/MAX_LAZY_DELETE_PAGES`). +2. **Vacuuming** -- calls `conn.vacuum()` to compact the database file by moving pages from the end to free slots, enabling file truncation. Bounded by `SPRING_CLEANING_MIN/MAX_VACUUM_PAGES` and `SPRING_CLEANING_VACUUM_TIME_ESTIMATE`. +3. The relative frequency of lazy deletion vs. vacuuming is controlled by `SPRING_CLEANING_VACUUMS_PER_LAZY_DELETE_PAGE`. + +The cleaning interval adapts: `SPRING_CLEANING_LAZY_DELETE_INTERVAL` if there's more work to do, `SPRING_CLEANING_NO_ACTION_INTERVAL` if idle. + +#### Page Checksums (V2) + +`PageChecksumCodec` computes an 8-byte checksum (two 32-bit halves: `part1` and `part2`) stored in the reserved bytes at the end of each SQLite page. + +**Writes** always use xxHash3 (with `part1`'s top 8 bits zeroed as a format marker). + +**Reads** try three algorithms in order to handle pages written by older code: +1. **CRC32c** -- tried first if `part1 == 0` (legacy format) +2. **xxHash3** -- tried next if `part1`'s top 8 bits are zero (current format) +3. **hashlittle2** -- fallback for the oldest format (seeded with page number) + +If all three fail, the page is corrupt. The V1 format (`SSD_BTREE_V1`) does not use page checksums. + +#### Integrity Checking + +On open, optional integrity checks can be performed: +- **`checkAllChecksumsOnOpen`** -- scans every page and validates checksums; throws `file_corrupt()` on mismatch +- **`checkIntegrityOnOpen`** -- runs a full B-tree structural integrity check via `conn.check()` + +A standalone `KVFileCheck()` utility function (line 2311) can validate `.fdb`/`.sqlite` files offline, and `KVFileDump()` can dump all key-value pairs. + +--- + +### Memory Engine -- [`KeyValueStoreMemory.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreMemory.cpp) (~960 lines) + +In-memory key-value store backed by a [`DiskQueue`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/DiskQueue.cpp) (append-only circular log) for durability: +- All mutations are logged to `.fdq` files (a pair of alternating files, checksummed with xxhash3) +- On recovery, the DiskQueue log is replayed to reconstruct the in-memory state +- Periodic snapshots write the full state to the log so older entries can be reclaimed +- Two backing containers: `IKeyValueContainer` (std::map-like, default) or `radix_tree` (`MEMORY_RADIXTREE` type) + +**Users:** +- **Coordinator Paxos state** -- `OnDemandStore` creates a `KeyValueStoreMemory` per coordinator to persist generation register state (see [Cluster Controller & Coordination](subsystem_04_cluster_controller.md#coordinator-storage)) +- **Transaction log mutation queues** -- TLogs use `KeyValueStoreMemory` with a DiskQueue for their durable mutation log +- **`txnStateStore`** during recovery -- ephemeral metadata reconstructed from TLog data +- **Testing** -- in-memory storage engine for simulation tests + +--- + +## Data Flow Summary + +``` +TLog stream ──peek(myTag)──▶ StorageServer update() loop + │ + ▼ + Parse mutations from cursor + │ + ▼ + Eager reads (prefetch for atomic ops) + │ + ▼ + applyMutation() → MVCC versionedData + │ + ▼ + Advance version + │ + ▼ + updateStorage() → makeVersionMutationsDurable() + │ + ▼ + storage.set()/clear() → engine-specific write batch + │ + ▼ + canCommit() (RocksDB backpressure check) + │ + ▼ + storage.commit() → durableVersion advances + │ + ▼ + pop(durableVersion, myTag) → TLog can discard old data + + +Client ──GetValueRequest──▶ StorageServer getValueQ() + │ + ▼ + waitForVersion(requestedVersion) + │ + ▼ + Check shard ownership + │ + ▼ + Read from MVCC or KVStore at version + │ + ▼ + Return value to client +``` + +--- + +## Engine Comparison + +| Aspect | RocksDB | Redwood | SQLite | Memory | +|--------|---------|---------|--------|--------| +| **Type** | LSM-tree | Copy-on-write B-tree | Modified SQLite B-tree | In-memory + WAL | +| **Store types** | `SSD_ROCKSDB_V1` | `SSD_REDWOOD_V1` | `SSD_BTREE_V1`, `SSD_BTREE_V2` | `MEMORY`, `MEMORY_RADIXTREE` | +| **Write amplification** | Higher (compaction) | Lower (in-place COW) | Low (WAL + checkpoint) | None (append-only log) | +| **Space amplification** | Moderate (dead data in LSM levels) | Low (delta compression) | Moderate (free-list fragmentation) | N/A (RAM-bound) | +| **Read amplification** | Moderate (bloom filter helps) | Low (direct B-tree traversal) | Low (direct B-tree lookup) | None (direct map lookup) | +| **Compression** | RocksDB block compression | DeltaTree prefix/suffix sharing | None | None | +| **Checkpoints** | Native RocksDB checkpoints | Not implemented | Not implemented | Not implemented | +| **Page checksums** | Block protection bytes | XXHash64 page encoding | xxHash3 per-page (V2 only; reads also accept CRC32c and hashlittle2 legacy formats) | N/A | +| **Threading** | Dedicated read/write thread pools | Flow event loop (async page I/O) | CoroThreadPool (read + write) | Single-threaded | +| **Space reclamation** | Background compaction | Lazy clear queue + remap cleanup | Spring cleaning (lazy delete + vacuum) | DiskQueue log reclamation | +| **Primary use** | Production storage servers | Alternative storage engine | Legacy (original engine) | Coordinators, TLogs, testing | + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/storageserver/storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/storageserver/storageserver.actor.cpp) | SS main loop, update, read serving, shard management | +| [`fdbserver/kvstore/include/fdbserver/kvstore/IKeyValueStore.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/include/fdbserver/kvstore/IKeyValueStore.h) | IKeyValueStore interface definition | +| [`fdbserver/kvstore/IKeyValueStore.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/IKeyValueStore.cpp) | Factory function `openKVStore()` | +| [`fdbserver/kvstore/KeyValueStoreRocksDB.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreRocksDB.actor.cpp) | RocksDB engine implementation | +| [`fdbserver/kvstore/VersionedBTree.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/VersionedBTree.actor.cpp) | Redwood engine (VersionedBTree + DWALPager) | +| [`fdbserver/kvstore/IPager.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/IPager.h) | Pager interface (IPager2) for Redwood | +| [`fdbserver/kvstore/DeltaTree.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/DeltaTree.h) | DeltaTree2 delta-compressed sorted structure | +| [`fdbserver/kvstore/KeyValueStoreSQLite.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreSQLite.cpp) | SQLite engine (V1/V2) | +| [`fdbserver/kvstore/KeyValueStoreMemory.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/KeyValueStoreMemory.cpp) | Memory engine | +| [`fdbserver/kvstore/DiskQueue.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/kvstore/DiskQueue.cpp) | Append-only circular log (for Memory engine durability) | diff --git a/design/AI-generated/subsystem_08_data_distribution.md b/design/AI-generated/subsystem_08_data_distribution.md new file mode 100644 index 00000000000..8e29afad6bb --- /dev/null +++ b/design/AI-generated/subsystem_08_data_distribution.md @@ -0,0 +1,238 @@ +# Subsystem 8: Data Distribution + +**[Diagrams](diagram_08_data_distribution.md)** + +**Location:** [`fdbserver/datadistributor/`](https://github.com/apple/foundationdb/tree/main/fdbserver/datadistributor) +**Size:** ~22K +**Role:** Shard management, team building, rebalancing, MoveKeys protocol. + +--- + +## Overview + +Data Distribution (DD) is a continuous background process that decides which storage servers hold which key ranges, and moves data when the assignment needs to change. The DD singleton is recruited by the Cluster Controller and runs for the lifetime of the cluster (re-recruited on failure). + +--- + +## Main Actor -- [`DataDistribution.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DataDistribution.cpp)`:2602-2888` + +The `dataDistribution()` actor spawns four major components: + +1. **DataDistributionTracker** -- monitors shard metrics, triggers splits/merges/moves +2. **DDQueue** -- prioritized queue of shard relocations, executes moves +3. **DDTeamCollection (Primary)** -- builds teams, monitors health, recruits storage servers +4. **DDTeamCollection (Remote)** -- same for remote datacenter (if `usableRegions > 1`) + +Plus optional bulk load/dump cores. + +### Initialization Sequence + +1. `initDDConfigWatch()` -- watch database configuration for changes +2. `DataDistributor::init()` -- load metadata from system keys +3. Resume relocations from previous run +4. Spawn tracker, queue, and team collections +5. `waitForAll(actors)` -- run until fatal error + +--- + +## DDTeamCollection -- [`DDTeamCollection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDTeamCollection.actor.cpp) + +### What is a Team? + +A "team" is a set of storage servers that together hold replicas of a shard. For `triple` replication, a team has 3 servers in 3 different zones. + +### Key Members (`DDTeamCollection.h:209+`) + +``` +std::vector> badTeams; +std::map> server_and_tss_info; +std::map lagging_zones; +Reference storageWiggler; + +int healthyTeamCount, optimalTeamCount; +AsyncVar zeroHealthyTeams; +Future teamBuilder; +PromiseStream output; // relocation requests +``` + +### Team Building + +`buildTeams()` creates new teams from healthy servers: +- Each team satisfies the replication policy (e.g., 3 replicas across 3 zones) +- All teams have exactly `teamSize` members +- Respects excluded/failed server lists +- **Machine teams** enforce datacenter locality constraints + +### Health Monitoring + +**`teamTracker()`** -- per-team actor: +- Watches for server failures (team < replication factor) +- Watches for server exclusion +- Emits `RelocateShard` when team health degrades + +**`storageServerTracker()`** -- per-server actor: +- Monitors interface responsiveness, disk space, excluded status +- Marks failed on timeout or storage errors + +**`storageRecruiter()`** -- continuous recruitment: +- Recruits new servers when existing ones fail +- Sends `RecruitStorageRequest` to cluster controller + +--- + +## DDShardTracker -- [`DDShardTracker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDShardTracker.cpp) + +Monitors shard sizes and operation rates, triggers splits/merges. + +### Bandwidth Status (lines 42-58) + +| Status | Condition | +|--------|-----------| +| `BandwidthStatusLow` | `bytesWrittenPerKSec < SHARD_MIN_BYTES_PER_KSEC` | +| `BandwidthStatusNormal` | Between min and max | +| `BandwidthStatusHigh` | `bytesWrittenPerKSec > SHARD_MAX_BYTES_PER_KSEC` | + +### Shard Size Bounds (lines 85-151) + +`calculateShardSizeBounds()` returns bounds that trigger splits/merges: + +- **Byte bounds**: `max.bytes = max(currentBytes * 1.1, MIN_SHARD_BYTES)`, permits 10% deviation +- **Write bandwidth bounds**: Based on `SHARD_MIN/MAX_BYTES_PER_KSEC` +- **Read bandwidth bounds**: Based on `SHARD_MAX_READ_DENSITY_RATIO * bytes` +- **Read operation bounds**: Track `opsReadPerKSecond`, detect hot shards + +### Tracking Logic (`trackShardMetrics`) + +Continuously monitors each shard. When metrics exceed bounds: +- **Split** (high bytes/ops): Create two smaller shards, emit `RelocateShard` with `SPLIT_SHARD` +- **Merge** (low bytes/ops): Merge with adjacent shard, emit `RelocateShard` with `MERGE_SHARD` + +--- + +## DDQueue / DDRelocationQueue -- [`DDRelocationQueue.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDRelocationQueue.actor.cpp) + +### Key Members (`DDRelocationQueue.h:125+`) + +``` +std::set> fetchingSourcesQueue; +std::map> queue; // per-server queue +KeyRangeMap inFlight; // currently executing +KeyRangeActorMap inFlightActors; // actor per in-flight move +KeyRangeMap dataMoves; // move metadata +std::map busymap, destBusymap; // server utilization +``` + +### RelocateData (lines 74-122) + +``` +struct RelocateData { + int priority; // urgency level + RelocateReason reason; + DataMovementReason dmReason; + KeyRange keys; // range to move + std::vector src; // source server IDs + int workFactor; // estimated cost + UID dataMoveId; + double startTime; +}; +``` + +### Relocation Priorities + +| Priority | Trigger | +|----------|---------| +| `PRIORITY_TEAM_REDUNDANT` | Team replicated on same machine | +| `PRIORITY_POPULATE_REGION` | Populate new region | +| `PRIORITY_TEAM_UNHEALTHY` | Unhealthy team | +| `PRIORITY_REBALANCE_*` | Disk/read balancing | +| `PRIORITY_SPLIT_SHARD` | Shard too large/hot | +| `PRIORITY_MERGE_SHARD` | Shard too small/cold | + +Ordering: `(priority desc) → (startTime asc) → (randomId desc)` + +### Core Operations + +- **`queueRelocation()`**: Enqueue shard relocation, search for source team +- **`launchQueuedWork()`**: Check for overlapping in-flight moves, cancel lower priority, start MoveKeys +- **`completeSourceFetch()`**: Source servers identified, move to ready-to-launch + +--- + +## MoveKeys Protocol -- [`fdbserver/core/MoveKeys.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/MoveKeys.cpp) + +Atomic shard ownership transfer using system key transactions. + +### Overall Flow + +1. Destination SS starts `fetchKeys()` -- pulls data from source SS +2. During fetch, mutations forwarded to dest via TLog stream (same tag assignment) +3. Once dest reaches `fetchVersion`: ownership transfers atomically +4. Source removes shard + +### System Keys Modified + +| Key Range | Change | +|-----------|--------| +| `serverKeysPrefixFor(sourceId)` | Remove key range | +| `serverKeysPrefixFor(destId)` | Add key range | +| `keyServersKey(range)` | Update server list for range | +| `moveKeysLockOwnerKey` | DD process ownership | + +### Key Functions + +- **`takeMoveKeysLock()`**: Acquire exclusive lock (prevents concurrent moves by different DD instances) +- **`readMoveKeysLock()`**: Validate current DD still holds lock +- **`unassignServerKeys()`**: Remove server from key range in system keys, with coalescing +- **`deleteCheckpoints()`**: Clean up checkpoint metadata after physical shard move + +### Conflict Resolution + +- Read conflicts added at range boundaries to detect concurrent assignments +- Ensures no mutations lost during ownership transfer +- Transactions retry if conflicts detected + +--- + +## Why Data Moves + +| Trigger | Action | +|---------|--------| +| Shard grows too large | Split into smaller shards | +| Storage server fails | Re-replicate shards to healthy servers | +| Load imbalance | Move shards from hot to cold servers | +| Configuration change | Adjust replication factor | +| Server exclusion | Move all shards off excluded server | +| Shard too small | Merge with adjacent shard | + +--- + +## Data Flow + +``` +DDShardTracker ──metrics exceed bounds──▶ RelocateShard request + │ + ▼ +DDQueue ──prioritize, dedup, find dest team──▶ launchQueuedWork() + │ + ▼ +MoveKeys protocol: + 1. Dest SS: fetchKeys() from source SS + 2. Dest SS: start consuming TLog for shard's tag + 3. Dest SS: catches up to current version + 4. Atomic system key transaction: transfer ownership + 5. Source SS: remove shard +``` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/datadistributor/DataDistribution.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DataDistribution.cpp) | Main DD actor, initialization | +| [`fdbserver/datadistributor/DDTeamCollection.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDTeamCollection.actor.cpp) | Team building, health monitoring, SS recruitment | +| [`fdbserver/datadistributor/DDShardTracker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDShardTracker.cpp) | Shard metrics, split/merge decisions | +| [`fdbserver/datadistributor/DDRelocationQueue.actor.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/DDRelocationQueue.actor.cpp) | Relocation queue, prioritization, execution | +| [`fdbserver/core/MoveKeys.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/MoveKeys.cpp) | Atomic shard transfer protocol | +| [`fdbserver/core/DataMovement.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/DataMovement.cpp) | Data move metadata | +| [`fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/datadistributor/ShardsAffectedByTeamFailure.cpp) | Impact analysis for team failures | diff --git a/design/AI-generated/subsystem_09_cluster_recovery.md b/design/AI-generated/subsystem_09_cluster_recovery.md new file mode 100644 index 00000000000..2316caa1703 --- /dev/null +++ b/design/AI-generated/subsystem_09_cluster_recovery.md @@ -0,0 +1,215 @@ +# Subsystem 9: Cluster Recovery + +**[Diagrams](diagram_09_cluster_recovery.md)** + +**Location:** [`fdbserver/clustercontroller/ClusterRecovery.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.cpp), [`fdbserver/core/RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h) +**Size:** Part of ClusterController files +**Role:** 10-state recovery state machine to reconstitute the transaction system after failures. + +--- + +## Overview + +Recovery is triggered when the master (sequencer) fails, a network partition heals, or coordinators change. The Cluster Controller drives a 10-state state machine that locks old TLogs, recruits a new transaction system, replays uncommitted data, and resumes accepting commits. + +--- + +## ClusterRecoveryData -- [`ClusterRecovery.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.h)`:178-318` + +``` +struct ClusterRecoveryData { + UID dbgid; + Version lastEpochEnd; // last committed version in old epoch + Version recoveryTransactionVersion; // first version in new epoch + Optional versionEpoch; + + DatabaseConfiguration configuration; + std::vector> primaryDcId, remoteDcIds; + + Reference logSystem; + IKeyValueStore* txnStateStore; // in-memory metadata store + LogSystemDiskQueueAdapter* txnStateLogAdapter; + + std::vector commitProxies; + std::vector grvProxies; + std::vector resolvers; + + ReusableCoordinatedState cstate; // coordinated cluster state + MasterInterface masterInterface; + + // Recovery signaling + Promise recoveryReadyForCommits; + Promise cstateUpdated; +}; +``` + +--- + +## The 10 Recovery States -- [`RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h)`:31-42` + +``` +enum class RecoveryState { + UNINITIALIZED = 0, + READING_CSTATE = 1, + LOCKING_CSTATE = 2, + RECRUITING = 3, + RECOVERY_TRANSACTION = 4, + WRITING_CSTATE = 5, + ACCEPTING_COMMITS = 6, + ALL_LOGS_RECRUITED = 7, + STORAGE_RECOVERED = 8, + FULLY_RECOVERED = 9 +}; +``` + +--- + +## clusterRecoveryCore() Walk-Through + +### State 1: READING_CSTATE (line ~1504) + +- Read coordinated state from coordinators +- Learn about previous epoch: `DBCoreState` with TLog configuration +- Validate protocol versions + +### State 2: LOCKING_CSTATE (line ~1517) + +- Lock coordinated state (increment recovery count) +- Prevents split-brain: old master cannot write new state +- Uses generation register for safe state transitions + +### State 3: RECRUITING (line ~1584) + +**`recruitEverything()`** (lines 999-1117): +1. Send `RecruitFromConfigurationRequest` to cluster controller +2. CC selects workers based on fitness, locality, exclusions +3. Initialize in parallel: + - `newCommitProxies()` -- `InitializeCommitProxyRequest` to workers + - `newGrvProxies()` -- `InitializeGrvProxyRequest` + - `newResolvers()` -- `InitializeResolverRequest` + - `newTLogServers()` -- creates new log epoch via `oldLogSystem->newEpoch()` + - `newSeedServers()` -- seed storage servers (only at version 0) +4. `monitorInitializingTxnSystem()` provides timeout with exponential backoff + +**`provisionalMaster()`** (line ~858): +- Provides minimal proxy implementation during recovery +- Serves clients with provisional (limited) responses +- Accepts emergency transactions (configuration mutations) + +### State 4: RECOVERY_TRANSACTION (line ~1635) + +**`readTransactionSystemState()`** (lines 1139-1270): +Peeks old TLogs to reconstruct metadata: +- `versionEpochKey` -- version epoch +- `minRequiredCommitVersionKey` -- for versionstamped operations +- Configuration keys -- database config +- Tag locality mappings -- datacenter localities +- Server tags and tag history + +Calculates: +- `lastEpochEnd` -- last committed version in old epoch +- `recoveryTransactionVersion` -- first version in new epoch + +Executes the recovery transaction: writes initial state to the new transaction system. + +### State 5: WRITING_CSTATE (line ~1767) + +- Write new epoch's `DBCoreState` to coordinators via coordinated state +- Contains: new TLog configuration, recovery count, log system state +- Must succeed at quorum of coordinators + +### State 6: ACCEPTING_COMMITS (line ~1802) + +- New transaction system is live +- CommitProxies, GrvProxies, Resolvers, TLogs all operational +- Client commits now flow through the new system +- Old TLog data still being consumed by storage servers + +### State 7: ALL_LOGS_RECRUITED + +Transition when all TLog sets (including remote region) are fully populated. + +### State 8: STORAGE_RECOVERED + +All old generation TLogs have been fully recruited into the system. + +### State 9: FULLY_RECOVERED + +- Old TLog data fully consumed by storage servers +- Old generations can be discarded +- `oldLogSystems->get()->stopRejoins()` +- Normal operation + +--- + +## trackTlogRecovery() -- [`ClusterRecovery.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.cpp)`:449-533` + +Monitors TLog set completeness and drives state transitions: + +``` +loop { + // Convert current logSystem to DBCoreState + self->logSystem->toCoreState(newState); + + // Check completeness + allLogs = newState.tLogs.size() == configuration.expectedLogSets(primaryDcId); + finalUpdate = !newState.oldTLogData.size() && allLogs; + + // Write state durably + co_await self->cstate.write(newState, finalUpdate); + + // Signal readiness + if (self->recoveryReadyForCommits.canBeSet()) + self->recoveryReadyForCommits.send(Void()); + + // Broadcast updated recovery state + self->logSystem->coreStateWritten(newState); + + if (finalUpdate) { + // FULLY_RECOVERED: stop rejoins, start rejoin handler + co_return; + } + + co_await changed; // wait for next logSystem state change +} +``` + +--- + +## CoordinatedState Protocol + +### Read + +1. Replicated read from quorum of coordinators +2. Returns highest-generation value +3. If client's generation > stored readGen: updates readGen + +### Write + +1. Replicated write with generation check +2. `readGen <= requestGen AND writeGen < requestGen` must hold +3. If valid: update value and writeGen +4. If conflict: throws `coordinated_state_conflict` + +--- + +## Recovery Timing + +| Knob | Default | Purpose | +|------|---------|---------| +| `CC_RECOVERY_INIT_REQ_TIMEOUT` | 30s | Base timeout for recruitment | +| `CC_RECOVERY_INIT_REQ_TIMEOUT_GROWTH_FACTOR` | 2x | Exponential backoff per unfinished recovery | +| `CC_RECOVERY_INIT_REQ_MAX_TIMEOUT` | 300s | Maximum timeout cap | + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/clustercontroller/ClusterRecovery.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.cpp) | clusterRecoveryCore, recruitment, state machine | +| [`fdbserver/clustercontroller/ClusterRecovery.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.h) | ClusterRecoveryData struct | +| [`fdbserver/core/include/fdbserver/core/RecoveryState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/RecoveryState.h) | RecoveryState enum | +| [`fdbserver/core/include/fdbserver/core/DBCoreState.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/include/fdbserver/core/DBCoreState.h) | DBCoreState (coordinated TLog config) | +| [`fdbserver/core/CoordinatedState.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/core/CoordinatedState.cpp) | Replicated read/write over generation registers | +| [`fdbserver/logsystem/LogSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp) | newEpoch() for creating new TLog generations | diff --git a/design/AI-generated/subsystem_10_ratekeeper.md b/design/AI-generated/subsystem_10_ratekeeper.md new file mode 100644 index 00000000000..a55ea688194 --- /dev/null +++ b/design/AI-generated/subsystem_10_ratekeeper.md @@ -0,0 +1,203 @@ +# Subsystem 10: Rate Keeping & Throttling + +**[Diagrams](diagram_10_ratekeeper.md)** + +**Location:** [`fdbserver/ratekeeper/`](https://github.com/apple/foundationdb/tree/main/fdbserver/ratekeeper) +**Size:** ~4K +**Role:** Back-pressure on commits and reads, tag-based throttling, throughput tracking. + +--- + +## Overview + +The Ratekeeper is a singleton role (recruited by Cluster Controller) that monitors cluster health metrics and calculates transaction rate limits. These limits are sent to GRV proxies, which enforce them by delaying `GetReadVersion` responses. This prevents the cluster from being overwhelmed when storage servers or TLogs fall behind. + +--- + +## Ratekeeper Structure -- [`Ratekeeper.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.h)`:129-232` + +``` +class Ratekeeper { + Map storageQueueInfo; // per-SS metrics + Map tlogQueueInfo; // per-TLog metrics + std::map grvProxyInfo; + + Smoother smoothReleasedTransactions; // actual TPS + Smoother smoothBatchReleasedTransactions; + Smoother smoothTotalDurableBytes; + + std::unique_ptr tagThrottler; + + RatekeeperLimits normalLimits; // default priority limits + RatekeeperLimits batchLimits; // batch priority limits +}; +``` + +### StorageQueueInfo (lines 37-73) + +``` +class StorageQueueInfo { + Smoother smoothFreeSpace, smoothTotalSpace; + Smoother smoothDurableBytes, smoothInputBytes; + Smoother smoothDurableVersion, smoothLatestVersion; + StorageQueuingMetricsReply lastReply; + limitReason_t limitReason; + std::vector busiestReadTags, busiestWriteTags; +}; +``` + +### TLogQueueInfo (lines 75-94) + +``` +class TLogQueueInfo { + Smoother smoothDurableBytes, smoothInputBytes; + Smoother smoothFreeSpace, smoothTotalSpace; + TLogQueuingMetricsReply lastReply; +}; +``` + +### RatekeeperLimits (lines 96-127) + +``` +struct RatekeeperLimits { + double tpsLimit; + int64_t storageTargetBytes, storageSpringBytes; + int64_t logTargetBytes, logSpringBytes; + double maxVersionDifference; + int64_t durabilityLagTargetVersions; + double durabilityLagLimit; + TransactionPriority priority; +}; +``` + +--- + +## Rate Calculation -- [`Ratekeeper.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.cpp)`:642-850` + +### `updateRate(RatekeeperLimits* limits)` + +Evaluates multiple metrics and takes the minimum rate: + +### 1. Storage Queue Depth + +``` +storageQueue = ss.getStorageQueueBytes() +``` + +If queue between `springBytes` and `targetBytes`: +``` +targetRateRatio = (queue - target + spring) / spring +tpsLimit = min(actualTps * maxBytesPerSecond / inputRate, + maxBytesPerSecond * MAX_TRANSACTIONS_PER_BYTE) +``` + +### 2. Durability Lag + +``` +storageDurabilityLag = ss.getDurabilityLag() // version gap between latest and durable +``` + +If lag exceeds `durabilityLagTargetVersions`: reduce rate proportionally. + +### 3. Free Space + +- Minimum free space enforcement with ratio factor +- Hard limit: `STORAGE_HARD_LIMIT_BYTES` +- Below hard limit: rate drops to 0 + +### 4. TLog Queue Depth + +Similar to storage queue depth but for TLog queues: +- `logTargetBytes` / `logSpringBytes` thresholds +- Limits rate when TLogs fall behind + +### 5. Write Bandwidth MVCC + +``` +maxBytesPerSecond = (targetBytes - springBytes) / mvcc_window +``` + +Prevents storage server from filling its MVCC queue faster than it can drain. + +### Rate Selection + +The final rate is the **minimum** across all metrics, with the `limitReason` tracking which factor is most constraining. + +--- + +## Communication with GRV Proxies -- [`Ratekeeper.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.cpp)`:345-410` + +### GetRateInfoRequest + +``` +struct GetRateInfoRequest { + UID requesterID; + int64_t totalReleasedTransactions; + int64_t batchReleasedTransactions; + Version version; + TransactionTagMap throttledTagCounts; + bool detailed; +}; +``` + +### GetRateInfoReply + +``` +struct GetRateInfoReply { + double transactionRate; // normal priority TPS limit + double batchTransactionRate; // batch priority TPS limit + double leaseDuration; // METRIC_UPDATE_RATE + HealthMetrics healthMetrics; + Optional> clientThrottledTags; +}; +``` + +### Flow + +1. GRV proxy sends `GetRateInfoRequest` periodically +2. Ratekeeper updates released transaction counts +3. Calculates per-proxy limits (total limit / num proxies) +4. Includes per-tag throttle info if tags changed +5. Returns rate limits + health metrics + +GRV proxy enforces limits by delaying `GetReadVersion` responses when rate exceeded. + +--- + +## Tag Throttling -- [`TagThrottler.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/TagThrottler.cpp) + +Per-tag (per-tenant/workload) transaction throttling. + +### Concepts + +| Term | Meaning | +|------|---------| +| **Manual throttle** | Operator-defined tag rate limit | +| **Automatic throttle** | Ratekeeper-generated tag limit for a busy tag | +| **Priority** | Maximum transaction priority affected by a throttle | +| **Expiration** | Time at which a throttle is removed | + +### Key Functions + +- `tryUpdateAutoThrottling()` -- update automatic throttles from busy-tag signals +- `getClientRates()` -- collect active limits to send to clients +- `monitorThrottlingChanges()` -- watch persisted throttle state + +### Throttle Enforcement + +1. Ratekeeper calculates per-tag rate limits +2. Sends to GRV proxies in `GetRateInfoReply.clientThrottledTags` +3. Clients enforce tag-specific limits locally +4. Clients see `tag_throttled` if the rate is exceeded + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/ratekeeper/Ratekeeper.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.cpp) | Rate calculation, proxy communication | +| [`fdbserver/ratekeeper/Ratekeeper.h`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/Ratekeeper.h) | Ratekeeper, StorageQueueInfo, TLogQueueInfo, RatekeeperLimits | +| [`fdbserver/ratekeeper/TagThrottler.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/TagThrottler.cpp) | Per-tag throttling implementation | +| [`fdbserver/ratekeeper/RkTagThrottleCollection.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/ratekeeper/RkTagThrottleCollection.cpp) | Tag throttle collection management | +| `fdbserver/ratekeeper/RatekeeperLimits.cpp` | Limit calculation utilities | diff --git a/design/AI-generated/subsystem_11_backup_dr.md b/design/AI-generated/subsystem_11_backup_dr.md new file mode 100644 index 00000000000..94e3404ef03 --- /dev/null +++ b/design/AI-generated/subsystem_11_backup_dr.md @@ -0,0 +1,290 @@ +# Subsystem 11: Backup, Restore & DR + +**[Diagrams](diagram_11_backup_dr.md)** + +**Location:** [`fdbserver/backupworker/`](https://github.com/apple/foundationdb/tree/main/fdbserver/backupworker), `fdbclient/FileBackupAgent*`, `fdbclient/BackupContainer*`, [`fdbbackup/`](https://github.com/apple/foundationdb/tree/main/fdbbackup) +**Size:** ~10K server, ~20K client +**Role:** Continuous backup to external storage, point-in-time restore, cross-cluster DR. + +--- + +## Overview + +FDB supports continuous backup of the mutation stream to external storage, enabling point-in-time restore. The backup system has two main components: server-side BackupWorkers that pull mutations from TLogs, and client-side BackupAgents that orchestrate the backup lifecycle and manage backup containers. + +--- + +## Backup Architecture + +### Two Backup File Types + +**Log files** -- continuous mutation stream: +``` +struct LogFile { + Version beginVersion, endVersion; + uint32_t blockSize; + std::string fileName; + int64_t fileSize; + int tagId, totalTags; // for partitioned logs +}; +``` + +**Range files** -- snapshot of key-value pairs at a version: +``` +struct RangeFile { + Version version; + uint32_t blockSize; + std::string fileName; + int64_t fileSize; +}; +``` + +Together, range files + log files enable point-in-time restore: restore the range snapshot, then replay log files to reach any target version. + +--- + +## BackupWorker -- [`fdbserver/backupworker/BackupWorker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/backupworker/BackupWorker.cpp) + +Server-side role that pulls mutations from TLogs (like a storage server, but writes to backup storage). + +### BackupData (lines 105-287) + +``` +struct BackupData { + const UID myId; + const Tag tag; // log router tag (-2, i) + const int totalTags; + const Version startVersion; + const Optional endVersion; // old epoch end + const LogEpoch recruitedEpoch, backupEpoch; + + Version savedVersion; // largest version saved to storage + AsyncVar> logSystem; + std::vector messages; // in-memory mutation buffer + std::map backups; // per-backup configurations +}; +``` + +### PerBackupInfo (lines 125-258) + +``` +struct PerBackupInfo { + Version startVersion, lastSavedVersion; + Future>> container; + Future>> ranges; + bool stopped; +}; +``` + +### Mutation Pulling + +1. BackupWorker peeks TLog via its assigned `tagLocalityLogRouter` tag +2. Filters mutations based on backup key ranges +3. Buffers in `messages` vector +4. Writes to backup container when buffer reaches threshold +5. Tracks progress in system keys + +--- + +## BackupAgent -- [`fdbclient/BackupAgent.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupAgent.h), [`fdbclient/FileBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/FileBackupAgent.cpp) + +Client-side orchestration of backup lifecycle. + +### Backup States + +``` +enum class EnumState { + STATE_ERRORED = 0, + STATE_SUBMITTED = 1, + STATE_RUNNING = 2, + STATE_RUNNING_DIFFERENTIAL = 3, + STATE_COMPLETED = 4, + STATE_NEVERRAN = 5, + STATE_ABORTED = 6 +}; +``` + +### FileBackupAgent Methods + +| Method | Purpose | +|--------|---------| +| `submitBackup()` | Queue a new backup task | +| `restore()` | Start point-in-time restore | +| `atomicRestore()` | Atomic restore (single transaction) | +| `abortRestore()` | Cancel in-progress restore | +| `waitRestore()` | Wait for restore completion | +| `restoreStatus()` | Query restore progress | + +### TaskBucket Pattern + +Backup/restore operations use a distributed task execution pattern: +- Tasks stored as key-value entries in the database +- `TaskBucket` polls for ready tasks at `pollDelay` intervals +- Tasks can spawn sub-tasks for parallelism +- `FutureBucket` tracks task completion +- Provides reliability: tasks survive process failures + +``` +Future run(Database cx, double pollDelay, int maxConcurrentTasks); +``` + +--- + +## Backup Container -- [`fdbclient/BackupContainer.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupContainer.h) + +### IBackupContainer Interface (lines 249-366) + +``` +class IBackupContainer { + // Write operations + Future> writeLogFile(beginVersion, endVersion, blockSize); + Future> writeRangeFile(version, snapshotFileCount, ...); + Future> writeTaggedLogFile(...); + + // Read/describe + Future> readFile(name); + Future describeBackup(bool deepScan); + Future> getRestoreSet(Version targetVersion); + + // Lifecycle + Future create(); + Future exists(); + Future expireData(Version expireEndVersion, bool force); + Future deleteContainer(int* pNumDeleted); +}; +``` + +### Implementations + +- **BackupContainerLocalDirectory** -- local filesystem +- **BackupContainerS3BlobStore** -- S3-compatible object storage + +### File Format Versions + +| Version | Format | +|---------|--------| +| `BACKUP_AGENT_MLOG_VERSION = 2001` | Old FileBackupAgent mutation logs | +| `PARTITIONED_MLOG_VERSION = 4110` | BackupWorker partitioned logs | +| `RANGE_PARTITIONED_MLOG_VERSION = 5001` | Range-partitioned logs | +| `BACKUP_AGENT_SNAPSHOT_FILE_VERSION = 1001` | Range file snapshots | +| `BACKUP_AGENT_ENCRYPTED_SNAPSHOT_FILE_VERSION = 1002` | Encrypted snapshots | + +### RestorableFileSet (BackupContainer.h:221-234) + +``` +struct RestorableFileSet { + Version targetVersion; + std::vector ranges; // snapshot files + std::vector logs; // mutation log files + Version continuousBeginVersion; + Version continuousEndVersion; + std::vector snapshots; +}; +``` + +--- + +## Restore Flow + +### Restore Modes + +| Mode | Description | +|------|-------------| +| `RANGEFILE` | Traditional: read range files + apply log files | +| `BULKLOAD` | Efficient: SST file ingestion via BulkLoad | + +### Traditional Restore + +1. Read range files to reconstruct base key-value state at snapshot version +2. Apply log files from snapshot version to target version +3. Each log file contains mutations that are replayed in order +4. Supports key prefix transformation (add/remove prefix during restore) + +### RestoreConfig + +``` +class RestoreConfig : public KeyBackedTaskConfig { + KeyBackedProperty stateEnum(); + KeyBackedProperty addPrefix(), removePrefix(); + KeyBackedProperty onlyApplyMutationLogs(); + KeyBackedProperty bulkDumpJobId(); + KeyBackedProperty firstConsistentVersion(); +}; +``` + +--- + +## Disaster Recovery (DR) -- [`fdbclient/DatabaseBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/DatabaseBackupAgent.cpp) + +Streams mutations from one cluster to another in near-real-time. + +### DatabaseBackupAgent + +``` +class DatabaseBackupAgent { + static const Key keyAddPrefix, keyRemovePrefix; + static const Key keyRangeVersions; + static const Key keyCopyStop, keyDatabasesInSync; +}; +``` + +### DR Flow + +1. Source cluster runs backup agent that reads committed mutations +2. Mutations streamed to destination cluster in near-real-time +3. Destination cluster applies mutations with optional prefix transformation +4. Enables warm standby cluster for disaster recovery + +### BackupRangeTaskFunc + +- Executes range-based copying for DR +- Finds shard boundaries, creates sub-tasks for parallelism +- Uses FlowLock for memory management (`CLIENT_KNOBS->BACKUP_LOCK_BYTES`) + +--- + +## Data Flow + +### Backup +``` +TLog ──peek(logRouterTag)──▶ BackupWorker + │ + ▼ + Filter by backup key ranges + │ + ▼ + Buffer mutations + │ + ▼ + Write to IBackupContainer (log files) + │ + + periodic range file snapshots +``` + +### Restore +``` +IBackupContainer ──read range files──▶ Restore Agent + │ + ▼ + Write base state to empty cluster + │ + ▼ + ──read log files──▶ Replay mutations to target version + │ + ▼ + Cluster at target version state +``` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbserver/backupworker/BackupWorker.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/backupworker/BackupWorker.cpp) | Server-side mutation pulling and writing | +| [`fdbclient/include/fdbclient/BackupAgent.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupAgent.h) | BackupAgentBase, FileBackupAgent API | +| [`fdbclient/FileBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/FileBackupAgent.cpp) | Backup/restore orchestration, TaskBucket | +| [`fdbclient/DatabaseBackupAgent.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/DatabaseBackupAgent.cpp) | DR agent, cross-cluster streaming | +| [`fdbclient/include/fdbclient/BackupContainer.h`](https://github.com/apple/foundationdb/blob/main/fdbclient/include/fdbclient/BackupContainer.h) | IBackupContainer, file format definitions | +| [`fdbclient/BackupContainerFileSystem.cpp`](https://github.com/apple/foundationdb/blob/main/fdbclient/BackupContainerFileSystem.cpp) | Filesystem-based container implementation | +| [`fdbbackup/backup.cpp`](https://github.com/apple/foundationdb/blob/main/fdbbackup/backup.cpp) | Backup CLI tool | diff --git a/design/AI-generated/subsystem_12_simulation_testing.md b/design/AI-generated/subsystem_12_simulation_testing.md new file mode 100644 index 00000000000..179b63198fb --- /dev/null +++ b/design/AI-generated/subsystem_12_simulation_testing.md @@ -0,0 +1,345 @@ +# Subsystem 12: Simulation & Testing + +**[Diagrams](diagram_12_simulation_testing.md)** + +**Location:** [`fdbrpc/sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp), [`fdbserver/workloads/`](https://github.com/apple/foundationdb/tree/main/fdbserver/workloads), `fdbserver/tester/`, `tests/` +**Size:** ~51K workloads + simulator +**Role:** Deterministic simulation (Sim2), fault injection (Buggify), workload-based integration tests. + +--- + +## Overview + +FDB's most distinctive engineering practice. The entire cluster -- multiple processes, network, disk -- runs deterministically in a single OS thread with virtual time. Combined with aggressive fault injection (Buggify), this finds bugs that would take years to manifest in production. + +--- + +## Sim2 -- Deterministic Simulator -- [`fdbrpc/sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp) + +### Sim2 Class (line 1025) + +``` +class Sim2 final : public ISimulator, public INetworkConnections +``` + +Implements `INetwork` (the event loop interface) and `ISimulator` (fault injection). + +### Key Properties + +- **Deterministic**: Same random seed → identical execution +- **Virtual time**: No wall-clock dependency; time advances via task scheduling +- **Single-threaded**: All virtual processes share one OS thread +- **Full cluster**: Multiple processes with separate globals, endpoints, listeners + +### Virtual Event Loop + +- `TaskQueue taskQueue` -- priority queue of pending tasks +- Time advances through task completion, not wall-clock +- `delay(seconds, taskId)` enqueues task at `currentTime + seconds` +- `timer()` can be up to 0.1s ahead of `now()` for timer jitter +- Buggified delays added via `FLOW_KNOBS->MAX_BUGGIFIED_DELAY` + +--- + +## ProcessInfo -- `SimulatorProcessInfo.h:42-170` + +State per simulated process: + +``` +struct ProcessInfo : NonCopyable { + std::string name, dataFolder, coordinationFolder; + MachineInfo* machine; + NetworkAddressList addresses; + LocalityData locality; + ProcessClass startingClass; + + bool failed, excluded, cleared, rebooting, failedDisk; + double fault_injection_p1, fault_injection_p2; + + std::vector globals; // per-process globals + INetworkConnections* network; + UID uid; + ProtocolVersion protocolVersion; + std::vector childs; + Promise shutdownSignal; + + bool isReliable(); // no faults injected + bool isAvailable(); // !excluded && isReliable() +}; +``` + +Each process has its own: +- Global variables (via `globals` vector) +- Endpoint registrations +- Listener map +- Fault injection parameters + +--- + +## Fault Injection + +### Kill Types -- `SimulatorKillType.h` + +| KillType | Effect | +|----------|--------| +| `KillInstantly` | Immediate failure, process gone | +| `InjectFaults` | Enable probabilistic fault injection | +| `FailDisk` | Simulate disk failure | +| `Reboot` | Machine reboot, keep data | +| `RebootProcess` | Process reboot, keep data | +| `RebootAndDelete` | Machine reboot, delete data | +| `RebootProcessAndDelete` | Process reboot, delete data | +| `RebootProcessAndSwitch` | Reboot with different cluster file | + +### Kill Hierarchy + +| Method | Scope | +|--------|-------| +| `killProcess(pid, kt)` | Single process | +| `killMachine(machineId, kt)` | All processes on machine | +| `killZone(zoneId, kt)` | All machines in zone | +| `killDataCenter(dcId, kt)` | All machines in datacenter | +| `killDataHall(hallId, kt)` | All machines in data hall | + +`killMachine()` checks replication policies before killing -- may upgrade kill type if processes are protected. + +### Network Clogging -- `SimClogging` (sim2.cpp:198-297) + +``` +struct SimClogging { + std::map clogSendUntil, clogRecvUntil; + std::map, double> clogPairUntil, clogPairLatency; + std::map, double> disconnectPairUntil; + + double getSendDelay(from, to); + double getRecvDelay(from, to); + bool disconnected(from, to); + void clogPairFor(from, to, seconds); + void disconnectPairFor(from, to, seconds); +}; +``` + +Latency model: +- 99.9% of packets: `MIN_NETWORK_LATENCY + FAST_NETWORK_LATENCY` +- 0.1% tail: `MIN_NETWORK_LATENCY + SLOW_NETWORK_LATENCY` +- Additional per-pair clogging latency +- Disconnection: throws `connection_failed()` + +### Disk Fault Injection -- `AsyncFileNonDurable.h` + +Simulates unreliable disk writes: + +``` +class AsyncFileNonDurable { + Reference file; // real underlying file + RangeMap> pendingModifications; + enum KillMode { NO_CORRUPTION, DROP_ONLY, FULL_CORRUPTION }; +}; +``` + +- Delays writes randomly (up to `NON_DURABLE_MAX_WRITE_DELAY`) +- On `kill()`: drops or corrupts pending writes based on `KillMode` +- `sync()` makes writes durable +- Simulates power failure: un-synced writes are lost + +### I/O Error Injection + +`simulator_should_inject_fault()` (sim2.cpp) -- called during I/O operations: +- Checks per-process `fault_injection_p1` and `fault_injection_p2` +- Probabilistically returns true → caller throws `io_timeout`, `io_error`, or `platform_error` + +--- + +## Buggify System -- `flow/include/flow/Buggify.h` + +Randomly enables rare code paths throughout the codebase. + +### Mechanism + +```cpp +#define BUGGIFY (getGeneralSBVar(__FILE__, __LINE__) && deterministicRandom()->random01() < P_FIRES) +#define BUGGIFY_WITH_PROB(x) (getGeneralSBVar(__FILE__, __LINE__) && deterministicRandom()->random01() < (x)) +``` + +Two-level randomization: +1. **Activation** (once per code location): `P_BUGGIFIED_SECTION_ACTIVATED = 0.25` -- 25% of BUGGIFY sites are active +2. **Firing** (each execution): `P_BUGGIFIED_SECTION_FIRES = 0.25` -- active sites fire 25% of the time + +Decisions cached in `SBVars` map keyed by `__FILE__ + __LINE__` for deterministic replay. + +### Variants + +| Macro | Scope | +|-------|-------| +| `BUGGIFY` | General server code | +| `BUGGIFY_WITH_PROB(x)` | Custom probability | +| `CLIENT_BUGGIFY` | Client-only code | +| `CLIENT_BUGGIFY_WITH_PROB(x)` | Client custom probability | + +### Examples in Codebase + +```cpp +if (BUGGIFY) delay(deterministicRandom()->random01()); // random extra delay +if (BUGGIFY) bufferSize = 1; // tiny buffer +if (BUGGIFY_WITH_PROB(0.001)) throw io_error(); // rare I/O error +``` + +--- + +## CODE_PROBE System -- `flow/include/flow/CodeProbe.h` + +Tracks code path coverage during testing: + +```cpp +CODE_PROBE(condition, "description of what happened"); +``` + +### Implementation + +``` +struct ICodeProbe { + const char* filePath(), line(), comment(), condition(); + bool wasHit() const; + unsigned hitCount() const; + void trace(bool condition) const; +}; +``` + +- Static singleton per source location +- Atomic hit counter +- Trace event logged on first hit +- Annotations: `context::Sim2`, `decoration::Rare`, `assert::SimOnly` +- Used to verify rare code paths are exercised during simulation + +--- + +## Workload System -- [`fdbserver/workloads/`](https://github.com/apple/foundationdb/tree/main/fdbserver/workloads) + +### TestWorkload Base Class (`workloads.h:65-98`) + +``` +struct TestWorkload : NonCopyable, WorkloadContext, ReferenceCounted { + int phases; // SETUP | EXECUTION | CHECK | METRICS + + virtual std::string description() const = 0; + virtual Future setup(Database const& cx); // optional + virtual Future start(Database const& cx) = 0; // main logic + virtual Future check(Database const& cx) = 0; // verify results + virtual Future> getMetrics(); +}; +``` + +### WorkloadContext (lines 38-50) + +- `clientId`, `clientCount` -- for distributed coordination +- `sharedRandomNumber` -- coordinated randomness across clients +- Reference to `ServerDBInfo` + +### Representative Workloads + +| Workload | Purpose | +|----------|---------| +| `ApiCorrectness` | Tests get/set/getRange/clear correctness | +| `ReadWrite` | Mixed read/write load with configurable patterns | +| `ConsistencyCheck` | Validates data consistency across replicas | +| `Cycle` | Tests cluster configuration changes | +| `BackupCorrectness` | Validates backup/restore integrity | +| `BulkLoad` / `BulkDumping` | Tests bulk operations | +| `AtomicOps` | Validates atomic operation correctness | +| `ChangeConfig` | Tests configuration changes under load | + +~60+ workload types covering all aspects of the system. + +--- + +## Test Configuration -- `tests/` directory + +### TOML Format + +```toml +[[knobs]] +enable_replica_consistency_check_on_reads = true + +[[test]] +testTitle = 'ApiCorrectnessWithConsistencyCheck' +clearAfterTest = true +timeout = 2100 + + [[test.workload]] + testName = 'ApiCorrectness' + numKeys = 5000 + numGets = 1000 + numGetRanges = 100 +``` + +### Parameters + +- `testTitle` -- test name +- `timeout` -- seconds before test times out +- `clearAfterTest` -- clear database after test +- Multiple `[[test.workload]]` sections for concurrent workloads +- `[[knobs]]` sections for server knob overrides + +--- + +## SimulatedCluster -- [`fdbserver/SimulatedCluster.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/SimulatedCluster.cpp) + +### TestConfig (lines 131-429) + +``` +class TestConfig : public BasicTestConfig { + int minimumReplication, minimumRegions; + Optional datacenters, processesPerMachine; + std::set storageEngineExcludeTypes; + Optional generateFearless, buggify, faultInjection; + bool simHTTPServerEnabled; + bool injectTargetedSSRestart, injectSSDelay; +}; +``` + +### Cluster Setup + +1. Load test configuration from TOML/INI file +2. Create machines with virtual IP addresses +3. Create processes with locality data (zone, machine, DC) +4. Configure replication policies +5. Start workload execution + +--- + +## Test Execution -- `fdbserver/tester/test.cpp` + +### `runWorkload()` (lines 67-187) + +Four phases: + +1. **Setup** -- call `setup()` on each tester, initialize state +2. **Execution** -- call `start()` on each tester, run workload logic +3. **Check** -- call `check()` on each tester, verify results +4. **Metrics** -- call `getMetrics()`, aggregate performance data + +### Distributed Coordination + +Each tester receives `WorkloadRequest`: +- `clientId` -- unique client identifier +- `clientCount` -- total clients +- `sharedRandomNumber` -- coordinated RNG seed +- Results aggregated via `aggregateMetrics()` + +--- + +## Principal Files + +| File | Purpose | +|------|---------| +| [`fdbrpc/sim2.cpp`](https://github.com/apple/foundationdb/blob/main/fdbrpc/sim2.cpp) | Sim2 deterministic simulator implementation | +| [`fdbrpc/include/fdbrpc/simulator.h`](https://github.com/apple/foundationdb/blob/main/fdbrpc/include/fdbrpc/simulator.h) | ISimulator interface | +| `fdbrpc/include/fdbrpc/SimulatorProcessInfo.h` | ProcessInfo, MachineInfo | +| `fdbrpc/include/fdbrpc/AsyncFileNonDurable.h` | Simulated unreliable disk | +| `flow/include/flow/Buggify.h` | BUGGIFY macros | +| `flow/include/flow/CodeProbe.h` | CODE_PROBE coverage tracking | +| [`fdbserver/SimulatedCluster.cpp`](https://github.com/apple/foundationdb/blob/main/fdbserver/SimulatedCluster.cpp) | Simulated cluster setup, TestConfig | +| `fdbserver/tester/include/fdbserver/tester/workloads.h` | TestWorkload base class | +| `fdbserver/tester/test.cpp` | Test execution orchestration | +| `fdbserver/workloads/*.cpp` | 60+ workload implementations | +| `tests/**/*.toml` | Test configuration files | diff --git a/design/AcAC.md b/design/AcAC.md index 3e2360e6200..00183100d46 100644 --- a/design/AcAC.md +++ b/design/AcAC.md @@ -54,9 +54,9 @@ The output is: 15624 /root/src/fdbserver/worker.actor.cpp:storageServerRollbackRebooter 5407 /root/src/fdbserver/worker.actor.cpp:workerServer 5140 /root/src/fdbserver/worker.actor.cpp:fdbd - 16 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulatedFDBDRebooter - 15 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulatedMachine - 10 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulationSetupAndRun + 16 /root/src/fdbserver/SimulatedCluster.cpp:simulatedFDBDRebooter + 15 /root/src/fdbserver/SimulatedCluster.cpp:simulatedMachine + 10 /root/src/fdbserver/SimulatedCluster.cpp:simulationSetupAndRun ``` which provides the actor ID, an analog of process ID in the actor context; the path of the source file, which contains the actor; the name of the actor; and a `` mark for the current running actor. @@ -102,15 +102,15 @@ AAAAAAAAAADwo2kloY1WAKDXp1f+BrgAAAAAAAAAAA== \ qDHU7CAAAAAAAADsIAAAAAAAAAD/+N1Ch/X3AEwORpyq68Y8AAAAAAAAADwAAAAAAAAAAEqD \ W3c/M9kAHCBUzBTlojsAAAAAAAAAOwAAAAAAAAAA+uRVDFla0gA5DXKp8yQbCgAAAAAAAAAK \ AAAAAAAAAADwo2kloY1WAKDXp1f+BrgAAAAAAAAAAA=="|bin/acac - 1304263 /root/src/fdbserver/workloads/FuzzApiCorrectness.actor.cpp:loadAndRun + 1304263 /root/src/fdbserver/workloads/FuzzApiCorrectness.cpp:loadAndRun 1269030 /root/src/fdbserver/tester.actor.cpp:runWorkloadAsync 1254091 /root/src/fdbserver/tester.actor.cpp:testerServerWorkload 8760 /root/src/fdbserver/tester.actor.cpp:testerServerCore 8746 /root/src/fdbserver/worker.actor.cpp:workerServer 8428 /root/src/fdbserver/worker.actor.cpp:fdbd - 60 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulatedFDBDRebooter - 59 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulatedMachine - 10 /root/src/fdbserver/SimulatedCluster.actor.cpp:simulationSetupAndRun + 60 /root/src/fdbserver/SimulatedCluster.cpp:simulatedFDBDRebooter + 59 /root/src/fdbserver/SimulatedCluster.cpp:simulatedMachine + 10 /root/src/fdbserver/SimulatedCluster.cpp:simulationSetupAndRun ``` The reason for not being able to generate the actor call backtrace inside the debugger directly is that the actor names are internally represented as UUIDs, and the mapping requires external files generated by the actor compiler. @@ -126,4 +126,3 @@ The reason for not being able to generate the actor call backtrace inside the de * All binding tests fail * Random failure on the `fdbserver` * It is possible for AcAC to report the line number of the source code where the actor is created. - diff --git a/design/Commit/commit.sequence b/design/Commit/commit.sequence index 502a572c222..a03727967d8 100644 --- a/design/Commit/commit.sequence +++ b/design/Commit/commit.sequence @@ -10,7 +10,7 @@ end participantgroup **CommitProxy** (CommitProxyServer.actor.cpp) participant "commitBatcher" as cB participant "commitBatch" as Batch - participant "TagPartitionedLogSystem" as TPLS + participant "LogSystem" as TPLS end participantgroup **Master** diff --git a/design/backup_v2_partitioned_logs.md b/design/backup_v2_partitioned_logs.md index 3e85e7e3363..1905601d7c7 100644 --- a/design/backup_v2_partitioned_logs.md +++ b/design/backup_v2_partitioned_logs.md @@ -70,7 +70,7 @@ A command line tool `fdbconvert` has been written to convert new backup logs int * How to start a new type backup: e.g., ``` - fdbbackup start -C fdb.cluster --partitioned-log-experimental -d blob_url + fdbbackup start -C fdb.cluster --mutation-log-type partitioned-log-experimental -d blob_url ``` ### KPI's and Health @@ -224,7 +224,7 @@ We strive to keep the operational interface the same as the old backup system. T By default, backup workers are not enabled in the system. When operators submit a new backup request for the first time, the database performs a configuration change (`backup_worker_enabled:=1`) that enables backup workers. -The operator’s backup request can indicate if an old backup or a new backup is used. This is a command line option (i.e., `--partitioned-log-experimental`) in the `fdbbackup` command. A backup request of the new type is started in the following steps: +The operator’s backup request can indicate if an old backup or a new backup is used. This is a command line option (i.e., `--mutation-log-type partitioned-log-experimental`) in the `fdbbackup` command. A backup request of the new type is started in the following steps: 1. Operators use `fdbbackup` tool to write the backup range to a system key, i.e., `\xff\x02/backupStarted`. 2. All backup workers monitor the key `\xff\x02/backupStarted`, see the change, and start logging mutations. diff --git a/design/cluster_health_metric.md b/design/cluster_health_metric.md new file mode 100644 index 00000000000..5a15a9f1d4e --- /dev/null +++ b/design/cluster_health_metric.md @@ -0,0 +1,171 @@ +# Cluster Health Metric + +`cluster_health::Monitor` is a cluster controller background task that periodically evaluates a fixed set of health factors and emits a `ClusterHealthMetric` trace event. + +The monitor is controlled by these server knobs: + +| Knob | Default | Meaning | +| --- | --- | --- | +| `CLUSTER_HEALTH_METRIC_ENABLE` | `false` | Enables the cluster controller monitor. When disabled, the monitor exits immediately and does not emit `ClusterHealthMetric`. Simulation buggification can set this to `true`. | +| `CLUSTER_HEALTH_METRIC_POLL_INTERVAL` | `5.0` seconds | Time between monitor evaluations after the first evaluation. | +| `CLUSTER_HEALTH_METRIC_STORAGE_INTERVENTION_THRESHOLD` | `0.20` | Storage server free-space warning threshold. This is a ratio, not a percentage: `0.20` means 20% free space. | +| `CLUSTER_HEALTH_METRIC_STORAGE_CRITICAL_THRESHOLD` | `0.10` | Storage server free-space critical threshold. This should be less than or equal to the storage intervention threshold. | +| `CLUSTER_HEALTH_METRIC_TLOG_INTERVENTION_THRESHOLD` | `0.20` | TLog queue-disk free-space warning threshold. This is a ratio, not a percentage: `0.20` means 20% free space. | +| `CLUSTER_HEALTH_METRIC_TLOG_CRITICAL_THRESHOLD` | `0.10` | TLog queue-disk free-space critical threshold. This should be less than or equal to the TLog intervention threshold. | +| `CLUSTER_HEALTH_METRIC_RK_CRITICAL_RELEASED_TPS_RATIO_THRESHOLD` | `1.2` | Ratekeeper critical throttling threshold for `TPSLimit / ReleasedTPS`. With the default, ratekeeper is critical when the current TPS limit is less than 120% of recently released TPS. | + +Threshold comparisons are strict: a value equal to a threshold does not trigger that threshold's health level. + +## Health Levels + +Each factor uses the following levels: + +- `HEALTHY`: No issue is currently indicated by the factor. +- `SELF_HEALING`: The cluster is degraded, but automatic recovery or repair is already in progress. +- `INTERVENTION_REQUIRED`: The cluster is still functioning, but an operator likely needs to act to restore full health. +- `CRITICAL_INTERVENTION_REQUIRED`: The cluster is functioning, but in a severe state that likely requires immediate operator action. +- `OUTAGE`: The factor indicates loss of availability or loss of a required safety property. +- `METRICS_MISSING`: The factor could not evaluate because the required trace-event data was unavailable or malformed. + +For aggregation, the implementation assigns an internal score to each level and keeps the lowest-scoring factor as the aggregate result: + +- `HEALTHY` = 100 +- `SELF_HEALING` = 80 +- `INTERVENTION_REQUIRED` = 60 +- `CRITICAL_INTERVENTION_REQUIRED` = 40 +- `METRICS_MISSING` = 20 +- `OUTAGE` = 0 + +These numeric values provide a scalar metric that can be emitted, and leave room for additional intermediate values to be added in the future. + +## Factors + +The monitor currently evaluates these factors: + +### `StorageSpace` + +Source event: +- `"/StorageMetrics"` + +Fields used: +- `KvstoreBytesAvailable` +- `KvstoreBytesTotal` + +Behavior: +- Computes the minimum `KvstoreBytesAvailable / KvstoreBytesTotal` ratio across storage servers. +- Returns `CRITICAL_INTERVENTION_REQUIRED` when the ratio is below `CLUSTER_HEALTH_METRIC_STORAGE_CRITICAL_THRESHOLD`. +- Returns `INTERVENTION_REQUIRED` when the ratio is below `CLUSTER_HEALTH_METRIC_STORAGE_INTERVENTION_THRESHOLD` and at or above `CLUSTER_HEALTH_METRIC_STORAGE_CRITICAL_THRESHOLD`. +- Returns `HEALTHY` otherwise. + +### `TLogSpace` + +Source event: +- `"/TLogMetrics"` + +Fields used: +- `QueueDiskBytesAvailable` +- `QueueDiskBytesTotal` + +Behavior: +- Computes the minimum `QueueDiskBytesAvailable / QueueDiskBytesTotal` ratio across tlogs. +- Returns `CRITICAL_INTERVENTION_REQUIRED` when the ratio is below `CLUSTER_HEALTH_METRIC_TLOG_CRITICAL_THRESHOLD`. +- Returns `INTERVENTION_REQUIRED` when the ratio is below `CLUSTER_HEALTH_METRIC_TLOG_INTERVENTION_THRESHOLD` and at or above `CLUSTER_HEALTH_METRIC_TLOG_CRITICAL_THRESHOLD`. +- Returns `HEALTHY` otherwise. + +### `StorageReplication` + +Source event: +- `MovingData` + +Fields used: +- `InQueue` +- `InFlight` +- `PriorityTeamUnhealthy` +- `PriorityTeam2Left` +- `PriorityTeam1Left` +- `PriorityTeam0Left` + +Behavior: +- Returns `OUTAGE` if any `PriorityTeam0Left > 0`. +- Returns `CRITICAL_INTERVENTION_REQUIRED` if any `PriorityTeam1Left > 0` and the configured storage team size is 3. +- Returns `SELF_HEALING` if data movement is queued or in flight to restore replication. +- Returns `HEALTHY` otherwise. + +### `RecoveryState` + +Source: +- `ServerDBInfo::recoveryState` from the cluster controller + +Fields used: +- none; this factor reads the cluster controller's in-memory `RecoveryState` + +Behavior: +- Returns `OUTAGE` if recovery state is below `RecoveryState::ACCEPTING_COMMITS`. +- Returns `SELF_HEALING` if recovery is at or above `ACCEPTING_COMMITS` but below `FULLY_RECOVERED`. +- Returns `HEALTHY` at `FULLY_RECOVERED`. + +### `ProcessErrors` + +Source event: +- latest worker error event, fetched with `EventLogRequest()` + +Fields used: +- none are parsed directly; the factor only checks whether a non-empty latest-error trace event exists. + +Behavior: +- Returns `CRITICAL_INTERVENTION_REQUIRED` if any worker reports a non-empty latest error. +- Returns `HEALTHY` if all non-failed latest-error records are empty and at least one latest-error request succeeded. +- Returns `METRICS_MISSING` only if every latest-error request fails. + +### `RkThrottling` + +Source event: +- `RkUpdate` + +Fields used: +- `ReleasedTPS`: Ratekeeper's smoothed rate of transactions that GRV proxies have recently released from their start-transaction queues. A released transaction has been allowed to get a read version and begin; this is not commit throughput. +- `TPSLimit`: Ratekeeper's current transaction-per-second limit for normal transaction starts, computed from cluster pressure signals and sent back to GRV proxies to pace future releases. A value of `0` means ratekeeper is not allowing normal transaction starts. + +Behavior: +- Returns `OUTAGE` if any `TPSLimit == 0`. +- Computes the minimum `TPSLimit / ReleasedTPS` ratio across samples with nonzero `ReleasedTPS`. +- Ignores samples with `ReleasedTPS == 0` for the ratio calculation to avoid dividing by zero. +- Returns `CRITICAL_INTERVENTION_REQUIRED` when the minimum ratio is below `CLUSTER_HEALTH_METRIC_RK_CRITICAL_RELEASED_TPS_RATIO_THRESHOLD`. +- Returns `HEALTHY` otherwise, including the case where every non-outage sample has `ReleasedTPS == 0`. + +For `CLUSTER_HEALTH_METRIC_RK_CRITICAL_RELEASED_TPS_RATIO_THRESHOLD`, lower ratios are worse: + +- `TPSLimit / ReleasedTPS == 2.0` means ratekeeper is allowing twice the recently released transaction rate, so this factor is healthy with the default threshold. +- `TPSLimit / ReleasedTPS == 1.0` means ratekeeper's current limit is equal to the recently released transaction rate, so this factor is critical with the default threshold of `1.2`. +- `TPSLimit / ReleasedTPS < 1.0` means ratekeeper's current limit is below the recently released transaction rate, which is also critical. + +## Missing Metrics Semantics + +Most factors treat missing or malformed inputs as `METRICS_MISSING`. + +This can happen when: + +- the underlying latest-event RPC fails +- no relevant worker emitted the requested trace event +- the trace event exists but does not contain the required fields +- a field cannot be parsed to the expected type + +The implementation filters out empty `TraceEventFields` before interpreting a factor, so workers that never emit a role-specific event do not automatically degrade the whole metric. + +## `ClusterHealthMetric` Trace Event Schema + +The monitor emits a periodic trace event named `ClusterHealthMetric`. + +Fields: + +- `FactorStorageSpace`: string enum, one of `Outage`, `CriticalInterventionRequired`, `InterventionRequired`, `SelfHealing`, `MetricsMissing`, `Healthy` +- `FactorTLogSpace`: same enum +- `FactorStorageReplication`: same enum +- `FactorRecoveryState`: same enum +- `FactorProcessErrors`: same enum +- `FactorRkThrottling`: same enum +- `Aggregate`: same enum, computed from the most limiting factor +- `AggregateValue`: numeric aggregate score, currently one of `0`, `20`, `40`, `60`, `80`, or `100` +- `LimitingFactor`: factor name string, present when not healthy + +The aggregate is determined by taking the lowest-scoring level across all factors. diff --git a/design/coroutines.md b/design/coroutines.md index 230408a7144..b35524043ef 100644 --- a/design/coroutines.md +++ b/design/coroutines.md @@ -6,6 +6,7 @@ * [Choose-When](#choose-when) * [Execution in when-expressions](#execution-in-when-expressions) * [Waiting in When-Blocks](#waiting-in-when-blocks) + * [Migrating `choose`-`when`](#migrating-choose-when) * [Generators](#generators) * [Generators and ranges](#generators-and-ranges) * [Eager vs Lazy Execution](#eager-vs-lazy-execution) @@ -18,6 +19,7 @@ * [Don't Wait in Error-Handlers](#dont-wait-in-error-handlers) * [Make Static Functions Class Members](#make-static-functions-class-members) * [Initialization of Locals](#initialization-of-locals) +* [Internals Reference](fdb-coroutines-internals.md) — deep dive into the coroutine runtime (SAV, Actor, awaiters, cancellation) ## Introduction @@ -98,9 +100,36 @@ choose { } ``` -Since this is a compiler functionality, we can't use this with C++ coroutines. We could -keep only this feature around, but only using standard C++ is desirable. So instead, we -introduce a new `class` called `Choose` to achieve something very similar: +Since this is a compiler functionality, we can't use this with C++ coroutines. For most +coroutine conversions, prefer `race(...)` and then branch on the returned `std::variant`. +This keeps the control flow explicit and usually maps more directly to what the coroutine +will do after the wait. `Choose` is still available for cases where the old `choose`-`when` +shape is the clearest fit, or where you need its ordered evaluation behavior described below. + +For example, this actor pattern: + +```c++ +choose { + when(R res = wait(f1)) { + return res; + } + when(wait(timeout(...))) { + throw io_timeout(); + } +} +``` + +should usually become: + +```c++ +auto result = co_await race(f1, timeout(...)); +if (result.index() == 0) { + co_return std::get<0>(result); +} +throw io_timeout(); +``` + +`Choose` remains useful when you specifically want callback-style handling of the winner: ```c++ co_await Choose() @@ -130,7 +159,7 @@ co_await Choose() }) .When([](){ return foo() }, [](Foo const& f) { // do something else - }).Run(); + }).run(); ``` The implementation of `When` will guarantee that this lambda will only be executed if all previous @@ -195,7 +224,40 @@ loop { } ``` -However, often using `choose`-`when` (or `Choose().When`) is overkill and other facilities like `quorum` and +### Migrating `choose`-`when` + +When porting actor code, use this rule of thumb: + +* Prefer `race(...)` when the `choose` picks one winner and then the code branches on which future completed. +* Use `Choose()` when you need ordered `when` evaluation, lazy creation of later futures, or the callback style is + genuinely clearer than branching on a `std::variant`. +* Use more specific helpers like `quorum`, `waitForAll`, `waitForAllReady`, or `operator||` when they express the + intent better than either `race` or `Choose`. + +`race(...)` is usually the best direct replacement for patterns like: + +```c++ +choose { + when(R res = wait(f1)) { + return res; + } + when(S res = wait(f2)) { + return use(res); + } +} +``` + +which becomes: + +```c++ +auto result = co_await race(f1, f2); +if (result.index() == 0) { + co_return std::get<0>(result); +} +co_return use(std::get<1>(result)); +``` + +However, often using `choose`-`when` is overkill and other facilities like `quorum` and `operator||` should be used instead. For example this: ```c++ @@ -490,6 +552,22 @@ But this one is: co_await bar(Uncancellable()); ``` +## NoThrowOnCancel + +`NoThrowOnCancel` is a marker for coroutines that should still cancel, but should not run cancellation through an +`actor_cancelled` exception inside the coroutine: + +```c++ +Future foo(NoThrowOnCancel = {}) { + Resource r; + co_await something(); +} +``` + +When a coroutine with this marker is cancelled, the coroutine frame is destroyed and normal C++ RAII cleanup runs for +locals in scope. `catch` blocks inside the coroutine do not observe `actor_cancelled` for cancellation. This differs from +`Uncancellable`, where cancelling the returned future is a no-op and the coroutine continues until completion. + ## Porting `ACTOR`'s to C++ Coroutines If you have an existing `ACTOR`, you can port it to a C++ coroutine by following these steps: @@ -500,7 +578,8 @@ If you have an existing `ACTOR`, you can port it to a C++ coroutine by following 3. Remove all `state` modifiers from local variables. 4. Replace all `wait(expr)` with `co_await expr`. 5. Remove all `waitNext(expr)` with `co_await expr`. -6. Rewrite existing `choose-when` statements using the `Choose` class. +6. Rewrite existing `choose-when` statements by preferring `race(...)` for first-ready branching; use `Choose` + only when you need ordered `when` semantics or callback-style handling. In addition, the following things should be looked out for: @@ -789,17 +868,26 @@ When a function is converted from `ACTOR` to a coroutine, any forward declaratio If you also write `const&` explicitly, the generated code will contain `const& const&`, which is a compile error. ```c++ -// workloads.actor.h — WRONG: ACTOR + const& = double const& +// workloads.h — WRONG: ACTOR + const& = double const& ACTOR Future foo(Database const& cx); -// workloads.actor.h — CORRECT: remove ACTOR since foo() is now a coroutine +// workloads.h — CORRECT: remove ACTOR since foo() is now a coroutine Future foo(Database const& cx); ``` +### `DESCR` Deprecation + +The older TDMetric `DESCR` shorthand is deprecated. When a coroutine conversion touches metric event types, do not add +new `DESCR(...)`-style declarations. Instead, define an explicit payload type with a +`...Descriptor` suffix and specialize `Descriptor` with `DescribeType<...>` and `DescribeField<...>` next to it. + +This keeps descriptor types unambiguous in coroutine-converted code and matches the old TDMetric pattern in files +such as `flow/EventTypes.h`, `fdbclient/EventTypes.h`, and the workload metric definitions. + ### File Naming Converted files should be renamed from `.actor.cpp` to `.cpp` (or `.actor.h` to `.h`) since they no longer need the -actor compiler. Both `fdbserver` and `flowbench` use `fdb_find_sources()` in their `CMakeLists.txt`, which +actor compiler. Both `fdbserver` and `flow_bench` use `fdb_find_sources()` in their `CMakeLists.txt`, which automatically picks up files by glob, so the rename is usually sufficient without any CMake changes. ### Conversion Checklist @@ -811,7 +899,8 @@ automatically picks up files by glob, so the rename is usually sufficient withou - **Check**: is the variable inside a block (`if`/`else`/`for`/`try`)? If so, move it to function scope. 5. Replace `wait(expr)` with `co_await expr`. Replace `waitNext(expr)` with `co_await expr`. 6. Replace `return expr` with `co_return expr`. Replace `return Void()` with `co_return`. -7. Rewrite `choose`/`when` using the `Choose` class. +7. Rewrite `choose`/`when` by preferring `race(...)`; use `Choose` only for patterns that do not map cleanly to + `race`. 8. Simplify: `wait(success(f))` → `co_await f`; `wait(store(v, f))` → `v = co_await f`. 9. For `const&` parameters: copy to a local before the first `co_await`. 10. Remove `ACTOR` from any forward declarations of the converted functions in `.actor.h` files. @@ -931,7 +1020,7 @@ python3 ../contrib/benchmark_comparison.py - OVERALL_GEOMEAN calculations for statistical analysis **Requirements**: -- Working flowbench binary with both actor and coroutine benchmarks +- Working flow_bench binary with both actor and coroutine benchmarks - Benchmark infrastructure must include: bench_net2, coroutine_net2, bench_delay, coroutine_delay_bench, coroutine_yield_bench, bench_callback, coroutine_callback **Usage**: Tool automatically runs benchmarks and generates comparison report in the format matching historical coroutine optimization reports. diff --git a/design/data-distributor-internals.md b/design/data-distributor-internals.md index 55ec8da3068..c18423a4a7a 100644 --- a/design/data-distributor-internals.md +++ b/design/data-distributor-internals.md @@ -88,7 +88,7 @@ There are roughly 4 class of movement priorities * Boundary change priority. The movement will change current shard boundaries. * Others. Like resuming a in-flight movement. -Each shard movement has a priority associating with the move attempt, The explanation of each priority knob (`PRIORITY_`) is in `ServerKnobs.h`. +Each shard movement has a priority associating with the move attempt, The explanation of each priority knob (`PRIORITY_`) is in `Knobs.h`. In `status json` output, please look at field `.data.team_tracker.state` for team priority state. @@ -186,3 +186,807 @@ CPU utilization. This metric is in a positive relationship with “FinishedQueri * First find the TraceId fields in `BgDDMountainChopper*`, `BgDDValleyFiller*`, which indicates a relocation is triggered. * (Only when enabled) Find the `QueuedRelocation` event with the same `BeginPair` and `EndPair` as the original `TraceId`. This means the relocation request is queued. * Find the `RelocateShard` event whose `BeginPair`, `EndPair` field is the same as `TraceId`. This event means the relocation is ongoing. + + +# Alternative View -- AI-generated for FDB 7.3 + +The following was generated by an AI assistant with access to the FDB source repository, including the documentation above in this file. + +## 1. Role in the Cluster + +The Data Distributor (DD) is a singleton role recruited by the Cluster Controller. It is +the only component authorized to mutate the shard-to-server mapping stored in the system +keyspace. Its responsibilities: + +1. **Shard lifecycle management** -- splitting shards that grow too large or too hot, merging + shards that shrink below thresholds. +2. **Team formation** -- building and maintaining server teams (groups of `k` storage servers, + where `k` = replication factor) and machine teams that satisfy the replication policy. +3. **Data movement** -- relocating shards to repair replication (after server failures), to + rebalance disk/read/write load, or to carry out operational directives (exclusions, wiggle). +4. **Audit & bulk operations** -- storage audits and bulk load/dump coordination (7.3 additions). + +DD is a single point of coordination -- if it crashes, the Cluster Controller recruits a new +one which recovers state from the system keyspace. During the gap, no new data movement occurs, +but existing storage servers continue to serve reads/writes normally. + +### Singleton Guarantee: MoveKeysLock + +Only one DD instance can modify the shard map at a time, enforced by `MoveKeysLock`: + +``` +moveKeysLockOwnerKey (\xff/moveKeysLock/Owner) -- which DD owns the lock +moveKeysLockWriteKey (\xff/moveKeysLock/Write) -- which DD is actively mutating +``` + +When a new DD starts, it sets its UID as the owner. If during a `moveKeys` operation it +discovers another DD has taken ownership, it throws `movekeys_conflict` and terminates. This +prevents split-brain corruption of the shard map. + +Source: [`MoveKeys.actor.h:37`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/MoveKeys.actor.h#L37) (`MoveKeysLock`), +[`DataDistribution.actor.cpp:511`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L511) (`takeMoveKeysLock`). + + +## 2. Top-Level Actor Topology + +``` +dataDistribution() [DataDistribution.actor.cpp:889] + ├─ DataDistributor::init() -- recover state from system keyspace + ├─ resumeRelocations() -- resume in-flight data moves + │ + ├─ DataDistributionTracker::run() -- shard health monitoring + │ ├─ shardTracker (per shard) -- monitors size/bandwidth/read metrics + │ ├─ shardSplitter -- splits oversized/overloaded shards + │ ├─ shardMerger -- merges underutilized neighbors + │ └─ readHotDetection -- identifies read-hot shards + │ + ├─ DDQueue::run() -- relocation scheduling + │ ├─ dataDistributionRelocator -- per-relocation actor + │ ├─ BgDDLoadRebalance (MC disk) -- MountainChopper for disk + │ ├─ BgDDLoadRebalance (VF disk) -- ValleyFiller for disk + │ ├─ BgDDLoadRebalance (MC read) -- MountainChopper for read + │ ├─ BgDDLoadRebalance (VF read) -- ValleyFiller for read + │ └─ periodicalRefreshCounter -- refresh DDQueueServerCounter metrics + │ + ├─ DDTeamCollection::run() [primary] -- primary DC team management + │ ├─ buildTeams -- create server/machine teams + │ ├─ storageServerTracker (per SS) -- monitor SS health + │ ├─ teamTracker (per team) -- monitor team health, emit RelocateShards + │ ├─ serverTeamRemover -- prune excess server teams + │ ├─ machineTeamRemover -- prune excess machine teams + │ ├─ storageRecruiter -- recruit new storage servers + │ ├─ monitorPerpetualStorageWiggle -- perpetual wiggle orchestration + │ │ ├─ perpetualStorageWiggleIterator + │ │ └─ perpetualStorageWiggler + │ ├─ trackExcludedServers -- process exclusion list + │ └─ monitorHealthyTeams -- track healthy team count + │ + └─ DDTeamCollection::run() [remote] -- remote DC (if usableRegions > 1) + └─ (same actors as primary) +``` + +Source: [`dataDistribution()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L889). + + +## 3. Core Data Structures + +### 3.1 Storage Server Info (`TCServerInfo`) + +Defined in [`TCInfo.h`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/TCInfo.h). One per storage server tracked by DD. Contains: +- **Locality**: processID (unique to ip:port), zoneId (rack), dcId (datacenter) +- **Teams**: list of `TCTeamInfo` references this server belongs to +- **Metrics**: disk usage, CPU, storage queue length +- **Status**: [`ServerStatus`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDTeamCollection.h#L139) in `DDTeamCollection.h` tracks `isFailed`, `isUndesired`, + `isWiggling`, `isWrongConfiguration` + +A server is "healthy" if its storage engine matches the configured engine AND it is marked +as desired by DD. See `ServerStatus::isUnhealthy()` at [`DDTeamCollection.h:152`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDTeamCollection.h#L152). + +### 3.2 Machine Info (`TCMachineInfo`) + +A "machine" in FDB represents a **rack** -- the assumption is one physical host per rack. +All servers sharing a `zoneId` locality belong to the same machine. Defined in [`TCInfo.h`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/TCInfo.h). + +### 3.3 Server Teams (`TCTeamInfo`) + +A group of `k` servers (where `k` = `storageTeamSize`, typically 3) that replicate the same +shards. Implements `IDataDistributionTeam`. A team is healthy iff every server is healthy +AND the team satisfies the replication policy. + +### 3.4 Machine Teams (`TCMachineTeamInfo`) + +A group of `k` machines. Every server team must map onto a machine team (one server per +machine in the team). This ensures that server teams span distinct failure domains. + +### 3.5 Team Collection (`DDTeamCollection`) + +Defined in [`DDTeamCollection.h:205`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDTeamCollection.h#L205). The global view of all servers, server teams, machines, +and machine teams for one datacenter. Key data members: + +```cpp +std::map> server_info; // all servers +std::vector> teams; // all server teams +std::map, Reference> machine_info; +std::vector> machineTeams; +``` + +There is one `DDTeamCollection` per DC (primary + optionally remote). + +### 3.6 Shards (`DDShardInfo`) + +A shard is a contiguous key range assigned to a server team. Defined in +[`DataDistribution.actor.h:483`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistribution.actor.h#L483): + +```cpp +struct DDShardInfo { + Key key; // start key of this shard + std::vector primarySrc; // current source servers (primary DC) + std::vector remoteSrc; // current source servers (remote DC) + std::vector primaryDest; // destination servers during move + std::vector remoteDest; + bool hasDest; // true if a move is in progress + UID srcId, destId; // data move IDs +}; +``` + +Shard boundaries are stored in the system keyspace: +- `\xff/keyServers/[start_key]` → source and destination server IDs +- `\xff/serverKeys/[serverID]/[start_key]` → which shards a server owns + +### 3.7 Shard Size Bounds + +Shard size is **workload-driven**, not fixed. The maximum shard size scales with database size: + +```cpp +int64_t getMaxShardSize(double dbSizeEstimate) { + return min((MIN_SHARD_BYTES + sqrt(max(dbSizeEstimate, 0)) * SHARD_BYTES_PER_SQRT_BYTES) + * SHARD_BYTES_RATIO, MAX_SHARD_BYTES); +} +``` + +| DB Size Estimate | Max Shard Size | +|------------------|----------------| +| 0 | ~40 MB | +| 1 GB | ~46 MB | +| 100 GB | ~97 MB | +| 1 TB | ~220 MB | +| >=6.5 TB | 500 MB (cap) | + +Source: [`DataDistribution.actor.h:518`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistribution.actor.h#L518) (`ShardSizeBounds`), documented in [`design/hotshard.md`](https://github.com/apple/foundationdb/blob/release-7.3/design/hotshard.md). + +### 3.8 RelocateShard + +The fundamental unit of work for data movement. Defined in [`DataDistribution.actor.h:147`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistribution.actor.h#L147): + +```cpp +struct RelocateShard { + KeyRange keys; + int priority; + bool cancelled; + std::shared_ptr dataMove; // non-null if restoring a prior move + UID dataMoveId; + RelocateReason reason; + DataMovementReason moveReason; + UID traceId; // for lifecycle tracking +}; +``` + +### 3.9 DDSharedContext + +The shared state container connecting all DD sub-components. Defined in [`DDSharedContext.h:31`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDSharedContext.h#L31): + +```cpp +class DDSharedContext { + std::unique_ptr ddEnabledState; + DataDistributorInterface interface; + UID ddId; + MoveKeysLock lock; + DatabaseConfiguration configuration; + Reference shardsAffectedByTeamFailure; + Reference tracker; + Reference ddQueue; + Reference primaryTeamCollection, remoteTeamCollection; +}; +``` + + +## 4. Initialization Flow + +When a new DD is recruited ([`DataDistribution.actor.cpp:889`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L889)): + +1. **Open database connection** -- `openDBOnServer()` with `TaskPriority::DataDistributionLaunch` +2. **Initialize config watcher** -- `initDDConfigWatch()` establishes baseline +3. **`DataDistributor::init()`** ([line 931](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L931)) -- reads system keyspace to recover: + - Current shard boundaries from `\xff/keyServers/` + - Server-shard assignments from `\xff/serverKeys/` + - Active storage servers and their interfaces from `\xff/serverList/` + - Cluster configuration (replication mode, storage engine, etc.) + - Takes ownership of `moveKeysLock` +4. **Resume relocations** -- [`resumeRelocations()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L806) reads persisted `DataMoveMetaData` to + resume any in-flight data moves from the previous DD +5. **Start sub-components** in parallel: + - `DataDistributionTracker::run()` -- begins shard monitoring + - `DDQueue::run()` -- begins processing relocation requests + - `DDTeamCollection::run()` (primary) -- team management for primary DC + - `DDTeamCollection::run()` (remote) -- if `usableRegions > 1` + - `pollMoveKeysLock()` -- periodic lock validation + - Physical shard monitoring (if enabled) + + +## 5. Data Movement Priority System + +Every relocation request has a priority that determines scheduling order. Higher values +take precedence. The full priority table from [`ServerKnobs.cpp:155-173`](https://github.com/apple/foundationdb/blob/release-7.3/fdbclient/ServerKnobs.cpp#L155): + +| Priority | Value | DataMovementReason | Category | +|----------|-------|---------------------------------------|---------------| +| `PRIORITY_RECOVER_MOVE` | 110 | `RECOVER_MOVE` | Recovery | +| `PRIORITY_REBALANCE_UNDERUTILIZED_TEAM`| 120 | `REBALANCE_UNDERUTILIZED_TEAM` | Load Balance | +| `PRIORITY_REBALANCE_READ_UNDERUTIL_TEAM`| 121 | `REBALANCE_READ_UNDERUTIL_TEAM`| Load Balance | +| `PRIORITY_REBALANCE_OVERUTILIZED_TEAM` | 122 | `REBALANCE_OVERUTILIZED_TEAM` | Load Balance | +| `PRIORITY_REBALANCE_READ_OVERUTIL_TEAM`| 123 | `REBALANCE_READ_OVERUTIL_TEAM` | Load Balance | +| `PRIORITY_REBALANCE_STORAGE_QUEUE` | 124 | `REBALANCE_STORAGE_QUEUE` | Load Balance | +| `PRIORITY_TEAM_HEALTHY` | 140 | `TEAM_HEALTHY` | Health | +| `PRIORITY_PERPETUAL_STORAGE_WIGGLE` | 141 | `PERPETUAL_STORAGE_WIGGLE` | Maintenance | +| `PRIORITY_TEAM_CONTAINS_UNDESIRED_SERVER`| 150 | `TEAM_CONTAINS_UNDESIRED_SERVER`| Health | +| `PRIORITY_TEAM_REDUNDANT` | 200 | `TEAM_REDUNDANT` | Health | +| `PRIORITY_MERGE_SHARD` | 340 | `MERGE_SHARD` | Boundary | +| `PRIORITY_POPULATE_REGION` | 600 | `POPULATE_REGION` | Health | +| `PRIORITY_TEAM_UNHEALTHY` | 700 | `TEAM_UNHEALTHY` | Health | +| `PRIORITY_TEAM_2_LEFT` | 709 | `TEAM_2_LEFT` | Health | +| `PRIORITY_TEAM_1_LEFT` | 800 | `TEAM_1_LEFT` | Health | +| `PRIORITY_TEAM_FAILED` | 805 | `TEAM_FAILED` | Health | +| `PRIORITY_TEAM_0_LEFT` | 809 | `TEAM_0_LEFT` | Health | +| `PRIORITY_SPLIT_SHARD` | 950 | `SPLIT_SHARD` | Boundary | +| `PRIORITY_ENFORCE_MOVE_OUT_OF_PHYSICAL_SHARD` | 960 | Physical shard enforcement | Boundary | + +Priority categories from [`DDRelocationQueue.h:41-86`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDRelocationQueue.h#L41) (`RelocateData` class): +- **Health priorities**: repairs to replication factor, team health +- **Boundary priorities**: shard splits and merges +- **Load balance priorities**: disk/read/write rebalancing (lowest urgency) + +Source: [`DDRelocationQueue.actor.cpp:77-101`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L77) for the complete mapping. + + +## 6. Shard Tracker + +The [`DataDistributionTracker`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDShardTracker.h#L54) (defined in `DDShardTracker.h`) monitors every shard's +metrics and initiates splits, merges, and relocations. + +### 6.1 Shard Splitting + +Triggered when a shard exceeds size or write bandwidth thresholds: +- **Size split**: `shard.bytes > maxShardSize` → `PRIORITY_SPLIT_SHARD (950)` +- **Write split**: `bytesWrittenPerKSecond > SHARD_MAX_BYTES_PER_KSEC (1 GB/s)` + +The split process: +1. Tracker calls `splitStorageMetrics` on the storage servers hosting the shard +2. Storage servers use their sampled byte/write histograms to compute split points +3. `getSplitKey()` adds jitter to avoid picking the same boundary repeatedly +4. Tracker calls `executeShardSplit` to update the shard map and issue relocations + +Split constraints: +- Each resulting piece must have bandwidth < `SHARD_SPLIT_BYTES_PER_KSEC (250 MB/s)` +- Minimum shard size enforced: `MIN_SHARD_BYTES (10 MB by default)` +- If the shard is too small to split (e.g., single hot key), no split occurs + +Source: [`shardSplitter()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDShardTracker.actor.cpp#L852), [`StorageMetrics.actor.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/StorageMetrics.actor.cpp). + +### 6.2 Shard Merging + +Triggered when a shard's write bandwidth drops below `SHARD_MIN_BYTES_PER_KSEC (100 MB/s)` +and its size is small enough that merging with a neighbor stays under the max shard size. +Priority: `PRIORITY_MERGE_SHARD (340)`. + +### 6.3 Read-Hot Shard Detection + +When `READ_SAMPLING_ENABLED=true`, storage servers sample read operations. Shards exceeding +`SHARD_MAX_READ_OPS_PER_KSEC (45k ops/s)` or having high read density ratios are flagged. +The tracker emits `ReadHotRangeLog` trace events. Read-hot shards trigger **rebalance moves** +(not splits) -- DD moves the shard to a less-loaded team. + +Source: [`StorageMetrics.actor.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/StorageMetrics.actor.cpp), [`design/hotshard.md`](https://github.com/apple/foundationdb/blob/release-7.3/design/hotshard.md). + + +## 7. Relocation Queue (DDQueue) + +[`DDQueue`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDRelocationQueue.h#L113) (defined in `DDRelocationQueue.h`) is the scheduler for all data movements. +It receives `RelocateShard` requests from the tracker and team collection, prioritizes them, +selects destination teams, and launches the actual data transfers. + +### 7.1 Queue Data Structures + +```cpp +KeyRangeMap queueMap; // all queued relocations +std::set fetchingSourcesQueue; // fetching source info +std::set fetchKeysComplete; // ready to launch +std::map> queue; // per-server queue +KeyRangeMap inFlight; // actively moving +std::map busymap; // source server busyness +std::map destBusymap; // dest server busyness +std::map priority_relocations; // count by priority +``` + +Source: [`DDRelocationQueue.h:113-307`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDRelocationQueue.h#L113). + +### 7.2 Throttling + +The [`Busyness`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DDRelocationQueue.h#L86) struct tracks per-server load across 10 priority +buckets. Before launching a relocation, DDQueue checks: +- Source server busyness: `RELOCATION_PARALLELISM_PER_SOURCE_SERVER` +- Dest server busyness: `RELOCATION_PARALLELISM_PER_DEST_SERVER` +- Total move keys parallelism: `DD_MOVE_KEYS_PARALLELISM` + +Inflight penalties increase apparent busyness based on relocation health status: +- `INFLIGHT_PENALTY_HEALTHY`, `INFLIGHT_PENALTY_REDUNDANT`, `INFLIGHT_PENALTY_UNHEALTHY`, + `INFLIGHT_PENALTY_ONE_LEFT` + +### 7.3 Relocation Lifecycle + +Each relocation goes through: +1. **Queued** → `queueRelocation()` -- overlapping in-flight moves are cancelled +2. **Fetch sources** → `fetchSourceServersComplete` -- identify current shard owners +3. **Team selection** → `getSrcDestTeams()` → `GetTeamRequest` to `DDTeamCollection` +4. **Launch** → [`dataDistributionRelocator()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L1302) +5. **Move keys** → `moveKeys()` in [`MoveKeys.actor.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L3180) -- the actual data transfer: + - Add destination as new shard owner (private mutation to `keyServers`/`serverKeys`) + - Destination SS reads data from sources via FDB transactions + - Remove source from shard ownership + - Update `ShardsAffectedByTeamFailure` +6. **Complete** → `relocationComplete` stream + + +## 8. Load Rebalancing: MountainChopper & ValleyFiller + +DD continuously rebalances load using two complementary algorithms implemented in a single +actor, [`BgDDLoadRebalance`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L2281): + +### MountainChopper (Overutilized → Underutilized) + +- **Source selection**: `TeamSelect::WANT_TRUE_BEST` -- picks the most loaded team +- **Dest selection**: `TeamSelect::ANY` with `PreferLowerDiskUtil::True` +- **DataMovementReason**: `REBALANCE_OVERUTILIZED_TEAM` (disk) or `REBALANCE_READ_OVERUTIL_TEAM` (read) +- For read rebalancing, calls `rebalanceReadLoad()` which selects top-K shards by read density + +### ValleyFiller (Fill Underutilized Teams) + +- **Source selection**: `TeamSelect::ANY` -- picks a random loaded team +- **Dest selection**: `TeamSelect::WANT_TRUE_BEST` with `PreferLowerDiskUtil::True` -- picks + the least loaded team +- **DataMovementReason**: `REBALANCE_UNDERUTILIZED_TEAM` (disk) or `REBALANCE_READ_UNDERUTIL_TEAM` (read) + +### Rebalancing Controls + +- Polling interval: `BG_REBALANCE_POLLING_INTERVAL` (adaptive, backed off on repeated no-ops) +- Parallelism cap: `DD_REBALANCE_PARALLELISM` -- max concurrent rebalance moves per priority +- Can be disabled at runtime via special database key (checked via `getSkipRebalanceValue()`) +- Read rebalance CPU threshold: `READ_REBALANCE_CPU_THRESHOLD = 0.15` (source team worst CPU) +- Read rebalance shard selection: top-K by `READ_DENSITY = READ_BYTES_PER_KSEC / SHARD_BYTES` + with constraint `LOAD(shard) < (LOAD(src) - LOAD(dest)) * READ_REBALANCE_MAX_SHARD_FRAC` + +Source: [`BgDDLoadRebalance`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L2281), +[`isDataMovementForMountainChopper`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L58), +[`isDataMovementForValleyFiller`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDRelocationQueue.actor.cpp#L69). + + +## 9. Team Building + +The team builder ([`buildTeams`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDTeamCollection.actor.cpp#L706) in `DDTeamCollection`) is invoked when: +- A new server is added +- A server is removed +- Zero teams exist + +The algorithm: +1. **Build machine teams** using `addBestMachineTeams()`: + - Uses `ReplicationPolicy::selectReplicas()` to find machine combinations + - Target count: `DESIRED_TEAMS_PER_SERVER * serverCount / teamSize` +2. **Build server teams** using `addTeamsBestOf()`: + - For each machine team, pick one server from each machine + - Prefer the server with the fewest existing teams (`findOneLeastUsedServer`) + - Verify the team satisfies the replication policy + +### Team Health Monitoring + +**[`teamTracker`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DDTeamCollection.actor.cpp#L860)** (per team): When a team's health changes, it evaluates: +- If all servers healthy → no action +- If a server is undesired → `PRIORITY_TEAM_CONTAINS_UNDESIRED_SERVER (150)` +- If team is redundant → `PRIORITY_TEAM_REDUNDANT (200)` +- If team is unhealthy → `PRIORITY_TEAM_UNHEALTHY (700)` +- If only 2 replicas left → `PRIORITY_TEAM_2_LEFT (709)` +- If only 1 replica left → `PRIORITY_TEAM_1_LEFT (800)` +- If team failed → `PRIORITY_TEAM_FAILED (805)` +- If 0 replicas left → `PRIORITY_TEAM_0_LEFT (809)` + +The priority escalation drives DDQueue to process the most critical relocations first. + +**`storageServerTracker`** (per SS): Monitors each server's health, storage engine type, +locality validity. When a server fails, it triggers data moves off all teams containing +that server. + +### Team Pruning + +- `serverTeamRemover`: periodically removes the server team whose members are on the most + teams when total team count > desired. Threshold: `TR_REDUNDANT_TEAM_PERCENTAGE_THRESHOLD`. +- `machineTeamRemover`: removes machine teams with the most overlapping members when count + exceeds desired. + + +## 10. Perpetual Storage Wiggle + +The "wiggle" gradually cycles data off each storage server in turn, enabling rolling +maintenance and ensuring no server holds stale data indefinitely. + +### Components + +- **[`StorageWiggler`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistribution.actor.h#L563)**: Maintains a min-heap priority queue + of servers ordered by `StorageMetadataType` (creation time). Servers with the oldest metadata + are wiggled first. +- **`perpetualStorageWiggleIterator`**: Selects the next server to wiggle and writes its ID to + `perpetualStorageWiggleIDPrefix` in the system keyspace. +- **`perpetualStorageWiggler`**: Watches the wiggle ID key, excludes the target server (causing + DD to move data off it), waits for completion, then includes it again and signals the iterator. +- **`clusterHealthCheckForPerpetualWiggle`**: Pauses wiggling if the cluster becomes unhealthy. + +### Configuration + +- Enable: `configure perpetual_storage_wiggle=1` +- Target specific locality: `configure perpetual_storage_wiggle_locality=:` +- Wiggle pause threshold: `DD_STORAGE_WIGGLE_PAUSE_THRESHOLD` +- Wiggle stuck threshold: `DD_STORAGE_WIGGLE_STUCK_THRESHOLD` +- Min bytes balance ratio: `PERPETUAL_WIGGLE_MIN_BYTES_BALANCE_RATIO` +- Min SS age before wiggle: `DD_STORAGE_WIGGLE_MIN_SS_AGE_SEC` + +### Wiggle States + +From `StorageWiggler::State`: +- `INVALID (0)`: Not initialized +- `RUN (1)`: Actively wiggling +- `PAUSE (2)`: Paused due to cluster health + +The wiggler state and timestamp are queryable via `GetStorageWigglerStateRequest` RPC. + + +## 11. Physical Shard Collection (Experimental) + +[`PhysicalShardCollection`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistribution.actor.h#L277) introduces a higher-level grouping +where multiple logical shards (key ranges) share the same server team. This reduces metadata +overhead and enables more efficient bulk moves. + +Key concepts: +- A **physical shard** contains one or more key ranges (logical shards) on the same team +- Two-step team selection: (1) select a physical shard for the primary team, (2) find the + remote team sharing that physical shard +- Physical shards track aggregate metrics and can be oversized, triggering + `PRIORITY_ENFORCE_MOVE_OUT_OF_PHYSICAL_SHARD (960)` moves + +Enabled via: `SHARD_ENCODE_LOCATION_METADATA=true` + `ENABLE_DD_PHYSICAL_SHARD=true` + +When DD restarts, all key ranges start in the "anonymous" physical shard and are gradually +transitioned out as data moves occur. + + +## 12. Multi-Datacenter Architecture + +With `usableRegions > 1`, DD maintains two `DDTeamCollection` instances: + +1. **Primary Team Collection** -- manages teams in `primaryDcId` +2. **Remote Team Collection** -- manages teams in `remoteDcIds` + +The remote collection only starts after `remoteRecovered()` signals that the remote region +has completed recovery. + +Every shard requires a team in both DCs. The DDQueue coordinates +relocations across both DCs -- when selecting destination teams, it requests teams from +both collections and ensures consistency. + +`ShardsAffectedByTeamFailure` tracks teams with a `primary` flag to distinguish: +```cpp +struct Team { + std::vector servers; + bool primary; +}; +``` + + +## 13. Storage Server Recruitment + +`storageRecruiter` in `DDTeamCollection` handles recruiting new storage servers: +- Sends `RecruitStorageRequest` to the Cluster Controller +- Validates the candidate's locality against the replication policy +- Optionally pairs with a Testing Storage Server (TSS) via `TSSPairState` +- Calls `initializeStorage()` to register the new SS + +The recruiter is debounced via `restartRecruiting` to avoid thundering herd during +rapid server changes. + + +## 14. Shard Map Metadata Lifecycle: `keyServers` and `serverKeys` + +The shard-to-server mapping is the most critical metadata in the cluster. It is stored as +two complementary indexes in the system keyspace and flows through every major component. + +### 14.1 Schema + +**`keyServers`** (`\xff/keyServers/` prefix, defined in [`SystemData.h`](https://github.com/apple/foundationdb/blob/release-7.3/fdbclient/include/fdbclient/SystemData.h)): + +``` +\xff/keyServers/[begin_key] → encoded(src_UIDs[], dest_UIDs[]) +``` + +Maps each shard's start key to its current source servers (who own the data) and destination +servers (who are receiving the data during a move). When no move is in progress, `dest` is +empty. The encoding supports both UID-based and Tag-based formats, plus optional shard IDs +when `SHARD_ENCODE_LOCATION_METADATA` is enabled. + +Key encoding/decoding functions in [`SystemData.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbclient/SystemData.cpp): +- `keyServersKey(k)` -- constructs the key +- `keyServersValue(UIDtoTagMap, src, dest)` -- encodes src/dest with tags +- `decodeKeyServersValue(serverTagMap, value, src, dest)` -- decodes + +**`serverKeys`** (`\xff/serverKeys/` prefix, defined in [`SystemData.h`](https://github.com/apple/foundationdb/blob/release-7.3/fdbclient/include/fdbclient/SystemData.h)): + +``` +\xff/serverKeys/[serverID]/[begin_key] → serverKeysTrue | serverKeysFalse +``` + +The inverse index: for each storage server, records which shard ranges it owns. +`serverKeysTrue` means the server owns that range; `serverKeysFalse` means it doesn't. +When `SHARD_ENCODE_LOCATION_METADATA` is enabled, the value also encodes the `DataMoveType`, +`DataMoveId`, and `DataMovementReason`. + +Key functions in [`SystemData.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbclient/SystemData.cpp): +- `serverKeysPrefixFor(serverID)` -- prefix for all shards on a server +- `serverKeysKey(serverID, key)` -- specific shard assignment key +- `decodeServerKeysValue(value, nowAssigned, emptyRange, type, id, reason)` -- decodes + +### 14.2 Bootstrap: `seedShardServers` + +At initial database creation, the master calls [`seedShardServers()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L3202) +to write the very first shard map: + +```cpp +// 1. Set keyServers: all initial servers own the entire keyspace +auto ksValue = keyServersValue(serverTags, serverSrcUID); +tr.set(arena, keyServersKey(allKeys.begin), ksValue); // \xff/keyServers/\x00 +tr.set(arena, keyServersKey(allKeys.end), ...); // \xff/keyServers/\xff + +// 2. Set serverKeys: each server owns everything +for (auto& s : servers) { + tr.set(arena, serverKeysKey(s.id(), allKeys.begin), serverKeysTrue); +} +``` + +This is committed as the very first transaction (`read_snapshot = 0`), establishing the +invariant that every key in the database is assigned to a team. + +### 14.3 The moveKeys Protocol in Detail + +The data transfer protocol is implemented in [`MoveKeys.actor.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp) and consists of three +coordinated phases. The [`moveKeys()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L3180) function orchestrates them: + +```cpp +wait(rawStartMovement(occ, params, tssMapping)); // Phase 1: startMoveKeys +wait(rawCheckFetchingState(occ, params, ...)); // Phase 2: monitor data copy +wait(rawFinishMovement(occ, params, tssMapping)); // Phase 3: finishMoveKeys +``` + +#### Phase 1: `startMoveKeys` ([`MoveKeys.actor.cpp:924`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L924)) + +This phase sets up the destination servers and tells them to begin fetching data. + +1. **Acquire lock**: `checkMoveKeysLock()` verifies this DD still owns the lock +2. **Read existing shard map**: `krmGetRanges(tr, keyServersPrefix, keys)` fetches all + current shard assignments overlapping the move range +3. **Validate destinations**: confirms each destination server exists in `serverList`; + throws `move_to_removed_server` if any are gone +4. **For each overlapping sub-shard**: + - Decode existing `src` and `dest` from `keyServers` + - **Write new `keyServers`**: [`krmSetPreviouslyEmptyRange`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1044) sets `dest` to the new destination team while + preserving `src` + - Track old destinations that need cleanup +5. **Remove stale destination entries**: for old `dest` servers no longer needed, + `removeOldDestinations()` sets their `serverKeys` to `serverKeysFalse` +6. **Write new `serverKeys` for destinations**: [`krmSetRangeCoalescing`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1082) for each destination server +7. **Commit transaction** + +This transaction may be split into multiple batches if there are many overlapping sub-shards +(`MOVE_KEYS_KRM_LIMIT` controls batch size). Each batch advances the `begin` cursor. + +**Important**: at this point, `keyServers[range]` has both `src` (old team) and `dest` +(new team). Both teams serve reads for this shard. The commit of this transaction triggers +the private mutation propagation described in section 14.4. + +#### Phase 2: Data Copy (Storage Server Fetch) + +After the `startMoveKeys` transaction commits, it propagates as a private mutation through +the transaction system (see 14.4). The destination storage servers receive the mutation and +begin `fetchKeys` -- reading the shard data from source servers via standard FDB read +transactions. DD monitors this via `rawCheckFetchingState()` which polls the destination +servers until they report the shard is ready. + +#### Phase 3: `finishMoveKeys` ([`MoveKeys.actor.cpp:1229`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1229)) + +Once all destinations have the data, DD atomically swaps ownership: + +1. **Read and validate**: reads `keyServers` for the range, decodes `src` and `dest`, + verifies `dest` matches expected servers +2. **Check readiness**: for each destination server, checks if the shard is fetched and ready +3. **Atomic swap** ([line 1519-1524](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1519)): + ```cpp + // Update keyServers: src = old_dest, dest = [] (move complete) + krmSetRangeCoalescing(&tr, keyServersPrefix, currentKeys, keys, + keyServersValue(UIDtoTagMap, dest)); // dest becomes new src + + // Update serverKeys for ALL involved servers + for (server in allServers) { + bool destHasServer = (server in dest); + krmSetRangeCoalescing(&tr, serverKeysPrefixFor(server), currentKeys, + allKeys, destHasServer ? serverKeysTrue : serverKeysFalse); + } + ``` +4. **Commit** + +After this commit, the old source servers are no longer owners. The private mutation +propagation tells them to drop the shard data. + +**Retry handling**: on transaction conflict, `finishMoveKeys` retries with +exponential backoff. After every 10 retries ([line 1551](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1551)) it emits a `RelocateShard_FinishMoveKeysRetrying` +warning; at 20 retries it escalates to `SevWarnAlways`. + +### 14.4 Private Mutation Propagation + +When `keyServers` or `serverKeys` mutations commit, they are classified as **private +mutations** and receive special treatment at every layer of the transaction system. + +#### At the Commit Proxy ([`ApplyMetadataMutation.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/ApplyMetadataMutation.cpp)) + +**[`checkSetKeyServersPrefix`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/ApplyMetadataMutation.cpp#L207)** (line 207): When the proxy sees a mutation to +`\xff/keyServers/`: +1. Stores the mutation in the local `txnStateStore` -- this is the proxy's + in-memory replica of the shard map +2. Decodes `src` and `dest` UIDs +3. Looks up each UID's `Tag` from the `storageCache` +4. Updates `keyInfo` (the `ServerCacheInfo` map) with the combined tag set +5. This updated `keyInfo` is used immediately for **routing subsequent mutations** -- any + user transaction touching this key range will now be tagged for both `src` and `dest` + servers + +**[`checkSetServerKeysPrefix`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/ApplyMetadataMutation.cpp#L258)** (line 258): When the proxy sees a mutation to +`\xff/serverKeys/`: +1. Looks up the target server's `Tag` (line 263: [`if (toCommit)`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/ApplyMetadataMutation.cpp#L263) guard) +2. **Privatizes** the mutation by prepending `systemKeys.begin` to `param1` +3. Adds the server's tag to the commit batch: `toCommit->addTag(tag)` ([line 277](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/ApplyMetadataMutation.cpp#L277)) +4. Writes the privatized mutation + +This ensures the `serverKeys` change is streamed only to the specific storage server it +affects (via the tag-based routing through tLogs). + +#### At the tLog + +The tLog receives the tagged mutations and stores them in the appropriate tag-specific +queue. Storage servers pull mutations by their tag, so each SS only sees `serverKeys` +mutations addressed to it. + +#### At the Storage Server ([`storageserver.actor.cpp`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/storageserver.actor.cpp)) + +[`applyPrivateData()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/storageserver.actor.cpp#L10435) processes incoming `serverKeys` mutations in pairs (begin, end): + +**First mutation**: Decodes the start key and metadata: +```cpp +decodeServerKeysValue(m.param2, nowAssigned, emptyRange, dataMoveType, dataMoveId, dataMoveReason); +``` +This extracts: whether the server is now assigned this range, whether it's an empty-range +optimization, the `DataMoveType` (LOGICAL, PHYSICAL, PHYSICAL_BULKLOAD), and the move reason. + +**Second mutation**: Forms the key range and applies the change: +- `setAssignedStatus(data, keys, nowAssigned)` -- updates the SS's in-memory shard map +- For shard-aware SS: [`changeServerKeysWithPhysicalShards()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/storageserver.actor.cpp#L8852) -- handles physical shard moves +- For non-shard-aware SS: [`changeServerKeys()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/storageserver.actor.cpp#L9803) -- triggers `fetchKeys` actor if newly assigned, + or drops data if unassigned + +If the SS is **newly assigned** this range (`nowAssigned = true`), `changeServerKeys` spawns +a `fetchKeys` actor that reads all data from the source servers and writes it into local +storage. Once complete, the shard transitions from FETCHING to READABLE. + +If the SS is **unassigned** from this range (`nowAssigned = false`), the data is removed +from the SS's versioned data. + +### 14.5 Recovery + +During cluster recovery, the `txnStateStore` is reconstructed from tLog data before any +new transactions can proceed. This means `keyServers` and `serverKeys` are recovered to +their last committed state. The new commit proxies replay all metadata mutations, rebuilding +their `keyInfo` routing tables. When DD starts on the new cluster controller, `init()` reads +the recovered `keyServers` and `serverKeys` to reconstruct its in-memory shard map, teams, +and any in-flight data moves. + +### 14.6 Consistency Model + +The use of FDB's own transaction system for shard map mutations provides strong guarantees: + +1. **Serializability**: all shard map changes are serialized through the resolver, so + concurrent moves to overlapping ranges are detected and one is retried +2. **Atomicity**: `finishMoveKeys` atomically updates both `keyServers` and all affected + `serverKeys` entries in a single transaction +3. **Consistency across proxies**: because metadata mutations update `txnStateStore` on + every commit proxy in the same total order, all proxies have an identical view of which + servers own which shards +4. **Durability**: mutations are persisted in tLogs before acknowledgment + +The cost of this elegance is coupling: shard map mutations compete with user transactions +for commit proxy bandwidth and resolver cycles. During heavy data movement, this can +measurably impact user commit latencies. + +### 14.7 Commented-Out Trace Events in moveKeys + +Notably, `startMoveKeys` contains several **commented-out** trace events ([lines 1009, 1031, 1054, 1060](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L1009)) that would log per-sub-shard details during the move: + +```cpp +// TraceEvent("StartMoveKeysOldRange", relocationIntervalId) +// .detail("KeyBegin", rangeIntersectKeys.begin.toString()) +// .detail("KeyEnd", rangeIntersectKeys.end.toString()) +// .detail("OldSrc", describe(src)) +// .detail("OldDest", describe(dest)) +// .detail("ReadVersion", tr->getReadVersion().get()); +``` + +These were likely disabled due to volume concerns, but their absence creates an observability +gap that we address in the recommendations document. + + +## 15. The High-Level moveKeys Orchestration + +Wrapping the protocol above, [`moveKeys()`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/MoveKeys.actor.cpp#L3180) orchestrates +the three phases: + +```cpp +ACTOR Future moveKeys(Database occ, MoveKeysParams params) { + wait(rawStartMovement(occ, params, tssMapping)); // startMoveKeys + state Future completionSignaller = + rawCheckFetchingState(occ, params, tssMapping); // monitor fetch progress + wait(rawFinishMovement(occ, params, tssMapping)); // finishMoveKeys + completionSignaller.cancel(); + if (!params.dataMovementComplete.isSet()) + params.dataMovementComplete.send(Void()); + return Void(); +} +``` + +The `dataMovementComplete` promise signals the relocation queue that the move has finished, +allowing it to update `ShardsAffectedByTeamFailure` and complete the relocation lifecycle. + + +## 16. DD RPC Interface + +[`DataDistributorInterface`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/include/fdbserver/DataDistributorInterface.h#L30) exposes these RPCs: + +| RPC | Purpose | +|-----|---------| +| `waitFailure` | Cluster Controller liveness check | +| `haltDataDistributor` | Graceful shutdown | +| `distributorSnapReq` | Consistent snapshot coordination | +| `distributorExclCheckReq` | Safety check before server exclusion | +| `dataDistributorMetrics` | Query shard metrics | +| `distributorSplitRange` | External split point insertion | +| `storageWigglerState` | Query wiggle status | +| `triggerAudit` | Trigger/cancel storage audit | + + +## 17. Error Handling & Recovery + +Normal DD errors that trigger re-recruitment (not cluster-wide failure): +```cpp +{error_code_movekeys_conflict, error_code_broken_promise, + error_code_data_move_cancelled, error_code_data_move_dest_team_not_found} +``` + +Source: [`DataDistribution.actor.cpp:305`](https://github.com/apple/foundationdb/blob/release-7.3/fdbserver/DataDistribution.actor.cpp#L305). + +When DD encounters a `movekeys_conflict` (another DD took the lock), it terminates and the +Cluster Controller recruits a fresh instance. The new DD re-reads all state from the system +keyspace, so no data is lost -- only in-flight moves may need to be re-evaluated. + +`DDEnabledState` tracks the overall mode: +- `ENABLED` -- normal operation +- `SNAPSHOT` -- DD paused for consistent snapshot +- `BLOB_RESTORE_PREPARING` -- DD paused for blob restore preparation diff --git a/design/fdb-coroutines-internals.md b/design/fdb-coroutines-internals.md new file mode 100644 index 00000000000..83f1da78287 --- /dev/null +++ b/design/fdb-coroutines-internals.md @@ -0,0 +1,727 @@ +# How C++ Coroutines Work in FoundationDB + +*Caveat: work in progress. This mentions code not yet submitted. Accuracy is believed to be good, +but ongoing work to increase clarity is needed.* + +This document explains the coroutine infrastructure in FoundationDB, from the +C++20 standard machinery down to FDB-specific implementation details. + +## C++20 Coroutine Fundamentals + +Any function whose body contains `co_await`, `co_return`, or `co_yield` is a +coroutine. The compiler rewrites it into a state machine. The programmer does +**not** call the coroutine machinery directly — the compiler generates all the +calls. The programmer's job is to implement the **policy** types that the +compiler calls into. + +### What the compiler generates + +Given: + +```cpp +Future foo(int x) { + int y = co_await bar(); + co_return y + 1; +} +``` + +The compiler emits roughly: + +```cpp +Future foo(int x) { + // 1. Look up the promise type via coroutine_traits, int> + using promise_type = std::coroutine_traits, int>::promise_type; + + // 2. Allocate the coroutine frame on the heap + // (calls promise_type::operator new if defined) + // The frame holds: the promise object, all local variables, + // bookkeeping for each suspend point, and the function arguments. + + // 3. Construct the promise + promise_type promise; + + // 4. Get the return object — this is what the caller receives. + // Called BEFORE the body executes. + Future retval = promise.get_return_object(); + + // 5. co_await promise.initial_suspend() + // FDB returns suspend_never → body starts immediately. + + try { + // === user's body === + + // co_await bar() expands to: + auto&& awaitable = promise.await_transform(bar()); + if (!awaitable.await_ready()) { + awaitable.await_suspend(handle_to_this_coroutine); + // <-- coroutine suspends here, control returns to caller --> + // <-- later: something calls handle.resume() --> + } + int y = awaitable.await_resume(); + + // co_return y + 1 expands to: + promise.return_value(y + 1); + goto final_suspend; + + } catch (...) { + promise.unhandled_exception(); + } + +final_suspend: + // 6. co_await promise.final_suspend() + // The compiler generates the same await_ready / await_suspend / + // await_resume sequence here. + + return retval; // Already returned to caller at step 4/5 +} +``` + +Key insight: `retval` is returned to the caller when the coroutine first +suspends (or completes without suspending). The caller never sees the +`co_return` value directly — it goes through `promise.return_value()` into +whatever result cell the promise manages. + +### The coroutine frame + +The compiler heap-allocates a "coroutine frame" containing: + +- The promise object +- Copies of all function parameters +- All local variables (live across any suspend point) +- Internal state for tracking which suspend point the coroutine is at +- Awaiter objects for each `co_await` (since they must survive suspension) + +FDB overrides `operator new`/`operator delete` on promise types to use its +`allocateFast`/`freeFast` allocator. + +A typical Clang-generated frame layout: + +``` ++0 void (*__resume)(void*) // resume function pointer ++8 void (*__destroy)(void*) // destroy function pointer ++16 promise_type promise // in FDB, contains the CoroActor / SAV ++?? uint32_t __suspend_index // which suspend point we're at ++?? bool __initial_await_resume_called ++?? // copies of function arguments ++?? // variables live across suspend points ++?? // one per co_await, alive during suspension ++?? +``` + +The layout is compiler-generated and not standardized. Everything that is live +across a suspend point must be stored in the frame. Typical FDB coroutines +range from ~200 bytes to over 1KB depending on the number of locals and +`co_await` points. + +**Parameter storage caveat:** The frame stores copies of all function +parameters. For `const&` parameters, the frame stores the *reference*, not a +copy of the referent. If the caller passed a temporary, it will be destroyed +before the coroutine resumes — leaving a dangling reference in the frame. +Always copy `const&` parameters to a local before the first `co_await`. + +The compiler splits the original function into three pieces: + +1. **Ramp function** — the entry point the caller invokes. Allocates the frame, + constructs the promise, calls `get_return_object()` and + `initial_suspend()`, starts the body, and returns `retval` to the caller + when the coroutine first suspends. +2. **Resume function** — called via `handle.resume()`. Jumps to the current + suspend point (via a switch on the saved index) and continues execution. +3. **Destroy function** — called via `handle.destroy()`. Jumps to the current + suspend point and runs destructors for in-scope locals, then deallocates. + +### `coroutine_handle<>` + +A single pointer to the coroutine frame — **8 bytes**. The resume and destroy +function pointers are stored in the frame itself (see layout above), not in +the handle. Calling `handle.resume()` dereferences the frame pointer to find +the resume function pointer, then makes an indirect call. + +`coroutine_handle

` (typed) and `coroutine_handle` (erased) are the +same size. The typed version adds a `promise()` accessor that returns a +reference to the promise at a compiler-known offset in the frame. They are +freely interconvertible. + +The compiler passes the handle to `await_suspend`. Key operations: + +- `handle.resume()` — resume the coroutine at its current suspend point +- `handle.destroy()` — destroy the coroutine frame (runs destructors for + in-scope locals, deallocates) +- `handle.done()` — true if the coroutine is at its final suspend point + +### `await_suspend` return types + +| Return type | Meaning | +|---|---| +| `void` | Always suspend | +| `bool` | `true` → suspend; `false` → don't suspend (resume immediately) | +| `coroutine_handle` | Symmetric transfer: suspend this coroutine, resume that one | + +### `final_suspend` rules + +- The expression `co_await promise.final_suspend()` **must not throw** + (compiler-enforced). Mark `final_suspend()` and the awaiter's methods + `noexcept`. +- If `await_suspend` returns `void` or `true`: the coroutine is suspended at + the final suspend point. The frame stays alive until someone calls + `handle.destroy()`. Calling `handle.resume()` on a coroutine suspended at + its final suspend point is **undefined behavior**. +- If `await_suspend` returns `false`: the coroutine was never suspended. The + runtime auto-destroys the frame after `await_resume()` completes. This is + well-defined. + +### `promise_type` required members + +| Method | Required? | Purpose | +|---|---|---| +| `get_return_object()` | Yes | Create the value returned to the caller | +| `initial_suspend()` | Yes | Controls whether body starts immediately | +| `final_suspend()` | Yes, `noexcept` | Controls frame lifetime after body completes | +| `unhandled_exception()` | Yes | Called if an exception escapes the body | +| `return_value(T)` or `return_void()` | Exactly one | Stores the co_return value | +| `await_transform(U)` | Optional | Intercepts every `co_await` expression | +| `yield_value(T)` | Optional | Enables `co_yield` | +| `operator new` / `operator delete` | Optional | Custom frame allocation | + +--- + +## FDB's Type Hierarchy + +### `Callback` — intrusive linked list node + +``` +flow/include/flow/flow.h:468 +``` + +A doubly-linked list node with virtual `fire(T const&)`, `fire(T&&)`, +`error(Error)`, and `unwait()`. When a `SAV` has no value yet, it serves as +the sentinel of a circular list of waiting callbacks. + +When a callback is `remove()`d and it was the last one, `unwait()` is called on +the sentinel (the SAV), which decrements the future refcount. + +### `SAV` — Single Assignment Variable + +``` +flow/include/flow/flow.h:732 +``` + +The core result cell. Inherits privately from `Callback` (to serve as the +list sentinel) and `FastAllocated>`. + +**Two independent refcounts:** + +- `promises` — one per `Promise` pointing at it, plus one for an active + actor (the producer side) +- `futures` — one per `Future` pointing at it, plus one if any callbacks + are registered + +**State machine** via `error_state.code()`: + +| Code | Constant | Meaning | +|---|---|---| +| -3 | `UNSET_ERROR_CODE` | Not yet assigned. `canBeSet()` is true. | +| -2 | `NEVER_ERROR_CODE` | Will never complete (set to `Never`). | +| -1 | `SET_ERROR_CODE` | Successfully set with a value. | +| > 0 | (error code) | Set with an error. | + +**Lifecycle rules:** + +- `delPromiseRef()`: when the last promise ref is released (promises is 1) and + the SAV is still unset, sends `broken_promise()` to any waiting callbacks. + Then sets promises to 0. If futures is also 0, calls `destroy()`. +- `delFutureRef()`: if futures drops to 0 and promises > 0, calls `cancel()` + (this is how dropping the last Future cancels an actor). If promises is also + 0, calls `destroy()`. +- `destroy()` and `cancel()` are virtual — subclasses override them. + +**Callback management:** `addCallbackAndDelFutureRef(cb)` transfers a future +reference into the callback list. If this is the first callback, the future +count stays the same (the "+1 for having callbacks" replaces the future ref). +If callbacks already exist, the future count is decremented. + +### `Actor` — actor base class + +``` +flow/include/flow/flow.h:1500 +``` + +Inherits from `SAV`. Adds one field: + +```cpp +int8_t actor_wait_state; +``` + +| Value | Constant | Meaning | +|---|---|---| +| > 0 | `ACTOR_WAIT_STATE_WAITING` (1) | Coroutine is suspended at a `co_await`; a callback is registered on some Future's SAV that will call `handle.resume()` when fired | +| 0 | `ACTOR_WAIT_STATE_NOT_WAITING` | Coroutine is running (between `co_await` points) or has completed | +| -1 | `ACTOR_WAIT_STATE_CANCELLED` | Cancellation requested | +| -2 | `ACTOR_WAIT_STATE_CANCELLED_DURING_READY_CHECK` | Cancellation detected during `await_ready()` before the awaiter registered a callback | + +In actor-compiler-generated code, values >1 identify *which* callback group +fired (the generated state machine uses the value to jump to the right +continuation). For C++ coroutines this distinction is unnecessary — the +`coroutine_handle` already encodes the suspend point — so the value is +always 1. + +Constructed with `SAV(1, 1)`: starts with futures=1 and promises=1. The +futures=1 is consumed by the `Future` returned from `get_return_object()`. +The promises=1 represents the actor itself as the producer. + +`Actor` is a separate specialization with no SAV base — for +fire-and-forget actors. + +### `Promise` and `Future` + +``` +flow/include/flow/flow.h:1091, 982 +``` + +`Promise` wraps a `SAV*`. Constructor creates `SAV(0, 1)` — zero +futures, one promise. `getFuture()` calls `addFutureRef()` then returns +`Future(sav)`. + +`Future` wraps a `SAV*` — **8 bytes**. Copy constructor calls +`addFutureRef()`. Destructor calls `delFutureRef()`. The explicit constructor +`Future(SAV* sav)` does **not** increment the refcount — it assumes +the count is already correct (used by `get_return_object()`). + +--- + +## FDB's Coroutine Promise: `CoroPromise` + +``` +flow/include/flow/CoroutinesImpl.h +``` + +### `coroutine_traits` specialization + +``` +flow/include/flow/Coroutines.h +``` + +```cpp +template +struct coroutine_traits, Args...> { + using promise_type = coro::CoroPromise, + coro::hasExplicitVoid, + coro::hasNoThrowOnCancel>; +}; +``` + +The `Args...` includes all function parameter types. Marker types like +`Uncancellable`, `NoThrowOnCancel`, and `ExplicitVoid` are detected via +template metaprogramming on the parameter list. They are always defaulted +parameters (`NoThrowOnCancel = {}`) so callers don't pass them. + +### `CoroActor` + +``` +flow/include/flow/CoroutinesImpl.h:124 +``` + +Inherits from `Actor`. Embedded **inside** the coroutine frame (the promise +holds it as a member). This means the frame allocation also allocates the +SAV — a single allocation for both. + +Adds a `coroutine_handle<>` and overrides: + +- `cancel()`: sets `actor_wait_state = CANCELLED`, then resumes the coroutine + if it was waiting. The resumed coroutine sees the cancelled state in + `resumeImpl()` and throws `actor_cancelled()`, which unwinds through the + coroutine body (running destructors and catch blocks), reaches + `unhandled_exception()`, and the error is stored on the SAV. +- `destroy()`: calls `handle.destroy()` to free the coroutine frame (which + also frees the embedded CoroActor, since it's part of the frame). + +### `get_return_object()` + +```cpp +ReturnFutureType get_return_object() noexcept { + coroActor.handle = handle(); + return ReturnFutureType(&coroActor); +} +``` + +`ReturnFutureType` is `Future` — an 8-byte wrapper around a +`SAV*` pointer. This constructs a `Future` pointing at the actor's SAV. +The SAV was initialized with `futures=1` by the `Actor` constructor, and the +explicit `Future(SAV*)` constructor does not increment the refcount — so the +returned Future consumes that initial reference. + +The compiler calls `get_return_object()` before the body executes. The caller +receives this Future immediately (when the coroutine first suspends or +completes without suspending). + +### `initial_suspend()` + +Returns `suspend_never` — the coroutine body starts executing immediately when +called. The `Future` has already been returned to the caller via +`get_return_object()`. + +### `return_value()` / `return_void()` + +Implemented via the `CoroReturn` CRTP mixin. Calls +`actor()->set(value)` which placement-news the value into the SAV's storage +and sets `error_state = SET_ERROR_CODE`. + +### `final_suspend()` + +Returns a custom `FinalAwaitable`: + +- `await_ready()` returns `false` — always enter `await_suspend` +- `await_suspend()` calls `finishSendAndDelPromiseRef()` (or the error + variant), which fires all waiting callbacks and decrements the promise count. + Returns `void` — the coroutine is left suspended. The frame stays alive + until the SAV's `destroy()` virtual is called (when both refcounts hit 0), + which calls `handle.destroy()`. + +### `unhandled_exception()` + +Catches the exception, extracts the `Error`, and stores it on the SAV via +`setError()`. If the exception isn't an `Error`, stores `unknown_error()`. + +### `await_transform()` + +Intercepts every `co_await` expression and wraps it in an FDB-specific awaiter +type: + +| Awaited type | Awaiter created | +|---|---| +| `Future` | `AwaitableFuture<..., false>` | +| `FutureStream` | `AwaitableFuture<..., true>` | +| `AsyncResult` | `AwaitableAsyncResult` | +| `ThreadFutureStream` | `ThreadAwaitableFutureStream` | +| `coro::FutureIgnore` | `AwaitableFutureIgnore` | +| `coro::FutureErrorOr` | `AwaitableFutureErrorOr` | + +--- + +## Awaiters: How `co_await` Suspends and Resumes + +All three awaiter methods — `await_ready()`, `await_suspend()`, and +`await_resume()` — are called by the **compiler**, never by FDB code directly. +The programmer implements them on the awaiter types; the compiler generates +calls to them in the correct order at each `co_await` expression. + +### `AwaitableFuture` + +The most common awaiter. Used for `co_await someFuture` and +`co_await someStream`. + +Inherits from `Callback` (or `SingleCallback` for streams), which means +the awaiter itself is the callback node that gets inserted into the Future's +callback list. + +**`await_ready()`**: Returns `true` if the Future is already resolved (skip +suspension). Also checks if the actor has been cancelled — if so, sets +`CANCELLED_DURING_READY_CHECK` and returns `true` to proceed to +`await_resume()` where the cancellation is handled. + +**`await_suspend(handle)`**: +1. Stores the coroutine handle via `pt->setHandle(h)` +2. Sets `actor_wait_state = ACTOR_WAIT_STATE_WAITING` +3. Registers this awaiter as a callback on the Future via + `addCallbackAndClear(this)` — this transfers the Future's reference to + the callback list + +**`fire(value)` / `error(err)`** (inherited from Callback): Called when the +awaited Future is resolved — someone called `promise.send(value)` or +`promise.sendError(err)`, and `SAV::send()` walked its callback list calling +`fire()` on each node. This happens synchronously on whatever call stack +resolved the Future (FDB is cooperative single-threaded — there is no +scheduler). For streams, `fire()` first stores the value in a local +`AwaitableFutureStore` (since the stream may yield further values). The +awaiter's `fire()` then calls `pt->resume()` → `handle.resume()`, which +resumes the coroutine at its suspend point. From the coroutine's perspective, +control returns from the `co_await` expression. + +**`resumeImpl()`**: Called at the start of `await_resume()`. Checks +`actor_wait_state`: +- If cancelled: detaches from the callback list and throws + `actor_cancelled()` +- If still waiting (future wasn't ready yet when we entered await_resume via + the non-ready-check path): detaches from callback list, resets wait state +- Returns whether the future was already ready (affects how the value is + retrieved for streams) + +**`await_resume()`**: Implemented via the `AwaitableResume` CRTP mixin. +Calls `resumeImpl()`, then returns the value from the resolved Future. +Throws if the Future completed with an error. + +### The full `co_await` sequence, step by step + +```cpp +Future example() { + Promise p; + Future f = p.getFuture(); + + // ... elsewhere, someone will call p.send(42) + + int val = co_await f; + // ^^^^^^^^^ + // 1. Compiler calls promise.await_transform(f) + // → creates AwaitableFuture<..., int, false>{f, &promise} + // + // 2. Compiler calls awaiter.await_ready() + // → f is not resolved yet → returns false + // + // 3. Compiler calls awaiter.await_suspend(handle) + // → stores handle in actor + // → sets actor_wait_state = WAITING + // → registers awaiter as callback on f's SAV + // → coroutine suspends, control returns to caller + // + // ... time passes, someone calls p.send(42) ... + // (The coroutine is suspended. Execution has returned to whoever + // called example(). That caller — or another coroutine that runs + // later on the same thread — eventually calls p.send(42). + // The send fires callbacks synchronously on that call stack.) + // + // 4. SAV::send() fires all callbacks → awaiter.fire(42) + // → calls promise.resume() → handle.resume() + // → coroutine resumes + // + // 5. Compiler calls awaiter.await_resume() + // → resumeImpl() checks wait state, detaches callback + // → returns f.get() → 42 + // + // 6. val = 42 + + co_return val; +} +``` + +--- + +## Cancellation + +### How cancellation is triggered + +Cancellation flows through the SAV refcount system: + +1. The caller drops the last `Future` reference to an actor +2. `Future::~Future()` calls `sav->delFutureRef()` +3. `delFutureRef()` sees futures hit 0 and promises > 0 → calls `cancel()` +4. `cancel()` is virtual, dispatched to the actor subclass + +Or explicitly: the caller calls `f.cancel()` which calls `sav->cancel()`. + +### Normal cancellation (CoroActor) + +```cpp +void cancel() override { + auto prev_wait_state = actor_wait_state; + actor_wait_state = ACTOR_WAIT_STATE_CANCELLED; + if (actorWaitStateIsWaiting(prev_wait_state)) { + handle.resume(); // resume the coroutine + } +} +``` + +If the coroutine is suspended at a `co_await`, it is resumed. The awaiter's +`resumeImpl()` sees `CANCELLED` and throws `actor_cancelled()`. The exception +unwinds through the coroutine body — running destructors, entering catch +blocks — until it reaches `unhandled_exception()`, which stores the error on +the SAV. + +If the coroutine is not waiting (between co_await points, which shouldn't +happen in cooperative scheduling), the flag is just set. The next +`await_ready()` will detect it. + +### `Uncancellable` coroutines + +`CoroActor` — the `cancel()` body is compiled away +(`if constexpr (IsCancellable)` is false). The coroutine runs to completion +regardless of whether futures are dropped. + +### `NoThrowOnCancel` coroutines + +Uses `NoThrowOnCancelCoroActor` instead of `CoroActor`. Key differences: + +1. **The actor is heap-allocated separately** from the coroutine frame + (via `new ActorType()` in the promise). This is necessary because cancel + destroys the frame while the SAV must survive for Future observers. + +2. **`cancel()` destroys the frame directly** instead of resuming: + +```cpp +void cancel() override { + if (!canBeSet()) return; + + if (cancelHandler) { + // Detach the awaiter's callback before destroying the frame + cancelHandler->cancelWait(); + } + + destroyFrame(); // handle.destroy() — RAII destructors run + + if (futures) { + sendErrorAndDelPromiseRef(actor_cancelled()); + } else { + promises = 0; + delete this; + } +} +``` + +3. **`final_suspend` returns `false`** from `await_suspend`. After completing + the SAV and clearing the handle, the frame auto-destructs (the coroutine was + never suspended at the final suspend point). This is well-defined per the + C++ standard: returning `false` means the coroutine resumes, control flows + off the end, and the frame is destroyed. + +4. **`AwaitCancelHandler`**: A virtual interface that awaiters implement. Each + awaiter registers itself via `pt->setCancelHandler(this)` in + `await_suspend` and clears itself in `resumeImpl`. When `cancel()` fires, + it calls `cancelHandler->cancelWait()` to detach the awaiter's callback + from whatever Future it's waiting on, *before* destroying the frame. Without + this, the destroyed awaiter would be a dangling node in the Future's + callback list. + +**Behavioral difference from normal cancellation:** + +- RAII destructors run (frame destruction invokes ~locals) +- `catch` blocks inside the coroutine are **not** entered for cancellation +- The coroutine never "sees" the cancellation — it simply ceases to exist + +--- + +## `AsyncResult` + +``` +flow/include/flow/Coroutines.h, CoroutinesImpl.h +``` + +An alternative to `Future` that transfers ownership of the result value +through `co_await`. Unlike `Future` where `co_await` returns `T const&`, +`co_await AsyncResult` returns `T` by value (moved out). This avoids copies +for expensive payloads. + +`AsyncResult` is backed by `AsyncResultState` (separate from SAV) with +its own reference counting. The coroutine promise is `AsyncResultPromise`, +which manages `producerHandle` and `producerWaitState` directly rather than +inheriting from Actor. + +Cancellation for AsyncResult coroutines goes through +`AsyncResultState::cancelProducer()`, which mirrors the Actor cancel logic: +either resumes the producer to throw `actor_cancelled()` or (with +`noThrowOnCancel`) destroys the producer frame directly. + +--- + +## `choose` / `when` for Coroutines + +``` +flow/include/flow/CoroUtils.h +``` + +The coroutine equivalent of the actor compiler's `choose { when(...) { ... } }` +pattern. Two forms: + +### `Choose().When(future, handler).When(...).run()` + +Callback-based. Creates a `ChooseImplActor` that registers callbacks on all +futures simultaneously. When the first one fires, it removes all other +callbacks and runs the handler. Returns `Future`. + +**Limitations:** handlers are synchronous functions — you cannot `co_await` +inside a `When` handler. + +### `co_await race(future1, future2, ...)` + +Template-based alternative that supports heterogeneous future types. Returns +`Future>` where each `Ti` is the value type of the +corresponding future. When the first future completes, `race` cancels the +others and constructs the variant with `std::in_place_index` for the +winning future's position. + +Use `result.index()` to determine which future won, and `std::get(result)` +or `std::visit(...)` to extract the value: + +```cpp +auto result = co_await race(futureInt, futureString); +if (result.index() == 0) { + int val = std::get<0>(result); +} else { + std::string val = std::get<1>(result); +} +``` + +--- + +## Marker Parameters + +FDB uses defaulted function parameters as compile-time markers: + +```cpp +Future foo(int x, NoThrowOnCancel = {}) { + co_await bar(); + co_return; +} +``` + +The compiler sees the parameter types `(int, NoThrowOnCancel)` and passes them +through `coroutine_traits, int, NoThrowOnCancel>`, which selects +the appropriate promise type. + +| Marker | Effect | +|---|---| +| (none) | Normal cancellable coroutine | +| `Uncancellable` | `cancel()` is a no-op; coroutine runs to completion | +| `NoThrowOnCancel` | `cancel()` destroys the frame directly; no `actor_cancelled` thrown | +| `ExplicitVoid` | `co_await Future` produces `Void` instead of `void` | + +`Uncancellable` and `NoThrowOnCancel` are mutually exclusive (enforced by +`static_assert`). Neither is compatible with `AsyncGenerator`. + +--- + +## Lifecycle Summary + +### Normal completion + +1. Coroutine is called → frame allocated, `get_return_object()` returns + `Future` to caller +2. `initial_suspend()` returns `suspend_never` → body runs immediately +3. Body executes, suspending/resuming at `co_await` points +4. `co_return value` → `promise.return_value(value)` stores result in SAV +5. `final_suspend()` → `await_suspend` fires callbacks via + `finishSendAndDelPromiseRef()`, decrements promise count +6. Frame is either left suspended (normal) or auto-destroyed + (NoThrowOnCancel) +7. When both refcounts hit 0, `destroy()` frees the frame (if still alive) + and the SAV + +### Cancellation (normal) + +1. Last `Future` dropped → `delFutureRef()` → `cancel()` on the actor +2. `cancel()` sets `actor_wait_state = CANCELLED`, resumes the coroutine +3. Awaiter's `resumeImpl()` throws `actor_cancelled()` +4. Exception unwinds through the body (running destructors and catch blocks) +5. `unhandled_exception()` stores the error on the SAV (sets `error_state`) +6. `final_suspend()` → `await_suspend` fires waiting callbacks via + `finishSendErrorAndDelPromiseRef()` and decrements the promise count +7. Frame cleanup proceeds as in normal completion + +### Cancellation (NoThrowOnCancel) + +1. Last `Future` dropped → `delFutureRef()` → `cancel()` on the actor +2. `cancel()` calls `cancelHandler->cancelWait()` to detach the awaiter +3. `cancel()` calls `handle.destroy()` — frame is destroyed, RAII runs +4. `cancel()` sends `actor_cancelled()` to any remaining Futures via SAV +5. Actor (heap-allocated) survives until all Future references are dropped + +### Refcount flow + +``` +Actor constructor: promises=1 futures=1 +get_return_object(): Future takes the futures=1 ref (no increment) +Caller copies Future: futures=2 +co_await registers cb: futures stays same (cb replaces future ref) +Future fires cb: coroutine resumes +final_suspend: promises-- (via finishSendAndDelPromiseRef) +Caller drops Future: futures-- → if both 0, destroy() +``` diff --git a/design/file-level-encryption-backup.md b/design/file-level-encryption-backup.md new file mode 100644 index 00000000000..265af6c00ea --- /dev/null +++ b/design/file-level-encryption-backup.md @@ -0,0 +1,370 @@ +# File-Level Encryption in FoundationDB Backups + +This document describes the design and usage of file-level encryption for +FoundationDB backups and restores. + +--- + +## 1. Overview + +File-level encryption protects backup data files written to local +directories or object stores (e.g., S3) by encrypting each file with a +user-supplied key before it is uploaded. Restore requires the same key to +be available to all participating processes. + +**What is encrypted:** + +- All **snapshot**, **range**, and **log** files. + +**What is *not* encrypted:** + +- The metadata files in the `properties/` folder of the backup container, + so tools like `fdbbackup describe` and `fdbbackup expire` can work + without holding the key. +- Anything inside the live cluster — mutations, keys, and values are + unchanged on the wire and in storage. + +**Cipher:** AES-256-GCM. The same key must be supplied to backup, restore, +modify (when re-encrypting), and decode operations. + +--- + +## 2. Encryption Key + +The encryption key is a **32-byte (256-bit)** value in raw binary format — +the exact key length AES-256 requires. + +The key value itself is **never** passed on the command line. The user +provides the **path** to a file holding the 32 raw bytes via +`--encryption-key-file `, and the CLI reads the file to load the key. +The file must contain exactly 32 bytes of raw binary data — no headers, no +encoding, no trailing newline. + +For example, a key file can be generated using OpenSSL: + +``` +openssl rand 32 > key_file +``` + +--- + +## 3. CLI Arguments + +Two command-line arguments drive the encrypted-backup workflow: + +### `--encryption-key-file ` + +Path to the file holding the AES-256 key. Encryption is opt-in: a backup +is encrypted only when this argument is supplied to `fdbbackup start`. The +same argument must be supplied to `fdbrestore` and `fdbdecode` so they can +read the ciphertext. + +### `--encryption-block-size ` + +Block size used to encrypt each data file. Optional on `fdbbackup start`, +defaults to `1048576` (1 MB). The chosen value is recorded in the backup's +metadata so that restore and decode pick it up automatically — those tools +do not accept the flag themselves. + +Validation rules on `fdbbackup start`: + +- Must be a positive integer; non-numeric or non-positive values are + rejected with `ERROR: Invalid encryption block size`. +- Must be combined with `--encryption-key-file`. Supplying it without a + key file is rejected with + `ERROR: --encryption-block-size option requires --encryption-key-file to be set`. + +### Per-command summary + +| Command | `--encryption-key-file` | `--encryption-block-size` | Notes | +| ------------------ | :---------------------: | :-----------------------: | -------------------------------------------------------------------------------------- | +| `fdbbackup start` | required for encryption | optional, default 1 MB | Starts a new encrypted backup. | +| `fdbbackup modify` | conditional | n/a | Required only when the destination URL changes and future files must be re-encrypted. | +| `fdbrestore start` | required if encrypted | n/a | Block size is read from the backup's metadata file, not the CLI. | +| `fdbdecode` | required if encrypted | n/a | Decodes encrypted log files with the supplied key. | + +--- + +## 4. Usage + +The same workflow applies whether the destination is S3 or a local file +directory. + +### 4.1 Start a backup + +`--encryption-block-size` is optional; it defaults to 1 MB (`1048576`) +when omitted. The example below sets it explicitly to half a megabyte +(`524288`): + +``` +fdbbackup start \ + -C $CLUSTER_FILE \ + -t test_backup \ + -w \ + -d $BACKUP_URL \ + -k '"" \xff' \ + --log --logdir=$LOG_DIR \ + --blob-credentials $BLOB_CREDENTIALS_FILE \ + --knob_blobstore_encryption_type=aws:kms \ + --encryption-key-file /root/backup_testing/test_encryption_key_file \ + --encryption-block-size 524288 +``` + +### 4.2 Stop a backup + +``` +fdbbackup discontinue -C $CLUSTER_FILE -t test_backup +``` + +### 4.3 Restore a backup + +Restore must use the **same key** that was used at backup time: + +``` +fdbrestore start \ + --dest-cluster-file $CLUSTER_FILE \ + -t test_backup \ + -w \ + -r $BACKUP_URL \ + --log --logdir=$LOG_DIR \ + --blob-credentials $BLOB_CREDENTIALS_FILE \ + --knob_blobstore_encryption_type=aws:kms \ + --encryption-key-file /root/backup_testing/test_encryption_key_file +``` + +`fdbrestore` does **not** take `--encryption-block-size`; the block size +is read from the backup's `encryption_metadata` file. + +### 4.4 Modify a backup destination + +``` +fdbbackup modify \ + -C $CLUSTER_FILE \ + -t mybackup \ + -d $BACKUP_URL \ + --encryption-key-file /root/local_testing/key_file \ + --log --logdir=$LOG_DIR +``` + +Caveats: + +- If the **same URL** is provided with a **different** key, `modify` + rejects the change to avoid mixing differently-encrypted files at the + same URL. +- If a **different URL** is provided, the encryption key may be different, + the same, or omitted entirely. + +### 4.5 Decode encrypted log files + +``` +fdbdecode \ + -r $BACKUP_URL \ + --encryption-key-file /root/local_testing/key_file +``` + +`--encryption-key-file` is required only when the backup at `BACKUP_URL` +is encrypted. + +--- + +## 5. Verifying Encryption Status + +### 5.1 `fdbbackup describe` + +`fdbbackup describe` reports whether a backup is encrypted and the block +size used. **No encryption key is required.** + +``` +fdbbackup describe \ + -C $CLUSTER_FILE \ + -d $BACKUP_URL \ + --blob-credentials $BLOB_CREDENTIALS_FILE +``` + +Text output: + +``` +URL: ... +Restorable: true +Partitioned logs: false +File-level encryption: true +Encryption block size: 1048576 +Snapshot: ... +``` + +JSON output (`--json`) adds `FileLevelEncryption` and +`EncryptionBlockSize`: + +```json +{ + "SchemaVersion": "1.0.0", + "URL": "file:///root/local_testing/backup_before/backup-2026-01-06-04-03-08.320838/", + "Restorable": true, + "Partitioned": false, + "FileLevelEncryption": true, + "EncryptionBlockSize": 1048576, + "Snapshots": [ ... ] +} +``` + +### 5.2 `fdbcli` status JSON + +Each backup agent reports an array of encryption key file paths along +with read success status, so distribution failures are visible at a +glance: + +```json +"encryption_keys": [ + { "path": "/root/local_testing/key1", "success": false }, + { "path": "/root/local_testing/key_file", "success": true } +] +``` + +Each backup tag reports its key file path and encryption status: + +```json +"encryption_key_file": "/root/local_testing/key1", +"file_level_encryption": true +``` + +A complete fragment: + +```json +"instances": { + "4747c54caca01d682a2780d27b7dc9f6": { + "blob_stats": { ... }, + "configured_workers": 10, + "encryption_keys": [ + { "path": "/root/local_testing/key1", "success": false }, + { "path": "/root/local_testing/key_file", "success": true } + ], + "id": "4747c54caca01d682a2780d27b7dc9f6" + } +}, +"tags": { + "test_backup5": { + "current_container": "file:///root/backup_testing/backupfolder/backup-2026-01-21-22-47-47.952405", + "current_status": "has been completed", + "encryption_key_file": "/root/local_testing/key1", + "file_level_encryption": true, + ... + }, + "test_backup8": { + "current_container": "file:///root/backup_testing/backupfolder/backup-2026-01-21-22-57-08.832311", + "current_status": "has been completed", + "file_level_encryption": false, + ... + } +} +``` + +--- + +## 6. Errors and Conflicting Scenarios + +| Scenario | Error | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Restoring with a key when backup is **not** encrypted | `ERROR: Backup is not encrypted, please remove the encryption key file path.` | +| Restoring **without** a key when backup **is** encrypted | `ERROR: Backup is encrypted, please provide the encryption key file path.` | +| Restoring with the **wrong** key | `ERROR: Failed to read data. Verify that backup and restore encryption keys match (if provided) or the data is corrupted.` | +| `modify` with same URL and different key | `Destination URL matches the existing backup URL for tag '...', but the encryption key file does not match. Fatal Error: Backup error` | +| `--encryption-block-size` without `--encryption-key-file` | `ERROR: --encryption-block-size option requires --encryption-key-file to be set` | +| Invalid `--encryption-block-size` value | `ERROR: Invalid encryption block size ''` | + +--- + +## 7. Key Distribution Requirements + +The encryption key file **path** is added to the backup container and +persisted in `BackupConfig`. That same path is then propagated to every +process that touches the encrypted files: + +- Every **`backup_agent`** host participating in the backup. +- Every **`backup_agent`** host participating in a restore (restore + workers read the encrypted files and must decrypt them). +- The **`fdbrestore`** client process that drives the restore — it opens + the backup container directly to read metadata and validate the + configuration. + +Each of these hosts **must** have the **same** encryption key file +stored locally **at the exact same path**. Otherwise individual agents or +the `fdbrestore` process will fail to read or write encrypted files and +the backup/restore will be unable to make progress. + +`fdbcli status json` (Section 5.2) is the operational signal for whether +the key file is present and readable on every agent. + +--- + +## 8. Design Internals + +### 8.1 What "file-level" means + +"File-level" refers to the **scope** of encryption: each whole backup +file (snapshot, range, or log) is the unit that gets encrypted, as +opposed to encrypting individual mutations, keys/values, or live cluster +storage. Mutations and the live cluster are unchanged; only the on-disk +backup artifacts are ciphertext. Filenames, directory layout, and files +under `properties/` stay plaintext so `fdbbackup describe` and +`fdbbackup expire` can work without the key. + +### 8.2 Block layout inside a file + +Inside each file, the data is split into fixed-size blocks (the +**encryption block size**) and each block is encrypted independently with +AES-256-GCM using the 32-byte key from `--encryption-key-file`. The +blocks are an implementation detail of the AES-GCM stream cipher — not a +change in scope — and they exist so restore can read a small byte range +without decrypting the entire file. + +Per-block IVs are derived deterministically: the first 12 bytes come from +a hash of the filename, and the last 4 bytes are the block index. This +gives every block a unique IV while keeping reads seekable. On restore, +the `encryption_metadata` file is read first to recover the +enable flag and the block size, so the reader splits each file into the +exact same blocks that were produced at backup time. + +### 8.3 Metadata files + +Metadata lives in the `properties/` folder of the backup container and +stays unencrypted. + +| File | Contents | Purpose | +| -------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------- | +| `encryption_metadata` | JSON object (see below) | Indicates whether the backup is encrypted and the encryption block size used. | +| `mutation_log_type` | `"1"` or `"0"` (1 byte) | Whether the backup uses partitioned mutation logs. | +| `log_begin_version` | Version number as text (10 bytes) | Earliest mutation-log version present in the backup. | +| `log_end_version` | Version number as text (10 bytes) | Latest mutation-log version present in the backup. | + +`encryption_metadata` is a JSON object containing both the on/off flag +and the block size used to encrypt the data files: + +```json +{ + "is_encryption_enabled": true, + "encryption_block_size": 1048576 +} +``` + +- `is_encryption_enabled` — `true` if all snapshot/range/log files are + encrypted, `false` otherwise. +- `encryption_block_size` — block size in bytes used to encrypt the data + files, or `0` if the backup is not encrypted. The block size must match + what restore/decode tools use for the same backup, so it is persisted + with the backup itself. + +The `encryption_metadata` metadata file is created immediately when a +backup starts. The other metadata files are generated only when +`fdbrestore` or `fdbbackup describe` runs. Those operations scan +filenames only, not file contents, when reconstructing the on-disk view. + +Example container layout: + +``` +ls -ltrh properties/ +total 16K +-rw-r--r-- 1 root root 74 May 5 21:04 encryption_metadata +-rw-r--r-- 1 root root 1 May 5 21:06 mutation_log_type +-rw-r--r-- 1 root root 10 May 5 21:41 log_end_version +-rw-r--r-- 1 root root 10 May 5 21:41 log_begin_version +``` diff --git a/design/global-tag-throttling.md b/design/global-tag-throttling.md deleted file mode 100644 index 1ea0db9a354..00000000000 --- a/design/global-tag-throttling.md +++ /dev/null @@ -1,131 +0,0 @@ -## Global Tag Throttling - -When the `GLOBAL_TAG_THROTTLING` knob is enabled, the ratekeeper will use the [transaction tagging feature](https://apple.github.io/foundationdb/transaction-tagging.html) to throttle tags according to the global tag throttling algorithm. This page describes the implementation of this algorithm. - -### Tag Quotas -The global tag throttler bases throttling decisions on "quotas" provided by clients through the tag quota API. Each tag quota has two components: - -* Reserved quota -* Total quota - -The global tag throttler cannot throttle tags to a throughput below the reserved quota, and it cannot allow throughput to exceed the total quota. - -### Cost -Internally, the units for these quotas are bytes. The cost of an operation is rounded up to the nearest page size. The cost of a read operation is computed as: - -``` -readCost = ceiling(bytesRead / CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE) * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE; -``` - -The cost of a write operation is computed as: - -``` -writeCost = CLIENT_KNOBS->GLOBAL_TAG_THROTTLING_RW_FUNGIBILITY_RATIO * ceiling(bytesWritten / CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE) * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE; -``` - -Here `bytesWritten` includes cleared bytes. The size of range clears is estimated at commit time. - -### Tuple Layer -Tag quotas are stored inside of the system keyspace (with prefix `\xff/tagQuota/`). They are stored using the tuple layer, in a tuple of form: `(reservedQuota, totalQuota)`. There is currently no custom code in the bindings for manipulating these system keys. However, in any language for which bindings are available, it is possible to use the tuple layer to manipulate tag quotas. - -### fdbcli -The easiest way for an external client to interact with tag quotas is through `fdbcli`. To get the quota (in bytes/second) of a particular tag, run the following command: - -``` -fdbcli> quota get [reserved_throughput|total_throughput] -``` - -To set the quota through `fdbcli`, run: - -``` -fdbcli> quota set [reserved_throughput|total_throughput] -``` - -To clear a both reserved and total throughput quotas for a tag, run: - -``` -fdbcli> quota clear -``` - -### Limit Calculation -The transaction budget that ratekeeper calculates and distributes to clients (via GRV proxies) for each tag is calculated based on several intermediate rate calculations, outlined in this section. - -* Reserved Rate: Based on reserved quota and the average transaction cost, a reserved TPS rate is computed for each tag. - -* Desired Rate: Based on total quota and the average transaction cost, a desired TPS rate is computed for each tag. - -* Limiting Rate: When a storage server is near saturation, tags contributing notably to the workload on this storage server will receive a limiting TPS rate, computed to relieve the workload on the storage server. - -* Target Rate: The target rate is the cluster-wide rate enforced by the global tag throttler. This rate is computed as: - -``` -targetTps = max(reservedTps, min(desiredTps, limitingTps)); -``` - -* Per-Client Rate: While the target rate represents the cluster-wide desired throughput according to the global tag throttler, this budget must be shared across potentially many clients. Therefore, based on observed throughput from various clients, each client will receive an equal budget based on a per-client, per-tag rate computed by the global tag throttler. This rate is in the end what will be sent to clients to enforce throttling. - -## Implementation - -### Stat collection -The transaction rates and costs of all transactions must be visible to the global tag throttler. Whenever a client tags a transaction, sampling is performed to determine whether to attach the tag to messages sent to storage servers and commit proxies. - -For read operations that are sampled (with probability `CLIENT_KNOBS->READ_TAG_SAMPLE_RATE`), read costs are aggregated on storage servers using the `TransactionTagCounter` class. This class tracks the busyness of the top-k tags affecting the storage server with read load (here `k` is determined by `SERVER_KNOBS->SS_THROTTLE_TAGS_TRACKED`). Storage servers periodically send per-tag read cost statistics to the ratekeeper through `StorageQueuingMetricsReply` messages. - -For write operations that are sampled (with probability `COMMIT_SAMPLE_COST`), write costs are aggregated on commit proxies in the `ProxyCommitData::ssTrTagCommitCost` object. Per-storage, per-tag write cost statistics are periodically sent from commit proxies to the ratekeeper through `ReportCommitCostEstimationRequest` messages. - -The ratekeeper tracks per-storage, per-tag cost statistics in the `GlobalTagThrottlerImpl::throughput` object. - -The ratekeeper must also track the rate of transactions performed with each tag. Each GRV proxy agreggates a per-tag counter of transactions started (without sampling). These are sent to the ratekeeper through `GetRateInfoRequest` messages. The global tag throttler then tracks per tag transaction rates in the `GlobalTagThrottlerImpl::tagStatistics` object. - -### Average Cost Calculation -Quotas are expressed in terms of cost, but because throttling is enforced at the beginning of transactions, budgets need to be calculated in terms of transactions per second. To make this conversion, it is necessary to track the average cost of transactions (per-tag, and per-tag on a particular storage server). - -Both cost and transaction counters are exponentially smoothed over time, with knob-configurable smoothing intervals. - -### Reserved Rate Calculation -The global tag throttler periodically reads reserved quotas from the system keyspace. Using these reserved quotas and the average cost of transactions with the given tag, a reserved TPS rate is computed. Read and write rates are aggregated as follows: - -``` -reservedTps = max(reservedReadTps, reservedWriteTps); -``` - -### Desired Rate Calculation -Similar to reserved rate calculation, the total quota is read from the system key space. Then, using the average cost of transactions with the given tag, a desired TPS rate is computed. Read and write rates are aggregated as follows: - -``` -desiredTps = min(desiredReadTps, desiredWriteTps); -``` - -### Limiting Rate Calculation -In addition to tag busyness statistics, the `StorageQueuingMetricsReply` messages sent from storage servers to the ratekeeper also contain metrics on the health of storage servers. The ratekeeper uses these metrics as part of its calculation of a global transaction rate (independent of tag throttling). - -The global tag throttler also makes use of these metrics to compute a "throttling ratio" for each storage server. This throttling ratio is computed in `StorageQueueInfo::getThrottlingRatio`. The global tag throttler uses the throttling ratio for each tracked storage server to compute a "limiting transaction rate" for each combination of storage server and tag. - -In the "healthy" case where no metrics are near saturation, the throttling ratio will be an empty `Optional`, indicating that the storage server is not near saturation. If, on the other hand, the metrics indicate approaching saturation, the throttling ratio will be a number between 0 and 2 indicating the ratio of current throughput the storage server can serve. In this case, the global tag throttler looks at the current cost being served by the storage server, multiplies it by the throttling ratio, and computes a limiting cost for the storage server. Among all tags using significant resources on this storage server, this limiting cost is divided up according to the relative total quotas allocated to these tags. Next, a transaction limit is determined for each tag, based on how much the average transaction for the given tag affects the given storage server. - -These per-tag, per-storage limiting transaction rates are aggregated to compute per-tag limiting transaction rates: - -``` -limitingTps(tag) = min{limitingTps(tag, storage) : all storage servers} -``` - -If the throttling ratio is empty for all storage servers affected by a tag, then the per-tag, per-storage limiting TPS rate is also empty. In this case the target rate for this tag is simply the desired rate. - -If an individual zone is unhealthy, it may cause the throttling ratio for storage servers in that zone to shoot up. This should not be misinterpreted as a workload issue that requires active throttling. Therefore, the zone with the worst throttling ratios is ignored when computing the limiting transaction rate for a tag (similar to the calculation of the global transaction limit in `Ratekeeper::updateRate`). - -### Client Rate Calculation -The smoothed per-client rate for each tag is tracked within `GlobalTagThrottlerImpl::PerTagStatistics`. Once a target rate has been computed, this is passed to `GlobalTagThrottlerImpl::PerTagStatistics::updateAndGetPerClientRate` which adjusts the per-client rate. The per-client rate is meant to limit the busiest clients, so that at equilibrium, the per-client rate will remain constant and the sum of throughput from all clients will match the target rate. - -## Simulation Testing -The `ThroughputQuota.toml` test provides a simple end-to-end test using the global tag throttler. Quotas are set using the internal tag quota API in the `ThroughputQuota` workload. This is run with the `Cycle` workload, which randomly tags transactions. - -In addition to this end-to-end test, there is a suite of unit tests with the `/GlobalTagThrottler/` prefix. These tests run in a mock environment, with mock storage servers providing simulated storage queue statistics and tag busyness reports. Mock clients simulate workload on these mock storage servers, and get throttling feedback directly from a global tag throttler which is monitoring the mock storage servers. - -In each unit test, the `GlobalTagThrottlerTesting::monitor` function is used to periodically check whether or not a desired equilibrium state has been reached. If the desired state is reached and maintained for a sufficient period of time, the test passes. If the unit test is unable to reach this desired equilibrium state before a timeout, the test will fail. Commonly, the desired state is for the global tag throttler to report a client rate sufficiently close to the desired rate specified as an input to the `GlobalTagThrottlerTesting::rateIsNear` function. - -## Visibility - -### Tracing -On the ratekeeper, every `SERVER_KNOBS->TAG_THROTTLE_PUSH_INTERVAL` seconds, the ratekeeper will call `GlobalTagThrottler::getClientRates`. At the end of the rate calculation for each tag, a trace event of type `GlobalTagThrottler_GotClientRate` is produced. This trace event reports the relevant inputs that went in to the rate calculation, and can be used for debugging. - -On storage servers, every `SERVER_KNOBS->TAG_MEASUREMENT_INTERVAL` seconds, there are `BusyReadTag` events for every tag that has sufficient read cost to be reported to the ratekeeper. Both cost and fractional busyness are reported. diff --git a/design/mocks3server_chaos_design.md b/design/mocks3server_chaos_design.md index 25c03ce0f7c..0e41be8cbab 100644 --- a/design/mocks3server_chaos_design.md +++ b/design/mocks3server_chaos_design.md @@ -53,7 +53,7 @@ if (BUGGIFY) { * **HTTP errors**: 429 (throttling), 503 (service unavailable), 500/502 (server errors) * **Auth errors**: 401 (unauthorized), 406 (not acceptable) -* **S3-specific errors**: InvalidToken, ExpiredToken (matching [`S3BlobStore.actor.cpp`](https://github.com/apple/foundationdb/tree/main/fdbclient/S3BlobStore.actor.cpp#L1241) patterns) +* **S3-specific errors**: InvalidToken, ExpiredToken (matching [`S3BlobStore.cpp`](https://github.com/apple/foundationdb/tree/main/fdbclient/S3BlobStore.cpp#L1241) patterns) * **Connection issues**: Connection drops, timeouts * **Data corruption**: Malformed responses, bit flips @@ -119,4 +119,3 @@ wait(startMockS3ServerChaos(listenAddress)); ``` Chaos behavior is controlled by **S3FaultInjector rates** (0.0-1.0), with **BUGGIFY providing occasional extra chaos** - no master boolean switch. - diff --git a/design/recovery-internals.md b/design/recovery-internals.md index 4b4ac34ebb6..9f77bc60e15 100644 --- a/design/recovery-internals.md +++ b/design/recovery-internals.md @@ -71,7 +71,7 @@ This phase locks the coordinated state (cstate) to make sure there is only one C Recall that `ServerDBInfo` has CC's interface and is propagated by CC to every process in a cluster. The current running tLogs can use the CC interface in its `ServerDBInfo` to send its interface to CC. CC simply waits on receiving the `TLogRejoinRequest` streams: for each tLog’s interface received, the CC compares the interface ID with the tLog ID read from cstate. Once the CC collects enough old tLog interfaces, it will use the interfaces to lock those tLogs. The logic of collecting tLogs’ interfaces is implemented in `trackRejoins()` function. -The logic of locking the tLogs is implemented in `epochEnd()` function in [TagPartitionedLogSystems.actor.cpp](https://github.com/apple/foundationdb/blob/main/fdbserver/TagPartitionedLogSystem.actor.cpp). +The logic of locking the tLogs is implemented in the `epochEnd()` function in [LogSystem.cpp](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp). Once we lock the cstate, we bump the `recoveryCount` by 1 and write the `recoveryCount` to the cstate. Each tLog in a recovery attempt records the `recoveryCount` and monitors the change of the variable. If the `recoveryCount` increases, becoming larger than the recorded value, the tLog will terminate itself. This mechanism makes sure that when multiple recovery attempts happen concurrently, only tLogs in the most recent recovery will be running. tLogs in other recovery attempts can release their memory earlier, reducing the memory pressure during recovery. This is an important memory optimization before shared tLogs, which allows tLogs in different generations to share the same memory, is introduced. @@ -100,7 +100,7 @@ Once the CC gets enough tLogs, it calculates the known committed version (i.e., The CC's finds the maximum `knownCommittedVersion` of all the tLogs. This defines the lower bound of what version range of mutations needs to be copied to the new generation. That is, any versions larger than the CC's `knownCommittedVersion` are not guaranteed to persist on all replicas. The CC chooses a *recovery version*, which is the minimum of durable versions on all tLogs of the old generation, and recruits a new set of tLogs that copy all data between `knownCommittedVersion + 1` and `recoveryVersion` from old tLogs. This copy makes sure data within the range has enough replicas to satisfy the replication policy. -Later, the CC will use the recruited tLogs and `recoveryVersion` to create a new `TagPartitionedLogSystem` for the new generation. +Later, the CC will use the recruited tLogs and `recoveryVersion` to create a new `LogSystem` for the new generation. Note that tLogs can continue to rejoin the cluster after the `recoveryVersion` has been calculated but before the log system has been created. **An example of `knownCommittedVersion` and `recoveryVersion`:** @@ -117,7 +117,7 @@ Consider an old generation with three TLogs: `A, B, C`. Their durable versions a * Situation 1: Too many tLogs in the previous generation permanently died, say due to hardware failure. If force recovery is allowed by system administrator, the CC can choose to force recovery, which can cause data loss; otherwise, to unblock the recovery, system administrator has to bring up those died tLogs, for example by copying their files onto new hardware. -* Situation 2: A tLog may die after it reports alive to the CC in the RECRUITING phase. This may cause the `RecoveryVersion` calculated by the CC in this phase to no longer be valid in the next phases (see [getDurableVersion()](https://github.com/apple/foundationdb/blob/cf7c8f41b22612c4a8a632ea6f173d891b00380d/fdbserver/TagPartitionedLogSystem.actor.cpp#L1940)). When this happens, the CC will detect it (that the previous computed recovery version is no longer viable, because the tail of versions are no longer retrievable from current set of live TLogs), terminate the current recovery, and start a new recovery. See https://github.com/apple/foundationdb/blob/cf7c8f41b22612c4a8a632ea6f173d891b00380d/fdbserver/TagPartitionedLogSystem.actor.cpp#L2468-L2525, where changes to TLogs can cause changes to old log system (`outLogSystem->set(logSystem);`), which in turn causes the recovery loop https://github.com/apple/foundationdb/blob/cf7c8f41b22612c4a8a632ea6f173d891b00380d/fdbserver/ClusterRecovery.actor.cpp#L1582-L1610 to abandon progress and restart. +* Situation 2: A tLog may die after it reports alive to the CC in the RECRUITING phase. This may cause the `RecoveryVersion` calculated by the CC in this phase to no longer be valid in the next phases (see `getDurableVersion()` in [LogSystem.cpp](https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp)). When this happens, the CC will detect it (that the previous computed recovery version is no longer viable, because the tail of versions are no longer retrievable from current set of live TLogs), terminate the current recovery, and start a new recovery. Changes to TLogs can cause changes to old log system (`outLogSystem->set(logSystem);`), which in turn causes the recovery loop in [ClusterRecovery.actor.cpp](https://github.com/apple/foundationdb/blob/main/fdbserver/clustercontroller/ClusterRecovery.actor.cpp) to abandon progress and restart. Once we have a `knownCommittedVersion`, the CC will reconstruct the [transaction state store](https://github.com/apple/foundationdb/blob/main/design/transaction-state-store.md) by peeking the txnStateTag in oldLogSystem. Recall that the txnStateStore includes the transaction system’s configuration, such as the assignment of shards to SS and to tLogs and that the txnStateStore was durable on disk in the oldLogSystem. diff --git a/design/restore_v1.md b/design/restore_v1.md index 95145597042..0a64b60bf05 100644 --- a/design/restore_v1.md +++ b/design/restore_v1.md @@ -106,7 +106,7 @@ In the restore mechanism, we define five core tasks: 2. `RESTORE_DISPATCH_ADDTASK_SIZE` specifies how many works are dispatched by a single transaction, where we do not want this value too large to make the transaction too large to commit successfully. We do not want this value too small to have too much overhead of transactions to dispatch tasks. - - When `usePartitionedLog` is set, the `StartFullRestoreTaskFunc` spawns `RestoreDispatchPartitionedTaskFunc` instead, as the core of Restore V2. We omit it here since we focus on the Restore V1. + - When `mutation-log-type partitioned-log-experimental` is set, the `StartFullRestoreTaskFunc` spawns `RestoreDispatchPartitionedTaskFunc` instead, as the core of Restore V2. We omit it here since we focus on the Restore V1. 3. `RestoreRangeTaskFunc` (aka. snapshot restore task) diff --git a/design/transaction-state-store.md b/design/transaction-state-store.md index 5cb126bea4b..13a5bed33fc 100644 --- a/design/transaction-state-store.md +++ b/design/transaction-state-store.md @@ -114,4 +114,4 @@ during recovery, `txnStateStore` is recovered by reading all data from this vers * Commit proxies only write metadata mutations in its own transaction batch to TLogs: https://github.com/apple/foundationdb/blob/6281e647784e74dccb3a6cb88efb9d8b9cccd376/fdbserver/CommitProxyServer.actor.cpp#L772-L774 adds mutations to `storeCommits`. Later in `postResolution()`, https://github.com/apple/foundationdb/blob/6281e647784e74dccb3a6cb88efb9d8b9cccd376/fdbserver/CommitProxyServer.actor.cpp#L1162-L1176, only the last one in `storeCommits` are send to TLogs. -* Commit proxies clear the buffered data, and advance the transaction state store's pop version `poppedUpTo`, in `LogSystemDiskQueueAdapter` after TLog push: https://github.com/apple/foundationdb/blob/6281e647784e74dccb3a6cb88efb9d8b9cccd376/fdbserver/CommitProxyServer.actor.cpp#L1283-L1287 https://github.com/apple/foundationdb/blob/4ce8a7c8b414936662622c83d99a67fe93783a81/fdbserver/KeyValueStoreMemory.actor.cpp#L1033-L1034 https://github.com/apple/foundationdb/blob/4ce8a7c8b414936662622c83d99a67fe93783a81/fdbserver/LogSystemDiskQueueAdapter.actor.cpp#L213. +* Commit proxies clear the buffered data, and advance the transaction state store's pop version `poppedUpTo`, in `LogSystemDiskQueueAdapter` after TLog push: https://github.com/apple/foundationdb/blob/6281e647784e74dccb3a6cb88efb9d8b9cccd376/fdbserver/CommitProxyServer.actor.cpp#L1283-L1287 https://github.com/apple/foundationdb/blob/4ce8a7c8b414936662622c83d99a67fe93783a81/fdbserver/KeyValueStoreMemory.cpp#L1033-L1034 https://github.com/apple/foundationdb/blob/4ce8a7c8b414936662622c83d99a67fe93783a81/fdbserver/LogSystemDiskQueueAdapter.actor.cpp#L213. diff --git a/design/validating_restored_data_using_one_cluster.md b/design/validating_restored_data_using_one_cluster.md index 34cac712288..ebb16a379dd 100644 --- a/design/validating_restored_data_using_one_cluster.md +++ b/design/validating_restored_data_using_one_cluster.md @@ -83,5 +83,3 @@ Alternative Design Considerations * Add new AuditType **ValidateRestore.** * New audit actor auditRestoreQ() in StorageServer similar to auditStorageShardReplicaQ() * Change the code to read the range appended with the prefix restored_data_prefix. Can compare, update metadata and error mechanism in the same way. [Range](https://github.com/apple/foundationdb/blob/release-7.4/fdbserver/storageserver.actor.cpp#L5671) - - diff --git a/documentation/coro_tutorial/tutorial.cpp b/documentation/coro_tutorial/tutorial.cpp index 91686bec934..fe52bb1d90d 100644 --- a/documentation/coro_tutorial/tutorial.cpp +++ b/documentation/coro_tutorial/tutorial.cpp @@ -24,7 +24,7 @@ #include "flow/DeterministicRandom.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "fdbrpc/Net2FileSystem.h" #include #include @@ -194,8 +194,7 @@ Future echoServer() { [&requests](StreamRequest const& req) { requests.add([](StreamRequest req) -> Future { req.reply.setByteLimit(1024); - int i = 0; - for (; i < 100; ++i) { + for (int i = 0; i < 100; ++i) { co_await req.reply.onReady(); std::cout << "Send " << i << std::endl; req.reply.send(StreamReply{ i }); @@ -439,7 +438,7 @@ Future fdbClientStream() { GetRangeLimits()); loop { Standalone range = co_await results.getFuture(); - if (range.size()) { + if (!range.empty()) { bytes += range.expectedSize(); next = keyAfter(range.back().key); } @@ -555,17 +554,6 @@ Future fdbClient() { } } -Future fdbStatusStresser() { - Database db = Database::createDatabase(clusterFile, 300); - Key statusJson(std::string("\xff\xff/status/json")); - loop { - co_await runRYWTransaction(db, [&statusJson](ReadYourWritesTransaction* tr) -> Future { - co_await tr->get(statusJson); - co_return; - }); - } -} - AsyncGenerator> readBlocks(Reference file, int64_t blockSize) { auto sz = co_await file->size(); decltype(sz) offset = 0; @@ -646,9 +634,8 @@ std::unordered_map()>> actors = { { "fdbClientStream", &fdbClientStream }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClientStream { "fdbClientGetRange", &fdbClientGetRange }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClientGetRange { "fdbClient", &fdbClient }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClient - { "fdbStatusStresser", &fdbStatusStresser }, { "testReadLines", &testReadLines } -}; // ./tutorial -C $CLUSTER_FILE_PATH fdbStatusStresser +}; int main(int argc, char* argv[]) { bool isServer = false; diff --git a/documentation/sphinx/source/api-c.rst b/documentation/sphinx/source/api-c.rst index a97fa5900cc..4a807d6f7e8 100644 --- a/documentation/sphinx/source/api-c.rst +++ b/documentation/sphinx/source/api-c.rst @@ -870,11 +870,11 @@ Applications must provide error handling and an appropriate retry loop around th .. function:: FDBFuture* fdb_transaction_get_tag_throttled_duration(FDBTransaction* transaction) - |future-return0| the time (in seconds) that the transaction was throttled by the tag throttler in the returned future. |future-return1| call :func:`fdb_future_get_double()` to extract the duration, |future-return2| + |future-return0| ``0``. This compatibility API used to report proxy-side tag throttling time, but proxy-side tag throttling has been removed. |future-return1| call :func:`fdb_future_get_double()` to extract the duration, |future-return2| .. function:: FDBFuture* fdb_transaction_get_total_cost(FDBTransaction* transaction) - |future-return0| the cost of the transaction so far (in bytes) in the returned future, as computed by the tag throttler, and used for tag throttling if throughput quotas are specified. |future-return1| call :func:`fdb_future_get_int64()` to extract the cost, |future-return2| + |future-return0| the cost of the transaction so far (in bytes) in the returned future, as computed by the tag throttler. |future-return1| call :func:`fdb_future_get_int64()` to extract the cost, |future-return2| .. function:: FDBFuture* fdb_transaction_get_approximate_size(FDBTransaction* transaction) diff --git a/documentation/sphinx/source/backups.rst b/documentation/sphinx/source/backups.rst index 77323530ee4..459dee628c1 100644 --- a/documentation/sphinx/source/backups.rst +++ b/documentation/sphinx/source/backups.rst @@ -284,7 +284,7 @@ The ``start`` subcommand is used to start a backup. If there is already a backu :: - user@host$ fdbbackup start [-t ] -d [-z] [-s ] [--partitioned-log-experimental] [-w] [-k '[ ]']... + user@host$ fdbbackup start [-t ] -d [-z] [-s ] [--mutation-log-type partitioned-log-experimental] [-w] [-k '[ ]']... ``-z`` Perform the backup continuously rather than terminating once a restorable backup is achieved. Database mutations within the backup's target key ranges will be continuously written to the backup as well as repeated inconsistent snapshots at the configured snapshot rate. @@ -295,7 +295,7 @@ The ``start`` subcommand is used to start a backup. If there is already a backu ``--initial-snapshot-interval `` Specifies the duration, in seconds, of the first inconsistent snapshot written to the backup. The default is 0, which means as fast as possible. -``--partitioned-log-experimental`` +``--mutation-log-type partitioned-log-experimental`` Specifies the backup uses the partitioned mutation logs generated by backup workers. Since FDB version 6.3, this option is experimental and requires using fast restore for restoring the database from the generated files. The default is to use non-partitioned mutation logs generated by backup agents. ``-w`` diff --git a/documentation/sphinx/source/clang-format.rst b/documentation/sphinx/source/clang-format.rst new file mode 100644 index 00000000000..52ecbf41ce4 --- /dev/null +++ b/documentation/sphinx/source/clang-format.rst @@ -0,0 +1,68 @@ +############ +Clang-Format +############ + +``clang-format`` enforces consistent code style across the FoundationDB codebase. It runs as part of CI on every pull request. If your changes introduce formatting violations, the PR build will fail. + +Configuration is in the ``.clang-format`` file at the repository root, based on the Mozilla style with FDB-specific adjustments (120 column limit, tab width 4, etc.). + +Running clang-format locally +============================ + +Format only your changed lines +------------------------------- + +Use ``git clang-format`` to format only the lines you changed, avoiding reformatting untouched code: + +.. code-block:: shell + + # Format staged changes (before committing) + git clang-format + + # Format changes between your branch and main + git clang-format origin/main + + # Preview what would change (dry run) + git clang-format --diff origin/main + +Format specific files +--------------------- + +If ``git clang-format`` complains about unstaged changes, or you want to format an entire file (not just your changed lines), use ``clang-format`` directly: + +.. code-block:: shell + + clang-format -i path/to/file.cpp + + # Format multiple files + clang-format -i fdbserver/DataDistribution.actor.cpp fdbclient/SystemData.cpp + +.. note:: + + This reformats the entire file, not just your changes. This may produce a large diff if the file was not previously formatted. + +IDE integration +=============== + +**VS Code** (with clangd extension): + +1. Open Settings (Cmd+,) +2. Set **Editor: Default Formatter** to ``llvm-vs-code-extensions.vscode-clangd`` +3. Check **Editor: Format On Save** +4. Set **Editor: Format On Save Mode** to ``file`` + +Files will be automatically formatted on save using the ``.clang-format`` configuration. + +Key style rules +=============== + +The ``.clang-format`` configuration enforces: + +* **Column limit**: 120 characters +* **Indentation**: Tabs with width 4 +* **Braces**: Attach style (opening brace on same line) +* **Arguments**: One per line when they don't fit +* **Pointer alignment**: Left (``int* p``, not ``int *p``) +* **Short functions**: Inline functions may be on a single line + +See the ``.clang-format`` file in the repository root for the full configuration. diff --git a/documentation/sphinx/source/clang-tidy.rst b/documentation/sphinx/source/clang-tidy.rst new file mode 100644 index 00000000000..145a7209bb2 --- /dev/null +++ b/documentation/sphinx/source/clang-tidy.rst @@ -0,0 +1,186 @@ +########## +Clang-Tidy +########## + +``clang-tidy`` is a static analysis tool that detects common programming errors, enforces coding standards, and suggests modern C++ improvements. +It runs as part of CI on every pull request targeting a branch that has a ``.clang-tidy`` file (currently only ``main``). Findings are reported in the build log but do not currently fail the build. To enforce failures, add ``WarningsAsErrors: '*'`` to ``.clang-tidy``. + +This guide explains how to run ``clang-tidy`` locally so you can fix issues before pushing. + +What clang-tidy checks +====================== + +FoundationDB enables 13 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: + +* **3 Bugprone rules** -- catch potential runtime errors (e.g., ``bugprone-use-after-move``) +* **4 Modernize rules** -- encourage modern C++ practices (e.g., ``modernize-use-auto``, ``modernize-use-override``) +* **1 Performance rule** -- avoid unnecessary copies (``performance-for-range-copy``) +* **5 Readability rules** -- improve code clarity (e.g., ``readability-container-contains``, ``readability-container-size-empty``) + +Basic examples of ``clang-tidy`` style and performance improvement changes: + +.. code-block:: cpp + + // Use empty() instead of size() == 0 + -if (str.size() == 0) { + +if (str.empty()) { + + // Use contains() instead of count() > 0 + -if (classPath.count(path) > 0) { + +if (classPath.contains(path)) { + + // Avoid unnecessary copies in range-for loops + -for (auto state : states) + +for (const auto& state : states) + + // Remove unnecessary const on return types + -const Key keyServersKey(const KeyRef& k) { + +Key keyServersKey(const KeyRef& k) { + +clang-tidy vs :doc:`clang-format` +================================= + +Both run in CI but serve different purposes: + +.. list-table:: + :header-rows: 1 + :widths: 15 40 45 + + * - Feature + - ``clang-format`` (Code Style) + - ``clang-tidy`` (Code Quality) + * - **Focus** + - Whitespace, indentation, braces + - Bugs, performance, modern practices + * - **Speed** + - Very fast (syntax-only) + - Slower (requires compilation database) + * - **Action** + - Modifies files in-place (``-i``) + - Reports violations (read-only) + * - **Example** + - ``if(x){...}`` to ``if (x) { ... }`` + - ``vec.size() == 0`` to ``vec.empty()`` + +Running clang-tidy locally +========================== + +Step 1: Generate the compilation database +------------------------------------------ + +``clang-tidy`` needs a ``compile_commands.json`` file to understand include paths and macros. Add ``-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`` when you configure your build: + +.. code-block:: shell + + cmake -B ~/build_output -S . -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +Then symlink it to your source root: + +.. code-block:: shell + + ln -sf ~/build_output/compile_commands.json . + +.. note:: + + A full build is recommended so that generated files (e.g., ``.actor.g.cpp`` headers) exist and include paths resolve correctly. Without a build, ``clang-tidy`` may report false errors on files that depend on generated code. If your ``compile_commands.json`` was generated on a different machine (e.g., Okteto), fix the paths with: + + .. code-block:: shell + + sed -i '' 's|/root/src/foundationdb/|/Users/YOU/checkouts/foundationdb/|g' compile_commands.json + +Step 2: Locate the helper script +-------------------------------- + +``clang-tidy-diff.py`` runs ``clang-tidy`` only on the lines changed in your diff, avoiding noise from existing code you did not touch. + +On macOS (with Homebrew LLVM): + +.. code-block:: shell + + export TIDY_DIFF=$(find $(brew --prefix llvm)/share/clang -name "clang-tidy-diff.py") + +On Linux: + +.. code-block:: shell + + export TIDY_DIFF=$(find /usr/lib/llvm-*/share/clang -name "clang-tidy-diff.py" | head -n 1) + +To make this permanent, add to your ``~/.bashrc`` or ``~/.zshrc``: + +.. code-block:: shell + + alias fdb-tidy='python3 $(find $(brew --prefix llvm 2>/dev/null || echo "/usr/lib/llvm-*") -name "clang-tidy-diff.py" | head -n 1) -p 1 -path .' + +Step 3: Run against your changes +--------------------------------- + +Check all changes between your branch and ``main``: + +.. code-block:: shell + + git diff -U0 origin/main...HEAD | grep -v -E '\.actor\.cpp' | python3 "$TIDY_DIFF" -p 1 -path . + + # Or with the alias: + git diff -U0 origin/main...HEAD | grep -v -E '\.actor\.cpp' | fdb-tidy + +Check a specific commit: + +.. code-block:: shell + + git show -U0 | python3 "$TIDY_DIFF" -p 1 -path . + +.. important:: + + Use ``-U0`` (no context lines). Without it, ``clang-tidy`` may lint surrounding lines that you did not change, reporting pre-existing issues. + +Understanding the flags +----------------------- + +The ``clang-tidy-diff.py`` script has different flag conventions from ``clang-tidy`` itself: + +* ``-p 1`` -- strip one directory prefix from git diff paths (``a/src/main.cpp`` becomes ``src/main.cpp``) +* ``-path .`` -- directory containing ``compile_commands.json`` +* ``-quiet`` -- suppress per-file warning count summaries +* ``-j N`` -- run N clang-tidy instances in parallel + +.. warning:: + + In the standard ``clang-tidy`` command, ``-p`` points to the build directory. In ``clang-tidy-diff.py``, ``-path`` points to the build directory and ``-p`` is for path stripping. + +Running clang-tidy during builds (CMake integration) +===================================================== + +FoundationDB's CMake build supports running ``clang-tidy`` automatically during C/C++ compilation: + +.. code-block:: shell + + cmake -S . -B build -G Ninja -DUSE_CLANG_TIDY=ON + ninja -C build fdbserver + +This runs ``clang-tidy`` on every file as it compiles. It is slower than using ``clang-tidy-diff.py`` on your diff but catches issues in all compiled code. + +Optional CMake variables: + +* ``CLANG_TIDY`` -- path to the ``clang-tidy`` executable (auto-detected by default) +* ``CLANG_TIDY_EXTRA_ARGS`` -- additional space-separated arguments passed to ``clang-tidy`` + +Known limitations +----------------- + +**``.actor.cpp`` files cannot be analyzed.** These files use FoundationDB's custom actor compiler syntax (``ACTOR``, ``wait()``, ``state``) that ``clang-tidy`` cannot parse. Exclude them from your diff when running locally: + +Quick reference +=============== + +.. code-block:: shell + + git diff -U0 origin/main...HEAD | grep -v -E '\.actor\.cpp' | python3 "$TIDY_DIFF" -p 1 -path . + +**GCC-built compile_commands.json with clang-tidy.** If your ``compile_commands.json`` was generated with GCC, it may contain GCC-specific flags that ``clang-tidy`` (which uses the clang frontend) does not recognize. Add ``-extra-arg=-Wno-unknown-warning-option`` to suppress these errors: + +.. code-block:: shell + + git diff -U0 origin/main...HEAD | python3 "$TIDY_DIFF" -p 1 -path . -extra-arg=-Wno-unknown-warning-option + +Quick reference diff --git a/documentation/sphinx/source/command-line-interface.rst b/documentation/sphinx/source/command-line-interface.rst index 779a1975c55..390c271e33a 100644 --- a/documentation/sphinx/source/command-line-interface.rst +++ b/documentation/sphinx/source/command-line-interface.rst @@ -64,7 +64,7 @@ The ``commit`` command commits the current transaction. Any sets or clears execu configure --------- -The ``configure`` command changes the database configuration. Its syntax is ``configure [new|tss] [single|double|triple|three_data_hall|three_datacenter] [ssd|memory] [grv_proxies=] [commit_proxies=] [resolvers=] [logs=] [count=] [perpetual_storage_wiggle=] [perpetual_storage_wiggle_locality=<:|0>] [storage_migration_type={disabled|aggressive|gradual}]``. +The ``configure`` command changes the database configuration. Its syntax is ``configure [new|tss] [single|double|triple|three_data_hall|three_datacenter] [ssd|memory] [grv_proxies=] [commit_proxies=] [resolvers=] [logs=] [count=] [perpetual_storage_wiggle=] [perpetual_storage_wiggle_locality=<:|0>] [perpetual_storage_wiggle_engine=] [storage_migration_type={disabled|aggressive|gradual}]``. The ``new`` option, if present, initializes a new database with the given configuration rather than changing the configuration of an existing one. When ``new`` is used, both a redundancy mode and a storage engine must be specified. diff --git a/documentation/sphinx/source/developer-tools.rst b/documentation/sphinx/source/developer-tools.rst new file mode 100644 index 00000000000..15a100c6382 --- /dev/null +++ b/documentation/sphinx/source/developer-tools.rst @@ -0,0 +1,13 @@ +############### +Developer Tools +############### + +Tools and guides for FoundationDB contributors. + +.. toctree:: + :maxdepth: 1 + + local-dev + clang-format + clang-tidy + internal-dev-tools diff --git a/documentation/sphinx/source/global-configuration.rst b/documentation/sphinx/source/global-configuration.rst index 663ad26eb4e..56316e7c52a 100644 --- a/documentation/sphinx/source/global-configuration.rst +++ b/documentation/sphinx/source/global-configuration.rst @@ -79,9 +79,9 @@ Values must always be encoded according to the :ref:`api-python-tuple-layer`. .. code-block:: cpp - // In GlobalConfig.actor.h + // In GlobalConfig.h extern const KeyRef myGlobalConfigKey; - // In GlobalConfig.actor.cpp + // In GlobalConfig.cpp const KeyRef myGlobalConfigKey = "config/key"_sr; // When you want to set the value.. diff --git a/documentation/sphinx/source/ha-write-path.rst b/documentation/sphinx/source/ha-write-path.rst index deea5d0b931..92d86b363e9 100644 --- a/documentation/sphinx/source/ha-write-path.rst +++ b/documentation/sphinx/source/ha-write-path.rst @@ -118,9 +118,9 @@ tLogs also maintain two properties: At primary SS ------------- -**Primary tLog of a SS.** Since a SS’s tag is identically mapped to one tLog. The tLog has all mutations for the SS and is the primary tLog for the SS. When the SS peeks data from tLogs, it will prefer to peek data from its primary tLog. If the primary tLog crashes, it will contact the rest of tLogs, ask for mutations with the SS’s tag, and merge them together. This complex merge operation is abstracted in the TagPartitionedLogSystem interface. +**Primary tLog of a SS.** Since a SS’s tag is identically mapped to one tLog. The tLog has all mutations for the SS and is the primary tLog for the SS. When the SS peeks data from tLogs, it will prefer to peek data from its primary tLog. If the primary tLog crashes, it will contact the rest of tLogs, ask for mutations with the SS’s tag, and merge them together. This complex merge operation is abstracted in the LogSystem interface. -**Pulling data from tLogs.** Each SS in the primary DC keeps pulling mutations, whose tag is the SS’s tag, from tLogs. Once mutations before a version V1 are made durable on a SS, the SS pops the tag upto the version V1 from *all* tLogs. The pop operation is an RPC to tLogs through the TagPartitionedLogSystem interface. +**Pulling data from tLogs.** Each SS in the primary DC keeps pulling mutations, whose tag is the SS’s tag, from tLogs. Once mutations before a version V1 are made durable on a SS, the SS pops the tag upto the version V1 from *all* tLogs. The pop operation is an RPC to tLogs through the LogSystem interface. Since the mutation m1 has three tags for primary SSes, the mutation will be made durable on three primary SSes. This marks the end of the mutation’s journey in the primary DC. @@ -164,7 +164,7 @@ https://github.com/apple/foundationdb/blob/7eabdf784a21bca102f84e7eaf14bafc54605 Mutation Serialization (WiP) ============================ -This section will go into detail on how mutations are serialized as preparation for ingestion into the TagPartitionedLogSystem. This has also been covered at: +This section will go into detail on how mutations are serialized as preparation for ingestion into the LogSystem. This has also been covered at: https://drive.google.com/file/d/1OaP5bqH2kst1VxD6RWj8h2cdr9rhhBHy/view diff --git a/documentation/sphinx/source/index.rst b/documentation/sphinx/source/index.rst index 167bebda43f..55f3d733a95 100644 --- a/documentation/sphinx/source/index.rst +++ b/documentation/sphinx/source/index.rst @@ -44,13 +44,14 @@ The latest changes are detailed in :ref:`release-notes`. The documentation has t * :doc:`visibility` contains documentation related to Visibility into FoundationDB. +* :doc:`developer-tools` contains documentation for FoundationDB contributors, including local development setup, static analysis, and internal testing tools. + .. toctree:: :maxdepth: 1 :titlesonly: :hidden: - local-dev - internal-dev-tools + developer-tools why-foundationdb technical-overview client-design diff --git a/documentation/sphinx/source/mr-status-json-schemas.rst.inc b/documentation/sphinx/source/mr-status-json-schemas.rst.inc index 060ff759ec0..a6df96e873e 100644 --- a/documentation/sphinx/source/mr-status-json-schemas.rst.inc +++ b/documentation/sphinx/source/mr-status-json-schemas.rst.inc @@ -348,6 +348,7 @@ "trace_log_file_write_error", "trace_log_could_not_create_file", "trace_log_writer_thread_unresponsive", + "exclude_from_tlog_recruitment_low_disk", "process_error", "io_error", "io_timeout", @@ -625,7 +626,8 @@ "incorrect_cluster_file_contents", "trace_log_file_write_error", "trace_log_could_not_create_file", - "trace_log_writer_thread_unresponsive" + "trace_log_writer_thread_unresponsive", + "exclude_from_tlog_recruitment_low_disk" ] }, "description":"Cluster file contents do not match current cluster connection string. Verify cluster file is writable and has not been overwritten externally." diff --git a/documentation/sphinx/source/read-write-path.rst b/documentation/sphinx/source/read-write-path.rst index 8257f8905f9..277950b832f 100644 --- a/documentation/sphinx/source/read-write-path.rst +++ b/documentation/sphinx/source/read-write-path.rst @@ -179,7 +179,7 @@ Implementation of FDB read path * Proxy confirm queuing system is alive: https://github.com/apple/foundationdb/blob/4b0fba6ea89b51b82df7868ca24b81f6997db4e4/fdbserver/MasterProxyServer.actor.cpp#L1199 * How is confirmEpochLive(..) implemented for the above item: - https://github.com/apple/foundationdb/blob/4b0fba6ea89b51b82df7868ca24b81f6997db4e4/fdbserver/TagPartitionedLogSystem.actor.cpp#L1216-L1225 + https://github.com/apple/foundationdb/blob/main/fdbserver/logsystem/LogSystem.cpp * **Step 4 (Locality request)**: https://github.com/apple/foundationdb/blob/4b0fba6ea89b51b82df7868ca24b81f6997db4e4/fdbclient/NativeAPI.actor.cpp#L1312-L1313 diff --git a/documentation/sphinx/source/release-notes/release-notes-700.rst b/documentation/sphinx/source/release-notes/release-notes-700.rst index de56447519c..e350341fb0a 100644 --- a/documentation/sphinx/source/release-notes/release-notes-700.rst +++ b/documentation/sphinx/source/release-notes/release-notes-700.rst @@ -32,7 +32,7 @@ Features Performance ----------- * Improved Deque copy performance. `(PR #3197) `_ -* Increased performance of dr_agent when copying the mutation log. The ``COPY_LOG_BLOCK_SIZE``, ``COPY_LOG_BLOCKS_PER_TASK``, ``COPY_LOG_PREFETCH_BLOCKS``, ``COPY_LOG_READ_AHEAD_BYTES`` and ``COPY_LOG_TASK_DURATION_NANOS`` knobs can be set. `(PR #3436) `_ +* Increased performance of dr_agent when copying the mutation log. The ``COPY_LOG_BLOCK_SIZE``, ``COPY_LOG_BLOCKS_PER_TASK``, ``COPY_LOG_PREFETCH_BLOCKS``, ``COPY_LOG_READ_AHEAD_BYTES`` and ``COPY_LOG_TASK_DURATION_SECONDS`` knobs can be set. `(PR #3436) `_ `(PR #12733) `_ * Added multiple new microbenchmarks for PromiseStream, Reference, IRandom, and timer, as well as support for benchmarking actors. `(PR #3590) `_ * Use xxhash3 for SQLite page checksums. `(PR #4075) `_ * fdbserver now uses jemalloc on Linux instead of the system malloc. `(PR #4222) `_ diff --git a/documentation/sphinx/source/release-notes/release-notes-730.rst b/documentation/sphinx/source/release-notes/release-notes-730.rst index f00ea980918..72935f9148c 100644 --- a/documentation/sphinx/source/release-notes/release-notes-730.rst +++ b/documentation/sphinx/source/release-notes/release-notes-730.rst @@ -2,6 +2,16 @@ Release Notes ############# +7.3.77 +====== +* Same as 7.3.76 release with AVX enabled. + +7.3.76 +====== +* Fixed peer disconnect detection in waitValueOrSignal so dead connections are detected immediately instead of hanging indefinitely. `(PR #12935) `_ +* Made getTeamByServers O(1) in time which was sometimes causing Data Distributor (DD) to get stuck during initialization when shard_encode_location_metadata was enabled. `(PR #12938) `_ +* Added miscellaneous observability improvements for CPU and memory usage, DD startup, and S3 backup subsystems. `(PR #12937) `_, `(PR #12913) `_, and `(PR #12997) `_ + 7.3.75 ====== * Same as 7.3.74 release with AVX enabled. diff --git a/documentation/tutorial/dining_philosophers.actor.cpp b/documentation/tutorial/dining_philosophers.actor.cpp index b2825fd57cd..8f6bc7065f2 100644 --- a/documentation/tutorial/dining_philosophers.actor.cpp +++ b/documentation/tutorial/dining_philosophers.actor.cpp @@ -24,7 +24,7 @@ #include "flow/DeterministicRandom.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include #include #include @@ -102,8 +102,8 @@ struct GetForkRequest { ForkState forkState; ReplyPromise reply; - GetForkRequest(ForkState fork_state) : forkState(fork_state) {} - GetForkRequest() {} + explicit GetForkRequest(ForkState fork_state) : forkState(fork_state) {} + GetForkRequest() = default; template void serialize(Ar& ar) { @@ -116,8 +116,8 @@ struct ReleaseForkRequest { ForkState forkState; ReplyPromise reply; - ReleaseForkRequest(ForkState fork_state) : forkState(fork_state) {} - ReleaseForkRequest() {} + explicit ReleaseForkRequest(ForkState fork_state) : forkState(fork_state) {} + ReleaseForkRequest() = default; template void serialize(Ar& ar) { diff --git a/documentation/tutorial/make_h2o.actor.cpp b/documentation/tutorial/make_h2o.actor.cpp index d3f0977e96a..d3739750450 100644 --- a/documentation/tutorial/make_h2o.actor.cpp +++ b/documentation/tutorial/make_h2o.actor.cpp @@ -24,7 +24,7 @@ #include "flow/DeterministicRandom.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "flow/actorcompiler.h" #include diff --git a/documentation/tutorial/play.actor.cpp b/documentation/tutorial/play.actor.cpp index 12eef72324a..a8ee1db16b8 100644 --- a/documentation/tutorial/play.actor.cpp +++ b/documentation/tutorial/play.actor.cpp @@ -22,7 +22,7 @@ #include "flow/Arena.h" #include "flow/flow.h" #include "flow/Platform.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "flow/actorcompiler.h" #include diff --git a/documentation/tutorial/play_network.actor.cpp b/documentation/tutorial/play_network.actor.cpp index 707ccde612c..3eafaf64f31 100644 --- a/documentation/tutorial/play_network.actor.cpp +++ b/documentation/tutorial/play_network.actor.cpp @@ -22,7 +22,7 @@ #include "flow/Arena.h" #include "flow/flow.h" #include "flow/Platform.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "flow/actorcompiler.h" #include diff --git a/documentation/tutorial/print_in_order.actor.cpp b/documentation/tutorial/print_in_order.actor.cpp index 97adf841ebc..174f202bfe4 100644 --- a/documentation/tutorial/print_in_order.actor.cpp +++ b/documentation/tutorial/print_in_order.actor.cpp @@ -24,7 +24,7 @@ #include "flow/DeterministicRandom.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "flow/actorcompiler.h" #include diff --git a/documentation/tutorial/tutorial.actor.cpp b/documentation/tutorial/tutorial.actor.cpp index 29888d0b69e..5a706a6f837 100644 --- a/documentation/tutorial/tutorial.actor.cpp +++ b/documentation/tutorial/tutorial.actor.cpp @@ -25,7 +25,7 @@ #include "flow/DeterministicRandom.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include #include #include @@ -518,20 +518,6 @@ ACTOR Future fdbClient() { } } -ACTOR Future fdbStatusStresser() { - state Database db = Database::createDatabase(clusterFile, 300); - state ReadYourWritesTransaction tx(db); - state Key statusJson(std::string("\xff\xff/status/json")); - loop { - try { - tx.reset(); - Optional _ = wait(tx.get(statusJson)); - } catch (Error& e) { - wait(tx.onError(e)); - } - } -} - std::unordered_map()>> actors = { { "timer", &simpleTimer }, // ./tutorial timer { "promiseDemo", &promiseDemo }, // ./tutorial promiseDemo @@ -543,9 +529,8 @@ std::unordered_map()>> actors = { { "multipleClients", &multipleClients }, // ./tutorial -s 127.0.0.1:6666 multipleClients { "fdbClientStream", &fdbClientStream }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClientStream { "fdbClientGetRange", &fdbClientGetRange }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClientGetRange - { "fdbClient", &fdbClient }, // ./tutorial -C $CLUSTER_FILE_PATH fdbClient - { "fdbStatusStresser", &fdbStatusStresser } -}; // ./tutorial -C $CLUSTER_FILE_PATH fdbStatusStresser + { "fdbClient", &fdbClient } // ./tutorial -C $CLUSTER_FILE_PATH fdbClient +}; int main(int argc, char* argv[]) { bool isServer = false; diff --git a/fdbbackup/CMakeLists.txt b/fdbbackup/CMakeLists.txt index ff0b0c2d559..63e5b394fba 100644 --- a/fdbbackup/CMakeLists.txt +++ b/fdbbackup/CMakeLists.txt @@ -1,22 +1,22 @@ set(FDBBACKUP_SRCS Decode.cpp - backup.actor.cpp) + backup.cpp) add_flow_target(EXECUTABLE NAME fdbbackup SRCS ${FDBBACKUP_SRCS}) -target_include_directories(fdbbackup PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbbackup PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(fdbbackup PRIVATE fdbclient) set(FDBCONVERT_SRCS - FileConverter.actor.cpp) + FileConverter.cpp) add_flow_target(EXECUTABLE NAME fdbconvert SRCS ${FDBCONVERT_SRCS}) -target_include_directories(fdbconvert PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbconvert PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(fdbconvert PRIVATE fdbclient) set(FDBDECODE_SRCS Decode.cpp - FileDecoder.actor.cpp) + FileDecoder.cpp) add_flow_target(EXECUTABLE NAME fdbdecode SRCS ${FDBDECODE_SRCS}) -target_include_directories(fdbdecode PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbdecode PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(fdbdecode PRIVATE fdbclient) if(NOT OPEN_FOR_IDE) @@ -39,19 +39,25 @@ if(NOT OPEN_FOR_IDE) symlink_files( LOCATION packages/bin SOURCE fdbbackup - TARGETS fdbdr dr_agent backup_agent fdbrestore fastrestore_tool) + TARGETS fdbdr dr_agent backup_agent fdbrestore) symlink_files( LOCATION bin SOURCE fdbbackup - TARGETS fdbdr dr_agent backup_agent fdbrestore fastrestore_tool) + TARGETS fdbdr dr_agent backup_agent fdbrestore) - # Test version of backup.actor.cpp without main() function - add_flow_target(EXECUTABLE NAME backup_tests SRCS backup.actor.cpp) - target_include_directories(backup_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") + # Test version of backup.cpp without main() function + add_flow_target(EXECUTABLE NAME backup_tests SRCS backup.cpp) + target_include_directories(backup_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(backup_tests PRIVATE fdbclient) target_compile_definitions(backup_tests PRIVATE EXCLUDE_MAIN_FUNCTION=1) add_test(NAME BackupTests COMMAND backup_tests) + # Test version of FileDecoder.cpp without main() function + add_flow_target(EXECUTABLE NAME fdbdecode_tests SRCS Decode.cpp FileDecoder.cpp) + target_include_directories(fdbdecode_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") + target_link_libraries(fdbdecode_tests PRIVATE fdbclient) + target_compile_definitions(fdbdecode_tests PRIVATE EXCLUDE_MAIN_FUNCTION=1) + add_test(NAME FileDecoderTests COMMAND fdbdecode_tests) endif() if (GPERFTOOLS_FOUND) @@ -62,7 +68,7 @@ endif() if (NOT WIN32 AND NOT OPEN_FOR_IDE) enable_testing() add_test(NAME dir_backup_tests COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/dir_backup_test.sh ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) - add_test(NAME s3_backup_tests COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/s3_backup_test.sh ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} --encrypt-at-random) + add_test(NAME blob_backup_restore_tests COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/blob_backup_restore_test.sh ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # Note: No --encrypt flag - BulkLoad doesn't support encryption yet add_test(NAME s3_backup_bulkdump_bulkload_tests COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/s3_backup_bulkdump_bulkload.sh ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) endif() diff --git a/fdbbackup/FileConverter.actor.cpp b/fdbbackup/FileConverter.cpp similarity index 85% rename from fdbbackup/FileConverter.actor.cpp rename to fdbbackup/FileConverter.cpp index 7ba2ae04e7c..27cf69243c1 100644 --- a/fdbbackup/FileConverter.actor.cpp +++ b/fdbbackup/FileConverter.cpp @@ -1,5 +1,5 @@ /* - * FileConverter.actor.cpp + * FileConverter.cpp * * This source file is part of the FoundationDB open source project * @@ -26,13 +26,12 @@ #include #include -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/BackupContainer.h" #include "fdbclient/MutationList.h" #include "flow/flow.h" #include "flow/serialize.h" #include "fdbclient/BuildFlags.h" -#include "flow/actorcompiler.h" // has to be last include namespace file_converter { @@ -254,7 +253,7 @@ struct MutationFilesReadProgress : public ReferenceCountedfd->getFilename() << " " << fp->mutations.size() << " mutations"; - if (fp->mutations.size() > 0) { + if (!fp->mutations.empty()) { std::cout << ", range " << fp->mutations[0].version.toString() << " " << fp->mutations.back().version.toString() << "\n"; } else { @@ -277,15 +276,15 @@ struct MutationFilesReadProgress : public ReferenceCounted getNextMutation() { return getMutationImpl(this); } - ACTOR static Future getMutationImpl(MutationFilesReadProgress* self) { + static Future getMutationImpl(MutationFilesReadProgress* self) { ASSERT(!self->fileProgress.empty() && !self->fileProgress[0]->mutations.empty()); - state Reference fp = self->fileProgress[0]; - state VersionedData data = fp->mutations[0]; + Reference fp = self->fileProgress[0]; + VersionedData data = fp->mutations[0]; fp->mutations.erase(fp->mutations.begin()); if (fp->mutations.empty()) { // decode one more block - wait(decodeToVersion(fp, /*version=*/0, self->endVersion, self->getLogFile(fp->idx))); + co_await decodeToVersion(fp, /*version=*/0, self->endVersion, self->getLogFile(fp->idx)); } if (fp->empty()) { @@ -299,7 +298,7 @@ struct MutationFilesReadProgress : public ReferenceCountedfileProgress[i - 1], self->fileProgress[i]); } } - return data; + co_return data; } LogFile& getLogFile(int index) { return files[index]; } @@ -307,13 +306,12 @@ struct MutationFilesReadProgress : public ReferenceCounted openLogFiles(Reference container) { return openLogFilesImpl(this, container); } // Opens log files in the progress and starts decoding until the beginVersion is seen. - ACTOR static Future openLogFilesImpl(MutationFilesReadProgress* progress, - Reference container) { - state std::vector>> asyncFiles; + static Future openLogFilesImpl(MutationFilesReadProgress* progress, Reference container) { + std::vector>> asyncFiles; for (const auto& file : progress->files) { asyncFiles.push_back(container->readFile(file.fileName)); } - wait(waitForAll(asyncFiles)); // open all files + co_await waitForAll(asyncFiles); // open all files // Attempt decode the first few blocks of log files until beginVersion is consumed std::vector> fileDecodes; @@ -324,37 +322,37 @@ struct MutationFilesReadProgress : public ReferenceCountedbeginVersion, progress->endVersion, progress->getLogFile(i))); } - wait(waitForAll(fileDecodes)); + co_await waitForAll(fileDecodes); progress->sortAndRemoveEmpty(); - - return Void(); } // Decodes the file until EOF or an mutation >= minVersion and saves these mutations. // Skip mutations >= maxVersion. - ACTOR static Future decodeToVersion(Reference fp, - Version minVersion, - Version maxVersion, - LogFile file) { - if (fp->empty()) - return Void(); + static Future decodeToVersion(Reference fp, + Version minVersion, + Version maxVersion, + LogFile file) { + if (fp->empty()) { + co_return; + } - if (!fp->mutations.empty() && fp->mutations.back().version.version >= minVersion) - return Void(); + if (!fp->mutations.empty() && fp->mutations.back().version.version >= minVersion) { + co_return; + } - state int64_t len; + int64_t len = 0; try { // Read block by block until we see the minVersion - loop { + while (true) { len = std::min(file.blockSize, file.fileSize - fp->offset); if (len == 0) { fp->eof = true; - return Void(); + co_return; } - state Standalone buf = makeString(len); - int rLen = wait(fp->fd->read(mutateString(buf), len, fp->offset)); + Standalone buf = makeString(len); + int rLen = co_await fp->fd->read(mutateString(buf), len, fp->offset); if (len != rLen) throw restore_bad_read(); @@ -365,7 +363,6 @@ struct MutationFilesReadProgress : public ReferenceCounteddecodeBlock(buf, rLen, minVersion, maxVersion)) break; } - return Void(); } catch (Error& e) { TraceEvent(SevWarn, "CorruptedLogFileBlock") .error(e) @@ -382,7 +379,7 @@ struct MutationFilesReadProgress : public ReferenceCounted f, int bsize) : file(f), blockSize(bsize) {} @@ -400,50 +397,47 @@ struct LogFileWriter { } // Start a new block if needed, then write the key and value - ACTOR static Future writeKV_impl(LogFileWriter* self, Key k, Value v) { + static Future writeKV_impl(LogFileWriter* self, Key k, Value v) { // If key and value do not fit in this block, end it and start a new one int toWrite = sizeof(int32_t) + k.size() + sizeof(int32_t) + v.size(); if (self->file->size() + toWrite > self->blockEnd) { // Write padding if needed int bytesLeft = self->blockEnd - self->file->size(); if (bytesLeft > 0) { - state Value paddingFFs = fileBackup::makePadding(bytesLeft); - wait(self->file->append(paddingFFs.begin(), bytesLeft)); + Value paddingFFs = fileBackup::makePadding(bytesLeft); + co_await self->file->append(paddingFFs.begin(), bytesLeft); } // Set new blockEnd self->blockEnd += self->blockSize; // write Header - wait(self->file->append((uint8_t*)&BACKUP_AGENT_MLOG_VERSION, sizeof(BACKUP_AGENT_MLOG_VERSION))); + co_await self->file->append((uint8_t*)&BACKUP_AGENT_MLOG_VERSION, sizeof(BACKUP_AGENT_MLOG_VERSION)); } - wait(self->file->appendStringRefWithLen(k)); - wait(self->file->appendStringRefWithLen(v)); + co_await self->file->appendStringRefWithLen(k); + co_await self->file->appendStringRefWithLen(v); // At this point we should be in whatever the current block is or the block size is too small if (self->file->size() > self->blockEnd) throw backup_bad_block_size(); - - return Void(); } Future writeKV(Key k, Value v) { return writeKV_impl(this, k, v); } // Adds a new mutation to an internal buffer and writes out when encountering // a new commitVersion or exceeding the block size. - ACTOR static Future addMutation(LogFileWriter* self, Version commitVersion, MutationListRef mutations) { - state Standalone value = BinaryWriter::toValue(mutations, IncludeVersion()); + static Future addMutation(LogFileWriter* self, Version commitVersion, MutationListRef mutations) { + Standalone value = BinaryWriter::toValue(mutations, IncludeVersion()); - state int part = 0; + int part = 0; for (; part * CLIENT_KNOBS->MUTATION_BLOCK_SIZE < value.size(); part++) { StringRef partBuf = value.substr( part * CLIENT_KNOBS->MUTATION_BLOCK_SIZE, std::min(value.size() - part * CLIENT_KNOBS->MUTATION_BLOCK_SIZE, CLIENT_KNOBS->MUTATION_BLOCK_SIZE)); Standalone key = getBlockKey(commitVersion, part); - wait(writeKV_impl(self, key, partBuf)); + co_await writeKV_impl(self, key, partBuf); } - return Void(); } private: @@ -452,13 +446,12 @@ struct LogFileWriter { int64_t blockEnd = 0; }; -ACTOR Future convert(ConvertParams params) { - state Reference container = - IBackupContainer::openContainer(params.container_url, params.proxy, {}); - state BackupFileList listing = wait(container->dumpFileList()); +Future convert(ConvertParams params) { + Reference container = IBackupContainer::openContainer(params.container_url, params.proxy, {}, 0); + BackupFileList listing = co_await container->dumpFileList(); std::sort(listing.logs.begin(), listing.logs.end()); TraceEvent("Container").detail("URL", params.container_url).detail("Logs", listing.logs.size()); - state BackupDescription desc = wait(container->describeBackup()); + BackupDescription desc = co_await container->describeBackup(); std::cout << "\n" << desc.toString() << "\n"; // std::cout << "Using Protocol Version: 0x" << std::hex << g_network->protocolVersion().version() << std::dec << @@ -467,24 +460,24 @@ ACTOR Future convert(ConvertParams params) { std::vector logs = getRelevantLogFiles(listing.logs, params.begin, params.end); printLogFiles("Range has", logs); - state Reference progress(new MutationFilesReadProgress(logs, params.begin, params.end)); + Reference progress(new MutationFilesReadProgress(logs, params.begin, params.end)); - wait(progress->openLogFiles(container)); + co_await progress->openLogFiles(container); - state int blockSize = CLIENT_KNOBS->BACKUP_LOGFILE_BLOCK_SIZE; - state Reference outFile = wait(container->writeLogFile(params.begin, params.end, blockSize)); - state LogFileWriter logFile(outFile, blockSize); + int blockSize = CLIENT_KNOBS->BACKUP_LOGFILE_BLOCK_SIZE; + Reference outFile = co_await container->writeLogFile(params.begin, params.end, blockSize); + LogFileWriter logFile(outFile, blockSize); std::cout << "Output file: " << outFile->getFileName() << "\n"; - state MutationList list; - state Arena arena; - state Version version = invalidVersion; + MutationList list; + Arena arena; + Version version = invalidVersion; while (progress->hasMutations()) { - state VersionedData data = wait(progress->getNextMutation()); + VersionedData data = co_await progress->getNextMutation(); // emit a mutation batch to file when encounter a new version if (list.totalSize() > 0 && version != data.version.version) { - wait(LogFileWriter::addMutation(&logFile, version, list)); + co_await LogFileWriter::addMutation(&logFile, version, list); list = MutationList(); arena = Arena(); } @@ -499,12 +492,10 @@ ACTOR Future convert(ConvertParams params) { version = data.version.version; } if (list.totalSize() > 0) { - wait(LogFileWriter::addMutation(&logFile, version, list)); + co_await LogFileWriter::addMutation(&logFile, version, list); } - wait(outFile->finish()); - - return Void(); + co_await outFile->finish(); } int parseCommandLine(ConvertParams* param, CSimpleOpt* args) { @@ -623,4 +614,4 @@ int main(int argc, char** argv) { TraceEvent(SevError, "MainError").error(unknown_error()).detail("RootException", e.what()); return FDB_EXIT_MAIN_EXCEPTION; } -} \ No newline at end of file +} diff --git a/fdbbackup/FileDecoder.actor.cpp b/fdbbackup/FileDecoder.cpp similarity index 86% rename from fdbbackup/FileDecoder.actor.cpp rename to fdbbackup/FileDecoder.cpp index 71872010481..bc192a80b64 100644 --- a/fdbbackup/FileDecoder.actor.cpp +++ b/fdbbackup/FileDecoder.cpp @@ -1,5 +1,5 @@ /* - * FileDecoder.actor.cpp + * FileDecoder.cpp * * This source file is part of the FoundationDB open source project * @@ -35,13 +35,11 @@ #include "fdbclient/BuildFlags.h" #include "fdbbackup/FileConverter.h" #include "fdbbackup/Decode.h" -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/BackupContainer.h" #include "fdbclient/BackupContainerFileSystem.h" -#include "fdbclient/BuildFlags.h" #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/IKnobCollection.h" #include "fdbclient/KeyRangeMap.h" #include "fdbclient/Knobs.h" #include "fdbclient/MutationList.h" @@ -55,8 +53,6 @@ #include "flow/flow.h" #include "flow/serialize.h" -#include "flow/actorcompiler.h" // has to be last include - #define SevDecodeInfo SevVerbose extern bool g_crashOnError; @@ -103,6 +99,8 @@ void printDecodeUsage() { " --knob-KNOBNAME KNOBVALUE\n" " Changes a knob value. KNOBNAME should be lowercase.\n" " -s, --save Save a copy of downloaded files (default: not saving).\n" + " --encryption-key-file FILE\n" + " AES-128-GCM encryption key file for encrypted backups.\n" "\n"; return; } @@ -130,6 +128,7 @@ struct DecodeParams : public ReferenceCounted { Version endVersionFilter = std::numeric_limits::max(); std::vector> knobs; + Optional encryptionKeyFileName; // Returns if [begin, end) overlap with the filter range bool overlap(Version begin, Version end) const { @@ -139,6 +138,8 @@ struct DecodeParams : public ReferenceCounted { bool overlap(Version version) const { return version >= beginVersionFilter && version < endVersionFilter; } + bool validVersionFilters() { return beginVersionFilter < endVersionFilter; } + void updateRangeMap() { filters.updateFilters(prefixes); } bool matchFilters(const MutationRef& m) const { @@ -237,14 +238,17 @@ struct DecodeParams : public ReferenceCounted { s.append(", KNOB-").append(knob).append(" = ").append(value); } s.append(", SaveFile: ").append(save_file_locally ? "true" : "false"); + if (encryptionKeyFileName.present()) { + s.append(", EncryptionKeyFile: ").append(encryptionKeyFileName.get()); + } return s; } void updateKnobs() { - IKnobCollection::setupKnobs(knobs); + setupClientKnobs(knobs); // Reinitialize knobs in order to update knobs that are dependent on explicitly set knobs - IKnobCollection::getMutableGlobalKnobCollection().initialize(Randomize::False, IsSimulated::False); + initializeClientKnobs(Randomize::False, IsSimulated::False); } }; @@ -305,7 +309,7 @@ int parseDecodeCommandLine(Reference param, CSimpleOpt* args) { param->decode_range = false; } else if (ftype == "range") { param->decode_logs = false; - } else if (ftype != "both" && ftype != "") { + } else if (ftype != "both" && !ftype.empty()) { err = true; std::cerr << "ERROR: Unrecognized backup file type option: " << args->OptionArg() << "\n"; return FDB_EXIT_ERROR; @@ -394,6 +398,10 @@ int parseDecodeCommandLine(Reference param, CSimpleOpt* args) { param->save_file_locally = true; break; + case OPT_ENCRYPTION_KEY_FILE: + param->encryptionKeyFileName = args->OptionArg(); + break; + case TLSConfig::OPT_TLS_PLUGIN: args->OptionArg(); break; @@ -468,9 +476,9 @@ struct VersionedMutations { * Model a decoding progress for a mutation file. Usage is: * * DecodeProgress progress(logfile); - * wait(progress->openFile(container)); + * co_await progress.openFile(container); * while (1) { - * Optional batch = wait(progress->getNextBatch()); + * Optional batch = progress.getNextBatch(); * if (!batch.present()) break; * ... // process the batch mutations * } @@ -523,11 +531,10 @@ class DecodeProgress { return Optional(); } - ACTOR static Future openFileImpl(DecodeProgress* self, Reference container) { - Reference fd = wait(container->readFile(self->file.fileName)); - self->fd = fd; - state Standalone buf = makeString(self->file.fileSize); - int rLen = wait(self->fd->read(mutateString(buf), self->file.fileSize, 0)); + static Future openFileImpl(DecodeProgress* self, Reference container) { + self->fd = co_await container->readFile(self->file.fileName); + Standalone buf = makeString(self->file.fileSize); + int rLen = co_await self->fd->read(mutateString(buf), self->file.fileSize, 0); if (rLen != self->file.fileSize) { throw restore_bad_read(); } @@ -557,7 +564,6 @@ class DecodeProgress { } self->decodeFile(buf); - return Void(); } // Add chunks to mutationBlocksByVersion @@ -571,7 +577,7 @@ class DecodeProgress { // Reads a file a file content in the buffer, decodes it into key/value pairs, and stores these pairs. void decodeFile(const Standalone& buf) { try { - loop { + while (true) { int64_t len = std::min(file.blockSize, file.fileSize - offset); if (len == 0) { return; @@ -617,13 +623,12 @@ class DecodeRangeProgress { // Open and loads file into memory Future openFile(Reference container) { return openFileImpl(this, container); } - ACTOR static Future openFileImpl(DecodeRangeProgress* self, Reference container) { + static Future openFileImpl(DecodeRangeProgress* self, Reference container) { TraceEvent("ReadFile").detail("Name", self->file.fileName).detail("Len", self->file.fileSize); - Reference fd = wait(container->readFile(self->file.fileName)); - self->fd = fd; - state Standalone buf = makeString(self->file.fileSize); - int rLen = wait(self->fd->read(mutateString(buf), self->file.fileSize, 0)); + self->fd = co_await container->readFile(self->file.fileName); + Standalone buf = makeString(self->file.fileSize); + int rLen = co_await self->fd->read(mutateString(buf), self->file.fileSize, 0); if (rLen != self->file.fileSize) { throw restore_bad_read(); } @@ -654,13 +659,12 @@ class DecodeRangeProgress { } self->decodeFile(buf); - return Void(); } // Reads a file content in the buffer, decodes it into key/value pairs, and stores these pairs. void decodeFile(const Standalone& buf) { try { - loop { + while (true) { // process one block at a time int64_t len = std::min(file.blockSize, file.fileSize - offset); if (len == 0) { @@ -698,18 +702,18 @@ std::string hexStringRef(const StringRef& s) { return result; } -ACTOR Future process_range_file(Reference container, - RangeFile file, - UID uid, - Reference params) { +Future process_range_file(Reference container, + RangeFile file, + UID uid, + Reference params) { if (file.fileSize == 0) { TraceEvent("SkipEmptyFile", uid).detail("Name", file.fileName); - return Void(); + co_return; } - state DecodeRangeProgress progress(file, params->save_file_locally); - wait(progress.openFile(container)); + DecodeRangeProgress progress(file, params->save_file_locally); + co_await progress.openFile(container); for (auto& block : progress.blocks) { for (const auto& kv : block) { @@ -730,21 +734,19 @@ ACTOR Future process_range_file(Reference container, } } TraceEvent("ProcessRangeFileDone", uid).detail("File", file.fileName); - - return Void(); } -ACTOR Future process_file(Reference container, - LogFile file, - UID uid, - Reference params) { +Future process_file(Reference container, + LogFile file, + UID uid, + Reference params) { if (file.fileSize == 0) { TraceEvent("SkipEmptyFile", uid).detail("Name", file.fileName); - return Void(); + co_return; } - state DecodeProgress progress(file, params->save_file_locally); - wait(progress.openFile(container)); + DecodeProgress progress(file, params->save_file_locally); + co_await progress.openFile(container); while (true) { auto batch = progress.getNextBatch(); if (!batch.present()) @@ -775,21 +777,19 @@ ACTOR Future process_file(Reference container, } } TraceEvent("ProcessFileDone", uid).detail("File", file.fileName); - return Void(); } // Use the snapshot metadata to quickly identify relevant range files and // then filter by versions. -ACTOR Future> getRangeFiles(Reference bc, Reference params) { - state std::vector snapshots = - wait((dynamic_cast(bc.getPtr()))->listKeyspaceSnapshots()); - state std::vector files; +Future> getRangeFiles(Reference bc, Reference params) { + std::vector snapshots = + co_await (dynamic_cast(bc.getPtr()))->listKeyspaceSnapshots(); + std::vector files; - state int i = 0; - for (; i < snapshots.size(); i++) { + for (int i = 0; i < snapshots.size(); i++) { try { std::pair, std::map> results = - wait((dynamic_cast(bc.getPtr()))->readKeyspaceSnapshot(snapshots[i])); + co_await (dynamic_cast(bc.getPtr()))->readKeyspaceSnapshot(snapshots[i]); for (const auto& rangeFile : results.first) { const auto& keyRange = results.second.at(rangeFile.fileName); if (params->matchFilters(keyRange)) { @@ -803,14 +803,16 @@ ACTOR Future> getRangeFiles(Reference b } } } - return getRelevantRangeFiles(files, params); + co_return getRelevantRangeFiles(files, params); } -ACTOR Future decode_logs(Reference params) { - state Reference container = - IBackupContainer::openContainer(params->container_url, params->proxy, {}); - state UID uid = deterministicRandom()->randomUniqueID(); - state BackupFileList listing = wait(container->dumpFileList()); +Future decode_logs(Reference params) { + Reference container = + IBackupContainer::openContainer(params->container_url, params->proxy, params->encryptionKeyFileName, 0); + UID uid = deterministicRandom()->randomUniqueID(); + + BackupFileList listing = co_await container->dumpFileList(); + // remove partitioned logs listing.logs.erase(std::remove_if(listing.logs.begin(), listing.logs.end(), @@ -823,11 +825,14 @@ ACTOR Future decode_logs(Reference params) { TraceEvent("Container", uid).detail("URL", params->container_url).detail("Logs", listing.logs.size()); TraceEvent("DecodeParam", uid).setMaxFieldLength(100000).detail("Value", params->toString()); - BackupDescription desc = wait(container->describeBackup()); + BackupDescription desc = co_await container->describeBackup(); + + container->setEncryptionBlockSize(desc.encryptionBlockSize); + std::cout << "\n" << desc.toString() << "\n"; - state std::vector logFiles; - state std::vector rangeFiles; + std::vector logFiles; + std::vector rangeFiles; if (params->decode_logs) { logFiles = getRelevantLogFiles(listing.logs, params); @@ -836,7 +841,7 @@ ACTOR Future decode_logs(Reference params) { if (params->decode_range) { // rangeFiles = getRelevantRangeFiles(filteredRangeFiles, params); - std::vector files = wait(getRangeFiles(container, params)); + std::vector files = co_await getRangeFiles(container, params); rangeFiles = files; printLogFiles("Relevant range files are: ", rangeFiles); } @@ -844,14 +849,14 @@ ACTOR Future decode_logs(Reference params) { TraceEvent("TotalFiles", uid).detail("LogFiles", logFiles.size()).detail("RangeFiles", rangeFiles.size()); if (params->list_only) - return Void(); + co_return; // Decode log files. - state int idx = 0; + int idx = 0; if (params->decode_logs) { while (idx < logFiles.size()) { TraceEvent("ProcessFile").detail("Name", logFiles[idx].fileName).detail("I", idx); - wait(process_file(container, logFiles[idx], uid, params)); + co_await process_file(container, logFiles[idx], uid, params); idx++; } TraceEvent("DecodeLogsDone", uid).log(); @@ -862,17 +867,16 @@ ACTOR Future decode_logs(Reference params) { idx = 0; while (idx < rangeFiles.size()) { TraceEvent("ProcessFile").detail("Name", rangeFiles[idx].fileName).detail("I", idx); - wait(process_range_file(container, rangeFiles[idx], uid, params)); + co_await process_range_file(container, rangeFiles[idx], uid, params); idx++; } TraceEvent("DecodeRangeFileDone", uid).log(); } - - return Void(); } } // namespace file_converter +#ifndef EXCLUDE_MAIN_FUNCTION int main(int argc, char** argv) { std::string commandLine; for (int a = 0; a < argc; a++) { @@ -893,6 +897,15 @@ int main(int argc, char** argv) { return status; } + // Check if the beginVersionFilter is greater than the endVersionFilter, otherwise the filtering will be + // invalid. + if (!param->validVersionFilters()) { + std::cerr << "--begin-version-filter " << param->beginVersionFilter + << " cannot be equal or greater than --end-version-filter " << param->endVersionFilter << "\n"; + file_converter::printDecodeUsage(); + return FDB_EXIT_ERROR; + } + if (param->log_enabled) { if (param->log_dir.empty()) { setNetworkOption(FDBNetworkOptions::TRACE_ENABLE); @@ -928,7 +941,7 @@ int main(int argc, char** argv) { .detail("SourceVersion", getSourceVersion()) .detail("Version", FDB_VT_VERSION) .detail("PackageName", FDB_VT_PACKAGE_NAME) - .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(NULL)) + .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(nullptr)) .setMaxFieldLength(10000) .detail("CommandLine", commandLine) .setMaxFieldLength(0) @@ -955,3 +968,36 @@ int main(int argc, char** argv) { return FDB_EXIT_MAIN_EXCEPTION; } } +#else // EXCLUDE_MAIN_FUNCTION + +int main() { + auto assertValid = [](file_converter::DecodeParams& p, bool expected, const char* label) { + bool result = p.validVersionFilters(); + if (result != expected) { + fprintf(stderr, "FAIL [%s]: expected %s\n", label, expected ? "valid" : "invalid"); + return false; + } + printf("PASS [%s]\n", label); + return true; + }; + + bool ok = true; + file_converter::DecodeParams p; + + ok &= assertValid(p, true, "defaults"); + + p.beginVersionFilter = 100; + p.endVersionFilter = 200; + ok &= assertValid(p, true, "begin < end"); + + p.beginVersionFilter = 200; + p.endVersionFilter = 200; + ok &= assertValid(p, false, "begin == end"); + + p.beginVersionFilter = 300; + p.endVersionFilter = 200; + ok &= assertValid(p, false, "begin > end"); + + return ok ? 0 : 1; +} +#endif // EXCLUDE_MAIN_FUNCTION diff --git a/fdbbackup/backup.actor.cpp b/fdbbackup/backup.cpp similarity index 82% rename from fdbbackup/backup.actor.cpp rename to fdbbackup/backup.cpp index 168bd2ccb62..0c24028f33a 100644 --- a/fdbbackup/backup.actor.cpp +++ b/fdbbackup/backup.cpp @@ -1,5 +1,5 @@ /* - * backup.actor.cpp + * backup.cpp * * This source file is part of the FoundationDB open source project * @@ -28,6 +28,7 @@ #include "flow/Error.h" #include "flow/SystemMonitor.h" #include "flow/Trace.h" +#include "flow/CoroUtils.h" #define BOOST_DATE_TIME_NO_LIB #include @@ -36,23 +37,22 @@ #include "flow/serialize.h" #include "flow/IRandom.h" #include "flow/genericactors.actor.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "fdbclient/DatabaseContext.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/Status.h" #include "fdbclient/BackupContainer.h" #include "fdbclient/ClusterConnectionFile.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "fdbclient/IKnobCollection.h" -#include "fdbclient/RunRYWTransaction.actor.h" -#include "fdbclient/S3BlobStore.h" +#include "fdbclient/KeyBackedTypes.h" +#include "fdbclient/Knobs.h" +#include "fdbclient/RunRYWTransaction.h" +#include "fdbclient/IBlobStore.h" #include "fdbclient/SystemData.h" #include "fdbclient/json_spirit/json_spirit_writer_template.h" #include "fdbclient/BulkLoading.h" -#include "fdbclient/ManagementAPI.actor.h" -#include "fdbclient/BackupContainer.h" +#include "fdbclient/ManagementAPI.h" #include "flow/Platform.h" @@ -82,10 +82,9 @@ #include "fdbclient/BuildFlags.h" #include "SimpleOpt/SimpleOpt.h" -#include "flow/actorcompiler.h" // This must be the last #include. // Type of program being executed -enum class ProgramExe { AGENT, BACKUP, RESTORE, FASTRESTORE_TOOL, DR_AGENT, DB_BACKUP, UNDEFINED }; +enum class ProgramExe { AGENT, BACKUP, RESTORE, DR_AGENT, DB_BACKUP, UNDEFINED }; enum class BackupType { UNDEFINED = 0, @@ -135,7 +134,7 @@ enum { OPT_JSON, OPT_DELETE_DATA, OPT_MIN_CLEANUP_SECONDS, - OPT_USE_PARTITIONED_LOG, + OPT_MUTATION_LOG_TYPE, OPT_MODE, // Backup and Restore constants @@ -147,6 +146,7 @@ enum { OPT_BACKUPKEYS_FILTER, OPT_INCREMENTALONLY, OPT_ENCRYPTION_KEY_FILE, + OPT_ENCRYPTION_BLOCK_SIZE, // Backup Modify OPT_MOD_ACTIVE_INTERVAL, @@ -253,9 +253,7 @@ CSimpleOpt::SOption g_rgBackupStartOptions[] = { { OPT_DESTCONTAINER, "-d", SO_REQ_SEP }, { OPT_DESTCONTAINER, "--destcontainer", SO_REQ_SEP }, { OPT_PROXY, "--proxy", SO_REQ_SEP }, - // Enable "-p" option after GA - // { OPT_USE_PARTITIONED_LOG, "-p", SO_NONE }, - { OPT_USE_PARTITIONED_LOG, "--partitioned-log-experimental", SO_NONE }, + { OPT_MUTATION_LOG_TYPE, "--mutation-log-type", SO_REQ_SEP }, { OPT_SNAPSHOTINTERVAL, "-s", SO_REQ_SEP }, { OPT_SNAPSHOTINTERVAL, "--snapshot-interval", SO_REQ_SEP }, { OPT_INITIAL_SNAPSHOT_INTERVAL, "--initial-snapshot-interval", SO_REQ_SEP }, @@ -284,6 +282,7 @@ CSimpleOpt::SOption g_rgBackupStartOptions[] = { { OPT_BLOB_CREDENTIALS, "--blob-credentials", SO_REQ_SEP }, { OPT_INCREMENTALONLY, "--incremental", SO_NONE }, { OPT_ENCRYPTION_KEY_FILE, "--encryption-key-file", SO_REQ_SEP }, + { OPT_ENCRYPTION_BLOCK_SIZE, "--encryption-block-size", SO_REQ_SEP }, { OPT_MODE, "--mode", SO_REQ_SEP }, TLS_OPTION_FLAGS, SO_END_OF_OPTIONS @@ -696,7 +695,7 @@ CSimpleOpt::SOption g_rgBackupQueryOptions[] = { SO_END_OF_OPTIONS }; -// g_rgRestoreOptions is used by fdbrestore and fastrestore_tool +// g_rgRestoreOptions is used by fdbrestore CSimpleOpt::SOption g_rgRestoreOptions[] = { #ifdef _WIN32 { OPT_PARENTPID, "--parentpid", SO_REQ_SEP }, @@ -932,7 +931,6 @@ CSimpleOpt::SOption g_rgDBPauseOptions[] = { const KeyRef exeAgent = "backup_agent"_sr; const KeyRef exeBackup = "fdbbackup"_sr; const KeyRef exeRestore = "fdbrestore"_sr; -const KeyRef exeFastRestoreTool = "fastrestore_tool"_sr; // must be lower case const KeyRef exeDatabaseAgent = "dr_agent"_sr; const KeyRef exeDatabaseBackup = "fdbdr"_sr; @@ -1115,7 +1113,10 @@ static void printBackupUsage(bool devhelp) { " If not specified, the entire database will be backed up or no filter will be applied.\n"); printf(" --keys-file FILE\n" " Same as -k option, except keys are specified in the input file.\n"); - printf(" --partitioned-log-experimental Starts with new type of backup system using partitioned logs.\n"); + printf(" --mutation-log-type TYPE\n" + " Specifies the mutation log type. Valid values are: " + "partitioned-log-experimental, range-partitioned-log-experimental.\n" + "If not specified, default log type is used.\n"); printf(" -n, --dryrun For backup start or restore start, performs a trial run with no actual changes made.\n"); printf(" --log Enables trace file logging for the CLI session.\n" " --logdir PATH Specifies the output directory for trace files. If\n" @@ -1141,6 +1142,9 @@ static void printBackupUsage(bool devhelp) { " For modify operations, need to pass encryption key file only if Backup container URL is " "changed to " "re-encrypt all future backup files. \n"); + printf(" --encryption-block-size" + " Block size in bytes for file encryption. Only used with fdbbackup start command. Default " + "is 1048576 (1MB).\n"); printf(TLS_HELP); printf(" -w, --wait Wait for the backup to complete (allowed with `start' and `discontinue').\n"); @@ -1257,14 +1261,6 @@ static void printRestoreUsage(bool devhelp) { return; } -static void printFastRestoreUsage(bool devhelp) { - printf(" NOTE: Fast restore aims to support the same fdbrestore option list.\n"); - printf(" But fast restore is still under development. The options may not be fully supported.\n"); - printf(" Supported options are: --dest-cluster-file, -r, --waitfordone, --logdir\n"); - printRestoreUsage(devhelp); - return; -} - static void printDBAgentUsage(bool devhelp) { printf("FoundationDB " FDB_VT_PACKAGE_NAME " (v" FDB_VT_VERSION ")\n"); printf("Usage: %s [OPTIONS]\n\n", exeDatabaseAgent.toString().c_str()); @@ -1367,9 +1363,6 @@ static void printUsage(ProgramExe programExe, bool devhelp) { case ProgramExe::RESTORE: printRestoreUsage(devhelp); break; - case ProgramExe::FASTRESTORE_TOOL: - printFastRestoreUsage(devhelp); - break; case ProgramExe::DR_AGENT: printDBAgentUsage(devhelp); break; @@ -1429,14 +1422,6 @@ ProgramExe getProgramType(std::string programExe) { enProgramExe = ProgramExe::RESTORE; } - // Check if restore - else if ((programExe.length() >= exeFastRestoreTool.size()) && - (programExe.compare(programExe.length() - exeFastRestoreTool.size(), - exeFastRestoreTool.size(), - (const char*)exeFastRestoreTool.begin()) == 0)) { - enProgramExe = ProgramExe::FASTRESTORE_TOOL; - } - // Check if db agent else if ((programExe.length() >= exeDatabaseAgent.size()) && (programExe.compare(programExe.length() - exeDatabaseAgent.size(), @@ -1510,6 +1495,16 @@ Optional getRestoreMode(std::string mode) { return Optional(); } +Optional getMutationLogType(std::string type) { + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + + if (type == "partitioned-log-experimental") + return MutationLogType::PARTITIONED_LOG; + if (type == "range-partitioned-log-experimental") + return MutationLogType::RANGE_PARTITIONED_LOG; + return Optional(); +} + RestoreType getRestoreType(std::string name) { if (name == "start") return RestoreType::START; @@ -1544,34 +1539,34 @@ DBType getDBType(std::string dbType) { return enBackupType; } -ACTOR Future getLayerStatus(Reference tr, - IPAddress localIP, - std::string name, - std::string id, - ProgramExe exe, - Database dest, - Snapshot snapshot = Snapshot::False) { +AsyncResult getLayerStatus(Reference tr, + IPAddress localIP, + std::string name, + std::string id, + ProgramExe exe, + Database dest, + Snapshot snapshot = Snapshot::False) { // This process will write a document that looks like this: // { backup : { $expires : {}, version: } // so that the value under 'backup' will eventually expire to null and thus be ignored by // readers of status. This is because if all agents die then they can no longer clean up old // status docs from other dead agents. - state Version readVer = wait(tr->getReadVersion()); + Version readVer = co_await tr->getReadVersion(); - state json_spirit::mValue layersRootValue; // Will contain stuff that goes into the doc at the layers status root + json_spirit::mValue layersRootValue; // Will contain stuff that goes into the doc at the layers status root JSONDoc layersRoot(layersRootValue); // Convenient mutator / accessor for the layers root JSONDoc op = layersRoot.subDoc(name); // Operator object for the $expires operation // Create the $expires key which is where the rest of the status output will go - state JSONDoc layerRoot = op.subDoc("$expires"); + JSONDoc layerRoot = op.subDoc("$expires"); // Set the version argument in the $expires operator object. op.create("version") = readVer + 120 * CLIENT_KNOBS->CORE_VERSIONSPERSECOND; layerRoot.create("instances_running.$sum") = 1; layerRoot.create("last_updated.$max") = now(); - state JSONDoc o = layerRoot.subDoc("instances." + id); + JSONDoc o = layerRoot.subDoc("instances." + id); o.create("version") = FDB_VT_VERSION; o.create("id") = id; @@ -1586,12 +1581,12 @@ ACTOR Future getLayerStatus(Reference tr o.create("networkAddress") = localIP.toString(); if (exe == ProgramExe::AGENT) { - static S3BlobStoreEndpoint::Stats last_stats; + static IBlobStoreEndpoint::Stats last_stats; static double last_ts = 0; - S3BlobStoreEndpoint::Stats current_stats = S3BlobStoreEndpoint::s_stats; + IBlobStoreEndpoint::Stats current_stats = IBlobStoreEndpoint::s_stats; JSONDoc blobstats = o.create("blob_stats"); blobstats.create("total") = current_stats.getJSON(); - S3BlobStoreEndpoint::Stats diff = current_stats - last_stats; + IBlobStoreEndpoint::Stats diff = current_stats - last_stats; json_spirit::mObject diffObj = diff.getJSON(); if (last_ts > 0) diffObj["bytes_per_second"] = double(current_stats.bytes_sent - last_stats.bytes_sent) / (now() - last_ts); @@ -1603,21 +1598,21 @@ ACTOR Future getLayerStatus(Reference tr for (auto& p : diffObj) totalBlobStats.create(p.first + ".$sum") = p.second; - state FileBackupAgent fba; - state std::vector backupTags = wait(getAllBackupTags(tr, snapshot)); - state std::vector>> tagLastRestorableVersions; - state std::vector> tagStates; - state std::vector>> tagContainers; - state std::vector> tagRangeBytes; - state std::vector> tagLogBytes; - state Future> fBackupPaused = tr->get(fba.taskBucket->getPauseKey(), snapshot); + FileBackupAgent fba; + std::vector backupTags = co_await getAllBackupTags(tr, snapshot); + std::vector>> tagLastRestorableVersions; + std::vector> tagStates; + std::vector>> tagContainers; + std::vector> tagRangeBytes; + std::vector> tagLogBytes; + Future> fBackupPaused = tr->get(fba.taskBucket->getPauseKey(), snapshot); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state std::vector::iterator tag; - state std::vector backupTagUids; + std::vector::iterator tag; + std::vector backupTagUids; for (tag = backupTags.begin(); tag != backupTags.end(); tag++) { - UidAndAbortedFlagT uidAndAbortedFlag = wait(tag->getOrThrow(tr, snapshot)); + UidAndAbortedFlagT uidAndAbortedFlag = co_await tag->getOrThrow(tr, snapshot); BackupConfig config(uidAndAbortedFlag.first); backupTagUids.push_back(config.getUid()); @@ -1628,11 +1623,11 @@ ACTOR Future getLayerStatus(Reference tr tagLastRestorableVersions.push_back(fba.getLastRestorable(tr, StringRef(tag->tagName), snapshot)); } - wait(waitForAll(tagLastRestorableVersions) && waitForAll(tagStates) && waitForAll(tagContainers) && - waitForAll(tagRangeBytes) && waitForAll(tagLogBytes) && success(fBackupPaused)); + co_await (waitForAll(tagLastRestorableVersions) && waitForAll(tagStates) && waitForAll(tagContainers) && + waitForAll(tagRangeBytes) && waitForAll(tagLogBytes) && success(fBackupPaused)); - state std::vector> encryptionSetupResults; - state std::vector encryptionContainerIndices; + std::vector> encryptionSetupResults; + std::vector encryptionContainerIndices; for (int i = 0; i < tagContainers.size(); i++) { if (tagContainers[i].get()->getEncryptionKeyFileName().present()) { @@ -1640,14 +1635,14 @@ ACTOR Future getLayerStatus(Reference tr encryptionContainerIndices.push_back(i); } } - wait(waitForAllReady(encryptionSetupResults)); + co_await waitForAllReady(encryptionSetupResults); json_spirit::mArray keysArr; std::unordered_set seenKeyPaths; for (int j = 0; j < encryptionContainerIndices.size() && j < 1e6; j++) { int i = encryptionContainerIndices[j]; std::string keyPath = tagContainers[i].get()->getEncryptionKeyFileName().get(); - if (seenKeyPaths.find(keyPath) == seenKeyPaths.end()) { + if (!seenKeyPaths.contains(keyPath)) { seenKeyPaths.insert(keyPath); json_spirit::mObject keyObj; keyObj["path"] = tagContainers[i].get()->getEncryptionKeyFileName().get(); @@ -1657,19 +1652,19 @@ ACTOR Future getLayerStatus(Reference tr } o.create("encryption_keys") = keysArr; - JSONDoc tagsRoot = layerRoot.subDoc("tags.$latest"); + JSONDoc tagsRoot = layerRoot.subDoc("tags"); layerRoot.create("tags.timestamp") = now(); layerRoot.create("total_workers.$sum") = fBackupPaused.get().present() ? 0 : CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT; layerRoot.create("paused.$latest") = fBackupPaused.get().present(); int j = 0; - for (KeyBackedTag eachTag : backupTags) { + for (const KeyBackedTag& eachTag : backupTags) { EBackupState status = tagStates[j].get(); const char* statusText = fba.getStateText(status); // The object for this backup tag inside this instance's subdocument - JSONDoc tagRoot = tagsRoot.subDoc(eachTag.tagName); + JSONDoc tagRoot = tagsRoot.subDoc(eachTag.tagName).subDoc("$latest"); tagRoot.create("current_container") = tagContainers[j].get()->getURL(); tagRoot.create("current_status") = statusText; if (tagLastRestorableVersions[j].get().present()) { @@ -1693,18 +1688,18 @@ ACTOR Future getLayerStatus(Reference tr j++; } } else if (exe == ProgramExe::DR_AGENT) { - state DatabaseBackupAgent dba; - state Reference tr2(new ReadYourWritesTransaction(dest)); + DatabaseBackupAgent dba; + Reference tr2(new ReadYourWritesTransaction(dest)); tr2->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr2->setOption(FDBTransactionOptions::LOCK_AWARE); - state RangeResult tagNames = wait(tr2->getRange(dba.tagNames.range(), 10000, snapshot)); - state std::vector>> backupVersion; - state std::vector> backupStatus; - state std::vector> tagRangeBytesDR; - state std::vector> tagLogBytesDR; - state Future> fDRPaused = tr->get(dba.taskBucket->getPauseKey(), snapshot); - - state std::vector drTagUids; + RangeResult tagNames = co_await tr2->getRange(dba.tagNames.range(), 10000, snapshot); + std::vector>> backupVersion; + std::vector> backupStatus; + std::vector> tagRangeBytesDR; + std::vector> tagLogBytesDR; + Future> fDRPaused = tr->get(dba.taskBucket->getPauseKey(), snapshot); + + std::vector drTagUids; for (int i = 0; i < tagNames.size(); i++) { backupVersion.push_back(tr2->get(tagNames[i].value.withPrefix(applyMutationsBeginRange.begin), snapshot)); UID tagUID = BinaryReader::fromStringRef(tagNames[i].value, Unversioned()); @@ -1714,10 +1709,10 @@ ACTOR Future getLayerStatus(Reference tr tagLogBytesDR.push_back(dba.getLogBytesWritten(tr2, tagUID, snapshot)); } - wait(waitForAll(backupStatus) && waitForAll(backupVersion) && waitForAll(tagRangeBytesDR) && - waitForAll(tagLogBytesDR) && success(fDRPaused)); + co_await (waitForAll(backupStatus) && waitForAll(backupVersion) && waitForAll(tagRangeBytesDR) && + waitForAll(tagLogBytesDR) && success(fDRPaused)); - JSONDoc tagsRoot = layerRoot.subDoc("tags.$latest"); + JSONDoc tagsRoot = layerRoot.subDoc("tags"); layerRoot.create("tags.timestamp") = now(); layerRoot.create("total_workers.$sum") = fDRPaused.get().present() ? 0 : CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT; layerRoot.create("paused.$latest") = fDRPaused.get().present(); @@ -1727,7 +1722,7 @@ ACTOR Future getLayerStatus(Reference tr auto status = backupStatus[i].get(); - JSONDoc tagRoot = tagsRoot.create(tagName); + JSONDoc tagRoot = tagsRoot.subDoc(tagName).subDoc("$latest"); tagRoot.create("running_backup") = (status == EBackupState::STATE_RUNNING_DIFFERENTIAL || status == EBackupState::STATE_RUNNING); tagRoot.create("running_backup_is_restorable") = (status == EBackupState::STATE_RUNNING_DIFFERENTIAL); @@ -1748,21 +1743,20 @@ ACTOR Future getLayerStatus(Reference tr } std::string json = json_spirit::write_string(layersRootValue); - return json; + co_return json; } // Check for unparsable or expired statuses and delete them. // First checks the first doc in the key range, and if it is valid, alive and not "me" then // returns. Otherwise, checks the rest of the range as well. -ACTOR Future cleanupStatus(Reference tr, - std::string rootKey, - std::string name, - std::string id, - int limit = 1) { - state RangeResult docs = wait(tr->getRange(KeyRangeRef(rootKey, strinc(rootKey)), limit, Snapshot::True)); - state bool readMore = false; - state int i; - for (i = 0; i < docs.size(); ++i) { +Future cleanupStatus(Reference tr, + std::string rootKey, + std::string name, + std::string id, + int limit = 1) { + RangeResult docs = co_await tr->getRange(KeyRangeRef(rootKey, strinc(rootKey)), limit, Snapshot::True); + bool readMore = false; + for (int i = 0; i < docs.size(); ++i) { json_spirit::mValue docValue; try { json_spirit::read_string(docs[i].value.toString(), docValue); @@ -1788,52 +1782,52 @@ ACTOR Future cleanupStatus(Reference tr, } if (readMore) { limit = 10000; - RangeResult docs2 = wait(tr->getRange(KeyRangeRef(rootKey, strinc(rootKey)), limit, Snapshot::True)); + RangeResult docs2 = co_await tr->getRange(KeyRangeRef(rootKey, strinc(rootKey)), limit, Snapshot::True); docs = std::move(docs2); readMore = false; } } - - return Void(); } // Get layer status document for just this layer -ACTOR Future getLayerStatus(Database src, std::string rootKey) { - state Transaction tr(src); +AsyncResult getLayerStatus(Database src, std::string rootKey) { + Transaction tr(src); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - state RangeResult kvPairs = - wait(tr.getRange(KeyRangeRef(rootKey, strinc(rootKey)), GetRangeLimits::ROW_LIMIT_UNLIMITED)); - state json_spirit::mObject statusDoc; - state JSONDoc modifier(statusDoc); + RangeResult kvPairs = + co_await tr.getRange(KeyRangeRef(rootKey, strinc(rootKey)), GetRangeLimits::ROW_LIMIT_UNLIMITED); + json_spirit::mObject statusDoc; + JSONDoc modifier(statusDoc); for (auto& kv : kvPairs) { - state json_spirit::mValue docValue; + json_spirit::mValue docValue; json_spirit::read_string(kv.value.toString(), docValue); - wait(yield()); + co_await yield(); modifier.absorb(docValue); - wait(yield()); + co_await yield(); } JSONDoc::expires_reference_version = (uint64_t)tr.getReadVersion().get(); modifier.cleanOps(); - return statusDoc; + co_return statusDoc; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } // Read layer status for this layer and get the total count of agent processes (instances) then adjust the poll delay // based on that and BACKUP_AGGREGATE_POLL_RATE -ACTOR Future updateAgentPollRate(Database src, - std::string rootKey, - std::string name, - std::shared_ptr pollDelay) { - loop { +Future updateAgentPollRate(Database src, + std::string rootKey, + std::string name, + std::shared_ptr pollDelay) { + while (true) { try { - json_spirit::mObject status = wait(getLayerStatus(src, rootKey)); + json_spirit::mObject status = co_await getLayerStatus(src, rootKey); int64_t processes = 0; // If instances count is present and greater than 0 then update pollDelay if (JSONDoc(status).tryGet(name + ".instances_running", processes) && processes > 0) { @@ -1845,161 +1839,171 @@ ACTOR Future updateAgentPollRate(Database src, } catch (Error& e) { TraceEvent(SevWarn, "BackupAgentPollRateUpdateError").error(e); } - wait(delay(CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE_UPDATE_INTERVAL)); + co_await delay(CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE_UPDATE_INTERVAL); } } -ACTOR Future statusUpdateActor(Database statusUpdateDest, - std::string name, - ProgramExe exe, - std::shared_ptr pollDelay, - Database taskDest = Database(), - std::string id = nondeterministicRandom()->randomUniqueID().toString()) { - state std::string metaKey = layerStatusMetaPrefixRange.begin.toString() + "json/" + name; - state std::string rootKey = backupStatusPrefixRange.begin.toString() + name + "/json"; - state std::string instanceKey = rootKey + "/" + "agent-" + id; - state Reference tr(new ReadYourWritesTransaction(statusUpdateDest)); - state Future pollRateUpdater; +Future statusUpdateActor(Database statusUpdateDest, + std::string name, + ProgramExe exe, + std::shared_ptr pollDelay, + Database taskDest = Database(), + std::string id = nondeterministicRandom()->randomUniqueID().toString()) { + std::string metaKey = layerStatusMetaPrefixRange.begin.toString() + "json/" + name; + std::string rootKey = backupStatusPrefixRange.begin.toString() + name + "/json"; + std::string instanceKey = rootKey + "/" + "agent-" + id; + Reference tr(new ReadYourWritesTransaction(statusUpdateDest)); + Future pollRateUpdater; // In order to report a useful networkAddress to the cluster's layer status JSON object, determine which local // network interface IP will be used to talk to the cluster. This is a blocking call, so it is only done once, // and in a retry loop because if we can't connect to the cluster we can't do any work anyway. - state IPAddress localIP; + IPAddress localIP; - loop { + while (true) { + Error err; try { localIP = statusUpdateDest->getConnectionRecord()->getConnectionString().determineLocalSourceIP(); break; } catch (Error& e) { - TraceEvent(SevWarn, "AgentCouldNotDetermineLocalIP").error(e); - wait(delay(1.0)); + err = e; } + TraceEvent(SevWarn, "AgentCouldNotDetermineLocalIP").error(err); + co_await delay(1.0); } // Register the existence of this layer in the meta key space - loop { + while (true) { tr->reset(); + Error err; try { - loop { + while (true) { + Error innerErr; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->set(metaKey, rootKey); - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - TraceEvent(SevWarnAlways, "LayerStatusMetaKeyUpdateError").errorUnsuppressed(e); - wait(tr->onError(e)); // Non-retryable txns throws back the error. + innerErr = e; } + TraceEvent(SevWarnAlways, "LayerStatusMetaKeyUpdateError").errorUnsuppressed(innerErr); + co_await tr->onError(innerErr); // Non-retryable txns throws back the error. } break; } catch (Error& e) { - // For non-retryable txns, do delay, reset txn and retry - TraceEvent(SevWarnAlways, "UnableToWriteLayerStatusMetaKey").errorUnsuppressed(e); - wait(delay(5.0)); + err = e; } + // For non-retryable txns, do delay, reset txn and retry + TraceEvent(SevWarnAlways, "UnableToWriteLayerStatusMetaKey").errorUnsuppressed(err); + co_await delay(5.0); } // Write status periodically - loop { + while (true) { tr->reset(); + Error err; try { - loop { + while (true) { + Error innerErr; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future futureStatusDoc = + AsyncResult asyncResultStatusDoc = getLayerStatus(tr, localIP, name, id, exe, taskDest, Snapshot::True); - wait(cleanupStatus(tr, rootKey, name, id)); - std::string statusdoc = wait(futureStatusDoc); + co_await cleanupStatus(tr, rootKey, name, id); + std::string statusdoc = co_await std::move(asyncResultStatusDoc); tr->set(instanceKey, statusdoc); - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - TraceEvent(SevWarnAlways, "LayerBackupStatusUpdateError").errorUnsuppressed(e); - wait(tr->onError(e)); + innerErr = e; } + TraceEvent(SevWarnAlways, "LayerBackupStatusUpdateError").errorUnsuppressed(innerErr); + co_await tr->onError(innerErr); } - wait(delay(CLIENT_KNOBS->BACKUP_STATUS_DELAY * - ((1.0 - CLIENT_KNOBS->BACKUP_STATUS_JITTER) + - 2 * deterministicRandom()->random01() * CLIENT_KNOBS->BACKUP_STATUS_JITTER))); + co_await delay(CLIENT_KNOBS->BACKUP_STATUS_DELAY * + ((1.0 - CLIENT_KNOBS->BACKUP_STATUS_JITTER) + + 2 * deterministicRandom()->random01() * CLIENT_KNOBS->BACKUP_STATUS_JITTER)); // Now that status was written at least once by this process (and hopefully others), start the poll rate // control updater if it wasn't started yet if (!pollRateUpdater.isValid()) pollRateUpdater = updateAgentPollRate(statusUpdateDest, rootKey, name, pollDelay); + continue; } catch (Error& e) { - TraceEvent(SevWarnAlways, "UnableToWriteBackupStatus").error(e); - wait(delay(10.0)); + err = e; } + TraceEvent(SevWarnAlways, "UnableToWriteBackupStatus").error(err); + co_await delay(10.0); } } -ACTOR Future runDBAgent(Database src, Database dest) { - state std::shared_ptr pollDelay = std::make_shared(1.0 / CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE); +Future runDBAgent(Database src, Database dest) { + std::shared_ptr pollDelay = std::make_shared(1.0 / CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE); std::string id = nondeterministicRandom()->randomUniqueID().toString(); - state Future status = statusUpdateActor(src, "dr_backup", ProgramExe::DR_AGENT, pollDelay, dest, id); - state Future status_other = - statusUpdateActor(dest, "dr_backup_dest", ProgramExe::DR_AGENT, pollDelay, dest, id); + Future status = statusUpdateActor(src, "dr_backup", ProgramExe::DR_AGENT, pollDelay, dest, id); + Future status_other = statusUpdateActor(dest, "dr_backup_dest", ProgramExe::DR_AGENT, pollDelay, dest, id); - state DatabaseBackupAgent backupAgent(src); + DatabaseBackupAgent backupAgent(src); - loop { + while (true) { + Error err; try { - wait(backupAgent.run(dest, pollDelay, CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT)); + co_await backupAgent.run(dest, pollDelay, CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT); break; } catch (Error& e) { - if (e.code() == error_code_operation_cancelled) - throw; + err = e; + } + if (err.code() == error_code_operation_cancelled) + throw err; - TraceEvent(SevError, "DA_runAgent").error(e); - fprintf(stderr, "ERROR: DR agent encountered fatal error `%s'\n", e.what()); + TraceEvent(SevError, "DA_runAgent").error(err); + fprintf(stderr, "ERROR: DR agent encountered fatal error `%s'\n", err.what()); - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - } + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); } - - return Void(); } -ACTOR Future runAgent(Database db) { - state std::shared_ptr pollDelay = std::make_shared(1.0 / CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE); - state Future status = statusUpdateActor(db, "backup", ProgramExe::AGENT, pollDelay); +Future runAgent(Database db) { + std::shared_ptr pollDelay = std::make_shared(1.0 / CLIENT_KNOBS->BACKUP_AGGREGATE_POLL_RATE); + Future status = statusUpdateActor(db, "backup", ProgramExe::AGENT, pollDelay); - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; - loop { + while (true) { + Error err; try { - wait(backupAgent.run(db, pollDelay, CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT)); + co_await backupAgent.run(db, pollDelay, CLIENT_KNOBS->BACKUP_TASKS_PER_AGENT); break; } catch (Error& e) { - if (e.code() == error_code_operation_cancelled) - throw; + err = e; + } + if (err.code() == error_code_operation_cancelled) + throw err; - TraceEvent(SevError, "BA_runAgent").error(e); - fprintf(stderr, "ERROR: backup agent encountered fatal error `%s'\n", e.what()); + TraceEvent(SevError, "BA_runAgent").error(err); + fprintf(stderr, "ERROR: backup agent encountered fatal error `%s'\n", err.what()); - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - } + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); } - - return Void(); } -ACTOR Future submitDBBackup(Database src, - Database dest, - Standalone> backupRanges, - std::string tagName) { +Future submitDBBackup(Database src, + Database dest, + Standalone> backupRanges, + std::string tagName) { try { - state DatabaseBackupAgent backupAgent(src); + DatabaseBackupAgent backupAgent(src); ASSERT(!backupRanges.empty()); - wait(backupAgent.submitBackup( - dest, KeyRef(tagName), backupRanges, StopWhenDone::False, StringRef(), StringRef(), LockDB::True)); + co_await backupAgent.submitBackup( + dest, KeyRef(tagName), backupRanges, StopWhenDone::False, StringRef(), StringRef(), LockDB::True); // Check if a backup agent is running - bool agentRunning = wait(backupAgent.checkActive(dest)); + bool agentRunning = co_await backupAgent.checkActive(dest); if (!agentRunning) { printf("The DR on tag `%s' was successfully submitted but no DR agents are responding.\n", @@ -2029,35 +2033,34 @@ ACTOR Future submitDBBackup(Database src, throw backup_error(); } - - return Void(); } -ACTOR Future submitBackup(Database db, - std::string url, - Optional proxy, - int initialSnapshotIntervalSeconds, - int snapshotIntervalSeconds, - Standalone> backupRanges, - std::string tagName, - bool dryRun, - WaitForComplete waitForCompletion, - StopWhenDone stopWhenDone, - UsePartitionedLog usePartitionedLog, - IncrementalBackupOnly incrementalBackupOnly, - Optional encryptionKeyFile, - SnapshotMode snapshotMode = SnapshotMode::RANGEFILE) { +Future submitBackup(Database db, + std::string url, + Optional proxy, + int initialSnapshotIntervalSeconds, + int snapshotIntervalSeconds, + Standalone> backupRanges, + std::string tagName, + bool dryRun, + WaitForComplete waitForCompletion, + StopWhenDone stopWhenDone, + MutationLogType mutationLogType, + IncrementalBackupOnly incrementalBackupOnly, + Optional encryptionKeyFile, + int encryptionBlockSize, + SnapshotMode snapshotMode = SnapshotMode::RANGEFILE) { try { - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; ASSERT(!backupRanges.empty()); if (dryRun) { - state KeyBackedTag tag = makeBackupTag(tagName); - Optional uidFlag = wait(tag.get(db.getReference())); + KeyBackedTag tag = makeBackupTag(tagName); + Optional uidFlag = co_await tag.get(db.getReference()); if (uidFlag.present()) { BackupConfig config(uidFlag.get().first); - EBackupState backupStatus = wait(config.stateEnum().getOrThrow(db.getReference())); + EBackupState backupStatus = co_await config.stateEnum().getOrThrow(db.getReference()); // Throw error if a backup is currently running until we support parallel backups if (BackupAgentBase::isRunnable(backupStatus)) { @@ -2072,7 +2075,7 @@ ACTOR Future submitBackup(Database db, else { // Check if a backup agent is running - bool agentRunning = wait(backupAgent.checkActive(db)); + bool agentRunning = co_await backupAgent.checkActive(db); if (!agentRunning) { printf("The backup on tag `%s' was successfully submitted but no backup agents are responding. " @@ -2089,27 +2092,28 @@ ACTOR Future submitBackup(Database db, } else { - wait(backupAgent.submitBackup(db, - KeyRef(url), - proxy, - initialSnapshotIntervalSeconds, - snapshotIntervalSeconds, - tagName, - backupRanges, - stopWhenDone, - usePartitionedLog, - incrementalBackupOnly, - encryptionKeyFile, - static_cast(snapshotMode))); + co_await backupAgent.submitBackup(db, + KeyRef(url), + proxy, + initialSnapshotIntervalSeconds, + snapshotIntervalSeconds, + tagName, + backupRanges, + stopWhenDone, + mutationLogType, + incrementalBackupOnly, + encryptionKeyFile, + encryptionBlockSize, + static_cast(snapshotMode)); // Wait for the backup to complete, if requested if (waitForCompletion) { printf("Submitted and now waiting for the backup on tag `%s' to complete.\n", printable(StringRef(tagName)).c_str()); - wait(success(backupAgent.waitBackup(db, tagName))); + co_await backupAgent.waitBackup(db, tagName); } else { // Check if a backup agent is running - bool agentRunning = wait(backupAgent.checkActive(db)); + bool agentRunning = co_await backupAgent.checkActive(db); if (!agentRunning) { printf("The backup on tag `%s' was successfully submitted but no backup agents are responding.\n", @@ -2140,20 +2144,19 @@ ACTOR Future submitBackup(Database db, throw backup_error(); } - - return Void(); } -ACTOR Future switchDBBackup(Database src, - Database dest, - Standalone> backupRanges, - std::string tagName, - ForceAction forceAction) { +Future switchDBBackup(Database src, + Database dest, + Standalone> backupRanges, + std::string tagName, + ForceAction forceAction) { try { - state DatabaseBackupAgent backupAgent(src); + DatabaseBackupAgent backupAgent(src); ASSERT(!backupRanges.empty()); - wait(backupAgent.atomicSwitchover(dest, KeyRef(tagName), backupRanges, StringRef(), StringRef(), forceAction)); + co_await backupAgent.atomicSwitchover( + dest, KeyRef(tagName), backupRanges, StringRef(), StringRef(), forceAction); printf("The DR on tag `%s' was successfully switched.\n", printable(StringRef(tagName)).c_str()); } @@ -2174,15 +2177,13 @@ ACTOR Future switchDBBackup(Database src, throw backup_error(); } - - return Void(); } -ACTOR Future statusDBBackup(Database src, Database dest, std::string tagName, int errorLimit) { +Future statusDBBackup(Database src, Database dest, std::string tagName, int errorLimit) { try { - state DatabaseBackupAgent backupAgent(src); + DatabaseBackupAgent backupAgent(src); - std::string statusText = wait(backupAgent.getStatus(dest, errorLimit, StringRef(tagName))); + std::string statusText = co_await backupAgent.getStatus(dest, errorLimit, StringRef(tagName)); printf("%s\n", statusText.c_str()); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -2190,16 +2191,14 @@ ACTOR Future statusDBBackup(Database src, Database dest, std::string tagNa fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future statusBackup(Database db, std::string tagName, ShowErrors showErrors, bool json) { +Future statusBackup(Database db, std::string tagName, ShowErrors showErrors, bool json) { try { - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; std::string statusText = - wait(json ? backupAgent.getStatusJSON(db, tagName) : backupAgent.getStatus(db, showErrors, tagName)); + co_await (json ? backupAgent.getStatusJSON(db, tagName) : backupAgent.getStatus(db, showErrors, tagName)); printf("%s\n", statusText.c_str()); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -2207,20 +2206,14 @@ ACTOR Future statusBackup(Database db, std::string tagName, ShowErrors sho fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future abortDBBackup(Database src, - Database dest, - std::string tagName, - PartialBackup partial, - DstOnly dstOnly) { +Future abortDBBackup(Database src, Database dest, std::string tagName, PartialBackup partial, DstOnly dstOnly) { try { - state DatabaseBackupAgent backupAgent(src); + DatabaseBackupAgent backupAgent(src); - wait(backupAgent.abortBackup(dest, Key(tagName), partial, AbortOldBackup::False, dstOnly)); - wait(backupAgent.unlockBackup(dest, Key(tagName))); + co_await backupAgent.abortBackup(dest, Key(tagName), partial, AbortOldBackup::False, dstOnly); + co_await backupAgent.unlockBackup(dest, Key(tagName)); printf("The DR on tag `%s' was successfully aborted.\n", printable(StringRef(tagName)).c_str()); } catch (Error& e) { @@ -2239,15 +2232,13 @@ ACTOR Future abortDBBackup(Database src, } throw; } - - return Void(); } -ACTOR Future abortBackup(Database db, std::string tagName) { +Future abortBackup(Database db, std::string tagName) { try { - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; - wait(backupAgent.abortBackup(db, tagName)); + co_await backupAgent.abortBackup(db, tagName); printf("The backup on tag `%s' was successfully aborted.\n", printable(StringRef(tagName)).c_str()); } catch (Error& e) { @@ -2266,28 +2257,24 @@ ACTOR Future abortBackup(Database db, std::string tagName) { } throw; } - - return Void(); } -ACTOR Future cleanupMutations(Database db, DeleteData deleteData) { +Future cleanupMutations(Database db, DeleteData deleteData) { try { - wait(cleanupBackup(db, deleteData)); + co_await cleanupBackup(db, deleteData); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) throw; fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future waitBackup(Database db, std::string tagName, StopWhenDone stopWhenDone) { +Future waitBackup(Database db, std::string tagName, StopWhenDone stopWhenDone) { try { - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; - EBackupState status = wait(backupAgent.waitBackup(db, tagName, stopWhenDone)); + EBackupState status = co_await backupAgent.waitBackup(db, tagName, stopWhenDone); printf("The backup on tag `%s' %s.\n", printable(StringRef(tagName)).c_str(), @@ -2298,21 +2285,19 @@ ACTOR Future waitBackup(Database db, std::string tagName, StopWhenDone sto fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future discontinueBackup(Database db, std::string tagName, WaitForComplete waitForCompletion) { +Future discontinueBackup(Database db, std::string tagName, WaitForComplete waitForCompletion) { try { - state FileBackupAgent backupAgent; + FileBackupAgent backupAgent; - wait(backupAgent.discontinueBackup(db, StringRef(tagName))); + co_await backupAgent.discontinueBackup(db, StringRef(tagName)); // Wait for the backup to complete, if requested if (waitForCompletion) { printf("Discontinued and now waiting for the backup on tag `%s' to complete.\n", printable(StringRef(tagName)).c_str()); - wait(success(backupAgent.waitBackup(db, tagName))); + co_await backupAgent.waitBackup(db, tagName); } else { printf("The backup on tag `%s' was successfully discontinued.\n", printable(StringRef(tagName)).c_str()); } @@ -2338,14 +2323,12 @@ ACTOR Future discontinueBackup(Database db, std::string tagName, WaitForCo } throw; } - - return Void(); } -ACTOR Future changeBackupResumed(Database db, bool pause) { +Future changeBackupResumed(Database db, bool pause) { try { FileBackupAgent backupAgent; - wait(backupAgent.changePause(db, pause)); + co_await backupAgent.changePause(db, pause); printf("All backup agents have been %s.\n", pause ? "paused" : "resumed"); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -2353,14 +2336,12 @@ ACTOR Future changeBackupResumed(Database db, bool pause) { fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future changeDBBackupResumed(Database src, Database dest, bool pause) { +Future changeDBBackupResumed(Database src, Database dest, bool pause) { try { - state DatabaseBackupAgent backupAgent(src); - wait(backupAgent.taskBucket->changePause(dest, pause)); + DatabaseBackupAgent backupAgent(src); + co_await backupAgent.taskBucket->changePause(dest, pause); printf("All DR agents have been %s.\n", pause ? "paused" : "resumed"); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -2368,14 +2349,13 @@ ACTOR Future changeDBBackupResumed(Database src, Database dest, bool pause fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } Reference openBackupContainer(const char* name, const std::string& destinationContainer, const Optional& proxy, - const Optional& encryptionKeyFile) { + const Optional& encryptionKeyFile, + int encryptionBlockSize) { // Error, if no dest container was specified if (destinationContainer.empty()) { fprintf(stderr, "ERROR: No backup destination was specified.\n"); @@ -2392,7 +2372,7 @@ Reference openBackupContainer(const char* name, Reference c; try { - c = IBackupContainer::openContainer(destinationContainer, proxy, encryptionKeyFile); + c = IBackupContainer::openContainer(destinationContainer, proxy, encryptionKeyFile, encryptionBlockSize); } catch (Error& e) { std::string msg = format("ERROR: '%s' on URL '%s'", e.what(), destinationContainer.c_str()); if (e.code() == error_code_backup_invalid_url && !IBackupContainer::lastOpenError.empty()) { @@ -2408,24 +2388,24 @@ Reference openBackupContainer(const char* name, // Submit the restore request to the database if "performRestore" is true. Otherwise, // check if the restore can be performed. -ACTOR Future runRestore(Database db, - std::string originalClusterFile, - std::string tagName, - std::string container, - Optional proxy, - Standalone> ranges, - Version beginVersion, - Version targetVersion, - std::string targetTimestamp, - bool performRestore, - Verbose verbose, - WaitForComplete waitForDone, - std::string addPrefix, - std::string removePrefix, - OnlyApplyMutationLogs onlyApplyMutationLogs, - InconsistentSnapshotOnly inconsistentSnapshotOnly, - Optional encryptionKeyFile, - RestoreMode restoreMode = RestoreMode::RANGEFILE) { +Future runRestore(Database db, + std::string originalClusterFile, + std::string tagName, + std::string container, + Optional proxy, + Standalone> ranges, + Version beginVersion, + Version targetVersion, + std::string targetTimestamp, + bool performRestore, + Verbose verbose, + WaitForComplete waitForDone, + std::string addPrefix, + std::string removePrefix, + OnlyApplyMutationLogs onlyApplyMutationLogs, + InconsistentSnapshotOnly inconsistentSnapshotOnly, + Optional encryptionKeyFile, + RestoreMode restoreMode = RestoreMode::RANGEFILE) { ASSERT(!ranges.empty()); if (targetVersion != invalidVersion && !targetTimestamp.empty()) { @@ -2433,7 +2413,7 @@ ACTOR Future runRestore(Database db, throw restore_error(); } - state Optional origDb; + Optional origDb; // Resolve targetTimestamp if given if (!targetTimestamp.empty()) { @@ -2451,17 +2431,18 @@ ACTOR Future runRestore(Database db, } origDb = Database::createDatabase(originalClusterFile, ApiVersion::LATEST_VERSION); - Version v = wait(timeKeeperVersionFromDatetime(targetTimestamp, origDb.get())); + Version v = co_await timeKeeperVersionFromDatetime(targetTimestamp, origDb.get()); fmt::print("Timestamp '{0}' resolves to version {1}\n", targetTimestamp, v); targetVersion = v; } try { - state FileBackupAgent backupAgent; - - state Reference bc = - openBackupContainer(exeRestore.toString().c_str(), container, proxy, encryptionKeyFile); + FileBackupAgent backupAgent; + // encryptionBlockSize is passed 0 because we don't know about the block size yet and it will be read in the + // describeBackup call after this. + Reference bc = openBackupContainer( + exeRestore.toString().c_str(), container, proxy, encryptionKeyFile, /*encryptionBlockSize=*/0); // If targetVersion is unset then use the maximum restorable version from the backup description if (targetVersion == invalidVersion) { if (verbose) @@ -2469,7 +2450,7 @@ ACTOR Future runRestore(Database db, "No restore target version given, will use maximum restorable version from backup description.\n"); // For blobstore:// URLs, use invalidVersion to allow describeBackup to write missing version properties - BackupDescription desc = wait(bc->describeBackup(false, isBlobstoreUrl(container) ? invalidVersion : 0)); + BackupDescription desc = co_await bc->describeBackup(false, isBlobstoreUrl(container) ? invalidVersion : 0); if (onlyApplyMutationLogs && desc.contiguousLogEnd.present()) { targetVersion = desc.contiguousLogEnd.get() - 1; @@ -2486,32 +2467,32 @@ ACTOR Future runRestore(Database db, } if (performRestore) { - Version restoredVersion = wait(backupAgent.restore(db, - origDb, - KeyRef(tagName), - KeyRef(container), - proxy, - ranges, - waitForDone, - targetVersion, - verbose, - KeyRef(addPrefix), - KeyRef(removePrefix), - LockDB::True, - UnlockDB::True, - onlyApplyMutationLogs, - inconsistentSnapshotOnly, - beginVersion, - encryptionKeyFile, - {}, - restoreMode == RestoreMode::RANGEFILE)); + Version restoredVersion = co_await backupAgent.restore(db, + origDb, + KeyRef(tagName), + KeyRef(container), + proxy, + ranges, + waitForDone, + targetVersion, + verbose, + KeyRef(addPrefix), + KeyRef(removePrefix), + LockDB::True, + UnlockDB::True, + onlyApplyMutationLogs, + inconsistentSnapshotOnly, + beginVersion, + encryptionKeyFile, + {}, + restoreMode == RestoreMode::RANGEFILE); if (waitForDone && verbose) { // If restore is now complete then report version restored fmt::print("Restored to version {}\n", restoredVersion); } } else { - state Optional rset = wait(bc->getRestoreSet(targetVersion, ranges)); + Optional rset = co_await bc->getRestoreSet(targetVersion, ranges); if (!rset.present()) { fmt::print(stderr, @@ -2529,139 +2510,17 @@ ACTOR Future runRestore(Database db, fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); -} - -// Fast restore agent that kicks off the restore: send restore requests to restore workers. -ACTOR Future runFastRestoreTool(Database db, - std::string tagName, - std::string container, - Optional proxy, - Standalone> ranges, - Version dbVersion, - bool performRestore, - Verbose verbose, - WaitForComplete waitForDone) { - try { - state FileBackupAgent backupAgent; - state Version restoreVersion = invalidVersion; - - if (ranges.size() > 1) { - fprintf(stdout, "[WARNING] Currently only a single restore range is tested!\n"); - } - - if (ranges.size() == 0) { - ranges.push_back(ranges.arena(), normalKeys); - } - - printf("[INFO] runFastRestoreTool: restore_ranges:%d first range:%s\n", - ranges.size(), - ranges.front().toString().c_str()); - TraceEvent ev("FastRestoreTool"); - ev.detail("RestoreRanges", ranges.size()); - for (int i = 0; i < ranges.size(); ++i) { - ev.detail(format("Range%d", i), ranges[i]); - } - - if (performRestore) { - if (dbVersion == invalidVersion) { - TraceEvent("FastRestoreTool").detail("TargetRestoreVersion", "Largest restorable version"); - // For blobstore:// URLs, use invalidVersion to allow describeBackup to write missing version properties - BackupDescription desc = - wait(IBackupContainer::openContainer(container, proxy, {}) - ->describeBackup(false, isBlobstoreUrl(container) ? invalidVersion : 0)); - if (!desc.maxRestorableVersion.present()) { - fprintf(stderr, "The specified backup is not restorable to any version.\n"); - throw restore_error(); - } - - dbVersion = desc.maxRestorableVersion.get(); - TraceEvent("FastRestoreTool").detail("TargetRestoreVersion", dbVersion); - } - state UID randomUID = deterministicRandom()->randomUniqueID(); - TraceEvent("FastRestoreTool") - .detail("SubmitRestoreRequests", ranges.size()) - .detail("RestoreUID", randomUID); - wait(backupAgent.submitParallelRestore(db, - KeyRef(tagName), - ranges, - KeyRef(container), - proxy, - dbVersion, - LockDB::True, - randomUID, - ""_sr, - ""_sr)); - // TODO: Support addPrefix and removePrefix - if (waitForDone) { - // Wait for parallel restore to finish and unlock DB after that - TraceEvent("FastRestoreTool").detail("BackupAndParallelRestore", "WaitForRestoreToFinish"); - wait(backupAgent.parallelRestoreFinish(db, randomUID)); - TraceEvent("FastRestoreTool").detail("BackupAndParallelRestore", "RestoreFinished"); - } else { - TraceEvent("FastRestoreTool") - .detail("RestoreUID", randomUID) - .detail("OperationGuide", "Manually unlock DB when restore finishes"); - printf("WARNING: DB will be in locked state after restore. Need UID:%s to unlock DB\n", - randomUID.toString().c_str()); - } - - restoreVersion = dbVersion; - } else { - state Reference bc = IBackupContainer::openContainer(container, proxy, {}); - // For blobstore:// URLs, use invalidVersion to allow describeBackup to write missing version properties - state BackupDescription description = - wait(bc->describeBackup(false, isBlobstoreUrl(container) ? invalidVersion : 0)); - - if (dbVersion <= 0) { - wait(description.resolveVersionTimes(db)); - if (description.maxRestorableVersion.present()) - restoreVersion = description.maxRestorableVersion.get(); - else { - fprintf(stderr, "Backup is not restorable\n"); - throw restore_invalid_version(); - } - } else { - restoreVersion = dbVersion; - } - - state Optional rset = wait(bc->getRestoreSet(restoreVersion)); - if (!rset.present()) { - fmt::print(stderr, "Insufficient data to restore to version {}\n", restoreVersion); - throw restore_invalid_version(); - } - - // Display the restore information, if requested - if (verbose) { - fmt::print("[DRY RUN] Restoring backup to version: {}\n", restoreVersion); - fmt::print("{}\n", description.toString()); - } - } - - if (waitForDone && verbose) { - // If restore completed then report version restored - fmt::print("Restored to version {0}{1}\n", restoreVersion, (performRestore) ? "" : " (DRY RUN)"); - } - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) - throw; - fprintf(stderr, "ERROR: %s\n", e.what()); - throw; - } - - return Void(); } -ACTOR Future dumpBackupData(const char* name, - std::string destinationContainer, - Optional proxy, - Version beginVersion, - Version endVersion) { - state Reference c = openBackupContainer(name, destinationContainer, proxy, {}); +Future dumpBackupData(const char* name, + std::string destinationContainer, + Optional proxy, + Version beginVersion, + Version endVersion) { + Reference c = openBackupContainer(name, destinationContainer, proxy, {}, 0); if (beginVersion < 0 || endVersion < 0) { - BackupDescription desc = wait(c->describeBackup()); + BackupDescription desc = co_await c->describeBackup(); if (!desc.maxLogEnd.present()) { fprintf(stderr, "ERROR: Backup must have log data in order to use relative begin/end versions.\n"); @@ -2678,29 +2537,26 @@ ACTOR Future dumpBackupData(const char* name, } fmt::print("Scanning version range {0} to {1}\n", beginVersion, endVersion); - BackupFileList files = wait(c->dumpFileList(beginVersion, endVersion)); + BackupFileList files = co_await c->dumpFileList(beginVersion, endVersion); files.toStream(stdout); - - return Void(); } -ACTOR Future expireBackupData(const char* name, - std::string destinationContainer, - Optional proxy, - Version endVersion, - std::string endDatetime, - Database db, - bool force, - Version restorableAfterVersion, - std::string restorableAfterDatetime, - Optional encryptionKeyFile) { +Future expireBackupData(const char* name, + std::string destinationContainer, + Optional proxy, + Version endVersion, + std::string endDatetime, + Database db, + bool force, + Version restorableAfterVersion, + std::string restorableAfterDatetime) { if (!endDatetime.empty()) { - Version v = wait(timeKeeperVersionFromDatetime(endDatetime, db)); + Version v = co_await timeKeeperVersionFromDatetime(endDatetime, db); endVersion = v; } if (!restorableAfterDatetime.empty()) { - Version v = wait(timeKeeperVersionFromDatetime(restorableAfterDatetime, db)); + Version v = co_await timeKeeperVersionFromDatetime(restorableAfterDatetime, db); restorableAfterVersion = v; } @@ -2708,29 +2564,25 @@ ACTOR Future expireBackupData(const char* name, fprintf(stderr, "ERROR: No version or date/time is specified.\n"); printHelpTeaser(name); throw backup_error(); - ; } try { - Reference c = openBackupContainer(name, destinationContainer, proxy, encryptionKeyFile); - - state IBackupContainer::ExpireProgress progress; - state std::string lastProgress; - state Future expire = c->expireData(endVersion, force, &progress, restorableAfterVersion); - - loop { - choose { - when(wait(delay(5))) { - std::string p = progress.toString(); - if (p != lastProgress) { - int spaces = lastProgress.size() - p.size(); - printf("\r%s%s", p.c_str(), (spaces > 0 ? std::string(spaces, ' ').c_str() : "")); - lastProgress = p; - } - } - when(wait(expire)) { - break; - } + Reference c = openBackupContainer(name, destinationContainer, proxy, {}, 0); + + IBackupContainer::ExpireProgress progress; + std::string lastProgress; + Future expire = c->expireData(endVersion, force, &progress, restorableAfterVersion); + + while (true) { + if (auto const res = co_await timeout(expire, 5); res.present()) { + break; + } + + std::string p = progress.toString(); + if (p != lastProgress) { + int spaces = lastProgress.size() - p.size(); + printf("\r%s%s", p.c_str(), (spaces > 0 ? std::string(spaces, ' ').c_str() : "")); + lastProgress = p; } } @@ -2756,32 +2608,25 @@ ACTOR Future expireBackupData(const char* name, fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future deleteBackupContainer(const char* name, - std::string destinationContainer, - Optional proxy) { +Future deleteBackupContainer(const char* name, std::string destinationContainer, Optional proxy) { try { - state Reference c = openBackupContainer(name, destinationContainer, proxy, {}); - state int numDeleted = 0; - state Future done = c->deleteContainer(&numDeleted); + Reference c = openBackupContainer(name, destinationContainer, proxy, {}, 0); + int numDeleted = 0; + Future done = c->deleteContainer(&numDeleted); - state int lastUpdate = -1; + int lastUpdate = -1; printf("Deleting %s...\n", destinationContainer.c_str()); - loop { - choose { - when(wait(done)) { - break; - } - when(wait(delay(5))) { - if (numDeleted != lastUpdate) { - printf("\r%d...", numDeleted); - lastUpdate = numDeleted; - } - } + while (true) { + if (auto const res = co_await timeout(done, 5); res.present()) { + break; + } + + if (numDeleted != lastUpdate) { + printf("\r%d...", numDeleted); + lastUpdate = numDeleted; } } printf("\r%d objects deleted\n", numDeleted); @@ -2792,22 +2637,19 @@ ACTOR Future deleteBackupContainer(const char* name, fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } -ACTOR Future describeBackup(const char* name, - std::string destinationContainer, - Optional proxy, - bool deep, - Optional cx, - bool json, - Optional encryptionKeyFile) { +Future describeBackup(const char* name, + std::string destinationContainer, + Optional proxy, + bool deep, + Optional cx, + bool json) { try { - Reference c = openBackupContainer(name, destinationContainer, proxy, encryptionKeyFile); - state BackupDescription desc = wait(c->describeBackup(deep)); + Reference c = openBackupContainer(name, destinationContainer, proxy, {}, 0); + BackupDescription desc = co_await c->describeBackup(deep); if (cx.present()) - wait(desc.resolveVersionTimes(cx.get())); + co_await desc.resolveVersionTimes(cx.get()); printf("%s\n", (json ? desc.toJSON() : desc.toString()).c_str()); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -2815,8 +2657,6 @@ ACTOR Future describeBackup(const char* name, fprintf(stderr, "ERROR: %s\n", e.what()); throw; } - - return Void(); } static void reportBackupQueryError(UID operationId, JsonBuilderObject& result, std::string errorMessage) { @@ -2846,19 +2686,19 @@ std::pair getMaxMinRestorableVersions(const BackupDescription& // If restoreVersion is invalidVersion or latestVersion, use the maximum or minimum restorable version respectively for // selected key ranges. If restoreTimestamp is specified, any specified restoreVersion will be overridden to the version // resolved to that timestamp. -ACTOR Future queryBackup(const char* name, - std::string destinationContainer, - Optional proxy, - Standalone> keyRangesFilter, - Version restoreVersion, - Version snapshotVersion, - std::string originalClusterFile, - std::string restoreTimestamp, - Verbose verbose, - Optional cx) { - state UID operationId = deterministicRandom()->randomUniqueID(); - state JsonBuilderObject result; - state std::string errorMessage; +Future queryBackup(const char* name, + std::string destinationContainer, + Optional proxy, + Standalone> keyRangesFilter, + Version restoreVersion, + Version snapshotVersion, + std::string originalClusterFile, + std::string restoreTimestamp, + Verbose verbose, + Optional cx) { + UID operationId = deterministicRandom()->randomUniqueID(); + JsonBuilderObject result; + std::string errorMessage; result["key_ranges_filter"] = printable(keyRangesFilter); result["destination_container"] = destinationContainer; @@ -2879,7 +2719,7 @@ ACTOR Future queryBackup(const char* name, result, format("an original cluster file must be given in order to resolve restore target timestamp '%s'", restoreTimestamp.c_str())); - return Void(); + co_return; } if (!fileExists(originalClusterFile)) { @@ -2887,23 +2727,23 @@ ACTOR Future queryBackup(const char* name, result, format("The specified original source database cluster file '%s' does not exist\n", originalClusterFile.c_str())); - return Void(); + co_return; } Database origDb = Database::createDatabase(originalClusterFile, ApiVersion::LATEST_VERSION); - Version v = wait(timeKeeperVersionFromDatetime(restoreTimestamp, origDb)); + Version v = co_await timeKeeperVersionFromDatetime(restoreTimestamp, origDb); result["restore_timestamp"] = restoreTimestamp; result["restore_timestamp_resolved_version"] = v; restoreVersion = v; } - state int64_t totalRangeFilesSize = 0; - state int64_t totalLogFilesSize = 0; - state JsonBuilderArray rangeFilesJson; - state JsonBuilderArray logFilesJson; + int64_t totalRangeFilesSize = 0; + int64_t totalLogFilesSize = 0; + JsonBuilderArray rangeFilesJson; + JsonBuilderArray logFilesJson; try { - state Reference bc = openBackupContainer(name, destinationContainer, proxy, {}); - BackupDescription desc = wait(bc->describeBackup()); + Reference bc = openBackupContainer(name, destinationContainer, proxy, {}, 0); + BackupDescription desc = co_await bc->describeBackup(); // Use continuous log end version for the maximum restorable version for the key ranges when a restorable // version doesn't exist. auto [maxRestorableVersion, minRestorableVersion] = getMaxMinRestorableVersions(desc, !keyRangesFilter.empty()); @@ -2924,14 +2764,14 @@ ACTOR Future queryBackup(const char* name, result, errorMessage = format("the specified restorable version %lld is not valid", restoreVersion)); - return Void(); + co_return; } - state Optional fileSet; + Optional fileSet; if (snapshotVersion != invalidVersion) { // When a snapshot version is specified, we will first get a restore set using the latest snapshot file to // restore to the snapshot version. After snapshot version, we will only use mutation logs to restore. - wait(store(fileSet, bc->getRestoreSet(snapshotVersion, keyRangesFilter))); + fileSet = co_await bc->getRestoreSet(snapshotVersion, keyRangesFilter); if (fileSet.present()) { result["snapshot_version"] = fileSet.get().targetVersion; for (const auto& rangeFile : fileSet.get().ranges) { @@ -2939,7 +2779,7 @@ ACTOR Future queryBackup(const char* name, object["file_name"] = rangeFile.fileName; object["file_size"] = rangeFile.fileSize; object["version"] = rangeFile.version; - object["key_range"] = fileSet.get().keyRanges.count(rangeFile.fileName) == 0 + object["key_range"] = !fileSet.get().keyRanges.contains(rangeFile.fileName) ? "none" : fileSet.get().keyRanges.at(rangeFile.fileName).toString(); rangeFilesJson.push_back(object); @@ -2972,14 +2812,14 @@ ACTOR Future queryBackup(const char* name, result, format("no restorable files set found for specified key ranges from snapshotVersion %lld", snapshotVersion)); - return Void(); + co_return; } // We only need to know all the mutation logs from `snapshotVersion` to `restoreVersion`. - wait(store(fileSet, bc->getRestoreSet(restoreVersion, keyRangesFilter, /*logOnly=*/true, snapshotVersion))); + fileSet = co_await bc->getRestoreSet(restoreVersion, keyRangesFilter, /*logOnly=*/true, snapshotVersion); } else { // When a snapshot version is not specified, we use the latest snapshot to restore to the `restoreVersion`. - wait(store(fileSet, bc->getRestoreSet(restoreVersion, keyRangesFilter))); + fileSet = co_await bc->getRestoreSet(restoreVersion, keyRangesFilter); } if (fileSet.present()) { @@ -2989,7 +2829,7 @@ ACTOR Future queryBackup(const char* name, object["file_name"] = rangeFile.fileName; object["file_size"] = rangeFile.fileSize; object["version"] = rangeFile.version; - object["key_range"] = fileSet.get().keyRanges.count(rangeFile.fileName) == 0 + object["key_range"] = !fileSet.get().keyRanges.contains(rangeFile.fileName) ? "none" : fileSet.get().keyRanges.at(rangeFile.fileName).toString(); rangeFilesJson.push_back(object); @@ -3015,12 +2855,12 @@ ACTOR Future queryBackup(const char* name, .detail("LogFilesBytes", totalLogFilesSize); } else if (snapshotVersion == invalidVersion) { reportBackupQueryError(operationId, result, "no restorable files set found for specified key ranges"); - return Void(); + co_return; } } catch (Error& e) { reportBackupQueryError(operationId, result, e.what()); - return Void(); + co_return; } result["total_range_files_size"] = totalRangeFilesSize; @@ -3032,13 +2872,12 @@ ACTOR Future queryBackup(const char* name, } printf("%s\n", result.getJson().c_str()); - return Void(); } -ACTOR Future listBackup(std::string baseUrl, Optional proxy) { +Future listBackup(std::string baseUrl, Optional proxy) { try { - std::vector containers = wait(IBackupContainer::listContainers(baseUrl, proxy)); - for (std::string container : containers) { + std::vector containers = co_await IBackupContainer::listContainers(baseUrl, proxy); + for (const std::string& container : containers) { printf("%s\n", container.c_str()); } } catch (Error& e) { @@ -3049,24 +2888,24 @@ ACTOR Future listBackup(std::string baseUrl, Optional proxy) fprintf(stderr, "%s\n", msg.c_str()); throw; } - - return Void(); } -ACTOR Future listBackupTags(Database cx) { - state Reference tr = makeReference(cx); - loop { +Future listBackupTags(Database cx) { + auto tr = makeReference(cx); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - std::vector tags = wait(getAllBackupTags(tr)); + std::vector tags = co_await getAllBackupTags(tr); for (const auto& tag : tags) { printf("%s\n", tag.tagName.c_str()); } - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } @@ -3082,21 +2921,22 @@ struct BackupModifyOptions { } }; -ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOptions options) { +Future modifyBackup(Database db, std::string tagName, BackupModifyOptions options) { if (!options.hasChanges()) { fprintf(stderr, "No changes were specified, nothing to do!\n"); throw backup_error(); } - state KeyBackedTag tag = makeBackupTag(tagName); + KeyBackedTag tag = makeBackupTag(tagName); - state Reference tr(new ReadYourWritesTransaction(db)); - loop { + Reference tr(new ReadYourWritesTransaction(db)); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Optional uidFlag = wait(tag.get(db.getReference())); + Optional uidFlag = co_await tag.get(db.getReference()); if (!uidFlag.present()) { fprintf(stderr, "No backup exists on tag '%s'\n", tagName.c_str()); @@ -3108,8 +2948,8 @@ ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOp throw backup_error(); } - state BackupConfig config(uidFlag.get().first); - EBackupState s = wait(config.stateEnum().getOrThrow(tr, Snapshot::False, backup_invalid_info())); + BackupConfig config(uidFlag.get().first); + EBackupState s = co_await config.stateEnum().getOrThrow(tr, Snapshot::False, backup_invalid_info()); if (!FileBackupAgent::isRunnable(s)) { fprintf(stderr, "Backup on tag '%s' is not runnable.\n", tagName.c_str()); throw backup_error(); @@ -3125,8 +2965,8 @@ ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOp } if (options.destURL.present()) { - state Reference prevContainer = - wait(config.backupContainer().getOrThrow(tr, Snapshot::False, backup_invalid_info())); + Reference prevContainer = + co_await config.backupContainer().getOrThrow(tr, Snapshot::False, backup_invalid_info()); std::string prevURL = prevContainer->getURL(); std::string newURL = options.destURL.get(); if (!prevURL.empty() && prevURL.back() == '/') { @@ -3149,16 +2989,19 @@ ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOp } } - state Reference bc; + Reference bc; TraceEvent("ModifyBackupSetNewContainer") .detail("TagName", tagName) .detail("DestURL", options.destURL.get()) .detail("EncryptionKeyFile", options.encryptionKeyFile.present() ? options.encryptionKeyFile.get() : "None"); - bc = openBackupContainer( - exeBackup.toString().c_str(), options.destURL.get(), options.proxy, options.encryptionKeyFile); + bc = openBackupContainer(exeBackup.toString().c_str(), + options.destURL.get(), + options.proxy, + options.encryptionKeyFile, + prevContainer->getEncryptionBlockSize()); try { - wait(timeoutError(bc->create(), 30)); + co_await timeoutError(bc->create(), 30); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) throw; @@ -3168,9 +3011,8 @@ ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOp e.what()); throw backup_error(); } - config.backupContainer().set(tr, bc); - wait(bc->writeEncryptionMetadata()); + co_await bc->writeEncryptionMetadata(bc->getEncryptionBlockSize()); } else if (options.encryptionKeyFile.present()) { fprintf(stdout, " Encryption key file specified without a new destination URL." @@ -3182,22 +3024,22 @@ ACTOR Future modifyBackup(Database db, std::string tagName, BackupModifyOp } if (options.activeSnapshotIntervalSeconds.present()) { - Version begin = wait(config.snapshotBeginVersion().getOrThrow(tr, Snapshot::False, backup_error())); + Version begin = co_await config.snapshotBeginVersion().getOrThrow(tr, Snapshot::False, backup_error()); config.snapshotTargetEndVersion().set(tr, begin + ((int64_t)options.activeSnapshotIntervalSeconds.get() * CLIENT_KNOBS->CORE_VERSIONSPERSECOND)); } - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - - return Void(); } +// NOLINTBEGIN(bugprone-use-after-move): ignore clang-tidy false positives in parseLine's token buffer handling. static std::vector> parseLine(std::string& line, bool& err, bool& partial) { err = false; partial = false; @@ -3219,8 +3061,9 @@ static std::vector> parseLine(std::string& line, bool& er buf.push_back(StringRef((uint8_t*)(line.data() + offset), i - offset)); ret.push_back(std::move(buf)); offset = i = line.find_first_not_of(' ', i + 1); - } else + } else { i++; + } break; case '"': quoted = !quoted; @@ -3233,8 +3076,9 @@ static std::vector> parseLine(std::string& line, bool& er buf.push_back(StringRef((uint8_t*)(line.data() + offset), i - offset)); offset = i = line.find_first_not_of(' ', i); forcetoken = false; - } else + } else { i++; + } break; case '\\': if (i + 2 > line.length()) { @@ -3289,6 +3133,7 @@ static std::vector> parseLine(std::string& line, bool& er return ret; } +// NOLINTEND(bugprone-use-after-move) static void addKeyRange(std::string optionValue, Standalone>& keyRanges) { bool err = false, partial = false; @@ -3539,8 +3384,8 @@ int main(int argc, char* argv[]) { registerCrashHandler(); // Set default of line buffering standard out and error - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); ProgramExe programExe = getProgramType(argv[0]); BackupType backupType = BackupType::UNDEFINED; @@ -3700,21 +3545,6 @@ int main(int argc, char* argv[]) { newArgC - 1, newArgV + 1, g_rgRestoreOptions, SO_O_EXACT | SO_O_HYPHEN_TO_UNDERSCORE); } break; - case ProgramExe::FASTRESTORE_TOOL: - if (newArgC < 2) { - printFastRestoreUsage(false); - return FDB_EXIT_ERROR; - } - // Get the restore operation type - restoreType = getRestoreType(newArgV[1]); - if (restoreType == RestoreType::UNKNOWN) { - args = - std::make_unique(newArgC, newArgV, g_rgOptions, SO_O_EXACT | SO_O_HYPHEN_TO_UNDERSCORE); - } else { - args = std::make_unique( - newArgC - 1, newArgV + 1, g_rgRestoreOptions, SO_O_EXACT | SO_O_HYPHEN_TO_UNDERSCORE); - } - break; case ProgramExe::UNDEFINED: default: fprintf(stderr, "FoundationDB " FDB_VT_PACKAGE_NAME " (v" FDB_VT_VERSION ")\n"); @@ -3756,7 +3586,7 @@ int main(int argc, char* argv[]) { std::string restoreTimestamp; WaitForComplete waitForDone{ false }; StopWhenDone stopWhenDone{ true }; - UsePartitionedLog usePartitionedLog{ false }; // Set to true to use new backup system + MutationLogType mutationLogType{ MutationLogType::DEFAULT }; IncrementalBackupOnly incrementalBackupOnly{ false }; OnlyApplyMutationLogs onlyApplyMutationLogs{ false }; InconsistentSnapshotOnly inconsistentSnapshotOnly{ false }; @@ -3787,6 +3617,7 @@ int main(int argc, char* argv[]) { bool jsonOutput = false; DeleteData deleteData{ false }; Optional encryptionKeyFile; + int encryptionBlockSize = 0; Optional blobManifestUrl; SnapshotMode snapshotMode = SnapshotMode::RANGEFILE; // Default to legacy rangefile mode @@ -3858,12 +3689,12 @@ int main(int argc, char* argv[]) { return FDB_EXIT_SUCCESS; break; case OPT_NOBUFSTDOUT: - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); break; case OPT_BUFSTDOUTERR: - setvbuf(stdout, NULL, _IOFBF, BUFSIZ); - setvbuf(stderr, NULL, _IOFBF, BUFSIZ); + setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); + setvbuf(stderr, nullptr, _IOFBF, BUFSIZ); break; case OPT_QUIET: quietDisplay = true; @@ -4039,9 +3870,18 @@ int main(int argc, char* argv[]) { case OPT_NOSTOPWHENDONE: stopWhenDone.set(false); break; - case OPT_USE_PARTITIONED_LOG: - usePartitionedLog.set(true); + case OPT_MUTATION_LOG_TYPE: { + auto parsedType = getMutationLogType(args->OptionArg()); + if (!parsedType.present()) { + fprintf(stderr, + "ERROR: Unknown mutation log type '%s'. Valid modes are: partitioned-log-experimental, " + "range-partitioned-log-experimental\n", + args->OptionArg()); + return FDB_EXIT_ERROR; + } + mutationLogType = parsedType.get(); break; + } case OPT_INCREMENTALONLY: incrementalBackupOnly.set(true); onlyApplyMutationLogs.set(true); @@ -4050,6 +3890,20 @@ int main(int argc, char* argv[]) { encryptionKeyFile = args->OptionArg(); modifyOptions.encryptionKeyFile = encryptionKeyFile; break; + case OPT_ENCRYPTION_BLOCK_SIZE: + try { + encryptionBlockSize = std::stoi(args->OptionArg()); + } catch (std::exception&) { + fprintf(stderr, "ERROR: Invalid encryption block size `%s'\n", args->OptionArg()); + printHelpTeaser(newArgV[0]); + return FDB_EXIT_ERROR; + } + if (encryptionBlockSize <= 0) { + fprintf(stderr, "ERROR: Invalid encryption block size `%s'\n", args->OptionArg()); + printHelpTeaser(newArgV[0]); + return FDB_EXIT_ERROR; + } + break; case OPT_RESTORECONTAINER: restoreContainer = args->OptionArg(); // If the url starts with '/' then prepend "file://" for backwards compatibility @@ -4217,7 +4071,7 @@ int main(int argc, char* argv[]) { return FDB_EXIT_ERROR; } snapshotMode = parsedMode.get(); - } else if (programExe == ProgramExe::RESTORE || programExe == ProgramExe::FASTRESTORE_TOOL) { + } else if (programExe == ProgramExe::RESTORE) { // Validate and store mode parameter for restore mechanism auto parsedMode = getRestoreMode(args->OptionArg()); if (!parsedMode.present()) { @@ -4244,7 +4098,7 @@ int main(int argc, char* argv[]) { // Add the backup key range case ProgramExe::BACKUP: // Error, if the keys option was not specified - if (backupKeys.size() == 0) { + if (backupKeys.empty()) { fprintf(stderr, "ERROR: Unknown backup option value `%s'\n", args->File(argLoop)); printHelpTeaser(newArgV[0]); return FDB_EXIT_ERROR; @@ -4266,13 +4120,6 @@ int main(int argc, char* argv[]) { return FDB_EXIT_ERROR; break; - case ProgramExe::FASTRESTORE_TOOL: - fprintf( - stderr, "ERROR: FDB Fast Restore Tool does not support argument value `%s'\n", args->File(argLoop)); - printHelpTeaser(newArgV[0]); - return FDB_EXIT_ERROR; - break; - case ProgramExe::DR_AGENT: fprintf(stderr, "ERROR: DR Agent does not support argument value `%s'\n", args->File(argLoop)); printHelpTeaser(newArgV[0]); @@ -4281,7 +4128,7 @@ int main(int argc, char* argv[]) { case ProgramExe::DB_BACKUP: // Error, if the keys option was not specified - if (backupKeys.size() == 0) { + if (backupKeys.empty()) { fprintf(stderr, "ERROR: Unknown DR option value `%s'\n", args->File(argLoop)); printHelpTeaser(newArgV[0]); return FDB_EXIT_ERROR; @@ -4350,16 +4197,16 @@ int main(int argc, char* argv[]) { Future memoryUsageMonitor = startMemoryUsageMonitor(memLimit); - IKnobCollection::setupKnobs(knobs); + setupClientKnobs(knobs); // Reinitialize knobs in order to update knobs that are dependent on explicitly set knobs - IKnobCollection::getMutableGlobalKnobCollection().initialize(Randomize::False, IsSimulated::False); + initializeClientKnobs(Randomize::False, IsSimulated::False); TraceEvent("ProgramStart") .setMaxEventLength(12000) .detail("SourceVersion", getSourceVersion()) .detail("Version", FDB_VT_VERSION) .detail("PackageName", FDB_VT_PACKAGE_NAME) - .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(NULL)) + .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(nullptr)) .setMaxFieldLength(10000) .detail("CommandLine", commandLine) .setMaxFieldLength(0) @@ -4402,7 +4249,7 @@ int main(int argc, char* argv[]) { }; auto initSourceCluster = [&](bool required, bool quiet = false) { - if (!sourceClusterFile.size() && required) { + if (sourceClusterFile.empty() && required) { if (!quiet) { fprintf(stderr, "ERROR: source cluster file is required\n"); } @@ -4427,18 +4274,19 @@ int main(int argc, char* argv[]) { return result.present(); }; - // The fastrestore tool does not yet support multiple ranges and is incompatible with - // features that back up data in the system keys. - if (!restoreSystemKeys && !restoreUserKeys && backupKeys.empty() && - programExe != ProgramExe::FASTRESTORE_TOOL) { - addDefaultBackupRanges(backupKeys); + if (encryptionKeyFile.present() && encryptionBlockSize == 0) { + encryptionBlockSize = DEFAULT_ENCRYPTION_BLOCK_SIZE; } - if ((restoreSystemKeys || restoreUserKeys) && programExe == ProgramExe::FASTRESTORE_TOOL) { - fprintf(stderr, "ERROR: Options: --user-data and --system-metadata are not supported with fastrestore\n"); + if (encryptionBlockSize > 0 && !encryptionKeyFile.present()) { + fprintf(stderr, "ERROR: --encryption-block-size option requires --encryption-key-file to be set\n"); return FDB_EXIT_ERROR; } + if (!restoreSystemKeys && !restoreUserKeys && backupKeys.empty()) { + addDefaultBackupRanges(backupKeys); + } + if ((restoreUserKeys || restoreSystemKeys) && !backupKeys.empty()) { fprintf(stderr, "ERROR: Cannot specify additional ranges when using --user-data or --system-metadata " @@ -4467,7 +4315,7 @@ int main(int argc, char* argv[]) { return FDB_EXIT_ERROR; // Test out the backup url to make sure it parses. Doesn't test to make sure it's actually // writeable. - openBackupContainer(newArgV[0], destinationContainer, proxy, encryptionKeyFile); + openBackupContainer(newArgV[0], destinationContainer, proxy, encryptionKeyFile, encryptionBlockSize); f = stopAfter(submitBackup(db, destinationContainer, proxy, @@ -4478,9 +4326,10 @@ int main(int argc, char* argv[]) { dryRun, waitForDone, stopWhenDone, - usePartitionedLog, + mutationLogType, incrementalBackupOnly, encryptionKeyFile, + encryptionBlockSize, snapshotMode)); break; } @@ -4550,8 +4399,7 @@ int main(int argc, char* argv[]) { db, forceAction, expireRestorableAfterVersion, - expireRestorableAfterDatetime, - encryptionKeyFile)); + expireRestorableAfterDatetime)); break; case BackupType::DELETE_BACKUP: @@ -4572,8 +4420,7 @@ int main(int argc, char* argv[]) { proxy, describeDeep, describeTimestamps ? Optional(db) : Optional(), - jsonOutput, - encryptionKeyFile)); + jsonOutput)); break; case BackupType::LIST: @@ -4694,80 +4541,6 @@ int main(int argc, char* argv[]) { throw restore_error(); } break; - case ProgramExe::FASTRESTORE_TOOL: - // Support --dest-cluster-file option as fdbrestore does - if (dryRun) { - if (restoreType != RestoreType::START) { - fprintf(stderr, "Restore dry run only works for 'start' command\n"); - return FDB_EXIT_ERROR; - } - - // Must explicitly call trace file options handling if not calling Database::createDatabase() - initTraceFile(); - } else { - if (restoreClusterFileDest.empty()) { - fprintf(stderr, "Restore destination cluster file must be specified explicitly.\n"); - return FDB_EXIT_ERROR; - } - - if (!fileExists(restoreClusterFileDest)) { - fprintf(stderr, - "Restore destination cluster file '%s' does not exist.\n", - restoreClusterFileDest.c_str()); - return FDB_EXIT_ERROR; - } - - try { - db = Database::createDatabase(restoreClusterFileDest, ApiVersion::LATEST_VERSION); - } catch (Error& e) { - fprintf(stderr, - "Restore destination cluster file '%s' invalid: %s\n", - restoreClusterFileDest.c_str(), - e.what()); - return FDB_EXIT_ERROR; - } - } - // TODO: We have not implemented the code commented out in this case - switch (restoreType) { - case RestoreType::START: - f = stopAfter(runFastRestoreTool(db, - tagName, - restoreContainer, - proxy, - backupKeys, - restoreVersion, - !dryRun, - Verbose{ !quietDisplay }, - waitForDone)); - break; - case RestoreType::WAIT: - printf("[TODO][ERROR] FastRestore does not support RESTORE_WAIT yet!\n"); - throw restore_error(); - // f = stopAfter( success(ba.waitRestore(db, KeyRef(tagName), true)) ); - break; - case RestoreType::ABORT: - printf("[TODO][ERROR] FastRestore does not support RESTORE_ABORT yet!\n"); - throw restore_error(); - // f = stopAfter( map(ba.abortRestore(db, KeyRef(tagName)), - //[tagName](FileBackupAgent::ERestoreState s) -> Void { printf("Tag: %s - // State: %s\n", tagName.c_str(), - // FileBackupAgent::restoreStateText(s).toString().c_str()); return Void(); - // }) ); - break; - case RestoreType::STATUS: - printf("[TODO][ERROR] FastRestore does not support RESTORE_STATUS yet!\n"); - throw restore_error(); - // If no tag is specifically provided then print all tag status, don't just use "default" - if (tagProvided) - tag = tagName; - // f = stopAfter( map(ba.restoreStatus(db, KeyRef(tag)), [](std::string s) -> Void - //{ printf("%s\n", s.c_str()); return Void(); - // }) ); - break; - default: - throw restore_error(); - } - break; case ProgramExe::DR_AGENT: if (!initCluster() || !initSourceCluster(true)) { return FDB_EXIT_ERROR; @@ -4834,7 +4607,7 @@ int main(int argc, char* argv[]) { std::string s; #ifdef __linux__ - char* demangled = abi::__cxa_demangle(i->first, NULL, NULL, NULL); + char* demangled = abi::__cxa_demangle(i->first, nullptr, nullptr, nullptr); if (demangled) { s = demangled; if (StringRef(s).startsWith("(anonymous namespace)::"_sr)) diff --git a/fdbbackup/include/fdbbackup/FileConverter.h b/fdbbackup/include/fdbbackup/FileConverter.h index 62be298d96c..f858c8d3101 100644 --- a/fdbbackup/include/fdbbackup/FileConverter.h +++ b/fdbbackup/include/fdbbackup/FileConverter.h @@ -24,7 +24,7 @@ #include #include "SimpleOpt/SimpleOpt.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" namespace file_converter { @@ -52,6 +52,7 @@ enum { OPT_END_VERSION_FILTER, OPT_KNOB, OPT_SAVE_FILE, + OPT_ENCRYPTION_KEY_FILE, OPT_HELP }; @@ -84,6 +85,7 @@ CSimpleOpt::SOption gConverterOptions[] = { { OPT_CONTAINER, "-r", SO_REQ_SEP }, { OPT_KNOB, "--knob-", SO_REQ_SEP }, { OPT_SAVE_FILE, "-s", SO_NONE }, { OPT_SAVE_FILE, "--save", SO_NONE }, + { OPT_ENCRYPTION_KEY_FILE, "--encryption-key-file", SO_REQ_SEP }, { OPT_HELP, "-?", SO_NONE }, { OPT_HELP, "-h", SO_NONE }, { OPT_HELP, "--help", SO_NONE }, diff --git a/fdbbackup/tests/backup_tests_common.sh b/fdbbackup/tests/backup_tests_common.sh index 81dac39fce6..1ab34b62503 100644 --- a/fdbbackup/tests/backup_tests_common.sh +++ b/fdbbackup/tests/backup_tests_common.sh @@ -19,9 +19,15 @@ # limitations under the License. # # Common backup test functions -# Shared between s3_backup_test.sh, s3_backup_bulkdump_bulkload.sh, dir_backup_test.sh, etc. +# Shared between blob_backup_restore_test.sh, s3_backup_bulkdump_bulkload.sh, dir_backup_test.sh, etc. # These functions work with both S3/blobstore and file-based backup testing +# Source shared test utilities (output_contains, output_matches, etc.) +_BACKUP_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ -f "${_BACKUP_COMMON_DIR}/../../fdbclient/tests/tests_common.sh" ]]; then + source "${_BACKUP_COMMON_DIR}/../../fdbclient/tests/tests_common.sh" +fi + # Helper function to add base arguments (cluster file and logging) # Uses bash nameref (requires bash 4.3+) to modify the array in place # $1 name of the array variable to modify (passed by name, not value) @@ -106,7 +112,7 @@ function s3_cleanup_url { local local_scratch_dir="${2}" local local_url="${3}" local credentials="${4}" - + local cmd=("${local_build_dir}/bin/s3client") cmd+=("${KNOBS[@]}") @@ -151,7 +157,11 @@ function run_backup { add_common_optional_args cmd_args "${blob_credentials}" "${backup_mode}" "${local_encryption_key_file}" if [[ "${USE_PARTITIONED_LOG:-false}" == "true" ]]; then - cmd_args+=("--partitioned-log-experimental") + cmd_args+=("--mutation-log-type" "partitioned-log-experimental") + fi + + if [[ "${USE_ENCRYPTION_BLOCK_SIZE:-false}" == "true" ]]; then + cmd_args+=("--encryption-block-size" "4096") fi # Start backup without -w flag to avoid hanging @@ -182,12 +192,12 @@ function run_backup { set -e # Check if backup is restorable (differential state) or completed - if echo "${status_output}" | grep -q "is restorable"; then + if output_contains "${status_output}" "is restorable"; then log "Backup is now restorable after ${elapsed}s" break fi - if echo "${status_output}" | grep -q "completed"; then + if output_contains "${status_output}" "completed"; then log "Backup completed after ${elapsed}s" break fi @@ -196,17 +206,17 @@ function run_backup { if [[ $((elapsed % 30)) -eq 0 ]]; then log "Still waiting for backup to become restorable (${elapsed}s elapsed)..." # Show current state for debugging - if echo "${status_output}" | grep -q "is restorable"; then + if output_contains "${status_output}" "is restorable"; then log " Status: backup is restorable (should have exited loop)" - elif echo "${status_output}" | grep -q "in progress to"; then + elif output_contains "${status_output}" "in progress to"; then log " Status: backup running, waiting for snapshot to complete" - elif echo "${status_output}" | grep -q "just started"; then + elif output_contains "${status_output}" "just started"; then log " Status: backup submitted, tasks starting up" fi # Check snapshot mode for debugging - if echo "${status_output}" | grep -q "Snapshot Mode: bulkdump"; then + if output_contains "${status_output}" "Snapshot Mode: bulkdump"; then log " Snapshot Mode: bulkdump (using BulkDump for snapshots)" - elif echo "${status_output}" | grep -q "Snapshot Mode: both"; then + elif output_contains "${status_output}" "Snapshot Mode: both"; then log " Snapshot Mode: both (generating both formats)" fi fi @@ -218,13 +228,48 @@ function run_backup { echo "${status_output}" return 1 fi - + + # For 'both' or 'bulkdump' mode, wait for BulkDump snapshot to be written + # BulkDump writes the snapshot file AFTER DD job completes, which is async from rangefile snapshot + if [[ "${backup_mode}" == "both" || "${backup_mode}" == "bulkdump" ]]; then + log "Waiting for BulkDump snapshot to be written (mode: ${backup_mode})..." + local bulkdump_timeout=120 # Additional timeout for BulkDump + local bulkdump_elapsed=0 + local bulkdump_poll=5 + + while [[ $bulkdump_elapsed -lt $bulkdump_timeout ]]; do + sleep $bulkdump_poll + bulkdump_elapsed=$((bulkdump_elapsed + bulkdump_poll)) + + # Check for BulkDump snapshot file with ",bulk" suffix (used in 'both' mode) + # or check if any snapshot has bulkDumpJobId field + set +e + snapshot_list=$("${local_build_dir}"/bin/fdbbackup describe -d "${local_url}" -C "${local_scratch_dir}/loopback_cluster/fdb.cluster" --log --logdir="${local_scratch_dir}" 2>&1) + set -e + + # Check if BulkDump snapshot exists (look for bulkDumpJobId in describe output) + if output_matches "${snapshot_list}" "bulkDumpJobId\|,bulk"; then + log "BulkDump snapshot found after ${bulkdump_elapsed}s" + break + fi + + if [[ $((bulkdump_elapsed % 30)) -eq 0 ]]; then + log "Still waiting for BulkDump snapshot (${bulkdump_elapsed}s elapsed)..." + fi + done + + if [[ $bulkdump_elapsed -ge $bulkdump_timeout ]]; then + log "Warning: Timeout waiting for BulkDump snapshot after ${bulkdump_timeout}s" + log "BulkDump may still be running - proceeding anyway" + fi + fi + # Check if backup already completed (no need to discontinue) - if echo "${status_output}" | grep -q "completed"; then + if output_contains "${status_output}" "completed"; then log "Backup already completed - no need to discontinue" return 0 fi - + # Stop the backup to finalize it (only if still running) log "Stopping backup to finalize restorable state" set +e @@ -233,7 +278,7 @@ function run_backup { set -e if [[ $stop_exit_code -ne 0 ]]; then - if echo "${stop_output}" | grep -q "already discontinued\|not running\|unneeded"; then + if output_matches "${stop_output}" "already discontinued\|not running\|unneeded"; then log "Backup already completed and finalized - this is success!" else err "Failed to stop backup: ${stop_output}" @@ -299,13 +344,13 @@ function run_restore { # Check if restore completed # Status output contains "State: completed" or "Phase: Complete" when done # Also check "No restore" for when restore tag doesn't exist (completed and cleaned up) - if echo "${status_output}" | grep -qi "State:.*completed\|Phase:.*Complete\|No restore"; then + if output_matches_i "${status_output}" "State:.*completed\|Phase:.*Complete\|No restore"; then log "Restore completed after ${elapsed}s" return 0 fi # Check if restore failed (be specific - "LastError: None" contains "Error" so avoid false positives) - if echo "${status_output}" | grep -qi "State:.*aborted"; then + if output_matches_i "${status_output}" "State:.*aborted"; then err "Restore aborted after ${elapsed}s" log "Status output:" echo "${status_output}" @@ -324,7 +369,7 @@ function run_restore { if [[ $((elapsed % 30)) -eq 0 ]]; then log "Still waiting for restore to complete (${elapsed}s elapsed)..." # Show phase info for debugging - if echo "${status_output}" | grep -qi "Phase:"; then + if output_matches_i "${status_output}" "Phase:"; then phase_info=$(echo "${status_output}" | grep -i "Phase:" | head -1) log " ${phase_info}" fi @@ -380,7 +425,9 @@ function test_encryption_mismatches { if [[ ${exit_code1} -eq 0 ]]; then err "Restore without encryption on encrypted backup succeeded when it should have failed!" - rm -rf "${mismatch_logdir}" + if [[ "${PRESERVE_TEST_DATA:-0}" != "1" ]]; then + rm -rf "${mismatch_logdir}" + fi return 1 fi log "SUCCESS: Restore without encryption on encrypted backup failed as expected (exit code: ${exit_code1})" @@ -402,7 +449,9 @@ function test_encryption_mismatches { if [[ ${exit_code2} -eq 0 ]]; then err "Restore with wrong encryption key succeeded when it should have failed!" - rm -rf "${mismatch_logdir}" + if [[ "${PRESERVE_TEST_DATA:-0}" != "1" ]]; then + rm -rf "${mismatch_logdir}" + fi return 1 fi log "SUCCESS: Restore with wrong encryption key failed as expected (exit code: ${exit_code2})" @@ -428,14 +477,18 @@ function test_encryption_mismatches { if [[ ${exit_code} -eq 0 ]]; then err "Restore with encryption on unencrypted backup succeeded when it should have failed!" - rm -rf "${mismatch_logdir}" + if [[ "${PRESERVE_TEST_DATA:-0}" != "1" ]]; then + rm -rf "${mismatch_logdir}" + fi return 1 fi log "SUCCESS: Restore with encryption on unencrypted backup failed as expected (exit code: ${exit_code})" fi # Clean up separate log directory - rm -rf "${mismatch_logdir}" + if [[ "${PRESERVE_TEST_DATA:-0}" != "1" ]]; then + rm -rf "${mismatch_logdir}" + fi log "All encryption mismatch tests completed successfully" return 0 @@ -461,11 +514,11 @@ function run_restore_wait { status_output=$("${local_build_dir}"/bin/fdbrestore status -t "${local_tag}" --dest-cluster-file "${local_scratch_dir}/loopback_cluster/fdb.cluster" --log --logdir="${local_scratch_dir}" 2>&1) set -e - if echo "${status_output}" | grep -qi "State:.*completed\|Phase:.*Complete\|No restore"; then + if output_matches_i "${status_output}" "State:.*completed\|Phase:.*Complete\|No restore"; then return 0 fi - if echo "${status_output}" | grep -qi "State:.*aborted"; then + if output_matches_i "${status_output}" "State:.*aborted"; then return 1 fi @@ -485,15 +538,18 @@ function run_restore_wait { function setup_backup_test_environment { local http_verbose_level="${1}" local additional_knobs=("${@:2}") - + # Clear proxy environment variables unset HTTP_PROXY unset HTTPS_PROXY - + # Set USE_S3 based on environment readonly USE_S3="${USE_S3:-$( if [[ -n "${OKTETO_NAMESPACE+x}" ]]; then echo "true" ; else echo "false"; fi )}" - - # Set KNOBS based on whether we're using real S3 or MockS3Server + + # Detect GCS from environment variables + detect_blobstore_provider + + # Set KNOBS based on which provider we're using if [[ "${USE_S3}" == "true" ]]; then # Use AWS KMS encryption for real S3 KNOBS=("--knob_blobstore_encryption_type=aws:kms" "--knob_http_verbose_level=${http_verbose_level}") @@ -501,13 +557,13 @@ function setup_backup_test_environment { # No encryption for MockS3Server KNOBS=("--knob_http_verbose_level=${http_verbose_level}") fi - + # Add any additional knobs (handle empty array when set -u is enabled) if [[ ${#additional_knobs[@]} -gt 0 ]]; then KNOBS+=("${additional_knobs[@]}") fi readonly KNOBS - + setup_tls_ca_file } diff --git a/fdbbackup/tests/s3_backup_test.sh b/fdbbackup/tests/blob_backup_restore_test.sh similarity index 85% rename from fdbbackup/tests/s3_backup_test.sh rename to fdbbackup/tests/blob_backup_restore_test.sh index 2664c55c0d5..b2dbc3dc003 100755 --- a/fdbbackup/tests/s3_backup_test.sh +++ b/fdbbackup/tests/blob_backup_restore_test.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash # -# Test backup and restore from s3. +# Test backup and restore from blob storage (S3, GCS, Azure, or MockS3Server). # # In the below we start a small FDB cluster, populate it with -# some data and then start up MockS3Server or use S3 -# if it is available. We then run a backup to 'S3' and then +# some data and then start up MockS3Server or use S3/GCS/Azure +# if configured. We then run a backup to blob storage and then # a restore. We verify the restore is the same as the original. # # Debugging: -# - Run with -x flag: bash -x s3_backup_test.sh... -# - Preserve test data: PRESERVE_TEST_DATA=1 ./s3_backup_test.sh ... +# - Run with -x flag: bash -x blob_backup_restore_test.sh... +# - Preserve test data: PRESERVE_TEST_DATA=1 ./blob_backup_restore_test.sh ... # This will leave all test data including MockS3 persistence files # in the test scratch directory for analysis after the test completes. # # Usage: -# s3_backup_unified.sh [scratch_dir] [--encrypt] +# blob_backup_restore_test.sh [scratch_dir] [--encrypt] # # See https://apple.github.io/foundationdb/backups.html @@ -104,7 +104,7 @@ function test_s3_backup_and_restore { err "Failed loading data into fdb" return 1 fi - log "Run s3 backup" + log "Run blob storage backup" if ! run_backup "${local_build_dir}" "${local_scratch_dir}" "${local_url}" "${TAG}" "${local_encryption_key_file}" "" "${credentials}"; then err "Failed backup" return 1 @@ -121,7 +121,7 @@ function test_s3_backup_and_restore { log "Testing encryption mismatches" test_encryption_mismatches "${local_build_dir}" "${local_scratch_dir}" "${local_url}" "${TAG}" "${local_encryption_key_file}" "${credentials}" - log "Restore from s3" + log "Restore from blob storage" if ! run_restore "${local_build_dir}" "${local_scratch_dir}" "${local_url}" "${TAG}" "${local_encryption_key_file}" "" "${credentials}"; then err "Failed restore" return 1 @@ -131,10 +131,12 @@ function test_s3_backup_and_restore { err "Failed verification of data in fdb" return 1 fi - - # Cleanup test data. - if ! s3_cleanup_url "${local_build_dir}" "${local_scratch_dir}" "${edited_url}" "${credentials}"; then - return 1 + + # Cleanup test data (skip if preserving test data for debugging). + if [[ "${PRESERVE_TEST_DATA:-0}" != "1" ]]; then + if ! s3_cleanup_url "${local_build_dir}" "${local_scratch_dir}" "${edited_url}" "${credentials}"; then + return 1 + fi fi log "Check for Severity=40 errors" if ! grep_for_severity40 "${local_scratch_dir}"; then @@ -150,42 +152,13 @@ set -o pipefail set -o noclobber # Parse command line arguments -USE_ENCRYPTION=false +USE_ENCRYPTION=$(((RANDOM % 2)) && echo true || echo false ) USE_PARTITIONED_LOG=$(((RANDOM % 2)) && echo true || echo false ) -PARAMS=() -while (( "$#" )); do - case "$1" in - --encrypt) - USE_ENCRYPTION=true - shift - ;; - --encrypt-at-random) - USE_ENCRYPTION=$(((RANDOM % 2)) && echo true || echo false ) - shift - ;; - --partitioned-log-experimental) - USE_PARTITIONED_LOG=true - shift - ;; - --partitioned-log-experimental-at-random) - USE_PARTITIONED_LOG=$(((RANDOM % 2)) && echo true || echo false ) - shift - ;; - -*|--*=) # unsupported flags - err "Error: Unsupported flag $1" >&2 - exit 1 - ;; - *) # preserve positional arguments - PARAMS+=("$1") - shift - ;; - esac -done - -# Set positional arguments in their proper place -if [ ${#PARAMS[@]} -ne 0 ]; then - set -- "${PARAMS[@]}" +# Set USE_ENCRYPTION_BLOCK_SIZE only if encryption is enabled +USE_ENCRYPTION_BLOCK_SIZE=false +if [[ "${USE_ENCRYPTION}" == "true" ]]; then + USE_ENCRYPTION_BLOCK_SIZE=$(((RANDOM % 2)) && echo true || echo false) fi # Get the working directory for this script. @@ -257,6 +230,7 @@ else fi readonly ENCRYPTION_KEY_FILE readonly USE_PARTITIONED_LOG +readonly USE_ENCRYPTION_BLOCK_SIZE # Setup S3/MockS3 environment using common function readonly temp_dir_prefix="mocks3_backup_test" diff --git a/fdbbackup/tests/dir_backup_test.sh b/fdbbackup/tests/dir_backup_test.sh index 27523b56c53..a4e3b65f17f 100755 --- a/fdbbackup/tests/dir_backup_test.sh +++ b/fdbbackup/tests/dir_backup_test.sh @@ -19,6 +19,10 @@ SCRATCH_DIR= readonly TAG="test_backup" USE_PARTITIONED_LOG=$(((RANDOM % 2)) && echo true || echo false ) USE_ENCRYPTION=$(((RANDOM % 2)) && echo true || echo false ) +USE_ENCRYPTION_BLOCK_SIZE=false +if [[ "${USE_ENCRYPTION}" == "true" ]]; then + USE_ENCRYPTION_BLOCK_SIZE=$(((RANDOM % 2)) && echo true || echo false) +fi ENCRYPTION_KEY_FILE= # Install signal traps. Calls the cleanup function. @@ -30,21 +34,27 @@ trap cleanup EXIT function cleanup { echo "$(date -Iseconds) cleanup: starting (with 30s hard timeout)" start_cleanup_watchdog 30 - + + if cleanup_with_preserve_check; then + echo "$(date -Iseconds) cleanup: preserving test data, skipping cleanup" + cancel_cleanup_watchdog + return 0 + fi + echo "$(date -Iseconds) cleanup: shutting down FDB cluster" shutdown_fdb_cluster - + if [[ -d "${SCRATCH_DIR}" ]]; then echo "$(date -Iseconds) cleanup: removing scratch dir: ${SCRATCH_DIR}" rm -rf "${SCRATCH_DIR}" fi - + # Clean up encryption key file if [[ -n "${ENCRYPTION_KEY_FILE:-}" ]] && [[ -f "${ENCRYPTION_KEY_FILE}" ]]; then echo "$(date -Iseconds) cleanup: removing encryption key file: ${ENCRYPTION_KEY_FILE}" rm -f "${ENCRYPTION_KEY_FILE}" fi - + echo "$(date -Iseconds) cleanup: complete" cancel_cleanup_watchdog } @@ -82,8 +92,12 @@ function backup { cmd_args+=("--encryption-key-file" "${local_encryption_key_file}") fi + if [[ "${USE_ENCRYPTION_BLOCK_SIZE}" == "true" ]]; then + cmd_args+=("--encryption-block-size" "4096") + fi + if [[ "${USE_PARTITIONED_LOG}" == "true" ]]; then - cmd_args+=("--partitioned-log-experimental") + cmd_args+=("--mutation-log-type" "partitioned-log-experimental") fi if ! "${local_build_dir}"/bin/fdbbackup start "${cmd_args[@]}"; then @@ -157,6 +171,17 @@ function test_dir_backup_and_restore { err "Failed clear data in fdb" return 1 fi + + log "Testing encryption mismatches" + local backup + local backup_name + if ! backup=$(ls -dt "${scratch_dir}"/backups/backup-* | head -1); then + err "Failed to list backups under ${scratch_dir}/backups/" + return 1 + fi + backup_name=$(basename "${backup}") + test_encryption_mismatches "${local_build_dir}" "${scratch_dir}" "file://${scratch_dir}/backups/${backup_name}" "${TAG}" "${local_encryption_key_file}" + log "Restore" if ! restore "${local_build_dir}" "${scratch_dir}" "${local_encryption_key_file}"; then err "Failed restore" @@ -197,6 +222,11 @@ if ! source "${cwd}/../../fdbclient/tests/tests_common.sh"; then err "Failed to source tests_common.sh" exit 1 fi +# shellcheck source=/dev/null +if ! source "${cwd}/backup_tests_common.sh"; then + err "Failed to source backup_tests_common.sh" + exit 1 +fi # Process command-line options. if (( $# < 2 )) || (( $# > 3 )); then @@ -238,6 +268,7 @@ fi readonly USE_PARTITIONED_LOG readonly USE_ENCRYPTION +readonly USE_ENCRYPTION_BLOCK_SIZE readonly ENCRYPTION_KEY_FILE # Startup fdb cluster and backup agent. diff --git a/fdbbackup/tests/s3_backup_bulkdump_bulkload.sh b/fdbbackup/tests/s3_backup_bulkdump_bulkload.sh index f460e903eb3..334d200e070 100755 --- a/fdbbackup/tests/s3_backup_bulkdump_bulkload.sh +++ b/fdbbackup/tests/s3_backup_bulkdump_bulkload.sh @@ -164,7 +164,7 @@ function run_validate_restore_audit { break fi - if echo "${audit_output}" | grep -qE "1221|1230|1010"; then + if output_matches_E "${audit_output}" "1221|1230|1010"; then log "Transient error detected, retrying in ${retry_delay}s..." sleep $retry_delay continue @@ -193,12 +193,12 @@ function run_validate_restore_audit { local status_output status_output=$("${fdbcli}" -C "${cluster_file}" --exec "get_audit_status validate_restore id ${audit_id}" 2>&1) - if echo "${status_output}" | grep -q "Phase.*2"; then + if output_matches "${status_output}" "Phase.*2"; then log "Audit completed successfully after ${elapsed}s" return 0 fi - if echo "${status_output}" | grep -q "Phase.*[34]"; then + if output_matches "${status_output}" "Phase.*[34]"; then err "Audit failed with status: ${status_output}" return 1 fi diff --git a/fdbcli/AdvanceVersionCommand.actor.cpp b/fdbcli/AdvanceVersionCommand.cpp similarity index 77% rename from fdbcli/AdvanceVersionCommand.actor.cpp rename to fdbcli/AdvanceVersionCommand.cpp index 656f06daeb6..7be9acc3916 100644 --- a/fdbcli/AdvanceVersionCommand.actor.cpp +++ b/fdbcli/AdvanceVersionCommand.cpp @@ -1,5 +1,5 @@ /* - * AdvanceVersionCommand.actor.cpp + * AdvanceVersionCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,45 +20,47 @@ #include "boost/lexical_cast.hpp" #include "fmt/format.h" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/IClientApi.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace fdb_cli { const KeyRef advanceVersionSpecialKey = "\xff\xff/management/min_required_commit_version"_sr; -ACTOR Future advanceVersionCommandActor(Reference db, std::vector tokens) { +Future advanceVersionCommandActor(Reference db, std::vector tokens) { if (tokens.size() != 2) { printUsage(tokens[0]); - return false; + co_return false; } else { - state Version v; + Version v; int n = 0; if (sscanf(tokens[1].toString().c_str(), "%" PRId64 "%n", &v, &n) != 1 || n != tokens[1].size()) { printUsage(tokens[0]); - return false; + co_return false; } else { - state Reference tr = db->createTransaction(); - loop { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { - Version rv = wait(safeThreadFutureToFuture(tr->getReadVersion())); + Version rv = co_await safeThreadFutureToFuture(tr->getReadVersion()); if (rv <= v) { tr->set(advanceVersionSpecialKey, boost::lexical_cast(v)); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); + continue; } else { fmt::print("Current read version is {}\n", rv); - return true; + co_return true; } } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } } diff --git a/fdbcli/AuditStorageCommand.actor.cpp b/fdbcli/AuditStorageCommand.cpp similarity index 89% rename from fdbcli/AuditStorageCommand.actor.cpp rename to fdbcli/AuditStorageCommand.cpp index 3d63a58912c..b2f18ebc374 100644 --- a/fdbcli/AuditStorageCommand.actor.cpp +++ b/fdbcli/AuditStorageCommand.cpp @@ -1,5 +1,5 @@ /* - * AuditStorageCommand.actor.cpp + * AuditStorageCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -58,32 +58,30 @@ * ============================================================================ */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/IClientApi.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/Audit.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace fdb_cli { -ACTOR Future auditStorageCommandActor(Reference clusterFile, - std::vector tokens) { +Future auditStorageCommandActor(Reference clusterFile, std::vector tokens) { if (tokens.size() < 2) { printUsage(tokens[0]); - return UID(); + co_return UID(); } - state UID resAuditId; + UID resAuditId; if (tokencmp(tokens[1], "cancel")) { if (tokens.size() != 4) { printUsage(tokens[0]); - return UID(); + co_return UID(); } AuditType type = AuditType::Invalid; if (tokencmp(tokens[2], "ha")) { @@ -98,10 +96,10 @@ ACTOR Future auditStorageCommandActor(Reference c type = AuditType::ValidateRestore; } else { printUsage(tokens[0]); - return UID(); + co_return UID(); } const UID auditId = UID::fromString(tokens[3].toString()); - UID cancelledAuditId = wait(cancelAuditStorage(clusterFile, type, auditId, /*timeoutSeconds=*/60)); + UID cancelledAuditId = co_await cancelAuditStorage(clusterFile, type, auditId, /*timeoutSeconds=*/60); resAuditId = cancelledAuditId; } else { @@ -118,7 +116,7 @@ ACTOR Future auditStorageCommandActor(Reference c type = AuditType::ValidateRestore; } else { printUsage(tokens[0]); - return UID(); + co_return UID(); } Key begin = allKeys.begin, end = allKeys.end; @@ -129,15 +127,15 @@ ACTOR Future auditStorageCommandActor(Reference c end = tokens[3]; } else { printUsage(tokens[0]); - return UID(); + co_return UID(); } if (end > allKeys.end) { printUsage(tokens[0]); - return UID(); + co_return UID(); } if (begin >= end) { printUsage(tokens[0]); - return UID(); + co_return UID(); } KeyValueStoreType engineType = KeyValueStoreType::END; @@ -146,16 +144,16 @@ ACTOR Future auditStorageCommandActor(Reference c if (engineType != KeyValueStoreType::SSD_BTREE_V2 && engineType != KeyValueStoreType::SSD_ROCKSDB_V1 && engineType != KeyValueStoreType::SSD_SHARDED_ROCKSDB) { printUsage(tokens[0]); - return UID(); + co_return UID(); } } // For KeyValueStoreType::END: do not specify any storage engine // Every storage engine will be audited UID startedAuditId = - wait(auditStorage(clusterFile, KeyRangeRef(begin, end), type, engineType, /*timeoutSeconds=*/60)); + co_await auditStorage(clusterFile, KeyRangeRef(begin, end), type, engineType, /*timeoutSeconds=*/60); resAuditId = startedAuditId; } - return resAuditId; + co_return resAuditId; } CommandFactory auditStorageFactory( diff --git a/fdbcli/BulkDumpCommand.actor.cpp b/fdbcli/BulkDumpCommand.cpp similarity index 60% rename from fdbcli/BulkDumpCommand.actor.cpp rename to fdbcli/BulkDumpCommand.cpp index fa95a1e1ea1..3776a7394ad 100644 --- a/fdbcli/BulkDumpCommand.actor.cpp +++ b/fdbcli/BulkDumpCommand.cpp @@ -1,5 +1,5 @@ /* - * BulkDumpCommand.actor.cpp + * BulkDumpCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,13 +20,13 @@ #include #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/BulkDumping.h" #include "fdbclient/BulkLoading.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "flow/Arena.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/Util.h" namespace fdb_cli { @@ -41,45 +41,46 @@ static const std::string BULK_DUMP_HELP_MESSAGE = std::string(BULK_DUMP_MODE_USAGE) + std::string(BULK_DUMP_DUMP_USAGE) + std::string(BULK_DUMP_STATUS_USAGE) + std::string(BULK_DUMP_CANCEL_USAGE); -ACTOR Future getOngoingBulkDumpJob(Database cx) { - state Transaction tr(cx); - loop { +Future getOngoingBulkDumpJob(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { - Optional job = wait(getSubmittedBulkDumpJob(&tr)); + Optional job = co_await getSubmittedBulkDumpJob(&tr); if (job.present()) { fmt::println("Running bulk dumping job: {}", job.get().getJobId().toString()); - return true; + co_return true; } else { fmt::println("No bulk dumping job is running"); - return false; + co_return false; } } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future getBulkDumpCompleteRanges(Database cx, KeyRange rangeToRead) { +Future getBulkDumpCompleteRanges(Database cx, KeyRange rangeToRead) { try { - size_t finishCount = wait(getBulkDumpCompleteTaskCount(cx, rangeToRead)); + size_t finishCount = co_await getBulkDumpCompleteTaskCount(cx, rangeToRead); fmt::println("Finished {} tasks", finishCount); } catch (Error& e) { if (e.code() == error_code_timed_out) { fmt::println("timed out"); } } - return Void(); } -ACTOR Future bulkDumpCommandActor(Database cx, std::vector tokens) { - state BulkDumpState bulkDumpJob; +Future bulkDumpCommandActor(Database cx, std::vector tokens) { + BulkDumpState bulkDumpJob; if (tokencmp(tokens[1], "mode")) { if (tokens.size() != 2 && tokens.size() != 3) { fmt::println("{}", BULK_DUMP_MODE_USAGE); - return UID(); + co_return UID(); } if (tokens.size() == 2) { - int mode = wait(getBulkDumpMode(cx)); + int mode = co_await getBulkDumpMode(cx); if (mode == 0) { fmt::println("Bulkdump mode is disabled"); } else if (mode == 1) { @@ -87,32 +88,32 @@ ACTOR Future bulkDumpCommandActor(Database cx, std::vector token } else { fmt::println("Invalid bulkload mode value {}", mode); } - return UID(); + co_return UID(); } ASSERT(tokens.size() == 3); if (tokencmp(tokens[2], "on")) { - int old = wait(setBulkDumpMode(cx, 1)); + int old = co_await setBulkDumpMode(cx, 1); TraceEvent("SetBulkDumpModeCommand").detail("OldValue", old).detail("NewValue", 1); - return UID(); + co_return UID(); } else if (tokencmp(tokens[2], "off")) { - int old = wait(setBulkDumpMode(cx, 0)); + int old = co_await setBulkDumpMode(cx, 0); TraceEvent("SetBulkDumpModeCommand").detail("OldValue", old).detail("NewValue", 0); - return UID(); + co_return UID(); } else { fmt::println("ERROR: Invalid bulkdump mode value {}", tokens[2].toString()); fmt::println("{}", BULK_DUMP_MODE_USAGE); - return UID(); + co_return UID(); } } else if (tokencmp(tokens[1], "dump")) { - int mode = wait(getBulkDumpMode(cx)); + int mode = co_await getBulkDumpMode(cx); if (mode == 0) { fmt::println("ERROR: Bulkdump mode must be enabled to dump data"); - return UID(); + co_return UID(); } if (tokens.size() != 5) { fmt::println("{}", BULK_DUMP_DUMP_USAGE); - return UID(); + co_return UID(); } Key rangeBegin = tokens[2]; Key rangeEnd = tokens[3]; @@ -121,7 +122,7 @@ ACTOR Future bulkDumpCommandActor(Database cx, std::vector token fmt::println( "ERROR: Invalid range: {} to {}, normal key space only", rangeBegin.toString(), rangeEnd.toString()); fmt::println("{}", BULK_DUMP_DUMP_USAGE); - return UID(); + co_return UID(); } KeyRange range = Standalone(KeyRangeRef(rangeBegin, rangeEnd)); std::string jobRoot = tokens[4].toString(); @@ -130,35 +131,83 @@ ACTOR Future bulkDumpCommandActor(Database cx, std::vector token BulkLoadType::SST, jobRoot.find("blobstore://") == 0 ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP); - wait(submitBulkDumpJob(cx, bulkDumpJob)); - return bulkDumpJob.getJobId(); + co_await submitBulkDumpJob(cx, bulkDumpJob); + co_return bulkDumpJob.getJobId(); } else if (tokencmp(tokens[1], "cancel")) { if (tokens.size() != 3) { fmt::println("{}", BULK_DUMP_CANCEL_USAGE); - return UID(); + co_return UID(); } - state UID jobId = UID::fromString(tokens[2].toString()); - wait(cancelBulkDumpJob(cx, jobId)); + UID jobId = validateBulkJobId(tokens[2], BULK_DUMP_CANCEL_USAGE); + co_await cancelBulkDumpJob(cx, jobId); fmt::println("Job {} has been cancelled. No new tasks will be spawned.", jobId.toString()); - return UID(); + co_return UID(); } else if (tokencmp(tokens[1], "status")) { if (tokens.size() != 2) { fmt::println("{}", BULK_DUMP_STATUS_USAGE); - return UID(); + co_return UID(); } - bool anyJob = wait(getOngoingBulkDumpJob(cx)); - if (!anyJob) { - return UID(); + + // Get aggregated progress + Optional progressOpt = co_await getBulkDumpProgress(cx); + if (!progressOpt.present()) { + fmt::println("No bulk dumping job is running"); + co_return UID(); + } + + BulkDumpProgress progress = progressOpt.get(); + + // ctest is dependent on this output; don't change it. + fmt::println("Running bulk dumping job: {}", progress.jobId.toString()); + + // Check if this bulkdump is owned by another operation + Transaction tr(cx); + Optional jobState; + while (true) { + Error err; + try { + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Optional job = co_await getSubmittedBulkDumpJob(&tr); + jobState = job; + break; + } catch (Error& e) { + err = e; + } + co_await tr.onError(err); + } + + std::string ownerSuffix = co_await getBulkOwnerSuffix(cx, progress.jobId, true); + + fmt::println(" Range: {}{}", progress.jobRange.toString(), ownerSuffix); + fmt::println(""); + fmt::println("Progress:"); + int runningTasks = progress.totalTasks - progress.completeTasks - progress.errorTasks; + if (progress.totalTasks > 0) { + fmt::println(" Tasks: {}/{} complete ({:.1f}%) | {} running{}", + progress.completeTasks, + progress.totalTasks, + progress.progressPercent(), + runningTasks > 0 ? runningTasks : 0, + progress.errorTasks > 0 ? fmt::format(", {} error", progress.errorTasks) : ""); } - KeyRange range = Standalone(KeyRangeRef(normalKeys.begin, normalKeys.end)); - wait(getBulkDumpCompleteRanges(cx, range)); - return UID(); + + fmt::println(" Bytes: {}", formatBytesProgress(progress.completedBytes, progress.totalBytes)); + + printProgressMetrics(progress.avgBytesPerSecond(), progress.etaSeconds(), progress.elapsedSeconds); + + if (progress.errorTasks > 0) { + fmt::println(""); + fmt::println("WARNING: {} tasks in error state", progress.errorTasks); + } + + co_return UID(); } else { printUsage(tokens[0]); printLongDesc(tokens[0]); - return UID(); + co_return UID(); } } diff --git a/fdbcli/BulkLoadCommand.actor.cpp b/fdbcli/BulkLoadCommand.cpp similarity index 68% rename from fdbcli/BulkLoadCommand.actor.cpp rename to fdbcli/BulkLoadCommand.cpp index 63aa9af9b37..3bfb1a64f8a 100644 --- a/fdbcli/BulkLoadCommand.actor.cpp +++ b/fdbcli/BulkLoadCommand.cpp @@ -1,5 +1,5 @@ /* - * BulkLoadCommand.actor.cpp + * BulkLoadCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,13 +18,13 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbcli/fdbcli.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/BulkLoading.h" #include "flow/Arena.h" #include "flow/IRandom.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/Util.h" namespace fdb_cli { @@ -50,32 +50,32 @@ static const std::string BULK_LOAD_HELP_MESSAGE = BULK_LOAD_HISTORY_USAGE + BULK_LOAD_HISTORY_CLEAR_USAGE + BULKLOAD_ADD_LOCK_OWNER_USAGE + BULKLOAD_PRINT_LOCK_USAGE + BULKLOAD_PRINT_LOCK_OWNER_USAGE + BULKLOAD_CLEAR_LOCK_USAGE; -ACTOR Future printPastBulkLoadJob(Database cx) { - std::vector jobs = wait(getBulkLoadJobFromHistory(cx)); +Future printPastBulkLoadJob(Database cx) { + std::vector jobs = co_await getBulkLoadJobFromHistory(cx); if (jobs.empty()) { fmt::println("No bulk loading job in the history"); - return Void(); + co_return; } for (const auto& job : jobs) { ASSERT(job.getPhase() == BulkLoadJobPhase::Complete || job.getPhase() == BulkLoadJobPhase::Error || job.getPhase() == BulkLoadJobPhase::Cancelled); if (!job.getTaskCount().present()) { - fmt::println("Job {} submitted at {} for range {}. The job has not initialized for {} mins and exited " + fmt::println("Job {} submitted at {} for range {}. The job has not initialized for {:.1f} mins and exited " "with status {}.", job.getJobId().toString(), - std::to_string(job.getSubmitTime()), + formatTimeISO8601(job.getSubmitTime()), job.getJobRange().toString(), - std::to_string((job.getEndTime() - job.getSubmitTime()) / 60.0), + (job.getEndTime() - job.getSubmitTime()) / 60.0, convertBulkLoadJobPhaseToString(job.getPhase())); } else { fmt::println( - "Job {} submitted at {} for range {}. The job has {} tasks. The job ran for {} mins and exited " + "Job {} submitted at {} for range {}. The job has {} tasks. The job ran for {:.1f} mins and exited " "with status {}.", job.getJobId().toString(), - std::to_string(job.getSubmitTime()), + formatTimeISO8601(job.getSubmitTime()), job.getJobRange().toString(), job.getTaskCount().get(), - std::to_string((job.getEndTime() - job.getSubmitTime()) / 60.0), + (job.getEndTime() - job.getSubmitTime()) / 60.0, convertBulkLoadJobPhaseToString(job.getPhase())); } if (job.getPhase() == BulkLoadJobPhase::Error) { @@ -83,7 +83,6 @@ ACTOR Future printPastBulkLoadJob(Database cx) { fmt::println("Error message: {}", errorMessage.present() ? errorMessage.get() : "Not provided."); } } - return Void(); } void printBulkLoadJobTotalTaskCount(Optional count) { @@ -95,22 +94,24 @@ void printBulkLoadJobTotalTaskCount(Optional count) { return; } -ACTOR Future printBulkLoadJobProgress(Database cx, BulkLoadJobState job) { - state Transaction tr(cx); - state Key readBegin = job.getJobRange().begin; - state Key readEnd = job.getJobRange().end; - state UID jobId = job.getJobId(); - state RangeResult rangeResult; - state size_t completeTaskCount = 0; - state size_t submitTaskCount = 0; - state size_t errorTaskCount = 0; - state Optional totalTaskCount = job.getTaskCount(); +Future printBulkLoadJobProgress(Database cx, BulkLoadJobState job) { + Transaction tr(cx); + Key readBegin = job.getJobRange().begin; + Key readEnd = job.getJobRange().end; + UID jobId = job.getJobId(); + RangeResult rangeResult; + size_t completeTaskCount = 0; + size_t submitTaskCount = 0; + size_t errorTaskCount = 0; + Optional totalTaskCount = job.getTaskCount(); while (readBegin < readEnd) { + Error err; + bool hasErr = false; try { rangeResult.clear(); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - wait(store(rangeResult, krmGetRanges(&tr, bulkLoadTaskPrefix, KeyRangeRef(readBegin, readEnd)))); + rangeResult = co_await krmGetRanges(&tr, bulkLoadTaskPrefix, KeyRangeRef(readBegin, readEnd)); for (int i = 0; i < rangeResult.size() - 1; ++i) { if (rangeResult[i].value.empty()) { continue; @@ -121,11 +122,10 @@ ACTOR Future printBulkLoadJobProgress(Database cx, BulkLoadJobState job) { fmt::println("Finished {} tasks", completeTaskCount); fmt::println("Error {} tasks", errorTaskCount); printBulkLoadJobTotalTaskCount(totalTaskCount); - if (bulkLoadTask.phase == BulkLoadPhase::Submitted && - bulkLoadTask.getJobId() != UID::fromString("00000000-0000-0000-0000-000000000000")) { + if (bulkLoadTask.phase == BulkLoadPhase::Submitted && bulkLoadTask.getJobId().isValid()) { fmt::println("Job {} has been cancelled or has completed", jobId.toString()); } - return Void(); + co_return; } if (bulkLoadTask.phase == BulkLoadPhase::Complete) { completeTaskCount = completeTaskCount + bulkLoadTask.getManifests().size(); @@ -136,20 +136,23 @@ ACTOR Future printBulkLoadJobProgress(Database cx, BulkLoadJobState job) { } readBegin = rangeResult.back().key; } catch (Error& e) { - wait(tr.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr.onError(err); } } fmt::println("Submitted {} tasks", submitTaskCount); fmt::println("Finished {} tasks", completeTaskCount); fmt::println("Error {} tasks", errorTaskCount); printBulkLoadJobTotalTaskCount(totalTaskCount); - return Void(); } -ACTOR Future bulkLoadCommandActor(Database cx, std::vector tokens) { +Future bulkLoadCommandActor(Database cx, std::vector tokens) { if (tokencmp(tokens[1], "mode")) { if (tokens.size() == 2) { - int mode = wait(getBulkLoadMode(cx)); + int mode = co_await getBulkLoadMode(cx); if (mode == 0) { fmt::println("Bulkload mode is disabled"); } else if (mode == 1) { @@ -157,42 +160,37 @@ ACTOR Future bulkLoadCommandActor(Database cx, std::vector token } else { fmt::println("Invalid bulkload mode value {}", mode); } - return UID(); + co_return UID(); } // Set bulk loading mode if (tokens.size() != 3) { fmt::println("{}", BULK_LOAD_MODE_USAGE); - return UID(); + co_return UID(); } if (tokencmp(tokens[2], "on")) { - int old = wait(setBulkLoadMode(cx, 1)); + int old = co_await setBulkLoadMode(cx, 1); TraceEvent("SetBulkLoadModeCommand").detail("OldValue", old).detail("NewValue", 1); - return UID(); + co_return UID(); } else if (tokencmp(tokens[2], "off")) { - int old = wait(setBulkLoadMode(cx, 0)); + int old = co_await setBulkLoadMode(cx, 0); TraceEvent("SetBulkLoadModeCommand").detail("OldValue", old).detail("NewValue", 0); - return UID(); + co_return UID(); } else { fmt::println("ERROR: Invalid bulkload mode value {}", tokens[2].toString()); fmt::println("{}", BULK_LOAD_MODE_USAGE); - return UID(); + co_return UID(); } } else if (tokencmp(tokens[1], "load")) { - int mode = wait(getBulkLoadMode(cx)); + int mode = co_await getBulkLoadMode(cx); if (mode == 0) { fmt::println("ERROR: Bulkload mode must be enabled to load data"); - return UID(); + co_return UID(); } if (tokens.size() != 6) { fmt::println("{}", BULK_LOAD_LOAD_USAGE); - return UID(); - } - UID jobId = UID::fromString(tokens[2].toString()); - if (!jobId.isValid()) { - fmt::println("ERROR: Invalid job id {}", tokens[2].toString()); - fmt::println("{}", BULK_LOAD_LOAD_USAGE); - return UID(); + co_return UID(); } + UID jobId = validateBulkJobId(tokens[2], BULK_LOAD_LOAD_USAGE.c_str()); Key rangeBegin = tokens[3]; Key rangeEnd = tokens[4]; // Bulk load can only inject data to normal key space, aka "" ~ \xff @@ -200,118 +198,142 @@ ACTOR Future bulkLoadCommandActor(Database cx, std::vector token fmt::println( "ERROR: Invalid range: {} to {}, normal key space only", rangeBegin.toString(), rangeEnd.toString()); fmt::println("{}", BULK_LOAD_LOAD_USAGE); - return UID(); + co_return UID(); } std::string jobRoot = tokens[5].toString(); KeyRange range = Standalone(KeyRangeRef(rangeBegin, rangeEnd)); - state BulkLoadJobState bulkLoadJob = createBulkLoadJob( + BulkLoadJobState bulkLoadJob = createBulkLoadJob( jobId, range, jobRoot, jobRoot.find("blobstore://") == 0 ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP); - wait(submitBulkLoadJob(cx, bulkLoadJob)); - return bulkLoadJob.getJobId(); + co_await submitBulkLoadJob(cx, bulkLoadJob); + co_return bulkLoadJob.getJobId(); } else if (tokencmp(tokens[1], "cancel")) { if (tokens.size() != 3) { fmt::println("{}", BULK_LOAD_CANCEL_USAGE); - return UID(); - } - state UID jobId = UID::fromString(tokens[2].toString()); - if (!jobId.isValid()) { - fmt::println("ERROR: Invalid job id {}", tokens[2].toString()); - fmt::println("{}", BULK_LOAD_CANCEL_USAGE); - return UID(); + co_return UID(); } - wait(cancelBulkLoadJob(cx, jobId)); - fmt::println("Job {} has been cancelled. The job range lock has been cleared", jobId.toString()); - return UID(); + UID cancelJobId = validateBulkJobId(tokens[2], BULK_LOAD_CANCEL_USAGE.c_str()); + co_await cancelBulkLoadJob(cx, cancelJobId); + fmt::println("Job {} has been cancelled. The job range lock has been cleared", cancelJobId.toString()); + co_return UID(); } else if (tokencmp(tokens[1], "status")) { if (tokens.size() != 2) { fmt::println("{}", BULK_LOAD_STATUS_USAGE); - return UID(); + co_return UID(); } - Optional job = wait(getRunningBulkLoadJob(cx)); - if (!job.present()) { + + // Get aggregated progress + Optional progressOpt = co_await getBulkLoadProgress(cx); + if (!progressOpt.present()) { fmt::println("No bulk loading job is running"); - return UID(); + co_return UID(); } - fmt::println("Running bulk loading job: {}", job.get().getJobId().toString()); - fmt::println("Job information: {}", job.get().toString()); - wait(printBulkLoadJobProgress(cx, job.get())); - return UID(); + + BulkLoadProgress progress = progressOpt.get(); + + fmt::println("BulkLoad job {} is in progress.", progress.jobId.toString()); + + std::string ownerSuffix = co_await getBulkOwnerSuffix(cx, progress.jobId, false); + + fmt::println(" Range: {}{}", progress.jobRange.toString(), ownerSuffix); + fmt::println(""); + fmt::println("Progress:"); + + int totalTasks = progress.submittedTasks + progress.triggeredTasks + progress.runningTasks + + progress.completeTasks + progress.errorTasks; + if (totalTasks > 0) { + fmt::println(" Tasks: {}/{} complete ({:.1f}%) | {} submitted, {} triggered, {} running{}", + progress.completeTasks, + totalTasks, + 100.0 * progress.completeTasks / totalTasks, + progress.submittedTasks, + progress.triggeredTasks, + progress.runningTasks, + progress.errorTasks > 0 ? fmt::format(", {} error", progress.errorTasks) : ""); + } + + fmt::println(" Bytes: {}", formatBytesProgress(progress.completedBytes, progress.totalBytes)); + + printProgressMetrics(progress.avgBytesPerSecond(), progress.etaSeconds(), progress.elapsedSeconds); + + // Only show warnings for actual problems (stalled tasks, errors) + printStalledTasks(progress.stalledTasks); + if (progress.errorTasks > 0) { + fmt::println(""); + fmt::println("WARNING: {} tasks in error state", progress.errorTasks); + } + + co_return UID(); } else if (tokencmp(tokens[1], "history")) { if (tokens.size() == 2) { - wait(printPastBulkLoadJob(cx)); - return UID(); + co_await printPastBulkLoadJob(cx); + co_return UID(); } if (tokens.size() == 3) { if (tokencmp(tokens[2], "clear")) { fmt::println("{}", BULK_LOAD_HISTORY_CLEAR_USAGE); - return UID(); + co_return UID(); } else { fmt::println("ERROR: Invalid history option {}", tokens[2].toString()); fmt::println("{}", BULK_LOAD_HISTORY_CLEAR_USAGE); - return UID(); + co_return UID(); } } if (tokens.size() == 4) { if (tokencmp(tokens[2], "clear")) { if (tokencmp(tokens[3], "all")) { - wait(clearBulkLoadJobHistory(cx)); + co_await clearBulkLoadJobHistory(cx); fmt::println("All bulkload job history has been cleared"); - return UID(); + co_return UID(); } else { fmt::println("ERROR: Invalid history clear option {}", tokens[3].toString()); fmt::println("{}", BULK_LOAD_HISTORY_CLEAR_USAGE); - return UID(); + co_return UID(); } } else { fmt::println("ERROR: Invalid history clear option {}", tokens[2].toString()); fmt::println("{}", BULK_LOAD_HISTORY_CLEAR_USAGE); - return UID(); + co_return UID(); } } if (tokens.size() == 5) { if (tokencmp(tokens[2], "clear") && tokencmp(tokens[3], "id")) { - UID jobId = UID::fromString(tokens[4].toString()); - if (!jobId.isValid()) { - fmt::println("ERROR: Invalid job id {}", tokens[4].toString()); - fmt::println("{}", BULK_LOAD_HISTORY_CLEAR_USAGE); - return UID(); - } - wait(clearBulkLoadJobHistory(cx, jobId)); + UID jobId = validateBulkJobId(tokens[4], BULK_LOAD_HISTORY_CLEAR_USAGE.c_str()); + co_await clearBulkLoadJobHistory(cx, jobId); fmt::println("Bulkload job {} has been cleared from history", jobId.toString()); - return jobId; + co_return jobId; } } printLongDesc(tokens[0]); - return UID(); + co_return UID(); } else if (tokencmp(tokens[1], "addlockowner")) { // For debugging purposes and invisible to users. if (tokens.size() != 3) { fmt::println("{}", BULK_LOAD_STATUS_USAGE); - return UID(); + co_return UID(); } std::string ownerUniqueID = tokens[2].toString(); if (ownerUniqueID.empty()) { fmt::println("ERROR: Owner unique id cannot be empty"); fmt::println("{}", BULKLOAD_ADD_LOCK_OWNER_USAGE); - return UID(); + co_return UID(); } - wait(registerRangeLockOwner(cx, ownerUniqueID, ownerUniqueID)); - return UID(); + co_await registerRangeLockOwner(cx, ownerUniqueID, ownerUniqueID); + co_return UID(); } else if (tokencmp(tokens[1], "printlock")) { // For debugging purposes and invisible to users. if (tokens.size() != 2) { fmt::println("{}", BULKLOAD_PRINT_LOCK_USAGE); - return UID(); + co_return UID(); } std::vector> lockedRanges = - wait(findExclusiveReadLockOnRange(cx, normalKeys)); + co_await findExclusiveReadLockOnRange(cx, normalKeys); fmt::println("Total {} locked ranges", lockedRanges.size()); if (lockedRanges.size() > 10) { fmt::println("First 10 locks are:"); @@ -324,34 +346,34 @@ ACTOR Future bulkLoadCommandActor(Database cx, std::vector token fmt::println("Lock {} on {} for {}", count, lock.first.toString(), lock.second.toString()); count++; } - return UID(); + co_return UID(); } else if (tokencmp(tokens[1], "printlockowner")) { // For debugging purposes and invisible to users. if (tokens.size() != 2) { fmt::println("{}", BULKLOAD_PRINT_LOCK_OWNER_USAGE); - return UID(); + co_return UID(); } - std::vector owners = wait(getAllRangeLockOwners(cx)); - for (const auto owner : owners) { + std::vector owners = co_await getAllRangeLockOwners(cx); + for (const auto& owner : owners) { fmt::println("{}", owner.toString()); } - return UID(); + co_return UID(); } else if (tokencmp(tokens[1], "clearlock")) { // For debugging purposes and invisible to users. if (tokens.size() != 3) { fmt::println("{}", BULKLOAD_CLEAR_LOCK_USAGE); - return UID(); + co_return UID(); } std::string ownerUniqueID = tokens[2].toString(); - wait(releaseExclusiveReadLockByUser(cx, ownerUniqueID)); - return UID(); + co_await releaseExclusiveReadLockByUser(cx, ownerUniqueID); + co_return UID(); } else { printUsage(tokens[0]); printLongDesc(tokens[0]); - return UID(); + co_return UID(); } } diff --git a/fdbcli/CMakeLists.txt b/fdbcli/CMakeLists.txt index 090649d080f..ad28477312c 100644 --- a/fdbcli/CMakeLists.txt +++ b/fdbcli/CMakeLists.txt @@ -2,7 +2,7 @@ include(AddFdbTest) fdb_find_sources(FDBCLI_SRCS) add_flow_target(EXECUTABLE NAME fdbcli SRCS ${FDBCLI_SRCS}) -target_include_directories(fdbcli PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbcli PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(fdbcli PRIVATE fdbctl fdbclient SimpleOpt) if (USE_UBSAN) diff --git a/fdbcli/ConfigureCommand.actor.cpp b/fdbcli/ConfigureCommand.cpp similarity index 90% rename from fdbcli/ConfigureCommand.actor.cpp rename to fdbcli/ConfigureCommand.cpp index 3f0a4164abe..13566965a28 100644 --- a/fdbcli/ConfigureCommand.actor.cpp +++ b/fdbcli/ConfigureCommand.cpp @@ -1,5 +1,5 @@ /* - * ConfigureCommand.actor.cpp + * ConfigureCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -19,28 +19,28 @@ */ #include "fdbcli/FlowLineNoise.h" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace fdb_cli { -ACTOR Future configureCommandActor(Reference db, - Database localDb, - std::vector tokens, - LineNoise* linenoise, - Future warn) { - state ConfigurationResult result; - state StatusObject s; - state int startToken = 1; - state bool force = false; +Future configureCommandActor(Reference db, + Database localDb, + std::vector tokens, + LineNoise* linenoise, + Future _warn) { + auto warn = std::move(_warn); + ConfigurationResult result = ConfigurationResult::SUCCESS; + StatusObject s; + int startToken = 1; + bool force = false; if (tokens.size() < 2) result = ConfigurationResult::NO_OPTIONS_PROVIDED; else { @@ -49,19 +49,19 @@ ACTOR Future configureCommandActor(Reference db, startToken = 2; } - state Optional conf; + Optional conf; if (tokens[startToken] == "auto"_sr) { // get cluster status - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); if (!tr->isValid()) { - StatusObject _s = wait(StatusClient::statusFetcher(localDb)); + StatusObject _s = co_await StatusClient::statusFetcher(localDb); s = _s; } else { - state ThreadFuture> statusValueF = tr->get("\xff\xff/status/json"_sr); - Optional statusValue = wait(safeThreadFutureToFuture(statusValueF)); + ThreadFuture> statusValueF = tr->get("\xff\xff/status/json"_sr); + Optional statusValue = co_await safeThreadFutureToFuture(statusValueF); if (!statusValue.present()) { fprintf(stderr, "ERROR: Failed to get status json from the cluster\n"); - return false; + co_return false; } json_spirit::mValue mv; json_spirit::read_string(statusValue.get().toString(), mv); @@ -75,7 +75,7 @@ ACTOR Future configureCommandActor(Reference db, if (!conf.get().isValid()) { printf("Unable to provide advice for the current configuration.\n"); - return false; + co_return false; } bool noChanges = conf.get().old_replication == conf.get().auto_replication && @@ -143,13 +143,13 @@ ACTOR Future configureCommandActor(Reference db, std::printf("%s", outputString.c_str()); if (noChanges) - return true; + co_return true; // TODO: disable completion - Optional line = wait(linenoise->read("Would you like to make these changes? [y/n]> ")); + Optional line = co_await linenoise->read("Would you like to make these changes? [y/n]> "); if (!line.present() || (line.get() != "y" && line.get() != "Y")) { - return true; + co_return true; } } @@ -160,11 +160,16 @@ ACTOR Future configureCommandActor(Reference db, result = ConfigurationResult::BACKUP_WORKER_ENABLED_RESTRICTED; break; } + if (it->startsWith("range_backup_worker_enabled:="_sr)) { + result = ConfigurationResult::RANGE_BACKUP_WORKER_ENABLED_RESTRICTED; + break; + } } - if (result != ConfigurationResult::BACKUP_WORKER_ENABLED_RESTRICTED) { - ConfigurationResult r = wait(ManagementAPI::changeConfig( - db, std::vector(tokens.begin() + startToken, tokens.end()), conf, force)); + if (result != ConfigurationResult::BACKUP_WORKER_ENABLED_RESTRICTED && + result != ConfigurationResult::RANGE_BACKUP_WORKER_ENABLED_RESTRICTED) { + ConfigurationResult r = co_await ManagementAPI::changeConfig( + db, std::vector(tokens.begin() + startToken, tokens.end()), conf, force); result = r; } } @@ -287,11 +292,17 @@ ACTOR Future configureCommandActor(Reference db, "backup system.\n"); ret = false; break; + case ConfigurationResult::RANGE_BACKUP_WORKER_ENABLED_RESTRICTED: + fprintf(stderr, + "ERROR: range_backup_worker_enabled configuration is restricted in fdbcli and managed " + "automatically by the backup system.\n"); + ret = false; + break; default: ASSERT(false); ret = false; }; - return ret; + co_return ret; } void configureGenerator(const char* text, @@ -317,7 +328,6 @@ void configureGenerator(const char* text, "resolvers=", "perpetual_storage_wiggle=", "perpetual_storage_wiggle_locality=", - // TODO(zhewu): update fdbcli command documentation. "perpetual_storage_wiggle_engine=", "storage_migration_type=", nullptr }; @@ -331,7 +341,8 @@ CommandFactory configureFactory( "|" "commit_proxies=|grv_proxies=|logs=|resolvers=>*|" "count=|perpetual_storage_wiggle=|perpetual_storage_wiggle_locality=" - "<:|0>|storage_migration_type={disabled|gradual|aggressive}" + "<:|0>|perpetual_storage_wiggle_engine=|" + "storage_migration_type={disabled|gradual|aggressive}" "|exclude=", "change the database configuration", "The `new' option, if present, initializes a new database with the given configuration rather than changing " diff --git a/fdbcli/ConsistencyCheckCommand.actor.cpp b/fdbcli/ConsistencyCheckCommand.cpp similarity index 72% rename from fdbcli/ConsistencyCheckCommand.actor.cpp rename to fdbcli/ConsistencyCheckCommand.cpp index 6745e3b830b..3540fe6c181 100644 --- a/fdbcli/ConsistencyCheckCommand.actor.cpp +++ b/fdbcli/ConsistencyCheckCommand.cpp @@ -1,5 +1,5 @@ /* - * ConsistencyCheckCommand.actor.cpp + * ConsistencyCheckCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -26,36 +26,34 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace fdb_cli { const KeyRef consistencyCheckSpecialKey = "\xff\xff/management/consistency_check_suspended"_sr; -ACTOR Future consistencyCheckCommandActor(Reference tr, - std::vector tokens, - bool intrans) { +Future consistencyCheckCommandActor(Reference tr, + std::vector const& tokens, + bool intrans) { // Here we do not proceed in a try-catch loop since the transaction is always supposed to succeed. - // If not, the outer loop catch block(fdbcli.actor.cpp) will handle the error and print out the error message + // If not, the outer loop catch block(fdbcli.cpp) will handle the error and print out the error message tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); if (tokens.size() == 1) { // hold the returned standalone object's memory - state ThreadFuture> suspendedF = tr->get(consistencyCheckSpecialKey); - Optional suspended = wait(safeThreadFutureToFuture(suspendedF)); + ThreadFuture> suspendedF = tr->get(consistencyCheckSpecialKey); + Optional suspended = co_await safeThreadFutureToFuture(suspendedF); printf("ConsistencyCheck is %s\n", suspended.present() ? "off" : "on"); } else if (tokens.size() == 2 && tokencmp(tokens[1], "off")) { tr->set(consistencyCheckSpecialKey, Value()); if (!intrans) - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); } else if (tokens.size() == 2 && tokencmp(tokens[1], "on")) { tr->clear(consistencyCheckSpecialKey); if (!intrans) - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); } else { printUsage(tokens[0]); - return false; + co_return false; } - return true; + co_return true; } CommandFactory consistencyCheckFactory( diff --git a/fdbcli/ConsistencyScanCommand.actor.cpp b/fdbcli/ConsistencyScanCommand.cpp similarity index 77% rename from fdbcli/ConsistencyScanCommand.actor.cpp rename to fdbcli/ConsistencyScanCommand.cpp index 1b215dd8d1d..0d56b7a0c35 100644 --- a/fdbcli/ConsistencyScanCommand.actor.cpp +++ b/fdbcli/ConsistencyScanCommand.cpp @@ -1,5 +1,5 @@ /* - * ConsistencyScanCommand.actor.cpp + * ConsistencyScanCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,42 +20,41 @@ #include #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/IClientApi.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/RunTransaction.actor.h" -#include "fdbclient/ConsistencyScanInterface.actor.h" - -#include "flow/actorcompiler.h" // This must be the last #include. +#include "fdbclient/RunTransaction.h" +#include "fdbclient/ConsistencyScanInterface.h" namespace fdb_cli { -ACTOR Future dumpStats(ConsistencyScanState* cs, Reference tr) { - state ConsistencyScanState::LifetimeStats statsLifetime; - state ConsistencyScanState::RoundStats statsCurrentRound; - wait(store(statsLifetime, cs->lifetimeStats().getD(tr)) && - store(statsCurrentRound, cs->currentRoundStats().getD(tr))); +Future dumpStats(ConsistencyScanState* cs, Reference tr) { + ConsistencyScanState::LifetimeStats statsLifetime; + ConsistencyScanState::RoundStats statsCurrentRound; + co_await (store(statsLifetime, cs->lifetimeStats().getD(tr)) && + store(statsCurrentRound, cs->currentRoundStats().getD(tr))); printf( "Current Round:\n%s\n", json_spirit::write_string(json_spirit::mValue(statsCurrentRound.toJSON()), json_spirit::pretty_print).c_str()); printf("Lifetime:\n%s\n", json_spirit::write_string(json_spirit::mValue(statsLifetime.toJSON()), json_spirit::pretty_print).c_str()); - return Void(); + co_return; } -ACTOR Future consistencyScanCommandActor(Database db, std::vector tokens) { +Future consistencyScanCommandActor(Database db, std::vector const& tokens) { // Skip the command token so start at begin+1 - state std::list args(tokens.begin() + 1, tokens.end()); + std::list args(tokens.begin() + 1, tokens.end()); - state ConsistencyScanState cs = ConsistencyScanState(); - state Reference tr = makeReference(db); - state bool error = false; + ConsistencyScanState cs = ConsistencyScanState(); + auto tr = makeReference(db); + bool error = false; - loop { + while (true) { + Error err; try { SystemDBWriteLockedNow(db.getReference())->setOptions(tr); - state ConsistencyScanState::Config config = wait(ConsistencyScanState().config().getD(tr)); + ConsistencyScanState::Config config = co_await ConsistencyScanState().config().getD(tr); if (args.empty()) { printf( @@ -76,9 +75,9 @@ ACTOR Future consistencyScanCommandActor(Database db, std::vectorgetReadVersion().get(); } else if (next == "stats") { - wait(dumpStats(&cs, tr)); + co_await dumpStats(&cs, tr); } else if (next == "clearstats") { - wait(cs.clearStats(tr)); + co_await cs.clearStats(tr); } else if (next == "maxRate") { error = args.empty(); if (!error) { @@ -98,19 +97,20 @@ ACTOR Future consistencyScanCommandActor(Database db, std::vectorcommit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } if (error) { printUsage(tokens[0]); - return false; + co_return false; } - return true; + co_return true; } CommandFactory consistencyScanFactory( @@ -137,4 +137,4 @@ CommandFactory consistencyScanFactory( // TODO: Syntax hint generator )); -} // namespace fdb_cli \ No newline at end of file +} // namespace fdb_cli diff --git a/fdbcli/CoordinatorsCommand.actor.cpp b/fdbcli/CoordinatorsCommand.cpp similarity index 70% rename from fdbcli/CoordinatorsCommand.actor.cpp rename to fdbcli/CoordinatorsCommand.cpp index d7b6ef947cc..319f5144c93 100644 --- a/fdbcli/CoordinatorsCommand.actor.cpp +++ b/fdbcli/CoordinatorsCommand.cpp @@ -1,5 +1,5 @@ /* - * CoordinatorsCommand.actor.cpp + * CoordinatorsCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,50 +20,51 @@ #include "boost/algorithm/string.hpp" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" #include "fdbclient/Knobs.h" #include "fdbclient/Schemas.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { -ACTOR Future printCoordinatorsInfo(Reference db) { - state Reference tr = db->createTransaction(); - loop { +Future printCoordinatorsInfo(Reference db) { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { // Hold the reference to the standalone's memory - state ThreadFuture> descriptionF = tr->get(fdb_cli::clusterDescriptionSpecialKey); - Optional description = wait(safeThreadFutureToFuture(descriptionF)); + ThreadFuture> descriptionF = tr->get(fdb_cli::clusterDescriptionSpecialKey); + Optional description = co_await safeThreadFutureToFuture(descriptionF); ASSERT(description.present()); printf("Cluster description: %s\n", description.get().toString().c_str()); // Hold the reference to the standalone's memory - state ThreadFuture> processesF = tr->get(fdb_cli::coordinatorsProcessSpecialKey); - Optional processes = wait(safeThreadFutureToFuture(processesF)); + ThreadFuture> processesF = tr->get(fdb_cli::coordinatorsProcessSpecialKey); + Optional processes = co_await safeThreadFutureToFuture(processesF); ASSERT(processes.present()); std::vector process_addresses; boost::split(process_addresses, processes.get().toString(), [](char c) { return c == ','; }); printf("Cluster coordinators (%zu): %s\n", process_addresses.size(), processes.get().toString().c_str()); printf("Type `help coordinators' to learn how to change this information.\n"); - return Void(); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future changeCoordinators(Reference db, std::vector tokens) { - state int retries = 0; - state int notEnoughMachineResults = 0; - state StringRef new_cluster_description; - state std::string auto_coordinators_str; +Future changeCoordinators(Reference db, std::vector tokens) { + int retries = 0; + int notEnoughMachineResults = 0; + StringRef new_cluster_description; + std::string auto_coordinators_str; StringRef nameTokenBegin = "description="_sr; for (auto tok = tokens.begin() + 1; tok != tokens.end(); ++tok) { if (tok->startsWith(nameTokenBegin) && new_cluster_description.empty()) { @@ -75,51 +76,52 @@ ACTOR Future changeCoordinators(Reference db, std::vector tr = db->createTransaction(); - loop { + bool automatic = tokens.size() == 2 && tokens[1] == "auto"_sr; + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error caughtErr; + bool hasCaughtErr = false; try { // update cluster description - if (new_cluster_description.size()) { + if (!new_cluster_description.empty()) { tr->set(fdb_cli::clusterDescriptionSpecialKey, new_cluster_description); } // if auto change, read the special key to retrieve the recommended config if (automatic) { // if previous read failed, retry, otherwise, use the same recommended config - if (!auto_coordinators_str.size()) { + if (auto_coordinators_str.empty()) { // Hold the reference to the standalone's memory - state ThreadFuture> auto_coordinatorsF = - tr->get(fdb_cli::coordinatorsAutoSpecialKey); - Optional auto_coordinators = wait(safeThreadFutureToFuture(auto_coordinatorsF)); + ThreadFuture> auto_coordinatorsF = tr->get(fdb_cli::coordinatorsAutoSpecialKey); + Optional auto_coordinators = co_await safeThreadFutureToFuture(auto_coordinatorsF); ASSERT(auto_coordinators.present()); auto_coordinators_str = auto_coordinators.get().toString(); } tr->set(fdb_cli::coordinatorsProcessSpecialKey, auto_coordinators_str); } else if (tokens.size() > 1) { - state std::set new_coordinators_addresses; - state std::set new_coordinators_hostnames; - state std::vector newCoordinatorslist; - state std::vector::iterator t; + std::set new_coordinators_addresses; + std::set new_coordinators_hostnames; + std::vector newCoordinatorslist; + std::vector::iterator t; for (t = tokens.begin() + 1; t != tokens.end(); ++t) { try { if (Hostname::isHostname(t->toString())) { // We do not resolve hostnames here. We commit them as is. const auto& hostname = Hostname::parse(t->toString()); - if (new_coordinators_hostnames.count(hostname)) { + if (new_coordinators_hostnames.contains(hostname)) { fprintf(stderr, "ERROR: passed redundant coordinators: `%s'\n", hostname.toString().c_str()); - return true; + co_return true; } new_coordinators_hostnames.insert(hostname); newCoordinatorslist.push_back(hostname.toString()); } else { const auto& addr = NetworkAddress::parse(t->toString()); - if (new_coordinators_addresses.count(addr)) { + if (new_coordinators_addresses.contains(addr)) { fprintf( stderr, "ERROR: passed redundant coordinators: `%s'\n", addr.toString().c_str()); - return true; + co_return true; } new_coordinators_addresses.insert(addr); newCoordinatorslist.push_back(addr.toString()); @@ -128,7 +130,7 @@ ACTOR Future changeCoordinators(Reference db, std::vectortoString().c_str()); - return true; + co_return true; } throw; } @@ -136,20 +138,26 @@ ACTOR Future changeCoordinators(Reference db, std::vectorset(fdb_cli::coordinatorsProcessSpecialKey, new_coordinators_str); } - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); // commit should always fail here - // If the commit succeeds, the coordinators change and the commit will fail with commit_unknown_result(). + // If the commit succeeds, the coordinators change and the commit will fail with + // commit_unknown_result(). ASSERT(false); } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_special_keys_api_failure) { - std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr)); + caughtErr = e; + hasCaughtErr = true; + } + if (hasCaughtErr) { + Error err(caughtErr); + if (caughtErr.code() == error_code_special_keys_api_failure) { + std::string errorMsgStr = co_await fdb_cli::getSpecialKeysFailureErrorMessage(tr); if (errorMsgStr == ManagementAPI::generateErrorMessage(CoordinatorsResult::NOT_ENOUGH_MACHINES) && notEnoughMachineResults < 1) { - // we could get not_enough_machines if we happen to see the database while the cluster controller is - // updating the worker list, so make sure it happens twice before returning a failure + // we could get not_enough_machines if we happen to see the database while the cluster + // controller is updating the worker list, so make sure it happens twice before returning a + // failure notEnoughMachineResults++; - wait(delay(1.0)); + co_await delay(1.0); tr->reset(); continue; } else if (errorMsgStr == @@ -158,13 +166,13 @@ ACTOR Future changeCoordinators(Reference db, std::vectoronError(err))); + co_await safeThreadFutureToFuture(tr->onError(err)); ++retries; } } @@ -178,13 +186,13 @@ const KeyRef clusterDescriptionSpecialKey = "\xff\xff/configuration/coordinators const KeyRef coordinatorsAutoSpecialKey = "\xff\xff/management/auto_coordinators"_sr; const KeyRef coordinatorsProcessSpecialKey = "\xff\xff/configuration/coordinators/processes"_sr; -ACTOR Future coordinatorsCommandActor(Reference db, std::vector tokens) { +Future coordinatorsCommandActor(Reference db, std::vector tokens) { if (tokens.size() < 2) { - wait(printCoordinatorsInfo(db)); - return true; + co_await printCoordinatorsInfo(db); + co_return true; } else { - bool result = wait(changeCoordinators(db, tokens)); - return result; + bool result = co_await changeCoordinators(db, tokens); + co_return result; } } diff --git a/fdbcli/DataDistributionCommand.actor.cpp b/fdbcli/DataDistributionCommand.cpp similarity index 72% rename from fdbcli/DataDistributionCommand.actor.cpp rename to fdbcli/DataDistributionCommand.cpp index 498cd1a580c..acc0457d72e 100644 --- a/fdbcli/DataDistributionCommand.actor.cpp +++ b/fdbcli/DataDistributionCommand.cpp @@ -1,5 +1,5 @@ /* - * DataDistributionCommand.actor.cpp + * DataDistributionCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,7 +20,7 @@ #include "boost/lexical_cast.hpp" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/IClientApi.h" @@ -29,22 +29,22 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { -ACTOR Future setDDMode(Reference db, int mode) { - state Reference tr = db->createTransaction(); - loop { +Future setDDMode(Reference db, int mode) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { tr->set(fdb_cli::ddModeSpecialKey, boost::lexical_cast(mode)); if (mode) { // set DDMode to 1 will enable all disabled parts, for instance the SS failure monitors. // hold the returned standalone object's memory - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::maintenanceSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - RangeResult res = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult res = co_await safeThreadFutureToFuture(resultFuture); ASSERT(res.size() <= 1); if (res.size() == 1 && res[0].key == fdb_cli::ignoreSSFailureSpecialKey) { // only clear the key if it is currently being used to disable all SS failure data movement @@ -52,30 +52,32 @@ ACTOR Future setDDMode(Reference db, int mode) { } tr->clear(fdb_cli::ddIgnoreRebalanceSpecialKey); } - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - TraceEvent("SetDDModeRetrying").error(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent("SetDDModeRetrying").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future setDDIgnoreRebalanceSwitch(Reference db, uint8_t DDIgnoreOptionMask, bool setMaskedBit) { - state Reference tr = db->createTransaction(); - loop { +Future setDDIgnoreRebalanceSwitch(Reference db, uint8_t DDIgnoreOptionMask, bool setMaskedBit) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + Error err; try { - state ThreadFuture> resultFuture = tr->get(rebalanceDDIgnoreKey); - Optional v = wait(safeThreadFutureToFuture(resultFuture)); + ThreadFuture> resultFuture = tr->get(rebalanceDDIgnoreKey); + Optional v = co_await safeThreadFutureToFuture(resultFuture); uint8_t oldValue = DDIgnore::NONE; // nothing is disabled if (v.present()) { - if (v.get().size() > 0) { + if (!v.get().empty()) { oldValue = BinaryReader::fromStringRef(v.get(), Unversioned()); } else { - // In old version (<= 7.1), the value is an empty string, which means all DD rebalance functions are - // disabled + // In old version (<= 7.1), the value is an empty string, which means all DD rebalance functions + // are disabled oldValue = DDIgnore::ALL; } // printf("oldValue: %d Mask: %d V:%d\n", oldValue, DDIgnoreOptionMask, v.get().size()); @@ -86,11 +88,12 @@ ACTOR Future setDDIgnoreRebalanceSwitch(Reference db, uint8_t D } else { tr->clear(fdb_cli::ddIgnoreRebalanceSpecialKey); } - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -113,30 +116,30 @@ const KeyRef ddIgnoreRebalanceSpecialKey = "\xff\xff/management/data_distributio constexpr auto usage = "Usage: datadistribution |enable " ">\n"; -ACTOR Future dataDistributionCommandActor(Reference db, std::vector tokens) { - state bool result = true; +Future dataDistributionCommandActor(Reference db, std::vector tokens) { + bool result = true; if (tokens.size() != 2 && tokens.size() != 3) { printf(usage); result = false; } else { if (tokencmp(tokens[1], "on")) { - wait(success(setDDMode(db, 1))); + co_await setDDMode(db, 1); printf("Data distribution is turned on.\n"); } else if (tokencmp(tokens[1], "off")) { - wait(success(setDDMode(db, 0))); + co_await setDDMode(db, 0); printf("Data distribution is turned off.\n"); } else if (tokencmp(tokens[1], "disable")) { if (tokencmp(tokens[2], "ssfailure")) { - wait(success((setHealthyZone(db, "IgnoreSSFailures"_sr, 0)))); + co_await (setHealthyZone(db, "IgnoreSSFailures"_sr, 0)); printf("Data distribution is disabled for storage server failures.\n"); } else if (tokencmp(tokens[2], "rebalance")) { - wait(setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_DISK | DDIgnore::REBALANCE_READ)); + co_await setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_DISK | DDIgnore::REBALANCE_READ); printf("Data distribution is disabled for rebalance.\n"); } else if (tokencmp(tokens[2], "rebalance_disk")) { - wait(setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_DISK)); + co_await setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_DISK); printf("Data distribution is disabled for rebalance_disk.\n"); } else if (tokencmp(tokens[2], "rebalance_read")) { - wait(setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_READ)); + co_await setDDIgnoreRebalanceOn(db, DDIgnore::REBALANCE_READ); printf("Data distribution is disabled for rebalance_read.\n"); } else { printf(usage); @@ -144,16 +147,16 @@ ACTOR Future dataDistributionCommandActor(Reference db, std::ve } } else if (tokencmp(tokens[1], "enable")) { if (tokencmp(tokens[2], "ssfailure")) { - wait(success((clearHealthyZone(db, false, true)))); + co_await (clearHealthyZone(db, false, true)); printf("Data distribution is enabled for storage server failures.\n"); } else if (tokencmp(tokens[2], "rebalance")) { - wait(setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_DISK | DDIgnore::REBALANCE_READ)); + co_await setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_DISK | DDIgnore::REBALANCE_READ); printf("Data distribution is enabled for rebalance.\n"); } else if (tokencmp(tokens[2], "rebalance_disk")) { - wait(setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_DISK)); + co_await setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_DISK); printf("Data distribution is enabled for rebalance_disk.\n"); } else if (tokencmp(tokens[2], "rebalance_read")) { - wait(setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_READ)); + co_await setDDIgnoreRebalanceOff(db, DDIgnore::REBALANCE_READ); printf("Data distribution is enabled for rebalance_read.\n"); } else { printf(usage); @@ -164,7 +167,7 @@ ACTOR Future dataDistributionCommandActor(Reference db, std::ve result = false; } } - return result; + co_return result; } // hidden commands, no help text for now diff --git a/fdbcli/DebugCommands.actor.cpp b/fdbcli/DebugCommands.cpp similarity index 77% rename from fdbcli/DebugCommands.actor.cpp rename to fdbcli/DebugCommands.cpp index 2403a650ab4..571a221427c 100644 --- a/fdbcli/DebugCommands.actor.cpp +++ b/fdbcli/DebugCommands.cpp @@ -1,5 +1,5 @@ /* - * DebugCommands.actor.cpp + * DebugCommands.cpp * * This source file is part of the FoundationDB open source project * @@ -20,12 +20,12 @@ #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/CoroUtils.h" namespace fdb_cli { @@ -39,16 +39,18 @@ std::string toHex(StringRef v) { } // Gets a version at which to read from the storage servers -ACTOR Future getVersion(Database cx) { - loop { - state Transaction tr(cx); +Future getVersion(Database cx) { + while (true) { + Transaction tr(cx); tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; try { - Version version = wait(tr.getReadVersion()); - return version; + Version version = co_await tr.getReadVersion(); + co_return version; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -57,78 +59,77 @@ ACTOR Future getVersion(Database cx) { // keyServersPromise will never be set). // If dcid is set, only return the storage servers in the given datacenter. // Ignore the input dcid if the cluster has not set dcid. -ACTOR Future getKeyServers( +Future getKeyServers( Database cx, Promise>>> keyServersPromise, KeyRangeRef kr, Optional dcid) { - state std::vector>> keyServers; + std::vector>> keyServers; // Try getting key server locations from the first commit proxy - state Future> keyServerLocationFuture; - state Key begin = kr.begin; - state Key end = kr.end; - state int limitKeyServers = 100; + Future> keyServerLocationFuture; + Key begin = kr.begin; + Key end = kr.end; + int limitKeyServers = 100; while (begin < end) { - state Reference commitProxyInfo = - wait(cx->getCommitProxiesFuture(UseProvisionalProxies::False)); + Reference commitProxyInfo = co_await cx->getCommitProxiesFuture(UseProvisionalProxies::False); keyServerLocationFuture = commitProxyInfo->get(0, &CommitProxyInterface::getKeyServersLocations) .getReplyUnlessFailedFor( GetKeyServerLocationsRequest({}, begin, end, limitKeyServers, false, latestVersion, Arena()), 2, 0); - state bool keyServersInsertedForThisIteration = false; - choose { - when(ErrorOr shards = wait(keyServerLocationFuture)) { - // Get the list of shards if one was returned. - if (shards.present() && !keyServersInsertedForThisIteration) { - std::vector>> shardResultList; - for (auto& result : shards.get().results) { - std::vector servers; - for (auto& server : result.second) { - // Filter out storage servers that are not in the given datacenter - Optional> serverDcId = server.locality.dcId(); - if (dcid.present() && serverDcId.present() && serverDcId.get() != dcid.get()) { - continue; - } - servers.push_back(server); + bool keyServersInsertedForThisIteration = false; + auto choice = co_await race(keyServerLocationFuture, cx->onProxiesChanged()); + if (choice.index() == 0) { + ErrorOr shards = std::get<0>(std::move(choice)); + // Get the list of shards if one was returned. + if (shards.present() && !keyServersInsertedForThisIteration) { + std::vector>> shardResultList; + for (auto& result : shards.get().results) { + std::vector servers; + for (auto& server : result.second) { + // Filter out storage servers that are not in the given datacenter + Optional> serverDcId = server.locality.dcId(); + if (dcid.present() && serverDcId.present() && serverDcId.get() != dcid.get()) { + continue; } - shardResultList.push_back({ result.first, servers }); + servers.push_back(server); } - keyServers.insert(keyServers.end(), shardResultList.begin(), shardResultList.end()); - keyServersInsertedForThisIteration = true; - begin = shards.get().results.back().first.end; - break; + shardResultList.push_back({ result.first, servers }); } + keyServers.insert(keyServers.end(), shardResultList.begin(), shardResultList.end()); + keyServersInsertedForThisIteration = true; + begin = shards.get().results.back().first.end; } - when(wait(cx->onProxiesChanged())) {} + } else if (choice.index() != 1) { + UNREACHABLE(); } if (!keyServersInsertedForThisIteration) // Retry the entire workflow - wait(delay(1.0)); + co_await delay(1.0); } keyServersPromise.send(keyServers); - return true; + co_return true; } // The command is used to get all storage server addresses for a given key. -ACTOR Future getLocationCommandActor(Database cx, std::vector tokens) { +Future getLocationCommandActor(Database cx, std::vector tokens) { if (tokens.size() != 2 && tokens.size() != 3) { fmt::println("getlocation []\n" "fetch the storage server address for a given key or range.\n" "Displays the addresses of storage servers, or `not found' if location is not found."); - return false; + co_return false; } - state KeyRange kr = KeyRangeRef(tokens[1], tokens.size() == 3 ? tokens[2] : keyAfter(tokens[1])); + KeyRange kr = KeyRangeRef(tokens[1], tokens.size() == 3 ? tokens[2] : keyAfter(tokens[1])); // find key range locations without GRV - state Promise>>> keyServersPromise; - bool found = wait(getKeyServers(cx, keyServersPromise, kr, Optional())); + Promise>>> keyServersPromise; + bool found = co_await getKeyServers(cx, keyServersPromise, kr, Optional()); if (!found) { fmt::println("{} locations not found", printable(kr)); - return false; + co_return false; } std::vector>> keyServers = keyServersPromise.getFuture().get(); @@ -138,33 +139,33 @@ ACTOR Future getLocationCommandActor(Database cx, std::vector t fmt::println(" {}", server.address().toString()); } } - return true; + co_return true; } // hidden commands, no help text for now CommandFactory getLocationCommandFactory("getlocation"); // The command is used to get values from all storage servers that have the given key. -ACTOR Future getallCommandActor(Database cx, std::vector tokens, Version version) { +Future getallCommandActor(Database cx, std::vector tokens, Version version) { if (tokens.size() != 2) { fmt::println("getall \n" "fetch values from all storage servers that have the given key.\n" "Displays the value and the addresses of storage servers, or `not found' if key is not found."); - return false; + co_return false; } - KeyRangeLocationInfo loc = wait(getKeyLocation_internal( - cx, tokens[1], SpanContext(), Optional(), UseProvisionalProxies::False, Reverse::False, version)); + KeyRangeLocationInfo loc = co_await getKeyLocation_internal( + cx, tokens[1], SpanContext(), Optional(), UseProvisionalProxies::False, Reverse::False, version); if (loc.locations) { fmt::println("version is {}", version); fmt::println("`{}' is at:", printable(tokens[1])); - state Reference locations = loc.locations->locations(); - state std::vector> replies; + Reference locations = loc.locations->locations(); + std::vector> replies; for (int i = 0; locations && i < locations->size(); i++) { GetValueRequest req(/*spanContext=*/{}, tokens[1], version, {}, {}, {}); replies.push_back(locations->get(i, &StorageServerInterface::getValue).getReply(req)); } - wait(waitForAll(replies)); + co_await waitForAll(replies); for (int i = 0; i < replies.size(); i++) { std::string ssi = locations->getInterface(i).address().toString(); if (replies[i].isError()) { @@ -177,7 +178,7 @@ ACTOR Future getallCommandActor(Database cx, std::vector tokens } else { fmt::println("`{}': location not found", printable(tokens[1])); } - return true; + co_return true; } // hidden commands, no help text for now CommandFactory getallCommandFactory("getall"); @@ -309,45 +310,46 @@ bool checkResults(Version version, return allSame; } -ACTOR Future doCheckAll(Database cx, KeyRange inputRange, Optional dcid, bool checkAll); +Future doCheckAll(Database cx, KeyRange inputRange, Optional dcid, bool checkAll); // Return whether inconsistency is detected in the inputRange -ACTOR Future doCheckAll(Database cx, KeyRange inputRange, Optional dcid, bool checkAll) { - state Transaction onErrorTr(cx); // This transaction exists only to access onError and its backoff behavior - state bool consistent = true; - loop { +Future doCheckAll(Database cx, KeyRange inputRange, Optional dcid, bool checkAll) { + Transaction onErrorTr(cx); // This transaction exists only to access onError and its backoff behavior + bool consistent = true; + while (true) { + Error err; try { fmt::println("Start checking for range: {}", printable(inputRange)); // Get SS interface for each shard of the inputRange - state Promise>>> keyServerPromise; - bool foundKeyServers = wait(getKeyServers(cx, keyServerPromise, inputRange, dcid)); + Promise>>> keyServerPromise; + bool foundKeyServers = co_await getKeyServers(cx, keyServerPromise, inputRange, dcid); if (!foundKeyServers) { fmt::println("key server locations for {} not found, retrying in 1s...", printable(inputRange)); - wait(delay(1.0)); + co_await delay(1.0); continue; } - state std::vector>> keyServers = + std::vector>> keyServers = keyServerPromise.getFuture().get(); // We partition the entire input range into shards // and we conduct comparison shard by shard - state int i = 0; + int i = 0; for (; i < keyServers.size(); i++) { // for each shard - state KeyRange rangeToCheck = keyServers[i].first; + KeyRange rangeToCheck = keyServers[i].first; rangeToCheck = rangeToCheck & inputRange; // Only check the shard part within the inputRange if (rangeToCheck.empty()) { continue; // Skip the shard if it is outside of the inputRange } const auto& servers = keyServers[i].second; - state Key beginKeyToCheck = rangeToCheck.begin; + Key beginKeyToCheck = rangeToCheck.begin; fmt::println("Key range to check: {}", printable(rangeToCheck)); for (const auto& server : servers) { fmt::println("\t{}", server.address().toString()); } - state std::vector>> replies; - state bool hasMore = true; - state int round = 0; - state Version version; + std::vector>> replies; + bool hasMore = true; + int round = 0; + Version version{ 0 }; while (hasMore) { - wait(store(version, getVersion(cx))); + version = co_await getVersion(cx); replies.clear(); fmt::println("Round {}: {} - {}", round, toHex(beginKeyToCheck), toHex(rangeToCheck.end)); for (const auto& s : keyServers[i].second) { // for each storage server @@ -360,7 +362,7 @@ ACTOR Future doCheckAll(Database cx, KeyRange inputRange, Optional doCheckAll(Database cx, KeyRange inputRange, Optional doCheckAll(Database cx, KeyRange inputRange, Optional doCheckAll(Database cx, KeyRange inputRange, Optional doCheckAll(Database cx, KeyRange inputRange, Optional checkallCommandActor(Database cx, std::vector tokens) { - state bool checkAll = false; // If set, do not return on first error, continue checking all keys - state Optional dcid; - state KeyRange inputRange; +Future checkallCommandActor(Database cx, std::vector tokens) { + bool checkAll = false; // If set, do not return on first error, continue checking all keys + Optional dcid; + KeyRange inputRange; if (tokens.size() == 3) { inputRange = KeyRangeRef(tokens[1], tokens[2]); } else if (tokens.size() == 4 && tokens[3] == "all"_sr) { @@ -475,16 +478,16 @@ ACTOR Future checkallCommandActor(Database cx, std::vector toke "`all` is optional. When `all` is appended, the checker does not stop until all subranges have checked.\n" "Note this is intended to check a small range of keys, not the entire database (consider consistencycheck " "for that purpose)."); - return false; + co_return false; } if (inputRange.empty()) { fmt::println("Input empty range: {}.\nImmediately exit.", printable(inputRange)); - return false; + co_return false; } // At this point, we have a non-empty inputRange to check - bool res = wait(doCheckAll(cx, inputRange, dcid, checkAll)); + bool res = co_await doCheckAll(cx, inputRange, dcid, checkAll); fmt::println("Checking complete. AllSame: {}", res); - return true; + co_return true; } CommandFactory checkallCommandFactory("checkall"); diff --git a/fdbcli/ExcludeCommand.actor.cpp b/fdbcli/ExcludeCommand.cpp similarity index 69% rename from fdbcli/ExcludeCommand.actor.cpp rename to fdbcli/ExcludeCommand.cpp index 1fde6c61de8..2590a802945 100644 --- a/fdbcli/ExcludeCommand.actor.cpp +++ b/fdbcli/ExcludeCommand.cpp @@ -1,5 +1,5 @@ /* - * ExcludeCommand.actor.cpp + * ExcludeCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,33 +18,33 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" #include "fdbclient/Knobs.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/Schemas.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { // Exclude the given servers and localities -ACTOR Future excludeServersAndLocalities(Reference db, - std::vector servers, - std::unordered_set localities, - bool failed, - bool force) { - state Reference tr = db->createTransaction(); - loop { +Future excludeServersAndLocalities(Reference db, + std::vector servers, + std::unordered_set localities, + bool failed, + bool force) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); + Error err; try { - if (force && servers.size()) + if (force && !servers.empty()) tr->set(failed ? fdb_cli::failedForceOptionSpecialKey : fdb_cli::excludedForceOptionSpecialKey, ValueRef()); for (const auto& s : servers) { @@ -52,7 +52,7 @@ ACTOR Future excludeServersAndLocalities(Reference db, : fdb_cli::excludedServersSpecialKeyRange.begin.withSuffix(s.toString()); tr->set(addr, ValueRef()); } - if (force && localities.size()) + if (force && !localities.empty()) tr->set(failed ? fdb_cli::failedLocalityForceOptionSpecialKey : fdb_cli::excludedLocalityForceOptionSpecialKey, ValueRef()); @@ -61,37 +61,38 @@ ACTOR Future excludeServersAndLocalities(Reference db, : fdb_cli::excludedLocalitySpecialKeyRange.begin.withSuffix(l); tr->set(addr, ValueRef()); } - wait(safeThreadFutureToFuture(tr->commit())); - return true; + co_await safeThreadFutureToFuture(tr->commit()); + co_return true; } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_special_keys_api_failure) { - std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr)); - // last character is \n - auto pos = errorMsgStr.find_last_of("\n", errorMsgStr.size() - 2); - auto last_line = errorMsgStr.substr(pos + 1); - // customized the error message for fdbcli - fprintf(stderr, - "%s\n%s\n", - errorMsgStr.substr(0, pos).c_str(), - last_line.find("free space") != std::string::npos - ? "Type `exclude FORCE ' to exclude without checking free space." - : "Type `exclude FORCE failed ' to exclude without performing safety checks."); - return false; - } - TraceEvent(SevWarn, "ExcludeServersAndLocalitiesError").error(err); - wait(safeThreadFutureToFuture(tr->onError(err))); + err = e; + } + if (err.code() == error_code_special_keys_api_failure) { + std::string errorMsgStr = co_await fdb_cli::getSpecialKeysFailureErrorMessage(tr); + // last character is \n + auto pos = errorMsgStr.find_last_of("\n", errorMsgStr.size() - 2); + auto last_line = errorMsgStr.substr(pos + 1); + // customized the error message for fdbcli + fprintf(stderr, + "%s\n%s\n", + errorMsgStr.substr(0, pos).c_str(), + last_line.find("free space") != std::string::npos + ? "Type `exclude FORCE ' to exclude without checking free space." + : "Type `exclude FORCE failed ' to exclude without performing safety checks."); + co_return false; } + TraceEvent(SevWarn, "ExcludeServersAndLocalitiesError").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future> getExcludedServers(Reference db) { - state Reference tr = db->createTransaction(); - loop { +AsyncResult> getExcludedServers(Reference db) { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::excludedServersSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - state RangeResult r = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult r = co_await safeThreadFutureToFuture(resultFuture); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector exclusions; @@ -99,22 +100,24 @@ ACTOR Future> getExcludedServers(Reference d auto addr = i.key.removePrefix(fdb_cli::excludedServersSpecialKeyRange.begin).toString(); exclusions.push_back(addr); } - return exclusions; + co_return exclusions; } catch (Error& e) { - TraceEvent(SevWarn, "GetExcludedServersError").error(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent(SevWarn, "GetExcludedServersError").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } // Get the list of excluded localities by reading the keys. -ACTOR Future> getExcludedLocalities(Reference db) { - state Reference tr = db->createTransaction(); - loop { +AsyncResult> getExcludedLocalities(Reference db) { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::excludedLocalitySpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - state RangeResult r = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult r = co_await safeThreadFutureToFuture(resultFuture); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector excludedLocalities; @@ -122,20 +125,22 @@ ACTOR Future> getExcludedLocalities(ReferenceonError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future> getFailedServers(Reference db) { - state Reference tr = db->createTransaction(); - loop { +AsyncResult> getFailedServers(Reference db) { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::failedServersSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - state RangeResult r = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult r = co_await safeThreadFutureToFuture(resultFuture); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector exclusions; @@ -143,21 +148,23 @@ ACTOR Future> getFailedServers(Reference db) auto addr = i.key.removePrefix(fdb_cli::failedServersSpecialKeyRange.begin).toString(); exclusions.push_back(addr); } - return exclusions; + co_return exclusions; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } // Get the list of failed localities by reading the keys. -ACTOR Future> getFailedLocalities(Reference db) { - state Reference tr = db->createTransaction(); - loop { +AsyncResult> getFailedLocalities(Reference db) { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::failedLocalitySpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - state RangeResult r = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult r = co_await safeThreadFutureToFuture(resultFuture); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector excludedLocalities; @@ -165,40 +172,43 @@ ACTOR Future> getFailedLocalities(Reference auto locality = i.key.removePrefix(fdb_cli::failedLocalitySpecialKeyRange.begin).toString(); excludedLocalities.push_back(locality); } - return excludedLocalities; + co_return excludedLocalities; } catch (Error& e) { - TraceEvent(SevWarn, "GetExcludedLocalitiesError").error(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent(SevWarn, "GetExcludedLocalitiesError").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future> getInProgressExclusion(Reference tr) { +Future> getInProgressExclusion(Reference tr) { ThreadFuture resultFuture = tr->getRange(fdb_cli::exclusionInProgressSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - RangeResult result = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult result = co_await safeThreadFutureToFuture(resultFuture); ASSERT(!result.more && result.size() < CLIENT_KNOBS->TOO_MANY); std::set inProgressExclusion; for (const auto& addr : result) { inProgressExclusion.insert( NetworkAddress::parse(addr.key.removePrefix(fdb_cli::exclusionInProgressSpecialKeyRange.begin).toString())); } - return inProgressExclusion; + co_return inProgressExclusion; } -ACTOR Future> checkForExcludingServers(Reference db, - std::set exclusions, - bool waitForAllExcluded) { - state std::set inProgressExclusion; - state Reference tr = db->createTransaction(); +Future> checkForExcludingServers(Reference db, + std::set exclusions, + bool waitForAllExcluded) { + std::set inProgressExclusion; + Reference tr = db->createTransaction(); tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - loop { + while (true) { inProgressExclusion.clear(); + Error err; + bool hasErr = false; try { - std::set result = wait(getInProgressExclusion(tr)); + std::set result = co_await getInProgressExclusion(tr); if (result.empty()) - return inProgressExclusion; + co_return inProgressExclusion; inProgressExclusion = result; // Check if all of the specified exclusions are done. @@ -219,38 +229,44 @@ ACTOR Future> checkForExcludingServers(ReferenceonError(e))); + err = e; + hasErr = true; + } + if (hasErr) { + TraceEvent(SevWarn, "CheckForExcludingServersError").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } - return inProgressExclusion; + co_return inProgressExclusion; } -ACTOR Future checkForCoordinators(Reference db, std::set exclusions) { +Future checkForCoordinators(Reference db, std::set exclusions) { - state bool foundCoordinator = false; - state std::vector coordinatorList; - state Reference tr = db->createTransaction(); - loop { + bool foundCoordinator = false; + std::vector coordinatorList; + Reference tr = db->createTransaction(); + while (true) { + Error err; try { // Hold the reference to the standalone's memory - state ThreadFuture> coordinatorsF = tr->get(fdb_cli::coordinatorsProcessSpecialKey); - Optional coordinators = wait(safeThreadFutureToFuture(coordinatorsF)); + ThreadFuture> coordinatorsF = tr->get(fdb_cli::coordinatorsProcessSpecialKey); + Optional coordinators = co_await safeThreadFutureToFuture(coordinatorsF); ASSERT(coordinators.present()); coordinatorList = NetworkAddress::parseList(coordinators.get().toString()); break; } catch (Error& e) { - TraceEvent(SevWarn, "CheckForCoordinatorsError").error(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent(SevWarn, "CheckForCoordinatorsError").error(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } for (const auto& c : coordinatorList) { @@ -263,7 +279,6 @@ ACTOR Future checkForCoordinators(Reference db, std::set excludeCommandActor(Reference db, std::vector tokens, Future warn) { +Future excludeCommandActor(Reference db, std::vector tokens, Future _warn) { + auto warn = std::move(_warn); if (tokens.size() <= 1) { - state std::vector excludedAddresses = wait(getExcludedServers(db)); - state std::vector excludedLocalities = wait(getExcludedLocalities(db)); - state std::vector failedAddresses = wait(getFailedServers(db)); - state std::vector failedLocalities = wait(getFailedLocalities(db)); + std::vector excludedAddresses = co_await getExcludedServers(db); + std::vector excludedLocalities = co_await getExcludedLocalities(db); + std::vector failedAddresses = co_await getFailedServers(db); + std::vector failedLocalities = co_await getFailedLocalities(db); - if (!excludedAddresses.size() && !excludedLocalities.size() && !failedAddresses.size() && - !failedLocalities.size()) { + if (excludedAddresses.empty() && excludedLocalities.empty() && failedAddresses.empty() && + failedLocalities.empty()) { printf("There are currently no servers or localities excluded from the database.\n" "To learn how to exclude a server, type `help exclude'.\n"); - return true; + co_return true; } printf("There are currently %zu servers or localities being excluded from the database:\n", @@ -305,7 +321,7 @@ ACTOR Future excludeCommandActor(Reference db, std::vector'.\n" "To return one of these servers to the cluster, type `include '.\n"); @@ -320,33 +336,33 @@ ACTOR Future excludeCommandActor(Reference db, std::vector'.\n"); } printf("\n"); Reference tr = db->createTransaction(); - std::set inProgressExclusion = wait(getInProgressExclusion(tr)); + std::set inProgressExclusion = co_await getInProgressExclusion(tr); printf("There are currently %zu processes for which exclusion is in progress:\n", inProgressExclusion.size()); for (const auto& addr : inProgressExclusion) { printf("%s\n", addr.toString().c_str()); } - return true; + co_return true; } else { - state std::set exclusionSet; - state std::vector exclusionAddresses; - state std::unordered_set exclusionLocalities; - state std::vector noMatchLocalities; - state bool force = false; - state bool waitForAllExcluded = true; - state bool markFailed = false; - state std::vector workers; - state std::map server_interfaces; - state Future future_workers = fdb_cli::getWorkers(db, &workers); - state Future future_server_interfaces = fdb_cli::getStorageServerInterfaces(db, &server_interfaces); - wait(success(future_workers) && success(future_server_interfaces)); + std::set exclusionSet; + std::vector exclusionAddresses; + std::unordered_set exclusionLocalities; + std::vector noMatchLocalities; + bool force = false; + bool waitForAllExcluded = true; + bool markFailed = false; + std::vector workers; + std::map server_interfaces; + Future future_workers = fdb_cli::getWorkers(db, &workers); + Future future_server_interfaces = fdb_cli::getStorageServerInterfaces(db, &server_interfaces); + co_await (success(future_workers) && success(future_server_interfaces)); for (auto t = tokens.begin() + 1; t != tokens.end(); ++t) { if (*t == "FORCE"_sr) { @@ -380,7 +396,7 @@ ACTOR Future excludeCommandActor(Reference db, std::vectortoString().c_str()); if (t->toString().find(":tls") != std::string::npos) printf(" Do not include the `:tls' suffix when naming a process\n"); - return true; + co_return true; } exclusionSet.insert(a); exclusionAddresses.push_back(a); @@ -391,12 +407,12 @@ ACTOR Future excludeCommandActor(Reference db, std::vector excludeCommandActor(Reference db, std::vector notExcludedServers = - wait(checkForExcludingServers(db, exclusionSet, waitForAllExcluded)); + std::set notExcludedServers = + co_await checkForExcludingServers(db, exclusionSet, waitForAllExcluded); std::map> workerPorts; - for (auto addr : workers) + for (const auto& addr : workers) workerPorts[addr.address.ip].insert(addr.address.port); // Print a list of all excluded addresses that don't have a corresponding worker @@ -418,7 +434,7 @@ ACTOR Future excludeCommandActor(Reference db, std::vector 0 && worker->second.count(addr.port) == 0) + else if (addr.port > 0 && !worker->second.contains(addr.port)) absentExclusions.insert(addr); } @@ -470,8 +486,8 @@ ACTOR Future excludeCommandActor(Reference db, std::vector expensiveDataCheckCommandActor( +Future expensiveDataCheckCommandActor( Reference db, Reference tr, std::vector tokens, std::map>* address_interface) { - state bool result = true; - state std::string addressesStr; + bool result = true; + std::string addressesStr; if (tokens.size() == 1) { // initialize worker interfaces address_interface->clear(); - wait(getWorkerInterfaces(tr, address_interface, true)); + co_await getWorkerInterfaces(tr, address_interface, true); } if (tokens.size() == 1 || tokencmp(tokens[1], "list")) { - if (address_interface->size() == 0) { + if (address_interface->empty()) { printf("\nNo addresses can be checked.\n"); } else if (address_interface->size() == 1) { printf("\nThe following address can be checked:\n"); } else { printf("\nThe following %zu addresses can be checked:\n", address_interface->size()); } - for (auto it : *address_interface) { + for (const auto& it : *address_interface) { printf("%s\n", printable(it.first).c_str()); } printf("\n"); } else if (tokencmp(tokens[1], "all")) { - if (address_interface->size() == 0) { + if (address_interface->empty()) { fprintf(stderr, "ERROR: no processes to check. You must run the `expensive_data_check’ " "command before running `expensive_data_check all’.\n"); @@ -72,7 +71,7 @@ ACTOR Future expensiveDataCheckCommandActor( } addressesStr = boost::algorithm::join(addressesVec, ","); // make sure we only call the interface once to send requests in parallel - int64_t checkRequestsSent = wait(safeThreadFutureToFuture(db->rebootWorker(addressesStr, true, 0))); + int64_t checkRequestsSent = co_await safeThreadFutureToFuture(db->rebootWorker(addressesStr, true, 0)); if (!checkRequestsSent) { result = false; fprintf(stderr, @@ -83,7 +82,7 @@ ACTOR Future expensiveDataCheckCommandActor( } } } else { - state int i; + int i{ 0 }; for (i = 1; i < tokens.size(); i++) { if (!address_interface->count(tokens[i])) { fprintf(stderr, "ERROR: process `%s' not recognized.\n", printable(tokens[i]).c_str()); @@ -98,7 +97,7 @@ ACTOR Future expensiveDataCheckCommandActor( addressesVec.push_back(tokens[i].toString()); } addressesStr = boost::algorithm::join(addressesVec, ","); - int64_t checkRequestsSent = wait(safeThreadFutureToFuture(db->rebootWorker(addressesStr, true, 0))); + int64_t checkRequestsSent = co_await safeThreadFutureToFuture(db->rebootWorker(addressesStr, true, 0)); if (!checkRequestsSent) { result = false; fprintf(stderr, @@ -110,7 +109,7 @@ ACTOR Future expensiveDataCheckCommandActor( } } } - return result; + co_return result; } // hidden commands, no help text for now CommandFactory expensiveDataCheckFactory("expensive_data_check"); diff --git a/fdbcli/FileConfigureCommand.actor.cpp b/fdbcli/FileConfigureCommand.cpp similarity index 87% rename from fdbcli/FileConfigureCommand.actor.cpp rename to fdbcli/FileConfigureCommand.cpp index a521e70aa92..b4535e53686 100644 --- a/fdbcli/FileConfigureCommand.actor.cpp +++ b/fdbcli/FileConfigureCommand.cpp @@ -1,5 +1,5 @@ /* - * FileConfigureCommand.actor.cpp + * FileConfigureCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -19,35 +19,33 @@ */ #include "fdbcli/FlowLineNoise.h" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/DatabaseConfiguration.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/Schemas.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace fdb_cli { -ACTOR Future fileConfigureCommandActor(Reference db, - std::string filePath, - bool isNewDatabase, - bool force) { - state ConfigurationResult result; +Future fileConfigureCommandActor(Reference db, + std::string const& filePath, + bool isNewDatabase, + bool force) { + ConfigurationResult result; std::string contents(readFileBytes(filePath, 100000)); json_spirit::mValue config; if (!json_spirit::read_string(contents, config)) { fprintf(stderr, "ERROR: Invalid JSON\n"); - return false; + co_return false; } if (config.type() != json_spirit::obj_type) { fprintf(stderr, "ERROR: Configuration file must contain a JSON object\n"); - return false; + co_return false; } StatusObject configJSON = config.get_obj(); @@ -59,17 +57,17 @@ ACTOR Future fileConfigureCommandActor(Reference db, std::string errorStr; if (!schemaMatch(schema.get_obj(), configJSON, errorStr)) { printf("%s", errorStr.c_str()); - return false; + co_return false; } - state std::string configString; + std::string configString; try { configString += DatabaseConfiguration::configureStringFromJSON(configJSON); } catch (Error& e) { fmt::print("ERROR: {}", e.what()); printUsage("fileconfigure"_sr); - return false; + co_return false; } if (isNewDatabase) { @@ -82,8 +80,10 @@ ACTOR Future fileConfigureCommandActor(Reference db, // This setting is now managed automatically by the backup system. if (configString.find(" backup_worker_enabled:=") != std::string::npos) { result = ConfigurationResult::BACKUP_WORKER_ENABLED_RESTRICTED; + } else if (configString.find(" range_backup_worker_enabled:=") != std::string::npos) { + result = ConfigurationResult::RANGE_BACKUP_WORKER_ENABLED_RESTRICTED; } else { - ConfigurationResult r = wait(ManagementAPI::changeConfig(db, configString, force)); + ConfigurationResult r = co_await ManagementAPI::changeConfig(db, configString, force); result = r; } // Real errors get thrown from makeInterruptable and printed by the catch block in cli(), but @@ -170,11 +170,17 @@ ACTOR Future fileConfigureCommandActor(Reference db, "backup system\n"); ret = false; break; + case ConfigurationResult::RANGE_BACKUP_WORKER_ENABLED_RESTRICTED: + fprintf(stderr, + "ERROR: range_backup_worker_enabled configuration is restricted in fdbcli and managed " + "automatically by the backup system\n"); + ret = false; + break; default: ASSERT(false); ret = false; }; - return ret; + co_return ret; } CommandFactory fileconfigureFactory( diff --git a/fdbcli/FlowLineNoise.actor.cpp b/fdbcli/FlowLineNoise.cpp similarity index 93% rename from fdbcli/FlowLineNoise.actor.cpp rename to fdbcli/FlowLineNoise.cpp index 91757e39267..05f52f57b4b 100644 --- a/fdbcli/FlowLineNoise.actor.cpp +++ b/fdbcli/FlowLineNoise.cpp @@ -1,5 +1,5 @@ /* - * FlowLineNoise.actor.cpp + * FlowLineNoise.cpp * * This source file is part of the FoundationDB open source project * @@ -40,7 +40,6 @@ #else #define HAVE_LINENOISE 0 #endif -#include "flow/actorcompiler.h" // This must be the last #include. struct LineNoiseReader final : IThreadPoolReceiver { void init() override {} @@ -143,8 +142,8 @@ Future> LineNoise::read(std::string const& prompt) { return f; } -ACTOR Future waitKeyboardInterrupt(boost::asio::io_service* ios) { - state boost::asio::signal_set signals(*ios, SIGINT); +Future waitKeyboardInterrupt(boost::asio::io_service* ios) { + boost::asio::signal_set signals(*ios, SIGINT); Promise result; signals.async_wait([result](const boost::system::error_code& error, int signal_number) { if (error) { @@ -154,12 +153,11 @@ ACTOR Future waitKeyboardInterrupt(boost::asio::io_service* ios) { } }); - wait(result.getFuture()); - return Void(); + co_await result.getFuture(); } Future LineNoise::onKeyboardInterrupt() { - boost::asio::io_service* ios = (boost::asio::io_service*)g_network->global(INetwork::enASIOService); + auto* ios = (boost::asio::io_service*)g_network->global(INetwork::enASIOService); if (!ios) return Never(); return waitKeyboardInterrupt(ios); diff --git a/fdbcli/ForceRecoveryWithDataLossCommand.actor.cpp b/fdbcli/ForceRecoveryWithDataLossCommand.cpp similarity index 82% rename from fdbcli/ForceRecoveryWithDataLossCommand.actor.cpp rename to fdbcli/ForceRecoveryWithDataLossCommand.cpp index 7c5e35c152d..37e1d4a1ee6 100644 --- a/fdbcli/ForceRecoveryWithDataLossCommand.actor.cpp +++ b/fdbcli/ForceRecoveryWithDataLossCommand.cpp @@ -1,5 +1,5 @@ /* - * ForceRecoveryWithDataLossCommand.actor.cpp + * ForceRecoveryWithDataLossCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,24 +18,22 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/IClientApi.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace fdb_cli { -ACTOR Future forceRecoveryWithDataLossCommandActor(Reference db, std::vector tokens) { +Future forceRecoveryWithDataLossCommandActor(Reference db, std::vector const& tokens) { if (tokens.size() != 2) { printUsage(tokens[0]); - return false; + co_return false; } - wait(safeThreadFutureToFuture(db->forceRecoveryWithDataLoss(tokens[1]))); - return true; + co_await safeThreadFutureToFuture(db->forceRecoveryWithDataLoss(tokens[1])); + co_return true; } CommandFactory forceRecoveryWithDataLossFactory( diff --git a/fdbcli/GetAuditStatusCommand.actor.cpp b/fdbcli/GetAuditStatusCommand.cpp similarity index 66% rename from fdbcli/GetAuditStatusCommand.actor.cpp rename to fdbcli/GetAuditStatusCommand.cpp index e66c610c3e4..0a9cf1d5983 100644 --- a/fdbcli/GetAuditStatusCommand.actor.cpp +++ b/fdbcli/GetAuditStatusCommand.cpp @@ -1,5 +1,5 @@ /* - * GetAuditStatusCommand.actor.cpp + * GetAuditStatusCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,29 +20,28 @@ #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/Audit.h" -#include "fdbclient/AuditUtils.actor.h" +#include "fdbclient/AuditUtils.h" #include "fdbclient/IClientApi.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace fdb_cli { -ACTOR Future getAuditProgressByRange(Database cx, AuditType auditType, UID auditId, KeyRange auditRange) { - state KeyRange rangeToRead = auditRange; - state Key rangeToReadBegin = auditRange.begin; - state int retryCount = 0; - state int64_t finishCount = 0; +Future getAuditProgressByRange(Database cx, AuditType auditType, UID auditId, KeyRange auditRange) { + KeyRange rangeToRead = auditRange; + Key rangeToReadBegin = auditRange.begin; + int retryCount = 0; + int64_t finishCount = 0; while (rangeToReadBegin < auditRange.end) { - loop { + while (true) { + Error err; try { rangeToRead = KeyRangeRef(rangeToReadBegin, auditRange.end); - state std::vector auditStates = - wait(getAuditStateByRange(cx, auditType, auditId, rangeToRead)); + std::vector auditStates = + co_await getAuditStateByRange(cx, auditType, auditId, rangeToRead); for (int i = 0; i < auditStates.size(); i++) { AuditPhase phase = auditStates[i].getPhase(); if (phase == AuditPhase::Invalid) { @@ -58,99 +57,104 @@ ACTOR Future getAuditProgressByRange(Database cx, AuditType auditType, UID rangeToReadBegin = auditStates.back().range.end; break; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw e; - } - if (retryCount > 30) { - fmt::println("Incomplete check"); - return Void(); - } - wait(delay(0.5)); - retryCount++; + err = e; + } + if (err.code() == error_code_actor_cancelled) { + throw err; + } + if (retryCount > 30) { + fmt::println("Incomplete check"); + co_return; } + co_await delay(0.5); + retryCount++; } } fmt::println("Finished range count: {}", finishCount); - return Void(); } -ACTOR Future> getStorageServers(Database cx) { - state Transaction tr(cx); - loop { +AsyncResult> getStorageServers(Database cx) { + Transaction tr(cx); + while (true) { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; try { - RangeResult serverList = wait(tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult serverList = co_await tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!serverList.more && serverList.size() < CLIENT_KNOBS->TOO_MANY); std::vector servers; servers.reserve(serverList.size()); for (int i = 0; i < serverList.size(); i++) servers.push_back(decodeServerListValue(serverList[i].value)); - return servers; + co_return servers; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future getAuditProgressByServer(Database cx, - AuditType auditType, - UID auditId, - KeyRange auditRange, - UID serverId) { - state KeyRange rangeToRead = auditRange; - state Key rangeToReadBegin = auditRange.begin; - state int retryCount = 0; +Future getAuditProgressByServer(Database cx, + AuditType auditType, + UID auditId, + KeyRange auditRange, + UID serverId) { + KeyRange rangeToRead = auditRange; + Key rangeToReadBegin = auditRange.begin; + int retryCount = 0; while (rangeToReadBegin < auditRange.end) { - loop { + while (true) { + Error err; try { rangeToRead = KeyRangeRef(rangeToReadBegin, auditRange.end); - state std::vector auditStates = - wait(getAuditStateByServer(cx, auditType, auditId, serverId, rangeToRead)); + std::vector auditStates = + co_await getAuditStateByServer(cx, auditType, auditId, serverId, rangeToRead); for (int i = 0; i < auditStates.size(); i++) { AuditPhase phase = auditStates[i].getPhase(); if (phase == AuditPhase::Invalid) { - return AuditPhase::Running; + co_return AuditPhase::Running; } else if (phase == AuditPhase::Error) { - return AuditPhase::Error; + co_return AuditPhase::Error; } } rangeToReadBegin = auditStates.back().range.end; break; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw e; - } - if (retryCount > 30) { - return AuditPhase::Invalid; - } - wait(delay(0.5)); - retryCount++; + err = e; + } + if (err.code() == error_code_actor_cancelled) { + throw err; + } + if (retryCount > 30) { + co_return AuditPhase::Invalid; } + co_await delay(0.5); + retryCount++; } } - return AuditPhase::Complete; + co_return AuditPhase::Complete; } -ACTOR Future getAuditProgress(Database cx, AuditType auditType, UID auditId, KeyRange auditRange) { +Future getAuditProgress(Database cx, AuditType auditType, UID auditId, KeyRange auditRange) { if (auditType == AuditType::ValidateHA || auditType == AuditType::ValidateReplica || auditType == AuditType::ValidateLocationMetadata || auditType == AuditType::ValidateRestore) { - wait(getAuditProgressByRange(cx, auditType, auditId, auditRange)); + co_await getAuditProgressByRange(cx, auditType, auditId, auditRange); } else if (auditType == AuditType::ValidateStorageServerShard) { - state std::vector> fs; - state std::unordered_map res; - state std::vector interfs = wait(getStorageServers(cx)); - state int i = 0; - state int numCompleteServers = 0; - state int numOngoingServers = 0; - state int numErrorServers = 0; - state int numTSSes = 0; + std::vector> fs; + std::unordered_map res; + std::vector interfs = co_await getStorageServers(cx); + int i = 0; + int numCompleteServers = 0; + int numOngoingServers = 0; + int numErrorServers = 0; + int numTSSes = 0; for (; i < interfs.size(); i++) { if (interfs[i].isTss()) { numTSSes++; continue; // SSShard audit does not test TSS } - AuditPhase serverPhase = wait(getAuditProgressByServer(cx, auditType, auditId, allKeys, interfs[i].id())); + AuditPhase serverPhase = + co_await getAuditProgressByServer(cx, auditType, auditId, allKeys, interfs[i].id()); if (serverPhase == AuditPhase::Running) { numOngoingServers++; } else if (serverPhase == AuditPhase::Complete) { @@ -168,13 +172,12 @@ ACTOR Future getAuditProgress(Database cx, AuditType auditType, UID auditI } else { fmt::println("AuditType not implemented"); } - return Void(); } -ACTOR Future getAuditStatusCommandActor(Database cx, std::vector tokens) { +Future getAuditStatusCommandActor(Database cx, std::vector tokens) { if (tokens.size() < 2 || tokens.size() > 5) { printUsage(tokens[0]); - return false; + co_return false; } AuditType type = AuditType::Invalid; @@ -190,26 +193,26 @@ ACTOR Future getAuditStatusCommandActor(Database cx, std::vector getAuditStatusCommandActor(Database cx, std::vector res = wait(getAuditStates(cx, type, /*newFirst=*/true, count)); + std::vector res = co_await getAuditStates(cx, type, /*newFirst=*/true, count); for (const auto& it : res) { fmt::println("Audit result is:\n{}", it.toString()); } @@ -226,22 +229,22 @@ ACTOR Future getAuditStatusCommandActor(Database cx, std::vectorTOO_MANY; if (tokens.size() == 5) { count = std::stoi(tokens[4].toString()); } - std::vector res = wait(getAuditStates(cx, type, /*newFirst=*/true, count, phase)); + std::vector res = co_await getAuditStates(cx, type, /*newFirst=*/true, count, phase); for (const auto& it : res) { fmt::println("Audit result is:\n{}", it.toString()); } } else { printUsage(tokens[0]); - return false; + co_return false; } - return true; + co_return true; } CommandFactory getAuditStatusFactory( diff --git a/fdbcli/HotRangeCommand.actor.cpp b/fdbcli/HotRangeCommand.cpp similarity index 83% rename from fdbcli/HotRangeCommand.actor.cpp rename to fdbcli/HotRangeCommand.cpp index b3d6788fffc..2913ced41e8 100644 --- a/fdbcli/HotRangeCommand.actor.cpp +++ b/fdbcli/HotRangeCommand.cpp @@ -1,5 +1,5 @@ /* - * HotRangeCommand.actor.cpp + * HotRangeCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,7 +20,7 @@ #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -31,8 +31,6 @@ #include "flow/FastRef.h" #include "flow/NetworkAddress.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace { ReadHotSubRangeRequest::SplitType parseSplitType(const std::string& typeStr) { @@ -53,43 +51,43 @@ ReadHotSubRangeRequest::SplitType parseSplitType(const std::string& typeStr) { namespace fdb_cli { -ACTOR Future hotRangeCommandActor(Database localdb, - Reference db, - std::vector tokens, - std::map* storage_interface) { +Future hotRangeCommandActor(Database localdb, + Reference db, + std::vector const& tokens, + std::map* const& storage_interface) { if (tokens.size() == 1) { // initialize storage interfaces storage_interface->clear(); - wait(getStorageServerInterfaces(db, storage_interface)); + co_await getStorageServerInterfaces(db, storage_interface); fmt::print("\nThe following {} storage servers can be queried:\n", storage_interface->size()); for (const auto& [addr, _] : *storage_interface) { fmt::print("{}\n", addr); } fmt::print("\n"); } else if (tokens.size() == 6) { - if (storage_interface->size() == 0) { + if (storage_interface->empty()) { fprintf(stderr, "ERROR: no storage processes to query. You must run the `hotrange’ command first.\n"); - return false; + co_return false; } - state Key address = tokens[1]; + Key address = tokens[1]; // At present we only support one process(IP:Port) at a time if (!storage_interface->count(address.toString())) { fprintf(stderr, "ERROR: storage process `%s' not recognized.\n", printable(address).c_str()); - return false; + co_return false; } - state int splitCount; + int splitCount{ 0 }; try { splitCount = boost::lexical_cast(tokens[5].toString()); } catch (...) { fmt::print("Error: splitCount value: '{}', cannot be parsed to an Integer\n", tokens[5].toString()); - return false; + co_return false; } ReadHotSubRangeRequest::SplitType splitType = parseSplitType(tokens[2].toString()); KeyRangeRef range(tokens[3], tokens[4]); // TODO: refactor this to support multiversion fdbcli in the future - Standalone> metrics = - wait(localdb->getHotRangeMetrics((*storage_interface)[address.toString()], range, splitType, splitCount)); + Standalone> metrics = co_await localdb->getHotRangeMetrics( + (*storage_interface)[address.toString()], range, splitType, splitCount); // next parse the result and form a json array for each object json_spirit::mArray resultArray; for (const auto& metric : metrics) { @@ -107,10 +105,10 @@ ACTOR Future hotRangeCommandActor(Database localdb, fmt::print("\n{}\n", result); } else { printUsage(tokens[0]); - return false; + co_return false; } - return true; + co_return true; } CommandFactory hotRangeFactory( diff --git a/fdbcli/IdempotencyIdsCommand.actor.cpp b/fdbcli/IdempotencyIdsCommand.cpp similarity index 80% rename from fdbcli/IdempotencyIdsCommand.actor.cpp rename to fdbcli/IdempotencyIdsCommand.cpp index 56457027235..1984b769cbc 100644 --- a/fdbcli/IdempotencyIdsCommand.actor.cpp +++ b/fdbcli/IdempotencyIdsCommand.cpp @@ -1,5 +1,5 @@ /* - * IdempotencyIdsCommand.actor.cpp + * IdempotencyIdsCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,12 +18,10 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" -#include "fdbclient/IdempotencyId.actor.h" +#include "fdbcli/fdbcli.h" +#include "fdbclient/IdempotencyId.h" #include "fdbclient/JsonBuilder.h" #include "fdbclient/json_spirit/json_spirit_reader_template.h" -#include "flow/actorcompiler.h" // This must be the last include - namespace { Optional parseAgeValue(StringRef token) { @@ -38,36 +36,36 @@ Optional parseAgeValue(StringRef token) { namespace fdb_cli { -ACTOR Future idempotencyIdsCommandActor(Database db, std::vector tokens) { +Future idempotencyIdsCommandActor(Database db, std::vector const& tokens) { if (tokens.size() < 2 || tokens.size() > 3) { printUsage(tokens[0]); - return false; + co_return false; } else { auto const action = tokens[1]; if (action == "status"_sr) { if (tokens.size() != 2) { printUsage(tokens[0]); - return false; + co_return false; } - JsonBuilderObject status = wait(getIdmpKeyStatus(db)); + JsonBuilderObject status = co_await getIdmpKeyStatus(db); fmt::print("{}\n", status.getJson()); - return true; + co_return true; } else if (action == "clear"_sr) { if (tokens.size() != 3) { printUsage(tokens[0]); - return false; + co_return false; } auto const age = parseAgeValue(tokens[2]); if (!age.present()) { printUsage(tokens[0]); - return false; + co_return false; } - wait(cleanIdempotencyIds(db, age.get())); + co_await cleanIdempotencyIds(db, age.get()); fmt::print("Successfully cleared idempotency IDs.\n"); - return true; + co_return true; } else { printUsage(tokens[0]); - return false; + co_return false; } } } diff --git a/fdbcli/IncludeCommand.actor.cpp b/fdbcli/IncludeCommand.cpp similarity index 72% rename from fdbcli/IncludeCommand.actor.cpp rename to fdbcli/IncludeCommand.cpp index 40a42e435e6..2691915bc45 100644 --- a/fdbcli/IncludeCommand.actor.cpp +++ b/fdbcli/IncludeCommand.cpp @@ -1,5 +1,5 @@ /* - * IncludeCommand.actor.cpp + * IncludeCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -27,22 +27,21 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { // Remove the given localities from the exclusion list. // include localities by clearing the keys. -ACTOR Future includeLocalities(Reference db, - std::vector localities, - bool failed, - bool includeAll) { - state std::string versionKey = deterministicRandom()->randomUniqueID().toString(); - state Reference tr = db->createTransaction(); - loop { +Future includeLocalities(Reference db, + std::vector localities, + bool failed, + bool includeAll) { + std::string versionKey = deterministicRandom()->randomUniqueID().toString(); + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { - if (includeAll) { if (failed) { tr->clear(fdb_cli::failedLocalitySpecialKeyRange); @@ -56,20 +55,22 @@ ACTOR Future includeLocalities(Reference db, tr->clear(locality); } } - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent("IncludeLocalitiesError").errorUnsuppressed(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future includeServers(Reference db, std::vector servers, bool failed) { - state std::string versionKey = deterministicRandom()->randomUniqueID().toString(); - state Reference tr = db->createTransaction(); - loop { +Future includeServers(Reference db, std::vector servers, bool failed) { + std::string versionKey = deterministicRandom()->randomUniqueID().toString(); + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { for (auto& s : servers) { // include all, just clear the whole key range @@ -95,21 +96,22 @@ ACTOR Future includeServers(Reference db, std::vectorclear(KeyRangeRef(addr.withSuffix(":"_sr), addr.withSuffix(";"_sr))); } } - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - TraceEvent("IncludeServersError").errorUnsuppressed(e); - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + TraceEvent("IncludeServersError").errorUnsuppressed(err); + co_await safeThreadFutureToFuture(tr->onError(err)); } } // Includes the servers that could be IP addresses or localities back to the cluster. -ACTOR Future include(Reference db, std::vector tokens) { - state std::vector addresses; - state std::vector localities; - state bool failed = false; - state bool all = false; +Future include(Reference db, std::vector tokens) { + std::vector addresses; + std::vector localities; + bool failed = false; + bool all = false; for (auto t = tokens.begin() + 1; t != tokens.end(); ++t) { if (*t == "all"_sr) { all = true; @@ -126,7 +128,7 @@ ACTOR Future include(Reference db, std::vector token t->toString().c_str()); if (t->toString().find(":tls") != std::string::npos) printf(" Do not include the `:tls' suffix when naming a process\n"); - return false; + co_return false; } addresses.push_back(a); } @@ -134,31 +136,31 @@ ACTOR Future include(Reference db, std::vector token if (all) { std::vector includeAll; includeAll.push_back(AddressExclusion()); - wait(includeServers(db, includeAll, failed)); - wait(includeLocalities(db, localities, failed, all)); + co_await includeServers(db, includeAll, failed); + co_await includeLocalities(db, localities, failed, all); } else { if (!addresses.empty()) { - wait(includeServers(db, addresses, failed)); + co_await includeServers(db, addresses, failed); } if (!localities.empty()) { // include the servers that belong to given localities. - wait(includeLocalities(db, localities, failed, all)); + co_await includeLocalities(db, localities, failed, all); } } - return true; + co_return true; }; } // namespace namespace fdb_cli { -ACTOR Future includeCommandActor(Reference db, std::vector tokens) { +Future includeCommandActor(Reference db, std::vector tokens) { if (tokens.size() < 2) { printUsage(tokens[0]); - return false; + co_return false; } else { - bool result = wait(include(db, tokens)); - return result; + bool result = co_await include(db, tokens); + co_return result; } } diff --git a/fdbcli/KillCommand.actor.cpp b/fdbcli/KillCommand.cpp similarity index 80% rename from fdbcli/KillCommand.actor.cpp rename to fdbcli/KillCommand.cpp index c520cd15c34..baa12ec976c 100644 --- a/fdbcli/KillCommand.actor.cpp +++ b/fdbcli/KillCommand.cpp @@ -1,5 +1,5 @@ /* - * KillCommand.actor.cpp + * KillCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,7 +20,7 @@ #include "boost/algorithm/string.hpp" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -30,36 +30,35 @@ #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" #include "flow/CodeProbe.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace fdb_cli { -ACTOR Future killCommandActor(Reference db, - Reference tr, - std::vector tokens, - std::map>* address_interface) { - ASSERT(tokens.size() >= 1); - state bool result = true; - state std::string addressesStr; +Future killCommandActor(Reference db, + Reference tr, + std::vector tokens, + std::map>* address_interface) { + ASSERT(!tokens.empty()); + bool result = true; + std::string addressesStr; if (tokens.size() == 1) { // initialize worker interfaces address_interface->clear(); - wait(getWorkerInterfaces(tr, address_interface, true)); + co_await getWorkerInterfaces(tr, address_interface, true); } if (tokens.size() == 1 || tokencmp(tokens[1], "list")) { - if (address_interface->size() == 0) { + if (address_interface->empty()) { printf("\nNo addresses can be killed.\n"); } else if (address_interface->size() == 1) { printf("\nThe following address can be killed:\n"); } else { printf("\nThe following %zu addresses can be killed:\n", address_interface->size()); } - for (auto it : *address_interface) { + for (const auto& it : *address_interface) { printf("%s\n", printable(it.first).c_str()); } printf("\n"); } else if (tokencmp(tokens[1], "all")) { - if (address_interface->size() == 0) { + if (address_interface->empty()) { result = false; fprintf(stderr, "ERROR: no processes to kill. You must run the `kill’ command before " @@ -71,7 +70,7 @@ ACTOR Future killCommandActor(Reference db, } addressesStr = boost::algorithm::join(addressesVec, ","); // make sure we only call the interface once to send requests in parallel - int64_t killRequestsSent = wait(safeThreadFutureToFuture(db->rebootWorker(addressesStr, false, 0))); + int64_t killRequestsSent = co_await safeThreadFutureToFuture(db->rebootWorker(addressesStr, false, 0)); if (!killRequestsSent) { result = false; fprintf(stderr, @@ -82,7 +81,7 @@ ACTOR Future killCommandActor(Reference db, } } } else { - state int i; + int i{ 0 }; for (i = 1; i < tokens.size(); i++) { if (!address_interface->count(tokens[i])) { fprintf(stderr, "ERROR: process `%s' not recognized.\n", printable(tokens[i]).c_str()); @@ -97,7 +96,7 @@ ACTOR Future killCommandActor(Reference db, addressesVec.push_back(tokens[i].toString()); } addressesStr = boost::algorithm::join(addressesVec, ","); - int64_t killRequestsSent = wait(safeThreadFutureToFuture(db->rebootWorker(addressesStr, false, 0))); + int64_t killRequestsSent = co_await safeThreadFutureToFuture(db->rebootWorker(addressesStr, false, 0)); if (!killRequestsSent) { result = false; fprintf(stderr, @@ -106,12 +105,12 @@ ACTOR Future killCommandActor(Reference db, addressesStr.c_str()); } else { // delay in case the network queue is not flush before the client exits - wait(delay(3.0)); + co_await delay(3.0); printf("Attempted to kill %zu processes\n", tokens.size() - 1); } } } - return result; + co_return result; } void killGenerator(const char* text, diff --git a/fdbcli/LocationMetadataCommand.actor.cpp b/fdbcli/LocationMetadataCommand.cpp similarity index 63% rename from fdbcli/LocationMetadataCommand.actor.cpp rename to fdbcli/LocationMetadataCommand.cpp index c1923242b82..94dfae46a1f 100644 --- a/fdbcli/LocationMetadataCommand.actor.cpp +++ b/fdbcli/LocationMetadataCommand.cpp @@ -1,5 +1,5 @@ /* - * LocationMetadataCommand.actor.cpp + * LocationMetadataCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,23 +18,21 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/Audit.h" -#include "fdbclient/AuditUtils.actor.h" +#include "fdbclient/AuditUtils.h" #include "fdbclient/IClientApi.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace { -ACTOR Future describeServers(Reference tr, std::vector ids) { +Future describeServers(Reference tr, std::vector ids) { std::vector>> serverListEntries; for (const UID& id : ids) { serverListEntries.push_back(tr->get(serverListKeyFor(id))); } - std::vector> serverListValues = wait(getAll(serverListEntries)); + std::vector> serverListValues = co_await getAll(serverListEntries); std::string res; for (auto& v : serverListValues) { StorageServerInterface ssi = decodeServerListValue(v.get()); @@ -44,50 +42,50 @@ ACTOR Future describeServers(Reference t res += fmt::format("ServerID: {}, Addr: {}", ssi.uniqueID.toString(), ssi.stableAddress().toString()); } - return res; + co_return res; } -ACTOR Future printKeyServersEntry(Reference tr, - RangeResultRef UIDtoTagMap, - Value entry, - KeyRangeRef range) { - state std::vector src; - state std::vector dest; - state UID srcId; - state UID destId; +Future printKeyServersEntry(Reference tr, + RangeResultRef UIDtoTagMap, + Value entry, + KeyRangeRef range) { + std::vector src; + std::vector dest; + UID srcId; + UID destId; decodeKeyServersValue(UIDtoTagMap, entry, src, dest, srcId, destId); - state std::string srcDesc = wait(describeServers(tr, src)); - std::string destDesc = wait(describeServers(tr, dest)); + std::string srcDesc = co_await describeServers(tr, src); + std::string destDesc = co_await describeServers(tr, dest); printf("Range: %s, ShardID: %s, Src Servers: %s, Dest Servers: %s\n", Traceable::toString(range).c_str(), srcId.toString().c_str(), srcDesc.c_str(), destDesc.c_str()); - return Void(); } -ACTOR Future printRandomShards(Database cx, int n, bool physicalShard) { - state Key begin = allKeys.begin; - state int numShards = 0; +Future printRandomShards(Database cx, int n, bool physicalShard) { + Key begin = allKeys.begin; + int numShards = 0; while (begin < allKeys.end && numShards < n) { // RYW to optimize re-reading the same key ranges - state Reference tr = makeReference(cx); + auto tr = makeReference(cx); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state RangeResult UIDtoTagMap = wait(tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult UIDtoTagMap = co_await tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!UIDtoTagMap.more && UIDtoTagMap.size() < CLIENT_KNOBS->TOO_MANY); - state KeyRangeRef currentKeys(begin, allKeys.end); - state RangeResult shards = - wait(krmGetRanges(tr, keyServersPrefix, currentKeys, n, CLIENT_KNOBS->TOO_MANY)); + KeyRangeRef currentKeys(begin, allKeys.end); + RangeResult shards = + co_await krmGetRanges(tr, keyServersPrefix, currentKeys, n, CLIENT_KNOBS->TOO_MANY); - state int i = 0; + int i = 0; for (; i < shards.size() - 1 && numShards < n; ++i) { KeyRangeRef currentRange(shards[i].key, shards[i + 1].key); std::vector src; @@ -96,7 +94,7 @@ ACTOR Future printRandomShards(Database cx, int n, bool physicalShard) { UID destId; decodeKeyServersValue(UIDtoTagMap, shards[i].value, src, dest, srcId, destId); if (physicalShard == (srcId != anonymousShardId)) { - wait(printKeyServersEntry(tr, UIDtoTagMap, shards[i].value, currentRange)); + co_await printKeyServersEntry(tr, UIDtoTagMap, shards[i].value, currentRange); ++numShards; } } @@ -104,37 +102,37 @@ ACTOR Future printRandomShards(Database cx, int n, bool physicalShard) { begin = shards.back().key; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } printf("Found %d %s shards\n", numShards, physicalShard ? "Physical" : "Non-physical"); - - return Void(); } -ACTOR Future printPhysicalShardCount(Database cx) { - state Key begin = allKeys.begin; - state int numShards = 0; - state int numPhysicalShards = 0; +Future printPhysicalShardCount(Database cx) { + Key begin = allKeys.begin; + int numShards = 0; + int numPhysicalShards = 0; while (begin < allKeys.end) { // RYW to optimize re-reading the same key ranges - state Reference tr = makeReference(cx); + auto tr = makeReference(cx); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state RangeResult UIDtoTagMap = wait(tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult UIDtoTagMap = co_await tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!UIDtoTagMap.more && UIDtoTagMap.size() < CLIENT_KNOBS->TOO_MANY); KeyRangeRef currentKeys(begin, allKeys.end); - RangeResult shards = wait( - krmGetRanges(tr, keyServersPrefix, currentKeys, CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->TOO_MANY)); + RangeResult shards = co_await krmGetRanges( + tr, keyServersPrefix, currentKeys, CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->TOO_MANY); for (int i = 0; i < shards.size() - 1; ++i) { std::vector src; @@ -151,31 +149,31 @@ ACTOR Future printPhysicalShardCount(Database cx) { numShards += shards.size() - 1; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } printf("Total number of shards: %d, number of physical shards: %d\n", numShards, numPhysicalShards); - - return Void(); } -ACTOR Future printServerShards(Database cx, UID serverId) { - state Key begin = allKeys.begin; +Future printServerShards(Database cx, UID serverId) { + Key begin = allKeys.begin; while (begin < allKeys.end) { // RYW to optimize re-reading the same key ranges - state Reference tr = makeReference(cx); + auto tr = makeReference(cx); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); RangeResult serverShards = - wait(krmGetRanges(tr, serverKeysPrefixFor(serverId), KeyRangeRef(begin, allKeys.end))); + co_await krmGetRanges(tr, serverKeysPrefixFor(serverId), KeyRangeRef(begin, allKeys.end)); for (int i = 0; i < serverShards.size() - 1; ++i) { KeyRangeRef currentRange(serverShards[i].key, serverShards[i + 1].key); @@ -194,92 +192,91 @@ ACTOR Future printServerShards(Database cx, UID serverId) { begin = serverShards.back().key; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - - return Void(); } -ACTOR Future resolveRange(Database cx, KeyRange range) { - state Key begin = range.begin; +Future resolveRange(Database cx, KeyRange range) { + Key begin = range.begin; while (begin < range.end) { // RYW to optimize re-reading the same key ranges - state Reference tr = makeReference(cx); + auto tr = makeReference(cx); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state RangeResult UIDtoTagMap = wait(tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult UIDtoTagMap = co_await tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!UIDtoTagMap.more && UIDtoTagMap.size() < CLIENT_KNOBS->TOO_MANY); - state KeyRangeRef currentKeys(begin, range.end); - state RangeResult shards = wait( - krmGetRanges(tr, keyServersPrefix, currentKeys, CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->TOO_MANY)); + KeyRangeRef currentKeys(begin, range.end); + RangeResult shards = co_await krmGetRanges( + tr, keyServersPrefix, currentKeys, CLIENT_KNOBS->TOO_MANY, CLIENT_KNOBS->TOO_MANY); - state int i = 0; + int i = 0; for (; i < shards.size() - 1; ++i) { - state KeyRangeRef currentRange(shards[i].key, shards[i + 1].key); - wait(printKeyServersEntry(tr, UIDtoTagMap, shards[i].value, currentRange)); + KeyRangeRef currentRange(shards[i].key, shards[i + 1].key); + co_await printKeyServersEntry(tr, UIDtoTagMap, shards[i].value, currentRange); } begin = shards.back().key; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - - return Void(); } } // namespace namespace fdb_cli { -ACTOR Future locationMetadataCommandActor(Database cx, std::vector tokens) { +Future locationMetadataCommandActor(Database cx, std::vector tokens) { if (tokens.size() < 2 || tokens.size() > 4) { printUsage(tokens[0]); - return false; + co_return false; } if (tokens.size() == 2) { if (!tokencmp(tokens[1], "physicalshards")) { printUsage(tokens[0]); - return false; + co_return false; } - wait(printPhysicalShardCount(cx)); + co_await printPhysicalShardCount(cx); } else if (tokencmp(tokens[1], "resolve")) { if (tokens.size() == 3) { - wait(resolveRange(cx, singleKeyRange(tokens[2]))); + co_await resolveRange(cx, singleKeyRange(tokens[2])); } else { - wait(resolveRange(cx, KeyRangeRef(tokens[2], tokens[3]))); + co_await resolveRange(cx, KeyRangeRef(tokens[2], tokens[3])); } } else if (tokencmp(tokens[1], "servershards")) { if (tokens.size() != 3) { printUsage(tokens[0]); - return false; + co_return false; } - wait(printServerShards(cx, UID::fromString(tokens[2].toString()))); + co_await printServerShards(cx, UID::fromString(tokens[2].toString())); } else if (tokencmp(tokens[1], "listshards")) { if (tokens.size() == 4 && !tokencmp(tokens[3], "physical")) { printUsage(tokens[0]); - return false; + co_return false; } const bool physical = tokens.size() == 4; - wait(printRandomShards(cx, std::stoi(tokens[2].toString()), physical)); + co_await printRandomShards(cx, std::stoi(tokens[2].toString()), physical); } else { printUsage(tokens[0]); - return false; + co_return false; } - return true; + co_return true; } CommandFactory locationMetadataFactory( diff --git a/fdbcli/LockCommand.actor.cpp b/fdbcli/LockCommand.cpp similarity index 59% rename from fdbcli/LockCommand.actor.cpp rename to fdbcli/LockCommand.cpp index 1ed959ae183..5570e7be763 100644 --- a/fdbcli/LockCommand.actor.cpp +++ b/fdbcli/LockCommand.cpp @@ -1,5 +1,5 @@ /* - * LockCommand.actor.cpp + * LockCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -28,30 +28,29 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace { -ACTOR Future lockDatabase(Reference db, UID id) { - state Reference tr = db->createTransaction(); - loop { +Future lockDatabase(Reference db, UID id) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { tr->set(fdb_cli::lockSpecialKey, id.toString()); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); printf("Database locked.\n"); - return true; + co_return true; } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_database_locked) - throw e; - else if (e.code() == error_code_special_keys_api_failure) { - std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr)); - fprintf(stderr, "%s\n", errorMsgStr.c_str()); - return false; - } - wait(safeThreadFutureToFuture(tr->onError(err))); + err = e; + } + if (err.code() == error_code_database_locked) + throw err; + if (err.code() == error_code_special_keys_api_failure) { + std::string errorMsgStr = co_await fdb_cli::getSpecialKeysFailureErrorMessage(tr); + fprintf(stderr, "%s\n", errorMsgStr.c_str()); + co_return false; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -61,47 +60,48 @@ namespace fdb_cli { const KeyRef lockSpecialKey = "\xff\xff/management/db_locked"_sr; -ACTOR Future lockCommandActor(Reference db, std::vector tokens) { +Future lockCommandActor(Reference db, std::vector const& tokens) { if (tokens.size() != 1) { printUsage(tokens[0]); - return false; + co_return false; } else { - state UID lockUID = deterministicRandom()->randomUniqueID(); + UID lockUID = deterministicRandom()->randomUniqueID(); printf("Locking database with lockUID: %s\n", lockUID.toString().c_str()); - bool result = wait((lockDatabase(db, lockUID))); - return result; + bool result = co_await lockDatabase(db, lockUID); + co_return result; } } -ACTOR Future unlockDatabaseActor(Reference db, UID uid) { - state Reference tr = db->createTransaction(); - loop { +Future unlockDatabaseActor(Reference db, UID uid) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { - state ThreadFuture> valF = tr->get(fdb_cli::lockSpecialKey); - Optional val = wait(safeThreadFutureToFuture(valF)); + ThreadFuture> valF = tr->get(fdb_cli::lockSpecialKey); + Optional val = co_await safeThreadFutureToFuture(valF); if (!val.present()) - return true; + co_return true; if (val.present() && UID::fromString(val.get().toString()) != uid) { printf("Unable to unlock database. Make sure to unlock with the correct lock UID.\n"); - return false; + co_return false; } tr->clear(fdb_cli::lockSpecialKey); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); printf("Database unlocked.\n"); - return true; + co_return true; } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_special_keys_api_failure) { - std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr)); - fprintf(stderr, "%s\n", errorMsgStr.c_str()); - return false; - } - wait(safeThreadFutureToFuture(tr->onError(err))); + err = e; + } + if (err.code() == error_code_special_keys_api_failure) { + std::string errorMsgStr = co_await fdb_cli::getSpecialKeysFailureErrorMessage(tr); + fprintf(stderr, "%s\n", errorMsgStr.c_str()); + co_return false; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } diff --git a/fdbcli/MaintenanceCommand.actor.cpp b/fdbcli/MaintenanceCommand.cpp similarity index 76% rename from fdbcli/MaintenanceCommand.actor.cpp rename to fdbcli/MaintenanceCommand.cpp index ca4da82a803..bcd7cf99e93 100644 --- a/fdbcli/MaintenanceCommand.actor.cpp +++ b/fdbcli/MaintenanceCommand.cpp @@ -1,5 +1,5 @@ /* - * MaintenanceCommand.actor.cpp + * MaintenanceCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -23,7 +23,7 @@ #include "boost/lexical_cast.hpp" #include "fmt/format.h" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/IClientApi.h" @@ -32,36 +32,37 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { // print zoneId under maintenance, only one is possible at the same time -ACTOR Future printHealthyZone(Reference db) { - state Reference tr = db->createTransaction(); - loop { +Future printHealthyZone(Reference db) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { // We need to keep the future as the returned standalone is not guaranteed to manage its memory when // using an external client, but the ThreadFuture holds a reference to the memory - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::maintenanceSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - RangeResult res = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult res = co_await safeThreadFutureToFuture(resultFuture); ASSERT(res.size() <= 1); if (res.size() == 1 && res[0].key == fdb_cli::ignoreSSFailureSpecialKey) { printf("Data distribution has been disabled for all storage server failures in this cluster and thus " "maintenance mode is not active.\n"); - } else if (!res.size() || boost::lexical_cast(res[0].value.toString()) <= 0) { + } else if (res.empty() || boost::lexical_cast(res[0].value.toString()) <= 0) { printf("No ongoing maintenance.\n"); } else { std::string zoneId = res[0].key.removePrefix(fdb_cli::maintenanceSpecialKeyRange.begin).toString(); int64_t seconds = static_cast(boost::lexical_cast(res[0].value.toString())); fmt::print("Maintenance for zone {0} will continue for {1} seconds.\n", zoneId, seconds); } - return Void(); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -75,16 +76,17 @@ const KeyRangeRef maintenanceSpecialKeyRange = const KeyRef ignoreSSFailureSpecialKey = "\xff\xff/management/maintenance/IgnoreSSFailures"_sr; // add a zone to maintenance and specify the maintenance duration -ACTOR Future setHealthyZone(Reference db, StringRef zoneId, double seconds, bool printWarning) { - state Reference tr = db->createTransaction(); +Future setHealthyZone(Reference db, StringRef zoneId, double seconds, bool printWarning) { + Reference tr = db->createTransaction(); TraceEvent("SetHealthyZone").detail("Zone", zoneId).detail("DurationSeconds", seconds); - loop { + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { // hold the returned standalone object's memory - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::maintenanceSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - RangeResult res = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult res = co_await safeThreadFutureToFuture(resultFuture); ASSERT(res.size() <= 1); if (res.size() == 1 && res[0].key == fdb_cli::ignoreSSFailureSpecialKey) { if (printWarning) { @@ -92,29 +94,31 @@ ACTOR Future setHealthyZone(Reference db, StringRef zoneId, dou "ERROR: Maintenance mode cannot be used while data distribution is disabled for storage " "server failures. Use 'datadistribution on' to reenable data distribution.\n"); } - return false; + co_return false; } tr->set(fdb_cli::maintenanceSpecialKeyRange.begin.withSuffix(zoneId), boost::lexical_cast(seconds)); - wait(safeThreadFutureToFuture(tr->commit())); - return true; + co_await safeThreadFutureToFuture(tr->commit()); + co_return true; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } // clear ongoing maintenance, let clearSSFailureZoneString = true to enable data distribution for storage -ACTOR Future clearHealthyZone(Reference db, bool printWarning, bool clearSSFailureZoneString) { - state Reference tr = db->createTransaction(); +Future clearHealthyZone(Reference db, bool printWarning, bool clearSSFailureZoneString) { + Reference tr = db->createTransaction(); TraceEvent("ClearHealthyZone").detail("ClearSSFailureZoneString", clearSSFailureZoneString); - loop { + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { // hold the returned standalone object's memory - state ThreadFuture resultFuture = + ThreadFuture resultFuture = tr->getRange(fdb_cli::maintenanceSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - RangeResult res = wait(safeThreadFutureToFuture(resultFuture)); + RangeResult res = co_await safeThreadFutureToFuture(resultFuture); ASSERT(res.size() <= 1); if (!clearSSFailureZoneString && res.size() == 1 && res[0].key == fdb_cli::ignoreSSFailureSpecialKey) { if (printWarning) { @@ -122,24 +126,25 @@ ACTOR Future clearHealthyZone(Reference db, bool printWarning, "ERROR: Maintenance mode cannot be used while data distribution is disabled for storage " "server failures. Use 'datadistribution on' to reenable data distribution.\n"); } - return false; + co_return false; } tr->clear(fdb_cli::maintenanceSpecialKeyRange); - wait(safeThreadFutureToFuture(tr->commit())); - return true; + co_await safeThreadFutureToFuture(tr->commit()); + co_return true; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR Future maintenanceCommandActor(Reference db, std::vector tokens) { - state bool result = true; +Future maintenanceCommandActor(Reference db, std::vector tokens) { + bool result = true; if (tokens.size() == 1) { - wait(printHealthyZone(db)); + co_await printHealthyZone(db); } else if (tokens.size() == 2 && tokencmp(tokens[1], "off")) { - bool clearResult = wait(clearHealthyZone(db, true)); + bool clearResult = co_await clearHealthyZone(db, true); result = clearResult; } else if (tokens.size() == 4 && tokencmp(tokens[1], "on")) { double seconds; @@ -149,14 +154,14 @@ ACTOR Future maintenanceCommandActor(Reference db, std::vector< printUsage(tokens[0]); result = false; } else { - bool setResult = wait(setHealthyZone(db, tokens[2], seconds, true)); + bool setResult = co_await setHealthyZone(db, tokens[2], seconds, true); result = setResult; } } else { printUsage(tokens[0]); result = false; } - return result; + co_return result; } CommandFactory maintenanceFactory( diff --git a/fdbcli/ProfileCommand.actor.cpp b/fdbcli/ProfileCommand.cpp similarity index 85% rename from fdbcli/ProfileCommand.actor.cpp rename to fdbcli/ProfileCommand.cpp index a997e7f1a39..a08392701ad 100644 --- a/fdbcli/ProfileCommand.actor.cpp +++ b/fdbcli/ProfileCommand.cpp @@ -1,5 +1,5 @@ /* - * ProfileCommand.actor.cpp + * ProfileCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,9 +20,9 @@ #include "boost/lexical_cast.hpp" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" -#include "fdbclient/GlobalConfig.actor.h" +#include "fdbclient/GlobalConfig.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" #include "fdbclient/Knobs.h" @@ -31,28 +31,24 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace fdb_cli { -ACTOR Future profileCommandActor(Database db, - Reference tr, - std::vector tokens, - bool intrans) { - state bool result = true; +Future profileCommandActor(Database db, Reference tr, std::vector tokens, bool intrans) { + bool result = true; if (tokens.size() == 1) { printUsage(tokens[0]); result = false; } else if (tokencmp(tokens[1], "client")) { if (tokens.size() == 2) { fprintf(stderr, "ERROR: Usage: profile client \n"); - return false; + co_return false; } - wait(db->globalConfig->onInitialized()); + co_await db->globalConfig->onInitialized(); if (tokencmp(tokens[2], "get")) { if (tokens.size() != 3) { fprintf(stderr, "ERROR: Additional arguments to `get` are not supported.\n"); - return false; + co_return false; } std::string sampleRateStr = "default"; std::string sizeLimitStr = "default"; @@ -71,7 +67,7 @@ ACTOR Future profileCommandActor(Database db, } else if (tokencmp(tokens[2], "set")) { if (tokens.size() != 5) { fprintf(stderr, "ERROR: Usage: profile client set \n"); - return false; + co_return false; } double sampleRate; if (tokencmp(tokens[3], "default")) { @@ -81,7 +77,7 @@ ACTOR Future profileCommandActor(Database db, sampleRate = std::strtod((const char*)tokens[3].begin(), &end); if (!std::isspace(*end)) { fprintf(stderr, "ERROR: %s failed to parse.\n", printable(tokens[3]).c_str()); - return false; + co_return false; } } int64_t sizeLimit; @@ -93,7 +89,7 @@ ACTOR Future profileCommandActor(Database db, sizeLimit = parsed.get(); } else { fprintf(stderr, "ERROR: `%s` failed to parse.\n", printable(tokens[4]).c_str()); - return false; + co_return false; } } @@ -103,7 +99,7 @@ ACTOR Future profileCommandActor(Database db, tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSampleRate), rate.pack()); tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSizeLimit), size.pack()); if (!intrans) { - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); } } else { fprintf(stderr, "ERROR: Unknown action: %s\n", printable(tokens[2]).c_str()); @@ -112,12 +108,12 @@ ACTOR Future profileCommandActor(Database db, } else if (tokencmp(tokens[1], "list")) { if (tokens.size() != 2) { fprintf(stderr, "ERROR: Usage: profile list\n"); - return false; + co_return false; } // Hold the reference to the standalone's memory - state ThreadFuture kvsFuture = tr->getRange( + ThreadFuture kvsFuture = tr->getRange( KeyRangeRef("\xff\xff/worker_interfaces/"_sr, "\xff\xff/worker_interfaces0"_sr), CLIENT_KNOBS->TOO_MANY); - RangeResult kvs = wait(safeThreadFutureToFuture(kvsFuture)); + RangeResult kvs = co_await safeThreadFutureToFuture(kvsFuture); ASSERT(!kvs.more); for (const auto& pair : kvs) { auto ip_port = (pair.key.endsWith(":tls"_sr) ? pair.key.removeSuffix(":tls"_sr) : pair.key) @@ -128,7 +124,7 @@ ACTOR Future profileCommandActor(Database db, fprintf(stderr, "ERROR: Unknown type: %s\n", printable(tokens[1]).c_str()); result = false; } - return result; + co_return result; } CommandFactory profileFactory("profile", diff --git a/fdbcli/RangeConfigCommand.actor.cpp b/fdbcli/RangeConfigCommand.cpp similarity index 78% rename from fdbcli/RangeConfigCommand.actor.cpp rename to fdbcli/RangeConfigCommand.cpp index bf83ddecfa8..f8b4e0e3d93 100644 --- a/fdbcli/RangeConfigCommand.actor.cpp +++ b/fdbcli/RangeConfigCommand.cpp @@ -1,5 +1,5 @@ /* - * RangeConfigCommand.actor.cpp + * RangeConfigCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -23,16 +23,14 @@ #include #include -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/NativeAPI.actor.h" -#include "fdbclient/DataDistributionConfig.actor.h" - -#include "flow/actorcompiler.h" // This must be the last #include. +#include "fdbclient/DataDistributionConfig.h" namespace fdb_cli { -ACTOR Future rangeConfigCommandActor(Database cx, std::vector tokens) { - state std::function fail = [&](std::string msg) { +Future rangeConfigCommandActor(Database cx, std::vector tokens) { + std::function fail = [&](std::string msg) { if (!msg.empty()) { fmt::print(stderr, "ERROR: {}\n", msg); } @@ -40,60 +38,60 @@ ACTOR Future rangeConfigCommandActor(Database cx, std::vector t return false; }; - state std::list args(tokens.begin() + 1, tokens.end()); - state std::function nextArg = [&]() { + std::list args(tokens.begin() + 1, tokens.end()); + std::function nextArg = [&]() { ASSERT(!args.empty()); auto s = args.front(); args.pop_front(); return s; }; - state std::function nextArgInt = [&]() { + std::function nextArgInt = [&]() { if (args.empty()) { throw boost::bad_lexical_cast(); } return boost::lexical_cast(nextArg().toString()); }; - if (args.size() < 1) { - return fail("No subcommand given."); + if (args.empty()) { + co_return fail("No subcommand given."); } - state StringRef cmd = nextArg(); + StringRef cmd = nextArg(); if (cmd == "show"_sr) { - state bool includeDefault = false; + bool includeDefault = false; while (!args.empty()) { auto arg = nextArg(); if (arg == "includeDefault"_sr) { includeDefault = true; } else { - return fail(fmt::format("Unknown argument: '{}'", arg.printable())); + co_return fail(fmt::format("Unknown argument: '{}'", arg.printable())); } } Reference config = - wait(DDConfiguration().userRangeConfig().getSnapshot( - SystemDBWriteLockedNow(cx.getReference()), allKeys.begin, allKeys.end)); + co_await DDConfiguration().userRangeConfig().getSnapshot( + SystemDBWriteLockedNow(cx.getReference()), allKeys.begin, allKeys.end); fmt::print( "{}\n", json_spirit::write_string(DDConfiguration::toJSON(*config, includeDefault), json_spirit::pretty_print)); } else if (cmd == "update"_sr || cmd == "set"_sr) { if (args.size() < 3) { - return fail("Begin, end, and at least one configuration option are required."); + co_return fail("Begin, end, and at least one configuration option are required."); } - state KeyRef begin = nextArg(); - state KeyRef end = nextArg(); + KeyRef begin = nextArg(); + KeyRef end = nextArg(); if (end <= begin) { - return fail("Range end must be > range begin."); + co_return fail("Range end must be > range begin."); } - state DDRangeConfig rangeConfig; + DDRangeConfig rangeConfig; while (!args.empty()) { - state StringRef option = nextArg(); + StringRef option = nextArg(); try { if (option == "replication"_sr) { @@ -103,21 +101,21 @@ ACTOR Future rangeConfigCommandActor(Database cx, std::vector t } else if (option == "default"_sr) { rangeConfig = DDRangeConfig(); } else { - return fail(fmt::format("Unknown range option: '{}'", option.printable())); + co_return fail(fmt::format("Unknown range option: '{}'", option.printable())); } } catch (...) { - return fail( + co_return fail( fmt::format("Required argument for range option '{}' missing or invalid.", option.toString())); } - wait(DDConfiguration().userRangeConfig().updateRange( - SystemDBWriteLockedNow(cx.getReference()), begin, end, rangeConfig, cmd == "set"_sr)); + co_await DDConfiguration().userRangeConfig().updateRange( + SystemDBWriteLockedNow(cx.getReference()), begin, end, rangeConfig, cmd == "set"_sr); } } else { - return fail(fmt::format("Unknown command: '{}'", cmd.printable())); + co_return fail(fmt::format("Unknown command: '{}'", cmd.printable())); } - return true; + co_return true; } std::vector rangeConfigGenerator(std::vector const& tokens, bool inArgument) { diff --git a/fdbcli/SetClassCommand.actor.cpp b/fdbcli/SetClassCommand.cpp similarity index 70% rename from fdbcli/SetClassCommand.actor.cpp rename to fdbcli/SetClassCommand.cpp index 80f8e4a2333..d5155d3ab75 100644 --- a/fdbcli/SetClassCommand.actor.cpp +++ b/fdbcli/SetClassCommand.cpp @@ -1,5 +1,5 @@ /* - * SetClassCommand.actor.cpp + * SetClassCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -20,7 +20,7 @@ #include "fmt/format.h" -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/IClientApi.h" @@ -29,26 +29,26 @@ #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { -ACTOR Future printProcessClass(Reference db) { - state Reference tr = db->createTransaction(); - loop { +Future printProcessClass(Reference db) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { // Hold the reference to the memory - state ThreadFuture classTypeFuture = + ThreadFuture classTypeFuture = tr->getRange(fdb_cli::processClassTypeSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - state ThreadFuture classSourceFuture = + ThreadFuture classSourceFuture = tr->getRange(fdb_cli::processClassSourceSpecialKeyRange, CLIENT_KNOBS->TOO_MANY); - wait(success(safeThreadFutureToFuture(classSourceFuture)) && - success(safeThreadFutureToFuture(classTypeFuture))); + co_await (success(safeThreadFutureToFuture(classSourceFuture)) && + success(safeThreadFutureToFuture(classTypeFuture))); RangeResult processTypeList = classTypeFuture.get(); RangeResult processSourceList = classSourceFuture.get(); ASSERT(processSourceList.size() == processTypeList.size()); - if (!processTypeList.size()) + if (processTypeList.empty()) printf("No processes are registered in the database.\n"); fmt::print("There are currently {} processes in the database:\n", processTypeList.size()); for (int index = 0; index < processTypeList.size(); index++) { @@ -65,38 +65,40 @@ ACTOR Future printProcessClass(Reference db) { processTypeList[index].value.toString().c_str(), processSourceList[index].value.toString().c_str()); } - return Void(); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } }; -ACTOR Future setProcessClass(Reference db, KeyRef network_address, KeyRef class_type) { - state Reference tr = db->createTransaction(); - loop { +Future setProcessClass(Reference db, KeyRef network_address, KeyRef class_type) { + Reference tr = db->createTransaction(); + while (true) { tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); + Error err; try { - state ThreadFuture> result = + ThreadFuture> result = tr->get(network_address.withPrefix(fdb_cli::processClassTypeSpecialKeyRange.begin)); - Optional val = wait(safeThreadFutureToFuture(result)); + Optional val = co_await safeThreadFutureToFuture(result); if (!val.present()) { printf("No matching addresses found\n"); - return false; + co_return false; } tr->set(network_address.withPrefix(fdb_cli::processClassTypeSpecialKeyRange.begin), class_type); - wait(safeThreadFutureToFuture(tr->commit())); - return true; + co_await safeThreadFutureToFuture(tr->commit()); + co_return true; } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_special_keys_api_failure) { - std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr)); - // error message already has \n at the end - fprintf(stderr, "%s", errorMsgStr.c_str()); - return false; - } - wait(safeThreadFutureToFuture(tr->onError(err))); + err = e; + } + if (err.code() == error_code_special_keys_api_failure) { + std::string errorMsgStr = co_await fdb_cli::getSpecialKeysFailureErrorMessage(tr); + // error message already has \n at the end + fprintf(stderr, "%s", errorMsgStr.c_str()); + co_return false; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -110,17 +112,17 @@ const KeyRangeRef processClassSourceSpecialKeyRange = const KeyRangeRef processClassTypeSpecialKeyRange = KeyRangeRef("\xff\xff/configuration/process/class_type/"_sr, "\xff\xff/configuration/process/class_type0"_sr); -ACTOR Future setClassCommandActor(Reference db, std::vector tokens) { +Future setClassCommandActor(Reference db, std::vector tokens) { if (tokens.size() != 3 && tokens.size() != 1) { printUsage(tokens[0]); - return false; + co_return false; } else if (tokens.size() == 1) { - wait(printProcessClass(db)); + co_await printProcessClass(db); } else { - bool successful = wait(setProcessClass(db, tokens[1], tokens[2])); - return successful; + bool successful = co_await setProcessClass(db, tokens[1], tokens[2]); + co_return successful; } - return true; + co_return true; } CommandFactory setClassFactory( @@ -131,7 +133,7 @@ CommandFactory setClassFactory( "`default' resets the process class to the class specified on the command line. The available " "classes are `unset', `storage', `transaction', `resolution', `commit_proxy', `grv_proxy', " "`master', `test', " - "`stateless', `log', `router', `cluster_controller', `fast_restore', `data_distributor', " + "`stateless', `log', `router', `cluster_controller', `data_distributor', " "`coordinator', `ratekeeper', `backup', and `default'.")); } // namespace fdb_cli diff --git a/fdbcli/SnapshotCommand.actor.cpp b/fdbcli/SnapshotCommand.cpp similarity index 79% rename from fdbcli/SnapshotCommand.actor.cpp rename to fdbcli/SnapshotCommand.cpp index 844ae88d588..d02a25c52c3 100644 --- a/fdbcli/SnapshotCommand.actor.cpp +++ b/fdbcli/SnapshotCommand.cpp @@ -1,5 +1,5 @@ /* - * SnapshotCommand.actor.cpp + * SnapshotCommand.cpp * * This source file is part of the FoundationDB open source project * @@ -18,25 +18,23 @@ * limitations under the License. */ -#include "fdbcli/fdbcli.actor.h" +#include "fdbcli/fdbcli.h" #include "fdbclient/IClientApi.h" #include "flow/Arena.h" #include "flow/FastRef.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - namespace fdb_cli { -ACTOR Future snapshotCommandActor(Reference db, std::vector tokens) { - state bool result = true; +Future snapshotCommandActor(Reference db, std::vector const& tokens) { + bool result = true; if (tokens.size() < 2) { printUsage(tokens[0]); result = false; } else { Standalone snap_cmd; - state Key uid(deterministicRandom()->randomUniqueID().toString()); + Key uid(deterministicRandom()->randomUniqueID().toString()); for (int i = 1; i < tokens.size(); i++) { snap_cmd = snap_cmd.withSuffix(tokens[i]); if (i != tokens.size() - 1) { @@ -44,7 +42,7 @@ ACTOR Future snapshotCommandActor(Reference db, std::vectorcreateSnapshot(uid, snap_cmd))); + co_await safeThreadFutureToFuture(db->createSnapshot(uid, snap_cmd)); printf("Snapshot command succeeded with UID %s\n", uid.toString().c_str()); } catch (Error& e) { fprintf(stderr, @@ -56,7 +54,7 @@ ACTOR Future snapshotCommandActor(Reference db, std::vector

:= kv pairs and issue RPC -// calls, then only return interfaces(kv pairs) the client can talk to -ACTOR Future getWorkerInterfaces(Reference tr, - std::map>* address_interface, - bool verify = false); -// print cluster status info -void printStatus(StatusObjectReader statusObj, - StatusClient::StatusLevel level, - bool displayDatabaseAvailable = true, - bool hideErrorMessages = false); - -// All fdbcli commands (alphabetically) -// All below actors return true if the command is executed successfully -// advanceversion command -ACTOR Future advanceVersionCommandActor(Reference db, std::vector tokens); -// configure command -ACTOR Future configureCommandActor(Reference db, - Database localDb, - std::vector tokens, - LineNoise* linenoise, - Future warn); -// consistency command -ACTOR Future consistencyCheckCommandActor(Reference tr, - std::vector tokens, - bool intrans); -// consistency scan command -ACTOR Future consistencyScanCommandActor(Database localDb, std::vector tokens); -// coordinators command -ACTOR Future coordinatorsCommandActor(Reference db, std::vector tokens); -// datadistribution command -ACTOR Future dataDistributionCommandActor(Reference db, std::vector tokens); -// exclude command -ACTOR Future excludeCommandActor(Reference db, std::vector tokens, Future warn); -// expensive_data_check command -ACTOR Future expensiveDataCheckCommandActor( - Reference db, - Reference tr, - std::vector tokens, - std::map>* address_interface); -// fileconfigure command -ACTOR Future fileConfigureCommandActor(Reference db, - std::string filePath, - bool isNewDatabase, - bool force); -// Trigger audit storage -ACTOR Future auditStorageCommandActor(Reference clusterFile, - std::vector tokens); -// Retrieve audit storage status -ACTOR Future getAuditStatusCommandActor(Database cx, std::vector tokens); -// Retrieve shard information command -ACTOR Future locationMetadataCommandActor(Database cx, std::vector tokens); -// Bulk loading command -ACTOR Future bulkLoadCommandActor(Database cx, std::vector tokens); -// Bulk dumping command -ACTOR Future bulkDumpCommandActor(Database cx, std::vector tokens); -// force_recovery_with_data_loss command -ACTOR Future forceRecoveryWithDataLossCommandActor(Reference db, std::vector tokens); -// include command -ACTOR Future includeCommandActor(Reference db, std::vector tokens); -// kill command -ACTOR Future killCommandActor(Reference db, - Reference tr, - std::vector tokens, - std::map>* address_interface); -// lock/unlock command -ACTOR Future lockCommandActor(Reference db, std::vector tokens); -ACTOR Future unlockDatabaseActor(Reference db, UID uid); - -// blobrestore command -ACTOR Future blobRestoreCommandActor(Database localDb, std::vector tokens); -// hotrange command -ACTOR Future hotRangeCommandActor(Database localDb, - Reference db, - std::vector tokens, - std::map* storage_interface); - -// maintenance command -ACTOR Future setHealthyZone(Reference db, StringRef zoneId, double seconds, bool printWarning = false); -ACTOR Future clearHealthyZone(Reference db, - bool printWarning = false, - bool clearSSFailureZoneString = false); -ACTOR Future maintenanceCommandActor(Reference db, std::vector tokens); -// profile command -ACTOR Future profileCommandActor(Database db, - Reference tr, - std::vector tokens, - bool intrans); -// quota command -ACTOR Future quotaCommandActor(Reference db, std::vector tokens); -// setclass command -ACTOR Future setClassCommandActor(Reference db, std::vector tokens); -// snapshot command -ACTOR Future snapshotCommandActor(Reference db, std::vector tokens); -// status command -ACTOR Future statusCommandActor(Reference db, - Database localDb, - std::vector tokens, - bool isExecMode = false); -// suspend command -ACTOR Future suspendCommandActor(Reference db, - Reference tr, - std::vector tokens, - std::map>* address_interface); -// throttle command -ACTOR Future throttleCommandActor(Reference db, std::vector tokens); -// triggerteaminfolog command -ACTOR Future triggerddteaminfologCommandActor(Reference db); -// tssq command -ACTOR Future tssqCommandActor(Reference db, std::vector tokens); -// versionepoch command -ACTOR Future versionEpochCommandActor(Reference db, Database cx, std::vector tokens); -// targetversion command -ACTOR Future targetVersionCommandActor(Reference db, std::vector tokens); -// idempotencyids command -ACTOR Future idempotencyIdsCommandActor(Database cx, std::vector tokens); - -// rangeconfig command -ACTOR Future rangeConfigCommandActor(Database cx, std::vector tokens); - -// debug commands: getlocation, getall -ACTOR Future getLocationCommandActor(Database cx, std::vector tokens); -ACTOR Future getallCommandActor(Database cx, std::vector tokens, Version version); -ACTOR Future checkallCommandActor(Database cx, std::vector tokens); -} // namespace fdb_cli - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbcli/include/fdbcli/fdbcli.h b/fdbcli/include/fdbcli/fdbcli.h new file mode 100644 index 00000000000..cdc3ce7975d --- /dev/null +++ b/fdbcli/include/fdbcli/fdbcli.h @@ -0,0 +1,342 @@ +/* + * fdbcli.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 "fdbcli/FlowLineNoise.h" + +#include "fdbclient/BulkLoading.h" +#include "fdbclient/CoordinationInterface.h" +#include "fdbclient/ManagementAPI.h" +#include "fdbclient/IClientApi.h" +#include "fdbclient/StatusClient.h" +#include "fdbclient/StorageServerInterface.h" +#include "flow/Arena.h" + +namespace fdb_cli { + +constexpr char msgTypeKey[] = "type"; +constexpr char msgClusterKey[] = "cluster"; +constexpr char msgCapacityKey[] = "capacity"; +constexpr char msgAllocatedKey[] = "allocated"; +constexpr char msgErrorKey[] = "error"; + +struct CommandHelp { + std::string usage; + std::string short_desc; + std::string long_desc; + CommandHelp() {} + CommandHelp(const char* usage, const char* short_desc, const char* long_desc) + : usage(usage), short_desc(short_desc), long_desc(long_desc) {} +}; + +void arrayGenerator(const char* text, const char* line, const char** options, std::vector& lc); + +struct CommandFactory { + typedef void (*CompletionGeneratorFunc)(const char* text, + const char* line, + std::vector& lc, + std::vector const& tokens); + + typedef std::vector (*HintGeneratorFunc)(std::vector const& tokens, bool inArgument); + + CommandFactory(const char* name, + CommandHelp help, + CompletionGeneratorFunc completionFunc = nullptr, + HintGeneratorFunc hintFunc = nullptr) { + commands()[name] = help; + if (completionFunc) { + completionGenerators()[name] = completionFunc; + } + if (hintFunc) { + hintGenerators()[name] = hintFunc; + } + } + explicit CommandFactory(const char* name) { hiddenCommands().insert(name); } + static std::map& commands() { + static std::map helpMap; + return helpMap; + } + static std::set& hiddenCommands() { + static std::set commands; + return commands; + } + static std::map& completionGenerators() { + static std::map completionMap; + return completionMap; + } + static std::map& hintGenerators() { + static std::map hintMap; + return hintMap; + } +}; + +// Special keys used by fdbcli commands + +extern const KeyRef advanceVersionSpecialKey; +extern const KeyRef consistencyCheckSpecialKey; +extern const KeyRef clusterDescriptionSpecialKey; +extern const KeyRef coordinatorsAutoSpecialKey; +extern const KeyRef coordinatorsProcessSpecialKey; +extern const KeyRef ddModeSpecialKey; +extern const KeyRef ddIgnoreRebalanceSpecialKey; + +extern const KeyRangeRef excludedServersSpecialKeyRange; +extern const KeyRangeRef failedServersSpecialKeyRange; +extern const KeyRangeRef excludedLocalitySpecialKeyRange; +extern const KeyRangeRef failedLocalitySpecialKeyRange; +extern const KeyRef excludedForceOptionSpecialKey; +extern const KeyRef failedForceOptionSpecialKey; +extern const KeyRef excludedLocalityForceOptionSpecialKey; +extern const KeyRef failedLocalityForceOptionSpecialKey; +extern const KeyRangeRef exclusionInProgressSpecialKeyRange; + +extern const KeyRef lockSpecialKey; + +extern const KeyRangeRef maintenanceSpecialKeyRange; +extern const KeyRef ignoreSSFailureSpecialKey; + +extern const KeyRangeRef processClassSourceSpecialKeyRange; +extern const KeyRangeRef processClassTypeSpecialKeyRange; + +inline const KeyRef errorMsgSpecialKey = "\xff\xff/error_message"_sr; +inline const KeyRef workerInterfacesVerifyOptionSpecialKey = "\xff\xff/management/options/worker_interfaces/verify"_sr; + +Future getWorkers(Reference db, std::vector* workers); +Future getStorageServerInterfaces(Reference db, + std::map* interfaces); + +bool tokencmp(StringRef token, const char* command); +void printUsage(StringRef command); +void printLongDesc(StringRef command); + +// Pre: tr failed with special_keys_api_failure error +// Read the error message special key and return the message +Future getSpecialKeysFailureErrorMessage(Reference tr); + +// ============================================================================ +// BulkDump/BulkLoad CLI Utilities +// ============================================================================ + +// Parse and validate a job ID token from CLI input. Prints usage and throws on invalid input. +UID validateBulkJobId(StringRef token, const char* usage); + +// Get owner information suffix for display (e.g., " (owned by: backup 'daily-backup')"). +// Returns empty string if no owner is set. +Future getBulkOwnerSuffix(Database cx, UID jobId, bool isDumpJob); + +// Format bytes with optional total and percentage. +std::string formatBytesProgress(int64_t completedBytes, Optional totalBytes); + +// Print throughput, ETA, and elapsed time metrics to stdout. +void printProgressMetrics(double avgBytesPerSecond, Optional etaSeconds, double elapsedSeconds); + +// Print task and byte completion summary (e.g., "Tasks: 5/10, Bytes: 1.2 GB / 2.4 GB"). +void printProgressSummary(const char* operationName, + int completeTasks, + int totalTasks, + int64_t completedBytes, + int64_t totalBytes); + +// Print detailed task state breakdown for bulkload operations. +void printTaskBreakdown(int submittedTasks, int triggeredTasks, int runningTasks, int completeTasks, int errorTasks); + +// ============================================================================ +// BulkDump/BulkLoad Health Analysis +// Used by "bulkdump status --analyze" and "bulkload status --analyze" +// ============================================================================ + +// Health assessment based on throughput, efficiency, and error rates. +// healthScore: 0-100 rating, healthStatus: "healthy"/"degraded"/"critical" +struct BulkHealthMetrics { + double healthScore; + std::string healthStatus; + std::vector recommendations; + + static BulkHealthMetrics analyze(double throughputMBps, + double efficiencyPercent, + int stalledTasks, + int errorTasks, + double elapsedMinutes); +}; + +// Error categorization and actionable diagnostics. +struct BulkErrorAnalysis { + int totalErrors; + std::map errorCategories; + std::vector criticalIssues; + std::vector actionableAdvice; + + static BulkErrorAnalysis analyze(int errorTasks, int stalledTasks, const std::vector& recentErrors); +}; + +// Performance and operational recommendations based on current metrics. +struct BulkOptimizationRecommendations { + std::vector performanceRecommendations; + std::vector reliabilityRecommendations; + std::vector operationalRecommendations; + + static BulkOptimizationRecommendations generate(double throughputMBps, + double efficiency, + int stalledTasks, + int errorTasks, + double elapsedMinutes, + int totalTasks); +}; + +// Print formatted health analysis to stdout. +void printBulkHealthAnalysis(const BulkHealthMetrics& health); + +// Print error diagnostics and advice to stdout. +void printErrorDiagnostics(const BulkErrorAnalysis& analysis); + +// Print optimization recommendations to stdout. +void printOptimizationRecommendations(const BulkOptimizationRecommendations& recs); + +// Print warnings for tasks that have stalled (no progress for extended period). +void printStalledTasks(const std::vector& stalledTasks); + +// Run full analysis and print results: health metrics, stalled tasks, error diagnostics, recommendations. +void printBulkAnalysis(double avgBytesPerSecond, + double elapsedSeconds, + int completeTasks, + int totalTasks, + int errorTasks, + const std::vector& stalledTasks); + +// Using \xff\xff/worker_interfaces/ special key, get all worker interfaces. +// A worker list will be returned from CC. +// If verify, we will try to establish connections to all workers returned. +// In particular, it will deserialize \xff\xff/worker_interfaces/
:= kv pairs and issue RPC +// calls, then only return interfaces(kv pairs) the client can talk to +Future getWorkerInterfaces(Reference tr, + std::map>* address_interface, + bool verify = false); +// print cluster status info +void printStatus(StatusObjectReader statusObj, + StatusClient::StatusLevel level, + bool displayDatabaseAvailable = true, + bool hideErrorMessages = false); + +// All fdbcli commands (alphabetically) +// All below actors return true if the command is executed successfully +// advanceversion command +Future advanceVersionCommandActor(Reference db, std::vector tokens); +// configure command +Future configureCommandActor(Reference db, + Database localDb, + std::vector tokens, + LineNoise* linenoise, + Future _warn); +// consistency command +Future consistencyCheckCommandActor(Reference tr, + std::vector const& tokens, + bool intrans); +// consistency scan command +Future consistencyScanCommandActor(Database localDb, std::vector const& tokens); +// coordinators command +Future coordinatorsCommandActor(Reference db, std::vector tokens); +// datadistribution command +Future dataDistributionCommandActor(Reference db, std::vector tokens); +// exclude command +Future excludeCommandActor(Reference db, std::vector tokens, Future _warn); +// expensive_data_check command +Future expensiveDataCheckCommandActor( + Reference db, + Reference tr, + std::vector tokens, + std::map>* address_interface); +// fileconfigure command +Future fileConfigureCommandActor(Reference db, + std::string const& filePath, + bool isNewDatabase, + bool force); +// Trigger audit storage +Future auditStorageCommandActor(Reference clusterFile, std::vector tokens); +// Retrieve audit storage status +Future getAuditStatusCommandActor(Database cx, std::vector tokens); +// Retrieve shard information command +Future locationMetadataCommandActor(Database cx, std::vector tokens); +// Bulk loading command +Future bulkLoadCommandActor(Database cx, std::vector tokens); +// Bulk dumping command +Future bulkDumpCommandActor(Database cx, std::vector tokens); +// force_recovery_with_data_loss command +Future forceRecoveryWithDataLossCommandActor(Reference db, std::vector const& tokens); +// include command +Future includeCommandActor(Reference db, std::vector tokens); +// kill command +Future killCommandActor(Reference db, + Reference tr, + std::vector tokens, + std::map>* address_interface); +// lock/unlock command +Future lockCommandActor(Reference db, std::vector const& tokens); +Future unlockDatabaseActor(Reference db, UID uid); + +// hotrange command +Future hotRangeCommandActor(Database localDb, + Reference db, + std::vector const& tokens, + std::map* const& storage_interface); + +// maintenance command +Future setHealthyZone(Reference db, StringRef zoneId, double seconds, bool printWarning = false); +Future clearHealthyZone(Reference db, + bool printWarning = false, + bool clearSSFailureZoneString = false); +Future maintenanceCommandActor(Reference db, std::vector tokens); +// profile command +Future profileCommandActor(Database db, Reference tr, std::vector tokens, bool intrans); +// setclass command +Future setClassCommandActor(Reference db, std::vector tokens); +// snapshot command +Future snapshotCommandActor(Reference db, std::vector const& tokens); +// status command +Future statusCommandActor(Reference db, + Database localDb, + std::vector tokens, + bool isExecMode = false); +// suspend command +Future suspendCommandActor(Reference db, + Reference tr, + std::vector const& tokens, + std::map>* address_interface); +// throttle command +Future throttleCommandActor(Reference db, std::vector tokens); +// triggerteaminfolog command +Future triggerddteaminfologCommandActor(Reference db); +// tssq command +Future tssqCommandActor(Reference db, std::vector tokens); +// versionepoch command +Future versionEpochCommandActor(Reference db, Database cx, std::vector tokens); +// targetversion command +Future targetVersionCommandActor(Reference db, std::vector tokens); +// idempotencyids command +Future idempotencyIdsCommandActor(Database cx, std::vector const& tokens); + +// rangeconfig command +Future rangeConfigCommandActor(Database cx, std::vector tokens); + +// debug commands: getlocation, getall +Future getLocationCommandActor(Database cx, std::vector tokens); +Future getallCommandActor(Database cx, std::vector tokens, Version version); +Future checkallCommandActor(Database cx, std::vector tokens); +} // namespace fdb_cli diff --git a/fdbcli/tests/fdbcli_tests.py b/fdbcli/tests/fdbcli_tests.py index 57722e8bd33..22f9d80cb39 100755 --- a/fdbcli/tests/fdbcli_tests.py +++ b/fdbcli/tests/fdbcli_tests.py @@ -119,76 +119,6 @@ def maintenance(logger): assert output3 == no_maintenance_output -@enable_logging() -def quota(logger): - # Should be a noop - command = "quota clear green" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully cleared quota." - - command = "quota get green total_throughput" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "" - - # Ignored update - command = "quota set red total_throughput 49152" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully updated quota." - - command = "quota set green total_throughput 32768" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully updated quota." - - command = "quota set green reserved_throughput 16384" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully updated quota." - - command = "quota set green storage 98765" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully updated quota." - - command = "quota get green total_throughput" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "32768" - - command = "quota get green reserved_throughput" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "16384" - - command = "quota get green storage" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "98765" - - command = "quota clear green" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "Successfully cleared quota." - - command = "quota get green total_throughput" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "" - - command = "quota get green storage" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - assert output == "" - - # Too few arguments, should log help message - command = "quota get green" - output = run_fdbcli_command(command) - logger.debug(command + " : " + output) - - @enable_logging() def setclass(logger): # get all processes' network addresses @@ -379,7 +309,7 @@ def suspend(logger): port = address.split(":")[1] logger.debug("Port: {}".format(port)) # use the port number to find the exact fdb process we are connecting to - # child process like fdbserver -r flowprocess does not provide `datadir` in the command line + # auxiliary processes may not provide `datadir` in the command line pinfo = list(filter(lambda x: port in x and "datadir" in x, pinfos)) assert len(pinfo) == 1 pid = pinfo[0].split(" ")[0] @@ -471,6 +401,64 @@ def get_value_from_status_json(retry, *args): return result +def status_json_file_region_failover_message(): + status_json = { + "client": { + "cluster_file": {"path": "fdb.cluster", "up_to_date": True}, + "coordinators": {"coordinators": [], "quorum_reachable": True}, + "database_status": {"available": True, "healthy": False}, + "messages": [], + "timestamp": 1417807090, + }, + "cluster": { + "configuration": { + "redundancy_mode": "three_data_hall", + "storage_engine": "ssd-2", + "coordinators_count": 3, + "excluded_servers": [], + }, + "data": {"state": {"name": "healthy", "healthy": True}}, + "fault_tolerance": { + "max_zone_failures_without_losing_availability": -1, + "max_zone_failures_without_losing_data": -1, + }, + "logs": [ + { + "epoch": 1, + "current": True, + "begin_version": 1, + "possibly_losing_data": False, + "log_interfaces": [ + { + "id": "aaaaaaaaaaaaaaaa", + "healthy": False, + "address": "1.1.1.1:4500", + } + ], + } + ], + "machines": {}, + "processes": {}, + }, + } + + with tempfile.NamedTemporaryFile(mode="w", suffix=".json") as status_file: + json.dump(status_json, status_file) + status_file.flush() + result = subprocess.run( + [command_template[0], "--status-from-json", status_file.name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=fdbcli_env, + ) + + stdout = result.stdout.decode("utf-8") + stderr = result.stderr.decode("utf-8") + assert result.returncode == 0, stderr + assert "Warning: the database may have availability loss." in stdout + assert "may have data loss" not in stdout + + @enable_logging() def consistencycheck(logger): consistency_check_on_output = "ConsistencyCheck is on" @@ -939,17 +927,16 @@ def tls_address_suffix(): lockAndUnlock() maintenance() profile() - # TODO: re-enable it until it's stable + # TODO: re-enable once stable # suspend() transaction() - # this is replaced by the "quota" command + # TODO: re-enable once stable # throttle() triggerddteaminfolog() versionepoch() integer_options() tls_address_suffix() - # TODO: fix the issue when running through the external client - # quota() + status_json_file_region_failover_message() idempotency_ids() else: assert args.process_number > 1, "Process number should be positive" diff --git a/fdbclient/ActorLineageProfiler.cpp b/fdbclient/ActorLineageProfiler.cpp index 50c6cfa0602..17180df6ddc 100644 --- a/fdbclient/ActorLineageProfiler.cpp +++ b/fdbclient/ActorLineageProfiler.cpp @@ -22,7 +22,7 @@ #include "flow/singleton.h" #include "flow/IAsyncFile.h" #include "fdbclient/ActorLineageProfiler.h" -#include "fdbclient/NameLineage.h" +#include "NameLineage.h" #include #include #include @@ -313,7 +313,7 @@ boost::asio::io_context& ActorLineageProfilerT::context() { return impl->context; } -SampleIngestor::~SampleIngestor() {} +SampleIngestor::~SampleIngestor() = default; void ProfilerConfigT::reset(std::map const& config) { bool expectNoMore = false, useFluentD = false, useTCP = false; diff --git a/fdbclient/AsyncFileS3BlobStore.actor.cpp b/fdbclient/AsyncFileBlobStore.cpp similarity index 66% rename from fdbclient/AsyncFileS3BlobStore.actor.cpp rename to fdbclient/AsyncFileBlobStore.cpp index 3e542794bfc..0952fec0752 100644 --- a/fdbclient/AsyncFileS3BlobStore.actor.cpp +++ b/fdbclient/AsyncFileBlobStore.cpp @@ -1,5 +1,5 @@ /* - * AsyncFileS3BlobStore.actor.cpp + * AsyncFileBlobStore.cpp * * This source file is part of the FoundationDB open source project * @@ -18,50 +18,45 @@ * limitations under the License. */ -#include "fdbclient/AsyncFileS3BlobStore.actor.h" -#include "fdbrpc/AsyncFileReadAhead.actor.h" +#include "fdbclient/AsyncFileBlobStore.h" #include "flow/UnitTest.h" -#include "flow/IConnection.h" -#include "flow/actorcompiler.h" // has to be last include -Future AsyncFileS3BlobStoreRead::size() const { +Future AsyncFileBlobStoreRead::size() const { if (!m_size.isValid()) m_size = m_bstore->objectSize(m_bucket, m_object); return m_size; } -Future AsyncFileS3BlobStoreRead::read(void* data, int length, int64_t offset) { +Future AsyncFileBlobStoreRead::read(void* data, int length, int64_t offset) { return m_bstore->readObject(m_bucket, m_object, data, length, offset); } -ACTOR Future sendStuff(int id, Reference t, int bytes) { +Future sendStuff(int id, Reference t, int bytes) { printf("Starting fake sender %d which will send send %d bytes.\n", id, bytes); - state double ts = timer(); - state int total = 0; + double ts = timer(); + int total = 0; while (total < bytes) { - state int r = std::min(deterministicRandom()->randomInt(0, 1000), bytes - total); - wait(t->getAllowance(r)); + int r = std::min(deterministicRandom()->randomInt(0, 1000), bytes - total); + co_await t->getAllowance(r); total += r; } double dur = timer() - ts; printf("Sender %d: Sent %d in %fs, %f/s\n", id, total, dur, total / dur); - return Void(); } TEST_CASE("/backup/throttling") { // Test will not work in simulation. if (g_network->isSimulated()) - return Void(); + co_return; - state int limit = 100000; - state Reference t(new SpeedLimit(limit, 1)); + int limit = 100000; + Reference t(new SpeedLimit(limit, 1)); - state int id = 1; + int id = 1; std::vector> f; - state double ts = timer(); - state int total = 0; - int s; - s = 500000; + double ts = timer(); + int total = 0; + int s = 500000; f.push_back(sendStuff(id++, t, s)); total += s; f.push_back(sendStuff(id++, t, s)); @@ -75,11 +70,9 @@ TEST_CASE("/backup/throttling") { f.push_back(sendStuff(id++, t, s)); total += s; - wait(waitForAll(f)); + co_await waitForAll(f); double dur = timer() - ts; int speed = int(total / dur); printf("Speed limit was %d, measured speed was %d\n", limit, speed); ASSERT(abs(speed - limit) / limit < .01); - - return Void(); } diff --git a/fdbclient/AsyncTaskThread.actor.cpp b/fdbclient/AsyncTaskThread.actor.cpp deleted file mode 100644 index adf9321355f..00000000000 --- a/fdbclient/AsyncTaskThread.actor.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * AsyncTaskThread.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. - */ - -#include - -#include "fdbclient/AsyncTaskThread.h" -#include "flow/UnitTest.h" -#include "flow/actorcompiler.h" // This must be the last #include. - -namespace { - -class TerminateTask final : public IAsyncTask { -public: - void operator()() override { ASSERT(false); } - bool isTerminate() const override { return true; } -}; - -ACTOR Future asyncTaskThreadClient(AsyncTaskThread* asyncTaskThread, - std::atomic* sum, - int count, - int clientId, - double meanSleep) { - state int i = 0; - state double randomSleep = 0.0; - for (; i < count; ++i) { - randomSleep = deterministicRandom()->random01() * 2 * meanSleep; - wait(delay(randomSleep)); - wait(asyncTaskThread->execAsync([sum = sum] { - sum->fetch_add(1); - return Void(); - })); - TraceEvent("AsyncTaskThreadIncrementedSum") - .detail("Index", i) - .detail("Sum", sum->load()) - .detail("ClientId", clientId) - .detail("RandomSleep", randomSleep) - .detail("MeanSleep", meanSleep); - } - return Void(); -} - -} // namespace - -const double AsyncTaskThread::meanDelay = 0.01; - -AsyncTaskThread::AsyncTaskThread() : thread([this] { run(this); }) {} - -AsyncTaskThread::~AsyncTaskThread() { - bool wakeUp = false; - { - std::lock_guard g(m); - wakeUp = queue.push(std::make_unique()); - } - if (wakeUp) { - cv.notify_one(); - } - thread.join(); -} - -void AsyncTaskThread::run(AsyncTaskThread* self) { - while (true) { - std::unique_ptr task; - { - std::unique_lock lk(self->m); - self->cv.wait(lk, [self] { return !self->queue.canSleep(); }); - task = self->queue.pop().get(); - if (task->isTerminate()) { - return; - } - } - (*task)(); - } -} - -TEST_CASE("/asynctaskthread/add") { - state std::atomic sum = 0; - state AsyncTaskThread asyncTaskThread; - state int numClients = 10; - state int incrementsPerClient = 100; - std::vector> clients; - clients.reserve(numClients); - for (int clientId = 0; clientId < numClients; ++clientId) { - clients.push_back(asyncTaskThreadClient( - &asyncTaskThread, &sum, incrementsPerClient, clientId, deterministicRandom()->random01() * 0.01)); - } - wait(waitForAll(clients)); - ASSERT_EQ(sum.load(), numClients * incrementsPerClient); - return Void(); -} - -TEST_CASE("/asynctaskthread/error") { - state AsyncTaskThread asyncTaskThread; - try { - wait(asyncTaskThread.execAsync([] { - throw operation_failed(); - return Void(); - })); - ASSERT(false); - } catch (Error& e) { - ASSERT_EQ(e.code(), error_code_operation_failed); - } - return Void(); -} diff --git a/fdbclient/AuditUtils.actor.cpp b/fdbclient/AuditUtils.cpp similarity index 64% rename from fdbclient/AuditUtils.actor.cpp rename to fdbclient/AuditUtils.cpp index d3fd08cc294..0bb5b3a72a5 100644 --- a/fdbclient/AuditUtils.actor.cpp +++ b/fdbclient/AuditUtils.cpp @@ -1,5 +1,5 @@ /* - * AuditUtils.actor.cpp + * AuditUtils.cpp * * This source file is part of the FoundationDB open source project * @@ -18,17 +18,16 @@ * limitations under the License. */ -#include "fdbclient/AuditUtils.actor.h" +#include "fdbclient/AuditUtils.h" #include "fdbclient/Audit.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/ClientKnobs.h" +#include "fdbclient/Knobs.h" +#include "flow/CoroUtils.h" #include -#include "flow/actorcompiler.h" // has to be last include - void clearAuditProgressMetadata(Transaction* tr, AuditType auditType, UID auditId) { // There are two possible places to store AuditProgressMetadata: // (1) auditServerBasedProgressRangeFor or (2) auditRangeBasedProgressRangeFor @@ -53,67 +52,68 @@ void clearAuditProgressMetadata(Transaction* tr, AuditType auditType, UID auditI return; } -ACTOR Future checkStorageServerRemoved(Database cx, UID ssid) { - state bool res = false; - state Transaction tr(cx); +Future checkStorageServerRemoved(Database cx, UID ssid) { + bool res = false; + Transaction tr(cx); TraceEvent(SevDebug, "AuditUtilStorageServerRemovedStart").detail("StorageServer", ssid); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional serverListValue = wait(tr.get(serverListKeyFor(ssid))); + Optional serverListValue = co_await tr.get(serverListKeyFor(ssid)); if (!serverListValue.present()) { res = true; // SS is removed } break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilStorageServerRemovedError") - .errorUnsuppressed(e) - .detail("StorageServer", ssid); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilStorageServerRemovedError").errorUnsuppressed(err).detail("StorageServer", ssid); + co_await tr.onError(err); } TraceEvent(SevDebug, "AuditUtilStorageServerRemovedEnd").detail("StorageServer", ssid).detail("Removed", res); - return res; + co_return res; } -ACTOR Future cancelAuditMetadata(Database cx, AuditType auditType, UID auditId) { +Future cancelAuditMetadata(Database cx, AuditType auditType, UID auditId) { try { - state Transaction tr(cx); + Transaction tr(cx); TraceEvent(SevInfo, "AuditUtilCancelAuditMetadataStart", auditId) .detail("AuditKey", auditKey(auditType, auditId)); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional res_ = wait(tr.get(auditKey(auditType, auditId))); + Optional res_ = co_await tr.get(auditKey(auditType, auditId)); if (!res_.present()) { // has been cancelled break; // Nothing to cancel } - state AuditStorageState toCancelState = decodeAuditStorageState(res_.get()); + AuditStorageState toCancelState = decodeAuditStorageState(res_.get()); // For a zombie audit, it is in running state ASSERT(toCancelState.id == auditId && toCancelState.getType() == auditType); toCancelState.setPhase(AuditPhase::Failed); tr.set(auditKey(toCancelState.getType(), toCancelState.id), auditStorageStateValue(toCancelState)); clearAuditProgressMetadata(&tr, toCancelState.getType(), toCancelState.id); - wait(tr.commit()); + co_await tr.commit(); TraceEvent(SevInfo, "AuditUtilCancelAuditMetadataEnd", auditId) .detail("AuditKey", auditKey(auditType, auditId)); break; } catch (Error& e) { - TraceEvent(SevWarn, "AuditUtilCancelAuditMetadataError", auditId) - .detail("AuditKey", auditKey(auditType, auditId)); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevWarn, "AuditUtilCancelAuditMetadataError", auditId) + .detail("AuditKey", auditKey(auditType, auditId)); + co_await tr.onError(err); } - } catch (Error& e) { + } catch (Error&) { throw cancel_audit_storage_failed(); } - return Void(); } AuditPhase stringToAuditPhase(std::string auditPhaseStr) { @@ -135,20 +135,21 @@ AuditPhase stringToAuditPhase(std::string auditPhaseStr) { } // This is not transactional -ACTOR Future> getAuditStates(Database cx, - AuditType auditType, - bool newFirst, - Optional num, - Optional phase) { - state Transaction tr(cx); - state std::vector auditStates; - state Key readBegin; - state Key readEnd; - state Reverse reverse = newFirst ? Reverse::True : Reverse::False; +AsyncResult> getAuditStates(Database cx, + AuditType auditType, + bool newFirst, + Optional num, + Optional phase) { + Transaction tr(cx); + std::vector auditStates; + Key readBegin; + Key readEnd; + Reverse reverse = newFirst ? Reverse::True : Reverse::False; if (num.present() && num.get() == 0) { - return auditStates; + co_return auditStates; } - loop { + while (true) { + Error err; try { readBegin = auditKeyRange(auditType).begin; readEnd = auditKeyRange(auditType).end; @@ -158,18 +159,18 @@ ACTOR Future> getAuditStates(Database cx, tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); KeyRangeRef rangeToRead(readBegin, readEnd); - state RangeResult res = wait(tr.getRange(rangeToRead, - num.present() ? GetRangeLimits(num.get()) : GetRangeLimits(), - Snapshot::False, - reverse)); - for (int i = 0; i < res.size(); ++i) { - const AuditStorageState auditState = decodeAuditStorageState(res[i].value); + RangeResult res = co_await tr.getRange(rangeToRead, + num.present() ? GetRangeLimits(num.get()) : GetRangeLimits(), + Snapshot::False, + reverse); + for (const auto& auditKeyValue : res) { + const AuditStorageState auditState = decodeAuditStorageState(auditKeyValue.value); if (phase.present() && auditState.getPhase() != phase.get()) { continue; } auditStates.push_back(auditState); if (num.present() && auditStates.size() == num.get()) { - return auditStates; // since res.more is not reliable when GetRangeLimits is set to 1 + co_return auditStates; // since res.more is not reliable when GetRangeLimits is set to 1 } } if (!res.more) { @@ -184,27 +185,28 @@ ACTOR Future> getAuditStates(Database cx, } break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return auditStates; + co_return auditStates; } -ACTOR Future clearAuditMetadataForType(Database cx, - AuditType auditType, - UID maxAuditIdToClear, - int numFinishAuditToKeep) { - state Transaction tr(cx); - state int numFinishAuditCleaned = 0; // We regard "Complete" and "Failed" audits as finish audits +Future clearAuditMetadataForType(Database cx, + AuditType auditType, + UID maxAuditIdToClear, + int numFinishAuditToKeep) { + Transaction tr(cx); + int numFinishAuditCleaned = 0; // We regard "Complete" and "Failed" audits as finish audits TraceEvent(SevDebug, "AuditUtilClearAuditMetadataForTypeStart") .detail("AuditType", auditType) .detail("MaxAuditIdToClear", maxAuditIdToClear); try { - loop { // Cleanup until succeed or facing unretriable error + while (true) { // Cleanup until succeed or facing unretriable error + Error err; try { - state std::vector auditStates = - wait(getAuditStates(cx, auditType, /*newFirst=*/false)); + std::vector auditStates = co_await getAuditStates(cx, auditType, /*newFirst=*/false); // auditStates has ascending order of auditIds // Read and clear are not atomic @@ -244,15 +246,15 @@ ACTOR Future clearAuditMetadataForType(Database cx, } // For a zombie audit, it is in running state } - wait(tr.commit()); + co_await tr.commit(); TraceEvent(SevDebug, "AuditUtilClearAuditMetadataForTypeEnd") .detail("AuditType", auditType) .detail("NumCleanedFinishAudits", numFinishAuditCleaned); break; - } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } catch (Error& e) { TraceEvent(SevInfo, "AuditUtilClearAuditMetadataForTypeError") @@ -260,25 +262,23 @@ ACTOR Future clearAuditMetadataForType(Database cx, .errorUnsuppressed(e); // We do not want audit cleanup effects DD } - - return Void(); } -ACTOR static Future checkMoveKeysLockForAudit(Transaction* tr, - MoveKeyLockInfo lock, - bool isDDEnabled, - bool isWrite = true) { +static Future checkMoveKeysLockForAudit(Transaction* tr, + MoveKeyLockInfo lock, + bool isDDEnabled, + bool isWrite = true) { tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); if (!isDDEnabled) { TraceEvent(SevDebug, "AuditUtilDisabledByInMemoryCheck").log(); throw movekeys_conflict(); // need a new name } - Optional readVal = wait(tr->get(moveKeysLockOwnerKey)); + Optional readVal = co_await tr->get(moveKeysLockOwnerKey); UID currentOwner = readVal.present() ? BinaryReader::fromStringRef(readVal.get(), Unversioned()) : UID(); if (currentOwner == lock.prevOwner) { // Check that the previous owner hasn't touched the lock since we took it - Optional readVal = wait(tr->get(moveKeysLockWriteKey)); + Optional readVal = co_await tr->get(moveKeysLockWriteKey); UID lastWrite = readVal.present() ? BinaryReader::fromStringRef(readVal.get(), Unversioned()) : UID(); if (lastWrite != lock.prevWrite) { TraceEvent(SevDebug, "ConflictWithPreviousOwner"); @@ -299,7 +299,7 @@ ACTOR static Future checkMoveKeysLockForAudit(Transaction* tr, .detail("MyOwner", lock.myOwner.toString()) .detail("Writer", lastWriter.toString()); } - return Void(); + co_return; } else if (currentOwner == lock.myOwner) { if (isWrite) { // Touch the lock, preventing overlapping attempts to take it @@ -309,7 +309,7 @@ ACTOR static Future checkMoveKeysLockForAudit(Transaction* tr, // Make this transaction self-conflicting so the database will not execute it twice with the same write key tr->makeSelfConflicting(); } - return Void(); + co_return; } else { TraceEvent(SevDebug, "AuditUtilConflictWithNewOwner") .detail("CurrentOwner", currentOwner.toString()) @@ -320,25 +320,23 @@ ACTOR static Future checkMoveKeysLockForAudit(Transaction* tr, } } -ACTOR Future persistNewAuditState(Database cx, - AuditStorageState auditState, - MoveKeyLockInfo lock, - bool ddEnabled) { +Future persistNewAuditState(Database cx, AuditStorageState auditState, MoveKeyLockInfo lock, bool ddEnabled) { ASSERT(!auditState.id.isValid()); - state Transaction tr(cx); - state UID auditId; - state AuditStorageState latestExistingAuditState; + Transaction tr(cx); + UID auditId; + AuditStorageState latestExistingAuditState; TraceEvent(SevDebug, "AuditUtilPersistedNewAuditStateStart", auditId); try { - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - wait(checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true)); + co_await checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true); RangeResult res = - wait(tr.getRange(auditKeyRange(auditState.getType()), 1, Snapshot::False, Reverse::True)); - ASSERT(res.size() == 0 || res.size() == 1); + co_await tr.getRange(auditKeyRange(auditState.getType()), 1, Snapshot::False, Reverse::True); + ASSERT(res.empty() || res.size() == 1); uint64_t nextId = 1; if (!res.empty()) { latestExistingAuditState = decodeAuditStorageState(res[0].value); @@ -348,13 +346,12 @@ ACTOR Future persistNewAuditState(Database cx, if (latestExistingAuditState.id.first() == auditId.first()) { // The new audit Id has been successfully persisted // No more action needed - return auditId; - } else { - // When latestExistingAuditState.id.first() < auditId - // The new audit Id is failed to persist - // Check to confirm no other actor can persist new audit state - ASSERT(auditId.first() == latestExistingAuditState.id.first() + 1); + co_return auditId; } + // When latestExistingAuditState.id.first() < auditId + // The new audit Id is failed to persist + // Check to confirm no other actor can persist new audit state + ASSERT(auditId.first() == latestExistingAuditState.id.first() + 1); } nextId = latestExistingAuditState.id.first() + 1; } @@ -363,16 +360,17 @@ ACTOR Future persistNewAuditState(Database cx, TraceEvent(SevVerbose, "AuditUtilPersistedNewAuditStateIdSelected", auditId) .detail("AuditKey", auditKey(auditState.getType(), auditId)); tr.set(auditKey(auditState.getType(), auditId), auditStorageStateValue(auditState)); - wait(tr.commit()); + co_await tr.commit(); TraceEvent(SevDebug, "AuditUtilPersistedNewAuditState", auditId) .detail("AuditKey", auditKey(auditState.getType(), auditId)); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilPersistedNewAuditStateError", auditId) - .errorUnsuppressed(e) - .detail("AuditKey", auditKey(auditState.getType(), auditId)); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilPersistedNewAuditStateError", auditId) + .errorUnsuppressed(err) + .detail("AuditKey", auditKey(auditState.getType(), auditId)); + co_await tr.onError(err); } } catch (Error& e) { TraceEvent(SevWarn, "AuditUtilPersistedNewAuditStateUnretriableError", auditId) @@ -381,35 +379,35 @@ ACTOR Future persistNewAuditState(Database cx, ASSERT_WE_THINK(e.code() == error_code_actor_cancelled || e.code() == error_code_movekeys_conflict); if (e.code() == error_code_actor_cancelled) { throw e; - } else { - throw persist_new_audit_metadata_error(); } + throw persist_new_audit_metadata_error(); } - return auditId; + co_return auditId; } -ACTOR Future persistAuditState(Database cx, - AuditStorageState auditState, - std::string context, - MoveKeyLockInfo lock, - bool ddEnabled) { - state Transaction tr(cx); - state AuditPhase auditPhase = auditState.getPhase(); +Future persistAuditState(Database cx, + AuditStorageState auditState, + std::string context, + MoveKeyLockInfo lock, + bool ddEnabled) { + Transaction tr(cx); + AuditPhase auditPhase = auditState.getPhase(); ASSERT(auditPhase == AuditPhase::Complete || auditPhase == AuditPhase::Failed || auditPhase == AuditPhase::Error); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - wait(checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true)); + co_await checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true); // Clear persistent progress data of the new audit if complete if (auditPhase == AuditPhase::Complete) { clearAuditProgressMetadata(&tr, auditState.getType(), auditState.id); } // We keep the progress metadata of Failed and Error audits for further investigations // Check existing state - Optional res_ = wait(tr.get(auditKey(auditState.getType(), auditState.id))); + Optional res_ = co_await tr.get(auditKey(auditState.getType(), auditState.id)); if (!res_.present()) { // has been cancelled throw audit_storage_cancelled(); } else { @@ -421,7 +419,7 @@ ACTOR Future persistAuditState(Database cx, } // Persist audit result tr.set(auditKey(auditState.getType(), auditState.id), auditStorageStateValue(auditState)); - wait(tr.commit()); + co_await tr.commit(); TraceEvent(SevInfo, "AuditUtilPersistAuditState", auditState.id) .detail("AuditID", auditState.id) .detail("AuditType", auditState.getType()) @@ -430,62 +428,63 @@ ACTOR Future persistAuditState(Database cx, .detail("Context", context); break; } catch (Error& e) { - TraceEvent(SevWarn, "AuditUtilPersistAuditStateError", auditState.id) - .errorUnsuppressed(e) - .detail("AuditID", auditState.id) - .detail("AuditType", auditState.getType()) - .detail("AuditPhase", auditPhase) - .detail("AuditKey", auditKey(auditState.getType(), auditState.id)) - .detail("Context", context); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevWarn, "AuditUtilPersistAuditStateError", auditState.id) + .errorUnsuppressed(err) + .detail("AuditID", auditState.id) + .detail("AuditType", auditState.getType()) + .detail("AuditPhase", auditPhase) + .detail("AuditKey", auditKey(auditState.getType(), auditState.id)) + .detail("Context", context); + co_await tr.onError(err); } - - return Void(); } -ACTOR Future getAuditState(Database cx, AuditType type, UID id) { - state Transaction tr(cx); - state Optional res; +Future getAuditState(Database cx, AuditType type, UID id) { + Transaction tr(cx); + Optional res; - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional res_ = wait(tr.get(auditKey(type, id))); - res = res_; + res = co_await tr.get(auditKey(type, id)); TraceEvent(SevDebug, "AuditUtilReadAuditState", id) .detail("AuditID", id) .detail("AuditType", type) .detail("AuditKey", auditKey(type, id)); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilReadAuditStateError", id) - .errorUnsuppressed(e) - .detail("AuditID", id) - .detail("AuditType", type) - .detail("AuditKey", auditKey(type, id)); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilReadAuditStateError", id) + .errorUnsuppressed(err) + .detail("AuditID", id) + .detail("AuditType", type) + .detail("AuditKey", auditKey(type, id)); + co_await tr.onError(err); } if (!res.present()) { throw key_not_found(); } - return decodeAuditStorageState(res.get()); + co_return decodeAuditStorageState(res.get()); } -ACTOR Future persistAuditStateByRange(Database cx, AuditStorageState auditState) { - state Transaction tr(cx); +Future persistAuditStateByRange(Database cx, AuditStorageState auditState) { + Transaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional ddAuditState_ = wait(tr.get(auditKey(auditState.getType(), auditState.id))); + Optional ddAuditState_ = co_await tr.get(auditKey(auditState.getType(), auditState.id)); if (!ddAuditState_.present()) { throw audit_storage_cancelled(); } @@ -507,48 +506,45 @@ ACTOR Future persistAuditStateByRange(Database cx, AuditStorageState audit if (ddAuditState.getPhase() == AuditPhase::Failed) { throw audit_storage_cancelled(); } - wait(krmSetRange(&tr, - auditRangeBasedProgressPrefixFor(auditState.getType(), auditState.id), - auditState.range, - auditStorageStateValue(auditState))); - wait(tr.commit()); + co_await krmSetRange(&tr, + auditRangeBasedProgressPrefixFor(auditState.getType(), auditState.id), + auditState.range, + auditStorageStateValue(auditState)); + co_await tr.commit(); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilPersistAuditStateByRangeError") - .errorUnsuppressed(e) - .detail("AuditID", auditState.id) - .detail("AuditType", auditState.getType()) - .detail("AuditPhase", auditState.getPhase()); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilPersistAuditStateByRangeError") + .errorUnsuppressed(err) + .detail("AuditID", auditState.id) + .detail("AuditType", auditState.getType()) + .detail("AuditPhase", auditState.getPhase()); + co_await tr.onError(err); } - - return Void(); } -ACTOR Future> getAuditStateByRange(Database cx, - AuditType type, - UID auditId, - KeyRange range) { - state RangeResult auditStates; - state Transaction tr(cx); +Future> getAuditStateByRange(Database cx, AuditType type, UID auditId, KeyRange range) { + RangeResult auditStates; + Transaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult res_ = wait(krmGetRanges(&tr, - auditRangeBasedProgressPrefixFor(type, auditId), - range, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES)); - auditStates = res_; + auditStates = co_await krmGetRanges(&tr, + auditRangeBasedProgressPrefixFor(type, auditId), + range, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilGetAuditStateForRangeError").errorUnsuppressed(e).detail("AuditID", auditId); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilGetAuditStateForRangeError").errorUnsuppressed(err).detail("AuditID", auditId); + co_await tr.onError(err); } // For a range of state that have value read from auditRangeBasedProgressPrefixFor @@ -567,18 +563,19 @@ ACTOR Future> getAuditStateByRange(Database cx, res.push_back(auditState); } - return res; + co_return res; } -ACTOR Future persistAuditStateByServer(Database cx, AuditStorageState auditState) { - state Transaction tr(cx); +Future persistAuditStateByServer(Database cx, AuditStorageState auditState) { + Transaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional ddAuditState_ = wait(tr.get(auditKey(auditState.getType(), auditState.id))); + Optional ddAuditState_ = co_await tr.get(auditKey(auditState.getType(), auditState.id)); if (!ddAuditState_.present()) { throw audit_storage_cancelled(); } @@ -600,55 +597,55 @@ ACTOR Future persistAuditStateByServer(Database cx, AuditStorageState audi if (ddAuditState.getPhase() == AuditPhase::Failed) { throw audit_storage_cancelled(); } - wait(krmSetRange( + co_await krmSetRange( &tr, auditServerBasedProgressPrefixFor(auditState.getType(), auditState.id, auditState.auditServerId), auditState.range, - auditStorageStateValue(auditState))); - wait(tr.commit()); + auditStorageStateValue(auditState)); + co_await tr.commit(); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilPersistAuditStateByRangeError") - .errorUnsuppressed(e) - .detail("AuditID", auditState.id) - .detail("AuditType", auditState.getType()) - .detail("AuditPhase", auditState.getPhase()) - .detail("AuditServerID", auditState.auditServerId); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilPersistAuditStateByRangeError") + .errorUnsuppressed(err) + .detail("AuditID", auditState.id) + .detail("AuditType", auditState.getType()) + .detail("AuditPhase", auditState.getPhase()) + .detail("AuditServerID", auditState.auditServerId); + co_await tr.onError(err); } - - return Void(); } -ACTOR Future> getAuditStateByServer(Database cx, - AuditType type, - UID auditId, - UID auditServerId, - KeyRange range) { - state RangeResult auditStates; - state Transaction tr(cx); +Future> getAuditStateByServer(Database cx, + AuditType type, + UID auditId, + UID auditServerId, + KeyRange range) { + RangeResult auditStates; + Transaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult res_ = wait(krmGetRanges(&tr, - auditServerBasedProgressPrefixFor(type, auditId, auditServerId), - range, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES)); - auditStates = res_; + auditStates = co_await krmGetRanges(&tr, + auditServerBasedProgressPrefixFor(type, auditId, auditServerId), + range, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); break; } catch (Error& e) { - TraceEvent(SevDebug, "AuditUtilGetAuditStateForRangeError") - .errorUnsuppressed(e) - .detail("AuditID", auditId) - .detail("AuditType", type) - .detail("AuditServerID", auditServerId); - wait(tr.onError(e)); + err = e; } + TraceEvent(SevDebug, "AuditUtilGetAuditStateForRangeError") + .errorUnsuppressed(err) + .detail("AuditID", auditId) + .detail("AuditType", type) + .detail("AuditServerID", auditServerId); + co_await tr.onError(err); } // For a range of state that have value read from auditServerBasedProgressPrefixFor @@ -667,24 +664,22 @@ ACTOR Future> getAuditStateByServer(Database cx, res.push_back(auditState); } - return res; + co_return res; } -ACTOR Future checkAuditProgressCompleteByRange(Database cx, - AuditType auditType, - UID auditId, - KeyRange auditRange) { +Future checkAuditProgressCompleteByRange(Database cx, AuditType auditType, UID auditId, KeyRange auditRange) { ASSERT(auditType == AuditType::ValidateHA || auditType == AuditType::ValidateReplica || auditType == AuditType::ValidateLocationMetadata || auditType == AuditType::ValidateRestore); - state KeyRange rangeToRead = auditRange; - state Key rangeToReadBegin = auditRange.begin; - state int retryCount = 0; + KeyRange rangeToRead = auditRange; + Key rangeToReadBegin = auditRange.begin; + int retryCount = 0; while (rangeToReadBegin < auditRange.end) { - loop { + while (true) { + Error err; try { rangeToRead = KeyRangeRef(rangeToReadBegin, auditRange.end); - state std::vector auditStates = - wait(getAuditStateByRange(cx, auditType, auditId, rangeToRead)); + std::vector auditStates = + co_await getAuditStateByRange(cx, auditType, auditId, rangeToRead); for (int i = 0; i < auditStates.size(); i++) { AuditPhase phase = auditStates[i].getPhase(); if (phase == AuditPhase::Invalid) { @@ -693,50 +688,52 @@ ACTOR Future checkAuditProgressCompleteByRange(Database cx, .detail("AuditRange", auditRange) .detail("AuditType", auditType) .detail("UnfinishedRange", auditStates[i].range); - return false; + co_return false; } } rangeToReadBegin = auditStates.back().range.end; break; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw e; - } - if (retryCount > 30) { - TraceEvent(SevWarn, "AuditUtilCheckAuditProgressFailed") - .detail("AuditID", auditId) - .detail("AuditRange", auditRange) - .detail("AuditType", auditType); - throw audit_storage_failed(); - } - wait(delay(0.5)); - retryCount++; + err = e; + } + if (err.code() == error_code_actor_cancelled) { + throw err; } + if (retryCount > 30) { + TraceEvent(SevWarn, "AuditUtilCheckAuditProgressFailed") + .detail("AuditID", auditId) + .detail("AuditRange", auditRange) + .detail("AuditType", auditType); + throw audit_storage_failed(); + } + co_await delay(0.5); + retryCount++; } } TraceEvent(SevInfo, "AuditUtilCheckAuditProgressFinish") .detail("AuditID", auditId) .detail("AuditRange", auditRange) .detail("AuditType", auditType); - return true; + co_return true; } -ACTOR Future checkAuditProgressCompleteByServer(Database cx, - AuditType auditType, - UID auditId, - KeyRange auditRange, - UID serverId, - std::shared_ptr> checkProgressBudget) { +Future checkAuditProgressCompleteByServer(Database cx, + AuditType auditType, + UID auditId, + KeyRange auditRange, + UID serverId, + std::shared_ptr> checkProgressBudget) { ASSERT(auditType == AuditType::ValidateStorageServerShard); - state KeyRange rangeToRead = auditRange; - state Key rangeToReadBegin = auditRange.begin; - state int retryCount = 0; + KeyRange rangeToRead = auditRange; + Key rangeToReadBegin = auditRange.begin; + int retryCount = 0; while (rangeToReadBegin < auditRange.end) { - loop { + while (true) { + Error err; try { rangeToRead = KeyRangeRef(rangeToReadBegin, auditRange.end); - state std::vector auditStates = - wait(getAuditStateByServer(cx, auditType, auditId, serverId, rangeToRead)); + std::vector auditStates = + co_await getAuditStateByServer(cx, auditType, auditId, serverId, rangeToRead); for (int i = 0; i < auditStates.size(); i++) { AuditPhase phase = auditStates[i].getPhase(); if (phase == AuditPhase::Invalid) { @@ -747,27 +744,28 @@ ACTOR Future checkAuditProgressCompleteByServer(Database cx, .detail("AuditType", auditType) .detail("UnfinishedRange", auditStates[i].range); checkProgressBudget->set(checkProgressBudget->get() + 1); - return false; + co_return false; } } rangeToReadBegin = auditStates.back().range.end; break; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw e; - } - if (retryCount > 30) { - TraceEvent(SevWarn, "AuditUtilCheckAuditProgressFailed") - .detail("ServerID", serverId) - .detail("AuditID", auditId) - .detail("AuditRange", auditRange) - .detail("AuditType", auditType); - checkProgressBudget->set(checkProgressBudget->get() + 1); - throw audit_storage_failed(); - } - wait(delay(0.5)); - retryCount++; + err = e; + } + if (err.code() == error_code_actor_cancelled) { + throw err; + } + if (retryCount > 30) { + TraceEvent(SevWarn, "AuditUtilCheckAuditProgressFailed") + .detail("ServerID", serverId) + .detail("AuditID", auditId) + .detail("AuditRange", auditRange) + .detail("AuditType", auditType); + checkProgressBudget->set(checkProgressBudget->get() + 1); + throw audit_storage_failed(); } + co_await delay(0.5); + retryCount++; } } checkProgressBudget->set(checkProgressBudget->get() + 1); @@ -776,21 +774,22 @@ ACTOR Future checkAuditProgressCompleteByServer(Database cx, .detail("AuditID", auditId) .detail("AuditRange", auditRange) .detail("AuditType", auditType); - return true; + co_return true; } // Load RUNNING audit states to resume, clean up COMPLETE and FAILED audit states // Update ddId for RUNNING audit states -ACTOR Future> initAuditMetadata(Database cx, - MoveKeyLockInfo lock, - bool ddEnabled, - UID dataDistributorId, - int persistFinishAuditCount) { - state std::unordered_map> existingAuditStates; - state std::vector auditStatesToResume; - state Transaction tr(cx); - state int retryCount = 0; - loop { +Future> initAuditMetadata(Database cx, + MoveKeyLockInfo lock, + bool ddEnabled, + UID dataDistributorId, + int persistFinishAuditCount) { + std::unordered_map> existingAuditStates; + std::vector auditStatesToResume; + Transaction tr(cx); + int retryCount = 0; + while (true) { + Error err; try { // Load existing audit states and update ddId in audit states existingAuditStates.clear(); @@ -798,8 +797,8 @@ ACTOR Future> initAuditMetadata(Database cx, tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - wait(checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true)); - RangeResult result = wait(tr.getRange(auditKeys, CLIENT_KNOBS->TOO_MANY)); + co_await checkMoveKeysLockForAudit(&tr, lock, ddEnabled, true); + RangeResult result = co_await tr.getRange(auditKeys, CLIENT_KNOBS->TOO_MANY); if (result.more || result.size() >= CLIENT_KNOBS->TOO_MANY) { TraceEvent(g_network->isSimulated() ? SevError : SevWarnAlways, "AuditUtilLoadMetadataIncomplete", @@ -807,8 +806,8 @@ ACTOR Future> initAuditMetadata(Database cx, .detail("ResMore", result.more) .detail("ResSize", result.size()); } - for (int i = 0; i < result.size(); ++i) { - auto auditState = decodeAuditStorageState(result[i].value); + for (const auto& auditKeyValue : result) { + auto auditState = decodeAuditStorageState(auditKeyValue.value); TraceEvent(SevVerbose, "AuditUtilLoadMetadataEach", dataDistributorId) .detail("CurrentDDID", dataDistributorId) .detail("AuditDDID", auditState.ddId) @@ -870,26 +869,27 @@ ACTOR Future> initAuditMetadata(Database cx, } } } - wait(tr.commit()); + co_await tr.commit(); break; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled || e.code() == error_code_movekeys_conflict) { - throw e; - } - if (retryCount > 50) { - TraceEvent(SevWarnAlways, "AuditUtilInitAuditMetadataExceedRetryMax", dataDistributorId) - .errorUnsuppressed(e); - break; - } - try { - wait(tr.onError(e)); - } catch (Error& e) { - retryCount++; - tr.reset(); - } + err = e; + } + if (err.code() == error_code_actor_cancelled || err.code() == error_code_movekeys_conflict) { + throw err; + } + if (retryCount > 50) { + TraceEvent(SevWarnAlways, "AuditUtilInitAuditMetadataExceedRetryMax", dataDistributorId) + .errorUnsuppressed(err); + break; + } + try { + co_await tr.onError(err); + } catch (Error&) { + retryCount++; + tr.reset(); } } - return auditStatesToResume; + co_return auditStatesToResume; } // Check if any pair of ranges are exclusive with each other @@ -1007,22 +1007,146 @@ Optional> rangesSame(std::vector rangesA return Optional>(); } +std::vector checkLocationMetadataConsistency( + const std::unordered_map>& mapFromKeyServers, + const std::unordered_map>& mapFromServerKeys, + KeyRange claimRange) { + std::vector errors; + // Check mapFromKeyServers => mapFromServerKeys + for (const auto& [ssid, keyServerRanges] : mapFromKeyServers) { + auto it = mapFromServerKeys.find(ssid); + if (it == mapFromServerKeys.end()) { + LocationMetadataError err; + err.serverId = ssid; + err.message = format("KeyServers and serverKeys mismatch: Some key in range(%s, %s) exists " + "on Server(%s) in KeyServers but not ServerKeys", + claimRange.begin.printable().c_str(), + claimRange.end.printable().c_str(), + ssid.toString().c_str()); + errors.push_back(std::move(err)); + continue; + } + Optional> anyMismatch = rangesSame(keyServerRanges, it->second); + if (anyMismatch.present()) { + LocationMetadataError err; + err.serverId = ssid; + err.mismatchedRangeByKeyServer = anyMismatch.get().first; + err.mismatchedRangeByServerKey = anyMismatch.get().second; + err.message = format("KeyServers and serverKeys mismatch on Server(%s): KeyServer: %s; ServerKey: %s", + ssid.toString().c_str(), + anyMismatch.get().first.toString().c_str(), + anyMismatch.get().second.toString().c_str()); + errors.push_back(std::move(err)); + } + } + // Check mapFromServerKeys => mapFromKeyServers + for (const auto& [ssid, serverKeyRanges] : mapFromServerKeys) { + if (!mapFromKeyServers.contains(ssid)) { + LocationMetadataError err; + err.serverId = ssid; + err.message = format("KeyServers and serverKeys mismatch: Some key of range(%s, %s) exists " + "on Server(%s) in ServerKeys but not KeyServers", + claimRange.begin.printable().c_str(), + claimRange.end.printable().c_str(), + ssid.toString().c_str()); + errors.push_back(std::move(err)); + } + } + return errors; +} + +LocationMetadataMaps buildLocationMetadataMaps( + const std::unordered_map>& mapFromKeyServersRaw, + const std::unordered_map>& serverOwnRangesMap, + KeyRange claimRange, + Optional traceId) { + LocationMetadataMaps result; + for (const auto& [ssid, ownRanges] : serverOwnRangesMap) { + for (const auto& range : ownRanges) { + KeyRange overlappingRange = range & claimRange; + if (overlappingRange.empty()) { + continue; + } + if (traceId.present()) { + TraceEvent(SevVerbose, "DDAuditLocationMetadataAddToServerKeyMap", traceId.get()) + .detail("RawRange", range) + .detail("ClaimRange", claimRange) + .detail("Range", overlappingRange) + .detail("SSID", ssid); + } + result.fromServerKeys[ssid].push_back(overlappingRange); + result.numValidatedServerKeys++; + } + } + for (const auto& [ssid, ranges] : mapFromKeyServersRaw) { + std::vector mergedRanges = coalesceRangeList(ranges); + for (const auto& range : mergedRanges) { + KeyRange overlappingRange = range & claimRange; + if (overlappingRange.empty()) { + continue; + } + if (traceId.present()) { + TraceEvent(SevVerbose, "DDAuditLocationMetadataAddToKeyServerMap", traceId.get()) + .detail("RawRange", range) + .detail("ClaimRange", claimRange) + .detail("Range", overlappingRange) + .detail("SSID", ssid); + } + result.fromKeyServers[ssid].push_back(overlappingRange); + result.numValidatedKeyServers++; + } + } + return result; +} + +std::vector buildOwnRangesFromServerKeysResult(const RangeResult& krmResult) { + std::vector ownRanges; + for (int i = 0; i < krmResult.size() - 1; ++i) { + if (serverHasKey(krmResult[i].value)) { + ownRanges.push_back(Standalone(KeyRangeRef(krmResult[i].key, krmResult[i + 1].key))); + } + } + return ownRanges; +} + +// src and dest UIDs from decodeKeyServersValue are expected to be sorted by the encoder +KeyServersOwnershipMap buildOwnershipMapFromKeyServersResult(const RangeResult& krmResult, + const RangeResult& uidToTagMap) { + KeyServersOwnershipMap result; + for (int i = 0; i < krmResult.size() - 1; ++i) { + std::vector src; + std::vector dest; + UID srcID; + UID destID; + decodeKeyServersValue(uidToTagMap, krmResult[i].value, src, dest, srcID, destID); + if (srcID == anonymousShardId) { + result.shardsInAnonymousPhysicalShardCount++; + } + result.totalShardsCount++; + std::vector servers(src.size() + dest.size()); + std::merge(src.begin(), src.end(), dest.begin(), dest.end(), servers.begin()); + for (auto& ssid : servers) { + result.rangeOwnershipMap[ssid].push_back(Standalone(KeyRangeRef(krmResult[i].key, krmResult[i + 1].key))); + } + } + return result; +} + // Given an input server, get ranges within the input range via the input transaction // from the perspective of ServerKeys system key space // Input: (1) SS id; (2) transaction tr; (3) within range // Return AuditGetServerKeysRes, including: (1) complete range by a single read range; // (2) version of the read; (3) ranges of the input SS -ACTOR Future getThisServerKeysFromServerKeys(UID serverID, Transaction* tr, KeyRange range) { - state RangeResult readResult; - state AuditGetServerKeysRes res; +Future getThisServerKeysFromServerKeys(UID serverID, Transaction* tr, KeyRange range) { + RangeResult readResult; + AuditGetServerKeysRes res; try { - wait(store(readResult, - krmGetRanges(tr, - serverKeysPrefixFor(serverID), - range, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES))); + readResult = co_await krmGetRanges(tr, + serverKeysPrefixFor(serverID), + range, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); Future grvF = tr->getReadVersion(); if (!grvF.isReady()) { TraceEvent(SevWarnAlways, "AuditUtilReadServerKeysGRVError", serverID); @@ -1036,17 +1160,13 @@ ACTOR Future getThisServerKeysFromServerKeys(UID serverID .detail("ResultSize", readResult.size()) .detail("AuditServerID", serverID); - std::vector ownRanges; + std::vector ownRanges = buildOwnRangesFromServerKeysResult(readResult); for (int i = 0; i < readResult.size() - 1; ++i) { TraceEvent(SevVerbose, "AuditUtilGetThisServerKeysFromServerKeysAddToResult", serverID) .detail("ValueIsServerKeysFalse", readResult[i].value == serverKeysFalse) .detail("ServerHasKey", serverHasKey(readResult[i].value)) .detail("Range", KeyRangeRef(readResult[i].key, readResult[i + 1].key)) .detail("AuditServerID", serverID); - if (serverHasKey(readResult[i].value)) { - KeyRange shardRange; - ownRanges.push_back(Standalone(KeyRangeRef(readResult[i].key, readResult[i + 1].key))); - } } const KeyRange completeRange = Standalone(KeyRangeRef(range.begin, readResult.back().key)); TraceEvent(SevVerbose, "AuditUtilGetThisServerKeysFromServerKeysEnd", serverID) @@ -1065,7 +1185,7 @@ ACTOR Future getThisServerKeysFromServerKeys(UID serverID throw e; } - return res; + co_return res; } // Given an input server, get ranges within the input range via the input transaction @@ -1073,13 +1193,11 @@ ACTOR Future getThisServerKeysFromServerKeys(UID serverID // Input: (1) Audit Server ID (for logging); (2) transaction tr; (3) within range // Return AuditGetKeyServersRes, including : (1) complete range by a single read range; (2) version of the read; // (3) map between SSes and their ranges --- in KeyServers space, a range corresponds to multiple SSes -ACTOR Future getShardMapFromKeyServers(UID auditServerId, Transaction* tr, KeyRange range) { - state AuditGetKeyServersRes res; - state std::vector> actors; - state RangeResult readResult; - state RangeResult UIDtoTagMap; - state int64_t totalShardsCount = 0; - state int64_t shardsInAnonymousPhysicalShardCount = 0; +Future getShardMapFromKeyServers(UID auditServerId, Transaction* tr, KeyRange range) { + AuditGetKeyServersRes res; + std::vector> actors; + RangeResult readResult; + RangeResult UIDtoTagMap; try { // read @@ -1090,7 +1208,7 @@ ACTOR Future getShardMapFromKeyServers(UID auditServerId, CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES))); actors.push_back(store(UIDtoTagMap, tr->getRange(serverTagKeys, CLIENT_KNOBS->TOO_MANY))); - wait(waitForAll(actors)); + co_await waitForAll(actors); if (UIDtoTagMap.more || UIDtoTagMap.size() >= CLIENT_KNOBS->TOO_MANY) { TraceEvent(g_network->isSimulated() ? SevError : SevWarnAlways, "AuditUtilReadKeyServersReadTagError", @@ -1110,31 +1228,16 @@ ACTOR Future getShardMapFromKeyServers(UID auditServerId, .detail("AuditServerID", auditServerId); // produce result - std::unordered_map> serverOwnRanges; - for (int i = 0; i < readResult.size() - 1; ++i) { - std::vector src; - std::vector dest; - UID srcID; - UID destID; - decodeKeyServersValue(UIDtoTagMap, readResult[i].value, src, dest, srcID, destID); - if (srcID == anonymousShardId) { - shardsInAnonymousPhysicalShardCount++; - } - totalShardsCount++; - std::vector servers(src.size() + dest.size()); - std::merge(src.begin(), src.end(), dest.begin(), dest.end(), servers.begin()); - for (auto& ssid : servers) { - serverOwnRanges[ssid].push_back(Standalone(KeyRangeRef(readResult[i].key, readResult[i + 1].key))); - } - } + KeyServersOwnershipMap ownershipResult = buildOwnershipMapFromKeyServersResult(readResult, UIDtoTagMap); const KeyRange completeRange = Standalone(KeyRangeRef(range.begin, readResult.back().key)); TraceEvent(SevInfo, "AuditUtilGetThisServerKeysFromKeyServersEnd", auditServerId) .detail("Range", range) .detail("CompleteRange", completeRange) .detail("AtVersion", readAtVersion) - .detail("ShardsInAnonymousPhysicalShardCount", shardsInAnonymousPhysicalShardCount) - .detail("TotalShardsCount", totalShardsCount); - res = AuditGetKeyServersRes(completeRange, readAtVersion, serverOwnRanges, readResult.logicalSize()); + .detail("ShardsInAnonymousPhysicalShardCount", ownershipResult.shardsInAnonymousPhysicalShardCount) + .detail("TotalShardsCount", ownershipResult.totalShardsCount); + res = AuditGetKeyServersRes( + completeRange, readAtVersion, std::move(ownershipResult.rangeOwnershipMap), readResult.logicalSize()); } catch (Error& e) { TraceEvent(SevDebug, "AuditUtilGetThisServerKeysFromKeyServersError", auditServerId) @@ -1143,5 +1246,5 @@ ACTOR Future getShardMapFromKeyServers(UID auditServerId, throw e; } - return res; + co_return res; } diff --git a/fdbclient/BackupAgentBase.actor.cpp b/fdbclient/BackupAgentBase.cpp similarity index 74% rename from fdbclient/BackupAgentBase.actor.cpp rename to fdbclient/BackupAgentBase.cpp index 3b9a832194e..e5018393db8 100644 --- a/fdbclient/BackupAgentBase.actor.cpp +++ b/fdbclient/BackupAgentBase.cpp @@ -1,5 +1,5 @@ /* - * BackupAgentBase.actor.cpp + * BackupAgentBase.cpp * * This source file is part of the FoundationDB open source project * @@ -21,20 +21,18 @@ #include #include -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/CommitProxyInterface.h" #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/DatabaseContext.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/SystemData.h" #include "fdbrpc/simulator.h" #include "flow/ActorCollection.h" #include "flow/DeterministicRandom.h" #include "flow/network.h" -#include "flow/actorcompiler.h" // has to be last include - std::string BackupAgentBase::formatTime(int64_t epochs) { time_t curTime = (time_t)epochs; char buffer[30]; @@ -279,19 +277,19 @@ void _addResult(VectorRef* result, int* mutationSize, Arena* arena, "result" vector. Each `value` is a param2 */ -ACTOR static Future decodeBackupLogValue(Arena* arena, - VectorRef* result, - int* mutationSize, - Standalone value, - Key addPrefix, - Key removePrefix, - Version version, - Reference> key_version, - Database cx, - bool provisionalProxy, - std::shared_ptr dbConfig) { +static Future decodeBackupLogValue(Arena* arena, + VectorRef* result, + int* mutationSize, + Standalone value, + Key addPrefix, + Key removePrefix, + Version version, + Reference> key_version, + Database cx, + bool provisionalProxy, + std::shared_ptr dbConfig) { try { - state uint64_t offset(0); + uint64_t offset(0); uint64_t protocolVersion = 0; memcpy(&protocolVersion, value.begin(), sizeof(uint64_t)); offset += sizeof(uint64_t); @@ -303,10 +301,10 @@ ACTOR static Future decodeBackupLogValue(Arena* arena, throw incompatible_protocol_version(); } - state uint32_t totalBytes = 0; + uint32_t totalBytes = 0; memcpy(&totalBytes, value.begin() + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); - state uint32_t consumed = 0; + uint32_t consumed = 0; if (totalBytes + offset > value.size()) { TraceEvent(SevError, "OffsetOutOfBoundary") @@ -317,25 +315,25 @@ ACTOR static Future decodeBackupLogValue(Arena* arena, throw restore_missing_data(); } - state int originalOffset = offset; + int originalOffset = offset; while (consumed < totalBytes) { uint32_t type = 0; // encoding format: type|kLen|vLen|Key|Value memcpy(&type, value.begin() + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); - state uint32_t len1 = 0; + uint32_t len1 = 0; memcpy(&len1, value.begin() + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); - state uint32_t len2 = 0; + uint32_t len2 = 0; memcpy(&len2, value.begin() + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); ASSERT(offset + len1 + len2 <= value.size() && isValidMutationType(type)); // Construct MutationRef from StringRef - state MutationRef logValue; - state Arena tempArena; + MutationRef logValue; + Arena tempArena; logValue.type = type; logValue.param1 = value.substr(offset, len1); offset += len1; @@ -348,24 +346,24 @@ ACTOR static Future decodeBackupLogValue(Arena* arena, for (auto r : ranges) { if (version > r.value() && r.value() != invalidVersion) { KeyRef minKey = std::min(r.range().end, range.end); - if (minKey == (removePrefix == StringRef() ? allKeys.end : strinc(removePrefix))) { + if (minKey == (removePrefix.empty() ? allKeys.end : strinc(removePrefix))) { logValue.param1 = std::max(r.range().begin, range.begin); - if (removePrefix.size()) { + if (!removePrefix.empty()) { logValue.param1 = logValue.param1.removePrefix(removePrefix); } - if (addPrefix.size()) { + if (!addPrefix.empty()) { logValue.param1 = logValue.param1.withPrefix(addPrefix, tempArena); } - logValue.param2 = addPrefix == StringRef() ? allKeys.end : strinc(addPrefix, tempArena); + logValue.param2 = addPrefix.empty() ? allKeys.end : strinc(addPrefix, tempArena); _addResult(result, mutationSize, arena, logValue); } else { logValue.param1 = std::max(r.range().begin, range.begin); logValue.param2 = minKey; - if (removePrefix.size()) { + if (!removePrefix.empty()) { logValue.param1 = logValue.param1.removePrefix(removePrefix); logValue.param2 = logValue.param2.removePrefix(removePrefix); } - if (addPrefix.size()) { + if (!addPrefix.empty()) { logValue.param1 = logValue.param1.withPrefix(addPrefix, tempArena); logValue.param2 = logValue.param2.withPrefix(addPrefix, tempArena); } @@ -380,10 +378,10 @@ ACTOR static Future decodeBackupLogValue(Arena* arena, // ver: the old version stored in keyVersionMap // as a result, only add this mutation in log when the version is larger(to work with range file) if (version > ver && ver != invalidVersion) { - if (removePrefix.size()) { + if (!removePrefix.empty()) { logValue.param1 = logValue.param1.removePrefix(removePrefix); } - if (addPrefix.size()) { + if (!addPrefix.empty()) { logValue.param1 = logValue.param1.withPrefix(addPrefix, tempArena); } _addResult(result, mutationSize, arena, logValue); @@ -438,33 +436,35 @@ Future logError(Reference tr, Key keyErrors, co return logError(tr->getDatabase(), keyErrors, message); } -ACTOR Future readCommitted(Database cx, - PromiseStream results, - Reference lock, - KeyRangeRef range, - Terminator terminator, - AccessSystemKeys systemAccess, - LockAware lockAware, - ReadLowPriority readLowPriority) { - state KeySelector begin = firstGreaterOrEqual(range.begin); - state KeySelector end = firstGreaterOrEqual(range.end); - state Transaction tr(cx); - state FlowLock::Releaser releaser; - - loop { +Future readCommitted(Database cx, + PromiseStream results, + Reference lock, + KeyRangeRef range, + Terminator terminator, + AccessSystemKeys systemAccess, + LockAware lockAware, + ReadLowPriority readLowPriority) { + KeySelector begin = firstGreaterOrEqual(range.begin); + KeySelector end = firstGreaterOrEqual(range.end); + Transaction tr(cx); + FlowLock::Releaser releaser; + + while (true) { + Error err; try { - state GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, - (g_network->isSimulated() && !g_simulator->speedUpSimulation) - ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES - : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); + GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, + (g_network->isSimulated() && !g_simulator->speedUpSimulation) + ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES + : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); if (systemAccess) - tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); if (lockAware) - tr.setOption(FDBTransactionOptions::LOCK_AWARE); + tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); if (readLowPriority) { tr.setOption(FDBTransactionOptions::READ_PRIORITY_LOW); } + tr.setOption(FDBTransactionOptions::READ_SERVER_SIDE_CACHE_DISABLE); if (CLIENT_KNOBS->ENABLE_REPLICA_CONSISTENCY_CHECK_ON_BACKUP_READS) { tr.setOption(FDBTransactionOptions::ENABLE_REPLICA_CONSISTENCY_CHECK); int64_t requiredReplicas = CLIENT_KNOBS->BACKUP_CONSISTENCY_CHECK_REQUIRED_REPLICAS; @@ -474,12 +474,12 @@ ACTOR Future readCommitted(Database cx, // add lock releaser.release(); - wait(lock->take(TaskPriority::DefaultYield, - limits.bytes + CLIENT_KNOBS->VALUE_SIZE_LIMIT + CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT)); + co_await lock->take(TaskPriority::DefaultYield, + limits.bytes + CLIENT_KNOBS->VALUE_SIZE_LIMIT + CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT); releaser = FlowLock::Releaser( *lock, limits.bytes + CLIENT_KNOBS->VALUE_SIZE_LIMIT + CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT); - state RangeResult values = wait(tr.getRange(begin, end, limits)); + RangeResult values = co_await tr.getRange(begin, end, limits); // When this buggify line is enabled, if there are more than 1 result then use half of the results // Copy the data instead of messing with the results directly to avoid TSS issues. @@ -493,7 +493,7 @@ ACTOR Future readCommitted(Database cx, values.more = true; // Half of the time wait for this tr to expire so that the next read is at a different version if (deterministicRandom()->random01() < 0.5) - wait(delay(6.0)); + co_await delay(6.0); } releaser.remaining -= @@ -502,51 +502,56 @@ ACTOR Future readCommitted(Database cx, results.send(RangeResultWithVersion(values, tr.getReadVersion().get())); - if (values.size() > 0) + if (!values.empty()) begin = firstGreaterThan(values.end()[-1].key); if (!values.more && !limits.isReached()) { if (terminator) results.sendError(end_of_stream()); - return Void(); + co_return; } + + continue; } catch (Error& e) { - if (e.code() == error_code_transaction_too_old) { - // We are using this transaction until it's too old and then resetting to a fresh one, - // so we don't need to delay. - tr.fullReset(); - } else { - wait(tr.onError(e)); - } + err = e; + } + + if (err.code() == error_code_transaction_too_old) { + // We are using this transaction until it's too old and then resetting to a fresh one, + // so we don't need to delay. + tr.fullReset(); + } else { + co_await tr.onError(err); } } } // read each version, potentially multiple part within the same version -ACTOR Future readCommitted(Database cx, - PromiseStream results, - Future active, - Reference lock, - KeyRangeRef range, - std::function(Key key)> groupBy, - Terminator terminator, - AccessSystemKeys systemAccess, - LockAware lockAware, - ReadLowPriority readLowPriority) { - state KeySelector nextKey = firstGreaterOrEqual(range.begin); - state KeySelector end = firstGreaterOrEqual(range.end); - - state RCGroup rcGroup = RCGroup(); - state uint64_t skipGroup(ULLONG_MAX); - state Transaction tr(cx); - state FlowLock::Releaser releaser; - - loop { +Future readCommitted(Database cx, + PromiseStream results, + Future active, + Reference lock, + KeyRangeRef range, + std::function(Key key)> groupBy, + Terminator terminator, + AccessSystemKeys systemAccess, + LockAware lockAware, + ReadLowPriority readLowPriority) { + KeySelector nextKey = firstGreaterOrEqual(range.begin); + KeySelector end = firstGreaterOrEqual(range.end); + + RCGroup rcGroup = RCGroup(); + uint64_t skipGroup(ULLONG_MAX); + Transaction tr(cx); + FlowLock::Releaser releaser; + + while (true) { + Error err; try { - state GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, - (g_network->isSimulated() && !g_simulator->speedUpSimulation) - ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES - : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); + GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, + (g_network->isSimulated() && !g_simulator->speedUpSimulation) + ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES + : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); if (systemAccess) tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); @@ -555,8 +560,9 @@ ACTOR Future readCommitted(Database cx, if (readLowPriority) { tr.setOption(FDBTransactionOptions::READ_PRIORITY_LOW); } + tr.setOption(FDBTransactionOptions::READ_SERVER_SIDE_CACHE_DISABLE); - state RangeResult rangevalue = wait(tr.getRange(nextKey, end, limits)); + RangeResult rangevalue = co_await tr.getRange(nextKey, end, limits); // When this buggify line is enabled, if there are more than 1 result then use half of the results. // Copy the data instead of messing with the results directly to avoid TSS issues. @@ -570,13 +576,13 @@ ACTOR Future readCommitted(Database cx, rangevalue.more = true; // Some of the time wait for this tr to expire so that the next read is at a different version if (deterministicRandom()->random01() < 0.01) - wait(delay(6.0)); + co_await delay(6.0); } // add lock - wait(active); + co_await active; releaser.release(); - wait(lock->take(TaskPriority::DefaultYield, rangevalue.expectedSize() + rcGroup.items.expectedSize())); + co_await lock->take(TaskPriority::DefaultYield, rangevalue.expectedSize() + rcGroup.items.expectedSize()); releaser = FlowLock::Releaser(*lock, rangevalue.expectedSize() + rcGroup.items.expectedSize()); // iterate on a version range, each key-value pair is (version, part) @@ -632,18 +638,21 @@ ACTOR Future readCommitted(Database cx, if (terminator) results.sendError(end_of_stream()); - return Void(); + co_return; } nextKey = firstGreaterThan(rangevalue.end()[-1].key); + continue; } catch (Error& e) { - if (e.code() == error_code_transaction_too_old) { - // We are using this transaction until it's too old and then resetting to a fresh one, - // so we don't need to delay. - tr.fullReset(); - } else { - wait(tr.onError(e)); - } + err = e; + } + + if (err.code() == error_code_transaction_too_old) { + // We are using this transaction until it's too old and then resetting to a fresh one, + // so we don't need to delay. + tr.fullReset(); + } else { + co_await tr.onError(err); } } } @@ -668,15 +677,15 @@ Future readCommitted(Database cx, // restore transaction has to be first in the batch, or it is the only txn in batch to make sure it never conflicts with // others. -ACTOR Future sendCommitTransactionRequest(CommitTransactionRequest req, - Key uid, - Version newBeginVersion, - Key rangeBegin, - NotifiedVersion* committedVersion, - int* totalBytes, - int* mutationSize, - FlowLock* commitLock, - PublicRequestStream commit) { +Future sendCommitTransactionRequest(CommitTransactionRequest req, + Key uid, + Version newBeginVersion, + Key rangeBegin, + NotifiedVersion* committedVersion, + int* totalBytes, + int* mutationSize, + FlowLock* commitLock, + PublicRequestStream commit) { Key applyBegin = uid.withPrefix(applyMutationsBeginRange.begin); Key versionKey = BinaryWriter::toValue(newBeginVersion, Unversioned()); Key rangeEnd = getApplyKey(newBeginVersion, uid); @@ -694,7 +703,7 @@ ACTOR Future sendCommitTransactionRequest(CommitTransactionRequest req, req.flags = req.flags | CommitTransactionRequest::FLAG_IS_LOCK_AWARE; *totalBytes += *mutationSize; - wait(commitLock->take(TaskPriority::DefaultYield, *mutationSize)); + co_await commitLock->take(TaskPriority::DefaultYield, *mutationSize); Future commitAndUnlock = commitLock->releaseWhen(success(commit.getReply(req)), *mutationSize); // NOTE: tenant has been removed, so revisit the following comment and possibly @@ -704,41 +713,40 @@ ACTOR Future sendCommitTransactionRequest(CommitTransactionRequest req, // Because we are bumping applyBegin version, we need to wait for the commit to be done. // Otherwise, an update to the applyEnd key will trigger another applyMutation() which can // have an overlapping range with the current applyMutation() and cause conflicts. - wait(commitAndUnlock); - return Void(); + co_await commitAndUnlock; } // Decodes the backup mutation log and send the mutations to the CommitProxy. // The mutation logs are grouped by version and passed in as a stream of RCGroup from readCommitted(). // The mutations are then decoded and sent to the CommitProxy in a batch. -ACTOR Future kvMutationLogToTransactions(Database cx, - PromiseStream results, - Reference lock, - Key uid, - Key addPrefix, - Key removePrefix, - PublicRequestStream commit, - NotifiedVersion* committedVersion, - Optional endVersion, - Key rangeBegin, - FlowLock* commitLock, - Reference> keyVersion, - bool provisionalProxy, - std::shared_ptr dbConfig) { - state Version lastVersion = invalidVersion; - state bool endOfStream = false; - state int totalBytes = 0; +Future kvMutationLogToTransactions(Database cx, + PromiseStream results, + Reference lock, + Key uid, + Key addPrefix, + Key removePrefix, + PublicRequestStream commit, + NotifiedVersion* committedVersion, + Optional endVersion, + Key rangeBegin, + FlowLock* commitLock, + Reference> keyVersion, + bool provisionalProxy, + std::shared_ptr dbConfig) { + Version lastVersion = invalidVersion; + bool endOfStream = false; + int totalBytes = 0; // outer loop to batch multiple versions while inner loop for each version - loop { - state CommitTransactionRequest req; - state Version newBeginVersion = invalidVersion; - state int mutationSize = 0; - loop { + while (true) { + CommitTransactionRequest req; + Version newBeginVersion = invalidVersion; + int mutationSize = 0; + while (true) { try { - state RCGroup group = waitNext(results.getFuture()); - state CommitTransactionRequest curReq; + RCGroup group = co_await results.getFuture(); + CommitTransactionRequest curReq; lock->release(group.items.expectedSize()); - state int curBatchMutationSize = 0; + int curBatchMutationSize = 0; BinaryWriter bw(Unversioned()); @@ -749,17 +757,17 @@ ACTOR Future kvMutationLogToTransactions(Database cx, } // Parse a single transaction from the backup mutation log Standalone value = bw.toValue(); - wait(decodeBackupLogValue(&curReq.arena, - &curReq.transaction.mutations, - &curBatchMutationSize, - value, - addPrefix, - removePrefix, - group.groupKey, - keyVersion, - cx, - provisionalProxy, - dbConfig)); + co_await decodeBackupLogValue(&curReq.arena, + &curReq.transaction.mutations, + &curBatchMutationSize, + value, + addPrefix, + removePrefix, + group.groupKey, + keyVersion, + cx, + provisionalProxy, + dbConfig); for (int i = 0; i < curReq.transaction.mutations.size(); i++) { req.transaction.mutations.push_back_deep(req.arena, curReq.transaction.mutations[i]); @@ -772,7 +780,7 @@ ACTOR Future kvMutationLogToTransactions(Database cx, newBeginVersion = endVersion.get(); } if (newBeginVersion == invalidVersion) - return totalBytes; + co_return totalBytes; endOfStream = true; break; } @@ -780,25 +788,25 @@ ACTOR Future kvMutationLogToTransactions(Database cx, } } // TraceEvent("MutationLogRestore").detail("BeginVersion", newBeginVersion); - wait(sendCommitTransactionRequest( - req, uid, newBeginVersion, rangeBegin, committedVersion, &totalBytes, &mutationSize, commitLock, commit)); + co_await sendCommitTransactionRequest( + req, uid, newBeginVersion, rangeBegin, committedVersion, &totalBytes, &mutationSize, commitLock, commit); if (endOfStream) { - return totalBytes; + co_return totalBytes; } } } -ACTOR Future coalesceKeyVersionCache(Key uid, - Version endVersion, - Reference> keyVersion, - PublicRequestStream commit, - NotifiedVersion* committedVersion, - PromiseStream> addActor, - FlowLock* commitLock) { +Future coalesceKeyVersionCache(Key uid, + Version endVersion, + Reference> keyVersion, + PublicRequestStream commit, + NotifiedVersion* committedVersion, + PromiseStream> addActor, + FlowLock* commitLock) { Version lastVersion = -1000; int64_t removed = 0; - state CommitTransactionRequest req; - state int64_t mutationSize = 0; + CommitTransactionRequest req; + int64_t mutationSize = 0; Key mapPrefix = uid.withPrefix(applyMutationsKeyVersionMapRange.begin); for (auto it : keyVersion->ranges()) { @@ -834,58 +842,56 @@ ACTOR Future coalesceKeyVersionCache(Key uid, req.transaction.read_snapshot = committedVersion->get(); req.flags = req.flags | CommitTransactionRequest::FLAG_IS_LOCK_AWARE; - wait(commitLock->take(TaskPriority::DefaultYield, mutationSize)); + co_await commitLock->take(TaskPriority::DefaultYield, mutationSize); addActor.send(commitLock->releaseWhen(success(commit.getReply(req)), mutationSize)); } - - return Void(); } -ACTOR Future applyMutations(Database cx, - Key uid, - Key addPrefix, - Key removePrefix, - Version beginVersion, - Version* endVersion, - PublicRequestStream commit, - NotifiedVersion* committedVersion, - Reference> keyVersion, - bool provisionalProxy) { - state FlowLock commitLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES); - state PromiseStream> addActor; - state Future error = actorCollection(addActor.getFuture()); - state int maxBytes = CLIENT_KNOBS->APPLY_MIN_LOCK_BYTES; - state std::shared_ptr dbConfig = std::make_shared(); +Future applyMutations(Database cx, + Key uid, + Key addPrefix, + Key removePrefix, + Version beginVersion, + Version* endVersion, + PublicRequestStream commit, + NotifiedVersion* committedVersion, + Reference> keyVersion, + bool provisionalProxy) { + FlowLock commitLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES); + PromiseStream> addActor; + Future error = actorCollection(addActor.getFuture()); + int maxBytes = CLIENT_KNOBS->APPLY_MIN_LOCK_BYTES; + std::shared_ptr dbConfig = std::make_shared(); keyVersion->insert(metadataVersionKey, 0); try { - wait(store(*dbConfig, getDatabaseConfiguration(cx))); + *dbConfig = co_await getDatabaseConfiguration(cx); - loop { + while (true) { if (beginVersion >= *endVersion) { // Why do we need to take a lock here? - wait(commitLock.take(TaskPriority::DefaultYield, CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + co_await commitLock.take(TaskPriority::DefaultYield, CLIENT_KNOBS->BACKUP_LOCK_BYTES); commitLock.release(CLIENT_KNOBS->BACKUP_LOCK_BYTES); if (beginVersion >= *endVersion) { - return Void(); + co_return; } } int rangeCount = std::max(1, CLIENT_KNOBS->APPLY_MAX_LOCK_BYTES / maxBytes); // newEndVersion can only be at most of size APPLY_BLOCK_SIZE - state Version newEndVersion = std::min(*endVersion, - ((beginVersion / CLIENT_KNOBS->APPLY_BLOCK_SIZE) + rangeCount) * - CLIENT_KNOBS->APPLY_BLOCK_SIZE); + Version newEndVersion = std::min(*endVersion, + ((beginVersion / CLIENT_KNOBS->APPLY_BLOCK_SIZE) + rangeCount) * + CLIENT_KNOBS->APPLY_BLOCK_SIZE); // ranges each represent a partition of version, e.g. [100, 200], [201, 300], [301, 400] // (64, 200) -> [(64, 128), (128, 192), (192, 200)] assuming block size is 64 // ranges have format: applyLogKeys.begin/uid/hash(uint8)/version(64bites)/part - state Standalone> ranges = getApplyRanges(beginVersion, newEndVersion, uid); - state size_t idx; - state std::vector> results; - state std::vector> rc; - state std::vector> locks; + Standalone> ranges = getApplyRanges(beginVersion, newEndVersion, uid); + size_t idx{ 0 }; + std::vector> results; + std::vector> rc; + std::vector> locks; // each RCGroup is for a single version, each results[i] is for a single range who can have multiple // versions @@ -900,30 +906,30 @@ ACTOR Future applyMutations(Database cx, maxBytes = std::max(maxBytes * CLIENT_KNOBS->APPLY_MAX_DECAY_RATE, CLIENT_KNOBS->APPLY_MIN_LOCK_BYTES); for (idx = 0; idx < ranges.size(); ++idx) { int bytes = - wait(kvMutationLogToTransactions(cx, - results[idx], - locks[idx], - uid, - addPrefix, - removePrefix, - commit, - committedVersion, - idx == ranges.size() - 1 ? newEndVersion : Optional(), - ranges[idx].begin, - &commitLock, - keyVersion, - provisionalProxy, - dbConfig)); + co_await kvMutationLogToTransactions(cx, + results[idx], + locks[idx], + uid, + addPrefix, + removePrefix, + commit, + committedVersion, + idx == ranges.size() - 1 ? newEndVersion : Optional(), + ranges[idx].begin, + &commitLock, + keyVersion, + provisionalProxy, + dbConfig); maxBytes = std::max(CLIENT_KNOBS->APPLY_MAX_INCREASE_FACTOR * bytes, maxBytes); if (error.isError()) throw error.getError(); } - wait(coalesceKeyVersionCache( - uid, newEndVersion, keyVersion, commit, committedVersion, addActor, &commitLock)); + co_await coalesceKeyVersionCache( + uid, newEndVersion, keyVersion, commit, committedVersion, addActor, &commitLock); beginVersion = newEndVersion; if (BUGGIFY) { - wait(delay(2.0)); + co_await delay(2.0); } } } catch (Error& e) { @@ -937,17 +943,17 @@ ACTOR Future applyMutations(Database cx, } } -ACTOR static Future _eraseLogData(Reference tr, - Key logUidValue, - Key destUidValue, - Optional endVersion, - CheckBackupUID checkBackupUid, - Version backupUid) { - state Key backupLatestVersionsPath = destUidValue.withPrefix(backupLatestVersionsPrefix); - state Key backupLatestVersionsKey = logUidValue.withPrefix(backupLatestVersionsPath); - - if (!destUidValue.size()) { - return Void(); +static Future _eraseLogData(Reference tr, + Key logUidValue, + Key destUidValue, + Optional endVersion, + CheckBackupUID checkBackupUid, + Version backupUid) { + Key backupLatestVersionsPath = destUidValue.withPrefix(backupLatestVersionsPrefix); + Key backupLatestVersionsKey = logUidValue.withPrefix(backupLatestVersionsPath); + + if (destUidValue.empty()) { + co_return; } tr->setOption(FDBTransactionOptions::LOCK_AWARE); @@ -956,16 +962,16 @@ ACTOR static Future _eraseLogData(Reference tr, if (checkBackupUid) { Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keySourceStates).get(logUidValue); - Optional v = wait(tr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId))); + Optional v = co_await tr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId)); if (v.present() && BinaryReader::fromStringRef(v.get(), Unversioned()) > backupUid) - return Void(); + co_return; } - state RangeResult backupVersions = wait( - tr->getRange(KeyRangeRef(backupLatestVersionsPath, strinc(backupLatestVersionsPath)), CLIENT_KNOBS->TOO_MANY)); + RangeResult backupVersions = co_await tr->getRange( + KeyRangeRef(backupLatestVersionsPath, strinc(backupLatestVersionsPath)), CLIENT_KNOBS->TOO_MANY); // Make sure version history key does exist and lower the beginVersion if needed - state Version currBeginVersion = invalidVersion; + Version currBeginVersion = invalidVersion; for (auto backupVersion : backupVersions) { Key currLogUidValue = backupVersion.key.removePrefix(backupLatestVersionsPrefix).removePrefix(destUidValue); @@ -977,15 +983,15 @@ ACTOR static Future _eraseLogData(Reference tr, // Do not clear anything if version history key cannot be found if (currBeginVersion == invalidVersion) { - return Void(); + co_return; } - state Version currEndVersion = std::numeric_limits::max(); + Version currEndVersion = std::numeric_limits::max(); if (endVersion.present()) { currEndVersion = std::min(currEndVersion, endVersion.get()); } - state Version nextSmallestVersion = currEndVersion; + Version nextSmallestVersion = currEndVersion; bool clearLogRangesRequired = true; // More than one backup/DR with the same range @@ -1053,16 +1059,14 @@ ACTOR static Future _eraseLogData(Reference tr, } if (!endVersion.present() && backupVersions.size() == 1) { - RangeResult existingDestUidValues = - wait(tr->getRange(KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY)); + RangeResult existingDestUidValues = co_await tr->getRange( + KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY); for (auto it : existingDestUidValues) { if (it.value == destUidValue) { tr->clear(it.key); } } } - - return Void(); } Future eraseLogData(Reference tr, @@ -1074,45 +1078,45 @@ Future eraseLogData(Reference tr, return _eraseLogData(tr, logUidValue, destUidValue, endVersion, checkBackupUid, backupUid); } -ACTOR Future cleanupLogMutations(Database cx, Value destUidValue, bool deleteData) { - state Key backupLatestVersionsPath = destUidValue.withPrefix(backupLatestVersionsPrefix); +Future cleanupLogMutations(Database cx, Value destUidValue, bool deleteData) { + Key backupLatestVersionsPath = destUidValue.withPrefix(backupLatestVersionsPrefix); - state Reference tr(new ReadYourWritesTransaction(cx)); - state Optional removingLogUid; - state std::set loggedLogUids; + Reference tr(new ReadYourWritesTransaction(cx)); + Optional removingLogUid; + std::set loggedLogUids; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - state RangeResult backupVersions = wait(tr->getRange( - KeyRangeRef(backupLatestVersionsPath, strinc(backupLatestVersionsPath)), CLIENT_KNOBS->TOO_MANY)); - state Version readVer = tr->getReadVersion().get(); + RangeResult backupVersions = co_await tr->getRange( + KeyRangeRef(backupLatestVersionsPath, strinc(backupLatestVersionsPath)), CLIENT_KNOBS->TOO_MANY); + Version readVer = tr->getReadVersion().get(); - state Version minVersion = std::numeric_limits::max(); - state Key minVersionLogUid; + Version minVersion = std::numeric_limits::max(); + Key minVersionLogUid; - state int backupIdx = 0; - for (; backupIdx < backupVersions.size(); backupIdx++) { - state Version currVersion = + for (int backupIdx = 0; backupIdx < backupVersions.size(); backupIdx++) { + Version currVersion = BinaryReader::fromStringRef(backupVersions[backupIdx].value, Unversioned()); - state Key currLogUid = + Key currLogUid = backupVersions[backupIdx].key.removePrefix(backupLatestVersionsPrefix).removePrefix(destUidValue); if (currVersion < minVersion) { minVersionLogUid = currLogUid; minVersion = currVersion; } - if (!loggedLogUids.count(currLogUid)) { - state Future> foundDRKey = tr->get(Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keySourceStates) - .get(currLogUid) - .pack(DatabaseBackupAgent::keyStateStatus)); - state Future> foundBackupKey = tr->get( + if (!loggedLogUids.contains(currLogUid)) { + Future> foundDRKey = tr->get(Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keySourceStates) + .get(currLogUid) + .pack(DatabaseBackupAgent::keyStateStatus)); + Future> foundBackupKey = tr->get( Subspace(currLogUid.withPrefix("uid->config/"_sr).withPrefix(fileBackupPrefixRange.begin)) .pack("stateEnum"_sr)); - wait(success(foundDRKey) && success(foundBackupKey)); + co_await (success(foundDRKey) && success(foundBackupKey)); if (foundDRKey.get().present() && foundBackupKey.get().present()) { printf("WARNING: Found a tag that looks like both a backup and a DR. This tag is %.4f hours " @@ -1136,8 +1140,8 @@ ACTOR Future cleanupLogMutations(Database cx, Value destUidValue, bool del if (readVer - minVersion > CLIENT_KNOBS->MIN_CLEANUP_SECONDS * CLIENT_KNOBS->CORE_VERSIONSPERSECOND && (!removingLogUid.present() || minVersionLogUid == removingLogUid.get())) { removingLogUid = minVersionLogUid; - wait(eraseLogData(tr, minVersionLogUid, destUidValue)); - wait(tr->commit()); + co_await eraseLogData(tr, minVersionLogUid, destUidValue); + co_await tr->commit(); printf("\nSuccessfully removed the tag that was %.4f hours behind.\n\n", (readVer - minVersion) / (3600.0 * CLIENT_KNOBS->CORE_VERSIONSPERSECOND)); } else if (removingLogUid.present() && minVersionLogUid != removingLogUid.get()) { @@ -1158,30 +1162,35 @@ ACTOR Future cleanupLogMutations(Database cx, Value destUidValue, bool del (readVer - minVersion) / (3600.0 * CLIENT_KNOBS->CORE_VERSIONSPERSECOND)); } - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + + co_await tr->onError(err); } } -ACTOR Future cleanupBackup(Database cx, DeleteData deleteData) { - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { +Future cleanupBackup(Database cx, DeleteData deleteData) { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - state RangeResult destUids = wait( - tr->getRange(KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY)); + RangeResult destUids = co_await tr->getRange(KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), + CLIENT_KNOBS->TOO_MANY); for (auto destUid : destUids) { - wait(cleanupLogMutations(cx, destUid.value, deleteData)); + co_await cleanupLogMutations(cx, destUid.value, deleteData); } - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + + co_await tr->onError(err); } } diff --git a/fdbclient/BackupContainer.actor.cpp b/fdbclient/BackupContainer.cpp similarity index 78% rename from fdbclient/BackupContainer.actor.cpp rename to fdbclient/BackupContainer.cpp index 37e5e5f61e1..520eb271787 100644 --- a/fdbclient/BackupContainer.actor.cpp +++ b/fdbclient/BackupContainer.cpp @@ -1,5 +1,5 @@ /* - * BackupContainer.actor.cpp + * BackupContainer.cpp * * This source file is part of the FoundationDB open source project * @@ -18,48 +18,31 @@ * limitations under the License. */ -#include -#include - -// FIXME: Trim this down -#include "fmt/format.h" -#include "flow/Platform.actor.h" -#include "fdbclient/AsyncTaskThread.h" #include "fdbclient/BackupContainer.h" -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/JsonBuilder.h" #include "flow/Arena.h" #include "flow/Trace.h" -#include "flow/UnitTest.h" -#include "flow/Hash3.h" -#include "fdbrpc/AsyncFileReadAhead.actor.h" -#include "fdbrpc/simulator.h" #include "flow/Platform.h" -#include "fdbclient/AsyncFileS3BlobStore.actor.h" #ifdef BUILD_AZURE_BACKUP #include "fdbclient/BackupContainerAzureBlobStore.h" #endif -#include "fdbclient/BackupContainerFileSystem.h" -#include "fdbclient/BackupContainerLocalDirectory.h" -#include "fdbclient/BackupContainerS3BlobStore.h" -#include "fdbclient/Status.h" +#include "BackupContainerLocalDirectory.h" +#include "BackupContainerBlobStore.h" #include "fdbclient/SystemData.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "fdbclient/RunRYWTransaction.actor.h" +#include "fdbclient/KeyBackedTypes.h" +#include "fdbclient/RunRYWTransaction.h" #include #include -#include -#include "flow/actorcompiler.h" // has to be last include namespace IBackupFile_impl { -ACTOR Future appendStringRefWithLen(Reference file, Standalone s) { - state uint32_t lenBuf = bigEndian32((uint32_t)s.size()); - wait(file->append(&lenBuf, sizeof(lenBuf))); - wait(file->append(s.begin(), s.size())); - return Void(); +Future appendStringRefWithLen(Reference file, Standalone s) { + uint32_t lenBuf = bigEndian32((uint32_t)s.size()); + co_await file->append(&lenBuf, sizeof(lenBuf)); + co_await file->append(s.begin(), s.size()); } } // namespace IBackupFile_impl @@ -137,8 +120,9 @@ std::string BackupDescription::toString() const { info.append(format("URL: %s\n", url.c_str())); info.append(format("Restorable: %s\n", maxRestorableVersion.present() ? "true" : "false")); - info.append(format("Partitioned logs: %s\n", partitioned ? "true" : "false")); + info.append(format("Mutation Log Type: %s\n", mutationLogTypeToString(mutationLogType).c_str())); info.append(format("File-level encryption: %s\n", fileLevelEncryption ? "true" : "false")); + info.append(format("Encryption block size: %d\n", encryptionBlockSize)); auto formatVersion = [&](Version v) { std::string s; @@ -158,13 +142,15 @@ std::string BackupDescription::toString() const { }; for (const KeyspaceSnapshotFile& m : snapshots) { - info.append( - format("Snapshot: startVersion=%s endVersion=%s totalBytes=%lld restorable=%s expiredPct=%.2f\n", - formatVersion(m.beginVersion).c_str(), - formatVersion(m.endVersion).c_str(), - m.totalSize, - m.restorable.orDefault(false) ? "true" : "false", - m.expiredPct(expiredEndVersion))); + const char* snapshotType = m.isBulkDump() ? "bulkdump" : "rangefile"; + info.append(format( + "Snapshot: type=%s startVersion=%s endVersion=%s totalBytes=%lld restorable=%s expiredPct=%.2f\n", + snapshotType, + formatVersion(m.beginVersion).c_str(), + formatVersion(m.endVersion).c_str(), + m.totalSize, + m.restorable.orDefault(false) ? "true" : "false", + m.expiredPct(expiredEndVersion))); } info.append(format("SnapshotBytes: %lld\n", snapshotBytes)); @@ -196,8 +182,9 @@ std::string BackupDescription::toJSON() const { doc.setKey("SchemaVersion", "1.0.0"); doc.setKey("URL", url.c_str()); doc.setKey("Restorable", maxRestorableVersion.present()); - doc.setKey("Partitioned", partitioned); + doc.setKey("MutationLogType", mutationLogTypeToString(mutationLogType).c_str()); doc.setKey("FileLevelEncryption", fileLevelEncryption); + doc.setKey("EncryptionBlockSize", encryptionBlockSize); auto formatVersion = [&](Version v) { JsonBuilderObject doc; @@ -258,14 +245,15 @@ std::vector IBackupContainer::getURLFormats() { BackupContainerAzureBlobStore::getURLFormat(), #endif BackupContainerLocalDirectory::getURLFormat(), - BackupContainerS3BlobStore::getURLFormat(), + BackupContainerBlobStore::getURLFormat(), }; } // Get an IBackupContainer based on a container URL string Reference IBackupContainer::openContainer(const std::string& url, const Optional& proxy, - const Optional& encryptionKeyFileName) { + const Optional& encryptionKeyFileName, + int encryptionBlockSize) { static std::map> m_cache; // In simulation, disable caching for blobstore:// URLs to prevent cross-process connection issues. @@ -275,24 +263,26 @@ Reference IBackupContainer::openContainer(const std::string& u // when containers are cached globally and accessed by different simulated processes, the connection // pools are shared across processes. This violates Sim2Conn's requirement that connections stay on // their originating process, leading to assertion failures: - // "g_simulator->getCurrentProcess() == self->peerProcess" at sim2.actor.cpp:500 + // "g_simulator->getCurrentProcess() == self->peerProcess" at sim2.cpp:500 // // Fix: Disable caching for blobstore:// URLs in simulation so each process creates its own - // container with its own connection pool. File-based backups don't have this issue because they - // don't use process-bound network connections. + // container with its own connection pool. Also disable caching for file:// URLs in simulation: + // multiple concurrent backups to the same base URL can have different encryption settings. // // Note: This only affects simulation; production always uses the cache for performance. - bool skipCache = g_network && g_network->isSimulated() && isBlobstoreUrl(url); + bool skipCache = g_network && g_network->isSimulated() && (isBlobstoreUrl(url) || url.find("file://") == 0); // Use a reference to the cache entry (for automatic cache population) unless we're skipping cache Reference r_local; Reference& r = skipCache ? r_local : m_cache[url]; - if (r) + if (r) { return r; + } + try { StringRef u(url); if (u.startsWith("file://"_sr)) { - r = makeReference(url, encryptionKeyFileName); + r = makeReference(url, encryptionKeyFileName, encryptionBlockSize); } else if (u.startsWith("blobstore://"_sr)) { std::string resource; Optional blobstoreProxy; @@ -306,16 +296,13 @@ Reference IBackupContainer::openContainer(const std::string& u } // The URL parameters contain blobstore endpoint tunables as well as possible backup-specific options. - S3BlobStoreEndpoint::ParametersT backupParams; - Reference bstore = - S3BlobStoreEndpoint::fromString(url, blobstoreProxy, &resource, &lastOpenError, &backupParams); + IBlobStoreEndpoint::ParametersT backupParams; + Reference bstore = + IBlobStoreEndpoint::fromString(url, blobstoreProxy, &resource, &lastOpenError, &backupParams); - if (resource.empty()) - throw backup_invalid_url(); - for (auto c : resource) - if (!isalnum(c) && c != '_' && c != '-' && c != '.' && c != '/') - throw backup_invalid_url(); - r = makeReference(bstore, resource, backupParams, encryptionKeyFileName, true); + BackupContainerBlobStore::validateBackupUrl(resource); + r = makeReference( + bstore, resource, backupParams, encryptionKeyFileName, encryptionBlockSize, /*isBackup=*/true); } #ifdef BUILD_AZURE_BACKUP else if (u.startsWith("azure://"_sr)) { @@ -387,17 +374,17 @@ Reference IBackupContainer::openContainer(const std::string& u // Get a list of URLS to backup containers based on some a shorter URL. This function knows about some set of supported // URL types which support this sort of backup discovery. -ACTOR Future> listContainers_impl(std::string baseURL, Optional proxy) { +Future> listContainers_impl(std::string baseURL, Optional proxy) { try { StringRef u(baseURL); if (u.startsWith("file://"_sr)) { - std::vector results = wait(BackupContainerLocalDirectory::listURLs(baseURL)); - return results; + std::vector results = co_await BackupContainerLocalDirectory::listURLs(baseURL); + co_return results; } else if (u.startsWith("blobstore://"_sr)) { std::string resource; - S3BlobStoreEndpoint::ParametersT backupParams; - Reference bstore = S3BlobStoreEndpoint::fromString( + IBlobStoreEndpoint::ParametersT backupParams; + Reference bstore = IBlobStoreEndpoint::fromString( baseURL, proxy, &resource, &IBackupContainer::lastOpenError, &backupParams); if (!resource.empty()) { @@ -408,10 +395,15 @@ ACTOR Future> listContainers_impl(std::string baseURL, } // Create a dummy container to parse the backup-specific parameters from the URL and get a final bucket name - BackupContainerS3BlobStore dummy(bstore, "dummy", backupParams, {}, true); - - std::vector results = wait(BackupContainerS3BlobStore::listURLs(bstore, dummy.getBucket())); - return results; + BackupContainerBlobStore dummy(bstore, + "dummy", + backupParams, + /*encryptionKeyFileName=*/{}, + /*isBackup=*/true, + /*encryptionBlockSize=*/0); + + std::vector results = co_await BackupContainerBlobStore::listURLs(bstore, dummy.getBucket()); + co_return results; } // TODO: Enable this when Azure backups are ready /* @@ -424,7 +416,6 @@ ACTOR Future> listContainers_impl(std::string baseURL, IBackupContainer::lastOpenError = "invalid URL prefix"; throw backup_invalid_url(); } - } catch (Error& e) { if (e.code() == error_code_actor_cancelled) throw; @@ -445,27 +436,28 @@ Future> IBackupContainer::listContainers(const std::str return listContainers_impl(baseURL, proxy); } -ACTOR Future timeKeeperVersionFromDatetime(std::string datetime, Database db) { - state KeyBackedMap versionMap(timeKeeperPrefixRange.begin); - state Reference tr = makeReference(db); +Future timeKeeperVersionFromDatetime(std::string datetime, Database db) { + KeyBackedMap versionMap(timeKeeperPrefixRange.begin); + auto tr = makeReference(db); - state int64_t time = BackupAgentBase::parseTime(datetime); + int64_t time = BackupAgentBase::parseTime(datetime); if (time < 0) { fprintf( stderr, "ERROR: Incorrect date/time or format. Format is %s.\n", BackupAgentBase::timeFormat().c_str()); throw backup_error(); } - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedRangeResult> rangeResult = - wait(versionMap.getRange(tr, 0, time, 1, Snapshot::False, Reverse::True)); + KeyBackedRangeResult> rangeResult = + co_await versionMap.getRange(tr, 0, time, 1, Snapshot::False, Reverse::True); if (rangeResult.results.size() != 1) { // No key less than time was found in the database // Look for a key >= time. - wait(store(rangeResult, versionMap.getRange(tr, time, std::numeric_limits::max(), 1))); + rangeResult = co_await versionMap.getRange(tr, time, std::numeric_limits::max(), 1); if (rangeResult.results.size() != 1) { fprintf(stderr, "ERROR: Unable to calculate a version for given date/time.\n"); @@ -475,42 +467,43 @@ ACTOR Future timeKeeperVersionFromDatetime(std::string datetime, Databa // Adjust version found by the delta between time and the time found and min with 0. auto& result = rangeResult.results[0]; - return std::max(0, result.second + (time - result.first) * CLIENT_KNOBS->CORE_VERSIONSPERSECOND); - + co_return std::max(0, + result.second + (time - result.first) * CLIENT_KNOBS->CORE_VERSIONSPERSECOND); } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } -ACTOR Future> timeKeeperEpochsFromVersion(Version v, Reference tr) { - state KeyBackedMap versionMap(timeKeeperPrefixRange.begin); +Future> timeKeeperEpochsFromVersion(Version v, Reference tr) { + KeyBackedMap versionMap(timeKeeperPrefixRange.begin); // Binary search to find the closest date with a version <= v - state int64_t min = 0; - state int64_t max = (int64_t)now(); - state int64_t mid; - state std::pair found; + int64_t min = 0; + int64_t max = (int64_t)now(); + int64_t mid{ 0 }; + std::pair found; tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - loop { + while (true) { mid = (min + max + 1) / 2; // ceiling // Find the highest time < mid - state KeyBackedRangeResult> rangeResult = - wait(versionMap.getRange(tr, min, mid, 1, Snapshot::False, Reverse::True)); + KeyBackedRangeResult> rangeResult = + co_await versionMap.getRange(tr, min, mid, 1, Snapshot::False, Reverse::True); if (rangeResult.results.size() != 1) { if (mid == min) { // There aren't any records having a version < v, so just look for any record having a time < now // and base a result on it - wait(store(rangeResult, versionMap.getRange(tr, 0, (int64_t)now(), 1))); + rangeResult = co_await versionMap.getRange(tr, 0, (int64_t)now(), 1); if (rangeResult.results.size() != 1) { // There aren't any timekeeper records to base a result on so return nothing - return Optional(); + co_return Optional(); } found = rangeResult.results[0]; @@ -533,5 +526,5 @@ ACTOR Future> timeKeeperEpochsFromVersion(Version v, Reference } } - return found.first + (v - found.second) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND; + co_return found.first + (v - found.second) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND; } diff --git a/fdbclient/BackupContainerS3BlobStore.actor.cpp b/fdbclient/BackupContainerBlobStore.cpp similarity index 54% rename from fdbclient/BackupContainerS3BlobStore.actor.cpp rename to fdbclient/BackupContainerBlobStore.cpp index f57bf53a929..505dea70a41 100644 --- a/fdbclient/BackupContainerS3BlobStore.actor.cpp +++ b/fdbclient/BackupContainerBlobStore.cpp @@ -1,5 +1,5 @@ /* - * BackupContainerS3BlobStore.actor.cpp + * BackupContainerBlobStore.cpp * * This source file is part of the FoundationDB open source project * @@ -18,14 +18,14 @@ * limitations under the License. */ -#include "fdbclient/AsyncFileS3BlobStore.actor.h" -#include "fdbclient/BackupContainerS3BlobStore.h" +#include "fdbclient/AsyncFileBlobStore.h" +#include "BackupContainerBlobStore.h" +#include "fdbclient/IBlobStore.h" #include "fdbrpc/AsyncFileEncrypted.h" -#include "fdbrpc/AsyncFileReadAhead.actor.h" +#include "fdbrpc/AsyncFileReadAhead.h" #include "fdbrpc/HTTP.h" -#include "flow/actorcompiler.h" // This must be the last #include. -class BackupContainerS3BlobStoreImpl { +class BackupContainerBlobStoreImpl { public: // Backup files to under a single folder prefix with subfolders for each named backup static const std::string DATAFOLDER; @@ -34,9 +34,9 @@ class BackupContainerS3BlobStoreImpl { // number of slashes so the backup names are kept in a separate folder tree from their actual data. static const std::string INDEXFOLDER; - ACTOR static Future> listURLs(Reference bstore, std::string bucket) { - state std::string basePath = INDEXFOLDER + '/'; - S3BlobStoreEndpoint::ListResult contents = wait(bstore->listObjects(bucket, basePath)); + static Future> listURLs(Reference bstore, std::string bucket) { + std::string basePath = INDEXFOLDER + '/'; + IBlobStoreEndpoint::ListResult contents = co_await bstore->listObjects(bucket, basePath); std::vector results; for (const auto& f : contents.objects) { // URL decode the object name since S3 XML responses contain URL-encoded names @@ -44,7 +44,7 @@ class BackupContainerS3BlobStoreImpl { results.push_back( bstore->getResourceURL(decodedName.substr(basePath.size()), format("bucket=%s", bucket.c_str()))); } - return results; + co_return results; } class BackupFile : public IBackupFile, ReferenceCounted { @@ -76,21 +76,21 @@ class BackupContainerS3BlobStoreImpl { int64_t m_offset; }; - ACTOR static Future listFiles( - Reference bc, + static Future listFiles( + Reference bc, std::string path, std::function pathFilter) { // pathFilter expects container based paths, so create a wrapper which converts a raw path // to a container path by removing the known backup name prefix. - state int prefixTrim = bc->dataPath("").size(); + int prefixTrim = bc->dataPath("").size(); std::function rawPathFilter = [=](const std::string& folderPath) { ASSERT(folderPath.size() >= prefixTrim); return pathFilter(folderPath.substr(prefixTrim)); }; // Use flat listing for backup files to ensure all files are found regardless of directory structure - state S3BlobStoreEndpoint::ListResult result = - wait(bc->m_bstore->listObjects(bc->m_bucket, bc->dataPath(path), Optional(), 0, rawPathFilter)); + IBlobStoreEndpoint::ListResult result = + co_await bc->m_bstore->listObjects(bc->m_bucket, bc->dataPath(path), Optional(), 0, rawPathFilter); BackupContainerFileSystem::FilesAndSizesT files; for (const auto& o : result.objects) { ASSERT(o.name.size() >= prefixTrim); @@ -98,51 +98,47 @@ class BackupContainerS3BlobStoreImpl { std::string decodedName = HTTP::urlDecode(o.name); files.push_back({ decodedName.substr(prefixTrim), o.size }); } - return files; + co_return files; } - ACTOR static Future create(Reference bc) { - wait(bc->m_bstore->createBucket(bc->m_bucket)); + static Future create(Reference bc) { + co_await bc->m_bstore->createBucket(bc->m_bucket); // Check/create the index entry - bool exists = wait(bc->m_bstore->objectExists(bc->m_bucket, bc->indexEntry())); + bool exists = co_await bc->m_bstore->objectExists(bc->m_bucket, bc->indexEntry()); if (!exists) { - wait(bc->m_bstore->writeEntireFile(bc->m_bucket, bc->indexEntry(), "")); + co_await bc->m_bstore->writeEntireFile(bc->m_bucket, bc->indexEntry(), ""); } if (bc->usesEncryption()) { - wait(bc->encryptionSetupComplete()); + co_await bc->encryptionSetupComplete(); } - - return Void(); } - ACTOR static Future deleteContainer(Reference bc, int* pNumDeleted) { - bool e = wait(bc->exists()); + static Future deleteContainer(Reference bc, int* pNumDeleted) { + bool e = co_await bc->exists(); if (!e) { TraceEvent(SevWarnAlways, "BackupContainerDoesNotExist").detail("URL", bc->getURL()); throw backup_does_not_exist(); } // First delete everything under the data prefix in the bucket - wait(bc->m_bstore->deleteRecursively(bc->m_bucket, bc->dataPath(""), pNumDeleted)); + co_await bc->m_bstore->deleteRecursively(bc->m_bucket, bc->dataPath(""), pNumDeleted); // Now that all files are deleted, delete the index entry - wait(bc->m_bstore->deleteObject(bc->m_bucket, bc->indexEntry())); - - return Void(); + co_await bc->m_bstore->deleteObject(bc->m_bucket, bc->indexEntry()); } }; -const std::string BackupContainerS3BlobStoreImpl::DATAFOLDER = "data"; -const std::string BackupContainerS3BlobStoreImpl::INDEXFOLDER = "backups"; +const std::string BackupContainerBlobStoreImpl::DATAFOLDER = "data"; +const std::string BackupContainerBlobStoreImpl::INDEXFOLDER = "backups"; -std::string BackupContainerS3BlobStore::dataPath(const std::string& path) { +std::string BackupContainerBlobStore::dataPath(const std::string& path) { // if backup, include the backup data prefix. // if m_name ends in a trailing slash, don't add another std::string dataPath = ""; if (isBackup) { - dataPath = BackupContainerS3BlobStoreImpl::DATAFOLDER + "/"; + dataPath = BackupContainerBlobStoreImpl::DATAFOLDER + "/"; } if (!m_name.empty() && m_name.back() == '/') { dataPath += m_name + path; @@ -153,46 +149,57 @@ std::string BackupContainerS3BlobStore::dataPath(const std::string& path) { } // Get the path of the backups's index entry -std::string BackupContainerS3BlobStore::indexEntry() { +std::string BackupContainerBlobStore::indexEntry() { ASSERT(isBackup); - return BackupContainerS3BlobStoreImpl::INDEXFOLDER + "/" + m_name; + return BackupContainerBlobStoreImpl::INDEXFOLDER + "/" + m_name; } -BackupContainerS3BlobStore::BackupContainerS3BlobStore(Reference bstore, - const std::string& name, - const S3BlobStoreEndpoint::ParametersT& params, - const Optional& encryptionKeyFileName, - bool isBackup) +BackupContainerBlobStore::BackupContainerBlobStore(Reference bstore, + const std::string& name, + const IBlobStoreEndpoint::ParametersT& params, + const Optional& encryptionKeyFileName, + int encryptionBlockSize, + bool isBackup) : m_bstore(bstore), m_name(name), m_bucket("FDB_BACKUPS_V2"), isBackup(isBackup) { setEncryptionKey(encryptionKeyFileName); + this->encryptionBlockSize = encryptionBlockSize; // Currently only one parameter is supported, "bucket" for (const auto& [name, value] : params) { if (name == "bucket") { m_bucket = value; continue; } - TraceEvent(SevWarn, "BackupContainerS3BlobStoreInvalidParameter").detail("Name", name).detail("Value", value); + TraceEvent(SevWarn, "BackupContainerBlobStoreInvalidParameter").detail("Name", name).detail("Value", value); IBackupContainer::lastOpenError = format("Unknown URL parameter: '%s'", name.c_str()); throw backup_invalid_url(); } } -void BackupContainerS3BlobStore::addref() { - return ReferenceCounted::addref(); +void BackupContainerBlobStore::addref() { + return ReferenceCounted::addref(); +} +void BackupContainerBlobStore::delref() { + return ReferenceCounted::delref(); } -void BackupContainerS3BlobStore::delref() { - return ReferenceCounted::delref(); + +std::string BackupContainerBlobStore::getURLFormat(bool withResource) { + return IBlobStoreEndpoint::getURLFormat(withResource) + " (Note: The 'bucket' parameter is required.)"; } -std::string BackupContainerS3BlobStore::getURLFormat() { - return S3BlobStoreEndpoint::getURLFormat(true) + " (Note: The 'bucket' parameter is required.)"; +void BackupContainerBlobStore::validateBackupUrl(const std::string& resource) { + if (resource.empty()) + throw backup_invalid_url(); + + for (auto c : resource) + if (!isalnum(static_cast(c)) && c != '_' && c != '-' && c != '.' && c != '/') + throw backup_invalid_url(); } -Future> BackupContainerS3BlobStore::readFile(const std::string& path) { - Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); +Future> BackupContainerBlobStore::readFile(const std::string& path) { + Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { - f = makeReference(f, AsyncFileEncrypted::Mode::READ_ONLY); + f = makeReference(f, AsyncFileEncrypted::Mode::READ_ONLY, encryptionBlockSize); } if (m_bstore->knobs.enable_read_cache) { f = makeReference(f, @@ -204,47 +211,46 @@ Future> BackupContainerS3BlobStore::readFile(const std::st return f; } -Future> BackupContainerS3BlobStore::listURLs(Reference bstore, - const std::string& bucket) { - return BackupContainerS3BlobStoreImpl::listURLs(bstore, bucket); +Future> BackupContainerBlobStore::listURLs(Reference bstore, + const std::string& bucket) { + return BackupContainerBlobStoreImpl::listURLs(bstore, bucket); } -Future> BackupContainerS3BlobStore::writeFile(const std::string& path) { - Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); +Future> BackupContainerBlobStore::writeFile(const std::string& path) { + Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { - f = makeReference(f, AsyncFileEncrypted::Mode::APPEND_ONLY); + f = makeReference(f, AsyncFileEncrypted::Mode::APPEND_ONLY, encryptionBlockSize); } - return Future>(makeReference(path, f)); + return Future>(makeReference(path, f)); } -Future BackupContainerS3BlobStore::writeEntireFile(const std::string& path, const std::string& fileContents) { +Future BackupContainerBlobStore::writeEntireFile(const std::string& path, const std::string& fileContents) { return m_bstore->writeEntireFile(m_bucket, dataPath(path), fileContents); } -Future BackupContainerS3BlobStore::deleteFile(const std::string& path) { +Future BackupContainerBlobStore::deleteFile(const std::string& path) { return m_bstore->deleteObject(m_bucket, dataPath(path)); } -Future BackupContainerS3BlobStore::listFiles( +Future BackupContainerBlobStore::listFiles( const std::string& path, std::function pathFilter) { - return BackupContainerS3BlobStoreImpl::listFiles( - Reference::addRef(this), path, pathFilter); + return BackupContainerBlobStoreImpl::listFiles(Reference::addRef(this), path, pathFilter); } -Future BackupContainerS3BlobStore::create() { - return BackupContainerS3BlobStoreImpl::create(Reference::addRef(this)); +Future BackupContainerBlobStore::create() { + return BackupContainerBlobStoreImpl::create(Reference::addRef(this)); } -Future BackupContainerS3BlobStore::exists() { +Future BackupContainerBlobStore::exists() { return m_bstore->objectExists(m_bucket, indexEntry()); } -Future BackupContainerS3BlobStore::deleteContainer(int* pNumDeleted) { - return BackupContainerS3BlobStoreImpl::deleteContainer(Reference::addRef(this), - pNumDeleted); +Future BackupContainerBlobStore::deleteContainer(int* pNumDeleted) { + return BackupContainerBlobStoreImpl::deleteContainer(Reference::addRef(this), + pNumDeleted); } -std::string BackupContainerS3BlobStore::getBucket() const { +std::string BackupContainerBlobStore::getBucket() const { return m_bucket; } diff --git a/fdbclient/include/fdbclient/BackupContainerS3BlobStore.h b/fdbclient/BackupContainerBlobStore.h similarity index 64% rename from fdbclient/include/fdbclient/BackupContainerS3BlobStore.h rename to fdbclient/BackupContainerBlobStore.h index 9aeca0fb0cf..546430b0554 100644 --- a/fdbclient/include/fdbclient/BackupContainerS3BlobStore.h +++ b/fdbclient/BackupContainerBlobStore.h @@ -1,5 +1,5 @@ /* - * BackupContainerS3BlobStore.h + * BackupContainerBlobStore.h * * This source file is part of the FoundationDB open source project * @@ -18,16 +18,16 @@ * limitations under the License. */ -#ifndef FDBCLIENT_BACKUP_CONTAINER_S3_BLOBSTORE_H -#define FDBCLIENT_BACKUP_CONTAINER_S3_BLOBSTORE_H +#ifndef FDBCLIENT_BACKUP_CONTAINER_BLOBSTORE_H +#define FDBCLIENT_BACKUP_CONTAINER_BLOBSTORE_H #pragma once -#include "fdbclient/AsyncFileS3BlobStore.actor.h" +#include "fdbclient/AsyncFileBlobStore.h" #include "fdbclient/BackupContainerFileSystem.h" +#include "fdbclient/IBlobStore.h" -class BackupContainerS3BlobStore final : public BackupContainerFileSystem, - ReferenceCounted { - Reference m_bstore; +class BackupContainerBlobStore final : public BackupContainerFileSystem, ReferenceCounted { + Reference m_bstore; std::string m_name; // All backup data goes into a single bucket @@ -41,23 +41,26 @@ class BackupContainerS3BlobStore final : public BackupContainerFileSystem, // Get the path of the backups's index entry std::string indexEntry(); - friend class BackupContainerS3BlobStoreImpl; + friend class BackupContainerBlobStoreImpl; public: - BackupContainerS3BlobStore(Reference bstore, - const std::string& name, - const S3BlobStoreEndpoint::ParametersT& params, - const Optional& encryptionKeyFileName, - bool isBackup); + BackupContainerBlobStore(Reference bstore, + const std::string& name, + const IBlobStoreEndpoint::ParametersT& params, + const Optional& encryptionKeyFileName, + int encryptionBlockSize, + bool isBackup); void addref() override; void delref() override; - static std::string getURLFormat(); + static std::string getURLFormat(bool withResource = true); + + static void validateBackupUrl(const std::string& resource); Future> readFile(const std::string& path) final; - static Future> listURLs(Reference bstore, const std::string& bucket); + static Future> listURLs(Reference bstore, const std::string& bucket); Future> writeFile(const std::string& path) final; diff --git a/fdbclient/BackupContainerFileSystem.actor.cpp b/fdbclient/BackupContainerFileSystem.cpp similarity index 77% rename from fdbclient/BackupContainerFileSystem.actor.cpp rename to fdbclient/BackupContainerFileSystem.cpp index e881a9e583e..69f38f76182 100644 --- a/fdbclient/BackupContainerFileSystem.actor.cpp +++ b/fdbclient/BackupContainerFileSystem.cpp @@ -1,5 +1,5 @@ /* - * BackupContainerFileSystem.actor.cpp + * BackupContainerFileSystem.cpp * * This source file is part of the FoundationDB open source project * @@ -18,15 +18,15 @@ * limitations under the License. */ -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/BackupContainer.h" #include "flow/BooleanParam.h" #ifdef BUILD_AZURE_BACKUP #include "fdbclient/BackupContainerAzureBlobStore.h" #endif #include "fdbclient/BackupContainerFileSystem.h" -#include "fdbclient/BackupContainerLocalDirectory.h" -#include "fdbclient/BackupContainerS3BlobStore.h" +#include "BackupContainerLocalDirectory.h" +#include "BackupContainerBlobStore.h" #include "fdbclient/JsonBuilder.h" #include "flow/StreamCipher.h" #include "flow/UnitTest.h" @@ -34,29 +34,27 @@ #include #include -#include "flow/actorcompiler.h" // This must be the last #include. - class BackupContainerFileSystemImpl { public: // TODO: Do this more efficiently, as the range file list for a snapshot could potentially be hundreds of // megabytes. - ACTOR static Future, std::map>> readKeyspaceSnapshot( + static Future, std::map>> readKeyspaceSnapshot( Reference bc, KeyspaceSnapshotFile snapshot) { // Read the range file list for the specified version range, and then index them by fileName. // This is so we can verify that each of the files listed in the manifest file are also in the container at this // time. - std::vector files = wait(bc->listRangeFiles(snapshot.beginVersion, snapshot.endVersion)); - state std::map rangeIndex; + std::vector files = co_await bc->listRangeFiles(snapshot.beginVersion, snapshot.endVersion); + std::map rangeIndex; for (auto& f : files) rangeIndex[f.fileName] = std::move(f); // Read the snapshot file, verify the version range, then find each of the range files by name in the index and // return them. - state Reference f = wait(bc->readFile(snapshot.fileName)); - int64_t size = wait(f->size()); - state Standalone buf = makeString(size); - wait(success(f->read(mutateString(buf), buf.size(), 0))); + Reference f = co_await bc->readFile(snapshot.fileName); + int64_t size = co_await f->size(); + Standalone buf = makeString(size); + co_await f->read(mutateString(buf), buf.size(), 0); json_spirit::mValue json; if (!json_spirit::read_string(buf.toString(), json)) { fprintf(stderr, @@ -126,13 +124,9 @@ class BackupContainerFileSystemImpl { } } - return std::make_pair(results, fileKeyRanges); + co_return std::make_pair(results, fileKeyRanges); } - // Backup log types - static constexpr Version NON_PARTITIONED_MUTATION_LOG = 0; - static constexpr Version PARTITIONED_MUTATION_LOG = 1; - // Find what should be the filename of a path by finding whatever is after the last forward or backward slash, or // failing to find those, the whole string. static std::string fileNameOnly(const std::string& path) { @@ -163,17 +157,17 @@ class BackupContainerFileSystemImpl { return false; } - ACTOR static Future writeKeyspaceSnapshotFile(Reference bc, - std::vector fileNames, - std::vector> beginEndKeys, - int64_t totalBytes, - IncludeKeyRangeMap includeKeyRangeMap, - Optional metadata) { - state Version minVer = std::numeric_limits::max(); - state Version maxVer = 0; - state RangeFile rf; - state json_spirit::mArray fileArray; - state bool isBulkDump = metadata.present() && metadata.get().isBulkDump(); + static Future writeKeyspaceSnapshotFile(Reference bc, + std::vector fileNames, + std::vector> beginEndKeys, + int64_t totalBytes, + IncludeKeyRangeMap includeKeyRangeMap, + Optional metadata) { + Version minVer = std::numeric_limits::max(); + Version maxVer = 0; + RangeFile rf; + json_spirit::mArray fileArray; + bool isBulkDump = metadata.present() && metadata.get().isBulkDump(); if (isBulkDump) { // For BulkDump snapshots, version comes from metadata @@ -192,14 +186,15 @@ class BackupContainerFileSystemImpl { minVer = rf.version; if (rf.version > maxVer) maxVer = rf.version; - } else + } else { throw restore_unknown_file_type(); - wait(yield()); + } + co_await yield(); } } - state json_spirit::mValue json; - state JSONDoc doc(json); + json_spirit::mValue json; + JSONDoc doc(json); doc.create("files") = std::move(fileArray); doc.create("totalBytes") = totalBytes; @@ -230,17 +225,17 @@ class BackupContainerFileSystemImpl { } } - wait(yield()); - state std::string docString = json_spirit::write_string(json); + co_await yield(); + std::string docString = json_spirit::write_string(json); // Generate filename - add suffixes only when 'both' mode is active to prevent collision // Single modes use original format for backward compatibility - state std::string fileName; - state std::string baseFileName = format("snapshots/snapshot,%lld,%lld,%lld", minVer, maxVer, totalBytes); + std::string fileName; + std::string baseFileName = format("snapshots/snapshot,%lld,%lld,%lld", minVer, maxVer, totalBytes); if (isBulkDump) { // For BulkDump: check if we're in 'both' mode by looking for potential rangefile collision - std::vector existingSnapshots = wait(bc->listKeyspaceSnapshots()); + std::vector existingSnapshots = co_await bc->listKeyspaceSnapshots(); bool hasRangefileSnapshot = false; for (const auto& snapshot : existingSnapshots) { @@ -259,7 +254,7 @@ class BackupContainerFileSystemImpl { } } else { // For Rangefile: check if we're in 'both' mode by looking for BulkDump collision - std::vector existingSnapshots = wait(bc->listKeyspaceSnapshots()); + std::vector existingSnapshots = co_await bc->listKeyspaceSnapshots(); bool hasBulkDumpSnapshot = false; for (const auto& snapshot : existingSnapshots) { @@ -278,25 +273,23 @@ class BackupContainerFileSystemImpl { } } - state Reference f = wait(bc->writeFile(fileName)); - wait(f->append(docString.data(), docString.size())); - wait(f->finish()); - return Void(); + Reference f = co_await bc->writeFile(fileName); + co_await f->append(docString.data(), docString.size()); + co_await f->finish(); } - ACTOR static Future dumpFileList(Reference bc, - Version begin, - Version end) { - state Future> fRanges = bc->listRangeFiles(begin, end); - state Future> fSnapshots = bc->listKeyspaceSnapshots(begin, end); - state std::vector logs; - state std::vector pLogs; + static Future dumpFileList(Reference bc, Version begin, Version end) { + Future> fRanges = bc->listRangeFiles(begin, end); + Future> fSnapshots = bc->listKeyspaceSnapshots(begin, end); + std::vector logs; + std::vector pLogs; - wait(success(fRanges) && success(fSnapshots) && store(logs, bc->listLogFiles(begin, end, false)) && - store(pLogs, bc->listLogFiles(begin, end, true))); + co_await (success(fRanges) && success(fSnapshots) && + store(logs, bc->listLogFiles(begin, end, MutationLogType::DEFAULT)) && + store(pLogs, bc->listLogFiles(begin, end, MutationLogType::PARTITIONED_LOG))); logs.insert(logs.end(), std::make_move_iterator(pLogs.begin()), std::make_move_iterator(pLogs.end())); - return BackupFileList({ fRanges.get(), std::move(logs), fSnapshots.get() }); + co_return BackupFileList({ fRanges.get(), std::move(logs), fSnapshots.get() }); } static Version resolveRelativeVersion(Optional max, Version v, const char* name, Error e) { @@ -434,7 +427,7 @@ class BackupContainerFileSystemImpl { if (logs.empty()) return; - Version snapshotBeginVersion = desc->snapshots.size() > 0 ? desc->snapshots[0].beginVersion : invalidVersion; + Version snapshotBeginVersion = !desc->snapshots.empty() ? desc->snapshots[0].beginVersion : invalidVersion; Version begin = std::max(scanBegin, desc->minLogBegin.get()); TraceEvent("ContinuousLogEnd") .detail("ScanBegin", scanBegin) @@ -561,10 +554,80 @@ class BackupContainerFileSystemImpl { return prevEnd; } - ACTOR static Future describeBackup(Reference bc, - bool deepScan, - Version logStartVersionOverride) { - state BackupDescription desc; + // Read encryption metadata from JSON file + static Future> readEncryptionMetadata(Reference bc) { + try { + Reference f = co_await bc->readFile(BackupContainerFileSystem::encryptionMetadataFileName()); + int64_t size = co_await f->size(); + std::string content; + content.resize(size); + co_await f->read((uint8_t*)content.data(), size, 0); + + json_spirit::mValue json; + if (json_spirit::read_string(content, json) && json.type() == json_spirit::obj_type) { + auto& obj = json.get_obj(); + + // Both fields must be present with correct types. + if (!obj.count("is_encryption_enabled") || + obj.at("is_encryption_enabled").type() != json_spirit::bool_type || + !obj.count("encryption_block_size") || + obj.at("encryption_block_size").type() != json_spirit::int_type) { + fprintf(stderr, "ERROR: Encryption metadata file is missing required fields or has wrong types.\n"); + TraceEvent(SevError, "BackupContainerReadEncryptionMetadataMalformed") + .detail("URL", bc->getURL()) + .detail("File", BackupContainerFileSystem::encryptionMetadataFileName()); + throw file_corrupt(); + } + + bool enabled = obj.at("is_encryption_enabled").get_bool(); + int blockSize = obj.at("encryption_block_size").get_int(); + + if ((enabled && blockSize <= 0) || (!enabled && blockSize != 0)) { + fprintf(stderr, + "ERROR: Encryption metadata is inconsistent (enabled=%d, blockSize=%d).\n", + enabled, + blockSize); + TraceEvent(SevError, "BackupContainerReadEncryptionMetadataInconsistent") + .detail("URL", bc->getURL()) + .detail("IsEncryptionEnabled", enabled) + .detail("EncryptionBlockSize", blockSize); + throw file_corrupt(); + } + + TraceEvent("BackupContainerReadEncryptionMetadata") + .detail("URL", bc->getURL()) + .detail("IsEncryptionEnabled", enabled) + .detail("EncryptionBlockSize", blockSize); + co_return std::make_pair(enabled, blockSize); + } else { + // If the file is malformed, throw an error. + fprintf(stderr, "ERROR: Failed to read encryption_metadata file due to incorrect format\n"); + TraceEvent(SevError, "BackupContainerReadEncryptionMetadataMalformed") + .detail("URL", bc->getURL()) + .detail("File", BackupContainerFileSystem::encryptionMetadataFileName()); + throw file_corrupt(); + } + } catch (Error& e) { + if (e.code() == error_code_file_not_found) { + // File may not be present for older backups, return encryption disabled. + TraceEvent(SevWarn, "BackupContainerEncryptionMetadataNotFound") + .detail("URL", bc->getURL()) + .detail("File", BackupContainerFileSystem::encryptionMetadataFileName()); + co_return std::make_pair(false, 0); + } + fprintf(stderr, "ERROR: Failed to read encryption_metadata file due to an error.\n"); + TraceEvent(SevError, "BackupContainerReadEncryptionMetadataError") + .error(e) + .detail("URL", bc->getURL()) + .detail("File", BackupContainerFileSystem::encryptionMetadataFileName()); + throw file_not_readable(); + } + } + + static Future describeBackup(Reference bc, + bool deepScan, + Version logStartVersionOverride) { + BackupDescription desc; desc.url = bc->getURL(); desc.proxy = bc->getProxy(); @@ -573,7 +636,7 @@ class BackupContainerFileSystemImpl { .detail("LogStartVersionOverride", logStartVersionOverride) .detail("DeepScan", deepScan); - bool e = wait(bc->exists()); + bool e = co_await bc->exists(); if (!e) { TraceEvent(SevWarnAlways, "BackupContainerDoesNotExist").detail("URL", bc->getURL()); throw backup_does_not_exist(); @@ -583,24 +646,24 @@ class BackupContainerFileSystemImpl { // from which to resolve the relative version. // This could be handled more efficiently without recursion but it's tricky, this will do for now. if (logStartVersionOverride != invalidVersion && logStartVersionOverride < 0) { - BackupDescription tmp = wait(bc->describeBackup(false, invalidVersion)); + BackupDescription tmp = co_await bc->describeBackup(false, invalidVersion); logStartVersionOverride = resolveRelativeVersion( tmp.maxLogEnd, logStartVersionOverride, "LogStartVersionOverride", invalid_option_value()); } // Get metadata versions - state Optional metaLogBegin; - state Optional metaLogEnd; - state Optional metaExpiredEnd; - state Optional metaUnreliableEnd; - state Optional metaLogType; - state Optional fileLevelEncryption; + Optional metaLogBegin; + Optional metaLogEnd; + Optional metaExpiredEnd; + Optional metaUnreliableEnd; + Optional metaLogType; + bool fileLevelEncryptionEnabled = false; + int encryptionBlockSize = 0; std::vector> metaReads; metaReads.push_back(store(metaExpiredEnd, bc->expiredEndVersion().get())); metaReads.push_back(store(metaUnreliableEnd, bc->unreliableEndVersion().get())); metaReads.push_back(store(metaLogType, bc->logType().get())); - metaReads.push_back(store(fileLevelEncryption, bc->fileLevelEncryption().get())); // Only read log begin/end versions if not doing a deep scan, otherwise scan files and recalculate them. if (!deepScan) { @@ -608,7 +671,11 @@ class BackupContainerFileSystemImpl { metaReads.push_back(store(metaLogEnd, bc->logEndVersion().get())); } - wait(waitForAll(metaReads)); + co_await waitForAll(metaReads); + + std::pair encryptionMeta = co_await readEncryptionMetadata(bc); + fileLevelEncryptionEnabled = encryptionMeta.first; + encryptionBlockSize = encryptionMeta.second; TraceEvent("BackupContainerDescribe2") .detail("URL", bc->getURL()) @@ -617,7 +684,9 @@ class BackupContainerFileSystemImpl { .detail("UnreliableEndVersion", metaUnreliableEnd.orDefault(invalidVersion)) .detail("LogBeginVersion", metaLogBegin.orDefault(invalidVersion)) .detail("LogEndVersion", metaLogEnd.orDefault(invalidVersion)) - .detail("LogType", metaLogType.orDefault(-1)); + .detail("LogType", metaLogType.orDefault(-1)) + .detail("FileLevelEncryption", fileLevelEncryptionEnabled) + .detail("EncryptionBlockSize", encryptionBlockSize); // If the logStartVersionOverride is positive (not relative) then ensure that unreliableEndVersion is equal or // greater @@ -657,8 +726,8 @@ class BackupContainerFileSystemImpl { // Start scanning at the end of the unreliable version range, which is the version before which data is likely // missing because an expire process has operated on that range. - state Version scanBegin = desc.unreliableEndVersion.orDefault(0); - state Version scanEnd = std::numeric_limits::max(); + Version scanBegin = desc.unreliableEndVersion.orDefault(0); + Version scanEnd = std::numeric_limits::max(); // Use the known log range if present // Logs are assumed to be contiguous between metaLogBegin and metaLogEnd, so initialize desc accordingly @@ -676,13 +745,13 @@ class BackupContainerFileSystemImpl { scanBegin = desc.contiguousLogEnd.get(); } - state std::vector logs; - state std::vector plogs; + std::vector logs; + std::vector plogs; TraceEvent("BackupContainerListFiles").detail("URL", bc->getURL()); - wait(store(logs, bc->listLogFiles(scanBegin, scanEnd, false)) && - store(plogs, bc->listLogFiles(scanBegin, scanEnd, true)) && - store(desc.snapshots, bc->listKeyspaceSnapshots())); + co_await (store(logs, bc->listLogFiles(scanBegin, scanEnd, MutationLogType::DEFAULT)) && + store(plogs, bc->listLogFiles(scanBegin, scanEnd, MutationLogType::PARTITIONED_LOG)) && + store(desc.snapshots, bc->listKeyspaceSnapshots())); TraceEvent("BackupContainerListFiles") .detail("URL", bc->getURL()) @@ -690,19 +759,16 @@ class BackupContainerFileSystemImpl { .detail("PLogsFiles", plogs.size()) .detail("Snapshots", desc.snapshots.size()); - if (plogs.size() > 0) { - desc.partitioned = true; + if (!plogs.empty()) { + desc.mutationLogType = MutationLogType::PARTITIONED_LOG; logs.swap(plogs); } else { - desc.partitioned = - metaLogType.present() && metaLogType.get() == BackupContainerFileSystemImpl::PARTITIONED_MUTATION_LOG; + desc.mutationLogType = + metaLogType.present() ? static_cast(metaLogType.get()) : MutationLogType::DEFAULT; } - if (fileLevelEncryption.present() && fileLevelEncryption.get() != 0) { - desc.fileLevelEncryption = true; - } else { - desc.fileLevelEncryption = false; - } + desc.fileLevelEncryption = fileLevelEncryptionEnabled; + desc.encryptionBlockSize = encryptionBlockSize; // List logs in version order so log continuity can be analyzed std::sort(logs.begin(), logs.end()); @@ -713,7 +779,7 @@ class BackupContainerFileSystemImpl { // If we didn't get log versions above then seed them using the first log file if (!desc.contiguousLogEnd.present()) { desc.minLogBegin = logs.begin()->beginVersion; - if (desc.partitioned) { + if (desc.mutationLogType == MutationLogType::PARTITIONED_LOG) { // Cannot use the first file's end version, which may not be contiguous // for other partitions. Set to its beginVersion to be safe. desc.contiguousLogEnd = logs.begin()->beginVersion; @@ -722,7 +788,7 @@ class BackupContainerFileSystemImpl { } } - if (desc.partitioned) { + if (desc.mutationLogType == MutationLogType::PARTITIONED_LOG) { updatePartitionedLogsContinuousEnd(&desc, logs, scanBegin, scanEnd); } else { Version& end = desc.contiguousLogEnd.get(); @@ -738,7 +804,7 @@ class BackupContainerFileSystemImpl { // then attempt to update the metadata in the backup container but ignore errors in case the // container is not writeable. try { - state Future updates = Void(); + Future updates = Void(); if (desc.minLogBegin.present() && metaLogBegin != desc.minLogBegin) { updates = updates && bc->logBeginVersion().set(desc.minLogBegin.get()); @@ -749,13 +815,10 @@ class BackupContainerFileSystemImpl { } if (!metaLogType.present()) { - updates = - updates && bc->logType().set(desc.partitioned - ? BackupContainerFileSystemImpl::PARTITIONED_MUTATION_LOG - : BackupContainerFileSystemImpl::NON_PARTITIONED_MUTATION_LOG); + updates = updates && bc->logType().set(static_cast(desc.mutationLogType)); } - wait(updates); + co_await updates; } catch (Error& e) { if (e.code() == error_code_actor_cancelled) throw; @@ -775,7 +838,7 @@ class BackupContainerFileSystemImpl { // If there is logs gap after contiguousLogEnd, then check whether the current snapshot // can be restored from the logs available after contiguousLogEnd. if (desc.contiguousLogEnd.present() && desc.contiguousLogEnd.get() <= s.beginVersion) { - if (desc.partitioned) + if (desc.mutationLogType == MutationLogType::PARTITIONED_LOG) s.restorable = isPartitionedLogsContinuous(logs, s.beginVersion, s.endVersion); else s.restorable = hasContinuousLogsForSnapshot(logs, s.beginVersion, s.endVersion); @@ -821,7 +884,7 @@ class BackupContainerFileSystemImpl { if (desc.minRestorableVersion.present() && desc.maxRestorableVersion.present()) { // check if we have contiguous logs from minRestorableVersion to current snapshot endVersion bool contiguousLogs = false; - if (desc.partitioned) + if (desc.mutationLogType == MutationLogType::PARTITIONED_LOG) contiguousLogs = isPartitionedLogsContinuous(logs, desc.minRestorableVersion.get(), s.endVersion); else @@ -846,7 +909,7 @@ class BackupContainerFileSystemImpl { // Find the continuousLogEnd after snapshotEndVersion and set it as // maxRestorableVersion. - if (desc.partitioned) { + if (desc.mutationLogType == MutationLogType::PARTITIONED_LOG) { // TO DO: Yet to implement similar function findContinuousLogEnd for partitioned logs. desc.maxRestorableVersion = s.endVersion; } else { @@ -857,14 +920,14 @@ class BackupContainerFileSystemImpl { } } - return desc; + co_return desc; } - ACTOR static Future expireData(Reference bc, - Version expireEndVersion, - bool force, - IBackupContainer::ExpireProgress* progress, - Version restorableBeginVersion) { + static Future expireData(Reference bc, + Version expireEndVersion, + bool force, + IBackupContainer::ExpireProgress* progress, + Version restorableBeginVersion) { if (progress != nullptr) { progress->step = "Describing backup"; progress->total = 0; @@ -876,7 +939,7 @@ class BackupContainerFileSystemImpl { .detail("RestorableBeginVersion", restorableBeginVersion); // Get the backup description. - state BackupDescription desc = wait(bc->describeBackup(false, expireEndVersion)); + BackupDescription desc = co_await bc->describeBackup(false, expireEndVersion); // Resolve relative versions using max log version expireEndVersion = @@ -891,14 +954,14 @@ class BackupContainerFileSystemImpl { // If the expire request is to a version at or before the previous version to which data was already deleted // then do nothing and just return if (expireEndVersion <= desc.expiredEndVersion.orDefault(invalidVersion)) { - return Void(); + co_return; } // Assume force is needed, then try to prove otherwise. // Force is required if there is not a restorable snapshot which both // - begins at or after expireEndVersion // - ends at or before restorableBeginVersion - state bool forceNeeded = true; + bool forceNeeded = true; for (KeyspaceSnapshotFile& s : desc.snapshots) { if (s.restorable.orDefault(false) && s.beginVersion >= expireEndVersion && s.endVersion <= restorableBeginVersion) { @@ -915,7 +978,7 @@ class BackupContainerFileSystemImpl { throw backup_cannot_expire(); // Start scan for files to delete at the last completed expire operation's end or 0. - state Version scanBegin = desc.expiredEndVersion.orDefault(0); + Version scanBegin = desc.expiredEndVersion.orDefault(0); TraceEvent("BackupContainerFileSystemExpire2") .detail("URL", bc->getURL()) @@ -923,21 +986,21 @@ class BackupContainerFileSystemImpl { .detail("RestorableBeginVersion", restorableBeginVersion) .detail("ScanBeginVersion", scanBegin); - state std::vector logs; - state std::vector pLogs; // partitioned mutation logs - state std::vector ranges; + std::vector logs; + std::vector pLogs; // partitioned mutation logs + std::vector ranges; if (progress != nullptr) { progress->step = "Listing files"; } // Get log files or range files that contain any data at or before expireEndVersion - wait(store(logs, bc->listLogFiles(scanBegin, expireEndVersion - 1, false)) && - store(pLogs, bc->listLogFiles(scanBegin, expireEndVersion - 1, true)) && - store(ranges, bc->listRangeFiles(scanBegin, expireEndVersion - 1))); + co_await (store(logs, bc->listLogFiles(scanBegin, expireEndVersion - 1, MutationLogType::DEFAULT)) && + store(pLogs, bc->listLogFiles(scanBegin, expireEndVersion - 1, MutationLogType::PARTITIONED_LOG)) && + store(ranges, bc->listRangeFiles(scanBegin, expireEndVersion - 1))); logs.insert(logs.end(), std::make_move_iterator(pLogs.begin()), std::make_move_iterator(pLogs.end())); // The new logBeginVersion will be taken from the last log file, if there is one - state Optional newLogBeginVersion; + Optional newLogBeginVersion; if (!logs.empty()) { // Linear scan the unsorted logs to find the latest one in sorted order LogFile& last = *std::max_element(logs.begin(), logs.end()); @@ -960,7 +1023,7 @@ class BackupContainerFileSystemImpl { } // Make a list of files to delete - state std::vector toDelete; + std::vector toDelete; // Move filenames out of vector then destroy it to save memory for (auto const& f : logs) { @@ -995,9 +1058,9 @@ class BackupContainerFileSystemImpl { if (progress != nullptr) { progress->step = "Initial metadata update"; } - Optional metaUnreliableEnd = wait(bc->unreliableEndVersion().get()); + Optional metaUnreliableEnd = co_await bc->unreliableEndVersion().get(); if (metaUnreliableEnd.orDefault(0) < expireEndVersion) { - wait(bc->unreliableEndVersion().set(expireEndVersion)); + co_await bc->unreliableEndVersion().set(expireEndVersion); } if (progress != nullptr) { @@ -1008,7 +1071,7 @@ class BackupContainerFileSystemImpl { // Delete files, but limit parallelism because the file list could use a lot of memory and the corresponding // delete actor states would use even more if they all existed at the same time. - state std::list> deleteFutures; + std::list> deleteFutures; while (!toDelete.empty() || !deleteFutures.empty()) { @@ -1021,10 +1084,10 @@ class BackupContainerFileSystemImpl { // Wait for deletes to finish until there are only targetDeletesInFlight remaining. // If there are no files left to start then this value is 0, otherwise it is one less // than the delete concurrency limit. - state int targetFuturesSize = toDelete.empty() ? 0 : (CLIENT_KNOBS->BACKUP_CONCURRENT_DELETES - 1); + int targetFuturesSize = toDelete.empty() ? 0 : (CLIENT_KNOBS->BACKUP_CONCURRENT_DELETES - 1); while (deleteFutures.size() > targetFuturesSize) { - wait(deleteFutures.front()); + co_await deleteFutures.front(); if (progress != nullptr) { ++progress->done; } @@ -1038,12 +1101,10 @@ class BackupContainerFileSystemImpl { } // Update the expiredEndVersion metadata to indicate that everything prior to that version has been // successfully deleted if the current version is lower or missing - Optional metaExpiredEnd = wait(bc->expiredEndVersion().get()); + Optional metaExpiredEnd = co_await bc->expiredEndVersion().get(); if (metaExpiredEnd.orDefault(0) < expireEndVersion) { - wait(bc->expiredEndVersion().set(expireEndVersion)); + co_await bc->expiredEndVersion().set(expireEndVersion); } - - return Void(); } // Returns true if logs are continuous in the range [begin, end]. @@ -1125,43 +1186,42 @@ class BackupContainerFileSystemImpl { // If "logsOnly" is true, then only log files are returned and "keyRangesFilter" is ignored, // because the log can contain mutations of the whole key space, unlike range files that each // is limited to a smaller key range. - ACTOR static Future> getRestoreSet(Reference bc, - Version targetVersion, - VectorRef keyRangesFilter, - bool logsOnly = false, - Version beginVersion = invalidVersion) { + static Future> getRestoreSet(Reference bc, + Version targetVersion, + VectorRef keyRangesFilter, + bool logsOnly = false, + Version beginVersion = invalidVersion) { for (const auto& range : keyRangesFilter) { TraceEvent("BackupContainerGetRestoreSet").detail("RangeFilter", printable(range)); } if (logsOnly) { - state RestorableFileSet restorableSet; + RestorableFileSet restorableSet; restorableSet.targetVersion = targetVersion; - state std::vector logFiles; + std::vector logFiles; Version begin = beginVersion == invalidVersion ? 0 : beginVersion; - wait(store(logFiles, bc->listLogFiles(begin, targetVersion, false))); + logFiles = co_await bc->listLogFiles(begin, targetVersion, MutationLogType::DEFAULT); // List logs in version order so log continuity can be analyzed std::sort(logFiles.begin(), logFiles.end()); if (!logFiles.empty()) { - return getRestoreSetFromLogs(logFiles, targetVersion, restorableSet); + co_return getRestoreSetFromLogs(logFiles, targetVersion, restorableSet); } } // Find the most recent keyrange snapshot through which we can restore filtered key ranges into targetVersion. - state std::vector snapshots = wait(bc->listKeyspaceSnapshots()); - state int i = snapshots.size() - 1; - for (; i >= 0; i--) { + std::vector snapshots = co_await bc->listKeyspaceSnapshots(); + for (int i = snapshots.size() - 1; i >= 0; i--) { // The smallest version of filtered range files >= snapshot beginVersion > targetVersion if (targetVersion >= 0 && snapshots[i].beginVersion > targetVersion) { continue; } - state RestorableFileSet restorable; - state Version minKeyRangeVersion = MAX_VERSION; - state Version maxKeyRangeVersion = -1; + RestorableFileSet restorable; + Version minKeyRangeVersion = MAX_VERSION; + Version maxKeyRangeVersion = -1; std::pair, std::map> results = - wait(bc->readKeyspaceSnapshot(snapshots[i])); + co_await bc->readKeyspaceSnapshot(snapshots[i]); // If there is no key ranges filter for the restore OR if the snapshot contains no per-file key range info // then return all of the range files @@ -1201,16 +1261,19 @@ class BackupContainerFileSystemImpl { .detail("KeyRangeVersion", restorable.targetVersion) .detail("NumberOfRangeFiles", restorable.ranges.size()) .detail("KeyRangesFilter", printable(keyRangesFilter)); - return Optional(restorable); + co_return Optional(restorable); } // FIXME: check if there are tagged logs. for each tag, there is no version gap. - state std::vector logs; - state std::vector plogs; - wait(store(logs, bc->listLogFiles(minKeyRangeVersion, restorable.targetVersion, false)) && - store(plogs, bc->listLogFiles(minKeyRangeVersion, restorable.targetVersion, true))); - - if (plogs.size() > 0) { + std::vector logs; + std::vector plogs; + co_await ( + store(logs, bc->listLogFiles(minKeyRangeVersion, restorable.targetVersion, MutationLogType::DEFAULT)) && + store( + plogs, + bc->listLogFiles(minKeyRangeVersion, restorable.targetVersion, MutationLogType::PARTITIONED_LOG))); + + if (!plogs.empty()) { logs.swap(plogs); // sort by tag ID so that filterDuplicates works. std::sort(logs.begin(), logs.end(), [](const LogFile& a, const LogFile& b) { @@ -1226,9 +1289,9 @@ class BackupContainerFileSystemImpl { if (isPartitionedLogsContinuous(restorable.logs, minKeyRangeVersion, restorable.targetVersion)) { restorable.continuousBeginVersion = minKeyRangeVersion; restorable.continuousEndVersion = restorable.targetVersion + 1; // not inclusive - return Optional(restorable); + co_return Optional(restorable); } - return Optional(); + co_return Optional(); } // List logs in version order so log continuity can be analyzed @@ -1236,10 +1299,10 @@ class BackupContainerFileSystemImpl { // If there are logs and the first one starts at or before the keyrange's snapshot begin version, then // it is valid restore set and proceed if (!logs.empty() && logs.front().beginVersion <= minKeyRangeVersion) { - return getRestoreSetFromLogs(logs, targetVersion, restorable); + co_return getRestoreSetFromLogs(logs, targetVersion, restorable); } } - return Optional(); + co_return Optional(); } static std::string versionFolderString(Version v, int smallestBucket) { @@ -1285,8 +1348,18 @@ class BackupContainerFileSystemImpl { // The innermost folder covers 100,000 seconds (1e11 versions) which is 5,000 mutation log files at current // settings. - static std::string logVersionFolderString(Version v, bool partitioned) { - return format("%s/%s/", (partitioned ? "plogs" : "logs"), versionFolderString(v, 11).c_str()); + static std::string logVersionFolderString(Version v, MutationLogType mutationLogType) { + return format("%s/%s/", + (mutationLogType == MutationLogType::PARTITIONED_LOG ? "plogs" : "logs"), + versionFolderString(v, 11).c_str()); + } + + static std::string logVersionFolderStringForRangePartitioned(Version v, Version baseVersion) { + Version directoryVersion = + baseVersion + ((v - baseVersion) / CLIENT_KNOBS->BACKUP_RANGE_PARTITIONED_VDIR_INTERVAL) * + CLIENT_KNOBS->BACKUP_RANGE_PARTITIONED_VDIR_INTERVAL; + std::string vFixed = format("%019lld", directoryVersion); + return format("rlogs/%s/", vFixed.c_str()); } static bool pathToLogFile(LogFile& out, const std::string& path, int64_t size) { @@ -1324,15 +1397,18 @@ class BackupContainerFileSystemImpl { KeyspaceSnapshotFile f; f.fileName = path; int len; + char typeBuf[64] = {}; // Try new format with type suffix: snapshot,beginVersion,endVersion,totalSize,type if (sscanf(name.c_str(), - "snapshot,%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%*[^,]%n", + "snapshot,%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%63[^,]%n", &f.beginVersion, &f.endVersion, &f.totalSize, - &len) == 3 && + typeBuf, + &len) == 4 && len == name.size()) { + f.snapshotType = typeBuf; out = f; return true; } @@ -1352,47 +1428,45 @@ class BackupContainerFileSystemImpl { } // fallback for using existing write api if the underlying blob store doesn't support efficient writeEntireFile - ACTOR static Future writeEntireFileFallback(Reference bc, - std::string fileName, - std::string fileContents) { - state Reference objectFile = wait(bc->writeFile(fileName)); - wait(objectFile->append(&fileContents[0], fileContents.size())); - wait(objectFile->finish()); - return Void(); + static Future writeEntireFileFallback(Reference bc, + std::string fileName, + std::string fileContents) { + Reference objectFile = co_await bc->writeFile(fileName); + co_await objectFile->append(&fileContents[0], fileContents.size()); + co_await objectFile->finish(); } - ACTOR static Future createTestEncryptionKeyFile(std::string filename) { + static Future createTestEncryptionKeyFile(std::string filename) { if (fileExists(filename)) { // Key file already exists, don't overwrite it -> only for testing between backup and restore workloads to // share the key. TraceEvent("EncryptionKeyFileExists").detail("FileName", filename); - return Void(); + co_return; } - state Reference keyFile = wait(IAsyncFileSystem::filesystem()->open( + Reference keyFile = co_await IAsyncFileSystem::filesystem()->open( filename, IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_CREATE, - 0600)); + 0600); StreamCipherKey testKey(AES_256_KEY_LENGTH); testKey.initializeRandomTestKey(); keyFile->write(testKey.data(), testKey.size(), 0); - wait(keyFile->sync()); - return Void(); + co_await keyFile->sync(); } - ACTOR static Future readEncryptionKey(std::string encryptionKeyFileName) { - state Reference keyFile; - state StreamCipherKey const* cipherKey = StreamCipherKey::getGlobalCipherKey(); + static Future readEncryptionKey(std::string encryptionKeyFileName) { + Reference keyFile; + StreamCipherKey const* cipherKey = StreamCipherKey::getGlobalCipherKey(); try { - Reference _keyFile = wait(IAsyncFileSystem::filesystem()->open( + Reference _keyFile = co_await IAsyncFileSystem::filesystem()->open( encryptionKeyFileName, IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED, - 0400)); + 0400); keyFile = _keyFile; } catch (Error& e) { TraceEvent(SevError, "FailedToOpenEncryptionKeyFile").error(e).detail("FileName", encryptionKeyFileName); throw e; } - int bytesRead = wait(uncancellable(keyFile->read(cipherKey->data(), cipherKey->size(), 0))); + int bytesRead = co_await uncancellable(keyFile->read(cipherKey->data(), cipherKey->size(), 0)); if (bytesRead != cipherKey->size()) { TraceEvent(SevError, "InvalidEncryptionKeyFileSize") .detail("ExpectedSize", cipherKey->size()) @@ -1401,20 +1475,38 @@ class BackupContainerFileSystemImpl { throw invalid_encryption_key_file(); } ASSERT_EQ(bytesRead, cipherKey->size()); - return Void(); } - ACTOR static Future writeEncryptionMetadataIfNotExists(Reference bc) { - Optional existingEncryptionMetadata = wait(bc->fileLevelEncryption().get()); - - if (!existingEncryptionMetadata.present()) { - bool exists = wait(bc->exists()); - if (!exists) { - wait(bc->create()); + static Future writeEncryptionMetadataIfNotExists(Reference bc, + int encryptionBlockSize) { + try { + Reference f = co_await bc->readFile(BackupContainerFileSystem::encryptionMetadataFileName()); + int64_t size = co_await f->size(); + TraceEvent("WriteEncryptionMetadataAlreadyExists").detail("URL", bc->getURL()).detail("FileSize", size); + co_return; + } catch (Error& e) { + if (e.code() != error_code_file_not_found) { + TraceEvent(SevWarn, "WriteEncryptionMetadataReadError").error(e).detail("URL", bc->getURL()); + throw e; } - wait(bc->fileLevelEncryption().set(bc->encryptionKeyFileName.present() ? 1 : 0)); } - return Void(); + + bool exists = co_await bc->exists(); + if (!exists) { + TraceEvent("WriteEncryptionMetadataCreatingContainer").detail("URL", bc->getURL()); + co_await bc->create(); + } + + // Write JSON with encryption metadata + bool enabled = bc->encryptionKeyFileName.present(); + JsonBuilderObject doc; + doc.setKey("is_encryption_enabled", enabled); + doc.setKey("encryption_block_size", encryptionBlockSize); + + std::string jsonStr = doc.getJson(); + TraceEvent("WriteEncryptionMetadataCompleted").detail("URL", bc->getURL()).detail("JSON", jsonStr); + + co_await bc->writeEntireFile(BackupContainerFileSystem::encryptionMetadataFileName(), jsonStr); } }; // class BackupContainerFileSystemImpl @@ -1422,7 +1514,7 @@ class BackupContainerFileSystemImpl { Future> BackupContainerFileSystem::writeLogFile(Version beginVersion, Version endVersion, int blockSize) { - return writeFile(BackupContainerFileSystemImpl::logVersionFolderString(beginVersion, false) + + return writeFile(BackupContainerFileSystemImpl::logVersionFolderString(beginVersion, MutationLogType::DEFAULT) + format("log,%lld,%lld,%s,%d", beginVersion, endVersion, @@ -1435,14 +1527,25 @@ Future> BackupContainerFileSystem::writeTaggedLogFile(Ver int blockSize, uint16_t tagId, int totalTags) { - return writeFile(BackupContainerFileSystemImpl::logVersionFolderString(beginVersion, true) + - format("log,%lld,%lld,%s,%d-of-%d,%d", - beginVersion, - endVersion, - deterministicRandom()->randomUniqueID().toString().c_str(), - tagId, - totalTags, - blockSize)); + return writeFile( + BackupContainerFileSystemImpl::logVersionFolderString(beginVersion, MutationLogType::PARTITIONED_LOG) + + format("log,%lld,%lld,%s,%d-of-%d,%d", + beginVersion, + endVersion, + deterministicRandom()->randomUniqueID().toString().c_str(), + tagId, + totalTags, + blockSize)); +} + +Future> BackupContainerFileSystem::writeRangePartitionedLogFile(Version beginVersion, + Version endVersion, + Version baseVersion, + int32_t partitionId, + int blockSize) { + return writeFile( + BackupContainerFileSystemImpl::logVersionFolderStringForRangePartitioned(beginVersion, baseVersion) + + format("log,%lld,%lld,%d,%d", beginVersion, endVersion, partitionId, blockSize)); } Future> BackupContainerFileSystem::writeRangeFile(Version snapshotBeginVersion, @@ -1461,6 +1564,12 @@ Future> BackupContainerFileSystem::writeRangeFile(Version format("/%d/", snapshotFileCount / (BUGGIFY ? 1 : 5000)) + fileName); } +Future BackupContainerFileSystem::writePartitionListFile(Version v, std::string contents) { + return writeEntireFile(BackupContainerFileSystemImpl::logVersionFolderStringForRangePartitioned(v, v) + + "partitionId_keyRange_map", + contents); +} + Future, std::map>> BackupContainerFileSystem::readKeyspaceSnapshot(KeyspaceSnapshotFile snapshot) { return BackupContainerFileSystemImpl::readKeyspaceSnapshot(Reference::addRef(this), @@ -1482,7 +1591,7 @@ Future BackupContainerFileSystem::writeKeyspaceSnapshotFile(const std::vec Future> BackupContainerFileSystem::listLogFiles(Version beginVersion, Version targetVersion, - bool partitioned) { + MutationLogType mutationLogType) { // The first relevant log file could have a begin version less than beginVersion based on the knobs which // determine log file range size, so start at an earlier version adjusted by how many versions a file could // contain. @@ -1492,9 +1601,9 @@ Future> BackupContainerFileSystem::listLogFiles(Version beg BackupContainerFileSystemImpl::cleanFolderString(BackupContainerFileSystemImpl::logVersionFolderString( std::max(0, beginVersion - CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES * CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE), - partitioned)); + mutationLogType)); std::string lastPath = BackupContainerFileSystemImpl::cleanFolderString( - BackupContainerFileSystemImpl::logVersionFolderString(targetVersion, partitioned)); + BackupContainerFileSystemImpl::logVersionFolderString(targetVersion, mutationLogType)); std::function pathFilter = [=](const std::string& folderPath) { // Remove slashes in the given folder path so that the '/' positions in the version folder string do not @@ -1505,16 +1614,17 @@ Future> BackupContainerFileSystem::listLogFiles(Version beg (cleaned > firstPath && cleaned < lastPath); }; - return map(listFiles((partitioned ? "plogs/" : "logs/"), pathFilter), [=](const FilesAndSizesT& files) { - std::vector results; - LogFile lf; - for (auto& f : files) { - if (BackupContainerFileSystemImpl::pathToLogFile(lf, f.first, f.second) && lf.endVersion > beginVersion && - lf.beginVersion <= targetVersion) - results.push_back(lf); - } - return results; - }); + return map(listFiles((mutationLogType == MutationLogType::PARTITIONED_LOG ? "plogs/" : "logs/"), pathFilter), + [=](const FilesAndSizesT& files) { + std::vector results; + LogFile lf; + for (auto& f : files) { + if (BackupContainerFileSystemImpl::pathToLogFile(lf, f.first, f.second) && + lf.endVersion > beginVersion && lf.beginVersion <= targetVersion) + results.push_back(lf); + } + return results; + }); } Future> BackupContainerFileSystem::old_listRangeFiles(Version beginVersion, Version endVersion) { @@ -1606,27 +1716,26 @@ Future BackupContainerFileSystem::expireData(Version expireEndVersion, Reference::addRef(this), expireEndVersion, force, progress, restorableBeginVersion); } -Future BackupContainerFileSystem::writeEncryptionMetadata() { +Future BackupContainerFileSystem::writeEncryptionMetadata(int encryptionBlockSize) { return BackupContainerFileSystemImpl::writeEncryptionMetadataIfNotExists( - Reference::addRef(this)); + Reference::addRef(this), encryptionBlockSize); } -ACTOR static Future getSnapshotFileKeyRange_impl(Reference bc, - RangeFile file, - Database cx) { - state int readFileRetries = 0; - state bool beginKeySet = false; - state Key beginKey; - state Key endKey; - loop { +static Future getSnapshotFileKeyRange_impl(Reference bc, + RangeFile file, + Database cx) { + int readFileRetries = 0; + Key beginKey; + Key endKey; + while (true) { + Error err; try { - state Reference inFile = wait(bc->readFile(file.fileName)); - beginKeySet = false; - state int64_t j = 0; - for (; j < file.fileSize; j += file.blockSize) { + Reference inFile = co_await bc->readFile(file.fileName); + bool beginKeySet = false; + for (int64_t j = 0; j < file.fileSize; j += file.blockSize) { int64_t len = std::min(file.blockSize, file.fileSize - j); Standalone> blockData = - wait(fileBackup::decodeRangeFileBlock(inFile, j, len, cx)); + co_await fileBackup::decodeRangeFileBlock(inFile, j, len, cx); if (!beginKeySet) { beginKey = blockData.front().key; beginKeySet = true; @@ -1635,33 +1744,33 @@ ACTOR static Future getSnapshotFileKeyRange_impl(Reference writeVersionProperty(Reference bc, std::string path, Version v) { +static Future writeVersionProperty(Reference bc, std::string path, Version v) { try { - state Reference f = wait(bc->writeFile(path)); + Reference f = co_await bc->writeFile(path); std::string s = format("%lld", v); - wait(f->append(s.data(), s.size())); - wait(f->finish()); - return Void(); + co_await f->append(s.data(), s.size()); + co_await f->finish(); } catch (Error& e) { TraceEvent(SevWarn, "BackupContainerWritePropertyFailed") .error(e) @@ -1671,24 +1780,24 @@ ACTOR static Future writeVersionProperty(Reference> readVersionProperty(Reference bc, std::string path) { +static Future> readVersionProperty(Reference bc, std::string path) { try { - state Reference f = wait(bc->readFile(path)); - state int64_t size = wait(f->size()); - state std::string s; + Reference f = co_await bc->readFile(path); + int64_t size = co_await f->size(); + std::string s; s.resize(size); - int rs = wait(f->read((uint8_t*)s.data(), size, 0)); + int rs = co_await f->read((uint8_t*)s.data(), size, 0); Version v; int len; if (rs == size && sscanf(s.c_str(), "%" SCNd64 "%n", &v, &len) == 1 && len == size) - return v; + co_return v; TraceEvent(SevWarn, "BackupContainerInvalidProperty").detail("URL", bc->getURL()).detail("Path", path); throw backup_invalid_info(); } catch (Error& e) { if (e.code() == error_code_file_not_found) - return Optional(); + co_return Optional(); TraceEvent(SevWarn, "BackupContainerReadPropertyFailed") .error(e) @@ -1737,8 +1846,9 @@ BackupContainerFileSystem::VersionProperty BackupContainerFileSystem::unreliable BackupContainerFileSystem::VersionProperty BackupContainerFileSystem::logType() { return { Reference::addRef(this), "mutation_log_type" }; } -BackupContainerFileSystem::VersionProperty BackupContainerFileSystem::fileLevelEncryption() { - return { Reference::addRef(this), "file_level_encryption" }; + +std::string BackupContainerFileSystem::encryptionMetadataFileName() { + return "properties/encryption_metadata"; } bool BackupContainerFileSystem::usesEncryption() const { @@ -1759,121 +1869,11 @@ void BackupContainerFileSystem::setEncryptionKey(Optional const& en encryptionSetupFuture = BackupContainerFileSystemImpl::readEncryptionKey(encryptionKeyFileName.get()); } } + Future BackupContainerFileSystem::createTestEncryptionKeyFile(std::string const& filename) { return BackupContainerFileSystemImpl::createTestEncryptionKeyFile(filename); } -// Get a BackupContainerFileSystem based on a container URL string -// TODO: refactor to not duplicate IBackupContainer::openContainer. It's the exact same -// code but returning a different template type because you can't cast between them -Reference BackupContainerFileSystem::openContainerFS( - const std::string& url, - const Optional& proxy, - const Optional& encryptionKeyFileName, - bool isBackup) { - static std::map> m_cache; - - Reference& r = m_cache[url]; - if (r) - return r; - - try { - StringRef u(url); - if (u.startsWith("file://"_sr)) { - r = makeReference(url, encryptionKeyFileName); - } else if (u.startsWith("blobstore://"_sr)) { - std::string resource; - Optional blobstoreProxy; - - // If no proxy is passed down to the openContainer method, try to fallback to the - // fileBackupAgentProxy which is a global variable and will be set for the backup_agent. - if (proxy.present()) { - blobstoreProxy = proxy.get(); - } else if (fileBackupAgentProxy.present()) { - blobstoreProxy = fileBackupAgentProxy.get(); - } - - // The URL parameters contain blobstore endpoint tunables as well as possible backup-specific options. - S3BlobStoreEndpoint::ParametersT backupParams; - Reference bstore = - S3BlobStoreEndpoint::fromString(url, blobstoreProxy, &resource, &lastOpenError, &backupParams); - - if (resource.empty()) - throw backup_invalid_url(); - for (auto c : resource) - if (!isalnum(c) && c != '_' && c != '-' && c != '.' && c != '/') - throw backup_invalid_url(); - r = makeReference( - bstore, resource, backupParams, encryptionKeyFileName, isBackup); - } -#ifdef BUILD_AZURE_BACKUP - else if (u.startsWith("azure://"_sr)) { - u.eat("azure://"_sr); - auto address = u.eat("/"_sr); - if (address.endsWith(std::string(azure::storage_lite::constants::default_endpoint_suffix))) { - CODE_PROBE(true, "Azure backup url with standard azure storage account endpoint"); - // ..core.windows.net/ - auto endPoint = address.toString(); - auto accountName = address.eat("."_sr).toString(); - auto containerName = u.eat("/"_sr).toString(); - r = makeReference( - endPoint, accountName, containerName, encryptionKeyFileName); - } else { - // resolve the network address if necessary - std::string endpoint(address.toString()); - Optional parsedAddress = NetworkAddress::parseOptional(endpoint); - if (!parsedAddress.present()) { - try { - auto hostname = Hostname::parse(endpoint); - auto resolvedAddress = hostname.resolveBlocking(); - if (resolvedAddress.present()) { - CODE_PROBE(true, "Azure backup url with hostname in the endpoint"); - parsedAddress = resolvedAddress.get(); - } - } catch (Error& e) { - TraceEvent(SevError, "InvalidAzureBackupUrl").error(e).detail("Endpoint", endpoint); - throw backup_invalid_url(); - } - } - if (!parsedAddress.present()) { - TraceEvent(SevError, "InvalidAzureBackupUrl").detail("Endpoint", endpoint); - throw backup_invalid_url(); - } - auto accountName = u.eat("/"_sr).toString(); - // Avoid including ":tls" and "(fromHostname)" - // note: the endpoint needs to contain the account name - // so either ".blob.core.windows.net" or ":/" - endpoint = - fmt::format("{}/{}", formatIpPort(parsedAddress.get().ip, parsedAddress.get().port), accountName); - auto containerName = u.eat("/"_sr).toString(); - r = makeReference( - endpoint, accountName, containerName, encryptionKeyFileName); - } - } -#endif - else { - lastOpenError = "invalid URL prefix"; - throw backup_invalid_url(); - } - - r->encryptionKeyFileName = encryptionKeyFileName; - r->URL = url; - return r; - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) - throw; - - TraceEvent m(SevWarn, "BackupContainer"); - m.error(e); - m.detail("Description", "Invalid container specification. See help."); - m.detail("URL", url); - if (e.code() == error_code_backup_invalid_url) - m.detail("LastOpenError", lastOpenError); - - throw; - } -} - namespace backup_test { int chooseFileSize(std::vector& sizes) { @@ -1885,40 +1885,36 @@ int chooseFileSize(std::vector& sizes) { return deterministicRandom()->randomInt(0, 2e6); } -ACTOR Future writeAndVerifyFile(Reference c, - Reference f, - int size, - FlowLock* lock) { - state Standalone> content; +Future writeAndVerifyFile(Reference c, Reference f, int size, FlowLock* lock) { + Standalone> content; - wait(lock->take(TaskPriority::DefaultYield, size)); - state FlowLock::Releaser releaser(*lock, size); + co_await lock->take(TaskPriority::DefaultYield, size); + FlowLock::Releaser releaser(*lock, size); printf("writeAndVerify size=%d file=%s\n", size, f->getFileName().c_str()); content.resize(content.arena(), size); - for (int i = 0; i < content.size(); ++i) { - content[i] = (uint8_t)deterministicRandom()->randomInt(0, 256); + for (auto& contentByte : content) { + contentByte = (uint8_t)deterministicRandom()->randomInt(0, 256); } - state VectorRef sendBuf = content; - while (sendBuf.size() > 0) { - state int n = std::min(sendBuf.size(), deterministicRandom()->randomInt(1, 16384)); - wait(f->append(sendBuf.begin(), n)); + VectorRef sendBuf = content; + while (!sendBuf.empty()) { + int n = std::min(sendBuf.size(), deterministicRandom()->randomInt(1, 16384)); + co_await f->append(sendBuf.begin(), n); sendBuf.pop_front(n); } - wait(f->finish()); + co_await f->finish(); - state Reference inputFile = wait(c->readFile(f->getFileName())); - int64_t fileSize = wait(inputFile->size()); + Reference inputFile = co_await c->readFile(f->getFileName()); + int64_t fileSize = co_await inputFile->size(); ASSERT_EQ(size, fileSize); if (size > 0) { - state Standalone> buf; + Standalone> buf; buf.resize(buf.arena(), fileSize); - int b = wait(inputFile->read(buf.begin(), buf.size(), 0)); + int b = co_await inputFile->read(buf.begin(), buf.size(), 0); ASSERT_EQ(b, buf.size()); ASSERT(buf == content); } - return Void(); } // Randomly advance version by up to 1 second of versions @@ -1928,72 +1924,73 @@ Version nextVersion(Version v) { } // Write a snapshot file with only begin & end key -ACTOR static Future testWriteSnapshotFile(Reference file, Key begin, Key end, uint32_t blockSize) { +static Future testWriteSnapshotFile(Reference file, Key begin, Key end, uint32_t blockSize) { ASSERT_GT(blockSize, 3 * sizeof(uint32_t) + begin.size() + end.size()); uint32_t fileVersion = BACKUP_AGENT_SNAPSHOT_FILE_VERSION; // write Header - wait(file->append((uint8_t*)&fileVersion, sizeof(fileVersion))); + co_await file->append((uint8_t*)&fileVersion, sizeof(fileVersion)); // write begin key length and key - wait(file->appendStringRefWithLen(begin)); + co_await file->appendStringRefWithLen(begin); // write end key length and key - wait(file->appendStringRefWithLen(end)); + co_await file->appendStringRefWithLen(end); int bytesLeft = blockSize - file->size(); if (bytesLeft > 0) { Value paddings = fileBackup::makePadding(bytesLeft); - wait(file->append(paddings.begin(), bytesLeft)); + co_await file->append(paddings.begin(), bytesLeft); } - wait(file->finish()); - return Void(); + co_await file->finish(); } -ACTOR Future testBackupContainer(std::string url, - Optional proxy, - Optional encryptionKeyFileName) { - state FlowLock lock(100e6); +Future testBackupContainer(std::string url, + Optional proxy, + Optional encryptionKeyFileName) { + FlowLock lock(100e6); if (encryptionKeyFileName.present()) { - wait(BackupContainerFileSystem::createTestEncryptionKeyFile(encryptionKeyFileName.get())); + co_await BackupContainerFileSystem::createTestEncryptionKeyFile(encryptionKeyFileName.get()); } printf("BackupContainerTest URL %s\n", url.c_str()); - state Reference c = IBackupContainer::openContainer(url, proxy, encryptionKeyFileName); + int encryptionBlockSize = encryptionKeyFileName.present() ? 4096 : 0; + Reference c = + IBackupContainer::openContainer(url, proxy, encryptionKeyFileName, encryptionBlockSize); // Make sure container doesn't exist, then create it. try { - wait(c->deleteContainer()); + co_await c->deleteContainer(); } catch (Error& e) { if (e.code() != error_code_backup_invalid_url && e.code() != error_code_backup_does_not_exist) throw; } - wait(c->create()); + co_await c->create(); - state std::vector> writes; - state std::map> snapshots; - state std::map snapshotSizes; - state std::map>> snapshotBeginEndKeys; - state int nRangeFiles = 0; - state std::map logs; - state Version v = deterministicRandom()->randomInt64(0, std::numeric_limits::max() / 2); + std::vector> writes; + std::map> snapshots; + std::map snapshotSizes; + std::map>> snapshotBeginEndKeys; + int nRangeFiles = 0; + std::map logs; + Version v = deterministicRandom()->randomInt64(0, std::numeric_limits::max() / 2); // List of sizes to use to test edge cases on underlying file implementations - state std::vector fileSizes = { 0 }; + std::vector fileSizes = { 0 }; if (StringRef(url).startsWith("blob"_sr)) { fileSizes.push_back(CLIENT_KNOBS->BLOBSTORE_MULTIPART_MIN_PART_SIZE); fileSizes.push_back(CLIENT_KNOBS->BLOBSTORE_MULTIPART_MIN_PART_SIZE + 10); } - loop { - state Version logStart = v; - state int kvfiles = deterministicRandom()->randomInt(0, 3); - state Key begin = ""_sr; - state Key end = ""_sr; - state int blockSize = 3 * sizeof(uint32_t) + begin.size() + end.size() + 8; + while (true) { + Version logStart = v; + int kvfiles = deterministicRandom()->randomInt(0, 3); + Key begin = ""_sr; + Key end = ""_sr; + int blockSize = 3 * sizeof(uint32_t) + begin.size() + end.size() + 8; while (kvfiles > 0) { if (snapshots.empty()) { @@ -2004,7 +2001,7 @@ ACTOR Future testBackupContainer(std::string url, v = nextVersion(v); } } - Reference range = wait(c->writeRangeFile(snapshots.rbegin()->first, 0, v, blockSize)); + Reference range = co_await c->writeRangeFile(snapshots.rbegin()->first, 0, v, blockSize); ++nRangeFiles; v = nextVersion(v); snapshots.rbegin()->second.push_back(range->getFileName()); @@ -2014,7 +2011,7 @@ ACTOR Future testBackupContainer(std::string url, snapshotSizes.rbegin()->second += size; // Write in actual range file format, instead of random data. // writes.push_back(writeAndVerifyFile(c, range, size, &lock)); - wait(testWriteSnapshotFile(range, begin, end, blockSize)); + co_await testWriteSnapshotFile(range, begin, end, blockSize); if (deterministicRandom()->random01() < .2) { writes.push_back(c->writeKeyspaceSnapshotFile(snapshots.rbegin()->second, @@ -2033,7 +2030,7 @@ ACTOR Future testBackupContainer(std::string url, if (logStart == v || deterministicRandom()->coinflip()) { v = nextVersion(v); } - state Reference log = wait(c->writeLogFile(logStart, v, 10)); + Reference log = co_await c->writeLogFile(logStart, v, 10); logs[logStart] = log->getFileName(); int size = chooseFileSize(fileSizes); writes.push_back(writeAndVerifyFile(c, log, size, &lock)); @@ -2046,99 +2043,95 @@ ACTOR Future testBackupContainer(std::string url, } } - wait(waitForAll(writes)); + co_await waitForAll(writes); - state BackupFileList listing = wait(c->dumpFileList()); + BackupFileList listing = co_await c->dumpFileList(); ASSERT_EQ(listing.ranges.size(), nRangeFiles); ASSERT_EQ(listing.logs.size(), logs.size()); ASSERT_EQ(listing.snapshots.size(), snapshots.size()); - state BackupDescription desc = wait(c->describeBackup()); + BackupDescription desc = co_await c->describeBackup(); printf("\n%s\n", desc.toString().c_str()); // Do a series of expirations and verify resulting state - state int i = 0; + int i = 0; for (; i < listing.snapshots.size(); ++i) { { // Ensure we can still restore to the latest version - Optional rest = wait(c->getRestoreSet(desc.maxRestorableVersion.get())); + Optional rest = co_await c->getRestoreSet(desc.maxRestorableVersion.get()); ASSERT(rest.present()); } { // Ensure we can restore to the end version of snapshot i - Optional rest = wait(c->getRestoreSet(listing.snapshots[i].endVersion)); + Optional rest = co_await c->getRestoreSet(listing.snapshots[i].endVersion); ASSERT(rest.present()); } // Test expiring to the end of this snapshot - state Version expireVersion = listing.snapshots[i].endVersion; + Version expireVersion = listing.snapshots[i].endVersion; // Expire everything up to but not including the snapshot end version fmt::print("EXPIRE TO {}\n", expireVersion); - state Future f = c->expireData(expireVersion); - wait(ready(f)); + Future f = c->expireData(expireVersion); + co_await ready(f); // If there is an error, it must be backup_cannot_expire and we have to be on the last snapshot if (f.isError()) { ASSERT_EQ(f.getError().code(), error_code_backup_cannot_expire); ASSERT_EQ(i, listing.snapshots.size() - 1); - wait(c->expireData(expireVersion, true)); + co_await c->expireData(expireVersion, true); } - BackupDescription d = wait(c->describeBackup()); + BackupDescription d = co_await c->describeBackup(); printf("\n%s\n", d.toString().c_str()); } printf("DELETING\n"); - wait(c->deleteContainer()); + co_await c->deleteContainer(); - state Future d = c->describeBackup(); - wait(ready(d)); + Future d = c->describeBackup(); + co_await ready(d); ASSERT(d.isError() && d.getError().code() == error_code_backup_does_not_exist); - BackupFileList empty = wait(c->dumpFileList()); + BackupFileList empty = co_await c->dumpFileList(); ASSERT_EQ(empty.ranges.size(), 0); ASSERT_EQ(empty.logs.size(), 0); ASSERT_EQ(empty.snapshots.size(), 0); printf("BackupContainerTest URL=%s PASSED.\n", url.c_str()); - - return Void(); } TEST_CASE("/backup/containers/localdir/unencrypted") { - wait(testBackupContainer(format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), {}, {})); - return Void(); + co_await testBackupContainer(format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), + Optional(), + Optional()); } TEST_CASE("/backup/containers/localdir/encrypted") { - wait(testBackupContainer(format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), - {}, - format("%s/test_encryption_key", params.getDataDir().c_str()))); - return Void(); + co_await testBackupContainer(format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), + Optional(), + format("%s/test_encryption_key", params.getDataDir().c_str())); } TEST_CASE("/backup/containers/url") { if (!g_network->isSimulated()) { const char* url = getenv("FDB_TEST_BACKUP_URL"); ASSERT(url != nullptr); - wait(testBackupContainer(url, {}, {})); + co_await testBackupContainer(url, Optional(), Optional()); } - return Void(); } TEST_CASE("/backup/containers_list") { if (!g_network->isSimulated()) { - state const char* url = getenv("FDB_TEST_BACKUP_URL"); + const char* url = getenv("FDB_TEST_BACKUP_URL"); ASSERT(url != nullptr); printf("Listing %s\n", url); - std::vector urls = wait(IBackupContainer::listContainers(url, {})); + std::vector urls = co_await IBackupContainer::listContainers(url, {}); for (auto& u : urls) { printf("%s\n", u.c_str()); } } - return Void(); } TEST_CASE("/backup/time") { @@ -2376,15 +2369,15 @@ TEST_CASE("/backup/logs_continuous") { void printFileList(BackupFileList& backupFileList) { printf("\nRangeFiles count:%lu", backupFileList.ranges.size()); - for (auto r : backupFileList.ranges) + for (const auto& r : backupFileList.ranges) printf("\n%s", r.toString().c_str()); printf("\nLogFiles count:%lu", backupFileList.logs.size()); - for (auto l : backupFileList.logs) + for (const auto& l : backupFileList.logs) printf("\n%s", l.toString().c_str()); printf("\nSnapshotFiles count:%lu", backupFileList.snapshots.size()); - for (auto s : backupFileList.snapshots) + for (const auto& s : backupFileList.snapshots) printf("\n%" PRId64 ", %" PRId64 ", %s, %" PRId64 "\n", s.beginVersion, s.endVersion, @@ -2393,45 +2386,46 @@ void printFileList(BackupFileList& backupFileList) { } // Intentionally missing some log range files and checking if the snapshot can be restored. -ACTOR Future testBackupContainerWithMissingLogRanges(std::string url, Optional proxy) { - state FlowLock lock(100e6); +Future testBackupContainerWithMissingLogRanges(std::string url, Optional proxy) { + FlowLock lock(100e6); printf("BackupContainerTest URL %s\n", url.c_str()); - state Reference c = IBackupContainer::openContainer(url, proxy, {}); + Reference c = + IBackupContainer::openContainer(url, proxy, /*encryptionKeyFileName=*/{}, /*encryptionBlockSize=*/0); // Make sure container doesn't exist, then create it. try { - wait(c->deleteContainer()); + co_await c->deleteContainer(); } catch (Error& e) { if (e.code() != error_code_backup_invalid_url && e.code() != error_code_backup_does_not_exist) throw; } - wait(c->create()); - - state Key begin = randomKeyBetween(normalKeys); - state Key end = randomKeyBetween(KeyRangeRef(begin, normalKeys.end)); - state int blockSize = 3 * sizeof(uint32_t) + begin.size() + end.size() + 8; - state std::vector> writes; - state std::pair beginEndKeys = std::make_pair(begin, end); - state std::vector snapshotsMissingLogs; - state Version v = deterministicRandom()->randomInt64(0, std::numeric_limits::max() / 2); - state Version tempLogEnd = 0; - state Version logStart = v; - state Version logEnd = v; - state Version snapshotBeginVersion = v; - state Version snapshotEndVersion = v; - state Version lastMissedLogFileEnd = 0; + co_await c->create(); + + Key begin = randomKeyBetween(normalKeys); + Key end = randomKeyBetween(KeyRangeRef(begin, normalKeys.end)); + int blockSize = 3 * sizeof(uint32_t) + begin.size() + end.size() + 8; + std::vector> writes; + std::pair beginEndKeys = std::make_pair(begin, end); + std::vector snapshotsMissingLogs; + Version v = deterministicRandom()->randomInt64(0, std::numeric_limits::max() / 2); + Version tempLogEnd = 0; + Version logStart = v; + Version logEnd = v; + Version snapshotBeginVersion = v; + Version snapshotEndVersion = v; + Version lastMissedLogFileEnd = 0; // create a random number of snapshots - state int numSnapshots = deterministicRandom()->randomInt(1, 10); + int numSnapshots = deterministicRandom()->randomInt(1, 10); while (numSnapshots) { - state std::vector rangeFileNames; - state std::vector> snapshotBeginEndKeys; + std::vector rangeFileNames; + std::vector> snapshotBeginEndKeys; // create a random number of range files per snapshot - state int numRangeFiles = deterministicRandom()->randomInt(2, 5); + int numRangeFiles = deterministicRandom()->randomInt(2, 5); snapshotBeginVersion = v; while (numRangeFiles) { - state Reference range = wait(c->writeRangeFile(v, 0, v, blockSize)); + Reference range = co_await c->writeRangeFile(v, 0, v, blockSize); writes.push_back(writeAndVerifyFile(c, range, deterministicRandom()->randomInt(0, 2e6), &lock)); rangeFileNames.push_back(range->getFileName()); snapshotBeginEndKeys.push_back(beginEndKeys); @@ -2456,7 +2450,7 @@ ACTOR Future testBackupContainerWithMissingLogRanges(std::string url, Opti while (logStart < logEnd) { tempLogEnd = nextVersion(logStart); if (deterministicRandom()->random01() < 0.5) { - state Reference log = wait(c->writeLogFile(logStart, tempLogEnd, blockSize)); + Reference log = co_await c->writeLogFile(logStart, tempLogEnd, blockSize); writes.push_back(writeAndVerifyFile(c, log, deterministicRandom()->randomInt(0, 2e6), &lock)); } else { // intentionally missing writing of some log files. // If the missing log range falls in the current snapshot range, mark it. @@ -2470,21 +2464,21 @@ ACTOR Future testBackupContainerWithMissingLogRanges(std::string url, Opti --numSnapshots; } - wait(waitForAll(writes)); - state BackupFileList listing = wait(c->dumpFileList()); + co_await waitForAll(writes); + BackupFileList listing = co_await c->dumpFileList(); printFileList(listing); printf("\n\nSnapshots missing logs:"); - state int i = 0; + int i = 0; for (; i < snapshotsMissingLogs.size(); ++i) printf("\nSnapshot%d: %s", i, snapshotsMissingLogs[i] ? "true" : "false"); - state BackupDescription desc = wait(c->describeBackup()); + BackupDescription desc = co_await c->describeBackup(); printf("\n\n%s\n", desc.toString().c_str()); for (i = 0; i < listing.snapshots.size(); ++i) { // Ensure we can restore to the end version of snapshot i - Optional rest = wait(c->getRestoreSet(listing.snapshots[i].endVersion)); + Optional rest = co_await c->getRestoreSet(listing.snapshots[i].endVersion); if (snapshotsMissingLogs[i]) ASSERT(!rest.present()); else @@ -2509,59 +2503,57 @@ ACTOR Future testBackupContainerWithMissingLogRanges(std::string url, Opti } printf("DELETING\n"); - wait(c->deleteContainer()); + co_await c->deleteContainer(); - state Future d = c->describeBackup(); - wait(ready(d)); + Future d = c->describeBackup(); + co_await ready(d); ASSERT(d.isError() && d.getError().code() == error_code_backup_does_not_exist); - BackupFileList empty = wait(c->dumpFileList()); + BackupFileList empty = co_await c->dumpFileList(); ASSERT_EQ(empty.ranges.size(), 0); ASSERT_EQ(empty.logs.size(), 0); ASSERT_EQ(empty.snapshots.size(), 0); printf("BackupContainerTest URL=%s PASSED.\n", url.c_str()); - - return Void(); } TEST_CASE("/backup/containers/localdir/missingLogRangesRestorability") { - wait(testBackupContainerWithMissingLogRanges( - format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), {})); - return Void(); + co_await testBackupContainerWithMissingLogRanges( + format("file://%s/fdb_backups/%llx", params.getDataDir().c_str(), timer_int()), Optional()); } -ACTOR Future testBackupContinuousLogEndVer(std::string url, Optional proxy) { - state FlowLock lock(100e6); +Future testBackupContinuousLogEndVer(std::string url, Optional proxy) { + FlowLock lock(100e6); printf("BackupContainerTest URL %s\n", url.c_str()); - state Reference c = IBackupContainer::openContainer(url, proxy, {}); + Reference c = + IBackupContainer::openContainer(url, proxy, /*encryptionKeyFileName=*/{}, /*encryptionBlockSize=*/0); // Make sure container doesn't exist, then create it. try { - wait(c->deleteContainer()); + co_await c->deleteContainer(); } catch (Error& e) { if (e.code() != error_code_backup_invalid_url && e.code() != error_code_backup_does_not_exist) throw; } - wait(c->create()); + co_await c->create(); - state int blockSize = 1024; - state std::vector rangeFileNames; - state Key begin = randomKeyBetween(normalKeys); - state Key end = randomKeyBetween(KeyRangeRef(begin, normalKeys.end)); - state std::pair beginEndKeys = std::make_pair(begin, end); - state std::vector> snapshotBeginEndKeys; + int blockSize = 1024; + std::vector rangeFileNames; + Key begin = randomKeyBetween(normalKeys); + Key end = randomKeyBetween(KeyRangeRef(begin, normalKeys.end)); + std::pair beginEndKeys = std::make_pair(begin, end); + std::vector> snapshotBeginEndKeys; // writing random number of range files with rangeSize 100 - state std::vector> writes; - state Version snapshotBeginVersion = 10; - state Version snapshotEndVersion = deterministicRandom()->randomInt(500, 1000); - state Version rangeSize = 100; - state Version v = snapshotBeginVersion; - state int numRangeFiles = 0; + std::vector> writes; + Version snapshotBeginVersion = 10; + Version snapshotEndVersion = deterministicRandom()->randomInt(500, 1000); + Version rangeSize = 100; + Version v = snapshotBeginVersion; + int numRangeFiles = 0; while (v <= snapshotEndVersion) { - state Reference range = wait(c->writeRangeFile(v, 0, v, blockSize)); + Reference range = co_await c->writeRangeFile(v, 0, v, blockSize); writes.push_back(writeAndVerifyFile(c, range, 100, &lock)); rangeFileNames.push_back(range->getFileName()); snapshotBeginEndKeys.push_back(beginEndKeys); @@ -2571,11 +2563,11 @@ ACTOR Future testBackupContinuousLogEndVer(std::string url, Optional log = wait(c->writeLogFile(v, v + logSize, blockSize)); + Reference log = co_await c->writeLogFile(v, v + logSize, blockSize); writes.push_back(writeAndVerifyFile(c, log, 100, &lock)); ++numLogFiles; v += logSize; @@ -2584,15 +2576,15 @@ ACTOR Future testBackupContinuousLogEndVer(std::string url, OptionalwriteKeyspaceSnapshotFile( rangeFileNames, snapshotBeginEndKeys, deterministicRandom()->randomInt(0, 2e6), IncludeKeyRangeMap(BUGGIFY))); - wait(waitForAll(writes)); + co_await waitForAll(writes); - state BackupFileList fileList = wait(c->dumpFileList()); + BackupFileList fileList = co_await c->dumpFileList(); printFileList(fileList); ASSERT_EQ(fileList.ranges.size(), numRangeFiles); ASSERT_EQ(fileList.logs.size(), numLogFiles); ASSERT_EQ(fileList.snapshots.size(), 1); - state BackupDescription desc = wait(c->describeBackup()); + BackupDescription desc = co_await c->describeBackup(); printf("\n%s\n", desc.toString().c_str()); ASSERT_EQ(desc.minLogBegin, snapshotBeginVersion); ASSERT_EQ(desc.maxLogEnd, v); @@ -2602,24 +2594,24 @@ ACTOR Future testBackupContinuousLogEndVer(std::string url, OptionalrandomInt(2, 8); + int newNumLogFiles = deterministicRandom()->randomInt(2, 8); numLogFiles += newNumLogFiles; writes.clear(); while (newNumLogFiles) { - Reference log = wait(c->writeLogFile(v, v + logSize, blockSize)); + Reference log = co_await c->writeLogFile(v, v + logSize, blockSize); writes.push_back(writeAndVerifyFile(c, log, 100, &lock)); --newNumLogFiles; v += logSize; } - wait(waitForAll(writes)); + co_await waitForAll(writes); - state BackupFileList fileList1 = wait(c->dumpFileList()); + BackupFileList fileList1 = co_await c->dumpFileList(); printFileList(fileList1); ASSERT_EQ(fileList1.ranges.size(), numRangeFiles); ASSERT_EQ(fileList1.logs.size(), numLogFiles); ASSERT_EQ(fileList1.snapshots.size(), 1); - state BackupDescription desc1 = wait(c->describeBackup()); + BackupDescription desc1 = co_await c->describeBackup(); printf("\n%s\n", desc1.toString().c_str()); ASSERT_EQ(desc1.minLogBegin, snapshotBeginVersion); ASSERT_EQ(desc1.maxLogEnd, v); @@ -2627,14 +2619,11 @@ ACTOR Future testBackupContinuousLogEndVer(std::string url, Optional()); } } // namespace backup_test diff --git a/fdbclient/BackupContainerLocalDirectory.actor.cpp b/fdbclient/BackupContainerLocalDirectory.cpp similarity index 86% rename from fdbclient/BackupContainerLocalDirectory.actor.cpp rename to fdbclient/BackupContainerLocalDirectory.cpp index a7e63c39fed..00b83a79b0b 100644 --- a/fdbclient/BackupContainerLocalDirectory.actor.cpp +++ b/fdbclient/BackupContainerLocalDirectory.cpp @@ -1,5 +1,5 @@ /* - * BackupContainerLocalDirectory.actor.cpp + * BackupContainerLocalDirectory.cpp * * This source file is part of the FoundationDB open source project * @@ -18,15 +18,14 @@ * limitations under the License. */ -#include "fdbclient/BackupContainerLocalDirectory.h" -#include "fdbrpc/AsyncFileReadAhead.actor.h" +#include "BackupContainerLocalDirectory.h" +#include "fdbrpc/AsyncFileEncrypted.h" +#include "fdbrpc/AsyncFileReadAhead.h" #include "flow/IAsyncFile.h" #include "flow/FaultInjection.h" -#include "flow/Platform.actor.h" #include "flow/Platform.h" #include "fdbrpc/simulator.h" #include "fdbrpc/SimulatorProcessInfo.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { @@ -73,17 +72,15 @@ class BackupFile : public IBackupFile, ReferenceCounted { return r; } - ACTOR static Future finish_impl(Reference f) { - wait(f->flush(f->m_buffer.size())); - wait(f->m_file->truncate(f->size())); // Some IAsyncFile implementations extend in whole block sizes. - wait(f->m_file->sync()); + static Future finish_impl(Reference f) { + co_await f->flush(f->m_buffer.size()); + co_await f->m_file->truncate(f->size()); // Some IAsyncFile implementations extend in whole block sizes. + co_await f->m_file->sync(); std::string name = f->m_file->getFilename(); f->m_file.clear(); - wait(IAsyncFileSystem::filesystem()->renameFile(name, f->m_finalFullPath)); + co_await IAsyncFileSystem::filesystem()->renameFile(name, f->m_finalFullPath); INJECT_BLOB_FAULT(http_request_failed, "BackupContainerLocalDirectory::finish"); - - return Void(); } int64_t size() const override { return m_buffer.size() + m_writeOffset; } @@ -101,9 +98,9 @@ class BackupFile : public IBackupFile, ReferenceCounted { int m_blockSize; }; -ACTOR static Future listFiles_impl(std::string path, std::string m_path) { - state std::vector files; - wait(platform::findFilesRecursivelyAsync(joinPath(m_path, path), &files)); +static Future listFiles_impl(std::string path, std::string m_path) { + std::vector files; + co_await platform::findFilesRecursivelyAsync(joinPath(m_path, path), &files); BackupContainerFileSystem::FilesAndSizesT results; @@ -124,7 +121,7 @@ ACTOR static Future listFiles_impl(st INJECT_BLOB_FAULT(http_request_failed, "BackupContainerLocalDirectory::listFiles"); - return results; + co_return results; } } // namespace @@ -141,8 +138,10 @@ std::string BackupContainerLocalDirectory::getURLFormat() { } BackupContainerLocalDirectory::BackupContainerLocalDirectory(const std::string& url, - const Optional& encryptionKeyFileName) { + const Optional& encryptionKeyFileName, + int encryptionBlockSize) { setEncryptionKey(encryptionKeyFileName); + this->encryptionBlockSize = encryptionBlockSize; std::string path; if (url.find("file://") != 0) { @@ -233,10 +232,6 @@ Future BackupContainerLocalDirectory::exists() { Future> BackupContainerLocalDirectory::readFile(const std::string& path) { int flags = IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED; - // Skip encryption for properties/ folder - if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { - flags |= IAsyncFile::OPEN_ENCRYPTED; - } INJECT_BLOB_FAULT(http_request_failed, "BackupContainerLocalDirectory::readFile"); // Simulation does not properly handle opening the same file from multiple machines using a shared filesystem, // so create a symbolic link to make each file opening appear to be unique. This could also work in production @@ -261,6 +256,14 @@ Future> BackupContainerLocalDirectory::readFile(const std: // by the same process that failed the first task execution anyway, which is a very rare case. #endif Future> f = IAsyncFileSystem::filesystem()->open(fullPath, flags, 0644); + // Skip encryption for properties/ folder + if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { + int encBlockSize = encryptionBlockSize; + f = map(f, [encBlockSize](Reference r) { + return Reference( + makeReference(r, AsyncFileEncrypted::Mode::READ_ONLY, encBlockSize)); + }); + } if (g_network->isSimulated()) { int blockSize = 0; @@ -277,11 +280,11 @@ Future> BackupContainerLocalDirectory::readFile(const std: } ASSERT(blockSize > 0); - return map(f, [=](Reference fr) { + return map(f, [=](Reference fr) -> Reference { int readAhead = deterministicRandom()->randomInt(0, 3); int reads = deterministicRandom()->randomInt(1, 3); int cacheSize = deterministicRandom()->randomInt(0, 3); - return Reference(new AsyncFileReadAheadCache(fr, blockSize, readAhead, reads, cacheSize)); + return makeReference(fr, blockSize, readAhead, reads, cacheSize); }); } @@ -292,15 +295,21 @@ Future> BackupContainerLocalDirectory::writeFile(const st INJECT_BLOB_FAULT(http_request_failed, "BackupContainerLocalDirectory::writeFile"); int flags = IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_READWRITE; - // Skip encryption for properties/ folder - if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { - flags |= IAsyncFile::OPEN_ENCRYPTED; - } std::string fullPath = joinPath(m_path, path); platform::createDirectory(parentDirectory(fullPath)); std::string temp = fullPath + "." + deterministicRandom()->randomUniqueID().toString() + ".temp"; Future> f = IAsyncFileSystem::filesystem()->open(temp, flags, 0644); - return map(f, [=](Reference f) { return Reference(new BackupFile(path, f, fullPath)); }); + // Skip encryption for properties/ folder + if (usesEncryption() && !StringRef(path).startsWith("properties/"_sr)) { + int encBlockSize = encryptionBlockSize; + f = map(f, [encBlockSize](Reference r) { + return Reference( + makeReference(r, AsyncFileEncrypted::Mode::APPEND_ONLY, encBlockSize)); + }); + } + return map(f, [=](Reference file) -> Reference { + return makeReference(path, file, fullPath); + }); } Future BackupContainerLocalDirectory::writeEntireFile(const std::string& path, const std::string& contents) { @@ -324,7 +333,7 @@ Future BackupContainerLocalDirectory::deleteContainer(int* pNumDeleted) { // and make sure it has something in it. return map(describeBackup(false, invalidVersion), [=](BackupDescription const& desc) { // If the backup has no snapshots and no logs then it's probably not a valid backup - if (desc.snapshots.size() == 0 && !desc.minLogBegin.present()) + if (desc.snapshots.empty() && !desc.minLogBegin.present()) throw backup_invalid_url(); int count = platform::eraseDirectoryRecursive(m_path); diff --git a/fdbclient/include/fdbclient/BackupContainerLocalDirectory.h b/fdbclient/BackupContainerLocalDirectory.h similarity index 90% rename from fdbclient/include/fdbclient/BackupContainerLocalDirectory.h rename to fdbclient/BackupContainerLocalDirectory.h index 47d34d87699..40ed369f41f 100644 --- a/fdbclient/include/fdbclient/BackupContainerLocalDirectory.h +++ b/fdbclient/BackupContainerLocalDirectory.h @@ -33,7 +33,9 @@ class BackupContainerLocalDirectory : public BackupContainerFileSystem, static std::string getURLFormat(); - BackupContainerLocalDirectory(const std::string& url, Optional const& encryptionKeyFileName); + BackupContainerLocalDirectory(const std::string& url, + Optional const& encryptionKeyFileName, + int encryptionBlockSize); static Future> listURLs(const std::string& url); diff --git a/fdbclient/BackupTLSConfig.cpp b/fdbclient/BackupTLSConfig.cpp index b866bee9236..a8e1ed79092 100644 --- a/fdbclient/BackupTLSConfig.cpp +++ b/fdbclient/BackupTLSConfig.cpp @@ -34,13 +34,13 @@ void BackupTLSConfig::setupBlobCredentials() { StringRef t((uint8_t*)blobCredsFromENV, strlen(blobCredsFromENV)); do { StringRef file = t.eat(":"); - if (file.size() != 0) + if (!file.empty()) blobCredentials.push_back(file.toString()); - } while (t.size() != 0); + } while (!t.empty()); } // Update the global blob credential files list - std::vector* pFiles = (std::vector*)g_network->global(INetwork::enBlobCredentialFiles); + auto* pFiles = (std::vector*)g_network->global(INetwork::enBlobCredentialFiles); if (pFiles != nullptr) { for (auto& f : blobCredentials) { pFiles->push_back(f); @@ -49,7 +49,7 @@ void BackupTLSConfig::setupBlobCredentials() { } bool BackupTLSConfig::setupTLS() { - if (tlsCertPath.size()) { + if (!tlsCertPath.empty()) { try { setNetworkOption(FDBNetworkOptions::TLS_CERT_PATH, tlsCertPath); } catch (Error& e) { @@ -58,7 +58,7 @@ bool BackupTLSConfig::setupTLS() { } } - if (tlsCAPath.size()) { + if (!tlsCAPath.empty()) { try { setNetworkOption(FDBNetworkOptions::TLS_CA_PATH, tlsCAPath); } catch (Error& e) { @@ -66,9 +66,9 @@ bool BackupTLSConfig::setupTLS() { return false; } } - if (tlsKeyPath.size()) { + if (!tlsKeyPath.empty()) { try { - if (tlsPassword.size()) + if (!tlsPassword.empty()) setNetworkOption(FDBNetworkOptions::TLS_PASSWORD, tlsPassword); setNetworkOption(FDBNetworkOptions::TLS_KEY_PATH, tlsKeyPath); @@ -77,7 +77,7 @@ bool BackupTLSConfig::setupTLS() { return false; } } - if (tlsVerifyPeers.size()) { + if (!tlsVerifyPeers.empty()) { try { setNetworkOption(FDBNetworkOptions::TLS_VERIFY_PEERS, tlsVerifyPeers); } catch (Error& e) { diff --git a/fdbclient/BlobStoreCommon.cpp b/fdbclient/BlobStoreCommon.cpp new file mode 100644 index 00000000000..693f28bce88 --- /dev/null +++ b/fdbclient/BlobStoreCommon.cpp @@ -0,0 +1,1015 @@ +/* + * BlobStoreCommon.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. + */ + +#include "fdbclient/IBlobStore.h" +#include "fdbclient/S3BlobStore.h" +#include "GCSBlobStore.h" +#include "fdbclient/Knobs.h" +#include "flow/Hostname.h" +#include "flow/IAsyncFile.h" +#include "flow/IConnection.h" +#include "flow/Trace.h" +#include "flow/CoroUtils.h" +#include "flow/genericactors.actor.h" + +#include "md5/md5.h" +#include "libb64/encode.h" +#include + +#include +#include +#include + +IBlobStoreEndpoint::ConnectionPoolData::~ConnectionPoolData() { + // In simulation, explicitly close all pooled connections before destruction. + // This satisfies Sim2Conn's assertion: !opened || closedByCaller + // Without this, connections would be destroyed without being closed, causing assertion failures. + if (g_network && g_network->isSimulated()) { + while (!pool.empty()) { + ReusableConnection& rconn = pool.front(); + if (rconn.conn.isValid()) { + rconn.conn->close(); + } + pool.pop(); + } + } +} + +json_spirit::mObject IBlobStoreEndpoint::Stats::getJSON() { + json_spirit::mObject o; + + o["requests_failed"] = requests_failed; + o["requests_successful"] = requests_successful; + o["bytes_sent"] = bytes_sent; + + return o; +} + +IBlobStoreEndpoint::Stats IBlobStoreEndpoint::Stats::operator-(const Stats& rhs) { + Stats r; + r.requests_failed = requests_failed - rhs.requests_failed; + r.requests_successful = requests_successful - rhs.requests_successful; + r.bytes_sent = bytes_sent - rhs.bytes_sent; + return r; +} + +IBlobStoreEndpoint::Stats IBlobStoreEndpoint::s_stats; + +BlobKnobs::BlobKnobs() { + secure_connection = 1; + connect_tries = CLIENT_KNOBS->BLOBSTORE_CONNECT_TRIES; + connect_timeout = CLIENT_KNOBS->BLOBSTORE_CONNECT_TIMEOUT; + max_connection_life = CLIENT_KNOBS->BLOBSTORE_MAX_CONNECTION_LIFE; + request_tries = CLIENT_KNOBS->BLOBSTORE_REQUEST_TRIES; + request_timeout_min = CLIENT_KNOBS->BLOBSTORE_REQUEST_TIMEOUT_MIN; + requests_per_second = CLIENT_KNOBS->BLOBSTORE_REQUESTS_PER_SECOND; + concurrent_requests = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_REQUESTS; + list_requests_per_second = CLIENT_KNOBS->BLOBSTORE_LIST_REQUESTS_PER_SECOND; + write_requests_per_second = CLIENT_KNOBS->BLOBSTORE_WRITE_REQUESTS_PER_SECOND; + read_requests_per_second = CLIENT_KNOBS->BLOBSTORE_READ_REQUESTS_PER_SECOND; + delete_requests_per_second = CLIENT_KNOBS->BLOBSTORE_DELETE_REQUESTS_PER_SECOND; + multipart_max_part_size = CLIENT_KNOBS->BLOBSTORE_MULTIPART_MAX_PART_SIZE; + multipart_min_part_size = CLIENT_KNOBS->BLOBSTORE_MULTIPART_MIN_PART_SIZE; + concurrent_uploads = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_UPLOADS; + concurrent_lists = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_LISTS; + concurrent_reads_per_file = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_READS_PER_FILE; + concurrent_writes_per_file = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_WRITES_PER_FILE; + enable_read_cache = CLIENT_KNOBS->BLOBSTORE_ENABLE_READ_CACHE; + read_block_size = CLIENT_KNOBS->BLOBSTORE_READ_BLOCK_SIZE; + read_ahead_blocks = CLIENT_KNOBS->BLOBSTORE_READ_AHEAD_BLOCKS; + read_cache_blocks_per_file = CLIENT_KNOBS->BLOBSTORE_READ_CACHE_BLOCKS_PER_FILE; + max_send_bytes_per_second = CLIENT_KNOBS->BLOBSTORE_MAX_SEND_BYTES_PER_SECOND; + max_recv_bytes_per_second = CLIENT_KNOBS->BLOBSTORE_MAX_RECV_BYTES_PER_SECOND; + max_delay_retryable_error = CLIENT_KNOBS->BLOBSTORE_MAX_DELAY_RETRYABLE_ERROR; + max_delay_connection_failed = CLIENT_KNOBS->BLOBSTORE_MAX_DELAY_CONNECTION_FAILED; + multipart_retry_delay_ms = CLIENT_KNOBS->BLOBSTORE_MULTIPART_RETRY_DELAY_MS; + sdk_auth = false; + enable_object_integrity_check = CLIENT_KNOBS->BLOBSTORE_ENABLE_OBJECT_INTEGRITY_CHECK; + global_connection_pool = CLIENT_KNOBS->BLOBSTORE_GLOBAL_CONNECTION_POOL; +} + +bool BlobKnobs::set(StringRef name, int value) { +#define TRY_PARAM(n, sn) \ + if (name == #n || name == #sn) { \ + n = value; \ + return true; \ + } + TRY_PARAM(secure_connection, sc) + TRY_PARAM(connect_tries, ct); + TRY_PARAM(connect_timeout, cto); + TRY_PARAM(max_connection_life, mcl); + TRY_PARAM(request_tries, rt); + TRY_PARAM(request_timeout_min, rtom); + // TODO: For backward compatibility because request_timeout was renamed to request_timeout_min + if (name == "request_timeout"_sr || name == "rto"_sr) { + request_timeout_min = value; + return true; + } + TRY_PARAM(requests_per_second, rps); + TRY_PARAM(list_requests_per_second, lrps); + TRY_PARAM(write_requests_per_second, wrps); + TRY_PARAM(read_requests_per_second, rrps); + TRY_PARAM(delete_requests_per_second, drps); + TRY_PARAM(concurrent_requests, cr); + TRY_PARAM(multipart_max_part_size, maxps); + TRY_PARAM(multipart_min_part_size, minps); + TRY_PARAM(multipart_retry_delay_ms, mrd); + TRY_PARAM(concurrent_uploads, cu); + TRY_PARAM(concurrent_lists, cl); + TRY_PARAM(concurrent_reads_per_file, crpf); + TRY_PARAM(concurrent_writes_per_file, cwpf); + TRY_PARAM(enable_read_cache, erc); + TRY_PARAM(read_block_size, rbs); + TRY_PARAM(read_ahead_blocks, rab); + TRY_PARAM(read_cache_blocks_per_file, rcb); + TRY_PARAM(max_send_bytes_per_second, sbps); + TRY_PARAM(max_recv_bytes_per_second, rbps); + TRY_PARAM(max_delay_retryable_error, dre); + TRY_PARAM(max_delay_connection_failed, dcf); + TRY_PARAM(sdk_auth, sa); + TRY_PARAM(enable_object_integrity_check, eoic); + TRY_PARAM(global_connection_pool, gcp); +#undef TRY_PARAM + return false; +} + +// Returns a Blob URL parameter string that specifies all of the non-default options using short names. +std::string BlobKnobs::getURLParameters() const { + static BlobKnobs defaults; + std::string r; +#define _CHECK_PARAM(n, sn) \ + if (n != defaults.n) { \ + r += format("%s%s=%d", r.empty() ? "" : "&", #sn, n); \ + } + _CHECK_PARAM(secure_connection, sc); + _CHECK_PARAM(connect_tries, ct); + _CHECK_PARAM(connect_timeout, cto); + _CHECK_PARAM(max_connection_life, mcl); + _CHECK_PARAM(request_tries, rt); + _CHECK_PARAM(request_timeout_min, rto); + _CHECK_PARAM(requests_per_second, rps); + _CHECK_PARAM(list_requests_per_second, lrps); + _CHECK_PARAM(write_requests_per_second, wrps); + _CHECK_PARAM(read_requests_per_second, rrps); + _CHECK_PARAM(delete_requests_per_second, drps); + _CHECK_PARAM(concurrent_requests, cr); + _CHECK_PARAM(multipart_max_part_size, maxps); + _CHECK_PARAM(multipart_min_part_size, minps); + _CHECK_PARAM(concurrent_uploads, cu); + _CHECK_PARAM(concurrent_lists, cl); + _CHECK_PARAM(concurrent_reads_per_file, crpf); + _CHECK_PARAM(concurrent_writes_per_file, cwpf); + _CHECK_PARAM(enable_read_cache, erc); + _CHECK_PARAM(read_block_size, rbs); + _CHECK_PARAM(read_ahead_blocks, rab); + _CHECK_PARAM(read_cache_blocks_per_file, rcb); + _CHECK_PARAM(max_send_bytes_per_second, sbps); + _CHECK_PARAM(max_recv_bytes_per_second, rbps); + _CHECK_PARAM(sdk_auth, sa); + _CHECK_PARAM(enable_object_integrity_check, eoic); + _CHECK_PARAM(global_connection_pool, gcp); + _CHECK_PARAM(max_delay_retryable_error, dre); + _CHECK_PARAM(max_delay_connection_failed, dcf); +#undef _CHECK_PARAM + return r; +} + +Future> IBlobStoreEndpoint::tryReadJSONFile(std::string path) { + std::string content; + + // Event type to be logged in the event of an exception + const char* errorEventType = "BlobCredentialFileError"; + + try { + Reference f = co_await IAsyncFileSystem::filesystem()->open( + path, IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED, 0); + int64_t size = co_await f->size(); + Standalone buf = makeString(size); + int r = co_await f->read(mutateString(buf), size, 0); + ASSERT(r == size); + content = buf.toString(); + + // Any exceptions from here forward are parse failures + errorEventType = "BlobCredentialFileParseFailed"; + json_spirit::mValue json; + json_spirit::read_string(content, json); + if (json.type() == json_spirit::obj_type) + co_return json.get_obj(); + else + TraceEvent(SevWarn, "BlobCredentialFileNotJSONObject").suppressFor(60).detail("File", path); + + } catch (Error& e) { + if (e.code() != error_code_actor_cancelled) + TraceEvent(SevWarn, errorEventType).errorUnsuppressed(e).suppressFor(60).detail("File", path); + } + + co_return Optional(); +} + +std::string IBlobStoreEndpoint::getResourceURL(std::string resource, std::string params) const { + std::string hostPort = host; + if (!service.empty()) { + hostPort.append(":"); + hostPort.append(service); + } + + std::string r = format("blobstore://@%s/%s", hostPort.c_str(), resource.c_str()); + + // Get params that are deviations from knob defaults + std::string knobParams = knobs.getURLParameters(); + if (!knobParams.empty()) { + if (!params.empty()) { + params.append("&"); + } + params.append(knobParams); + } + + for (const auto& [k, v] : extraHeaders) { + if (!params.empty()) { + params.append("&"); + } + params.append("header="); + params.append(k); + params.append(":"); + params.append(v); + } + + if (!params.empty()) + r.append("?").append(params); + + return r; +} + +Reference IBlobStoreEndpoint::fromString(const std::string& url, + const Optional& proxy, + std::string* resourceFromURL, + std::string* error, + ParametersT* ignored_parameters) { + if (resourceFromURL) + resourceFromURL->clear(); + + try { + // Replace HTML-encoded ampersands with raw ampersands + std::string decoded_url = url; + size_t pos = 0; + while ((pos = decoded_url.find("&", pos)) != std::string::npos) { + decoded_url.replace(pos, 5, "&"); + pos += 1; + } + + // Also handle double-encoded ampersands + pos = 0; + while ((pos = decoded_url.find("&amp;", pos)) != std::string::npos) { + decoded_url.replace(pos, 9, "&"); + pos += 1; + } + + StringRef t(decoded_url); + StringRef prefix = t.eat("://"); + if (prefix != "blobstore"_sr) + throw format("Invalid blobstore URL prefix '%s'", prefix.toString().c_str()); + + Optional proxyHost, proxyPort; + if (proxy.present()) { + StringRef proxyRef(proxy.get()); + if (proxy.get().find("://") != std::string::npos) { + StringRef proxyPrefix = proxyRef.eat("://"); + if (proxyPrefix != "http"_sr) { + throw format("Invalid proxy URL prefix '%s'. Either don't use a prefix, or use http://", + proxyPrefix.toString().c_str()); + } + } + std::string proxyBody = proxyRef.eat().toString(); + if (!Hostname::isHostname(proxyBody) && !NetworkAddress::parseOptional(proxyBody).present()) { + throw format("'%s' is not a valid value for proxy. Format should be either IP:port or host:port.", + proxyBody.c_str()); + } + StringRef p(proxyBody); + proxyHost = p.eat(":").toString(); + proxyPort = p.eat().toString(); + } + + Optional cred; + if (url.find("@") != std::string::npos) { + cred = t.eat("@"); + } + uint8_t foundSeparator = 0; + StringRef hostPort = t.eatAny("/?", &foundSeparator); + StringRef resource; + if (foundSeparator == '/') { + resource = t.eat("?"); + } + + // hostPort is at least a host or IP address, optionally followed by :portNumber or :serviceName + StringRef h(hostPort); + StringRef host = h.eat(":"); + if (host.size() == 0) + throw std::string("host cannot be empty"); + + StringRef service = h.eat(); + + std::string region; + std::string gcsProjectId; + std::string provider = "s3"; + + BlobKnobs knobs; + HTTP::Headers extraHeaders; + while (1) { + StringRef name = t.eat("="); + if (name.size() == 0) + break; + StringRef value = t.eat("&"); + + // Special case for header + if (name == "header"_sr) { + StringRef originalValue = value; + StringRef headerFieldName = value.eat(":"); + StringRef headerFieldValue = value; + if (headerFieldName.size() == 0 || headerFieldValue.size() == 0) { + throw format("'%s' is not a valid value for '%s' parameter. Format is : " + "where strings are not empty.", + originalValue.toString().c_str(), + name.toString().c_str()); + } + std::string& fieldValue = extraHeaders[headerFieldName.toString()]; + // RFC 2616 section 4.2 says header field names can repeat but only if it is valid to concatenate + // their values with comma separation + if (!fieldValue.empty()) { + fieldValue.append(","); + } + fieldValue.append(headerFieldValue.toString()); + continue; + } + + // overwrite region from parameter + if (name == "region"_sr) { + region = value.toString(); + continue; + } + + if (name == "gcs_project_id"_sr || name == "gcspid"_sr) { + gcsProjectId = value.toString(); + continue; + } + + if (name == "provider"_sr || name == "p"_sr) { + provider = value.toString(); + continue; + } + + // See if the parameter is a knob + // First try setting a dummy value (all knobs are currently numeric) just to see if this parameter is + // known. If it is, then we will set it to a good value or throw below, so the dummy set has no bad + // side effects. + bool known = knobs.set(name, 0); + + // If the parameter is not known then throw unless there is an ignored_parameters set to add it to + if (!known) { + if (ignored_parameters == nullptr) { + throw format("%s is not a valid parameter name", name.toString().c_str()); + } + (*ignored_parameters)[name.toString()] = value.toString(); + continue; + } + + // The parameter is known so it must be numeric and valid. + char* valueEnd = nullptr; + std::string s = value.toString(); + long int ivalue = strtol(s.c_str(), &valueEnd, 10); + if (*valueEnd || (ivalue == 0 && s != "0") || + (((ivalue == LONG_MAX) || (ivalue == LONG_MIN)) && errno == ERANGE)) + throw format("%s is not a valid value for %s", s.c_str(), name.toString().c_str()); + + // It should not be possible for this set to fail now since the dummy set above had to have worked. + ASSERT(knobs.set(name, ivalue)); + } + + if (resourceFromURL != nullptr) + *resourceFromURL = resource.toString(); + + if (provider == "gcs") { + return makeReference( + host.toString(), service.toString(), proxyHost, proxyPort, cred, gcsProjectId, knobs, extraHeaders); + } + + return makeReference( + host.toString(), service.toString(), region, proxyHost, proxyPort, cred, knobs, extraHeaders); + + } catch (std::string& err) { + if (error != nullptr) + *error = err; + TraceEvent(SevWarnAlways, "BlobStoreEndpointBadURL") + .suppressFor(60) + .detail("Description", err) + .detail("Format", getURLFormat()) + .detail("URL", url); + throw backup_invalid_url(); + } +} + +Future updateSecret_impl(Reference b) { + auto* pFiles = (std::vector*)g_network->global(INetwork::enBlobCredentialFiles); + if (pFiles == nullptr) + co_return; + + std::vector>> reads; + for (auto& f : *pFiles) + reads.push_back(IBlobStoreEndpoint::tryReadJSONFile(f)); + + co_await waitForAll(reads); + + std::string credentialsFileKey = b->credentialFileKey(); + + int invalid = 0; + + for (auto& f : reads) { + // If value not present then the credentials file wasn't readable or valid. Continue to check other results. + if (!f.get().present()) { + TraceEvent(SevWarn, "BlobStoreCredentialFileNotReadable") + .detail("Endpoint", b->host) + .detail("Service", b->service); + ++invalid; + continue; + } + + JSONDoc doc(f.get().get()); + if (doc.has("accounts") && doc.last().type() == json_spirit::obj_type) { + JSONDoc accounts(doc.last().get_obj()); + if (accounts.has(credentialsFileKey, false) && accounts.last().type() == json_spirit::obj_type) { + JSONDoc account(accounts.last()); + if (b->extractCredentialFields(account)) { + co_return; + } + } + } + } + + // If any sources were invalid + if (invalid > 0) { + TraceEvent(SevWarn, "BlobStoreCredentialFileInvalid") + .detail("Endpoint", b->host) + .detail("Service", b->service) + .detail("Invalid", invalid); + throw backup_auth_unreadable(); + } + + // All sources were valid but didn't contain the desired info + TraceEvent(SevWarn, "BlobStoreCredentialsMissing") + .detail("Endpoint", b->host) + .detail("Service", b->service) + .detail("Reason", "No valid credentials found"); + throw backup_auth_missing(); +} + +Future IBlobStoreEndpoint::updateSecret() { + return updateSecret_impl(Reference::addRef(this)); +} + +AsyncResult listObjects_impl( + Reference bstore, + std::string bucket, + Optional prefix, + Optional delimiter, + int maxDepth, + std::function recurseFilterUnsafe) { + // C++20 coroutine safety: copy const& params to survive across suspension. + auto recurseFilter = recurseFilterUnsafe; + + IBlobStoreEndpoint::ListResult results; + PromiseStream resultStream; + Future done = bstore->listObjectsStream(bucket, resultStream, prefix, delimiter, maxDepth, recurseFilter); + // Wrap done in an actor which sends end_of_stream because list does not so that many lists can write to the same + // stream + done = map(done, [=](Void) mutable { + resultStream.sendError(end_of_stream()); + return Void(); + }); + + try { + while (true) { + // Preserve actor choose{} semantics: if the stream result and completion are both ready, + // drain the stream item before honoring done. Using race(done, stream) drops the final + // stream item because race() breaks ties by argument order. + auto res = co_await race(resultStream.getFuture(), done); + if (res.index() == 0) { + IBlobStoreEndpoint::ListResult info = std::get<0>(std::move(res)); + + results.commonPrefixes.insert( + results.commonPrefixes.end(), info.commonPrefixes.begin(), info.commonPrefixes.end()); + results.objects.insert(results.objects.end(), info.objects.begin(), info.objects.end()); + } else if (res.index() == 1) { + done = Never(); + } else { + UNREACHABLE(); + } + } + } catch (Error& e) { + if (e.code() != error_code_end_of_stream) + throw; + } + + co_return results; +} + +AsyncResult IBlobStoreEndpoint::listObjects( + std::string const& bucket, + Optional prefix, + Optional delimiter, + int maxDepth, + std::function recurseFilter) { + return listObjects_impl( + Reference::addRef(this), bucket, prefix, delimiter, maxDepth, recurseFilter); +} + +Future deleteRecursively_impl(Reference b, + std::string bucket, + std::string prefix, + int* pNumDeleted, + int64_t* pBytesDeleted) { + PromiseStream resultStream; + // Start a recursive parallel listing which will send results to resultStream as they are received + Future done = b->listObjectsStream(bucket, resultStream, prefix, '/', std::numeric_limits::max()); + // Wrap done in an actor which will send end_of_stream since listObjectsStream() does not (so that many calls can + // write to the same stream) + done = map(done, [=](Void) mutable { + resultStream.sendError(end_of_stream()); + return Void(); + }); + + std::list> deleteFutures; + try { + while (true) { + // Preserve actor choose{} semantics: if the stream result and completion are both ready, + // drain the stream item before honoring done. Using race(done, stream) drops the final + // stream item because race() breaks ties by argument order. + auto res = co_await race(resultStream.getFuture(), done); + if (res.index() == 0) { + IBlobStoreEndpoint::ListResult list = std::get<0>(std::move(res)); + + for (auto& object : list.objects) { + deleteFutures.push_back(map(b->deleteObject(bucket, object.name), [=](Void) -> Void { + if (pNumDeleted != nullptr) { + ++*pNumDeleted; + } + if (pBytesDeleted != nullptr) { + *pBytesDeleted += object.size; + } + return Void(); + })); + } + } else if (res.index() == 1) { + done = Never(); + } else { + UNREACHABLE(); + } + + // This is just a precaution to avoid having too many outstanding delete actors waiting to run + while (deleteFutures.size() > CLIENT_KNOBS->BLOBSTORE_CONCURRENT_REQUESTS) { + co_await deleteFutures.front(); + deleteFutures.pop_front(); + } + } + } catch (Error& e) { + if (e.code() != error_code_end_of_stream) + throw; + } + + while (!deleteFutures.empty()) { + co_await deleteFutures.front(); + deleteFutures.pop_front(); + } +} + +Future IBlobStoreEndpoint::deleteRecursively(std::string const& bucket, + std::string prefix, + int* pNumDeleted, + int64_t* pBytesDeleted) { + return deleteRecursively_impl( + Reference::addRef(this), bucket, prefix, pNumDeleted, pBytesDeleted); +} + +Future writeEntireFile_impl(Reference bstore, + std::string bucket, + std::string object, + std::string content) { + UnsentPacketQueue packets; + if (content.size() > bstore->knobs.multipart_max_part_size) + throw file_too_large(); + + PacketWriter pw(packets.getWriteBuffer(content.size()), nullptr, Unversioned()); + pw.serializeBytes(content); + + // Yield because we may have just had to copy several MB's into packet buffer chain and next we have to calculate a + // hash of it. + // TODO: If this actor is used to send large files then combine the summing and packetization into a loop + // with a yield() every 20k or so. + co_await yield(); + + // If enable_object_integrity_check is true, calculate the sha256 sum of the content. + // Otherwise, do md5. The hash type must match what the provider's writeEntireFileFromBuffer expects. + std::string contentHash; + if (bstore->knobs.enable_object_integrity_check) { + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, content.data(), content.size()); + SHA256_Final(hash, &sha256); + contentHash = base64::encoder::from_string(std::string(reinterpret_cast(hash), SHA256_DIGEST_LENGTH)); + contentHash.resize(contentHash.size() - 1); + } else { + MD5_CTX sum; + ::MD5_Init(&sum); + ::MD5_Update(&sum, content.data(), content.size()); + std::string sumBytes; + sumBytes.resize(16); + ::MD5_Final((unsigned char*)sumBytes.data(), &sum); + contentHash = base64::encoder::from_string(sumBytes); + contentHash.resize(contentHash.size() - 1); + } + + co_await bstore->writeEntireFileFromBuffer(bucket, object, &packets, content.size(), contentHash); +} + +Future IBlobStoreEndpoint::writeEntireFile(std::string const& bucket, + std::string const& object, + std::string const& content) { + return writeEntireFile_impl(Reference::addRef(this), bucket, object, content); +} + +Future connect_impl(Reference b, bool* reusingConn) { + // First try to get a connection from the pool + *reusingConn = false; + while (!b->connectionPool->pool.empty()) { + IBlobStoreEndpoint::ReusableConnection rconn = b->connectionPool->pool.front(); + b->connectionPool->pool.pop(); + + // If the connection expires in the future then return it + if (rconn.expirationTime > now()) { + *reusingConn = true; + ++b->blobStats->reusedConnections; + TraceEvent("IBlobStoreEndpointReusingConnected") + .suppressFor(60) + .detail("RemoteEndpoint", rconn.conn->getPeerAddress()) + .detail("ExpiresIn", rconn.expirationTime - now()) + .detail("Proxy", b->proxyHost.orDefault("")); + co_return rconn; + } + ++b->blobStats->expiredConnections; + } + ++b->blobStats->newConnections; + std::string host = b->host, service = b->service; + TraceEvent(SevDebug, "IBlobStoreEndpointBuildingNewConnection") + .detail("UseProxy", b->useProxy) + .detail("TLS", b->knobs.secure_connection == 1) + .detail("Host", host) + .detail("Service", service) + .log(); + if (service.empty()) { + if (b->useProxy) { + fprintf(stderr, "ERROR: Port can't be empty when using HTTP proxy.\n"); + throw connection_failed(); + } + service = b->knobs.secure_connection ? "https" : "http"; + } + bool isTLS = b->knobs.isTLS(); + Reference conn; + if (b->useProxy) { + if (isTLS) { + conn = co_await HTTP::proxyConnect(host, service, b->proxyHost.get(), b->proxyPort.get()); + } else { + host = b->proxyHost.get(); + service = b->proxyPort.get(); + conn = co_await INetworkConnections::net()->connect(host, service, false); + } + } else { + conn = co_await INetworkConnections::net()->connect(host, service, isTLS); + } + ASSERT(conn.isValid()); + co_await conn->connectHandshake(); + + TraceEvent("IBlobStoreEndpointNewConnectionSuccess") + .suppressFor(60) + .detail("RemoteEndpoint", conn->getPeerAddress()) + .detail("ExpiresIn", b->knobs.max_connection_life) + .detail("Proxy", b->proxyHost.orDefault("")); + + if (b->lookupSecretOnEachRequest()) { + co_await b->updateSecret(); + } + + co_return IBlobStoreEndpoint::ReusableConnection({ conn, now() + b->knobs.max_connection_life }); +} + +Future IBlobStoreEndpoint::connect(bool* reusingConn) { + return connect_impl(Reference::addRef(this), reusingConn); +} + +void IBlobStoreEndpoint::returnConnection(ReusableConnection& rconn) { + // If it expires in the future then add it to the pool in the front + if (rconn.expirationTime > now()) { + connectionPool->pool.push(rconn); + } else { + ++blobStats->expiredConnections; + } + rconn.conn = Reference(); +} + +// Do a request, get a Response. +// Request content is provided as UnsentPacketQueue *pContent which will be depleted as bytes are sent but the queue +// itself must live for the life of this actor and be destroyed by the caller. +Future> doRequest_impl(Reference bstore, + std::string verb, + std::string resource, + HTTP::Headers headers, + UnsentPacketQueue* pContent, + int contentLen, + std::set successCodes) { + UnsentPacketQueue contentCopy; + auto req = makeReference(); + req->verb = verb; + req->data.content = &contentCopy; + req->data.contentLen = contentLen; + + if (resource.empty()) { + resource = "/"; + } + + // For requests with content to upload, the request timeout should be at least twice the amount of time + // it would take to upload the content given the upload bandwidth and concurrency limits. + int bandwidthThisRequest = 1 + bstore->knobs.max_send_bytes_per_second / bstore->knobs.concurrent_uploads; + int contentUploadSeconds = contentLen / bandwidthThisRequest; + int requestTimeout = std::max(bstore->knobs.request_timeout_min, 3 * contentUploadSeconds); + + co_await bstore->concurrentRequests.take(); + FlowLock::Releaser globalReleaser(bstore->concurrentRequests, 1); + + int maxTries = std::min(bstore->knobs.request_tries, bstore->knobs.connect_tries); + int thisTry = 1; + double nextRetryDelay = 2.0; + bool retryExtended = false; + + while (true) { + Optional err; + Optional remoteAddress; + bool connectionEstablished = false; + Reference r; + std::string canonicalURI = resource; + UID connID = UID(); + double reqStartTimer{ 0 }; + double connectStartTimer = g_network->timer(); + bool reusingConn = false; + bool fastRetry = false; + IBlobStoreEndpoint::ReusableConnection rconn; + + // Reset headers to initial state for this retry attempt to prevent header accumulation + req->data.headers = headers; + req->data.headers["Host"] = bstore->host; + + // Merge extraHeaders into headers + for (const auto& [k, v] : bstore->extraHeaders) { + std::string& fieldValue = req->data.headers[k]; + if (!fieldValue.empty()) { + fieldValue.append(","); + } + fieldValue.append(v); + } + + try { + Future frconn = bstore->connect(&reusingConn); + + // Make a shallow copy of the queue + req->data.content->discardAll(); + if (pContent != nullptr) { + PacketBuffer* pFirst = pContent->getUnsent(); + PacketBuffer* pLast = nullptr; + for (PacketBuffer* p = pFirst; p != nullptr; p = p->nextPacketBuffer()) { + p->addref(); + p->bytes_sent = 0; // Reset sent count on each buffer + pLast = p; + } + req->data.content->prependWriteBuffer(pFirst, pLast); + } + + rconn = co_await timeoutError(frconn, bstore->knobs.connect_timeout); + connectionEstablished = true; + connID = rconn.conn->getDebugID(); + reqStartTimer = g_network->timer(); + + // Provider hook: pre-retry check + if (retryExtended) { + bool proceed = co_await bstore->preRetryCheck(verb, resource, rconn, requestTimeout, retryExtended); + if (!proceed) { + bstore->returnConnection(rconn); + continue; + } + } + + // Finish/update the request headers (which includes auth headers). + // This must be done AFTER the connection is ready because credentials may be + // refreshed when a new connection is established. + bstore->setRequestHeaders(verb, resource, req->data.headers); + canonicalURI = bstore->normalizeResourceForRequest(resource); + + // Has to be in absolute-form. + if (bstore->useProxy && bstore->knobs.secure_connection == 0) { + canonicalURI = "http://" + bstore->host + ":" + bstore->service + canonicalURI; + } + + req->resource = canonicalURI; + + remoteAddress = rconn.conn->getPeerAddress(); + co_await bstore->requestRate->getAllowance(1); + + Future> reqF = + HTTP::doRequest(rconn.conn, req, bstore->sendRate, &bstore->s_stats.bytes_sent, bstore->recvRate); + + if (reqF.isReady() && reusingConn) { + fastRetry = true; + } + + Reference _r = co_await timeoutError(reqF, requestTimeout); + r = _r; + + bstore->simulateRequestFailure(verb, resource, r); + + // Since the response was parsed successfully reuse the connection unless we + // received the "Connection: close" header. + if (r->data.headers["Connection"] != "close") { + bstore->returnConnection(rconn); + } else { + ++bstore->blobStats->expiredConnections; + } + rconn.conn.clear(); + + } catch (Error& e) { + TraceEvent(SevWarn, "BlobStoreDoRequestError") + .errorUnsuppressed(e) + .detail("Verb", verb) + .detail("Resource", resource); + // Close the connection on error to satisfy Sim2Conn's assertion in simulation + if (connectionEstablished && rconn.conn.isValid()) { + rconn.conn->close(); + rconn.conn.clear(); + } + if (e.code() == error_code_actor_cancelled) + throw; + err = e; + } + + double end = g_network->timer(); + double connectDuration = reqStartTimer - connectStartTimer; + double reqDuration = end - reqStartTimer; + bstore->blobStats->requestLatency.addMeasurement(reqDuration); + + // If err is not present then r is valid. If r->code is in successCodes then record the successful request and + // return r. + if (!err.present() && successCodes.contains(r->code)) { + bstore->s_stats.requests_successful++; + ++bstore->blobStats->requestsSuccessful; + co_return r; + } + + bstore->s_stats.requests_failed++; + ++bstore->blobStats->requestsFailed; + + // All errors in err are potentially retryable as well as certain HTTP response codes... + // But only if our previous attempt was not the last allowable try. + bool retryable = err.present() || r->code == 500 || r->code == 502 || r->code == 503 || r->code == 429; + retryable = retryable && (thisTry < maxTries); + + if (!retryable || !err.present()) { + fastRetry = false; + } + + TraceEvent event(SevWarn, + (retryable || retryExtended) ? (fastRetry ? "BlobStoreEndpointRequestFailedFastRetryable" + : "BlobStoreEndpointRequestFailedRetryable") + : "BlobStoreEndpointRequestFailed"); + + bool connectionFailed = false; + if (err.present()) { + event.errorUnsuppressed(err.get()); + if (err.get().code() == error_code_connection_failed) { + connectionFailed = true; + } + } + event.suppressFor(1); + if (!err.present()) { + event.detail("ResponseCode", r->code); + } + + // Provider hook: process failure + if (!err.present()) { + bstore->processRequestFailure(r, event, retryExtended); + } + + event.detail("ConnectionEstablished", connectionEstablished); + event.detail("ReusingConn", reusingConn); + if (connectionEstablished) { + event.detail("ConnID", connID); + event.detail("ConnectDuration", connectDuration); + event.detail("ReqDuration", reqDuration); + } + + if (remoteAddress.present()) + event.detail("RemoteEndpoint", remoteAddress.get()); + else + event.detail("RemoteHost", bstore->host); + + event.detail("Verb", verb) + .detail("Resource", resource) + .detail("ThisTry", thisTry) + .detail("URI", canonicalURI) + .detail("Proxy", bstore->proxyHost.orDefault("")); + + // If r is not valid or not code 429 then increment the try count. + // 429's will not count against the attempt limit. Also skip incrementing the retry count for fast retries. + if (!fastRetry && (!r || r->code != 429)) + ++thisTry; + + if (fastRetry) { + ++bstore->blobStats->fastRetries; + co_await delay(0); + } else if (retryable || retryExtended) { + // We will wait delay seconds before the next retry, start with nextRetryDelay. + double delaySeconds = nextRetryDelay; + // connectionFailed is treated specially + double limit = + connectionFailed ? bstore->knobs.max_delay_connection_failed : bstore->knobs.max_delay_retryable_error; + // Double but limit the *next* nextRetryDelay. + nextRetryDelay = std::min(nextRetryDelay * 2, limit); + // If r is valid then obey the Retry-After response header if present. + if (r) { + auto iRetryAfter = r->data.headers.find("Retry-After"); + if (iRetryAfter != r->data.headers.end()) { + event.detail("RetryAfterHeader", iRetryAfter->second); + char* pEnd; + double retryAfter = strtod(iRetryAfter->second.c_str(), &pEnd); + if (*pEnd) + retryAfter = 300; + delaySeconds = std::max(delaySeconds, retryAfter); + } + } + + event.detail("RetryDelay", delaySeconds); + co_await ::delay(delaySeconds); + } else { + // We can't retry, so throw something. + if (r && r->code == 406) + throw http_not_accepted(); + + // This error code means the authentication header was not accepted, likely the account or key is wrong. + if (r && r->code == 401) + throw http_auth_failed(); + + // Recognize and throw specific errors + if (err.present()) { + int code = err.get().code(); + // If we get a timed_out error during the connect() phase, we'll call that connection_failed + // despite the fact that there was technically never a 'connection' to begin with. It differentiates + // between an active connection timing out vs a connection timing out, though not between an active + // connection failing vs connection attempt failing. + if (code == error_code_timed_out && !connectionEstablished) { + TraceEvent(SevWarn, "BlobStoreEndpointConnectTimeout") + .suppressFor(60) + .detail("Timeout", requestTimeout); + throw connection_failed(); + } + + // TODO: Add more error types? + if (code == error_code_timed_out || code == error_code_connection_failed || + code == error_code_lookup_failed) + throw err.get(); + } + + throw http_request_failed(); + } + } +} + +Future> IBlobStoreEndpoint::doRequest(std::string const& verb, + std::string const& resource, + const HTTP::Headers& headers, + UnsentPacketQueue* pContent, + int contentLen, + std::set successCodes) { + return doRequest_impl( + Reference::addRef(this), verb, resource, headers, pContent, contentLen, successCodes); +} diff --git a/fdbclient/BulkLoading.cpp b/fdbclient/BulkLoading.cpp index eba11f76f8e..ed31f040cc3 100644 --- a/fdbclient/BulkLoading.cpp +++ b/fdbclient/BulkLoading.cpp @@ -23,8 +23,6 @@ #include #include -#include -#include bool getConductBulkLoadFromDataMoveId(const UID& dataMoveId) { bool nowAssigned = false; @@ -119,7 +117,13 @@ std::string getPath(const std::string& path) { // We want boost::url to parse out the path but it cannot digest credentials. Strip them out // before passing to boost::url. try { - return boost::urls::parse_uri(matches[1].str() + matches[3].str()).value().path(); + std::string result = boost::urls::parse_uri(matches[1].str() + matches[3].str()).value().path(); + // Remove leading slash if present - S3 object keys don't have leading slashes, + // and BackupContainer's dataPath() expects paths without them + if (!result.empty() && result[0] == '/') { + result = result.substr(1); + } + return result; } catch (std::system_error& e) { TraceEvent(SevError, "BulkLoadGetPathError") .detail("Path", path) diff --git a/fdbclient/CMakeLists.txt b/fdbclient/CMakeLists.txt index 5aefcc219bf..b8a43bffbd1 100644 --- a/fdbclient/CMakeLists.txt +++ b/fdbclient/CMakeLists.txt @@ -1,4 +1,6 @@ fdb_find_sources(FDBCLIENT_SRCS) +list(REMOVE_ITEM FDBCLIENT_SRCS LinkTest.cpp) +list(REMOVE_ITEM FDBCLIENT_SRCS S3Client_cli.cpp) list(APPEND FDBCLIENT_SRCS sha1/SHA1.cpp) message(STATUS "FDB version is ${FDB_VERSION}") @@ -90,7 +92,12 @@ if(WITH_AWS_BACKUP) endif() add_flow_target(STATIC_LIBRARY NAME fdbclient SRCS ${FDBCLIENT_SRCS} ADDL_SRCS ${options_srcs}) -target_include_directories(fdbclient PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbclient + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/versions.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/versions.h) add_dependencies(fdbclient fdboptions) target_link_libraries(fdbclient PUBLIC fdbrpc msgpack PRIVATE rapidxml) @@ -98,10 +105,9 @@ if(WITH_TLS) target_link_libraries(fdbclient PRIVATE OpenSSL::Crypto) endif() -add_flow_target(EXECUTABLE NAME s3client SRCS S3Client_cli.actor.cpp) +add_flow_target(EXECUTABLE NAME s3client SRCS S3Client_cli.cpp) target_include_directories(s3client PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_SOURCE_DIR}/flow/include" "${CMAKE_BINARY_DIR}/flow/include" "${CMAKE_SOURCE_DIR}/fdbrpc/include" @@ -180,7 +186,12 @@ endif() # fdbserver retain sampling functionality in client code while disabling # sampling for pure clients. add_flow_target(STATIC_LIBRARY NAME fdbclient_sampling SRCS ${FDBCLIENT_SRCS} ADDL_SRCS ${options_srcs}) -target_include_directories(fdbclient_sampling PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(fdbclient_sampling + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}") add_dependencies(fdbclient_sampling fdboptions) target_link_libraries(fdbclient_sampling PUBLIC fdbrpc_sampling msgpack PRIVATE rapidxml) target_compile_definitions(fdbclient_sampling PRIVATE -DENABLE_SAMPLING) @@ -189,7 +200,12 @@ if(WIN32) endif() add_flow_target(LINK_TEST NAME fdbclientlinktest SRCS LinkTest.cpp) -target_link_libraries(fdbclientlinktest PRIVATE fdbclient rapidxml) # re-link rapidxml due to private link interface +target_link_libraries(fdbclientlinktest PRIVATE "$" rapidxml) # re-link rapidxml due to private link interface + +if(NOT FOUNDATIONDB_CROSS_COMPILING) # FIXME(swift): make this work when + # x-compiling. + add_subdirectory(bench EXCLUDE_FROM_ALL) +endif() if(BUILD_AZURE_BACKUP) target_link_libraries(fdbclient PRIVATE curl azure-storage-lite) @@ -197,12 +213,13 @@ if(BUILD_AZURE_BACKUP) endif() if(WITH_AWS_BACKUP) - target_link_libraries(fdbclient PUBLIC awssdk_target) - target_link_libraries(fdbclient_sampling PUBLIC awssdk_target) + target_link_libraries(fdbclient PRIVATE awssdk_target) + target_link_libraries(fdbclient_sampling PRIVATE awssdk_target) endif() if (NOT WIN32 AND NOT OPEN_FOR_IDE) enable_testing() add_test(NAME s3client_test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/s3client_test.sh ${CMAKE_BINARY_DIR}) + add_test(NAME gcs_client_test COMMAND ${CMAKE_COMMAND} -E env USE_MOCK_GCS=true ${CMAKE_CURRENT_SOURCE_DIR}/tests/s3client_test.sh ${CMAKE_BINARY_DIR}) add_test(NAME bulkload_test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/bulkload_test.sh ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) endif() diff --git a/fdbclient/ClientKnobCollection.cpp b/fdbclient/ClientKnobCollection.cpp deleted file mode 100644 index 81826218478..00000000000 --- a/fdbclient/ClientKnobCollection.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ClientKnobCollection.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. - */ - -#include "fdbclient/ClientKnobCollection.h" - -ClientKnobCollection::ClientKnobCollection(Randomize randomize, IsSimulated isSimulated) - : flowKnobs(randomize, isSimulated), clientKnobs(randomize, isSimulated) {} - -void ClientKnobCollection::initialize(Randomize randomize, IsSimulated isSimulated) { - flowKnobs.initialize(randomize, isSimulated); - clientKnobs.initialize(randomize, isSimulated); -} - -void ClientKnobCollection::reset(Randomize randomize, IsSimulated isSimulated) { - flowKnobs.reset(randomize, isSimulated); - clientKnobs.reset(randomize, isSimulated); -} - -Optional ClientKnobCollection::tryParseKnobValue(std::string const& knobName, - std::string const& knobValue) const { - auto parsedKnobValue = flowKnobs.parseKnobValue(knobName, knobValue); - if (!std::holds_alternative(parsedKnobValue)) { - return KnobValueRef::create(parsedKnobValue); - } - parsedKnobValue = clientKnobs.parseKnobValue(knobName, knobValue); - if (!std::holds_alternative(parsedKnobValue)) { - return KnobValueRef::create(parsedKnobValue); - } - return {}; -} - -bool ClientKnobCollection::trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) { - // Do not short circuit by directly returning: - // knobValue.visitSetKnob(knobName, flowKnobs) || knobValue.visitSetKnob(knobName, clientKnobs) - // This is because if a knob with name FOO exists in both fdbclient/ClientKnobs.h and flow/Knobs.h, then - // only flow knob FOO will be updated but client knob FOO will not. - // Although today there are no knobs that share their name between flow and client, this was done to be - // consistent with ServerKnobCollection, see: - // https://github.com/apple/foundationdb/blob/596f4cba6dcac00a14080c57d5ea037272240a08/fdbclient/ServerKnobCollection.cpp#L51-L57 - const bool setFlowKnob = knobValue.visitSetKnob(knobName, flowKnobs); - const bool setClientKnob = knobValue.visitSetKnob(knobName, clientKnobs); - return setFlowKnob || setClientKnob; -} diff --git a/fdbclient/ClientKnobs.cpp b/fdbclient/ClientKnobs.cpp index 2c6f9b8ba0a..8e82ec2dc43 100644 --- a/fdbclient/ClientKnobs.cpp +++ b/fdbclient/ClientKnobs.cpp @@ -24,6 +24,8 @@ #include "flow/UnitTest.h" #include "flow/flow.h" +#include + int getSimulatedTxnTimeoutSeconds() { if (deterministicRandom()->truePercent(90)) { return 5; @@ -38,6 +40,103 @@ ClientKnobs::ClientKnobs(Randomize randomize, IsSimulated isSimulated) { initialize(randomize, isSimulated); } +namespace { +FlowKnobs globalFlowKnobs(Randomize::False, IsSimulated::False); +ClientKnobs globalClientKnobs(Randomize::False, IsSimulated::False); +ClientKnobs bootstrapGlobalClientKnobs(Randomize::False, IsSimulated::False); + +FlowKnobs& mutableFlowKnobs() { + return *const_cast(FLOW_KNOBS); +} + +ClientKnobs& mutableClientKnobs() { + return *const_cast(CLIENT_KNOBS); +} + +Optional tryParseKnobValueImpl(std::string const& knobName, std::string const& knobValue) { + auto parsedKnobValue = FLOW_KNOBS->parseKnobValue(knobName, knobValue); + if (!std::holds_alternative(parsedKnobValue)) { + return KnobValueRef::create(parsedKnobValue); + } + + parsedKnobValue = CLIENT_KNOBS->parseKnobValue(knobName, knobValue); + if (!std::holds_alternative(parsedKnobValue)) { + return KnobValueRef::create(parsedKnobValue); + } + + return {}; +} +} // namespace + +ClientKnobs const* CLIENT_KNOBS = &bootstrapGlobalClientKnobs; + +void resetClientKnobs(Randomize randomize, IsSimulated isSimulated) { + FLOW_KNOBS = &globalFlowKnobs; + CLIENT_KNOBS = &globalClientKnobs; + globalFlowKnobs.reset(randomize, isSimulated); + globalClientKnobs.reset(randomize, isSimulated); +} + +void initializeClientKnobs(Randomize randomize, IsSimulated isSimulated) { + mutableFlowKnobs().initialize(randomize, isSimulated); + mutableClientKnobs().initialize(randomize, isSimulated); +} + +Optional tryParseClientKnobValue(std::string const& knobName, std::string const& knobValue) { + return tryParseKnobValueImpl(knobName, knobValue); +} + +KnobValue parseClientKnobValue(std::string const& knobName, std::string const& knobValue) { + auto result = tryParseClientKnobValue(knobName, knobValue); + if (!result.present()) { + throw invalid_option(); + } + return result.get(); +} + +bool trySetClientKnob(std::string const& knobName, KnobValueRef const& knobValue) { + const bool setFlowKnob = knobValue.visitSetKnob(knobName, mutableFlowKnobs()); + const bool setClientKnob = knobValue.visitSetKnob(knobName, mutableClientKnobs()); + return setFlowKnob || setClientKnob; +} + +void setClientKnob(std::string const& knobName, KnobValueRef const& knobValue) { + if (trySetClientKnob(knobName, knobValue)) { + return; + } + + TraceEvent(SevWarnAlways, "FailedToSetKnob").detail("KnobName", knobName).detail("KnobValue", knobValue.toString()); + throw invalid_option_value(); +} + +void setupClientKnobs(std::vector> const& knobs) { + for (const auto& [knobName, knobValueString] : knobs) { + try { + setClientKnob(knobName, parseClientKnobValue(knobName, knobValueString)); + } catch (Error& e) { + if (e.code() == error_code_invalid_option_value) { + std::cerr << "WARNING: Invalid value '" << knobValueString << "' for knob option '" << knobName + << "'\n"; + TraceEvent(SevWarnAlways, "InvalidKnobValue") + .detail("Knob", printable(knobName)) + .detail("Value", printable(knobValueString)); + } else if (e.code() == error_code_invalid_option) { + std::cerr << "WARNING: Invalid knob option '" << knobName << "'\n"; + TraceEvent(SevWarnAlways, "InvalidKnobName") + .detail("Knob", printable(knobName)) + .detail("Value", printable(knobValueString)); + } else { + std::cerr << "ERROR: Failed to set knob option '" << knobName << "': " << e.what() << "\n"; + TraceEvent(SevError, "FailedToSetKnob") + .errorUnsuppressed(e) + .detail("Knob", printable(knobName)) + .detail("Value", printable(knobValueString)); + throw e; + } + } + } +} + void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { // clang-format off @@ -61,6 +160,7 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { init( MAX_COMMIT_PROXY_CONNECTIONS, 5 ); if( randomize && BUGGIFY ) MAX_COMMIT_PROXY_CONNECTIONS = 1; init( MAX_GRV_PROXY_CONNECTIONS, 3 ); if( randomize && BUGGIFY ) MAX_GRV_PROXY_CONNECTIONS = 1; init( STATUS_IDLE_TIMEOUT, 120.0 ); + init( STATUS_TIMEOUT, 30.0 ); init( GRPC_CTL_SERVICE_DEFAULT_TIMEOUT, 5.0 ); init( SEND_ENTIRE_VERSION_VECTOR, false ); @@ -114,8 +214,6 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { init( DETAILED_HEALTH_METRICS_MAX_STALENESS, 5.0 ); init( MID_SHARD_SIZE_MAX_STALENESS, 10.0 ); init( TAG_ENCODE_KEY_SERVERS, false ); if( randomize && BUGGIFY ) TAG_ENCODE_KEY_SERVERS = true; - init( RANGESTREAM_FRAGMENT_SIZE, 1e6 ); - init( RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT, 20 ); init( QUARANTINE_TSS_ON_MISMATCH, true ); if( randomize && BUGGIFY ) QUARANTINE_TSS_ON_MISMATCH = false; // if true, a tss mismatch will put the offending tss in quarantine. If false, it will just be killed init( CHANGE_FEED_EMPTY_BATCH_TIME, 0.005 ); @@ -185,7 +283,7 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { init( COPY_LOG_BLOCKS_PER_TASK, 1000 ); init( COPY_LOG_PREFETCH_BLOCKS, 3 ); init( COPY_LOG_READ_AHEAD_BYTES, BACKUP_LOCK_BYTES / COPY_LOG_PREFETCH_BLOCKS); // each task will use up to COPY_LOG_PREFETCH_BLOCKS * COPY_LOG_READ_AHEAD_BYTES memory - init( COPY_LOG_TASK_DURATION_NANOS, 1e10 ); // 10 seconds + init( COPY_LOG_TASK_DURATION_SECONDS, 10 ); init( BACKUP_TASKS_PER_AGENT, 10 ); init( BACKUP_POLL_PROGRESS_SECONDS, 10 ); init( SIM_BACKUP_TASKS_PER_AGENT, 10 ); @@ -205,9 +303,9 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { init( BACKUP_STATUS_DELAY, 40.0 ); init( BACKUP_STATUS_JITTER, 0.05 ); init( MIN_CLEANUP_SECONDS, 3600.0 ); - init( FASTRESTORE_ATOMICOP_WEIGHT, 1 ); if( randomize && BUGGIFY ) { FASTRESTORE_ATOMICOP_WEIGHT = deterministicRandom()->random01() * 200 + 1; } init( RESTORE_RANGES_READ_BATCH, 10000 ); + init( BACKUP_RANGE_PARTITIONED_VDIR_INTERVAL, 100000 * 1000000LL ); init( BACKUP_CONTAINER_LOCAL_ALLOW_RELATIVE_PATH, false ); init( ENABLE_REPLICA_CONSISTENCY_CHECK_ON_BACKUP_READS, false ); if( randomize && BUGGIFY ) { ENABLE_REPLICA_CONSISTENCY_CHECK_ON_BACKUP_READS = true; } init( BACKUP_CONSISTENCY_CHECK_REQUIRED_REPLICAS, -2 ); // Do consistency check based on all available storage replicas @@ -321,8 +419,10 @@ void ClientKnobs::initialize(Randomize randomize, IsSimulated isSimulated) { init( TAG_THROTTLE_RECHECK_INTERVAL, 5.0 ); if( randomize && BUGGIFY ) TAG_THROTTLE_RECHECK_INTERVAL = 0.0; init( TAG_THROTTLE_EXPIRATION_INTERVAL, 60.0 ); if( randomize && BUGGIFY ) TAG_THROTTLE_EXPIRATION_INTERVAL = 1.0; init( TAG_THROTTLING_PAGE_SIZE, 4096 ); if( randomize && BUGGIFY ) TAG_THROTTLING_PAGE_SIZE = 4096; - init( GLOBAL_TAG_THROTTLING_RW_FUNGIBILITY_RATIO, 4.0 ); - init( PROXY_MAX_TAG_THROTTLE_DURATION, 5.0 ); if( randomize && BUGGIFY ) PROXY_MAX_TAG_THROTTLE_DURATION = 0.5; + init( TAG_THROTTLING_RW_FUNGIBILITY_RATIO, 4.0 ); + // Preserve the old public knob spelling while moving new code to the non-global name. + double_knobs["global_tag_throttling_rw_fungibility_ratio"] = + KnobValue{ &TAG_THROTTLING_RW_FUNGIBILITY_RATIO }; init( TRANSACTION_LOCK_REJECTION_RETRIABLE, true ); // busyness reporting diff --git a/fdbclient/ClientStatusReport.cpp b/fdbclient/ClientStatusReport.cpp index be1fa9e475a..a4a7c4d742c 100644 --- a/fdbclient/ClientStatusReport.cpp +++ b/fdbclient/ClientStatusReport.cpp @@ -30,7 +30,7 @@ namespace { class ClientReportGenerator { public: - ClientReportGenerator(DatabaseContext& cx) : cx(cx), healthy(true), numConnectionsFailed(0) {} + explicit ClientReportGenerator(DatabaseContext& cx) : cx(cx), healthy(true), numConnectionsFailed(0) {} Standalone generateReport() { if (cx.isError()) { @@ -89,7 +89,7 @@ class ClientReportGenerator { statusObj["CommitProxies"] = commitProxyArr; // Update health status - if (clientInfo.grvProxies.size() == 0 || clientInfo.commitProxies.size() == 0) { + if (clientInfo.grvProxies.empty() || clientInfo.commitProxies.empty()) { healthy = false; } } diff --git a/fdbclient/ClusterConnectionFile.actor.cpp b/fdbclient/ClusterConnectionFile.cpp similarity index 97% rename from fdbclient/ClusterConnectionFile.actor.cpp rename to fdbclient/ClusterConnectionFile.cpp index 4dff5e3181f..52883104718 100644 --- a/fdbclient/ClusterConnectionFile.actor.cpp +++ b/fdbclient/ClusterConnectionFile.cpp @@ -1,5 +1,5 @@ /* - * ClusterConnectionFile.actor.cpp + * ClusterConnectionFile.cpp * * This source file is part of the FoundationDB open source project * @@ -20,7 +20,6 @@ #include "fdbclient/ClusterConnectionFile.h" #include "fdbclient/MonitorLeader.h" -#include "flow/actorcompiler.h" // has to be last include // Loads and parses the file at 'filename', throwing errors if the file cannot be read or the format is invalid. ClusterConnectionFile::ClusterConnectionFile(std::string const& filename) @@ -52,7 +51,7 @@ Reference ClusterConnectionFile::openOrDefault(const char // Sets the connections string held by this object and persists it. Future ClusterConnectionFile::setAndPersistConnectionString(ClusterConnectionString const& conn) { - ASSERT(filename.size()); + ASSERT(!filename.empty()); cs = conn; return success(persist()); } @@ -105,7 +104,7 @@ std::string ClusterConnectionFile::toString() const { // returns std::pair ClusterConnectionFile::lookupClusterFileName(std::string const& filename) { - if (filename.length()) + if (!filename.empty()) return std::make_pair(filename, false); std::string f; @@ -148,7 +147,7 @@ std::string ClusterConnectionFile::getErrorString(std::pair c Future ClusterConnectionFile::persist() { setPersisted(); - if (filename.size()) { + if (!filename.empty()) { try { atomicReplace(filename, "# DO NOT EDIT!\n# This file is auto-generated, it is not to be edited by hand\n" + diff --git a/fdbclient/ClusterConnectionKey.actor.cpp b/fdbclient/ClusterConnectionKey.cpp similarity index 70% rename from fdbclient/ClusterConnectionKey.actor.cpp rename to fdbclient/ClusterConnectionKey.cpp index a6c00dda09e..cb0dcd966d5 100644 --- a/fdbclient/ClusterConnectionKey.actor.cpp +++ b/fdbclient/ClusterConnectionKey.cpp @@ -1,5 +1,5 @@ /* - * ClusterConnectionKey.actor.cpp + * ClusterConnectionKey.cpp * * This source file is part of the FoundationDB open source project * @@ -18,8 +18,7 @@ * limitations under the License. */ -#include "fdbclient/ClusterConnectionKey.actor.h" -#include "flow/actorcompiler.h" // has to be last include +#include "ClusterConnectionKey.h" // Creates a cluster connection record with a given connection string and saves it to the specified key. Needs to be // persisted should be set to true unless this ClusterConnectionKey is being created with the value read from the @@ -37,22 +36,24 @@ ClusterConnectionKey::ClusterConnectionKey(Database db, // Loads and parses the connection string at the specified key, throwing errors if the file cannot be read or the // format is invalid. -ACTOR Future> ClusterConnectionKey::loadClusterConnectionKey(Database db, - Key connectionStringKey) { - state Transaction tr(db); - loop { +Future> ClusterConnectionKey::loadClusterConnectionKey(Database db, + Key connectionStringKey) { + Transaction tr(db); + while (true) { + Error err; try { - Optional v = wait(tr.get(connectionStringKey)); + Optional v = co_await tr.get(connectionStringKey); if (!v.present()) { throw connection_string_invalid(); } - return makeReference(db, - connectionStringKey, - ClusterConnectionString(v.get().toString()), - ConnectionStringNeedsPersisted::False); + co_return makeReference(db, + connectionStringKey, + ClusterConnectionString(v.get().toString()), + ConnectionStringNeedsPersisted::False); } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -63,31 +64,31 @@ Future ClusterConnectionKey::setAndPersistConnectionString(ClusterConnecti } // Get the connection string stored in the database. -ACTOR Future ClusterConnectionKey::getStoredConnectionStringImpl( +Future ClusterConnectionKey::getStoredConnectionStringImpl( Reference self) { Reference cck = - wait(ClusterConnectionKey::loadClusterConnectionKey(self->db, self->connectionStringKey)); - return cck->cs; + co_await ClusterConnectionKey::loadClusterConnectionKey(self->db, self->connectionStringKey); + co_return cck->cs; } Future ClusterConnectionKey::getStoredConnectionString() { return getStoredConnectionStringImpl(Reference::addRef(this)); } -ACTOR Future ClusterConnectionKey::upToDateImpl(Reference self, - ClusterConnectionString* connectionString) { +Future ClusterConnectionKey::upToDateImpl(Reference self, + ClusterConnectionString* connectionString) { try { // the cluster file hasn't been created yet so there's nothing to check if (self->needsToBePersisted()) - return true; + co_return true; Reference temp = - wait(ClusterConnectionKey::loadClusterConnectionKey(self->db, self->connectionStringKey)); + co_await ClusterConnectionKey::loadClusterConnectionKey(self->db, self->connectionStringKey); *connectionString = temp->getConnectionString(); - return connectionString->toString() == self->cs.toString(); + co_return connectionString->toString() == self->cs.toString(); } catch (Error& e) { TraceEvent(SevWarnAlways, "ClusterKeyError").error(e).detail("Key", self->connectionStringKey); - return false; // Swallow the error and report that the file is out of date + co_return false; // Swallow the error and report that the file is out of date } } @@ -114,25 +115,26 @@ std::string ClusterConnectionKey::toString() const { return "fdbkey://" + printable(connectionStringKey); } -ACTOR Future ClusterConnectionKey::persistImpl(Reference self) { +Future ClusterConnectionKey::persistImpl(Reference self) { self->setPersisted(); - state Value newConnectionString = ValueRef(self->cs.toString()); + Value newConnectionString = ValueRef(self->cs.toString()); try { - state Transaction tr(self->db); - loop { + Transaction tr(self->db); + while (true) { + Error err; try { - Optional existingConnectionString = wait(tr.get(self->connectionStringKey)); + Optional existingConnectionString = co_await tr.get(self->connectionStringKey); // Someone has already updated the connection string to what we want if (existingConnectionString.present() && existingConnectionString.get() == newConnectionString) { self->lastPersistedConnectionString = newConnectionString; - return true; + co_return true; } - // Someone has updated the connection string to something we didn't expect, in which case we leave it - // alone. It's possible this could result in the stored string getting stuck if the connection string - // changes twice and only the first change is recorded. If the process that wrote the first change dies - // and no other process attempts to write the intermediate state, then only a newly opened connection - // key would be able to update the state. + // Someone has updated the connection string to something we didn't expect, in which case we leave + // it alone. It's possible this could result in the stored string getting stuck if the connection + // string changes twice and only the first change is recorded. If the process that wrote the first + // change dies and no other process attempts to write the intermediate state, then only a newly + // opened connection key would be able to update the state. else if (existingConnectionString.present() && existingConnectionString != self->lastPersistedConnectionString) { TraceEvent(SevWarnAlways, "UnableToChangeConnectionKeyDueToMismatch") @@ -140,16 +142,17 @@ ACTOR Future ClusterConnectionKey::persistImpl(ReferencelastPersistedConnectionString) .detail("ActualStoredConnectionString", existingConnectionString); - return false; + co_return false; } tr.set(self->connectionStringKey, newConnectionString); - wait(tr.commit()); + co_await tr.commit(); self->lastPersistedConnectionString = newConnectionString; - return true; + co_return true; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } catch (Error& e) { TraceEvent(SevWarnAlways, "UnableToChangeConnectionKey") @@ -158,10 +161,10 @@ ACTOR Future ClusterConnectionKey::persistImpl(Referencecs.toString()); } - return false; + co_return false; }; // Writes the connection string to the database Future ClusterConnectionKey::persist() { return persistImpl(Reference::addRef(this)); -} \ No newline at end of file +} diff --git a/fdbclient/include/fdbclient/ClusterConnectionKey.actor.h b/fdbclient/ClusterConnectionKey.h similarity index 76% rename from fdbclient/include/fdbclient/ClusterConnectionKey.actor.h rename to fdbclient/ClusterConnectionKey.h index e728f61a265..ce8be18b1a4 100644 --- a/fdbclient/include/fdbclient/ClusterConnectionKey.actor.h +++ b/fdbclient/ClusterConnectionKey.h @@ -1,5 +1,5 @@ /* - * ClusterConnectionKey.actor.h + * ClusterConnectionKey.h * * This source file is part of the FoundationDB open source project * @@ -20,17 +20,8 @@ #pragma once -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_CLUSTERCONNECTIONKEY_ACTOR_G_H) -#define FDBCLIENT_CLUSTERCONNECTIONKEY_ACTOR_G_H -#include "fdbclient/ClusterConnectionKey.actor.g.h" -#elif !defined(FDBCLIENT_CLUSTERCONNECTIONKEY_ACTOR_H) -#define FDBCLIENT_CLUSTERCONNECTIONKEY_ACTOR_H - #include "fdbclient/CoordinationInterface.h" #include "fdbclient/NativeAPI.actor.h" -#include "flow/actorcompiler.h" // has to be last include // An implementation of IClusterConnectionRecord backed by a key in a FoundationDB database. class ClusterConnectionKey : public IClusterConnectionRecord, ReferenceCounted, NonCopyable { @@ -45,7 +36,7 @@ class ClusterConnectionKey : public IClusterConnectionRecord, ReferenceCounted> loadClusterConnectionKey(Database db, Key connectionStringKey); + static Future> loadClusterConnectionKey(Database db, Key connectionStringKey); // Sets the connections string held by this object and persists it. Future setAndPersistConnectionString(ClusterConnectionString const&) override; @@ -76,10 +67,9 @@ class ClusterConnectionKey : public IClusterConnectionRecord, ReferenceCounted persist() override; private: - ACTOR static Future getStoredConnectionStringImpl(Reference self); - ACTOR static Future upToDateImpl(Reference self, - ClusterConnectionString* connectionString); - ACTOR static Future persistImpl(Reference self); + static Future getStoredConnectionStringImpl(Reference self); + static Future upToDateImpl(Reference self, ClusterConnectionString* connectionString); + static Future persistImpl(Reference self); // The database where the connection key is stored. Note that this does not need to be the same database as the one // that the connection string would connect to. @@ -87,6 +77,3 @@ class ClusterConnectionKey : public IClusterConnectionRecord, ReferenceCounted lastPersistedConnectionString; }; - -#include "flow/unactorcompiler.h" -#endif \ No newline at end of file diff --git a/fdbclient/ClusterConnectionMemoryRecord.actor.cpp b/fdbclient/ClusterConnectionMemoryRecord.cpp similarity index 94% rename from fdbclient/ClusterConnectionMemoryRecord.actor.cpp rename to fdbclient/ClusterConnectionMemoryRecord.cpp index ec7d2a9afb3..fbc0f86c752 100644 --- a/fdbclient/ClusterConnectionMemoryRecord.actor.cpp +++ b/fdbclient/ClusterConnectionMemoryRecord.cpp @@ -1,5 +1,5 @@ /* - * ClusterConnectionMemoryRecord.actor.cpp + * ClusterConnectionMemoryRecord.cpp * * This source file is part of the FoundationDB open source project * @@ -19,8 +19,6 @@ */ #include "fdbclient/ClusterConnectionMemoryRecord.h" -#include "fdbclient/MonitorLeader.h" -#include "flow/actorcompiler.h" // has to be last include // Sets the connections string held by this object. Future ClusterConnectionMemoryRecord::setAndPersistConnectionString(ClusterConnectionString const& conn) { @@ -60,4 +58,4 @@ std::string ClusterConnectionMemoryRecord::toString() const { // This is a no-op for memory records. Returns true to indicate success. Future ClusterConnectionMemoryRecord::persist() { return true; -} \ No newline at end of file +} diff --git a/fdbclient/CoordinationInterface.cpp b/fdbclient/CoordinationInterface.cpp index 4a58fe9e8cd..d8ee138fb7c 100644 --- a/fdbclient/CoordinationInterface.cpp +++ b/fdbclient/CoordinationInterface.cpp @@ -18,9 +18,6 @@ * limitations under the License. */ -#include "flow/Platform.h" -#include - #ifndef BOOST_SYSTEM_NO_LIB #define BOOST_SYSTEM_NO_LIB #endif diff --git a/fdbclient/DataDistributionConfig.actor.cpp b/fdbclient/DataDistributionConfig.cpp similarity index 91% rename from fdbclient/DataDistributionConfig.actor.cpp rename to fdbclient/DataDistributionConfig.cpp index 649efd88387..43d3302daa2 100644 --- a/fdbclient/DataDistributionConfig.actor.cpp +++ b/fdbclient/DataDistributionConfig.cpp @@ -1,5 +1,5 @@ /* - * DataDistributionConfig.actor.cpp + * DataDistributionConfig.cpp * * This source file is part of the FoundationDB open source project * @@ -18,9 +18,8 @@ * limitations under the License. */ -#include "fdbclient/DataDistributionConfig.actor.h" +#include "fdbclient/DataDistributionConfig.h" #include "fdbclient/json_spirit/json_spirit_value.h" -#include "flow/actorcompiler.h" // This must be the last #include. json_spirit::mValue DDConfiguration::toJSON(RangeConfigMapSnapshot const& config, bool includeDefaultRanges) { json_spirit::mObject doc; diff --git a/fdbclient/DatabaseBackupAgent.actor.cpp b/fdbclient/DatabaseBackupAgent.cpp similarity index 59% rename from fdbclient/DatabaseBackupAgent.actor.cpp rename to fdbclient/DatabaseBackupAgent.cpp index 6bdcaa154e8..a094fec7ece 100644 --- a/fdbclient/DatabaseBackupAgent.actor.cpp +++ b/fdbclient/DatabaseBackupAgent.cpp @@ -1,5 +1,5 @@ /* - * DatabaseBackupAgent.actor.cpp + * DatabaseBackupAgent.cpp * * This source file is part of the FoundationDB open source project * @@ -19,7 +19,7 @@ */ #include -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/Status.h" #include "fdbclient/StatusClient.h" #include "fdbclient/DatabaseContext.h" @@ -32,13 +32,11 @@ #include "flow/genericactors.actor.h" #include "flow/Hash3.h" #include -#include "fdbclient/ManagementAPI.actor.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/ManagementAPI.h" +#include "fdbclient/KeyBackedTypes.h" #include #include -#include "flow/actorcompiler.h" // has to be last include - const Key DatabaseBackupAgent::keyAddPrefix = "add_prefix"_sr; const Key DatabaseBackupAgent::keyRemovePrefix = "remove_prefix"_sr; const Key DatabaseBackupAgent::keyRangeVersions = "range_versions"_sr; @@ -76,9 +74,9 @@ DatabaseBackupAgent::DatabaseBackupAgent(Database src) // Any new per-DR properties should go here. class DRConfig { public: - DRConfig(UID uid = UID()) + explicit DRConfig(UID uid = UID()) : uid(uid), configSpace(uidPrefixKey("uid->config/"_sr.withPrefix(databaseBackupPrefixRange.begin), uid)) {} - DRConfig(Reference task) + explicit DRConfig(Reference task) : DRConfig(BinaryReader::fromStringRef(task->params[BackupAgentBase::keyConfigLogUid], Unversioned())) {} KeyBackedBinaryValue rangeBytesWritten() { return configSpace.pack(__FUNCTION__sr); } @@ -110,7 +108,7 @@ bool copyDefaultParameters(Reference source, Reference dest) { return false; } -ACTOR template +template Future checkTaskVersion(Tr tr, Reference task, StringRef name, uint32_t version) { uint32_t taskVersion = task->getVersion(); if (taskVersion > version) { @@ -118,19 +116,17 @@ Future checkTaskVersion(Tr tr, Reference task, StringRef name, uint3 .detail("TaskVersion", taskVersion) .detail("Name", name) .detail("Version", version); - wait(logError(tr, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyErrors) - .pack(task->params[BackupAgentBase::keyConfigLogUid]), - format("ERROR: %s task version `%lu' is greater than supported version `%lu'", - task->params[Task::reservedTaskParamKeyType].toString().c_str(), - (unsigned long)taskVersion, - (unsigned long)version))); + co_await logError(tr, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyErrors) + .pack(task->params[BackupAgentBase::keyConfigLogUid]), + format("ERROR: %s task version `%lu' is greater than supported version `%lu'", + task->params[Task::reservedTaskParamKeyType].toString().c_str(), + (unsigned long)taskVersion, + (unsigned long)version)); throw task_invalid_version(); } - - return Void(); } struct BackupRangeTaskFunc : TaskFuncBase { @@ -159,32 +155,32 @@ struct BackupRangeTaskFunc : TaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future>> getBlockOfShards(Reference tr, - Key beginKey, - Key endKey, - int limit) { + static Future>> getBlockOfShards(Reference tr, + Key beginKey, + Key endKey, + int limit) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Standalone> results; - RangeResult values = wait(tr->getRange( - KeyRangeRef(keyAfter(beginKey.withPrefix(keyServersPrefix)), endKey.withPrefix(keyServersPrefix)), limit)); + Standalone> results; + RangeResult values = co_await tr->getRange( + KeyRangeRef(keyAfter(beginKey.withPrefix(keyServersPrefix)), endKey.withPrefix(keyServersPrefix)), limit); for (auto& s : values) { KeyRef k = s.key.removePrefix(keyServersPrefix); results.push_back_deep(results.arena(), k); } - return results; + co_return results; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Key begin, - Key end, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Key begin, + Key end, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(BackupRangeTaskFunc::name, BackupRangeTaskFunc::version, doneKey); copyDefaultParameters(parentTask, task); @@ -193,84 +189,85 @@ struct BackupRangeTaskFunc : TaskFuncBase { task->params[BackupAgentBase::keyEndKey] = end; if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(cx, task, BackupRangeTaskFunc::name, BackupRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, BackupRangeTaskFunc::name, BackupRangeTaskFunc::version); // Find out if there is a shard boundary in(beginKey, endKey) Standalone> keys = - wait(runRYWTransaction(taskBucket->src, [=](Reference tr) { + co_await runRYWTransaction(taskBucket->src, [=](Reference tr) { return getBlockOfShards(tr, task->params[DatabaseBackupAgent::keyBeginKey], task->params[DatabaseBackupAgent::keyEndKey], CLIENT_KNOBS->BACKUP_SHARD_TASK_LIMIT); - })); - if (keys.size() > 0) { + }); + if (!keys.empty()) { task->params[BackupRangeTaskFunc::keyAddBackupRangeTasks] = BinaryWriter::toValue(keys, IncludeVersion()); - return Void(); + co_return; } // Read everything from beginKey to endKey, write it to an output file, run the output file processor, and // then set on_done.If we are still writing after X seconds, end the output file and insert a new backup_range // task for the remainder. - state double timeout = now() + CLIENT_KNOBS->BACKUP_RANGE_TIMEOUT; - state Key addPrefix = task->params[DatabaseBackupAgent::keyAddPrefix]; - state Key removePrefix = task->params[DatabaseBackupAgent::keyRemovePrefix]; + double timeout = now() + CLIENT_KNOBS->BACKUP_RANGE_TIMEOUT; + Key addPrefix = task->params[DatabaseBackupAgent::keyAddPrefix]; + Key removePrefix = task->params[DatabaseBackupAgent::keyRemovePrefix]; - state KeyRange range( + KeyRange range( KeyRangeRef(task->params[BackupAgentBase::keyBeginKey], task->params[BackupAgentBase::keyEndKey])); // retrieve kvData - state PromiseStream results; - - state Future rc = readCommitted(taskBucket->src, - results, - lock, - range, - Terminator::True, - AccessSystemKeys::True, - LockAware::True, - ReadLowPriority(CLIENT_KNOBS->BACKUP_READS_USE_LOW_PRIORITY)); - state Key rangeBegin = range.begin; - state Key rangeEnd; - state bool endOfStream = false; - state RangeResultWithVersion nextValues; - state int64_t nextValuesSize = 0; + PromiseStream results; + + Future rc = readCommitted(taskBucket->src, + results, + lock, + range, + Terminator::True, + AccessSystemKeys::True, + LockAware::True, + ReadLowPriority(CLIENT_KNOBS->BACKUP_READS_USE_LOW_PRIORITY)); + Key rangeBegin = range.begin; + Key rangeEnd; + bool endOfStream = false; + RangeResultWithVersion nextValues; + int64_t nextValuesSize = 0; nextValues.second = invalidVersion; - loop { + while (true) { if (endOfStream && nextValues.second == invalidVersion) { - return Void(); + co_return; } - state RangeResultWithVersion values = std::move(nextValues); - state int64_t valuesSize = nextValuesSize; + RangeResultWithVersion values = std::move(nextValues); + int64_t valuesSize = nextValuesSize; nextValues = RangeResultWithVersion(); nextValues.second = invalidVersion; nextValuesSize = 0; if (!endOfStream) { - loop { + while (true) { + Error err; try { - RangeResultWithVersion v = waitNext(results.getFuture()); + RangeResultWithVersion v = co_await results.getFuture(); int64_t resultSize = v.first.expectedSize(); lock->release(resultSize); @@ -288,71 +285,74 @@ struct BackupRangeTaskFunc : TaskFuncBase { } valuesSize += resultSize; + continue; } catch (Error& e) { - state Error err = e; - if (err.code() == error_code_actor_cancelled) - throw err; + err = e; + } + if (err.code() == error_code_actor_cancelled) + throw err; - if (err.code() == error_code_end_of_stream) { - endOfStream = true; - if (values.second != invalidVersion) - break; - return Void(); - } + if (err.code() == error_code_end_of_stream) { + endOfStream = true; + if (values.second != invalidVersion) + break; + co_return; + } - wait(logError(cx, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyErrors) - .pack(task->params[BackupAgentBase::keyConfigLogUid]), - format("ERROR: %s", err.what()))); + co_await logError(cx, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyErrors) + .pack(task->params[BackupAgentBase::keyConfigLogUid]), + format("ERROR: %s", err.what())); - throw err; - } + throw err; } } if (now() >= timeout) { task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey] = rangeBegin; - return Void(); + co_return; } rangeEnd = values.first.more ? keyAfter(values.first.end()[-1].key) : range.end; - state int valueLoc = 0; - state int committedValueLoc = 0; - state Reference tr = makeReference(cx); - loop { + int valueLoc = 0; + int committedValueLoc = 0; + auto tr = makeReference(cx); + while (true) { + Error err; + bool hasErr = false; try { tr->reset(); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Key prefix = task->params[BackupAgentBase::keyConfigLogUid].withPrefix( + Key prefix = task->params[BackupAgentBase::keyConfigLogUid].withPrefix( applyMutationsKeyVersionMapRange.begin); - state Key rangeCountKey = task->params[BackupAgentBase::keyConfigLogUid].withPrefix( + Key rangeCountKey = task->params[BackupAgentBase::keyConfigLogUid].withPrefix( applyMutationsKeyVersionCountRange.begin); - state Future backupVersions = + Future backupVersions = krmGetRanges(tr, prefix, KeyRangeRef(rangeBegin, rangeEnd), BUGGIFY ? 2 : 2000, 1e5); - state Future> logVersionValue = + Future> logVersionValue = tr->get(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsEndRange.begin), Snapshot::True); - state Future> rangeCountValue = tr->get(rangeCountKey, Snapshot::True); - state Future prevRange = tr->getRange(firstGreaterOrEqual(prefix), - lastLessOrEqual(rangeBegin.withPrefix(prefix)), - 1, - Snapshot::True, - Reverse::True); - state Future nextRange = tr->getRange(firstGreaterOrEqual(rangeEnd.withPrefix(prefix)), - firstGreaterOrEqual(strinc(prefix)), - 1, - Snapshot::True, - Reverse::False); - state Future verified = taskBucket->keepRunning(tr, task); - - wait(checkDatabaseLock(tr, - BinaryReader::fromStringRef( - task->params[BackupAgentBase::keyConfigLogUid], Unversioned()))); - wait(success(backupVersions) && success(logVersionValue) && success(rangeCountValue) && - success(prevRange) && success(nextRange) && success(verified)); + Future> rangeCountValue = tr->get(rangeCountKey, Snapshot::True); + Future prevRange = tr->getRange(firstGreaterOrEqual(prefix), + lastLessOrEqual(rangeBegin.withPrefix(prefix)), + 1, + Snapshot::True, + Reverse::True); + Future nextRange = tr->getRange(firstGreaterOrEqual(rangeEnd.withPrefix(prefix)), + firstGreaterOrEqual(strinc(prefix)), + 1, + Snapshot::True, + Reverse::False); + Future verified = taskBucket->keepRunning(tr, task); + + co_await checkDatabaseLock(tr, + BinaryReader::fromStringRef( + task->params[BackupAgentBase::keyConfigLogUid], Unversioned())); + co_await (success(backupVersions) && success(logVersionValue) && success(rangeCountValue) && + success(prevRange) && success(nextRange) && success(verified)); int64_t rangeCount = 0; if (rangeCountValue.get().present()) { @@ -361,10 +361,10 @@ struct BackupRangeTaskFunc : TaskFuncBase { } bool prevAdjacent = - prevRange.get().size() && prevRange.get()[0].value.size() && + !prevRange.get().empty() && !prevRange.get()[0].value.empty() && BinaryReader::fromStringRef(prevRange.get()[0].value, Unversioned()) != invalidVersion; bool nextAdjacent = - nextRange.get().size() && nextRange.get()[0].value.size() && + !nextRange.get().empty() && !nextRange.get()[0].value.empty() && BinaryReader::fromStringRef(nextRange.get()[0].value, Unversioned()) != invalidVersion; if ((!prevAdjacent || !nextAdjacent) && @@ -376,9 +376,9 @@ struct BackupRangeTaskFunc : TaskFuncBase { if (rangeCount > CLIENT_KNOBS->BACKUP_MAP_KEY_UPPER_LIMIT) TraceEvent(SevWarnAlways, "DBA_KeyRangeMapTooLarge").log(); - wait(delay(1)); + co_await delay(1); task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey] = rangeBegin; - return Void(); + co_return; } Version logVersion = @@ -387,21 +387,21 @@ struct BackupRangeTaskFunc : TaskFuncBase { : ::invalidVersion; if (logVersion >= values.second) { task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey] = rangeBegin; - return Void(); + co_return; } //TraceEvent("DBA_Range").detail("Range", KeyRangeRef(rangeBegin, rangeEnd)).detail("Version", values.second).detail("Size", values.first.size()).detail("LogUID", task->params[BackupAgentBase::keyConfigLogUid]).detail("AddPrefix", addPrefix).detail("RemovePrefix", removePrefix); Subspace krv(conf.get(DatabaseBackupAgent::keyRangeVersions)); - state KeyRange versionRange = singleKeyRange(krv.pack(values.second)); + KeyRange versionRange = singleKeyRange(krv.pack(values.second)); tr->addReadConflictRange(versionRange); tr->addWriteConflictRange(versionRange); int versionLoc = 0; std::vector> setRanges; - state int64_t bytesSet = 0; + int64_t bytesSet = 0; - loop { + while (true) { while (versionLoc < backupVersions.get().size() - 1 && (backupVersions.get()[versionLoc].value.size() < sizeof(Version) || BinaryReader::fromStringRef(backupVersions.get()[versionLoc].value, @@ -413,10 +413,10 @@ struct BackupRangeTaskFunc : TaskFuncBase { break; if (backupVersions.get()[versionLoc + 1].key == - (removePrefix == StringRef() ? allKeys.end : strinc(removePrefix))) { + (removePrefix.empty() ? allKeys.end : strinc(removePrefix))) { tr->clear(KeyRangeRef( backupVersions.get()[versionLoc].key.removePrefix(removePrefix).withPrefix(addPrefix), - addPrefix == StringRef() ? allKeys.end : strinc(addPrefix))); + addPrefix.empty() ? allKeys.end : strinc(addPrefix))); } else { tr->clear(KeyRangeRef(backupVersions.get()[versionLoc].key, backupVersions.get()[versionLoc + 1].key) @@ -448,9 +448,9 @@ struct BackupRangeTaskFunc : TaskFuncBase { versionLoc++; } - wait(waitForAll(setRanges)); + co_await waitForAll(setRanges); - wait(tr->commit()); + co_await tr->commit(); Params.bytesWritten().set(task, Params.bytesWritten().getOrDefault(task) + bytesSet); //TraceEvent("DBA_SetComplete", debugID).detail("Ver", values.second).detail("LogVersion", logVersion).detail("ReadVersion", readVer).detail("CommitVer", tr.getCommittedVersion()).detail("Range", versionRange); @@ -462,7 +462,11 @@ struct BackupRangeTaskFunc : TaskFuncBase { break; } } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); valueLoc = committedValueLoc; } } @@ -471,23 +475,23 @@ struct BackupRangeTaskFunc : TaskFuncBase { } } - ACTOR static Future startBackupRangeInternal(Reference tr, - Standalone> keys, - Reference taskBucket, - Reference futureBucket, - Reference task, - Reference onDone) { + static Future startBackupRangeInternal(Reference tr, + Standalone> keys, + Reference taskBucket, + Reference futureBucket, + Reference task, + Reference onDone) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Key nextKey = task->params[BackupAgentBase::keyBeginKey]; + Key nextKey = task->params[BackupAgentBase::keyBeginKey]; std::vector> addTaskVector; - for (int idx = 0; idx < keys.size(); ++idx) { - if (nextKey != keys[idx]) { + for (const auto& splitKey : keys) { + if (nextKey != splitKey) { addTaskVector.push_back( - addTask(tr, taskBucket, task, nextKey, keys[idx], TaskCompletionKey::joinWith(onDone))); + addTask(tr, taskBucket, task, nextKey, splitKey, TaskCompletionKey::joinWith(onDone))); } - nextKey = keys[idx]; + nextKey = splitKey; } if (nextKey != task->params[BackupAgentBase::keyEndKey]) { @@ -499,16 +503,14 @@ struct BackupRangeTaskFunc : TaskFuncBase { TaskCompletionKey::joinWith(onDone))); } - wait(waitForAll(addTaskVector)); - - return Void(); + co_await waitForAll(addTaskVector); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); // Get the bytesWritten parameter from task and atomically add it to the rangeBytesWritten() property of the DR // config. @@ -517,31 +519,29 @@ struct BackupRangeTaskFunc : TaskFuncBase { config.rangeBytesWritten().atomicOp(tr, bytesWritten, MutationRef::AddValue); if (task->params.find(BackupRangeTaskFunc::keyAddBackupRangeTasks) != task->params.end()) { - wait(startBackupRangeInternal( - tr, - BinaryReader::fromStringRef>>( - task->params[BackupRangeTaskFunc::keyAddBackupRangeTasks], IncludeVersion()), - taskBucket, - futureBucket, - task, - taskFuture) && - taskBucket->finish(tr, task)); + co_await (startBackupRangeInternal( + tr, + BinaryReader::fromStringRef>>( + task->params[BackupRangeTaskFunc::keyAddBackupRangeTasks], IncludeVersion()), + taskBucket, + futureBucket, + task, + taskFuture) && + taskBucket->finish(tr, task)); } else if (task->params.find(BackupRangeTaskFunc::keyBackupRangeBeginKey) != task->params.end() && task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey] < task->params[BackupAgentBase::keyEndKey]) { - ASSERT(taskFuture->key.size() > 0); - wait(success(BackupRangeTaskFunc::addTask(tr, - taskBucket, - task, - task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey], - task->params[BackupAgentBase::keyEndKey], - TaskCompletionKey::signal(taskFuture->key))) && - taskBucket->finish(tr, task)); + ASSERT(!taskFuture->key.empty()); + co_await (success(BackupRangeTaskFunc::addTask(tr, + taskBucket, + task, + task->params[BackupRangeTaskFunc::keyBackupRangeBeginKey], + task->params[BackupAgentBase::keyEndKey], + TaskCompletionKey::signal(taskFuture->key))) && + taskBucket->finish(tr, task)); } else { - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); } - - return Void(); } }; StringRef BackupRangeTaskFunc::name = "dr_backup_range"_sr; @@ -553,50 +553,48 @@ struct FinishFullBackupTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace states = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(tr, task, FinishFullBackupTaskFunc::name, FinishFullBackupTaskFunc::version)); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace states = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + co_await checkTaskVersion(tr, task, FinishFullBackupTaskFunc::name, FinishFullBackupTaskFunc::version); // Enable the stop key Transaction srcTr(taskBucket->src); srcTr.setOption(FDBTransactionOptions::LOCK_AWARE); - Version readVersion = wait(srcTr.getReadVersion()); + Version readVersion = co_await srcTr.getReadVersion(); tr->set(states.pack(DatabaseBackupAgent::keyCopyStop), BinaryWriter::toValue(readVersion, Unversioned())); TraceEvent("DBA_FinishFullBackup").detail("CopyStop", readVersion); - wait(taskBucket->finish(tr, task)); - - return Void(); + co_await taskBucket->finish(tr, task); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { // After the BackupRangeTask completes, set the stop key which will stop the BackupLogsTask - Key doneKey = wait(completionKey.get(tr, taskBucket)); + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(FinishFullBackupTaskFunc::name, FinishFullBackupTaskFunc::version, doneKey); copyDefaultParameters(parentTask, task); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -636,41 +634,47 @@ struct EraseLogRangeTaskFunc : TaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state FlowLock lock(CLIENT_KNOBS->BACKUP_LOCK_BYTES); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + FlowLock lock(CLIENT_KNOBS->BACKUP_LOCK_BYTES); - wait(checkTaskVersion(cx, task, EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version); - state Reference tr(new ReadYourWritesTransaction(taskBucket->src)); - loop { + Reference tr(new ReadYourWritesTransaction(taskBucket->src)); + while (true) { + Error err; + bool hasErr = false; try { Version endVersion = BinaryReader::fromStringRef( task->params[DatabaseBackupAgent::keyEndVersion], Unversioned()); - wait(eraseLogData( + co_await eraseLogData( tr, task->params[BackupAgentBase::keyConfigLogUid], task->params[BackupAgentBase::destUid], Optional(endVersion), CheckBackupUID::True, - BinaryReader::fromStringRef(task->params[BackupAgentBase::keyFolderId], Unversioned()))); - wait(tr->commit()); - return Void(); + BinaryReader::fromStringRef(task->params[BackupAgentBase::keyFolderId], Unversioned())); + co_await tr->commit(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version endVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version endVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); @@ -680,29 +684,28 @@ struct EraseLogRangeTaskFunc : TaskFuncBase { task->params[DatabaseBackupAgent::keyEndVersion] = BinaryWriter::toValue(endVersion, Unversioned()); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); - return Void(); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); } }; StringRef EraseLogRangeTaskFunc::name = "dr_erase_log_range"_sr; @@ -735,36 +738,37 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { // store mutation data from results until the end of stream or the timeout. If breaks on timeout returns the first // uncopied version - ACTOR static Future> dumpData(Database cx, - Reference task, - PromiseStream results, - FlowLock* lock, - Reference tb, - double breakTime) { - state bool endOfStream = false; - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - state std::vector nextMutations; - state bool isTimeoutOccurred = false; - state Optional lastKey; - state Version lastVersion; - state int64_t nextMutationSize = 0; - loop { + static Future> dumpData(Database cx, + Reference task, + PromiseStream results, + FlowLock* lock, + Reference tb, + double breakTime) { + bool endOfStream = false; + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + std::vector nextMutations; + bool isTimeoutOccurred = false; + Optional lastKey; + Version lastVersion{ 0 }; + int64_t nextMutationSize = 0; + while (true) { + Error err; try { if (endOfStream && !nextMutationSize) { - return Optional(); + co_return Optional(); } - state std::vector mutations = std::move(nextMutations); - state int64_t mutationSize = nextMutationSize; + std::vector mutations = std::move(nextMutations); + int64_t mutationSize = nextMutationSize; nextMutations = std::vector(); nextMutationSize = 0; if (!endOfStream) { - loop { + while (true) { try { - RCGroup group = waitNext(results.getFuture()); + RCGroup group = co_await results.getFuture(); lock->release(group.items.expectedSize()); int vecSize = group.items.expectedSize(); @@ -778,7 +782,7 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { mutations.push_back(group.items); mutationSize += vecSize; } catch (Error& e) { - state Error error = e; + Error error = e; if (e.code() == error_code_end_of_stream) { endOfStream = true; break; @@ -789,20 +793,22 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { } } - state Optional nextVersionAfterBreak; - state Transaction tr(cx); + Optional nextVersionAfterBreak; + Transaction tr(cx); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.trState->options.sizeLimit = 2 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT; - wait(checkDatabaseLock(&tr, - BinaryReader::fromStringRef( - task->params[BackupAgentBase::keyConfigLogUid], Unversioned()))); - state int64_t bytesSet = 0; + co_await checkDatabaseLock(&tr, + BinaryReader::fromStringRef( + task->params[BackupAgentBase::keyConfigLogUid], Unversioned())); + int64_t bytesSet = 0; bool first = true; - for (auto m : mutations) { + for (const auto& m : mutations) { for (auto kv : m) { if (isTimeoutOccurred) { Version newVersion = getLogKeyVersion(kv.key); @@ -827,15 +833,19 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { } } - wait(tr.commit()); + co_await tr.commit(); Params.bytesWritten().set(task, Params.bytesWritten().getOrDefault(task) + bytesSet); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr.onError(err); } } if (nextVersionAfterBreak.present()) { - return nextVersionAfterBreak; + co_return nextVersionAfterBreak; } if (!isTimeoutOccurred && timer_monotonic() >= breakTime && lastKey.present()) { // timeout occurred @@ -845,33 +855,34 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { lastVersion = getLogKeyVersion(lastKey.get()); isTimeoutOccurred = true; } + continue; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled || e.code() == error_code_backup_error) - throw e; + err = e; + } + if (err.code() == error_code_actor_cancelled || err.code() == error_code_backup_error) + throw err; - state Error err = e; - wait(logError(cx, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyErrors) - .pack(task->params[BackupAgentBase::keyConfigLogUid]), - format("ERROR: Failed to dump mutations because of error %s", err.what()))); + co_await logError(cx, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyErrors) + .pack(task->params[BackupAgentBase::keyConfigLogUid]), + format("ERROR: Failed to dump mutations because of error %s", err.what())); - throw err; - } + throw err; } } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { // state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - wait(checkTaskVersion(cx, task, CopyLogRangeTaskFunc::name, CopyLogRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, CopyLogRangeTaskFunc::name, CopyLogRangeTaskFunc::version); - state Version beginVersion = + Version beginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyBeginVersion], Unversioned()); - state Version endVersion = + Version endVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyEndVersion], Unversioned()); Version newEndVersion = std::min(endVersion, @@ -880,18 +891,18 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { (g_network->isSimulated() ? CLIENT_KNOBS->BACKUP_SIM_COPY_LOG_RANGES : 0)) * CLIENT_KNOBS->COPY_LOG_BLOCK_SIZE); - state Standalone> ranges = getLogRanges( + Standalone> ranges = getLogRanges( beginVersion, newEndVersion, task->params[BackupAgentBase::destUid], CLIENT_KNOBS->COPY_LOG_BLOCK_SIZE); - state int nRanges = ranges.size(); + int nRanges = ranges.size(); - state std::vector> results; - state std::vector> rc; - state std::vector> locks; - state Version nextVersion = beginVersion; - state double breakTime = timer_monotonic() + CLIENT_KNOBS->COPY_LOG_TASK_DURATION_NANOS; - state int rangeN = 0; + std::vector> results; + std::vector> rc; + std::vector> locks; + Version nextVersion = beginVersion; + double breakTime = timer_monotonic() + CLIENT_KNOBS->COPY_LOG_TASK_DURATION_SECONDS; + int rangeN = 0; - loop { + while (true) { if (rangeN >= nRanges) break; @@ -915,14 +926,14 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { // copy the range Optional nextVersionBr = - wait(dumpData(cx, task, results[rangeN], locks[rangeN].getPtr(), taskBucket, breakTime)); + co_await dumpData(cx, task, results[rangeN], locks[rangeN].getPtr(), taskBucket, breakTime); // exit from the task if a timeout occurs if (nextVersionBr.present()) { nextVersion = nextVersionBr.get(); // cancel prefetch TraceEvent(SevInfo, "CopyLogRangeTaskFuncAborted") - .detail("DurationNanos", CLIENT_KNOBS->COPY_LOG_TASK_DURATION_NANOS) + .detail("DurationSeconds", CLIENT_KNOBS->COPY_LOG_TASK_DURATION_SECONDS) .detail("RangeN", rangeN) .detail("BytesWritten", Params.bytesWritten().getOrDefault(task)); for (int j = results.size(); --j >= rangeN;) @@ -937,17 +948,16 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { if (nextVersion < endVersion) { task->params[CopyLogRangeTaskFunc::keyNextBeginVersion] = BinaryWriter::toValue(nextVersion, Unversioned()); } - return Void(); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version beginVersion, - Version endVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version beginVersion, + Version endVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(CopyLogRangeTaskFunc::name, CopyLogRangeTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); @@ -956,28 +966,28 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { task->params[DatabaseBackupAgent::keyEndVersion] = BinaryWriter::toValue(endVersion, Unversioned()); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { - state Version endVersion = + Version endVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyEndVersion], Unversioned()); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); // Get the bytesWritten parameter from task and atomically add it to the logBytesWritten() property of the DR // config. @@ -986,16 +996,14 @@ struct CopyLogRangeTaskFunc : TaskFuncBase { config.logBytesWritten().atomicOp(tr, bytesWritten, MutationRef::AddValue); if (task->params.find(CopyLogRangeTaskFunc::keyNextBeginVersion) != task->params.end()) { - state Version nextVersion = BinaryReader::fromStringRef( + Version nextVersion = BinaryReader::fromStringRef( task->params[CopyLogRangeTaskFunc::keyNextBeginVersion], Unversioned()); - wait(success(CopyLogRangeTaskFunc::addTask( - tr, taskBucket, task, nextVersion, endVersion, TaskCompletionKey::signal(taskFuture->key))) && - taskBucket->finish(tr, task)); + co_await (success(CopyLogRangeTaskFunc::addTask( + tr, taskBucket, task, nextVersion, endVersion, TaskCompletionKey::signal(taskFuture->key))) && + taskBucket->finish(tr, task)); } else { - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); } - - return Void(); } }; StringRef CopyLogRangeTaskFunc::name = "dr_copy_log_range"_sr; @@ -1006,46 +1014,45 @@ struct CopyLogsTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - state Subspace states = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(tr, task, CopyLogsTaskFunc::name, CopyLogsTaskFunc::version)); - - state Version beginVersion = + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + Subspace states = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + co_await checkTaskVersion(tr, task, CopyLogsTaskFunc::name, CopyLogsTaskFunc::version); + + Version beginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyBeginVersion], Unversioned()); - state Version prevBeginVersion = + Version prevBeginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyPrevBeginVersion], Unversioned()); - state Future> fStopValue = tr->get(states.pack(DatabaseBackupAgent::keyCopyStop)); - state Future> fAppliedValue = + Future> fStopValue = tr->get(states.pack(DatabaseBackupAgent::keyCopyStop)); + Future> fAppliedValue = tr->get(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsBeginRange.begin)); Transaction srcTr(taskBucket->src); srcTr.setOption(FDBTransactionOptions::LOCK_AWARE); - state Version endVersion = wait(srcTr.getReadVersion()); + Version endVersion = co_await srcTr.getReadVersion(); - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); if (endVersion <= beginVersion) { - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(CopyLogsTaskFunc::addTask( - tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)))); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await CopyLogsTaskFunc::addTask( + tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)); + co_await taskBucket->finish(tr, task); + co_return; } - Optional appliedValue = wait(fAppliedValue); - state Version appliedVersion = + Optional appliedValue = co_await fAppliedValue; + Version appliedVersion = appliedValue.present() ? BinaryReader::fromStringRef(appliedValue.get(), Unversioned()) : 100; - state Version applyVersion = - std::max(appliedVersion, beginVersion - CLIENT_KNOBS->BACKUP_VERSION_DELAY); + Version applyVersion = std::max(appliedVersion, beginVersion - CLIENT_KNOBS->BACKUP_VERSION_DELAY); Subspace krv = conf.get(DatabaseBackupAgent::keyRangeVersions); KeyRange versionRange = KeyRangeRef(krv.pack(0), krv.pack(applyVersion + 1)); tr->addReadConflictRange(versionRange); @@ -1053,8 +1060,8 @@ struct CopyLogsTaskFunc : TaskFuncBase { tr->set(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsEndRange.begin), BinaryWriter::toValue(applyVersion, Unversioned())); - Optional stopValue = wait(fStopValue); - state Version stopVersionData = + Optional stopValue = co_await fStopValue; + Version stopVersionData = stopValue.present() ? BinaryReader::fromStringRef(stopValue.get(), Unversioned()) : -1; if (endVersion - beginVersion > deterministicRandom()->randomInt64(0, CLIENT_KNOBS->BACKUP_VERSION_DELAY)) { @@ -1067,7 +1074,7 @@ struct CopyLogsTaskFunc : TaskFuncBase { } if ((stopVersionData == -1) || (stopVersionData >= applyVersion)) { - state Reference allPartsDone = futureBucket->future(tr); + Reference allPartsDone = futureBucket->future(tr); std::vector> addTaskVector; addTaskVector.push_back(CopyLogsTaskFunc::addTask( tr, taskBucket, task, beginVersion, endVersion, TaskCompletionKey::signal(onDone), allPartsDone)); @@ -1091,31 +1098,29 @@ struct CopyLogsTaskFunc : TaskFuncBase { tr, taskBucket, task, beginVersion, TaskCompletionKey::joinWith(allPartsDone))); } - wait(waitForAll(addTaskVector) && taskBucket->finish(tr, task)); + co_await (waitForAll(addTaskVector) && taskBucket->finish(tr, task)); } else { if (appliedVersion < applyVersion) { - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(CopyLogsTaskFunc::addTask( - tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)))); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await CopyLogsTaskFunc::addTask( + tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)); + co_await taskBucket->finish(tr, task); + co_return; } - wait(onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); tr->set(states.pack(DatabaseBackupAgent::keyStateStop), BinaryWriter::toValue(beginVersion, Unversioned())); } - - return Void(); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version prevBeginVersion, - Version beginVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version prevBeginVersion, + Version beginVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(CopyLogsTaskFunc::name, CopyLogsTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); @@ -1123,18 +1128,18 @@ struct CopyLogsTaskFunc : TaskFuncBase { task->params[DatabaseBackupAgent::keyPrevBeginVersion] = BinaryWriter::toValue(prevBeginVersion, Unversioned()); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -1162,107 +1167,120 @@ struct FinishedFullBackupTaskFunc : TaskFuncBase { StringRef getName() const override { return name; }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keySourceStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keySourceStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(cx, task, FinishedFullBackupTaskFunc::name, FinishedFullBackupTaskFunc::version)); + co_await checkTaskVersion(cx, task, FinishedFullBackupTaskFunc::name, FinishedFullBackupTaskFunc::version); - state Transaction tr2(cx); - loop { + Transaction tr2(cx); + while (true) { + Error err; + bool hasErr = false; try { tr2.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional beginValue = wait( - tr2.get(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsBeginRange.begin))); - state Version appliedVersion = + Optional beginValue = co_await tr2.get( + task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsBeginRange.begin)); + Version appliedVersion = beginValue.present() ? BinaryReader::fromStringRef(beginValue.get(), Unversioned()) : -1; - Optional endValue = wait( - tr2.get(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsEndRange.begin))); + Optional endValue = co_await tr2.get( + task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsEndRange.begin)); Version endVersion = endValue.present() ? BinaryReader::fromStringRef(endValue.get(), Unversioned()) : -1; //TraceEvent("DBA_FinishedFullBackup").detail("Applied", appliedVersion).detail("EndVer", endVersion); if (appliedVersion < endVersion) { - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); task->params[FinishedFullBackupTaskFunc::keyInsertTask] = StringRef(); - return Void(); + co_return; } break; } catch (Error& e) { - wait(tr2.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr2.onError(err); } } - state Reference tr(new ReadYourWritesTransaction(taskBucket->src)); - state Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; - state Key destUidValue = task->params[BackupAgentBase::destUid]; - state Version backupUid = + Reference tr(new ReadYourWritesTransaction(taskBucket->src)); + Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; + Key destUidValue = task->params[BackupAgentBase::destUid]; + Version backupUid = BinaryReader::fromStringRef(task->params[BackupAgentBase::keyFolderId], Unversioned()); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional v = wait(tr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId))); + Optional v = co_await tr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId)); if (v.present() && BinaryReader::fromStringRef(v.get(), Unversioned()) > BinaryReader::fromStringRef( task->params[DatabaseBackupAgent::keyFolderId], Unversioned())) - return Void(); + co_return; - wait(eraseLogData(tr, logUidValue, destUidValue, Optional(), CheckBackupUID::True, backupUid)); - wait(tr->commit()); - return Void(); + co_await eraseLogData( + tr, logUidValue, destUidValue, Optional(), CheckBackupUID::True, backupUid); + co_await tr->commit(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(FinishedFullBackupTaskFunc::name, FinishedFullBackupTaskFunc::version, doneKey); copyDefaultParameters(parentTask, task); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - state Subspace states = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + Subspace states = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); if (task->params.find(FinishedFullBackupTaskFunc::keyInsertTask) != task->params.end()) { - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(success(FinishedFullBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::signal(onDone)))); - wait(taskBucket->finish(tr, task)); - return Void(); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await FinishedFullBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::signal(onDone)); + co_await taskBucket->finish(tr, task); + co_return; } tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); @@ -1275,8 +1293,7 @@ struct FinishedFullBackupTaskFunc : TaskFuncBase { tr->set(states.pack(DatabaseBackupAgent::keyStateStatus), StringRef(BackupAgentBase::getStateText(EBackupState::STATE_COMPLETED))); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); } Future execute(Database cx, @@ -1300,41 +1317,41 @@ struct CopyDiffLogsTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - state Subspace states = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(tr, task, CopyDiffLogsTaskFunc::name, CopyDiffLogsTaskFunc::version)); - - state Version beginVersion = + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + Subspace states = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + co_await checkTaskVersion(tr, task, CopyDiffLogsTaskFunc::name, CopyDiffLogsTaskFunc::version); + + Version beginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyBeginVersion], Unversioned()); - state Version prevBeginVersion = + Version prevBeginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyPrevBeginVersion], Unversioned()); - state Future> fStopWhenDone = tr->get(conf.pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey)); + Future> fStopWhenDone = tr->get(conf.pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey)); Transaction srcTr(taskBucket->src); srcTr.setOption(FDBTransactionOptions::LOCK_AWARE); - state Version endVersion = wait(srcTr.getReadVersion()); + Version endVersion = co_await srcTr.getReadVersion(); - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); if (endVersion <= beginVersion) { - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(CopyDiffLogsTaskFunc::addTask( - tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)))); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await CopyDiffLogsTaskFunc::addTask( + tr, taskBucket, task, prevBeginVersion, beginVersion, TaskCompletionKey::signal(onDone)); + co_await taskBucket->finish(tr, task); + co_return; } tr->set(task->params[BackupAgentBase::keyConfigLogUid].withPrefix(applyMutationsEndRange.begin), BinaryWriter::toValue(beginVersion, Unversioned())); - Optional stopWhenDone = wait(fStopWhenDone); + Optional stopWhenDone = co_await fStopWhenDone; if (endVersion - beginVersion > deterministicRandom()->randomInt64(0, CLIENT_KNOBS->BACKUP_VERSION_DELAY)) { TraceEvent("DBA_CopyDiffLogs") @@ -1348,7 +1365,7 @@ struct CopyDiffLogsTaskFunc : TaskFuncBase { BinaryWriter::toValue(beginVersion, Unversioned())); if (!stopWhenDone.present()) { - state Reference allPartsDone = futureBucket->future(tr); + Reference allPartsDone = futureBucket->future(tr); std::vector> addTaskVector; addTaskVector.push_back(CopyDiffLogsTaskFunc::addTask( tr, taskBucket, task, beginVersion, endVersion, TaskCompletionKey::signal(onDone), allPartsDone)); @@ -1371,21 +1388,20 @@ struct CopyDiffLogsTaskFunc : TaskFuncBase { tr, taskBucket, task, beginVersion, TaskCompletionKey::joinWith(allPartsDone))); } - wait(waitForAll(addTaskVector) && taskBucket->finish(tr, task)); + co_await (waitForAll(addTaskVector) && taskBucket->finish(tr, task)); } else { - wait(onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); } - return Void(); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version prevBeginVersion, - Version beginVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version prevBeginVersion, + Version beginVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(CopyDiffLogsTaskFunc::name, CopyDiffLogsTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); @@ -1394,18 +1410,18 @@ struct CopyDiffLogsTaskFunc : TaskFuncBase { task->params[DatabaseBackupAgent::keyPrevBeginVersion] = BinaryWriter::toValue(prevBeginVersion, Unversioned()); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -1431,13 +1447,12 @@ struct SkipOldEraseLogRangeTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); - return Void(); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); } StringRef getName() const override { return name; }; @@ -1485,33 +1500,34 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future dumpData(Database cx, - Reference task, - PromiseStream results, - FlowLock* lock, - Reference tb) { - state bool endOfStream = false; - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - - state std::vector nextMutations; - state int64_t nextMutationSize = 0; - loop { + static Future dumpData(Database cx, + Reference task, + PromiseStream results, + FlowLock* lock, + Reference tb) { + bool endOfStream = false; + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + + std::vector nextMutations; + int64_t nextMutationSize = 0; + while (true) { + Error err; try { if (endOfStream && !nextMutationSize) { - return Void(); + co_return; } - state std::vector mutations = std::move(nextMutations); - state int64_t mutationSize = nextMutationSize; + std::vector mutations = std::move(nextMutations); + int64_t mutationSize = nextMutationSize; nextMutations = std::vector(); nextMutationSize = 0; if (!endOfStream) { - loop { + while (true) { try { - RCGroup group = waitNext(results.getFuture()); + RCGroup group = co_await results.getFuture(); lock->release(group.items.expectedSize()); int vecSize = group.items.expectedSize(); @@ -1525,7 +1541,7 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { mutations.push_back(group.items); mutationSize += vecSize; } catch (Error& e) { - state Error error = e; + Error error = e; if (e.code() == error_code_end_of_stream) { endOfStream = true; break; @@ -1536,19 +1552,21 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { } } - state Transaction tr(cx); + Transaction tr(cx); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.trState->options.sizeLimit = 2 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT; - wait(checkDatabaseLock(&tr, - BinaryReader::fromStringRef( - task->params[BackupAgentBase::keyConfigLogUid], Unversioned()))); - state int64_t bytesSet = 0; + co_await checkDatabaseLock(&tr, + BinaryReader::fromStringRef( + task->params[BackupAgentBase::keyConfigLogUid], Unversioned())); + int64_t bytesSet = 0; bool first = true; - for (auto m : mutations) { + for (const auto& m : mutations) { for (auto kv : m) { if (first) { tr.addReadConflictRange(singleKeyRange(kv.key)); @@ -1561,54 +1579,58 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { } } - wait(tr.commit()); + co_await tr.commit(); Params.bytesWritten().set(task, Params.bytesWritten().getOrDefault(task) + bytesSet); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr.onError(err); } } + continue; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled || e.code() == error_code_backup_error) - throw e; + err = e; + } + if (err.code() == error_code_actor_cancelled || err.code() == error_code_backup_error) + throw err; - state Error err = e; - wait(logError(cx, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyErrors) - .pack(task->params[BackupAgentBase::keyConfigLogUid]), - format("ERROR: Failed to dump mutations because of error %s", err.what()))); + co_await logError(cx, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyErrors) + .pack(task->params[BackupAgentBase::keyConfigLogUid]), + format("ERROR: Failed to dump mutations because of error %s", err.what())); - throw err; - } + throw err; } } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - wait(checkTaskVersion(cx, task, OldCopyLogRangeTaskFunc::name, OldCopyLogRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, OldCopyLogRangeTaskFunc::name, OldCopyLogRangeTaskFunc::version); - state Version beginVersion = + Version beginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyBeginVersion], Unversioned()); - state Version endVersion = + Version endVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyEndVersion], Unversioned()); - state Version newEndVersion = - std::min(endVersion, - (((beginVersion - 1) / CLIENT_KNOBS->BACKUP_BLOCK_SIZE) + 2 + - (g_network->isSimulated() ? CLIENT_KNOBS->BACKUP_SIM_COPY_LOG_RANGES : 0)) * - CLIENT_KNOBS->BACKUP_BLOCK_SIZE); - - state Standalone> ranges = getLogRanges(beginVersion, - newEndVersion, - task->params[BackupAgentBase::keyConfigLogUid], - CLIENT_KNOBS->BACKUP_BLOCK_SIZE); - state std::vector> results; - state std::vector> rc; - state std::vector> dump; + Version newEndVersion = std::min(endVersion, + (((beginVersion - 1) / CLIENT_KNOBS->BACKUP_BLOCK_SIZE) + 2 + + (g_network->isSimulated() ? CLIENT_KNOBS->BACKUP_SIM_COPY_LOG_RANGES : 0)) * + CLIENT_KNOBS->BACKUP_BLOCK_SIZE); + + Standalone> ranges = getLogRanges(beginVersion, + newEndVersion, + task->params[BackupAgentBase::keyConfigLogUid], + CLIENT_KNOBS->BACKUP_BLOCK_SIZE); + std::vector> results; + std::vector> rc; + std::vector> dump; for (int i = 0; i < ranges.size(); ++i) { results.push_back(PromiseStream()); @@ -1625,24 +1647,22 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { dump.push_back(dumpData(cx, task, results[i], lock.getPtr(), taskBucket)); } - wait(waitForAll(dump)); + co_await waitForAll(dump); if (newEndVersion < endVersion) { task->params[OldCopyLogRangeTaskFunc::keyNextBeginVersion] = BinaryWriter::toValue(newEndVersion, Unversioned()); } - - return Void(); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version beginVersion, - Version endVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version beginVersion, + Version endVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(OldCopyLogRangeTaskFunc::name, OldCopyLogRangeTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); @@ -1651,28 +1671,28 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { task->params[DatabaseBackupAgent::keyEndVersion] = BinaryWriter::toValue(endVersion, Unversioned()); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { - state Version endVersion = + Version endVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyEndVersion], Unversioned()); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); // Get the bytesWritten parameter from task and atomically add it to the logBytesWritten() property of the DR // config. @@ -1681,16 +1701,14 @@ struct OldCopyLogRangeTaskFunc : TaskFuncBase { config.logBytesWritten().atomicOp(tr, bytesWritten, MutationRef::AddValue); if (task->params.find(OldCopyLogRangeTaskFunc::keyNextBeginVersion) != task->params.end()) { - state Version nextVersion = BinaryReader::fromStringRef( + Version nextVersion = BinaryReader::fromStringRef( task->params[OldCopyLogRangeTaskFunc::keyNextBeginVersion], Unversioned()); - wait(success(OldCopyLogRangeTaskFunc::addTask( - tr, taskBucket, task, nextVersion, endVersion, TaskCompletionKey::signal(taskFuture->key))) && - taskBucket->finish(tr, task)); + co_await (success(OldCopyLogRangeTaskFunc::addTask( + tr, taskBucket, task, nextVersion, endVersion, TaskCompletionKey::signal(taskFuture->key))) && + taskBucket->finish(tr, task)); } else { - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); } - - return Void(); } }; StringRef OldCopyLogRangeTaskFunc::name = "db_copy_log_range"_sr; @@ -1701,69 +1719,72 @@ struct AbortOldBackupTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state DatabaseBackupAgent srcDrAgent(taskBucket->src); - state Reference tr(new ReadYourWritesTransaction(cx)); - state Key tagNameKey; - - loop { + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + DatabaseBackupAgent srcDrAgent(taskBucket->src); + Reference tr(new ReadYourWritesTransaction(cx)); + Key tagNameKey; + + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); Key tagPath = srcDrAgent.states.get(task->params[DatabaseBackupAgent::keyConfigLogUid]) .pack(BackupAgentBase::keyConfigBackupTag); - Optional tagName = wait(tr->get(tagPath)); + Optional tagName = co_await tr->get(tagPath); if (!tagName.present()) { - return Void(); + co_return; } tagNameKey = tagName.get(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } TraceEvent("DBA_AbortOldBackup").detail("TagName", tagNameKey.printable()); - wait(srcDrAgent.abortBackup(cx, tagNameKey, PartialBackup::False, AbortOldBackup::True)); - - return Void(); + co_await srcDrAgent.abortBackup(cx, tagNameKey, PartialBackup::False, AbortOldBackup::True); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(taskBucket->finish(tr, task)); - return Void(); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await taskBucket->finish(tr, task); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(AbortOldBackupTaskFunc::name, AbortOldBackupTaskFunc::version, doneKey, 1); copyDefaultParameters(parentTask, task); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -1795,58 +1816,65 @@ struct CopyDiffLogsUpgradeTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; - state Subspace sourceStates = + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; + Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keySourceStates).get(logUidValue); - state Subspace config = - Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyConfig).get(logUidValue); - wait(checkTaskVersion(cx, task, CopyDiffLogsUpgradeTaskFunc::name, CopyDiffLogsUpgradeTaskFunc::version)); + Subspace config = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyConfig).get(logUidValue); + co_await checkTaskVersion(cx, task, CopyDiffLogsUpgradeTaskFunc::name, CopyDiffLogsUpgradeTaskFunc::version); // Retrieve backupRanges - state Standalone> backupRanges; - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { + Standalone> backupRanges; + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); Future verified = taskBucket->keepRunning(tr, task); - wait(verified); + co_await verified; - Optional backupKeysPacked = wait(tr->get(config.pack(BackupAgentBase::keyConfigBackupRanges))); + Optional backupKeysPacked = co_await tr->get(config.pack(BackupAgentBase::keyConfigBackupRanges)); if (!backupKeysPacked.present()) { - return Void(); + co_return; } BinaryReader br(backupKeysPacked.get(), IncludeVersion()); br >> backupRanges; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } // Set destUidValue and versionKey on src side - state Key destUidValue(logUidValue); - state Reference srcTr(new ReadYourWritesTransaction(taskBucket->src)); - loop { + Key destUidValue(logUidValue); + Reference srcTr(new ReadYourWritesTransaction(taskBucket->src)); + while (true) { + Error err; + bool hasErr = false; try { srcTr->setOption(FDBTransactionOptions::LOCK_AWARE); srcTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - state Optional v = wait(srcTr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId))); + Optional v = co_await srcTr->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId)); if (v.present() && BinaryReader::fromStringRef(v.get(), Unversioned()) > BinaryReader::fromStringRef( task->params[DatabaseBackupAgent::keyFolderId], Unversioned())) { - return Void(); + co_return; } if (backupRanges.size() == 1 || isDefaultBackup(backupRanges)) { - RangeResult existingDestUidValues = wait(srcTr->getRange( - KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY)); + RangeResult existingDestUidValues = co_await srcTr->getRange( + KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY); bool found = false; KeyRangeRef targetRange = (backupRanges.size() == 1) ? backupRanges[0] : getDefaultBackupSharedRange(); @@ -1856,7 +1884,7 @@ struct CopyDiffLogsUpgradeTaskFunc : TaskFuncBase { if (uidRange == targetRange) { if (destUidValue != it.value) { // existing backup/DR is running - return Void(); + co_return; } else { // due to unknown commit result found = true; @@ -1876,29 +1904,31 @@ struct CopyDiffLogsUpgradeTaskFunc : TaskFuncBase { Key versionKey = logUidValue.withPrefix(destUidValue).withPrefix(backupLatestVersionsPrefix); srcTr->set(versionKey, task->params[DatabaseBackupAgent::keyBeginVersion]); - wait(srcTr->commit()); + co_await srcTr->commit(); break; } catch (Error& e) { - wait(srcTr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await srcTr->onError(err); } } task->params[BackupAgentBase::destUid] = destUidValue; ASSERT(destUidValue == logUidValue); - - return Void(); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(checkTaskVersion(tr, task, CopyDiffLogsUpgradeTaskFunc::name, CopyDiffLogsUpgradeTaskFunc::version)); - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await checkTaskVersion(tr, task, CopyDiffLogsUpgradeTaskFunc::name, CopyDiffLogsUpgradeTaskFunc::version); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - if (task->params[BackupAgentBase::destUid].size() == 0) { + if (task->params[BackupAgentBase::destUid].empty()) { TraceEvent("DBA_CopyDiffLogsUpgradeTaskFuncAbortInUpgrade").log(); - wait(success(AbortOldBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::signal(onDone)))); + co_await AbortOldBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::signal(onDone)); } else { Version beginVersion = BinaryReader::fromStringRef(task->params[DatabaseBackupAgent::keyBeginVersion], Unversioned()); @@ -1908,12 +1938,11 @@ struct CopyDiffLogsUpgradeTaskFunc : TaskFuncBase { tr->set(config.pack(BackupAgentBase::destUid), task->params[BackupAgentBase::destUid]); tr->set(config.pack(BackupAgentBase::keyDrVersion), BinaryWriter::toValue(DatabaseBackupAgent::LATEST_DR_VERSION, Unversioned())); - wait(success(CopyDiffLogsTaskFunc::addTask( - tr, taskBucket, task, 0, beginVersion, TaskCompletionKey::signal(onDone)))); + co_await CopyDiffLogsTaskFunc::addTask( + tr, taskBucket, task, 0, beginVersion, TaskCompletionKey::signal(onDone)); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); } StringRef getName() const override { return name; }; @@ -1938,16 +1967,18 @@ struct BackupRestorableTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keySourceStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(cx, task, BackupRestorableTaskFunc::name, BackupRestorableTaskFunc::version)); - state Transaction tr(taskBucket->src); - loop { + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keySourceStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + co_await checkTaskVersion(cx, task, BackupRestorableTaskFunc::name, BackupRestorableTaskFunc::version); + Transaction tr(taskBucket->src); + while (true) { + Error err; + bool hasErr = false; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.addReadConflictRange(singleKeyRange(sourceStates.pack(DatabaseBackupAgent::keyStateStatus))); @@ -1957,41 +1988,45 @@ struct BackupRestorableTaskFunc : TaskFuncBase { Key versionKey = task->params[DatabaseBackupAgent::keyConfigLogUid] .withPrefix(task->params[BackupAgentBase::destUid]) .withPrefix(backupLatestVersionsPrefix); - Optional prevBeginVersion = wait(tr.get(versionKey)); + Optional prevBeginVersion = co_await tr.get(versionKey); if (!prevBeginVersion.present()) { - return Void(); + co_return; } task->params[DatabaseBackupAgent::keyPrevBeginVersion] = prevBeginVersion.get(); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr.onError(err); } } } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Subspace conf = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - state Subspace states = Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyStates) - .get(task->params[BackupAgentBase::keyConfigLogUid]); - wait(checkTaskVersion(tr, task, BackupRestorableTaskFunc::name, BackupRestorableTaskFunc::version)); - - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - - state Optional stopValue = wait(tr->get(states.pack(DatabaseBackupAgent::keyStateStop))); - state Version restoreVersion = + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Subspace conf = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + Subspace states = Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyStates) + .get(task->params[BackupAgentBase::keyConfigLogUid]); + co_await checkTaskVersion(tr, task, BackupRestorableTaskFunc::name, BackupRestorableTaskFunc::version); + + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + + Optional stopValue = co_await tr->get(states.pack(DatabaseBackupAgent::keyStateStop)); + Version restoreVersion = stopValue.present() ? BinaryReader::fromStringRef(stopValue.get(), Unversioned()) : -1; - state Optional stopWhenDone = wait(tr->get(conf.pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey))); - state Reference allPartsDone; + Optional stopWhenDone = co_await tr->get(conf.pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey)); + Reference allPartsDone; TraceEvent("DBA_Complete") .detail("RestoreVersion", restoreVersion) @@ -2001,7 +2036,7 @@ struct BackupRestorableTaskFunc : TaskFuncBase { // Start the complete task, if differential is not enabled if (stopWhenDone.present()) { // After the Backup completes, clear the backup subspace and update the status - wait(success(FinishedFullBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()))); + co_await FinishedFullBackupTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()); } else { // Start the writing of logs, if differential tr->set(states.pack(DatabaseBackupAgent::keyStateStatus), StringRef(BackupAgentBase::getStateText(EBackupState::STATE_RUNNING_DIFFERENTIAL))); @@ -2010,41 +2045,40 @@ struct BackupRestorableTaskFunc : TaskFuncBase { Version prevBeginVersion = BinaryReader::fromStringRef( task->params[DatabaseBackupAgent::keyPrevBeginVersion], Unversioned()); - wait(success(CopyDiffLogsTaskFunc::addTask( - tr, taskBucket, task, prevBeginVersion, restoreVersion, TaskCompletionKey::joinWith(allPartsDone)))); + co_await CopyDiffLogsTaskFunc::addTask( + tr, taskBucket, task, prevBeginVersion, restoreVersion, TaskCompletionKey::joinWith(allPartsDone)); // After the Backup completes, clear the backup subspace and update the status - wait(success(FinishedFullBackupTaskFunc::addTask( - tr, taskBucket, task, TaskCompletionKey::noSignal(), allPartsDone))); + co_await FinishedFullBackupTaskFunc::addTask( + tr, taskBucket, task, TaskCompletionKey::noSignal(), allPartsDone); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(BackupRestorableTaskFunc::name, BackupRestorableTaskFunc::version, doneKey); copyDefaultParameters(parentTask, task); if (!waitFor) { - return taskBucket->addTask(tr, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - parentTask->params[Task::reservedTaskParamValidKey], - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + parentTask->params[Task::reservedTaskParamValidKey], + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -2069,31 +2103,32 @@ struct StartFullBackupTaskFunc : TaskFuncBase { static StringRef name; static constexpr uint32_t version = 1; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; - state Subspace sourceStates = + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Key logUidValue = task->params[DatabaseBackupAgent::keyConfigLogUid]; + Subspace sourceStates = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keySourceStates).get(logUidValue); - wait(checkTaskVersion(cx, task, StartFullBackupTaskFunc::name, StartFullBackupTaskFunc::version)); - state Key destUidValue(logUidValue); + co_await checkTaskVersion(cx, task, StartFullBackupTaskFunc::name, StartFullBackupTaskFunc::version); + Key destUidValue(logUidValue); - state Standalone> backupRanges = - BinaryReader::fromStringRef>>( - task->params[DatabaseBackupAgent::keyConfigBackupRanges], IncludeVersion()); - state Key beginVersionKey; + auto backupRanges = BinaryReader::fromStringRef>>( + task->params[DatabaseBackupAgent::keyConfigBackupRanges], IncludeVersion()); + Key beginVersionKey; - state Reference srcTr(new ReadYourWritesTransaction(taskBucket->src)); - loop { + Reference srcTr(new ReadYourWritesTransaction(taskBucket->src)); + while (true) { + Error err; + bool hasErr = false; try { srcTr->setOption(FDBTransactionOptions::LOCK_AWARE); srcTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); // Initialize destUid if (backupRanges.size() == 1 || isDefaultBackup(backupRanges)) { - RangeResult existingDestUidValues = wait(srcTr->getRange( - KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY)); + RangeResult existingDestUidValues = co_await srcTr->getRange( + KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY); KeyRangeRef targetRange = (backupRanges.size() == 1) ? backupRanges[0] : getDefaultBackupSharedRange(); bool found = false; @@ -2117,39 +2152,45 @@ struct StartFullBackupTaskFunc : TaskFuncBase { } } - Version bVersion = wait(srcTr->getReadVersion()); + Version bVersion = co_await srcTr->getReadVersion(); beginVersionKey = BinaryWriter::toValue(bVersion, Unversioned()); - state Key versionKey = logUidValue.withPrefix(destUidValue).withPrefix(backupLatestVersionsPrefix); - Optional versionRecord = wait(srcTr->get(versionKey)); + Key versionKey = logUidValue.withPrefix(destUidValue).withPrefix(backupLatestVersionsPrefix); + Optional versionRecord = co_await srcTr->get(versionKey); if (!versionRecord.present()) { srcTr->set(versionKey, beginVersionKey); } task->params[BackupAgentBase::destUid] = destUidValue; - wait(srcTr->commit()); + co_await srcTr->commit(); break; } catch (Error& e) { - wait(srcTr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await srcTr->onError(err); } } - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - state Future verified = taskBucket->keepRunning(tr, task); - wait(verified); + Future verified = taskBucket->keepRunning(tr, task); + co_await verified; // Set destUid at destination side - state Subspace config = + Subspace config = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyConfig).get(logUidValue); tr->set(config.pack(BackupAgentBase::destUid), task->params[BackupAgentBase::destUid]); // Use existing beginVersion if we already have one - Optional backupStartVersion = wait(tr->get(config.pack(BackupAgentBase::backupStartVersion))); + Optional backupStartVersion = co_await tr->get(config.pack(BackupAgentBase::backupStartVersion)); if (backupStartVersion.present()) { beginVersionKey = backupStartVersion.get(); } else { @@ -2158,26 +2199,32 @@ struct StartFullBackupTaskFunc : TaskFuncBase { task->params[BackupAgentBase::keyBeginVersion] = beginVersionKey; - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - TraceEvent("SetDestUidOrBeginVersionError").errorUnsuppressed(e); - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + TraceEvent("SetDestUidOrBeginVersionError").errorUnsuppressed(err); + co_await tr->onError(err); } } - state Reference srcTr2(new ReadYourWritesTransaction(taskBucket->src)); - loop { + Reference srcTr2(new ReadYourWritesTransaction(taskBucket->src)); + while (true) { + Error err; + bool hasErr = false; try { srcTr2->setOption(FDBTransactionOptions::LOCK_AWARE); srcTr2->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - state Optional v = wait(srcTr2->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId))); + Optional v = co_await srcTr2->get(sourceStates.pack(DatabaseBackupAgent::keyFolderId)); if (v.present() && BinaryReader::fromStringRef(v.get(), Unversioned()) >= BinaryReader::fromStringRef( task->params[DatabaseBackupAgent::keyFolderId], Unversioned())) - return Void(); + co_return; srcTr2->set(Subspace(databaseBackupPrefixRange.begin) .get(BackupAgentBase::keySourceTagName) @@ -2188,7 +2235,7 @@ struct StartFullBackupTaskFunc : TaskFuncBase { srcTr2->set(sourceStates.pack(DatabaseBackupAgent::keyStateStatus), StringRef(BackupAgentBase::getStateText(EBackupState::STATE_RUNNING))); - state Key destPath = destUidValue.withPrefix(backupLogKeys.begin); + Key destPath = destUidValue.withPrefix(backupLogKeys.begin); // Start logging the mutations for the specified ranges of the tag for (auto& backupRange : backupRanges) { srcTr2->set(logRangesEncodeKey(backupRange.begin, @@ -2196,46 +2243,51 @@ struct StartFullBackupTaskFunc : TaskFuncBase { logRangesEncodeValue(backupRange.end, destPath)); } - wait(srcTr2->commit()); + co_await srcTr2->commit(); break; } catch (Error& e) { - wait(srcTr2->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await srcTr2->onError(err); } } - state Reference srcTr3(new ReadYourWritesTransaction(taskBucket->src)); - loop { + Reference srcTr3(new ReadYourWritesTransaction(taskBucket->src)); + while (true) { + Error err; + bool hasErr = false; try { srcTr3->setOption(FDBTransactionOptions::LOCK_AWARE); srcTr3->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); srcTr3->atomicOp(metadataVersionKey, metadataVersionRequiredValue, MutationRef::SetVersionstampedValue); - wait(srcTr3->commit()); + co_await srcTr3->commit(); break; } catch (Error& e) { - wait(srcTr3->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await srcTr3->onError(err); } } - - return Void(); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Key logUidValue = task->params[BackupAgentBase::keyConfigLogUid]; - state Subspace states = - Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyStates).get(logUidValue); - state Subspace config = - Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyConfig).get(logUidValue); - - state Version beginVersion = + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Key logUidValue = task->params[BackupAgentBase::keyConfigLogUid]; + Subspace states = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyStates).get(logUidValue); + Subspace config = Subspace(databaseBackupPrefixRange.begin).get(BackupAgentBase::keyConfig).get(logUidValue); + + Version beginVersion = BinaryReader::fromStringRef(task->params[BackupAgentBase::keyBeginVersion], Unversioned()); - state Standalone> backupRanges = - BinaryReader::fromStringRef>>( - task->params[DatabaseBackupAgent::keyConfigBackupRanges], IncludeVersion()); + auto backupRanges = BinaryReader::fromStringRef>>( + task->params[DatabaseBackupAgent::keyConfigBackupRanges], IncludeVersion()); tr->set(logUidValue.withPrefix(applyMutationsBeginRange.begin), BinaryWriter::toValue(beginVersion, Unversioned())); @@ -2244,40 +2296,39 @@ struct StartFullBackupTaskFunc : TaskFuncBase { tr->set(states.pack(DatabaseBackupAgent::keyStateStatus), StringRef(BackupAgentBase::getStateText(EBackupState::STATE_RUNNING))); - state Reference kvBackupRangeComplete = futureBucket->future(tr); - state Reference kvBackupComplete = futureBucket->future(tr); - state int rangeCount = 0; + Reference kvBackupRangeComplete = futureBucket->future(tr); + Reference kvBackupComplete = futureBucket->future(tr); + int rangeCount = 0; if (task->params[DatabaseBackupAgent::keyDatabasesInSync] != std::string("t")) { for (; rangeCount < backupRanges.size(); ++rangeCount) { - wait(success(BackupRangeTaskFunc::addTask(tr, - taskBucket, - task, - backupRanges[rangeCount].begin, - backupRanges[rangeCount].end, - TaskCompletionKey::joinWith(kvBackupRangeComplete)))); + co_await BackupRangeTaskFunc::addTask(tr, + taskBucket, + task, + backupRanges[rangeCount].begin, + backupRanges[rangeCount].end, + TaskCompletionKey::joinWith(kvBackupRangeComplete)); } } else { kvBackupRangeComplete->set(tr, taskBucket); } // After the BackupRangeTask completes, set the stop key which will stop the BackupLogsTask - wait(success(FinishFullBackupTaskFunc::addTask( - tr, taskBucket, task, TaskCompletionKey::noSignal(), kvBackupRangeComplete))); + co_await FinishFullBackupTaskFunc::addTask( + tr, taskBucket, task, TaskCompletionKey::noSignal(), kvBackupRangeComplete); // Backup the logs which will create BackupLogRange tasks - wait(success(CopyLogsTaskFunc::addTask( - tr, taskBucket, task, 0, beginVersion, TaskCompletionKey::joinWith(kvBackupComplete)))); + co_await CopyLogsTaskFunc::addTask( + tr, taskBucket, task, 0, beginVersion, TaskCompletionKey::joinWith(kvBackupComplete)); // After the Backup completes, clear the backup subspace and update the status - wait(success( - BackupRestorableTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal(), kvBackupComplete))); + co_await BackupRestorableTaskFunc::addTask( + tr, taskBucket, task, TaskCompletionKey::noSignal(), kvBackupComplete); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); } - ACTOR static Future addTask( + static Future addTask( Reference tr, Reference taskBucket, Key logUid, @@ -2289,7 +2340,7 @@ struct StartFullBackupTaskFunc : TaskFuncBase { TaskCompletionKey completionKey, Reference waitFor = Reference(), DatabaseBackupAgent::PreBackupAction backupAction = DatabaseBackupAgent::PreBackupAction::VERIFY) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + Key doneKey = co_await completionKey.get(tr, taskBucket); auto task = makeReference(StartFullBackupTaskFunc::name, StartFullBackupTaskFunc::version, doneKey); task->params[BackupAgentBase::keyFolderId] = backupUid; @@ -2302,24 +2353,24 @@ struct StartFullBackupTaskFunc : TaskFuncBase { backupAction == DatabaseBackupAgent::PreBackupAction::NONE ? "t"_sr : "f"_sr; if (!waitFor) { - return taskBucket->addTask(tr, - task, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(logUid) - .pack(BackupAgentBase::keyFolderId), - task->params[BackupAgentBase::keyFolderId]); - } - - wait(waitFor->onSetAddTask(tr, - taskBucket, - task, - Subspace(databaseBackupPrefixRange.begin) - .get(BackupAgentBase::keyConfig) - .get(logUid) - .pack(BackupAgentBase::keyFolderId), - task->params[BackupAgentBase::keyFolderId])); - return "OnSetAddTask"_sr; + co_return taskBucket->addTask(tr, + task, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(logUid) + .pack(BackupAgentBase::keyFolderId), + task->params[BackupAgentBase::keyFolderId]); + } + + co_await waitFor->onSetAddTask(tr, + taskBucket, + task, + Subspace(databaseBackupPrefixRange.begin) + .get(BackupAgentBase::keyConfig) + .get(logUid) + .pack(BackupAgentBase::keyFolderId), + task->params[BackupAgentBase::keyFolderId]); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -2349,7 +2400,7 @@ std::set getDRAgentsIds(StatusObjectReader statusObj, const char* c StatusObjectReader instances; std::string path = format("%s.instances", context); if (statusObjLayers.tryGet(path, instances)) { - for (auto itr : instances.obj()) { + for (const auto& itr : instances.obj()) { drBackupAgents.insert(itr.first); } } @@ -2434,23 +2485,25 @@ void checkAtomicSwitchOverConfig(StatusObjectReader srcStatus, StatusObjectReade class DatabaseBackupAgentImpl { public: - ACTOR static Future waitUpgradeToLatestDrVersion(DatabaseBackupAgent* backupAgent, Database cx, Key tagName) { - state UID logUid = wait(backupAgent->getLogUid(cx, tagName)); - state Key drVersionKey = backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) - .pack(DatabaseBackupAgent::keyDrVersion); + static Future waitUpgradeToLatestDrVersion(DatabaseBackupAgent* backupAgent, Database cx, Key tagName) { + UID logUid = co_await backupAgent->getLogUid(cx, tagName); + Key drVersionKey = backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) + .pack(DatabaseBackupAgent::keyDrVersion); TraceEvent("DRU_WatchLatestDrVersion") .detail("DrVersionKey", drVersionKey.printable()) .detail("LogUid", BinaryWriter::toValue(logUid, Unversioned()).printable()); - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional drVersion = wait(tr->get(drVersionKey)); + Optional drVersion = co_await tr->get(drVersionKey); TraceEvent("DRU_VersionCheck") .detail("Current", @@ -2460,97 +2513,113 @@ class DatabaseBackupAgentImpl { .detail("LogUid", BinaryWriter::toValue(logUid, Unversioned()).printable()); if (drVersion.present() && BinaryReader::fromStringRef(drVersion.get(), Unversioned()) == DatabaseBackupAgent::LATEST_DR_VERSION) { - return Void(); + co_return; } - state Future watchDrVersionFuture = tr->watch(drVersionKey); - wait(tr->commit()); - wait(watchDrVersionFuture); + Future watchDrVersionFuture = tr->watch(drVersionKey); + co_await tr->commit(); + co_await watchDrVersionFuture; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } } // This method will return the final status of the backup - ACTOR static Future waitBackup(DatabaseBackupAgent* backupAgent, - Database cx, - Key tagName, - StopWhenDone stopWhenDone) { - state std::string backTrace; - state UID logUid = wait(backupAgent->getLogUid(cx, tagName)); - state Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) - .pack(DatabaseBackupAgent::keyStateStatus); - - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future waitBackup(DatabaseBackupAgent* backupAgent, + Database cx, + Key tagName, + StopWhenDone stopWhenDone) { + std::string backTrace; + UID logUid = co_await backupAgent->getLogUid(cx, tagName); + Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) + .pack(DatabaseBackupAgent::keyStateStatus); + + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; + bool hasErr = false; try { - state EBackupState status = wait(backupAgent->getStateValue(tr, logUid)); + EBackupState status = co_await backupAgent->getStateValue(tr, logUid); // Break, if no longer runnable if (!DatabaseBackupAgent::isRunnable(status) || EBackupState::STATE_PARTIALLY_ABORTED == status) { - return status; + co_return status; } // Break, if in differential mode (restorable) and stopWhenDone is not enabled if ((!stopWhenDone) && (EBackupState::STATE_RUNNING_DIFFERENTIAL == status)) { - return status; + co_return status; } - state Future watchFuture = tr->watch(statusKey); - wait(tr->commit()); - wait(watchFuture); + Future watchFuture = tr->watch(statusKey); + co_await tr->commit(); + co_await watchFuture; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } // This method will return the final status of the backup - ACTOR static Future waitSubmitted(DatabaseBackupAgent* backupAgent, Database cx, Key tagName) { - state UID logUid = wait(backupAgent->getLogUid(cx, tagName)); - state Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) - .pack(DatabaseBackupAgent::keyStateStatus); + static Future waitSubmitted(DatabaseBackupAgent* backupAgent, Database cx, Key tagName) { + UID logUid = co_await backupAgent->getLogUid(cx, tagName); + Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) + .pack(DatabaseBackupAgent::keyStateStatus); - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; + bool hasErr = false; try { - state EBackupState status = wait(backupAgent->getStateValue(tr, logUid)); + EBackupState status = co_await backupAgent->getStateValue(tr, logUid); // Break, if no longer runnable if (EBackupState::STATE_SUBMITTED != status) { - return status; + co_return status; } - state Future watchFuture = tr->watch(statusKey); - wait(tr->commit()); - wait(watchFuture); + Future watchFuture = tr->watch(statusKey); + co_await tr->commit(); + co_await watchFuture; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } - ACTOR static Future submitBackup(DatabaseBackupAgent* backupAgent, - Reference tr, - Key tagName, - Standalone> backupRanges, - StopWhenDone stopWhenDone, - Key addPrefix, - Key removePrefix, - LockDB lockDB, - DatabaseBackupAgent::PreBackupAction backupAction) { - state UID logUid = deterministicRandom()->randomUniqueID(); - state Key logUidValue = BinaryWriter::toValue(logUid, Unversioned()); - state UID logUidCurrent = wait(backupAgent->getLogUid(tr, tagName)); + static Future submitBackup(DatabaseBackupAgent* backupAgent, + Reference tr, + Key tagName, + Standalone> backupRanges, + StopWhenDone stopWhenDone, + Key addPrefix, + Key removePrefix, + LockDB lockDB, + DatabaseBackupAgent::PreBackupAction backupAction) { + UID logUid = deterministicRandom()->randomUniqueID(); + Key logUidValue = BinaryWriter::toValue(logUid, Unversioned()); + UID logUidCurrent = co_await backupAgent->getLogUid(tr, tagName); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); @@ -2560,7 +2629,7 @@ class DatabaseBackupAgentImpl { tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); // We will use the global status for now to ensure that multiple backups do not start place with different tags - state EBackupState status = wait(backupAgent->getStateValue(tr, logUidCurrent)); + EBackupState status = co_await backupAgent->getStateValue(tr, logUidCurrent); if (DatabaseBackupAgent::isRunnable(status)) { throw backup_duplicate(); @@ -2571,11 +2640,11 @@ class DatabaseBackupAgentImpl { logUidValue = BinaryWriter::toValue(logUid, Unversioned()); } - Optional v = wait(tr->get(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId))); + Optional v = co_await tr->get(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId)); Version uidVersion = 0; if (v.present()) uidVersion = BinaryReader::fromStringRef(v.get(), Unversioned()) + 1; - state Standalone backupUid = BinaryWriter::toValue(uidVersion, Unversioned()); + Standalone backupUid = BinaryWriter::toValue(uidVersion, Unversioned()); KeyRangeMap backupRangeSet; for (auto& backupRange : backupRanges) { @@ -2593,14 +2662,14 @@ class DatabaseBackupAgentImpl { if (backupAction == DatabaseBackupAgent::PreBackupAction::VERIFY) { // Make sure all of the ranges are empty before we backup into them. - state std::vector> backupIntoResults; + std::vector> backupIntoResults; for (auto& backupRange : backupRanges) { backupIntoResults.push_back( tr->getRange(backupRange.removePrefix(removePrefix).withPrefix(addPrefix), 1)); } - wait(waitForAll(backupIntoResults)); - for (auto result : backupIntoResults) { - if (result.get().size() > 0) { + co_await waitForAll(backupIntoResults); + for (const auto& result : backupIntoResults) { + if (!result.get().empty()) { // One of the ranges we will be backing up into has pre-existing data. throw restore_destination_not_empty(); } @@ -2640,23 +2709,23 @@ class DatabaseBackupAgentImpl { } int64_t startCount = 0; - state Key mapPrefix = logUidValue.withPrefix(applyMutationsKeyVersionMapRange.begin); + Key mapPrefix = logUidValue.withPrefix(applyMutationsKeyVersionMapRange.begin); Key mapEnd = allKeys.end.withPrefix(mapPrefix); tr->set(logUidValue.withPrefix(applyMutationsAddPrefixRange.begin), addPrefix); tr->set(logUidValue.withPrefix(applyMutationsRemovePrefixRange.begin), removePrefix); tr->set(logUidValue.withPrefix(applyMutationsKeyVersionCountRange.begin), StringRef((uint8_t*)&startCount, 8)); tr->clear(KeyRangeRef(mapPrefix, mapEnd)); - state Version readVersion = invalidVersion; + Version readVersion = invalidVersion; if (backupAction == DatabaseBackupAgent::PreBackupAction::NONE) { Transaction readTransaction(backupAgent->taskBucket->src); readTransaction.setOption(FDBTransactionOptions::LOCK_AWARE); - Version _ = wait(readTransaction.getReadVersion()); + Version _ = co_await readTransaction.getReadVersion(); readVersion = _; } tr->set(mapPrefix, BinaryWriter::toValue(readVersion, Unversioned())); - Key taskKey = wait(dbBackup::StartFullBackupTaskFunc::addTask( + Key taskKey = co_await dbBackup::StartFullBackupTaskFunc::addTask( tr, backupAgent->taskBucket, logUidValue, @@ -2667,12 +2736,12 @@ class DatabaseBackupAgentImpl { tagName, TaskCompletionKey::noSignal(), Reference(), - backupAction)); + backupAction); if (lockDB) - wait(lockDatabase(tr, logUid)); + co_await lockDatabase(tr, logUid); else - wait(checkDatabaseLock(tr, logUid)); + co_await checkDatabaseLock(tr, logUid); TraceEvent("DBA_Submit") .detail("LogUid", logUid) @@ -2681,29 +2750,26 @@ class DatabaseBackupAgentImpl { .detail("Tag", tagName) .detail("Key", backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId)) .detail("MapPrefix", mapPrefix); - - return Void(); } - ACTOR static Future unlockBackup(DatabaseBackupAgent* backupAgent, - Reference tr, - Key tagName) { - UID logUid = wait(backupAgent->getLogUid(tr, tagName)); - wait(unlockDatabase(tr, logUid)); + static Future unlockBackup(DatabaseBackupAgent* backupAgent, + Reference tr, + Key tagName) { + UID logUid = co_await backupAgent->getLogUid(tr, tagName); + co_await unlockDatabase(tr, logUid); TraceEvent("DBA_Unlock").detail("Tag", tagName); - return Void(); } - ACTOR static Future atomicSwitchover(DatabaseBackupAgent* backupAgent, - Database dest, - Key tagName, - Standalone> backupRanges, - Key addPrefix, - Key removePrefix, - ForceAction forceAction) { - state DatabaseBackupAgent drAgent(dest); - state UID destlogUid = wait(backupAgent->getLogUid(dest, tagName)); - state EBackupState status = wait(backupAgent->getStateValue(dest, destlogUid)); + static Future atomicSwitchover(DatabaseBackupAgent* backupAgent, + Database dest, + Key tagName, + Standalone> backupRanges, + Key addPrefix, + Key removePrefix, + ForceAction forceAction) { + DatabaseBackupAgent drAgent(dest); + UID destlogUid = co_await backupAgent->getLogUid(dest, tagName); + EBackupState status = co_await backupAgent->getStateValue(dest, destlogUid); TraceEvent("DBA_SwitchoverStart").detail("Status", status); if (status != EBackupState::STATE_RUNNING_DIFFERENTIAL && status != EBackupState::STATE_COMPLETED) { @@ -2711,14 +2777,14 @@ class DatabaseBackupAgentImpl { } if (!g_network->isSimulated() && !forceAction) { - state StatusObject srcStatus = wait(StatusClient::statusFetcher(backupAgent->taskBucket->src)); - StatusObject destStatus = wait(StatusClient::statusFetcher(dest)); + StatusObject srcStatus = co_await StatusClient::statusFetcher(backupAgent->taskBucket->src); + StatusObject destStatus = co_await StatusClient::statusFetcher(dest); checkAtomicSwitchOverConfig(srcStatus, destStatus, tagName); } - state UID logUid = deterministicRandom()->randomUniqueID(); - state Key logUidValue = BinaryWriter::toValue(logUid, Unversioned()); - state UID logUidCurrent = wait(drAgent.getLogUid(backupAgent->taskBucket->src, tagName)); + UID logUid = deterministicRandom()->randomUniqueID(); + Key logUidValue = BinaryWriter::toValue(logUid, Unversioned()); + UID logUidCurrent = co_await drAgent.getLogUid(backupAgent->taskBucket->src, tagName); if (logUidCurrent.isValid()) { logUid = logUidCurrent; @@ -2726,31 +2792,39 @@ class DatabaseBackupAgentImpl { } // Lock src, record commit version - state Transaction tr(backupAgent->taskBucket->src); - state Version commitVersion; - loop { + Transaction tr(backupAgent->taskBucket->src); + Version commitVersion{ 0 }; + while (true) { + Error err; + bool hasErr = false; try { - wait(lockDatabase(&tr, logUid)); + co_await lockDatabase(&tr, logUid); tr.set(backupAgent->tagNames.pack(tagName), logUidValue); - wait(tr.commit()); + co_await tr.commit(); commitVersion = tr.getCommittedVersion(); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr.onError(err); } } TraceEvent("DBA_SwitchoverLocked").detail("Version", commitVersion); // Wait for the destination to apply mutations up to the lock commit before switching over. - state ReadYourWritesTransaction tr2(dest); - loop { + ReadYourWritesTransaction tr2(dest); + while (true) { + Error err; + bool hasErr = false; try { tr2.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr2.setOption(FDBTransactionOptions::LOCK_AWARE); - state Optional backupUid = - wait(tr2.get(backupAgent->states.get(BinaryWriter::toValue(destlogUid, Unversioned())) - .pack(DatabaseBackupAgent::keyFolderId))); + Optional backupUid = + co_await tr2.get(backupAgent->states.get(BinaryWriter::toValue(destlogUid, Unversioned())) + .pack(DatabaseBackupAgent::keyFolderId)); TraceEvent("DBA_SwitchoverBackupUID") .detail("Uid", backupUid) .detail("Key", @@ -2758,66 +2832,76 @@ class DatabaseBackupAgentImpl { .pack(DatabaseBackupAgent::keyFolderId)); if (!backupUid.present()) throw backup_duplicate(); - Optional v = wait(tr2.get( - BinaryWriter::toValue(destlogUid, Unversioned()).withPrefix(applyMutationsBeginRange.begin))); + Optional v = co_await tr2.get( + BinaryWriter::toValue(destlogUid, Unversioned()).withPrefix(applyMutationsBeginRange.begin)); TraceEvent("DBA_SwitchoverVersion") .detail("Version", v.present() ? BinaryReader::fromStringRef(v.get(), Unversioned()) : 0); if (v.present() && BinaryReader::fromStringRef(v.get(), Unversioned()) >= commitVersion) break; - state Future versionWatch = tr2.watch( + Future versionWatch = tr2.watch( BinaryWriter::toValue(destlogUid, Unversioned()).withPrefix(applyMutationsBeginRange.begin)); - wait(tr2.commit()); - wait(versionWatch); + co_await tr2.commit(); + co_await versionWatch; tr2.reset(); } catch (Error& e) { - wait(tr2.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr2.onError(err); } } TraceEvent("DBA_SwitchoverReady").log(); try { - wait(backupAgent->discontinueBackup(dest, tagName)); + co_await backupAgent->discontinueBackup(dest, tagName); } catch (Error& e) { if (e.code() != error_code_backup_duplicate && e.code() != error_code_backup_unneeded) throw; } - wait(success(backupAgent->waitBackup(dest, tagName, StopWhenDone::True))); + co_await backupAgent->waitBackup(dest, tagName, StopWhenDone::True); TraceEvent("DBA_SwitchoverStopped").log(); - state ReadYourWritesTransaction tr3(dest); - loop { + ReadYourWritesTransaction tr3(dest); + while (true) { + Error err; + bool hasErr = false; try { tr3.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr3.setOption(FDBTransactionOptions::LOCK_AWARE); - Version destVersion = wait(tr3.getReadVersion()); + Version destVersion = co_await tr3.getReadVersion(); TraceEvent("DBA_SwitchoverVersionUpgrade").detail("Src", commitVersion).detail("Dest", destVersion); if (destVersion <= commitVersion) { CODE_PROBE(true, "Forcing dest backup cluster to higher version"); tr3.set(minRequiredCommitVersionKey, BinaryWriter::toValue(commitVersion + 1, Unversioned())); - wait(tr3.commit()); + co_await tr3.commit(); } else { break; } } catch (Error& e) { - wait(tr3.onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr3.onError(err); } } TraceEvent("DBA_SwitchoverVersionUpgraded").log(); try { - wait(drAgent.submitBackup(backupAgent->taskBucket->src, - tagName, - backupRanges, - StopWhenDone::False, - addPrefix, - removePrefix, - LockDB::True, - DatabaseBackupAgent::PreBackupAction::NONE)); + co_await drAgent.submitBackup(backupAgent->taskBucket->src, + tagName, + backupRanges, + StopWhenDone::False, + addPrefix, + removePrefix, + LockDB::True, + DatabaseBackupAgent::PreBackupAction::NONE); } catch (Error& e) { if (e.code() != error_code_backup_duplicate) throw; @@ -2825,32 +2909,30 @@ class DatabaseBackupAgentImpl { TraceEvent("DBA_SwitchoverSubmitted").log(); - wait(success(drAgent.waitSubmitted(backupAgent->taskBucket->src, tagName))); + co_await drAgent.waitSubmitted(backupAgent->taskBucket->src, tagName); TraceEvent("DBA_SwitchoverStarted").log(); - wait(backupAgent->unlockBackup(dest, tagName)); + co_await backupAgent->unlockBackup(dest, tagName); TraceEvent("DBA_SwitchoverUnlocked").log(); - - return Void(); } - ACTOR static Future discontinueBackup(DatabaseBackupAgent* backupAgent, - Reference tr, - Key tagName) { + static Future discontinueBackup(DatabaseBackupAgent* backupAgent, + Reference tr, + Key tagName) { tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state UID logUid = wait(backupAgent->getLogUid(tr, tagName)); - state EBackupState status = wait(backupAgent->getStateValue(tr, logUid)); + UID logUid = co_await backupAgent->getLogUid(tr, tagName); + EBackupState status = co_await backupAgent->getStateValue(tr, logUid); TraceEvent("DBA_Discontinue").detail("Status", status); if (!DatabaseBackupAgent::isRunnable(status)) { throw backup_unneeded(); } - state Optional stopWhenDoneValue = - wait(tr->get(backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) - .pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey))); + Optional stopWhenDoneValue = + co_await tr->get(backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) + .pack(DatabaseBackupAgent::keyConfigStopWhenDoneKey)); if (stopWhenDoneValue.present()) { throw backup_duplicate(); @@ -2861,35 +2943,37 @@ class DatabaseBackupAgentImpl { tr->set(backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(BackupAgentBase::keyConfigStopWhenDoneKey), StringRef()); - - return Void(); } - ACTOR static Future abortBackup(DatabaseBackupAgent* backupAgent, - Database cx, - Key tagName, - PartialBackup partial, - AbortOldBackup abortOldBackup, - DstOnly dstOnly, - WaitForDestUID waitForDestUID) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state Key logUidValue, destUidValue; - state UID logUid, destUid; - state Value backupUid; - - loop { + static Future abortBackup(DatabaseBackupAgent* backupAgent, + Database cx, + Key tagName, + PartialBackup partial, + AbortOldBackup abortOldBackup, + DstOnly dstOnly, + WaitForDestUID waitForDestUID) { + Reference tr(new ReadYourWritesTransaction(cx)); + Key logUidValue; + Key destUidValue; + UID logUid; + UID destUid; + Value backupUid; + + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); - UID _logUid = wait(backupAgent->getLogUid(tr, tagName)); + UID _logUid = co_await backupAgent->getLogUid(tr, tagName); logUid = _logUid; logUidValue = BinaryWriter::toValue(logUid, Unversioned()); - state Future statusFuture = backupAgent->getStateValue(tr, logUid); - state Future destUidFuture = backupAgent->getDestUid(tr, logUid); - wait(success(statusFuture) && success(destUidFuture)); + Future statusFuture = backupAgent->getStateValue(tr, logUid); + Future destUidFuture = backupAgent->getDestUid(tr, logUid); + co_await (success(statusFuture) && success(destUidFuture)); EBackupState status = statusFuture.get(); if (!backupAgent->isRunnable(status)) { @@ -2898,7 +2982,7 @@ class DatabaseBackupAgentImpl { UID destUid = destUidFuture.get(); if (destUid.isValid()) { destUidValue = BinaryWriter::toValue(destUid, Unversioned()); - } else if (destUidValue.size() == 0 && waitForDestUID) { + } else if (destUidValue.empty() && waitForDestUID) { // Give DR task a chance to update destUid to avoid the problem of // leftover version key. If we got an commit_unknown_result before, // reuse the previous destUidValue. @@ -2906,7 +2990,7 @@ class DatabaseBackupAgentImpl { } Optional _backupUid = - wait(tr->get(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId))); + co_await tr->get(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId)); backupUid = _backupUid.get(); // Clearing the folder id will prevent future tasks from executing @@ -2920,17 +3004,21 @@ class DatabaseBackupAgentImpl { tr->set(StringRef(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyStateStatus)), StringRef(DatabaseBackupAgent::getStateText(EBackupState::STATE_PARTIALLY_ABORTED))); - wait(tr->commit()); + co_await tr->commit(); TraceEvent("DBA_Abort").detail("CommitVersion", tr->getCommittedVersion()); break; } catch (Error& e) { - TraceEvent("DBA_AbortError").errorUnsuppressed(e); - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + TraceEvent("DBA_AbortError").errorUnsuppressed(err); + co_await tr->onError(err); } } tr = makeReference(cx); - loop { + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); // dumpData's commits are unstoppable, and we need to make sure that no dumpData commits @@ -2941,9 +3029,11 @@ class DatabaseBackupAgentImpl { // transactions to the proxy's commit promise stream, so our commit will come after all // dumpData transactions. tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); + Error err; + bool hasErr = false; try { // Ensure that we're at a version higher than the data that we've written. - Optional lastApplied = wait(tr->get(logUidValue.withPrefix(applyMutationsBeginRange.begin))); + Optional lastApplied = co_await tr->get(logUidValue.withPrefix(applyMutationsBeginRange.begin)); if (lastApplied.present()) { Version current = tr->getReadVersion().get(); Version applied = BinaryReader::fromStringRef(lastApplied.get(), Unversioned()); @@ -2963,27 +3053,32 @@ class DatabaseBackupAgentImpl { tr->addWriteConflictRange(singleKeyRange(minRequiredCommitVersionKey)); } } - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } if (!dstOnly) { - state Future partialTimeout = partial ? delay(30.0) : Never(); - state Reference srcTr( - new ReadYourWritesTransaction(backupAgent->taskBucket->src)); + Future partialTimeout = partial ? delay(30.0) : Never(); + Reference srcTr(new ReadYourWritesTransaction(backupAgent->taskBucket->src)); - loop { + while (true) { + Error err; + bool hasErr = false; try { srcTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); srcTr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future> backupVersionF = + Future> backupVersionF = srcTr->get(backupAgent->sourceStates.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId)); - wait(success(backupVersionF) || partialTimeout); + co_await (success(backupVersionF) || partialTimeout); if (partialTimeout.isReady()) { - return Void(); + co_return; } if (backupVersionF.get().present() && @@ -3004,10 +3099,10 @@ class DatabaseBackupAgentImpl { Key latestVersionKey = logUidValue.withPrefix(destUidValue.withPrefix(backupLatestVersionsPrefix)); - state Future> bVersionF = srcTr->get(latestVersionKey); - wait(success(bVersionF) || partialTimeout); + Future> bVersionF = srcTr->get(latestVersionKey); + co_await (success(bVersionF) || partialTimeout); if (partialTimeout.isReady()) { - return Void(); + co_return; } if (!bVersionF.get().present()) { @@ -3019,121 +3114,130 @@ class DatabaseBackupAgentImpl { srcTr->set(backupAgent->sourceStates.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId), backupUid); - wait(eraseLogData(srcTr, logUidValue, destUidValue) || partialTimeout); + co_await (eraseLogData(srcTr, logUidValue, destUidValue) || partialTimeout); if (partialTimeout.isReady()) { - return Void(); + co_return; } - wait(srcTr->commit() || partialTimeout); + co_await (srcTr->commit() || partialTimeout); if (partialTimeout.isReady()) { - return Void(); + co_return; } break; } catch (Error& e) { - wait(srcTr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await srcTr->onError(err); } } } tr = makeReference(cx); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional v = wait( - tr->get(StringRef(backupAgent->config.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId)))); + Optional v = co_await tr->get( + StringRef(backupAgent->config.get(logUidValue).pack(DatabaseBackupAgent::keyFolderId))); if (v.present()) { - return Void(); + co_return; } tr->set(StringRef(backupAgent->states.get(logUidValue).pack(DatabaseBackupAgent::keyStateStatus)), StringRef(DatabaseBackupAgent::getStateText(EBackupState::STATE_ABORTED))); - wait(tr->commit()); + co_await tr->commit(); - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; + hasErr = true; + } + if (hasErr) { + co_await tr->onError(err); } } } - ACTOR static Future getStatus(DatabaseBackupAgent* backupAgent, - Database cx, - int errorLimit, - Key tagName) { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future getStatus(DatabaseBackupAgent* backupAgent, Database cx, int errorLimit, Key tagName) { + Reference tr(new ReadYourWritesTransaction(cx)); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state std::string statusText; - state int retries = 0; + std::string statusText; + int retries = 0; - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(success(tr->getReadVersion())); // get the read version before getting a version from the source - // database to prevent the time differential from going negative + co_await tr->getReadVersion(); // get the read version before getting a version from the source + // database to prevent the time differential from going negative - state Transaction scrTr(backupAgent->taskBucket->src); + Transaction scrTr(backupAgent->taskBucket->src); scrTr.setOption(FDBTransactionOptions::LOCK_AWARE); - state Future srcReadVersion = scrTr.getReadVersion(); + Future srcReadVersion = scrTr.getReadVersion(); statusText = ""; - state UID logUid = wait(backupAgent->getLogUid(tr, tagName)); + UID logUid = co_await backupAgent->getLogUid(tr, tagName); - state Future> fPaused = tr->get(backupAgent->taskBucket->getPauseKey()); - state Future fErrorValues = + Future> fPaused = tr->get(backupAgent->taskBucket->getPauseKey()); + Future fErrorValues = errorLimit > 0 ? tr->getRange(backupAgent->errors.get(BinaryWriter::toValue(logUid, Unversioned())).range(), errorLimit, Snapshot::False, Reverse::True) : Future(); - state Future> fBackupUid = + Future> fBackupUid = tr->get(backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(DatabaseBackupAgent::keyFolderId)); - state Future> fBackupVerison = + Future> fBackupVerison = tr->get(BinaryWriter::toValue(logUid, Unversioned()).withPrefix(applyMutationsBeginRange.begin)); - state Future> fTagName = + Future> fTagName = tr->get(backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(BackupAgentBase::keyConfigBackupTag)); - state Future> fStopVersionKey = + Future> fStopVersionKey = tr->get(backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(BackupAgentBase::keyStateStop)); - state Future> fBackupKeysPacked = + Future> fBackupKeysPacked = tr->get(backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(BackupAgentBase::keyConfigBackupRanges)); - state Future> flogVersionKey = + Future> flogVersionKey = tr->get(backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) .pack(BackupAgentBase::keyStateLogBeginVersion)); - state EBackupState backupState = wait(backupAgent->getStateValue(tr, logUid)); + EBackupState backupState = co_await backupAgent->getStateValue(tr, logUid); if (backupState == EBackupState::STATE_NEVERRAN) { statusText += "No previous backups found.\n"; } else { - state std::string tagNameDisplay; - Optional tagName = wait(fTagName); + std::string tagNameDisplay; + Optional tagName = co_await fTagName; // Define the display tag name if (tagName.present()) { tagNameDisplay = tagName.get().toString(); } - state Optional stopVersionKey = wait(fStopVersionKey); - Optional logVersionKey = wait(flogVersionKey); - state std::string logVersionText = + Optional stopVersionKey = co_await fStopVersionKey; + Optional logVersionKey = co_await flogVersionKey; + std::string logVersionText = ". Last log version is " + (logVersionKey.present() ? format("%lld", BinaryReader::fromStringRef(logVersionKey.get(), Unversioned())) : "unset"); - Optional backupKeysPacked = wait(fBackupKeysPacked); + Optional backupKeysPacked = co_await fBackupKeysPacked; - state Standalone> backupRanges; + Standalone> backupRanges; if (backupKeysPacked.present()) { BinaryReader br(backupKeysPacked.get(), IncludeVersion()); br >> backupRanges; @@ -3175,10 +3279,10 @@ class DatabaseBackupAgentImpl { // Append the errors, if requested if (errorLimit > 0) { - RangeResult values = wait(fErrorValues); + RangeResult values = co_await fErrorValues; // Display the errors, if any - if (values.size() > 0) { + if (!values.empty()) { // Inform the user that the list of errors is complete or partial statusText += (values.size() < errorLimit) ? "WARNING: Some DR agents have reported issues:\n" @@ -3192,72 +3296,77 @@ class DatabaseBackupAgentImpl { } // calculate time differential - Optional backupUid = wait(fBackupUid); + Optional backupUid = co_await fBackupUid; if (backupUid.present()) { - Optional v = wait(fBackupVerison); + Optional v = co_await fBackupVerison; if (v.present()) { - state Version destApplyBegin = BinaryReader::fromStringRef(v.get(), Unversioned()); - Version sourceVersion = wait(srcReadVersion); + Version destApplyBegin = BinaryReader::fromStringRef(v.get(), Unversioned()); + Version sourceVersion = co_await srcReadVersion; double secondsBehind = ((double)(sourceVersion - destApplyBegin)) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND; statusText += format("\nThe DR is %.6f seconds behind.\n", secondsBehind); } } - Optional paused = wait(fPaused); + Optional paused = co_await fPaused; if (paused.present()) { statusText += format("\nAll DR agents have been paused.\n"); } break; } catch (Error& e) { + err = e; + hasErr = true; + } + if (hasErr) { retries++; if (retries > 5) { - statusText += format("\nWARNING: Could not fetch full DR status: %s\n", e.name()); - return statusText; + statusText += format("\nWARNING: Could not fetch full DR status: %s\n", err.name()); + co_return statusText; } - wait(tr->onError(e)); + co_await tr->onError(err); } } - return statusText; + co_return statusText; } - ACTOR static Future getStateValue(DatabaseBackupAgent* backupAgent, - Reference tr, - UID logUid, - Snapshot snapshot) { + static Future getStateValue(DatabaseBackupAgent* backupAgent, + Reference tr, + UID logUid, + Snapshot snapshot) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) - .pack(DatabaseBackupAgent::keyStateStatus); - Optional status = wait(tr->get(statusKey, snapshot)); + Key statusKey = backupAgent->states.get(BinaryWriter::toValue(logUid, Unversioned())) + .pack(DatabaseBackupAgent::keyStateStatus); + Optional status = co_await tr->get(statusKey, snapshot); - return (!status.present()) ? EBackupState::STATE_NEVERRAN : BackupAgentBase::getState(status.get().toString()); + co_return (!status.present()) ? EBackupState::STATE_NEVERRAN + : BackupAgentBase::getState(status.get().toString()); } - ACTOR static Future getDestUid(DatabaseBackupAgent* backupAgent, - Reference tr, - UID logUid, - Snapshot snapshot) { + static Future getDestUid(DatabaseBackupAgent* backupAgent, + Reference tr, + UID logUid, + Snapshot snapshot) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Key destUidKey = + Key destUidKey = backupAgent->config.get(BinaryWriter::toValue(logUid, Unversioned())).pack(BackupAgentBase::destUid); - Optional destUid = wait(tr->get(destUidKey, snapshot)); + Optional destUid = co_await tr->get(destUidKey, snapshot); - return (destUid.present()) ? BinaryReader::fromStringRef(destUid.get(), Unversioned()) : UID(); + co_return (destUid.present()) ? BinaryReader::fromStringRef(destUid.get(), Unversioned()) : UID(); } - ACTOR static Future getLogUid(DatabaseBackupAgent* backupAgent, - Reference tr, - Key tagName, - Snapshot snapshot) { + static Future getLogUid(DatabaseBackupAgent* backupAgent, + Reference tr, + Key tagName, + Snapshot snapshot) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Optional logUid = wait(tr->get(backupAgent->tagNames.pack(tagName), snapshot)); + Optional logUid = co_await tr->get(backupAgent->tagNames.pack(tagName), snapshot); - return (logUid.present()) ? BinaryReader::fromStringRef(logUid.get(), Unversioned()) : UID(); + co_return (logUid.present()) ? BinaryReader::fromStringRef(logUid.get(), Unversioned()) : UID(); } }; diff --git a/fdbclient/DatabaseConfiguration.cpp b/fdbclient/DatabaseConfiguration.cpp index 2e05ea18f43..5dcc24d7d93 100644 --- a/fdbclient/DatabaseConfiguration.cpp +++ b/fdbclient/DatabaseConfiguration.cpp @@ -52,6 +52,7 @@ void DatabaseConfiguration::resetInternal() { remoteDesiredTLogCount = -1; remoteTLogReplicationFactor = repopulateRegionAntiQuorum = 0; backupWorkerEnabled = false; + rangeBackupWorkerEnabled = false; perpetualStorageWiggleSpeed = 0; perpetualStorageWiggleLocality = "0"; storageMigrationType = StorageMigrationType::DEFAULT; @@ -83,7 +84,7 @@ void parseReplicationPolicy(Reference* policy, ValueRef cons void parse(std::vector* regions, ValueRef const& v) { try { - StatusObject statusObj = BinaryReader::fromStringRef(v, IncludeVersion()); + auto statusObj = BinaryReader::fromStringRef(v, IncludeVersion()); regions->clear(); if (statusObj["regions"].type() != json_spirit::array_type) { return; @@ -122,47 +123,39 @@ void parse(std::vector* regions, ValueRef const& v) { info.satelliteTLogReplicationFactor = 1; info.satelliteTLogUsableDcs = 1; info.satelliteTLogWriteAntiQuorum = 0; - info.satelliteTLogPolicy = Reference(new PolicyOne()); + info.satelliteTLogPolicy = makeReference(); } else if (satelliteReplication == "one_satellite_double") { info.satelliteTLogReplicationFactor = 2; info.satelliteTLogUsableDcs = 1; info.satelliteTLogWriteAntiQuorum = 0; - info.satelliteTLogPolicy = Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))); + info.satelliteTLogPolicy = makeReference(2, "zoneid", makeReference()); } else if (satelliteReplication == "one_satellite_triple") { info.satelliteTLogReplicationFactor = 3; info.satelliteTLogUsableDcs = 1; info.satelliteTLogWriteAntiQuorum = 0; - info.satelliteTLogPolicy = Reference( - new PolicyAcross(3, "zoneid", Reference(new PolicyOne()))); + info.satelliteTLogPolicy = makeReference(3, "zoneid", makeReference()); } else if (satelliteReplication == "two_satellite_safe") { info.satelliteTLogReplicationFactor = 4; info.satelliteTLogUsableDcs = 2; info.satelliteTLogWriteAntiQuorum = 0; - info.satelliteTLogPolicy = Reference( - new PolicyAcross(2, - "dcid", - Reference(new PolicyAcross( - 2, "zoneid", Reference(new PolicyOne()))))); + info.satelliteTLogPolicy = makeReference( + 2, "dcid", makeReference(2, "zoneid", makeReference())); info.satelliteTLogReplicationFactorFallback = 2; info.satelliteTLogUsableDcsFallback = 1; info.satelliteTLogWriteAntiQuorumFallback = 0; - info.satelliteTLogPolicyFallback = Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))); + info.satelliteTLogPolicyFallback = + makeReference(2, "zoneid", makeReference()); } else if (satelliteReplication == "two_satellite_fast") { info.satelliteTLogReplicationFactor = 4; info.satelliteTLogUsableDcs = 2; info.satelliteTLogWriteAntiQuorum = 2; - info.satelliteTLogPolicy = Reference( - new PolicyAcross(2, - "dcid", - Reference(new PolicyAcross( - 2, "zoneid", Reference(new PolicyOne()))))); + info.satelliteTLogPolicy = makeReference( + 2, "dcid", makeReference(2, "zoneid", makeReference())); info.satelliteTLogReplicationFactorFallback = 2; info.satelliteTLogUsableDcsFallback = 1; info.satelliteTLogWriteAntiQuorumFallback = 0; - info.satelliteTLogPolicyFallback = Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))); + info.satelliteTLogPolicyFallback = + makeReference(2, "zoneid", makeReference()); } else { throw invalid_option(); } @@ -184,29 +177,63 @@ void parse(std::vector* regions, ValueRef const& v) { void DatabaseConfiguration::setDefaultReplicationPolicy() { if (!storagePolicy) { - storagePolicy = Reference( - new PolicyAcross(storageTeamSize, "zoneid", Reference(new PolicyOne()))); + storagePolicy = makeReference(storageTeamSize, "zoneid", makeReference()); } if (!tLogPolicy) { - tLogPolicy = Reference( - new PolicyAcross(tLogReplicationFactor, "zoneid", Reference(new PolicyOne()))); + tLogPolicy = makeReference(tLogReplicationFactor, "zoneid", makeReference()); } if (remoteTLogReplicationFactor > 0 && !remoteTLogPolicy) { - remoteTLogPolicy = Reference( - new PolicyAcross(remoteTLogReplicationFactor, "zoneid", Reference(new PolicyOne()))); + remoteTLogPolicy = + makeReference(remoteTLogReplicationFactor, "zoneid", makeReference()); } for (auto& r : regions) { if (r.satelliteTLogReplicationFactor > 0 && !r.satelliteTLogPolicy) { - r.satelliteTLogPolicy = Reference(new PolicyAcross( - r.satelliteTLogReplicationFactor, "zoneid", Reference(new PolicyOne()))); + r.satelliteTLogPolicy = + makeReference(r.satelliteTLogReplicationFactor, "zoneid", makeReference()); } if (r.satelliteTLogReplicationFactorFallback > 0 && !r.satelliteTLogPolicyFallback) { - r.satelliteTLogPolicyFallback = Reference(new PolicyAcross( - r.satelliteTLogReplicationFactorFallback, "zoneid", Reference(new PolicyOne()))); + r.satelliteTLogPolicyFallback = makeReference( + r.satelliteTLogReplicationFactorFallback, "zoneid", makeReference()); } } } +int32_t DatabaseConfiguration::maxZoneFailuresTolerated(int fullyReplicatedRegions, bool forAvailability) const { + int worstSatelliteTLogReplicationFactor = !regions.empty() ? std::numeric_limits::max() : 0; + int regionsWithNonNegativePriority = 0; + for (auto& r : regions) { + if (r.priority >= 0) { + regionsWithNonNegativePriority++; + } + worstSatelliteTLogReplicationFactor = std::min( + worstSatelliteTLogReplicationFactor, r.satelliteTLogReplicationFactor - r.satelliteTLogWriteAntiQuorum); + if (r.satelliteTLogUsableDcsFallback > 0) { + worstSatelliteTLogReplicationFactor = + std::min(worstSatelliteTLogReplicationFactor, + r.satelliteTLogReplicationFactorFallback - r.satelliteTLogWriteAntiQuorumFallback); + } + } + + if (worstSatelliteTLogReplicationFactor <= 0) { + // HA is not enabled in this database. Return single cluster zone failures to tolerate. + return std::min(tLogReplicationFactor - 1 - tLogWriteAntiQuorum, storageTeamSize - 1); + } + + // Compute HA enabled database zone failure tolerance. + auto isGeoReplicatedData = [this, &fullyReplicatedRegions]() { + return usableRegions > 1 && fullyReplicatedRegions > 1; + }; + + if (isGeoReplicatedData() && (!forAvailability || regionsWithNonNegativePriority > 1)) { + return 1 + std::min(std::max(tLogReplicationFactor - 1 - tLogWriteAntiQuorum, + worstSatelliteTLogReplicationFactor - 1), + storageTeamSize - 1); + } + // Primary and Satellite tLogs are synchronously replicated, hence we can lose all but 1. + return std::min(tLogReplicationFactor + worstSatelliteTLogReplicationFactor - 1 - tLogWriteAntiQuorum, + storageTeamSize - 1); +} + bool DatabaseConfiguration::isValid() const { // enable this via `fdbcli --knob_cli_print_invalid_configuration=1` command line parameter auto log_test = [](const char* text, bool val) { @@ -233,8 +260,8 @@ bool DatabaseConfiguration::isValid() const { LOG_TEST(remoteTLogReplicationFactor >= 0) && LOG_TEST(repopulateRegionAntiQuorum >= 0) && LOG_TEST(repopulateRegionAntiQuorum <= 1) && LOG_TEST(usableRegions >= 1) && LOG_TEST(usableRegions <= 2) && LOG_TEST(regions.size() <= 2) && LOG_TEST((usableRegions == 1 || regions.size() == 2)) && - LOG_TEST((regions.size() == 0 || regions[0].priority >= 0)) && - LOG_TEST((regions.size() == 0 || tLogPolicy->info() != "dcid^2 x zoneid^2 x 1")) && + LOG_TEST((regions.empty() || regions[0].priority >= 0)) && + LOG_TEST((regions.empty() || tLogPolicy->info() != "dcid^2 x zoneid^2 x 1")) && // We cannot specify regions with three_datacenter replication LOG_TEST((perpetualStorageWiggleSpeed == 0 || perpetualStorageWiggleSpeed == 1)) && LOG_TEST(isValidPerpetualStorageWiggleLocality(perpetualStorageWiggleLocality)) && @@ -245,9 +272,9 @@ bool DatabaseConfiguration::isValid() const { std::set dcIds; dcIds.insert(Key()); for (auto& r : regions) { - if (!(!dcIds.count(r.dcId) && r.satelliteTLogReplicationFactor >= 0 && r.satelliteTLogWriteAntiQuorum >= 0 && + if (!(!dcIds.contains(r.dcId) && r.satelliteTLogReplicationFactor >= 0 && r.satelliteTLogWriteAntiQuorum >= 0 && r.satelliteTLogUsableDcs >= 1 && - (r.satelliteTLogReplicationFactor == 0 || (r.satelliteTLogPolicy && r.satellites.size())) && + (r.satelliteTLogReplicationFactor == 0 || (r.satelliteTLogPolicy && !r.satellites.empty())) && (r.satelliteTLogUsableDcsFallback == 0 || (r.satelliteTLogReplicationFactor > 0 && r.satelliteTLogReplicationFactorFallback > 0)))) { return false; @@ -257,7 +284,7 @@ bool DatabaseConfiguration::isValid() const { satelliteDcIds.insert(Key()); satelliteDcIds.insert(r.dcId); for (auto& s : r.satellites) { - if (satelliteDcIds.count(s.dcId)) { + if (satelliteDcIds.contains(s.dcId)) { return false; } satelliteDcIds.insert(s.dcId); @@ -340,7 +367,7 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const { } result["usable_regions"] = usableRegions; - if (regions.size()) { + if (!regions.empty()) { result["regions"] = getRegionJSON(); } @@ -394,6 +421,7 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const { } result["backup_worker_enabled"] = (int32_t)backupWorkerEnabled; + result["range_backup_worker_enabled"] = (int32_t)rangeBackupWorkerEnabled; result["perpetual_storage_wiggle"] = perpetualStorageWiggleSpeed; result["perpetual_storage_wiggle_locality"] = perpetualStorageWiggleLocality; if (perpetualStoreType.storeType() != KeyValueStoreType::END) { @@ -506,7 +534,7 @@ StatusArray DatabaseConfiguration::getRegionJSON() const { regionObj["satellite_logs"] = r.satelliteDesiredTLogCount; } - if (r.satellites.size()) { + if (!r.satellites.empty()) { for (auto& s : r.satellites) { StatusObject satObj; satObj["id"] = s.dcId.toString(); @@ -664,6 +692,9 @@ bool DatabaseConfiguration::setInternal(KeyRef key, ValueRef value) { } else if (ck == "backup_worker_enabled"_sr) { parse((&type), value); backupWorkerEnabled = (type != 0); + } else if (ck == "range_backup_worker_enabled"_sr) { + parse((&type), value); + rangeBackupWorkerEnabled = (type != 0); } else if (ck == "usable_regions"_sr) { parse(&usableRegions, value); } else if (ck == "repopulate_anti_quorum"_sr) { diff --git a/fdbclient/DatabaseContext.actor.cpp b/fdbclient/DatabaseContext.cpp similarity index 87% rename from fdbclient/DatabaseContext.actor.cpp rename to fdbclient/DatabaseContext.cpp index fb1fbaad1e2..9870f382939 100644 --- a/fdbclient/DatabaseContext.actor.cpp +++ b/fdbclient/DatabaseContext.cpp @@ -18,80 +18,47 @@ * limitations under the License. */ -// TODO: prune down the list of includes. This was copied from NativeAPI.actor.cpp. #include "fdbclient/NativeAPI.actor.h" -#include -#include +#include #include #include #include -#include #include -#include -#include #include #include -#include "boost/algorithm/string.hpp" - #include "fdbclient/Knobs.h" #include "flow/CodeProbe.h" -#include "fmt/format.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/FDBTypes.h" -#include "fdbrpc/FailureMonitor.h" #include "fdbrpc/MultiInterface.h" -#include "fdbclient/ActorLineageProfiler.h" #include "fdbclient/AnnotateActor.h" -#include "fdbclient/Atomic.h" #include "fdbclient/ClusterInterface.h" -#include "fdbclient/ClusterConnectionFile.h" -#include "fdbclient/ClusterConnectionMemoryRecord.h" #include "fdbclient/CoordinationInterface.h" #include "fdbclient/CommitTransaction.h" #include "fdbclient/DatabaseContext.h" -#include "fdbclient/GlobalConfig.actor.h" -#include "fdbclient/IKnobCollection.h" -#include "fdbclient/JsonBuilder.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/GlobalConfig.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/KeyRangeMap.h" -#include "fdbclient/ManagementAPI.actor.h" -#include "fdbclient/NameLineage.h" #include "fdbclient/CommitProxyInterface.h" -#include "fdbclient/MonitorLeader.h" -#include "fdbclient/MutationList.h" -#include "fdbclient/ParallelStream.actor.h" +#include "ProxyLoadBalance.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/SpecialKeySpace.h" #include "fdbclient/StorageServerInterface.h" #include "fdbclient/SystemData.h" -#include "fdbclient/TransactionLineage.h" -#include "fdbclient/versions.h" -#include "fdbrpc/WellKnownEndpoints.h" -#include "fdbrpc/LoadBalance.h" -#include "fdbrpc/Net2FileSystem.h" -#include "fdbrpc/simulator.h" -#include "fdbrpc/sim_validation.h" #include "flow/Arena.h" #include "flow/ActorCollection.h" -#include "flow/DeterministicRandom.h" #include "flow/Error.h" #include "flow/FastRef.h" -#include "flow/GetSourceVersion.h" #include "flow/IRandom.h" #include "flow/Trace.h" -#include "flow/ProtocolVersion.h" #include "flow/flow.h" #include "flow/genericactors.actor.h" -#include "flow/Knobs.h" #include "flow/Platform.h" -#include "flow/SystemMonitor.h" -#include "flow/TLSConfig.actor.h" #include "fdbclient/Tracing.h" -#include "flow/UnitTest.h" #include "flow/network.h" #include "flow/serialize.h" @@ -105,9 +72,8 @@ #undef min #undef max #else -#include #endif -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/CoroUtils.h" Reference DatabaseContext::getWatchMetadata(KeyRef key) const { const auto it = watchMap.find(WatchMapKey(key)); @@ -456,22 +422,17 @@ inline HealthMetrics populateHealthMetrics(const HealthMetrics& detailedMetrics, } } -ACTOR static Future getHealthMetricsActor(DatabaseContext* cx, bool detailed, bool sendDetailedRequest) { - loop { - choose { - when(wait(cx->onProxiesChanged())) {} - when(GetHealthMetricsReply rep = wait(basicLoadBalance(cx->getGrvProxies(UseProvisionalProxies::False), - &GrvProxyInterface::getHealthMetrics, - GetHealthMetricsRequest(sendDetailedRequest)))) { - cx->healthMetrics.update(rep.healthMetrics, sendDetailedRequest, true); - cx->healthMetricsLastUpdated = now(); - if (sendDetailedRequest) { - cx->detailedHealthMetricsLastUpdated = now(); - } - return populateHealthMetrics(cx->healthMetrics, detailed); - } - } +static Future getHealthMetricsActor(DatabaseContext* cx, bool detailed, bool sendDetailedRequest) { + GetHealthMetricsReply rep = + co_await grvProxyLoadBalance(Database(Reference::addRef(cx)), + makeReqBuilder(sendDetailedRequest), + &GrvProxyInterface::getHealthMetrics); + cx->healthMetrics.update(rep.healthMetrics, sendDetailedRequest, true); + cx->healthMetricsLastUpdated = now(); + if (sendDetailedRequest) { + cx->detailedHealthMetricsLastUpdated = now(); } + co_return populateHealthMetrics(cx->healthMetrics, detailed); } Future DatabaseContext::getHealthMetrics(bool detailed = false) { @@ -483,13 +444,13 @@ Future DatabaseContext::getHealthMetrics(bool detailed = false) { return getHealthMetricsActor(this, detailed, sendDetailedRequest); } -Future> DatabaseContext::getStorageStats(const UID& id, double maxStaleness) { +Future> DatabaseContext::getStorageStats(UID id, double maxStaleness) { if (now() - detailedHealthMetricsLastUpdated < maxStaleness) { auto it = healthMetrics.storageStats.find(id); return it == healthMetrics.storageStats.end() ? Optional() : it->second; } - return map(getHealthMetricsActor(this, true, true), [&id](auto metrics) -> Optional { + return map(getHealthMetricsActor(this, true, true), [id](auto metrics) -> Optional { auto it = metrics.storageStats.find(id); return it == metrics.storageStats.end() ? Optional() : it->second; }); @@ -548,10 +509,10 @@ void traceTSSPercentiles(TraceEvent& ev, } } -ACTOR Future tssLogger(DatabaseContext* cx) { - state double lastLogged = 0; - loop { - wait(delay(CLIENT_KNOBS->TSS_METRICS_LOGGING_INTERVAL, TaskPriority::FlushTrace)); +Future tssLogger(DatabaseContext* cx) { + double lastLogged = 0; + while (true) { + co_await delay(CLIENT_KNOBS->TSS_METRICS_LOGGING_INTERVAL, TaskPriority::FlushTrace); // Log each TSS pair separately for (const auto& it : cx->tssMetrics) { @@ -592,10 +553,10 @@ ACTOR Future tssLogger(DatabaseContext* cx) { } } -ACTOR Future databaseLogger(DatabaseContext* cx) { - state double lastLogged = 0; - loop { - wait(delay(CLIENT_KNOBS->SYSTEM_MONITOR_INTERVAL, TaskPriority::FlushTrace)); +Future databaseLogger(DatabaseContext* cx) { + double lastLogged = 0; + while (true) { + co_await delay(CLIENT_KNOBS->SYSTEM_MONITOR_INTERVAL, TaskPriority::FlushTrace); bool logMetrics = !g_network->isSimulated() || BUGGIFY_WITH_PROB(0.01); if (logMetrics) { @@ -653,15 +614,16 @@ struct TrInfoChunk { static const Key CLIENT_LATENCY_INFO_PREFIX = "client_latency/"_sr; static const Key CLIENT_LATENCY_INFO_CTR_PREFIX = "client_latency_counter/"_sr; -ACTOR static Future transactionInfoCommitActor(Transaction* tr, std::vector* chunks) { - state const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); - state int retryCount = 0; - loop { +static Future transactionInfoCommitActor(Transaction* tr, std::vector* chunks) { + const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); + int retryCount = 0; + while (true) { + Error err; try { tr->reset(); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future> vstamp = tr->getVersionstamp(); + [[maybe_unused]] Future> vstamp = tr->getVersionstamp(); int64_t numCommitBytes = 0; for (auto& chunk : *chunks) { tr->atomicOp(chunk.key, chunk.value, MutationRef::SetVersionstampedKey); @@ -669,47 +631,49 @@ ACTOR static Future transactionInfoCommitActor(Transaction* tr, std::vecto 4; // subtract number of bytes of key that denotes version stamp index } tr->atomicOp(clientLatencyAtomicCtr, StringRef((uint8_t*)&numCommitBytes, 8), MutationRef::AddValue); - wait(tr->commit()); - return Void(); + co_await tr->commit(); + co_return; } catch (Error& e) { - retryCount++; - if (retryCount == 10) - throw; - wait(tr->onError(e)); + err = e; } + retryCount++; + if (retryCount == 10) + throw err; + co_await tr->onError(err); } } -ACTOR static Future delExcessClntTxnEntriesActor(Transaction* tr, int64_t clientTxInfoSizeLimit) { - state const Key clientLatencyName = CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); - state const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); +static Future delExcessClntTxnEntriesActor(Transaction* tr, int64_t clientTxInfoSizeLimit) { + const Key clientLatencyName = CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); + const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin); TraceEvent(SevInfo, "DelExcessClntTxnEntriesCalled").log(); // If we don't limit it with retries, the DatabaseContext will never cleanup as Transaction // object will be alive and hold reference to DatabaseContext. - state int retries = 0; - loop { + int retries = 0; + while (true) { + Error err; try { tr->reset(); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional ctrValue = wait(tr->get(KeyRef(clientLatencyAtomicCtr), Snapshot::True)); + Optional ctrValue = co_await tr->get(KeyRef(clientLatencyAtomicCtr), Snapshot::True); if (!ctrValue.present()) { TraceEvent(SevInfo, "NumClntTxnEntriesNotFound").log(); - return Void(); + co_return; } - state int64_t txInfoSize = 0; + int64_t txInfoSize = 0; ASSERT(ctrValue.get().size() == sizeof(int64_t)); memcpy(&txInfoSize, ctrValue.get().begin(), ctrValue.get().size()); if (txInfoSize < clientTxInfoSizeLimit) - return Void(); + co_return; int getRangeByteLimit = (txInfoSize - clientTxInfoSizeLimit) < CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT ? (txInfoSize - clientTxInfoSizeLimit) : CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT; GetRangeLimits limit(GetRangeLimits::ROW_LIMIT_UNLIMITED, getRangeByteLimit); RangeResult txEntries = - wait(tr->getRange(KeyRangeRef(clientLatencyName, strinc(clientLatencyName)), limit)); - state int64_t numBytesToDel = 0; + co_await tr->getRange(KeyRangeRef(clientLatencyName, strinc(clientLatencyName)), limit); + int64_t numBytesToDel = 0; KeyRef endKey; for (auto& kv : txEntries) { endKey = kv.key; @@ -723,44 +687,48 @@ ACTOR static Future delExcessClntTxnEntriesActor(Transaction* tr, int64_t int64_t bytesDel = -numBytesToDel; tr->atomicOp(clientLatencyAtomicCtr, StringRef((uint8_t*)&bytesDel, 8), MutationRef::AddValue); - wait(tr->commit()); + co_await tr->commit(); } if (txInfoSize - numBytesToDel <= clientTxInfoSizeLimit) - return Void(); + co_return; + continue; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled || retries++ >= 10) { - throw; - } - - wait(tr->onError(e)); + err = e; + } + if (err.code() == error_code_actor_cancelled || retries++ >= 10) { + throw err; } + + co_await tr->onError(err); } } -// FIXME: explain what "client status" is +// Client status is the client-side transaction profiling data persisted under +// fdbClientInfoPrefixRange for status reporting. // The reason for getting a pointer to DatabaseContext instead of a reference counted object is because reference // counting will increment reference count for DatabaseContext which holds the future of this actor. This creates a // cyclic reference and hence this actor and Database object will not be destroyed at all. -ACTOR static Future clientStatusUpdateActor(DatabaseContext* cx) { - state const std::string clientLatencyName = +static Future clientStatusUpdateActor(DatabaseContext* cx) { + const std::string clientLatencyName = CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin).toString(); - state Transaction tr; - state std::vector commitQ; - state int txBytes = 0; + Transaction tr; + std::vector commitQ; + int txBytes = 0; - loop { + while (true) { // Make sure we are connected to the server. Otherwise we may just try to keep reconnecting // with incompatible clusters. - wait(cx->onConnected()); + co_await cx->onConnected(); // Need to make sure that we eventually destroy tr. We can't rely on getting cancelled to do // this because of the cyclic reference to self. - wait(refreshTransaction(cx, &tr)); + co_await refreshTransaction(cx, &tr); + Error err; try { ASSERT(cx->clientStatusUpdater.outStatusQ.empty()); cx->clientStatusUpdater.inStatusQ.swap(cx->clientStatusUpdater.outStatusQ); // Split Transaction Info into chunks - state std::vector trChunksQ; + std::vector trChunksQ; for (auto& entry : cx->clientStatusUpdater.outStatusQ) { auto& bw = entry.second; int64_t value_size_limit = BUGGIFY @@ -789,19 +757,19 @@ ACTOR static Future clientStatusUpdateActor(DatabaseContext* cx) { } // Commit the chunks splitting into different transactions if needed - state int64_t dataSizeLimit = + int64_t dataSizeLimit = BUGGIFY ? deterministicRandom()->randomInt(200e3, 1.5 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT) : 0.8 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT; - state std::vector::iterator tracking_iter = trChunksQ.begin(); + std::vector::iterator tracking_iter = trChunksQ.begin(); ASSERT(commitQ.empty() && (txBytes == 0)); - loop { - state std::vector::iterator iter = tracking_iter; + while (true) { + std::vector::iterator iter = tracking_iter; txBytes = 0; commitQ.clear(); try { while (iter != trChunksQ.end()) { if (iter->value.size() + iter->key.size() + txBytes > dataSizeLimit) { - wait(transactionInfoCommitActor(&tr, &commitQ)); + co_await transactionInfoCommitActor(&tr, &commitQ); tracking_iter = iter; commitQ.clear(); txBytes = 0; @@ -811,7 +779,7 @@ ACTOR static Future clientStatusUpdateActor(DatabaseContext* cx) { ++iter; } if (!commitQ.empty()) { - wait(transactionInfoCommitActor(&tr, &commitQ)); + co_await transactionInfoCommitActor(&tr, &commitQ); commitQ.clear(); txBytes = 0; } @@ -829,7 +797,7 @@ ACTOR static Future clientStatusUpdateActor(DatabaseContext* cx) { } } cx->clientStatusUpdater.outStatusQ.clear(); - wait(cx->globalConfig->onInitialized()); + co_await cx->globalConfig->onInitialized(); double sampleRate = cx->globalConfig->get(fdbClientInfoTxnSampleRate, std::numeric_limits::infinity()); double clientSamplingProbability = @@ -837,28 +805,30 @@ ACTOR static Future clientStatusUpdateActor(DatabaseContext* cx) { int64_t sizeLimit = cx->globalConfig->get(fdbClientInfoTxnSizeLimit, -1); int64_t clientTxnInfoSizeLimit = sizeLimit == -1 ? CLIENT_KNOBS->CSI_SIZE_LIMIT : sizeLimit; if (!trChunksQ.empty() && deterministicRandom()->random01() < clientSamplingProbability) - wait(delExcessClntTxnEntriesActor(&tr, clientTxnInfoSizeLimit)); + co_await delExcessClntTxnEntriesActor(&tr, clientTxnInfoSizeLimit); // Cleanup Transaction sooner than later, so that we don't hold reference to context. tr = Transaction(); - wait(delay(CLIENT_KNOBS->CSI_STATUS_DELAY)); + co_await delay(CLIENT_KNOBS->CSI_STATUS_DELAY); + continue; } catch (Error& e) { - TraceEvent(SevWarnAlways, "UnableToWriteClientStatus").error(e); - if (e.code() == error_code_actor_cancelled) { - throw; - } - cx->clientStatusUpdater.outStatusQ.clear(); - - // Cleanup Transaction sooner than later, so that we don't hold reference to context. - tr = Transaction(); - wait(delay(10.0)); + err = e; } + TraceEvent(SevWarnAlways, "UnableToWriteClientStatus").error(err); + if (err.code() == error_code_actor_cancelled) { + throw err; + } + cx->clientStatusUpdater.outStatusQ.clear(); + + // Cleanup Transaction sooner than later, so that we don't hold reference to context. + tr = Transaction(); + co_await delay(10.0); } } -ACTOR Future assertFailure(GrvProxyInterface remote, Future> reply) { +Future assertFailure(GrvProxyInterface remote, Future> reply) { try { - ErrorOr res = wait(reply); + ErrorOr res = co_await reply; if (!res.isError()) { TraceEvent(SevError, "GotStaleReadVersion") .detail("Remote", remote.getConsistentReadVersion.getEndpoint().addresses.address.toString()) @@ -872,7 +842,6 @@ ACTOR Future assertFailure(GrvProxyInterface remote, Future attemptGRVFromOldProxies(std::vector oldProxies, @@ -883,7 +852,7 @@ Future attemptGRVFromOldProxies(std::vector oldProxies, std::vector> replies; replies.reserve(oldProxies.size()); GetReadVersionRequest req( - span.context, 1, TransactionPriority::IMMEDIATE, GetReadVersionRequest::FLAG_CAUSAL_READ_RISKY); + span.context, 1, TransactionPriority::IMMEDIATE, invalidVersion, GetReadVersionRequest::FLAG_CAUSAL_READ_RISKY); TraceEvent evt("AttemptGRVFromOldProxies"); evt.detail("NumOldProxies", oldProxies.size()).detail("NumNewProxies", newProxies.size()); auto traceProxies = [&](std::vector& proxies, std::string const& key) { @@ -902,45 +871,45 @@ Future attemptGRVFromOldProxies(std::vector oldProxies, return waitForAll(replies); } -ACTOR static Future monitorClientDBInfoChange(DatabaseContext* cx, - Reference const> clientDBInfo, - AsyncTrigger* proxiesChangeTrigger) { - state std::vector curCommitProxies; - state std::vector curGrvProxies; - state ActorCollection actors(false); - state Future clientDBInfoOnChange = clientDBInfo->onChange(); +static Future monitorClientDBInfoChange(DatabaseContext* cx, + Reference const> clientDBInfo, + AsyncTrigger* proxiesChangeTrigger) { + std::vector curCommitProxies; + std::vector curGrvProxies; + ActorCollection actors(false); + Future clientDBInfoOnChange = clientDBInfo->onChange(); curCommitProxies = clientDBInfo->get().commitProxies; curGrvProxies = clientDBInfo->get().grvProxies; - loop { - choose { - when(wait(clientDBInfoOnChange)) { - clientDBInfoOnChange = clientDBInfo->onChange(); - if (clientDBInfo->get().commitProxies != curCommitProxies || - clientDBInfo->get().grvProxies != curGrvProxies) { - // This condition is a bit complicated. Here we want to verify that we're unable to receive a read - // version from a proxy of an old generation after a successful recovery. The conditions are: - // 1. We only do this with a configured probability. - // 2. If the old set of Grv proxies is empty, there's nothing to do - // 3. If the new set of Grv proxies is empty, it means the recovery is not complete. So if an old - // Grv proxy still gives out read versions, this would be correct behavior. - // 4. If we see a provisional proxy, it means the recovery didn't complete yet, so the same as (3) - // applies. - if (deterministicRandom()->random01() < cx->verifyCausalReadsProp && !curGrvProxies.empty() && - !clientDBInfo->get().grvProxies.empty() && !clientDBInfo->get().grvProxies[0].provisional) { - actors.add(attemptGRVFromOldProxies(curGrvProxies, clientDBInfo->get().grvProxies)); - } - curCommitProxies = clientDBInfo->get().commitProxies; - curGrvProxies = clientDBInfo->get().grvProxies; - // Commits in the previous epoch may have been recovered but not included in the version vector. - // Clear the version vector to ensure the latest commit versions are received. - cx->ssVersionVectorCache.clear(); - proxiesChangeTrigger->trigger(); + while (true) { + auto res = co_await race(clientDBInfoOnChange, actors.getResult()); + if (res.index() == 0) { + clientDBInfoOnChange = clientDBInfo->onChange(); + if (clientDBInfo->get().commitProxies != curCommitProxies || + clientDBInfo->get().grvProxies != curGrvProxies) { + // This condition is a bit complicated. Here we want to verify that we're unable to receive a read + // version from a proxy of an old generation after a successful recovery. The conditions are: + // 1. We only do this with a configured probability. + // 2. If the old set of Grv proxies is empty, there's nothing to do + // 3. If the new set of Grv proxies is empty, it means the recovery is not complete. So if an old + // Grv proxy still gives out read versions, this would be correct behavior. + // 4. If we see a provisional proxy, it means the recovery didn't complete yet, so the same as (3) + // applies. + if (deterministicRandom()->random01() < cx->verifyCausalReadsProp && !curGrvProxies.empty() && + !clientDBInfo->get().grvProxies.empty() && !clientDBInfo->get().grvProxies[0].provisional) { + actors.add(attemptGRVFromOldProxies(curGrvProxies, clientDBInfo->get().grvProxies)); } + curCommitProxies = clientDBInfo->get().commitProxies; + curGrvProxies = clientDBInfo->get().grvProxies; + // Commits in the previous epoch may have been recovered but not included in the version vector. + // Clear the version vector to ensure the latest commit versions are received. + cx->ssVersionVectorCache.clear(); + proxiesChangeTrigger->trigger(); } - when(wait(actors.getResult())) { - UNSTOPPABLE_ASSERT(false); - } + } else if (res.index() == 1) { + UNSTOPPABLE_ASSERT(false); + } else { + UNREACHABLE(); } } } @@ -980,19 +949,19 @@ Reference addCaches(const Reference& loc, return makeReference(interfaces, true); } -ACTOR static Future handleTssMismatches(DatabaseContext* cx) { - state Reference tr; - state KeyBackedMap tssMapDB = KeyBackedMap(tssMappingKeys.begin); - state KeyBackedMap tssMismatchDB = KeyBackedMap(tssMismatchKeys.begin); - loop { +static Future handleTssMismatches(DatabaseContext* cx) { + Reference tr; + KeyBackedMap tssMapDB = KeyBackedMap(tssMappingKeys.begin); + KeyBackedMap tssMismatchDB = KeyBackedMap(tssMismatchKeys.begin); + while (true) { // return to calling actor, cx might be destroyed already with the tr reset below. // This gives ~DatabaseContext a chance to cancel this actor. - wait(delay(0)); + co_await delay(0); // - state std::pair> data = waitNext(cx->tssMismatchStream.getFuture()); + std::pair> data = co_await cx->tssMismatchStream.getFuture(); // find ss pair id so we can remove it from the mapping - state UID tssPairID; + UID tssPairID; bool found = false; for (const auto& it : cx->tssMapping) { if (it.second.id() == data.first) { @@ -1002,15 +971,16 @@ ACTOR static Future handleTssMismatches(DatabaseContext* cx) { } } if (found) { - state bool quarantine = CLIENT_KNOBS->QUARANTINE_TSS_ON_MISMATCH; + bool quarantine = CLIENT_KNOBS->QUARANTINE_TSS_ON_MISMATCH; TraceEvent(SevWarnAlways, quarantine ? "TSS_QuarantineMismatch" : "TSS_KillMismatch") .detail("TSSID", data.first.toString()); CODE_PROBE(quarantine, "Quarantining TSS because it got mismatch"); CODE_PROBE(!quarantine, "Killing TSS because it got mismatch"); tr = makeReference(Database(Reference::addRef(cx))); - state int tries = 0; - loop { + int tries = 0; + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); @@ -1028,12 +998,13 @@ ACTOR static Future handleTssMismatches(DatabaseContext* cx) { d.traceString); } - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); tries++; if (tries > 10) { // Give up, it'll get another mismatch or a human will investigate eventually @@ -1049,8 +1020,6 @@ ACTOR static Future handleTssMismatches(DatabaseContext* cx) { } } -ACTOR Future> getJSON(Database db, std::string jsonField = ""); - struct SingleSpecialKeyImpl : SpecialKeyRangeReadImpl { Future getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr, @@ -1149,12 +1118,12 @@ static RangeResult healthMetricsToKVPairs(const HealthMetrics& metrics, KeyRange return result; } -ACTOR static Future healthMetricsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { - HealthMetrics metrics = wait(ryw->getDatabase()->getHealthMetrics( +static Future healthMetricsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { + HealthMetrics metrics = co_await ryw->getDatabase()->getHealthMetrics( /*detailed ("per process")*/ kr.intersects( KeyRangeRef("\xff\xff/metrics/health/storage/"_sr, "\xff\xff/metrics/health/storage0"_sr)) || - kr.intersects(KeyRangeRef("\xff\xff/metrics/health/log/"_sr, "\xff\xff/metrics/health/log0"_sr)))); - return healthMetricsToKVPairs(metrics, kr); + kr.intersects(KeyRangeRef("\xff\xff/metrics/health/log/"_sr, "\xff\xff/metrics/health/log0"_sr))); + co_return healthMetricsToKVPairs(metrics, kr); } HealthMetricsRangeImpl::HealthMetricsRangeImpl(KeyRangeRef kr) : SpecialKeyRangeAsyncImpl(kr) {} @@ -1165,11 +1134,11 @@ Future HealthMetricsRangeImpl::getRange(ReadYourWritesTransaction* return healthMetricsGetRangeActor(ryw, kr); } -ACTOR Future getClusterId(Database db) { +Future getClusterId(Database db) { while (!db->clientInfo->get().clusterId.isValid()) { - wait(db->clientInfo->onChange()); + co_await db->clientInfo->onChange(); } - return db->clientInfo->get().clusterId; + co_return db->clientInfo->get().clusterId; } void DatabaseContext::initializeSpecialCounters() { @@ -1471,7 +1440,8 @@ DatabaseContext::DatabaseContext(Reference(); })); } - throttleExpirer = recurring([this]() { expireThrottles(); }, CLIENT_KNOBS->TAG_THROTTLE_EXPIRATION_INTERVAL); + throttleExpirer = recurring(std::bind_front(&DatabaseContext::expireThrottles, this), + CLIENT_KNOBS->TAG_THROTTLE_EXPIRATION_INTERVAL); if (BUGGIFY) { DatabaseContext::debugUseTags = true; @@ -1546,7 +1516,6 @@ DatabaseContext::~DatabaseContext() { clientDBInfoMonitor.cancel(); monitorTssInfoChange.cancel(); tssMismatchHandler.cancel(); - storage = nullptr; if (grvUpdateHandler.isValid()) { grvUpdateHandler.cancel(); diff --git a/fdbclient/FDBAWSCredentialsProvider.cpp b/fdbclient/FDBAWSCredentialsProvider.cpp index d0324bb11c3..7cde0de401a 100644 --- a/fdbclient/FDBAWSCredentialsProvider.cpp +++ b/fdbclient/FDBAWSCredentialsProvider.cpp @@ -19,7 +19,7 @@ * limitations under the License. */ -#include "fdbclient/FDBAWSCredentialsProvider.h" +#include "FDBAWSCredentialsProvider.h" #include "fdbclient/Tracing.h" #ifdef WITH_AWS_BACKUP diff --git a/fdbclient/include/fdbclient/FDBAWSCredentialsProvider.h b/fdbclient/FDBAWSCredentialsProvider.h similarity index 100% rename from fdbclient/include/fdbclient/FDBAWSCredentialsProvider.h rename to fdbclient/FDBAWSCredentialsProvider.h diff --git a/fdbclient/FDBTypes.cpp b/fdbclient/FDBTypes.cpp index 6e8a1bf749b..c86acd86b08 100644 --- a/fdbclient/FDBTypes.cpp +++ b/fdbclient/FDBTypes.cpp @@ -75,8 +75,7 @@ Key randomKeyBetween(const KeyRangeRef& keys) { ASSERT_LT(pos, end.size()); // otherwise, begin >= end // find the lowest char in range begin[pos+1, begin.size()) that is not \xff (255) - int lowest = begin.size() - 1; - for (; lowest > pos; lowest--) { + for (int lowest = begin.size() - 1; lowest > pos; lowest--) { if (begin[lowest] < 255) { Key res = begin; uint8_t* ptr = mutateString(res); @@ -206,7 +205,7 @@ TEST_CASE("/KeyRangeUtil/KeyRangeComplement") { Key b = "a"_sr; Key e = "z"_sr; std::vector result = range - KeyRangeRef(b, e); - ASSERT(result.size() == 0); + ASSERT(result.empty()); } return Void(); @@ -396,4 +395,4 @@ TEST_CASE("/PerpetualStorageWiggleLocality/ParsePerpetualStorageWiggleLocality") } } return Void(); -} \ No newline at end of file +} diff --git a/fdbclient/FileBackupAgent.actor.cpp b/fdbclient/FileBackupAgent.cpp similarity index 61% rename from fdbclient/FileBackupAgent.actor.cpp rename to fdbclient/FileBackupAgent.cpp index b0637db904c..99423c0ba56 100644 --- a/fdbclient/FileBackupAgent.actor.cpp +++ b/fdbclient/FileBackupAgent.cpp @@ -1,5 +1,5 @@ /* - * FileBackupAgent.actor.cpp + * FileBackupAgent.cpp * * This source file is part of the FoundationDB open source project * @@ -25,7 +25,7 @@ #include "flow/FastRef.h" #include "flow/flow.h" #include "fmt/format.h" -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/BackupAgent.h" #include "fdbclient/BackupContainer.h" #include "fdbclient/BackupContainerFileSystem.h" #include "fdbclient/BulkDumping.h" @@ -35,17 +35,18 @@ #include "fdbclient/FDBTypes.h" #include "fdbclient/JsonBuilder.h" #include "fdbclient/JSONDoc.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/KeyRangeMap.h" #include "fdbclient/Knobs.h" -#include "fdbclient/ManagementAPI.actor.h" -#include "fdbclient/PartitionedLogIterator.h" -#include "fdbclient/RestoreInterface.h" +#include "fdbclient/ManagementAPI.h" +#include "PartitionedLogIterator.h" +#include "RestoreInterface.h" #include "fdbclient/Status.h" #include "fdbclient/SystemData.h" #include "fdbclient/TaskBucket.h" #include "flow/network.h" #include "flow/Trace.h" +#include "flow/Util.h" #include #include @@ -64,91 +65,67 @@ #include #include -#include "flow/actorcompiler.h" // This must be the last #include. - // Counters to verify BulkDump/BulkLoad were actually used (for test assertions) std::atomic g_bulkDumpTaskCompleteCount(0); std::atomic g_bulkLoadRestoreTaskCompleteCount(0); // Helper function to monitor BulkDump job completion // Returns true if job completed successfully, false if timed out -ACTOR Future monitorBulkDumpJobCompletion(Database cx, UID jobId, double timeoutDuration, double pollInterval) { - state double timeoutStart = now(); - state Transaction tr(cx); +Future monitorBulkDumpJobCompletion(Database cx, UID jobId, double timeoutDuration, double pollInterval) { + double timeoutStart = now(); + Transaction tr(cx); - loop { + while (true) { + Error err; try { - Optional currentJob = wait(getSubmittedBulkDumpJob(&tr)); + Optional currentJob = co_await getSubmittedBulkDumpJob(&tr); bool stillRunning = currentJob.present() && currentJob.get().getJobId() == jobId; if (!stillRunning) { - return true; // Job completed successfully + co_return true; // Job completed successfully } if (now() - timeoutStart > timeoutDuration) { - return false; // Timed out + co_return false; // Timed out } - wait(delay(pollInterval)); + co_await delay(pollInterval); tr.reset(); + continue; } catch (Error& e) { - wait(tr.onError(e)); - } - } -} - -// Helper function to monitor BulkLoad job completion -// Returns true if job completed successfully, false if timed out -// lockAware must be true when DB is locked (e.g., during restore) -ACTOR Future monitorBulkLoadJobCompletion(Database cx, - UID jobId, - double timeoutDuration, - double pollInterval, - bool lockAware) { - state double timeoutStart = now(); - - loop { - Optional currentJob = wait(getRunningBulkLoadJob(cx, lockAware)); - bool stillRunning = currentJob.present() && currentJob.get().getJobId() == jobId; - - if (!stillRunning) { - return true; // Job completed successfully - } - - if (now() - timeoutStart > timeoutDuration) { - return false; // Timed out + err = e; } - - wait(delay(pollInterval)); + co_await tr.onError(err); } } // Verify that a complete BulkDump dataset exists for BulkLoad restore // Returns true if dataset is complete, false if incomplete // JobId corresponds to a single BulkDump job which represents one snapshot at a specific version -ACTOR Future verifyBulkDumpDatasetCompleteness(Reference bc, std::string bulkDumpJobId) { +Future verifyBulkDumpDatasetCompleteness(Reference bc, std::string bulkDumpJobId) { try { if (bulkDumpJobId.empty()) { TraceEvent(SevWarn, "BulkLoadVerifyDatasetEmptyJobId"); - return false; + co_return false; } // Check if job-specific directory exists: bulkdump_data// // BulkDump stores data under data//bulkdump_data/ via getBackupDataPath(), // which is consistent with where BackupContainer stores other files (logs, ranges, etc.) - state std::string jobDirectoryPath = "bulkdump_data/" + bulkDumpJobId + "/"; + std::string jobDirectoryPath = "bulkdump_data/" + bulkDumpJobId + "/"; + Error err; try { // Try to list files in the job directory to verify it exists and has content Reference bcfs = bc.castTo(); if (bcfs) { // Standard listFiles works because BulkDump now writes under data// - BackupContainerFileSystem::FilesAndSizesT files = wait(bcfs->listFiles(jobDirectoryPath)); + BackupContainerFileSystem::FilesAndSizesT files = co_await bcfs->listFiles(jobDirectoryPath); if (files.empty()) { TraceEvent(SevWarn, "BulkLoadVerifyDatasetJobDirectoryEmpty") .detail("JobDirectoryPath", jobDirectoryPath) .detail("BulkDumpJobId", bulkDumpJobId); - return false; + co_return false; } TraceEvent("BulkLoadVerifyDatasetJobDirectoryFound") @@ -158,28 +135,28 @@ ACTOR Future verifyBulkDumpDatasetCompleteness(Reference // Basic verification: job directory exists and contains files // More detailed verification (parsing shard manifests) is delegated to BulkLoad system - return true; + co_return true; } else { // Cannot verify - backup container doesn't support file listing // Use SevWarn (not SevError) to avoid abort in simulation TraceEvent(SevWarn, "BulkLoadVerifyDatasetCannotCast") .detail("BulkDumpJobId", bulkDumpJobId) .detail("Note", "BackupContainer does not support file listing required for verification"); - return false; + co_return false; } } catch (Error& e) { if (e.code() == error_code_file_not_found) { TraceEvent(SevWarn, "BulkLoadVerifyDatasetJobDirectoryNotFound") .detail("JobDirectoryPath", jobDirectoryPath) .detail("BulkDumpJobId", bulkDumpJobId); - return false; + co_return false; } throw; } } catch (Error& e) { TraceEvent(SevWarn, "BulkLoadVerifyDatasetError").error(e).detail("BulkDumpJobId", bulkDumpJobId); - return false; + co_return false; } } @@ -236,7 +213,7 @@ std::string secondsToTimeFormat(int64_t seconds) { const Key FileBackupAgent::keyLastRestorable = "last_restorable"_sr; // For convenience -typedef FileBackupAgent::ERestoreState ERestoreState; +using ERestoreState = FileBackupAgent::ERestoreState; StringRef FileBackupAgent::restoreStateText(ERestoreState id) { switch (id) { @@ -262,15 +239,15 @@ Key FileBackupAgent::getPauseKey() { return backupAgent.taskBucket->getPauseKey(); } -ACTOR Future> TagUidMap::getAll_impl(TagUidMap* tagsMap, - Reference tr, - Snapshot snapshot) { - state Key prefix = tagsMap->prefix; // Copying it here as tagsMap lifetime is not tied to this actor - TagMap::RangeResultType tagPairs = wait(tagsMap->getRange(tr, std::string(), {}, 1e6, snapshot)); +Future> TagUidMap::getAll_impl(TagUidMap* tagsMap, + Reference tr, + Snapshot snapshot) { + Key prefix = tagsMap->prefix; // Copying it here as tagsMap lifetime is not tied to this actor + TagMap::RangeResultType tagPairs = co_await tagsMap->getRange(tr, std::string(), {}, 1e6, snapshot); std::vector results; for (auto& p : tagPairs.results) results.push_back(KeyBackedTag(p.first, prefix)); - return results; + co_return results; } KeyBackedTag::KeyBackedTag(std::string tagName, StringRef tagMapPrefix) @@ -278,38 +255,69 @@ KeyBackedTag::KeyBackedTag(std::string tagName, StringRef tagMapPrefix) tagMapPrefix(tagMapPrefix) {} // Lists all backups and find if any partitioned backup is running. -ACTOR Future anyPartitionedBackupRunning(Reference tr) { +Future anyPartitionedBackupRunning(Reference tr) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - std::vector tags = wait(getAllBackupTags(tr)); + std::vector tags = co_await getAllBackupTags(tr); - state std::vector>> futures; + std::vector>> futures; for (const auto& tag : tags) { futures.push_back(tag.get(tr)); } - wait(waitForAll(futures)); - state int i = 0; + co_await waitForAll(futures); + int i = 0; for (i = 0; i < futures.size(); i++) { if (futures[i].get().present()) { - state Optional partitionedLog; - state EBackupState eState; + Optional mutationLogType; + EBackupState eState; BackupConfig config(futures[i].get().get().first); - wait(store(eState, config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)) && - store(partitionedLog, config.partitionedLogEnabled().get(tr))); - if (FileBackupAgent::isRunnable(eState) && partitionedLog.present() && partitionedLog.get()) { - return true; + co_await (store(eState, config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)) && + store(mutationLogType, config.mutationLogType().get(tr))); + if (FileBackupAgent::isRunnable(eState) && + mutationLogType.orDefault(MutationLogType::DEFAULT) == MutationLogType::PARTITIONED_LOG) { + co_return true; } } } - return false; + co_return false; +} + +// Lists all backups and find if any range-partitioned backup is running. +Future anyRangePartitionedBackupRunning(Reference tr) { + tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + tr->setOption(FDBTransactionOptions::LOCK_AWARE); + std::vector tags = co_await getAllBackupTags(tr); + + std::vector>> futures; + for (const auto& tag : tags) { + futures.push_back(tag.get(tr)); + } + + co_await waitForAll(futures); + int i = 0; + for (i = 0; i < futures.size(); i++) { + if (futures[i].get().present()) { + Optional mutationLogType; + EBackupState eState; + BackupConfig config(futures[i].get().get().first); + + co_await (store(eState, config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)) && + store(mutationLogType, config.mutationLogType().get(tr))); + if (FileBackupAgent::isRunnable(eState) && + mutationLogType.orDefault(MutationLogType::DEFAULT) == MutationLogType::RANGE_PARTITIONED_LOG) { + co_return true; + } + } + } + co_return false; } class RestoreConfig : public KeyBackedTaskConfig { public: - RestoreConfig(UID uid = UID()) : KeyBackedTaskConfig(fileRestorePrefixRange.begin, uid) {} - RestoreConfig(Reference task) : KeyBackedTaskConfig(fileRestorePrefixRange.begin, task) {} + explicit RestoreConfig(UID uid = UID()) : KeyBackedTaskConfig(fileRestorePrefixRange.begin, uid) {} + explicit RestoreConfig(Reference task) : KeyBackedTaskConfig(fileRestorePrefixRange.begin, task) {} KeyBackedProperty stateEnum() { return configSpace.pack(__FUNCTION__sr); } Future stateText(Reference tr) { @@ -321,7 +329,7 @@ class RestoreConfig : public KeyBackedTaskConfig { KeyBackedProperty onlyApplyMutationLogs() { return configSpace.pack(__FUNCTION__sr); } KeyBackedProperty inconsistentSnapshotOnly() { return configSpace.pack(__FUNCTION__sr); } KeyBackedProperty unlockDBAfterRestore() { return configSpace.pack(__FUNCTION__sr); } - KeyBackedProperty transformPartitionedLog() { return configSpace.pack(__FUNCTION__sr); } + KeyBackedProperty mutationLogType() { return configSpace.pack(__FUNCTION__sr); } // BulkLoad integration properties KeyBackedProperty useRangeFileRestore() { return configSpace.pack(__FUNCTION__sr); } KeyBackedProperty bulkDumpJobId() { return configSpace.pack(__FUNCTION__sr); } @@ -353,20 +361,25 @@ class RestoreConfig : public KeyBackedTaskConfig { KeyBackedBinaryValue fileCount() { return configSpace.pack(__FUNCTION__sr); } // Total number of file blocks in the fileMap KeyBackedBinaryValue fileBlockCount() { return configSpace.pack(__FUNCTION__sr); } + // BulkLoad sub-phase task counts for detailed progress tracking + KeyBackedBinaryValue bulkLoadSubmittedTasks() { return configSpace.pack(__FUNCTION__sr); } + KeyBackedBinaryValue bulkLoadTriggeredTasks() { return configSpace.pack(__FUNCTION__sr); } + KeyBackedBinaryValue bulkLoadRunningTasks() { return configSpace.pack(__FUNCTION__sr); } + KeyBackedBinaryValue bulkLoadTotalTasks() { return configSpace.pack(__FUNCTION__sr); } Future> getRestoreRangesOrDefault(Reference tr) { return getRestoreRangesOrDefault_impl(this, tr); } - ACTOR static Future> getRestoreRangesOrDefault_impl(RestoreConfig* self, - Reference tr) { - state std::vector ranges; - state int batchSize = BUGGIFY ? 1 : CLIENT_KNOBS->RESTORE_RANGES_READ_BATCH; - state Optional begin; - state Arena arena; - loop { + static Future> getRestoreRangesOrDefault_impl(RestoreConfig* self, + Reference tr) { + std::vector ranges; + int batchSize = BUGGIFY ? 1 : CLIENT_KNOBS->RESTORE_RANGES_READ_BATCH; + Optional begin; + Arena arena; + while (true) { KeyBackedSet::RangeResultType rangeResult = - wait(self->restoreRangeSet().getRange(tr, begin, {}, batchSize)); + co_await self->restoreRangeSet().getRange(tr, begin, {}, batchSize); ranges.insert(ranges.end(), rangeResult.results.begin(), rangeResult.results.end()); if (!rangeResult.more) { break; @@ -377,14 +390,14 @@ class RestoreConfig : public KeyBackedTaskConfig { // fall back to original fields if the new field is empty if (ranges.empty()) { - std::vector _ranges = wait(self->restoreRanges().getD(tr)); + std::vector _ranges = co_await self->restoreRanges().getD(tr); ranges = _ranges; if (ranges.empty()) { - KeyRange range = wait(self->restoreRange().getD(tr)); + KeyRange range = co_await self->restoreRange().getD(tr); ranges.push_back(range); } } - return ranges; + co_return ranges; } // Describes a file to load blocks from during restore. Ordered by version and then fileName to enable @@ -418,7 +431,7 @@ class RestoreConfig : public KeyBackedTaskConfig { } }; - typedef KeyBackedSet FileSetT; + using FileSetT = KeyBackedSet; FileSetT fileSet() { return configSpace.pack(__FUNCTION__sr); } FileSetT logFileSet() { return configSpace.pack(__FUNCTION__sr); } @@ -451,18 +464,17 @@ class RestoreConfig : public KeyBackedTaskConfig { Key applyMutationsMapPrefix() { return uidPrefixKey(applyMutationsKeyVersionMapRange.begin, uid); } - ACTOR static Future getApplyVersionLag_impl(Reference tr, UID uid) { - state Future> beginVal = - tr->get(uidPrefixKey(applyMutationsBeginRange.begin, uid), Snapshot::True); - state Future> endVal = tr->get(uidPrefixKey(applyMutationsEndRange.begin, uid), Snapshot::True); - wait(success(beginVal) && success(endVal)); + static Future getApplyVersionLag_impl(Reference tr, UID uid) { + Future> beginVal = tr->get(uidPrefixKey(applyMutationsBeginRange.begin, uid), Snapshot::True); + Future> endVal = tr->get(uidPrefixKey(applyMutationsEndRange.begin, uid), Snapshot::True); + co_await (success(beginVal) && success(endVal)); if (!beginVal.get().present() || !endVal.get().present()) - return 0; + co_return 0; Version beginVersion = BinaryReader::fromStringRef(beginVal.get().get(), Unversioned()); Version endVersion = BinaryReader::fromStringRef(endVal.get().get(), Unversioned()); - return endVersion - beginVersion; + co_return endVersion - beginVersion; } Future getApplyVersionLag(Reference tr) { @@ -532,52 +544,199 @@ class RestoreConfig : public KeyBackedTaskConfig { }); } - ACTOR static Future getCurrentVersion_impl(RestoreConfig* self, Reference tr) { - state ERestoreState status = wait(self->stateEnum().getD(tr)); - state Version version = -1; + static Future getCurrentVersion_impl(RestoreConfig* self, Reference tr) { + ERestoreState status = co_await self->stateEnum().getD(tr); + Version version = -1; if (status == ERestoreState::RUNNING) { - wait(store(version, self->getApplyBeginVersion(tr))); + version = co_await self->getApplyBeginVersion(tr); } else if (status == ERestoreState::COMPLETED) { - wait(store(version, self->restoreVersion().getD(tr))); + version = co_await self->restoreVersion().getD(tr); } - return version; + co_return version; } Future getCurrentVersion(Reference tr) { return getCurrentVersion_impl(this, tr); } - ACTOR static Future getProgress_impl(RestoreConfig restore, Reference tr); + static Future getProgress_impl(RestoreConfig restore, Reference tr); Future getProgress(Reference tr) { return getProgress_impl(*this, tr); } - ACTOR static Future getFullStatus_impl(RestoreConfig restore, Reference tr); + static Future getFullStatus_impl(RestoreConfig restore, Reference tr); Future getFullStatus(Reference tr) { return getFullStatus_impl(*this, tr); } }; -typedef RestoreConfig::RestoreFile RestoreFile; +using RestoreFile = RestoreConfig::RestoreFile; + +// Helper to count bulkload task progress for a job +// Returns: +// Sub-phases help track progress during the long "running" period: +// - Submitted: task created, waiting to be picked up by DD +// - Triggered: assigned to storage server, waiting to start +// - Running: storage server actively downloading/ingesting SST files +Future> getBulkLoadTaskProgress(Database cx, + UID jobId) { + Transaction tr(cx); + Key readBegin = normalKeys.begin; + Key readEnd = normalKeys.end; + int64_t completedTasks = 0; + int64_t submittedTasks = 0; + int64_t triggeredTasks = 0; + int64_t runningTasks = 0; + int64_t totalTasks = 0; + int64_t completedBytes = 0; + + while (readBegin < readEnd) { + Error err; + try { + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + RangeResult rangeResult = co_await krmGetRanges(&tr, bulkLoadTaskPrefix, KeyRangeRef(readBegin, readEnd)); + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { + if (rangeResult[i].value.empty()) { + continue; + } + BulkLoadTaskState task = decodeBulkLoadTaskState(rangeResult[i].value); + if (task.getJobId() != jobId) { + // Different job, stop counting + co_return std::make_tuple( + completedTasks, submittedTasks, triggeredTasks, runningTasks, totalTasks, completedBytes); + } + int manifestCount = task.getManifests().size(); + totalTasks += manifestCount; + if (task.phase == BulkLoadPhase::Complete) { + completedTasks += manifestCount; + // Sum bytes from manifest data sizes + for (const auto& manifest : task.getManifests()) { + completedBytes += manifest.getTotalBytes(); + } + } else if (task.phase == BulkLoadPhase::Submitted) { + submittedTasks += manifestCount; + } else if (task.phase == BulkLoadPhase::Triggered) { + triggeredTasks += manifestCount; + } else if (task.phase == BulkLoadPhase::Running) { + runningTasks += manifestCount; + } + } + readBegin = rangeResult.back().key; + } catch (Error& e) { + err = e; + } + if (err.isValid() && err.code() != error_code_success) { + TraceEvent(SevWarn, "BulkLoadTaskProgressRetry").error(err).detail("JobId", jobId); + co_await tr.onError(err); + } + } + co_return std::make_tuple(completedTasks, submittedTasks, triggeredTasks, runningTasks, totalTasks, completedBytes); +} + +// Monitor BulkLoad job completion and update restore progress counters +// restoreUid is used to update the RestoreConfig progress +Future monitorBulkLoadJobCompletionWithProgress(Database cx, + UID jobId, + UID restoreUid, + int64_t totalBlocks, + double timeoutDuration, + double pollInterval, + bool lockAware) { + double timeoutStart = now(); + RestoreConfig restore(restoreUid); + + while (true) { + Optional currentJob = co_await getRunningBulkLoadJob(cx, lockAware); + bool stillRunning = currentJob.present() && currentJob.get().getJobId() == jobId; + + if (!stillRunning) { + co_return true; + } + + // Update progress based on completed bulkload tasks + try { + auto [completed, submitted, triggered, running, total, bytes] = co_await getBulkLoadTaskProgress(cx, jobId); + if (total > 0) { + // For bulkload restores, fileBlockCount is 0, so use task count as "blocks" + // This provides meaningful progress tracking for the restore status display + // Include all in-progress tasks in dispatched count to show scheduling progress + int64_t inProgress = submitted + triggered + running; + int64_t effectiveTotalBlocks = totalBlocks > 0 ? totalBlocks : total; + int64_t blocksFinished = totalBlocks > 0 ? (totalBlocks * completed) / total : completed; + int64_t blocksDispatched = + totalBlocks > 0 ? (totalBlocks * (completed + inProgress)) / total : (completed + inProgress); + + Reference tr(new ReadYourWritesTransaction(cx)); + tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + if (lockAware) { + tr->setOption(FDBTransactionOptions::LOCK_AWARE); + } + restore.fileBlocksFinished().set(tr, blocksFinished); + restore.filesBlocksDispatched().set(tr, blocksDispatched); + restore.fileBlockCount().set(tr, effectiveTotalBlocks); + restore.bytesWritten().set(tr, bytes); + // Store sub-phase counts for detailed progress display + restore.bulkLoadSubmittedTasks().set(tr, submitted); + restore.bulkLoadTriggeredTasks().set(tr, triggered); + restore.bulkLoadRunningTasks().set(tr, running); + restore.bulkLoadTotalTasks().set(tr, total); + co_await tr->commit(); + + TraceEvent("BulkLoadRestoreProgress") + .detail("RestoreUID", restoreUid) + .detail("JobId", jobId) + .detail("CompletedTasks", completed) + .detail("SubmittedTasks", submitted) + .detail("TriggeredTasks", triggered) + .detail("RunningTasks", running) + .detail("TotalTasks", total) + .detail("BlocksFinished", blocksFinished) + .detail("BlocksDispatched", blocksDispatched) + .detail("EffectiveTotalBlocks", effectiveTotalBlocks) + .detail("BytesWritten", bytes); + } + } catch (Error& e) { + // Log but don't fail - progress updates are best-effort + TraceEvent(SevWarn, "BulkLoadRestoreProgressError").error(e).detail("JobId", jobId); + } + + if (now() - timeoutStart > timeoutDuration) { + co_return false; // Timed out + } + + co_await delay(pollInterval); + } +} -ACTOR Future RestoreConfig::getProgress_impl(RestoreConfig restore, - Reference tr) { +Future RestoreConfig::getProgress_impl(RestoreConfig restore, Reference tr) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future fileCount = restore.fileCount().getD(tr); - state Future fileBlockCount = restore.fileBlockCount().getD(tr); - state Future fileBlocksDispatched = restore.filesBlocksDispatched().getD(tr); - state Future fileBlocksFinished = restore.fileBlocksFinished().getD(tr); - state Future bytesWritten = restore.bytesWritten().getD(tr); - state Future status = restore.stateText(tr); - state Future currentVersion = restore.getCurrentVersion(tr); - state Future lag = restore.getApplyVersionLag(tr); - state Future firstConsistentVersion = restore.firstConsistentVersion().getD(tr); - state Future tag = restore.tag().getD(tr); - state Future> lastError = restore.lastError().getD(tr); - - // restore might no longer be valid after the first wait so make sure it is not needed anymore. - state UID uid = restore.getUid(); - wait(success(fileCount) && success(fileBlockCount) && success(fileBlocksDispatched) && - success(fileBlocksFinished) && success(bytesWritten) && success(status) && success(currentVersion) && - success(lag) && success(firstConsistentVersion) && success(tag) && success(lastError)); + Future fileCount = restore.fileCount().getD(tr); + Future fileBlockCount = restore.fileBlockCount().getD(tr); + Future fileBlocksDispatched = restore.filesBlocksDispatched().getD(tr); + Future fileBlocksFinished = restore.fileBlocksFinished().getD(tr); + Future bytesWritten = restore.bytesWritten().getD(tr); + Future status = restore.stateText(tr); + Future currentVersion = restore.getCurrentVersion(tr); + Future lag = restore.getApplyVersionLag(tr); + Future firstConsistentVersion = restore.firstConsistentVersion().getD(tr); + Future tag = restore.tag().getD(tr); + Future> lastError = restore.lastError().getD(tr); + Future submittedTasks = restore.bulkLoadSubmittedTasks().getD(tr); + Future triggeredTasks = restore.bulkLoadTriggeredTasks().getD(tr); + Future runningTasks = restore.bulkLoadRunningTasks().getD(tr); + Future totalTasks = restore.bulkLoadTotalTasks().getD(tr); + Future> useRangeFileRestore = restore.useRangeFileRestore().get(tr); + + UID uid = restore.getUid(); + co_await (success(fileCount) && success(fileBlockCount) && success(fileBlocksDispatched) && + success(fileBlocksFinished) && success(bytesWritten) && success(status) && success(currentVersion) && + success(lag) && success(firstConsistentVersion) && success(tag) && success(lastError) && + success(submittedTasks) && success(triggeredTasks) && success(runningTasks) && success(totalTasks) && + success(useRangeFileRestore)); + + bool useRangeFile = !useRangeFileRestore.get().present() || useRangeFileRestore.get().get(); std::string errstr = "None"; if (lastError.get().second != 0) @@ -593,44 +752,63 @@ ACTOR Future RestoreConfig::getProgress_impl(RestoreConfig restore, .detail("FileBlocksFinished", fileBlocksFinished.get()) .detail("FileBlocksTotal", fileBlockCount.get()) .detail("FileBlocksInProgress", fileBlocksDispatched.get() - fileBlocksFinished.get()) + .detail("SubmittedTasks", submittedTasks.get()) + .detail("TriggeredTasks", triggeredTasks.get()) + .detail("RunningTasks", runningTasks.get()) + .detail("TotalTasks", totalTasks.get()) .detail("BytesWritten", bytesWritten.get()) .detail("CurrentVersion", currentVersion.get()) .detail("FirstConsistentVersion", firstConsistentVersion.get()) - .detail("ApplyLag", lag.get()) - .detail("TaskInstance", THIS_ADDR); - - return format("Tag: %s UID: %s State: %s Blocks: %lld/%lld BlocksInProgress: %lld Files: %lld BytesWritten: " - "%lld ApplyVersionLag: %lld LastError: %s", - tag.get().c_str(), - uid.toString().c_str(), - status.get().toString().c_str(), - fileBlocksFinished.get(), - fileBlockCount.get(), - fileBlocksDispatched.get() - fileBlocksFinished.get(), - fileCount.get(), - bytesWritten.get(), - lag.get(), - errstr.c_str()); + .detail("ApplyLag", lag.get()); + + std::string progressStr; + if (useRangeFile) { + progressStr = format("Tag: %s UID: %s State: %s\n", + tag.get().c_str(), + uid.toString().c_str(), + status.get().toString().c_str()); + progressStr += format(" Blocks: %lld/%lld complete\n", fileBlocksFinished.get(), fileBlockCount.get()); + progressStr += format(" Files: %lld\n", fileCount.get()); + progressStr += format(" Bytes written: %s\n", formatBytesHumanReadable(bytesWritten.get()).c_str()); + progressStr += format(" Apply version lag: %s\n", versionToString(lag.get()).c_str()); + } else { + progressStr = format("Tag: %s UID: %s State: %s\n", + tag.get().c_str(), + uid.toString().c_str(), + status.get().toString().c_str()); + progressStr += format(" Tasks submitted: %lld triggered: %lld running: %lld\n", + submittedTasks.get(), + triggeredTasks.get(), + runningTasks.get()); + progressStr += format(" Tasks triggered: %lld / %lld total\n", triggeredTasks.get(), totalTasks.get()); + progressStr += format(" Bytes written: %s\n", formatBytesHumanReadable(bytesWritten.get()).c_str()); + double avgBytesPerTask = triggeredTasks.get() > 0 ? (double)bytesWritten.get() / triggeredTasks.get() : 0; + if (avgBytesPerTask > 0) { + progressStr += format(" Avg bytes/task: %s\n", formatBytesHumanReadable((int64_t)avgBytesPerTask).c_str()); + } + } + + co_return progressStr; } -ACTOR Future RestoreConfig::getFullStatus_impl(RestoreConfig restore, - Reference tr) { +Future RestoreConfig::getFullStatus_impl(RestoreConfig restore, Reference tr) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future> ranges = restore.getRestoreRangesOrDefault(tr); - state Future addPrefix = restore.addPrefix().getD(tr); - state Future removePrefix = restore.removePrefix().getD(tr); - state Future url = restore.sourceContainerURL().getD(tr); - state Future restoreVersion = restore.restoreVersion().getD(tr); - state Future progress = restore.getProgress(tr); - state Future restoreState = restore.stateEnum().getD(tr); - state Future> useRangeFileRestore = restore.useRangeFileRestore().get(tr); - state Future> bulkLoadComplete = restore.bulkLoadComplete().get(tr); + Future> ranges = restore.getRestoreRangesOrDefault(tr); + Future addPrefix = restore.addPrefix().getD(tr); + Future removePrefix = restore.removePrefix().getD(tr); + Future url = restore.sourceContainerURL().getD(tr); + Future restoreVersion = restore.restoreVersion().getD(tr); + Future progress = restore.getProgress(tr); + Future restoreState = restore.stateEnum().getD(tr); + Future> useRangeFileRestore = restore.useRangeFileRestore().get(tr); + Future> bulkLoadComplete = restore.bulkLoadComplete().get(tr); // restore might no longer be valid after the first wait so make sure it is not needed anymore. - wait(success(ranges) && success(addPrefix) && success(removePrefix) && success(url) && success(restoreVersion) && - success(progress) && success(restoreState) && success(useRangeFileRestore) && success(bulkLoadComplete)); + co_await (success(ranges) && success(addPrefix) && success(removePrefix) && success(url) && + success(restoreVersion) && success(progress) && success(restoreState) && success(useRangeFileRestore) && + success(bulkLoadComplete)); std::string returnStr; returnStr = format("%s URL: %s", progress.get().c_str(), url.get().toString().c_str()); @@ -643,15 +821,15 @@ ACTOR Future RestoreConfig::getFullStatus_impl(RestoreConfig restor restoreVersion.get()); // Add enhanced status fields for BulkLoad integration - // useRangeFileRestore is Future>, after wait() we call .get() to get Optional bool usingBulkLoad = useRangeFileRestore.get().present() && !useRangeFileRestore.get().get(); std::string snapshotMethod = usingBulkLoad ? "bulkload" : "rangefile"; returnStr += format(" Snapshot Method: %s", snapshotMethod.c_str()); // Add phase status information ERestoreState currentState = restoreState.get(); + bool bulkLoadDone = bulkLoadComplete.get().present() && bulkLoadComplete.get().get(); + if (currentState == ERestoreState::RUNNING) { - bool bulkLoadDone = bulkLoadComplete.get().present() && bulkLoadComplete.get().get(); if (usingBulkLoad) { std::string snapshotPhase = bulkLoadDone ? "complete" : "in_progress"; returnStr += format(" Snapshot Phase: %s", snapshotPhase.c_str()); @@ -664,7 +842,7 @@ ACTOR Future RestoreConfig::getFullStatus_impl(RestoreConfig restor returnStr += " Snapshot Phase: complete Mutation Log Phase: complete"; } - return returnStr; + co_return returnStr; } // two buffers are alternatively serving data and reading data from file @@ -681,7 +859,7 @@ class TwoBuffers : public ReferenceCounted, NonCopyable { size_t size; int index; int capacity; - IteratorBuffer(int _capacity) { + explicit IteratorBuffer(int _capacity) { capacity = _capacity; data = std::shared_ptr(new char[capacity]()); fetchingData.reset(); @@ -699,10 +877,10 @@ class TwoBuffers : public ReferenceCounted, NonCopyable { // because a shared_ptr cannot be wrapped by a Future // this method ensures the current buffer has available data Future ready(); - ACTOR static Future ready(Reference self); + static Future ready(Reference self); // fill buffer[index] with the next block of file // it has side effects to change currentFileIndex and currentFilePosition - ACTOR static Future readNextBlock(Reference self, int index); + static Future readNextBlock(Reference self, int index); // peek can only be called after ready is called // it returns the pointer to the active buffer std::shared_ptr peek(); @@ -762,19 +940,18 @@ Future TwoBuffers::ready() { return ready(Reference::addRef(this)); } -ACTOR Future TwoBuffers::ready(Reference self) { +Future TwoBuffers::ready(Reference self) { // if cur is not ready, then wait if (!self->hasNext()) { - return Void(); + co_return; } // try to fill the current buffer, and wait before it is filled self->fillBufferIfAbsent(self->cur); - wait(self->buffers[self->cur]->fetchingData.value()); + co_await self->buffers[self->cur]->fetchingData.value(); // try to fill the next buffer, do not wait for the filling if (self->hasNext()) { self->fillBufferIfAbsent(1 - self->cur); } - return Void(); } std::shared_ptr TwoBuffers::peek() { @@ -815,8 +992,7 @@ size_t TwoBuffers::getBufferSize() { } // only one readNextBlock can be run at a single time, otherwie the same block might be loaded twice -ACTOR Future TwoBuffers::readNextBlock(Reference self, int index) { - state Reference asyncFile; +Future TwoBuffers::readNextBlock(Reference self, int index) { if (self->currentFileIndex >= self->files.size()) { TraceEvent(SevError, "ReadNextBlockOutOfBound") .detail("FileIndex", self->currentFileIndex) @@ -825,22 +1001,19 @@ ACTOR Future TwoBuffers::readNextBlock(Reference self, int ind .detail("FileSize", self->files[self->currentFileIndex].fileSize) .detail("FilesCount", self->files.size()) .log(); - return Void(); + co_return; } - Reference asyncFileTmp = wait(self->bc->readFile(self->files[self->currentFileIndex].fileName)); - asyncFile = asyncFileTmp; - state size_t fileSize = self->files[self->currentFileIndex].fileSize; + Reference asyncFile = co_await self->bc->readFile(self->files[self->currentFileIndex].fileName); + size_t fileSize = self->files[self->currentFileIndex].fileSize; size_t remaining = fileSize - self->currentFilePosition; - state size_t bytesToRead = std::min(self->bufferCapacity, remaining); - state int bytesRead = wait( - asyncFile->read(static_cast(self->buffers[index]->data.get()), bytesToRead, self->currentFilePosition)); + size_t bytesToRead = std::min(self->bufferCapacity, remaining); + int bytesRead = co_await asyncFile->read( + static_cast(self->buffers[index]->data.get()), bytesToRead, self->currentFilePosition); if (bytesRead != bytesToRead) throw restore_bad_read(); self->buffers[index]->index = self->currentFileIndex; self->buffers[index]->size = bytesRead; // Set to actual bytes read self->currentFilePosition += bytesRead; - - return Void(); } void TwoBuffers::fillBufferIfAbsent(int index) { @@ -883,22 +1056,21 @@ class PartitionedLogIteratorSimple : public PartitionedLogIterator { std::vector _files, std::vector _endVersions); - bool hasNext(); + bool hasNext() override; Future loadNextBlock(); - ACTOR static Future loadNextBlock(Reference self); + static Future loadNextBlock(Reference self); void removeBlockHeader(); Standalone> consumeData(Version firstVersion); // find the next version without advanding the iterator - Future peekNextVersion(); - ACTOR static Future peekNextVersion(Reference iterator); + Future peekNextVersion() override; + static Future peekNextVersion(Reference iterator); // get all the mutations of next version and advance the iterator // this might issue multiple consumeData() if the data of a version cross buffer boundary - Future>> getNext(); - ACTOR static Future>> getNext( - Reference iterator); + Future>> getNext() override; + static Future>> getNext(Reference iterator); }; PartitionedLogIteratorSimple::PartitionedLogIteratorSimple(Reference _bc, @@ -1005,42 +1177,38 @@ Future PartitionedLogIteratorSimple::loadNextBlock() { return loadNextBlock(Reference::addRef(this)); } -ACTOR Future PartitionedLogIteratorSimple::loadNextBlock(Reference self) { +Future PartitionedLogIteratorSimple::loadNextBlock(Reference self) { if (self->bufferOffset < self->bufferSize) { // do nothing - return Void(); + co_return; } if (!self->hasNext()) { - return Void(); + co_return; } - state Reference asyncFile; - Reference asyncFileTmp = wait(self->bc->readFile(self->files[self->fileIndex].fileName)); - asyncFile = asyncFileTmp; - state size_t fileSize = self->files[self->fileIndex].fileSize; + Reference asyncFile = co_await self->bc->readFile(self->files[self->fileIndex].fileName); + size_t fileSize = self->files[self->fileIndex].fileSize; size_t remaining = fileSize - self->fileOffset; - state size_t bytesToRead = std::min(self->bufferCapacity, remaining); - state int bytesRead = - wait(asyncFile->read(static_cast((self->buffer.get())), bytesToRead, self->fileOffset)); + size_t bytesToRead = std::min(self->bufferCapacity, remaining); + int bytesRead = co_await asyncFile->read(static_cast((self->buffer.get())), bytesToRead, self->fileOffset); if (bytesRead != bytesToRead) throw restore_bad_read(); self->bufferSize = bytesRead; // Set to actual bytes read self->bufferOffset = 0; // Reset bufferOffset for the new data self->fileOffset += bytesRead; - return Void(); } Future PartitionedLogIteratorSimple::peekNextVersion() { return peekNextVersion(Reference::addRef(this)); } -ACTOR Future PartitionedLogIteratorSimple::peekNextVersion(Reference self) { +Future PartitionedLogIteratorSimple::peekNextVersion(Reference self) { // Read the first mutation's version if (!self->hasNext()) { - return Version(0); + co_return Version(0); } - wait(self->loadNextBlock()); + co_await self->loadNextBlock(); self->removeBlockHeader(); - state Version version; + Version version{ 0 }; std::memcpy(&version, self->buffer.get() + self->bufferOffset, sizeof(Version)); version = bigEndian64(version); @@ -1053,26 +1221,26 @@ ACTOR Future PartitionedLogIteratorSimple::peekNextVersion(ReferencebufferSize = 0; self->fileOffset = 0; self->fileIndex += 1; - wait(self->loadNextBlock()); + co_await self->loadNextBlock(); self->removeBlockHeader(); std::memcpy(&version, self->buffer.get() + self->bufferOffset, sizeof(Version)); version = bigEndian64(version); } - return version; + co_return version; } -ACTOR Future>> PartitionedLogIteratorSimple::getNext( +Future>> PartitionedLogIteratorSimple::getNext( Reference self) { - state Standalone> mutations; + Standalone> mutations; if (!self->hasNext()) { TraceEvent(SevWarn, "SimpleIteratorExhausted") .detail("BufferOffset", self->bufferOffset) .detail("BufferSize", self->bufferSize) .detail("Tag", self->tag) .log(); - return mutations; + co_return mutations; } - state Version firstVersion = wait(self->peekNextVersion()); + Version firstVersion = co_await self->peekNextVersion(); Standalone> firstBatch = self->consumeData(firstVersion); mutations = firstBatch; // If the current buffer is fully consumed, then we need to check the next buffer in case @@ -1084,7 +1252,7 @@ ACTOR Future>> PartitionedLogIteratorSim if (self->hasNext()) { // now this is run for each block, but it is not necessary if it is the last block of a file // cannot check hasMoreData here because other buffer might have the last piece - wait(self->loadNextBlock()); + co_await self->loadNextBlock(); Standalone> batch = self->consumeData(firstVersion); for (const VersionedMutation& vm : batch) { mutations.push_back_deep(mutations.arena(), vm); @@ -1093,7 +1261,7 @@ ACTOR Future>> PartitionedLogIteratorSim break; } } - return mutations; + co_return mutations; } Future>> PartitionedLogIteratorSimple::getNext() { @@ -1108,7 +1276,7 @@ class PartitionedLogIteratorTwoBuffers : public PartitionedLogIterator { // stop if seeing a different version from the parameter. // it has side effects to update bufferOffset after reading the data Future>> consumeData(Version firstVersion); - ACTOR static Future>> consumeData( + static Future>> consumeData( Reference self, Version v); @@ -1137,16 +1305,16 @@ class PartitionedLogIteratorTwoBuffers : public PartitionedLogIterator { std::vector _endVersions); // whether there are more contents for this tag in all files specified - bool hasNext(); + bool hasNext() override; // find the next version without advanding the iterator - Future peekNextVersion(); - ACTOR static Future peekNextVersion(Reference iterator); + Future peekNextVersion() override; + static Future peekNextVersion(Reference iterator); // get all the mutations of next version and advance the iterator // this might issue multiple consumeData() if the data of a version cross buffer boundary - Future>> getNext(); - ACTOR static Future>> getNext( + Future>> getNext() override; + static Future>> getNext( Reference iterator); }; @@ -1154,11 +1322,11 @@ Future>> PartitionedLogIteratorTwoBuffer return consumeData(Reference::addRef(this), firstVersion); } -ACTOR Future>> PartitionedLogIteratorTwoBuffers::consumeData( +Future>> PartitionedLogIteratorTwoBuffers::consumeData( Reference self, Version firstVersion) { - state Standalone> mutations = Standalone>(); - wait(self->twobuffer->ready()); + Standalone> mutations = Standalone>(); + co_await self->twobuffer->ready(); std::shared_ptr start = self->twobuffer->peek(); int size = self->twobuffer->getBufferSize(); bool foundNewVersion = false; @@ -1217,7 +1385,7 @@ ACTOR Future>> PartitionedLogIteratorTwo break; } } - return mutations; + co_return mutations; } void PartitionedLogIteratorTwoBuffers::removeBlockHeader() { @@ -1242,16 +1410,15 @@ bool PartitionedLogIteratorTwoBuffers::hasNext() { Future PartitionedLogIteratorTwoBuffers::peekNextVersion() { return peekNextVersion(Reference::addRef(this)); } -ACTOR Future PartitionedLogIteratorTwoBuffers::peekNextVersion( - Reference self) { +Future PartitionedLogIteratorTwoBuffers::peekNextVersion(Reference self) { // Read the first mutation's version - state std::shared_ptr start; - state Version version; - state int fileIndex; + std::shared_ptr start; + Version version{ 0 }; + int fileIndex{ 0 }; if (!self->hasNext()) { - return Version(0); + co_return Version(0); } - wait(self->twobuffer->ready()); + co_await self->twobuffer->ready(); start = self->twobuffer->peek(); self->removeBlockHeader(); std::memcpy(&version, start.get() + self->bufferOffset, sizeof(Version)); @@ -1266,26 +1433,26 @@ ACTOR Future PartitionedLogIteratorTwoBuffers::peekNextVersion( self->twobuffer->reset(); self->bufferOffset = 0; self->twobuffer->setFileIndex(fileIndex + 1); - wait(self->twobuffer->ready()); + co_await self->twobuffer->ready(); start = self->twobuffer->peek(); self->removeBlockHeader(); std::memcpy(&version, start.get() + self->bufferOffset, sizeof(Version)); version = bigEndian64(version); fileIndex = self->twobuffer->getFileIndex(); } - return version; + co_return version; } -ACTOR Future>> PartitionedLogIteratorTwoBuffers::getNext( +Future>> PartitionedLogIteratorTwoBuffers::getNext( Reference self) { - state Standalone> mutations; + Standalone> mutations; if (!self->hasNext()) { TraceEvent(SevWarn, "IteratorExhausted").log(); - return mutations; + co_return mutations; } - state Version firstVersion = wait(self->peekNextVersion()); + Version firstVersion = co_await self->peekNextVersion(); - Standalone> firstBatch = wait(self->consumeData(firstVersion)); + Standalone> firstBatch = co_await self->consumeData(firstVersion); mutations = firstBatch; // If the current buffer is fully consumed, then we need to check the next buffer in case // the version is sliced across this buffer boundary @@ -1297,11 +1464,11 @@ ACTOR Future>> PartitionedLogIteratorTwo if (self->twobuffer->hasNext()) { // now this is run for each block, but it is not necessary if it is the last block of a file // cannot check hasMoreData here because other buffer might have the last piece - Version nextVersion = wait(self->peekNextVersion()); + Version nextVersion = co_await self->peekNextVersion(); if (nextVersion != firstVersion) { break; } - Standalone> batch = wait(self->consumeData(firstVersion)); + Standalone> batch = co_await self->consumeData(firstVersion); for (const VersionedMutation& vm : batch) { mutations.push_back_deep(mutations.arena(), vm); } @@ -1309,7 +1476,7 @@ ACTOR Future>> PartitionedLogIteratorTwo break; } } - return mutations; + co_return mutations; } Future>> PartitionedLogIteratorTwoBuffers::getNext() { @@ -1351,7 +1518,7 @@ struct IRangeFileWriter { virtual Future finish() = 0; - virtual ~IRangeFileWriter() {} + virtual ~IRangeFileWriter() = default; }; // File Format handlers. @@ -1388,46 +1555,46 @@ struct IRangeFileWriter { // then the space after the final key to the next 1MB boundary would // just be padding anyway. struct RangeFileWriter : public IRangeFileWriter { - RangeFileWriter(Reference file = Reference(), int blockSize = 0) + explicit RangeFileWriter(Reference file = Reference(), int blockSize = 0) : file(file), blockSize(blockSize), blockEnd(0), fileVersion(BACKUP_AGENT_SNAPSHOT_FILE_VERSION) {} // Handles the first block and internal blocks. Ends current block if needed. // The final flag is used in simulation to pad the file's final block to a whole block size - ACTOR static Future newBlock(RangeFileWriter* self, int bytesNeeded, bool final = false) { + static Future newBlock(RangeFileWriter* self, int bytesNeeded, bool final = false) { // Write padding to finish current block if needed int bytesLeft = self->blockEnd - self->file->size(); if (bytesLeft > 0) { - state Value paddingFFs = makePadding(bytesLeft); - wait(self->file->append(paddingFFs.begin(), bytesLeft)); + Value paddingFFs = makePadding(bytesLeft); + co_await self->file->append(paddingFFs.begin(), bytesLeft); } if (final) { ASSERT(g_network->isSimulated()); - return Void(); + co_return; } // Set new blockEnd self->blockEnd += self->blockSize; // write Header - wait(self->file->append((uint8_t*)&self->fileVersion, sizeof(self->fileVersion))); + co_await self->file->append((uint8_t*)&self->fileVersion, sizeof(self->fileVersion)); // If this is NOT the first block then write duplicate stuff needed from last block if (self->blockEnd > self->blockSize) { - wait(self->file->appendStringRefWithLen(self->lastKey)); - wait(self->file->appendStringRefWithLen(self->lastKey)); - wait(self->file->appendStringRefWithLen(self->lastValue)); + co_await self->file->appendStringRefWithLen(self->lastKey); + co_await self->file->appendStringRefWithLen(self->lastKey); + co_await self->file->appendStringRefWithLen(self->lastValue); } // There must now be room in the current block for bytesNeeded or the block size is too small if (self->file->size() + bytesNeeded > self->blockEnd) throw backup_bad_block_size(); - return Void(); + co_return; } // Used in simulation only to create backup file sizes which are an integer multiple of the block size - Future padEnd(bool final) { + Future padEnd(bool final) override { ASSERT(g_network->isSimulated()); if (file->size() > 0) { return newBlock(this, 0, final); @@ -1443,29 +1610,29 @@ struct RangeFileWriter : public IRangeFileWriter { } // Start a new block if needed, then write the key and value - ACTOR static Future writeKV_impl(RangeFileWriter* self, Key k, Value v) { + static Future writeKV_impl(RangeFileWriter* self, Key k, Value v) { int toWrite = sizeof(int32_t) + k.size() + sizeof(int32_t) + v.size(); - wait(self->newBlockIfNeeded(toWrite)); - wait(self->file->appendStringRefWithLen(k)); - wait(self->file->appendStringRefWithLen(v)); + co_await self->newBlockIfNeeded(toWrite); + co_await self->file->appendStringRefWithLen(k); + co_await self->file->appendStringRefWithLen(v); self->lastKey = k; self->lastValue = v; - return Void(); + co_return; } - Future writeKV(Key k, Value v) { return writeKV_impl(this, k, v); } + Future writeKV(Key k, Value v) override { return writeKV_impl(this, k, v); } // Write begin key or end key. - ACTOR static Future writeKey_impl(RangeFileWriter* self, Key k) { + static Future writeKey_impl(RangeFileWriter* self, Key k) { int toWrite = sizeof(uint32_t) + k.size(); - wait(self->newBlockIfNeeded(toWrite)); - wait(self->file->appendStringRefWithLen(k)); - return Void(); + co_await self->newBlockIfNeeded(toWrite); + co_await self->file->appendStringRefWithLen(k); + co_return; } - Future writeKey(Key k) { return writeKey_impl(this, k); } + Future writeKey(Key k) override { return writeKey_impl(this, k); } - Future finish() { return Void(); } + Future finish() override { return Void(); } Reference file; int blockSize; @@ -1513,7 +1680,8 @@ void decodeKVPairs(StringRefReader* reader, Standalone>* } static Reference getBackupContainerWithProxy(Reference _bc) { - Reference bc = IBackupContainer::openContainer(_bc->getURL(), fileBackupAgentProxy, {}); + Reference bc = IBackupContainer::openContainer( + _bc->getURL(), fileBackupAgentProxy, _bc->getEncryptionKeyFileName(), _bc->getEncryptionBlockSize()); return bc; } @@ -1562,29 +1730,27 @@ Standalone> decodeRangeFileBlock(const Standalone>> decodeRangeFileBlock(Reference file, - int64_t offset, - int len, - Database cx) { - state Standalone buf = makeString(len); - int rLen = wait(uncancellable(holdWhile(buf, file->read(mutateString(buf), len, offset)))); +Future>> decodeRangeFileBlock(Reference file, + int64_t offset, + int len, + Database cx) { + Standalone buf = makeString(len); + int rLen = co_await uncancellable(holdWhile(buf, file->read(mutateString(buf), len, offset))); if (rLen != len) throw restore_bad_read(); simulateBlobFailure(); - state Standalone> results({}, buf.arena()); - state StringRefReader reader(buf, restore_corrupted_data()); - state Arena arena; - state int64_t blockDomainId = -1; - + Standalone> results({}, buf.arena()); + StringRefReader reader(buf, restore_corrupted_data()); + Arena arena; try { int32_t file_version = reader.consume(); if (file_version != BACKUP_AGENT_SNAPSHOT_FILE_VERSION) { throw restore_unsupported_file_version(); } decodeKVPairs(&reader, &results); - return results; + co_return results; } catch (Error& e) { TraceEvent(SevWarn, "FileRestoreDecodeRangeFileBlockFailed") .error(e) @@ -1600,36 +1766,36 @@ ACTOR Future>> decodeRangeFileBlock(Reference< // Very simple format compared to KeyRange files. // Header, [Key, Value]... Key len struct LogFileWriter { - LogFileWriter(Reference file = Reference(), int blockSize = 0) + explicit LogFileWriter(Reference file = Reference(), int blockSize = 0) : file(file), blockSize(blockSize), blockEnd(0) {} // Start a new block if needed, then write the key and value - ACTOR static Future writeKV_impl(LogFileWriter* self, Key k, Value v) { + static Future writeKV_impl(LogFileWriter* self, Key k, Value v) { // If key and value do not fit in this block, end it and start a new one int toWrite = sizeof(int32_t) + k.size() + sizeof(int32_t) + v.size(); if (self->file->size() + toWrite > self->blockEnd) { // Write padding if needed int bytesLeft = self->blockEnd - self->file->size(); if (bytesLeft > 0) { - state Value paddingFFs = makePadding(bytesLeft); - wait(self->file->append(paddingFFs.begin(), bytesLeft)); + Value paddingFFs = makePadding(bytesLeft); + co_await self->file->append(paddingFFs.begin(), bytesLeft); } // Set new blockEnd self->blockEnd += self->blockSize; // write the block header - wait(self->file->append((uint8_t*)&BACKUP_AGENT_MLOG_VERSION, sizeof(BACKUP_AGENT_MLOG_VERSION))); + co_await self->file->append((uint8_t*)&BACKUP_AGENT_MLOG_VERSION, sizeof(BACKUP_AGENT_MLOG_VERSION)); } - wait(self->file->appendStringRefWithLen(k)); - wait(self->file->appendStringRefWithLen(v)); + co_await self->file->appendStringRefWithLen(k); + co_await self->file->appendStringRefWithLen(v); // At this point we should be in whatever the current block is or the block size is too small if (self->file->size() > self->blockEnd) throw backup_bad_block_size(); - return Void(); + co_return; } Future writeKV(Key k, Value v) { return writeKV_impl(this, k, v); } @@ -1674,16 +1840,16 @@ Standalone> decodeMutationLogFileBlock(const Standalone>> decodeMutationLogFileBlock(Reference file, - int64_t offset, - int len) { - state Standalone buf = makeString(len); - int rLen = wait(file->read(mutateString(buf), len, offset)); +Future>> decodeMutationLogFileBlock(Reference file, + int64_t offset, + int len) { + Standalone buf = makeString(len); + int rLen = co_await file->read(mutateString(buf), len, offset); if (rLen != len) throw restore_bad_read(); try { - return decodeMutationLogFileBlock(buf); + co_return decodeMutationLogFileBlock(buf); } catch (Error& e) { TraceEvent(SevWarn, "FileRestoreCorruptLogFileBlock") .error(e) @@ -1694,10 +1860,10 @@ ACTOR Future>> decodeMutationLogFileBlock(Refe } } -ACTOR Future checkTaskVersion(Database cx, Reference task, StringRef name, uint32_t version) { +Future checkTaskVersion(Database cx, Reference task, StringRef name, uint32_t version) { uint32_t taskVersion = task->getVersion(); if (taskVersion > version) { - state Error err = task_invalid_version(); + Error err = task_invalid_version(); TraceEvent(SevWarn, "BA_BackupRangeTaskFuncExecute") .detail("TaskVersion", taskVersion) @@ -1708,35 +1874,33 @@ ACTOR Future checkTaskVersion(Database cx, Reference task, StringRef task->params[Task::reservedTaskParamKeyType].toString().c_str(), (unsigned long)taskVersion, (unsigned long)version); - wait(BackupConfig(task).logError(cx, err, msg)); + co_await BackupConfig(task).logError(cx, err, msg); } throw err; } - - return Void(); } -ACTOR static Future abortFiveZeroBackup(FileBackupAgent* backupAgent, - Reference tr, - std::string tagName) { +static Future abortFiveZeroBackup(FileBackupAgent* backupAgent, + Reference tr, + std::string tagName) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Subspace tagNames = backupAgent->subspace.get(BackupAgentBase::keyTagName); - Optional uidStr = wait(tr->get(tagNames.pack(Key(tagName)))); + Subspace tagNames = backupAgent->subspace.get(BackupAgentBase::keyTagName); + Optional uidStr = co_await tr->get(tagNames.pack(Key(tagName))); if (!uidStr.present()) { TraceEvent(SevWarn, "FileBackupAbortIncompatibleBackup_TagNotFound").detail("TagName", tagName.c_str()); - return Void(); + co_return; } - state UID uid = BinaryReader::fromStringRef(uidStr.get(), Unversioned()); + UID uid = BinaryReader::fromStringRef(uidStr.get(), Unversioned()); - state Subspace statusSpace = backupAgent->subspace.get(BackupAgentBase::keyStates).get(uid.toString()); - state Subspace globalConfig = backupAgent->subspace.get(BackupAgentBase::keyConfig).get(uid.toString()); - state Subspace newConfigSpace = uidPrefixKey("uid->config/"_sr.withPrefix(fileBackupPrefixRange.begin), uid); + Subspace statusSpace = backupAgent->subspace.get(BackupAgentBase::keyStates).get(uid.toString()); + Subspace globalConfig = backupAgent->subspace.get(BackupAgentBase::keyConfig).get(uid.toString()); + Subspace newConfigSpace(uidPrefixKey("uid->config/"_sr.withPrefix(fileBackupPrefixRange.begin), uid)); - Optional statusStr = wait(tr->get(statusSpace.pack(FileBackupAgent::keyStateStatus))); - state EBackupState status = + Optional statusStr = co_await tr->get(statusSpace.pack(FileBackupAgent::keyStateStatus)); + EBackupState status = !statusStr.present() ? EBackupState::STATE_NEVERRAN : BackupAgentBase::getState(statusStr.get().toString()); TraceEvent(SevInfo, "FileBackupAbortIncompatibleBackup") @@ -1760,26 +1924,24 @@ ACTOR static Future abortFiveZeroBackup(FileBackupAgent* backupAgent, // Set old style state key to Aborted if it was Runnable if (backupAgent->isRunnable(status)) tr->set(statusKey, StringRef(FileBackupAgent::getStateText(EBackupState::STATE_ABORTED))); - - return Void(); } struct AbortFiveZeroBackupTask : TaskFuncBase { static StringRef name; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state FileBackupAgent backupAgent; - state std::string tagName = task->params[BackupAgentBase::keyConfigBackupTag].toString(); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + FileBackupAgent backupAgent; + std::string tagName = task->params[BackupAgentBase::keyConfigBackupTag].toString(); TraceEvent(SevInfo, "FileBackupCancelOldTask") .detail("Task", task->params[Task::reservedTaskParamKeyType]) .detail("TagName", tagName); - wait(abortFiveZeroBackup(&backupAgent, tr, tagName)); + co_await abortFiveZeroBackup(&backupAgent, tr, tagName); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } StringRef getName() const override { @@ -1813,17 +1975,17 @@ REGISTER_TASKFUNC_ALIAS(AbortFiveZeroBackupTask, file_finish_full_backup); REGISTER_TASKFUNC_ALIAS(AbortFiveZeroBackupTask, file_finished_full_backup); REGISTER_TASKFUNC_ALIAS(AbortFiveZeroBackupTask, file_start_full_backup); -ACTOR static Future abortFiveOneBackup(FileBackupAgent* backupAgent, - Reference tr, - std::string tagName) { +static Future abortFiveOneBackup(FileBackupAgent* backupAgent, + Reference tr, + std::string tagName) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeBackupTag(tagName); - state UidAndAbortedFlagT current = wait(tag.getOrThrow(tr, Snapshot::False, backup_unneeded())); + KeyBackedTag tag = makeBackupTag(tagName); + UidAndAbortedFlagT current = co_await tag.getOrThrow(tr, Snapshot::False, backup_unneeded()); - state BackupConfig config(current.first); - EBackupState status = wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + BackupConfig config(current.first); + EBackupState status = co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); if (!backupAgent->isRunnable(status)) { throw backup_unneeded(); @@ -1834,7 +1996,7 @@ ACTOR static Future abortFiveOneBackup(FileBackupAgent* backupAgent, .detail("Status", BackupAgentBase::getStateText(status)); // Cancel backup task through tag - wait(tag.cancel(tr)); + co_await tag.cancel(tr); Key configPath = uidPrefixKey(logRangesRange.begin, config.getUid()); Key logsPath = uidPrefixKey(backupLogKeys.begin, config.getUid()); @@ -1843,27 +2005,24 @@ ACTOR static Future abortFiveOneBackup(FileBackupAgent* backupAgent, tr->clear(KeyRangeRef(logsPath, strinc(logsPath))); config.stateEnum().set(tr, EBackupState::STATE_ABORTED); - - return Void(); } struct AbortFiveOneBackupTask : TaskFuncBase { static StringRef name; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state FileBackupAgent backupAgent; - state BackupConfig config(task); - state std::string tagName = wait(config.tag().getOrThrow(tr)); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + FileBackupAgent backupAgent; + BackupConfig config(task); + std::string tagName = co_await config.tag().getOrThrow(tr); TraceEvent(SevInfo, "FileBackupCancelFiveOneTask") .detail("Task", task->params[Task::reservedTaskParamKeyType]) .detail("TagName", tagName); - wait(abortFiveOneBackup(&backupAgent, tr, tagName)); + co_await abortFiveOneBackup(&backupAgent, tr, tagName); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); } StringRef getName() const override { @@ -1898,41 +2057,41 @@ REGISTER_TASKFUNC_ALIAS(AbortFiveOneBackupTask, file_backup_write_snapshot_manif REGISTER_TASKFUNC_ALIAS(AbortFiveOneBackupTask, file_backup_start); std::function)> NOP_SETUP_TASK_FN = [](Reference task) { /* NOP */ }; -ACTOR static Future addBackupTask(StringRef name, - uint32_t version, - Reference tr, - Reference taskBucket, - TaskCompletionKey completionKey, - BackupConfig config, - Reference waitFor = Reference(), - std::function)> setupTaskFn = NOP_SETUP_TASK_FN, - int priority = 0, - SetValidation setValidation = SetValidation::True) { +static Future addBackupTask(StringRef name, + uint32_t version, + Reference tr, + Reference taskBucket, + TaskCompletionKey completionKey, + BackupConfig config, + Reference waitFor = Reference(), + std::function)> setupTaskFn = NOP_SETUP_TASK_FN, + int priority = 0, + SetValidation setValidation = SetValidation::True) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task(new Task(name, version, doneKey, priority)); + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(name, version, doneKey, priority)); // Bind backup config to new task // allow this new task to find the config(keyspace) of the parent task - wait(config.toTask(tr, task, setValidation)); + co_await config.toTask(tr, task, setValidation); // Set task specific params setupTaskFn(task); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); + co_await waitFor->onSetAddTask(tr, taskBucket, task); - return "OnSetAddTask"_sr; + co_return "OnSetAddTask"_sr; } // Clears the backup ID from "backupStartedKey" to pause backup workers. -ACTOR static Future clearBackupStartID(Reference tr, UID backupUid) { +static Future clearBackupStartID(Reference tr, UID backupUid) { // If backup worker is not enabled, exit early. - Optional started = wait(tr->get(backupStartedKey)); + Optional started = co_await tr->get(backupStartedKey); std::vector> ids; if (started.present()) { ids = decodeBackupStartedValue(started.get()); @@ -1949,7 +2108,6 @@ ACTOR static Future clearBackupStartID(Referenceset(backupStartedKey, encodeBackupStartedValue(ids)); } - return Void(); } // Backup and Restore taskFunc definitions will inherit from one of the following classes which @@ -1974,23 +2132,23 @@ struct BackupTaskFuncBase : TaskFuncBase { virtual std::string toString(Reference task) const { return ""; } }; -ACTOR static Future>> getBlockOfShards(Reference tr, - Key beginKey, - Key endKey, - int limit) { +static Future>> getBlockOfShards(Reference tr, + Key beginKey, + Key endKey, + int limit) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Standalone> results; - RangeResult values = wait(tr->getRange( - KeyRangeRef(keyAfter(beginKey.withPrefix(keyServersPrefix)), endKey.withPrefix(keyServersPrefix)), limit)); + Standalone> results; + RangeResult values = co_await tr->getRange( + KeyRangeRef(keyAfter(beginKey.withPrefix(keyServersPrefix)), endKey.withPrefix(keyServersPrefix)), limit); for (auto& s : values) { KeyRef k = s.key.removePrefix(keyServersPrefix); results.push_back_deep(results.arena(), k); } - return results; + co_return results; } struct BackupRangeTaskFunc : BackupTaskFuncBase { @@ -2032,28 +2190,38 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { // - update the task begin key // - save/extend the task with the new params // Returns whether or not the caller should continue executing the task. - ACTOR static Future finishRangeFile(Reference file, - Database cx, - Reference task, - Reference taskBucket, - KeyRange range, - Version version) { - wait(file->finish()); + static Future finishRangeFile(Reference file, + Database cx, + Reference task, + Reference taskBucket, + KeyRange range, + Version version) { + co_await file->finish(); + + TraceEvent("BackupRangeFileFinished") + .detail("BackupUID", BackupConfig(task).getUid()) + .detail("FileName", file->getFileName()) + .detail("FileSize", file->size()) + .detail("RangeBegin", range.begin.printable()) + .detail("RangeEnd", range.end.printable()) + .detail("RangeEmpty", range.empty()) + .detail("Version", version); // Ignore empty ranges. if (range.empty()) - return false; + co_return false; - state Reference tr(new ReadYourWritesTransaction(cx)); - state BackupConfig backup(task); - state bool usedFile = false; + Reference tr(new ReadYourWritesTransaction(cx)); + BackupConfig backup(task); + bool usedFile = false; // Avoid unnecessary conflict by prevent taskbucket's automatic timeout extension // because the following transaction loop extends and updates the task. - wait(task->extendMutex.take()); - state FlowLock::Releaser releaser(task->extendMutex, 1); + co_await task->extendMutex.take(); + FlowLock::Releaser releaser(task->extendMutex, 1); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); @@ -2063,41 +2231,42 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { Params.beginKey().set(task, range.end); // Save and extend the task with the new begin parameter - state Version newTimeout = wait(taskBucket->extendTimeout(tr, task, UpdateParams::True)); + Version newTimeout = co_await taskBucket->extendTimeout(tr, task, UpdateParams::True); // Update the range bytes written in the backup config backup.rangeBytesWritten().atomicOp(tr, file->size(), MutationRef::AddValue); backup.snapshotRangeFileCount().atomicOp(tr, 1, MutationRef::AddValue); // See if there is already a file for this key which has an earlier begin, update the map if not. - Optional s = wait(backup.snapshotRangeFileMap().get(tr, range.end)); + Optional s = co_await backup.snapshotRangeFileMap().get(tr, range.end); if (!s.present() || s.get().begin >= range.begin) { backup.snapshotRangeFileMap().set( tr, range.end, { range.begin, version, file->getFileName(), file->size() }); usedFile = true; } - wait(tr->commit()); + co_await tr->commit(); task->timeoutVersion = newTimeout; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return usedFile; + co_return usedFile; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - int priority, - Key begin, - Key end, - TaskCompletionKey completionKey, - Reference waitFor = Reference(), - Version scheduledVersion = invalidVersion) { - Key key = wait(addBackupTask( + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + int priority, + Key begin, + Key end, + TaskCompletionKey completionKey, + Reference waitFor = Reference(), + Version scheduledVersion = invalidVersion) { + Key key = co_await addBackupTask( BackupRangeTaskFunc::name, BackupRangeTaskFunc::version, tr, @@ -2112,20 +2281,20 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { if (scheduledVersion != invalidVersion) ReservedTaskParams::scheduledVersion().set(task, scheduledVersion); }, - priority)); - return key; + priority); + co_return key; } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - wait(checkTaskVersion(cx, task, BackupRangeTaskFunc::name, BackupRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, BackupRangeTaskFunc::name, BackupRangeTaskFunc::version); - state Key beginKey = Params.beginKey().get(task); - state Key endKey = Params.endKey().get(task); + Key beginKey = Params.beginKey().get(task); + Key endKey = Params.endKey().get(task); TraceEvent("FileBackupRangeStart") .suppressFor(60) @@ -2137,54 +2306,55 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { // When a key range task saves the last chunk of progress and then the executor dies, when the task // continues its beginKey and endKey will be equal but there is no work to be done. if (beginKey == endKey) - return Void(); + co_return; // Find out if there is a shard boundary in(beginKey, endKey) - Standalone> keys = wait(runRYWTransaction( - cx, [=](Reference tr) { return getBlockOfShards(tr, beginKey, endKey, 1); })); - if (keys.size() > 0) { + Standalone> keys = co_await runRYWTransaction( + cx, [=](Reference tr) { return getBlockOfShards(tr, beginKey, endKey, 1); }); + if (!keys.empty()) { Params.addBackupRangeTasks().set(task, true); - return Void(); + co_return; } // Read everything from beginKey to endKey, write it to an output file, run the output file processor, and // then set on_done. If we are still writing after X seconds, end the output file and insert a new // backup_range task for the remainder. - state Reference outFile; - state Version outVersion = invalidVersion; - state Key lastKey; + Reference outFile; + Version outVersion = invalidVersion; + Key lastKey; // retrieve kvData - state PromiseStream results; - - state Future rc = readCommitted(cx, - results, - lock, - KeyRangeRef(beginKey, endKey), - Terminator::True, - AccessSystemKeys::True, - LockAware::True, - ReadLowPriority(CLIENT_KNOBS->BACKUP_READS_USE_LOW_PRIORITY)); - state std::unique_ptr rangeFile; - state BackupConfig backup(task); - state Arena arena; - - DatabaseConfiguration config = wait(getDatabaseConfiguration(cx)); + PromiseStream results; + + Future rc = readCommitted(cx, + results, + lock, + KeyRangeRef(beginKey, endKey), + Terminator::True, + AccessSystemKeys::True, + LockAware::True, + ReadLowPriority(CLIENT_KNOBS->BACKUP_READS_USE_LOW_PRIORITY)); + std::unique_ptr rangeFile; + BackupConfig backup(task); + Arena arena; + + DatabaseConfiguration config = co_await getDatabaseConfiguration(cx); // Don't need to check keepRunning(task) here because we will do that while finishing each output file, but // if bc is false then clearly the backup is no longer in progress - Reference _bc = wait(backup.backupContainer().getD(cx.getReference())); + Reference _bc = co_await backup.backupContainer().getD(cx.getReference()); if (!_bc) { - return Void(); + co_return; } - state Reference bc = getBackupContainerWithProxy(_bc); - state bool done = false; - state int64_t nrKeys = 0; - loop { - state RangeResultWithVersion values; + Reference bc = getBackupContainerWithProxy(_bc); + bool done = false; + int64_t nrKeys = 0; + + while (true) { + RangeResultWithVersion values; try { - RangeResultWithVersion _values = waitNext(results.getFuture()); + RangeResultWithVersion _values = co_await results.getFuture(); values = _values; lock->release(values.first.expectedSize()); } catch (Error& e) { @@ -2199,17 +2369,17 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { if (values.second != outVersion || done) { if (outFile) { CODE_PROBE(outVersion != invalidVersion, "Backup range task wrote multiple versions"); - state Key nextKey = done ? endKey : keyAfter(lastKey); - wait(rangeFile->writeKey(nextKey)); + Key nextKey = done ? endKey : keyAfter(lastKey); + co_await rangeFile->writeKey(nextKey); if (BUGGIFY) { - wait(rangeFile->padEnd(true)); + co_await rangeFile->padEnd(true); } - wait(rangeFile->finish()); + co_await rangeFile->finish(); - bool usedFile = wait( - finishRangeFile(outFile, cx, task, taskBucket, KeyRangeRef(beginKey, nextKey), outVersion)); + bool usedFile = co_await finishRangeFile( + outFile, cx, task, taskBucket, KeyRangeRef(beginKey, nextKey), outVersion); TraceEvent("FileBackupWroteRangeFile") .suppressFor(60) .detail("BackupUID", backup.getUid()) @@ -2225,48 +2395,49 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { } if (done) - return Void(); + co_return; // Start writing a new file after verifying this task should keep running as of a new read version // (which must be >= outVersion) outVersion = values.second; // block size must be at least large enough for 3 max size keys and 2 max size values + overhead so // 250k conservatively. - state int blockSize = + int blockSize = BUGGIFY ? deterministicRandom()->randomInt(250e3, 4e6) : CLIENT_KNOBS->BACKUP_RANGEFILE_BLOCK_SIZE; - state Version snapshotBeginVersion; - state int64_t snapshotRangeFileCount; + Version snapshotBeginVersion{ 0 }; + int64_t snapshotRangeFileCount{ 0 }; - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(taskBucket->keepRunning(tr, task) && - storeOrThrow(snapshotBeginVersion, backup.snapshotBeginVersion().get(tr)) && - store(snapshotRangeFileCount, backup.snapshotRangeFileCount().getD(tr))); + co_await (taskBucket->keepRunning(tr, task) && + storeOrThrow(snapshotBeginVersion, backup.snapshotBeginVersion().get(tr)) && + store(snapshotRangeFileCount, backup.snapshotRangeFileCount().getD(tr))); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } Reference f = - wait(bc->writeRangeFile(snapshotBeginVersion, snapshotRangeFileCount, outVersion, blockSize)); + co_await bc->writeRangeFile(snapshotBeginVersion, snapshotRangeFileCount, outVersion, blockSize); outFile = f; // Initialize range file writer and write begin key rangeFile = std::make_unique(outFile, blockSize); - wait(rangeFile->writeKey(beginKey)); + co_await rangeFile->writeKey(beginKey); } // write kvData to file, update lastKey and key count - if (values.first.size() != 0) { - state size_t i = 0; - for (; i < values.first.size(); ++i) { - wait(rangeFile->writeKV(values.first[i].key, values.first[i].value)); + if (!values.first.empty()) { + for (size_t i = 0; i < values.first.size(); ++i) { + co_await rangeFile->writeKV(values.first[i].key, values.first[i].value); } lastKey = values.first.back().key; nrKeys += values.first.size(); @@ -2274,70 +2445,65 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { } } - ACTOR static Future startBackupRangeInternal(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task, - Reference onDone) { + static Future startBackupRangeInternal(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task, + Reference onDone) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Key nextKey = Params.beginKey().get(task); - state Key endKey = Params.endKey().get(task); + Key nextKey = Params.beginKey().get(task); + Key endKey = Params.endKey().get(task); - state Standalone> keys = - wait(getBlockOfShards(tr, nextKey, endKey, CLIENT_KNOBS->BACKUP_SHARD_TASK_LIMIT)); + Standalone> keys = + co_await getBlockOfShards(tr, nextKey, endKey, CLIENT_KNOBS->BACKUP_SHARD_TASK_LIMIT); std::vector> addTaskVector; - for (int idx = 0; idx < keys.size(); ++idx) { - if (nextKey != keys[idx]) { - addTaskVector.push_back(addTask(tr, - taskBucket, - task, - task->getPriority(), - nextKey, - keys[idx], - TaskCompletionKey::joinWith(onDone))); + for (const auto& splitKey : keys) { + if (nextKey != splitKey) { + addTaskVector.push_back(addTask( + tr, taskBucket, task, task->getPriority(), nextKey, splitKey, TaskCompletionKey::joinWith(onDone))); TraceEvent("FileBackupRangeSplit") .suppressFor(60) .detail("BackupUID", BackupConfig(task).getUid()) .detail("BeginKey", Params.beginKey().get(task).printable()) .detail("EndKey", Params.endKey().get(task).printable()) .detail("SliceBeginKey", nextKey.printable()) - .detail("SliceEndKey", keys[idx].printable()); + .detail("SliceEndKey", splitKey.printable()); } - nextKey = keys[idx]; + nextKey = splitKey; } - wait(waitForAll(addTaskVector)); + co_await waitForAll(addTaskVector); if (nextKey != endKey) { // Add task to cover nextKey to the end, using the priority of the current task - wait(success(addTask(tr, - taskBucket, - task, - task->getPriority(), - nextKey, - endKey, - TaskCompletionKey::joinWith(onDone), - Reference(), - task->getPriority()))); + co_await addTask(tr, + taskBucket, + task, + task->getPriority(), + nextKey, + endKey, + TaskCompletionKey::joinWith(onDone), + Reference(), + task->getPriority()); } - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); if (Params.addBackupRangeTasks().get(task)) { - wait(startBackupRangeInternal(tr, taskBucket, futureBucket, task, taskFuture)); + co_await startBackupRangeInternal(tr, taskBucket, futureBucket, task, taskFuture); } else { - wait(taskFuture->set(tr, taskBucket)); + co_await taskFuture->set(tr, taskBucket); } - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); TraceEvent("FileBackupRangeFinish") .suppressFor(60) @@ -2346,7 +2512,7 @@ struct BackupRangeTaskFunc : BackupTaskFuncBase { .detail("EndKey", Params.endKey().get(task).printable()) .detail("TaskKey", task->key.printable()); - return Void(); + co_return; } }; StringRef BackupRangeTaskFunc::name = "file_backup_write_range_5.2"_sr; @@ -2380,14 +2546,14 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - int priority, - TaskCompletionKey completionKey, - Reference waitFor = Reference(), - Version scheduledVersion = invalidVersion) { - Key key = wait(addBackupTask( + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + int priority, + TaskCompletionKey completionKey, + Reference waitFor = Reference(), + Version scheduledVersion = invalidVersion) { + Key key = co_await addBackupTask( name, version, tr, @@ -2399,40 +2565,41 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { if (scheduledVersion != invalidVersion) ReservedTaskParams::scheduledVersion().set(task, scheduledVersion); }, - priority)); - return key; + priority); + co_return key; } enum DispatchState { SKIP = 0, DONE = 1, NOT_DONE_MIN = 2 }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - wait(checkTaskVersion(cx, task, name, version)); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + co_await checkTaskVersion(cx, task, name, version); - state double startTime = timer(); - state Reference tr(new ReadYourWritesTransaction(cx)); + double startTime = timer(); + Reference tr(new ReadYourWritesTransaction(cx)); // The shard map will use 3 values classes. Exactly SKIP, exactly DONE, then any number >= NOT_DONE_MIN // which will mean not done. This is to enable an efficient coalesce() call to squash adjacent ranges which // are not yet finished to enable efficiently finding random database shards which are not done. - state int notDoneSequence = NOT_DONE_MIN; - state KeyRangeMap shardMap(notDoneSequence++); - state Key beginKey = allKeys.begin; + int notDoneSequence = NOT_DONE_MIN; + KeyRangeMap shardMap(notDoneSequence++); + Key beginKey = allKeys.begin; // Read all shard boundaries and add them to the map - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future>> shardBoundaries = + Future>> shardBoundaries = getBlockOfShards(tr, beginKey, allKeys.end, CLIENT_KNOBS->TOO_MANY); - wait(success(shardBoundaries) && taskBucket->keepRunning(tr, task)); + co_await (success(shardBoundaries) && taskBucket->keepRunning(tr, task)); - if (shardBoundaries.get().size() == 0) + if (shardBoundaries.get().empty()) break; for (auto& boundary : shardBoundaries.get()) { @@ -2441,38 +2608,41 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { beginKey = keyAfter(shardBoundaries.get().back()); tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // Read required stuff from backup config - state BackupConfig config(task); - state Version recentReadVersion; - state Version snapshotBeginVersion; - state Version snapshotTargetEndVersion; - state int64_t snapshotIntervalSeconds; - state Optional latestSnapshotEndVersion; - state std::vector backupRanges; - state Optional snapshotBatchFutureKey; - state Reference snapshotBatchFuture; - state Optional snapshotBatchSize; + BackupConfig config(task); + Version recentReadVersion{ 0 }; + Version snapshotBeginVersion{ 0 }; + Version snapshotTargetEndVersion{ 0 }; + int64_t snapshotIntervalSeconds{ 0 }; + Optional latestSnapshotEndVersion; + std::vector backupRanges; + Optional snapshotBatchFutureKey; + Reference snapshotBatchFuture; + Optional snapshotBatchSize; tr->reset(); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(store(snapshotBeginVersion, config.snapshotBeginVersion().getOrThrow(tr)) && - store(snapshotTargetEndVersion, config.snapshotTargetEndVersion().getOrThrow(tr)) && - store(backupRanges, config.backupRanges().getOrThrow(tr)) && - store(snapshotIntervalSeconds, config.snapshotIntervalSeconds().getOrThrow(tr)) - // The next two parameters are optional - && store(snapshotBatchFutureKey, config.snapshotBatchFuture().get(tr)) && - store(snapshotBatchSize, config.snapshotBatchSize().get(tr)) && - store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && - store(recentReadVersion, tr->getReadVersion()) && taskBucket->keepRunning(tr, task)); + co_await (store(snapshotBeginVersion, config.snapshotBeginVersion().getOrThrow(tr)) && + store(snapshotTargetEndVersion, config.snapshotTargetEndVersion().getOrThrow(tr)) && + store(backupRanges, config.backupRanges().getOrThrow(tr)) && + store(snapshotIntervalSeconds, config.snapshotIntervalSeconds().getOrThrow(tr)) + // The next two parameters are optional + && store(snapshotBatchFutureKey, config.snapshotBatchFuture().get(tr)) && + store(snapshotBatchSize, config.snapshotBatchSize().get(tr)) && + store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && + store(recentReadVersion, tr->getReadVersion()) && taskBucket->keepRunning(tr, task)); // If the snapshot batch future key does not exist, this is the first execution of this dispatch // task so @@ -2489,16 +2659,16 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { // The dispatch of this batch can take multiple separate executions if the executor fails // so store a completion key for the dispatch finish() to set when dispatching the batch is // done. - state TaskCompletionKey dispatchCompletionKey = TaskCompletionKey::joinWith(snapshotBatchFuture); + TaskCompletionKey dispatchCompletionKey = TaskCompletionKey::joinWith(snapshotBatchFuture); // this is a bad hack - but flow doesn't work well with lambda functions and capturing // state variables... auto cfg = &config; auto tx = &tr; - wait(map(dispatchCompletionKey.get(tr, taskBucket), [cfg, tx](Key const& k) { + co_await map(dispatchCompletionKey.get(tr, taskBucket), [cfg, tx](Key const& k) { cfg->snapshotBatchDispatchDoneKey().set(*tx, k); return Void(); - })); - wait(tr->commit()); + }); + co_await tr->commit(); } else { ASSERT(snapshotBatchSize.present()); // Batch future key exists in the config so create future from it @@ -2507,24 +2677,26 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // Read all dispatched ranges - state std::vector> dispatchBoundaries; + std::vector> dispatchBoundaries; tr->reset(); beginKey = allKeys.begin; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future bounds = + Future bounds = config.snapshotRangeDispatchMap().getRange( tr, beginKey, keyAfter(allKeys.end), CLIENT_KNOBS->TOO_MANY); - wait(success(bounds) && taskBucket->keepRunning(tr, task) && - store(recentReadVersion, tr->getReadVersion())); + co_await (success(bounds) && taskBucket->keepRunning(tr, task) && + store(recentReadVersion, tr->getReadVersion())); if (!bounds.get().results.empty()) { dispatchBoundaries.reserve(dispatchBoundaries.size() + bounds.get().results.size()); @@ -2532,29 +2704,32 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { dispatchBoundaries.end(), bounds.get().results.begin(), bounds.get().results.end()); } - if (!bounds.get().more) + if (!bounds.get().more) { break; + } beginKey = keyAfter(bounds.get().results.back().first); tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // The next few sections involve combining the results above. Yields are used after operations // that could have operated on many thousands of things and in loops which could have many // thousands of iterations. // Declare some common iterators which must be state vars and will be used multiple times. - state int i; - state RangeMap::iterator iShard; - state RangeMap::iterator iShardEnd; + int i{ 0 }; + RangeMap::iterator iShard; + RangeMap::iterator iShardEnd; // Set anything inside a dispatched range to DONE. // Also ensure that the boundary value are true, false, [true, false]... - if (dispatchBoundaries.size() > 0) { - state bool lastValue = false; - state Key lastKey; + if (!dispatchBoundaries.empty()) { + bool lastValue = false; + Key lastKey; for (i = 0; i < dispatchBoundaries.size(); ++i) { const std::pair& boundary = dispatchBoundaries[i]; @@ -2571,34 +2746,34 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { iShardEnd = shardRanges.end(); for (; iShard != iShardEnd; ++iShard) { iShard->value() = DONE; - wait(yield()); + co_await yield(); } } lastValue = dispatchBoundaries[i].second; lastKey = dispatchBoundaries[i].first; - wait(yield()); + co_await yield(); } ASSERT(lastValue == false); } // Set anything outside the backup ranges to SKIP. We can use insert() here instead of modify() // because it's OK to delete shard boundaries in the skipped ranges. - if (backupRanges.size() > 0) { + if (!backupRanges.empty()) { shardMap.insert(KeyRangeRef(allKeys.begin, backupRanges.front().begin), SKIP); - wait(yield()); + co_await yield(); for (i = 0; i < backupRanges.size() - 1; ++i) { shardMap.insert(KeyRangeRef(backupRanges[i].end, backupRanges[i + 1].begin), SKIP); - wait(yield()); + co_await yield(); } shardMap.insert(KeyRangeRef(backupRanges.back().end, allKeys.end), SKIP); - wait(yield()); + co_await yield(); } - state int countShardsDone = 0; - state int countShardsNotDone = 0; + int countShardsDone = 0; + int countShardsNotDone = 0; // Scan through the shard map, counting the DONE and NOT_DONE shards. RangeMap::Ranges shardRanges = shardMap.ranges(); @@ -2610,15 +2785,26 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { } else if (iShard->value() >= NOT_DONE_MIN) ++countShardsNotDone; - wait(yield()); + co_await yield(); } // Coalesce the shard map to make random selection below more efficient. shardMap.coalesce(allKeys); - wait(yield()); + co_await yield(); // In this context "all" refers to all of the shards relevant for this particular backup - state int countAllShards = countShardsDone + countShardsNotDone; + int countAllShards = countShardsDone + countShardsNotDone; + + // Log backup ranges and shard counts for debugging mode=BOTH issues + TraceEvent("FileBackupSnapshotDispatchShardCount") + .detail("BackupUID", config.getUid()) + .detail("BackupRangesCount", backupRanges.size()) + .detail("FirstRangeBegin", backupRanges.empty() ? ""_sr : backupRanges.front().begin.printable()) + .detail("FirstRangeEnd", backupRanges.empty() ? ""_sr : backupRanges.front().end.printable()) + .detail("CountAllShards", countAllShards) + .detail("CountShardsDone", countShardsDone) + .detail("CountShardsNotDone", countShardsNotDone) + .detail("LatestSnapshotEndVersion", latestSnapshotEndVersion.orDefault(-1)); // NOTE: Don't finish here even if countShardsNotDone == 0. We need to dispatch tasks first. // The completion check after dispatch (with dispatchedInThisIteration guard) prevents @@ -2630,7 +2816,7 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { } // Decide when the next snapshot dispatch should run. - state Version nextDispatchVersion; + Version nextDispatchVersion{ 0 }; // In simulation, use snapshot interval / 5 to ensure multiple dispatches run // Otherwise, use the knob for the number of seconds between snapshot dispatch tasks. @@ -2660,8 +2846,8 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { else timeElapsed = 1.0; - state int countExpectedShardsDone = countAllShards * timeElapsed; - state int countShardsToDispatch = std::max(0, countExpectedShardsDone - countShardsDone); + int countExpectedShardsDone = countAllShards * timeElapsed; + int countShardsToDispatch = std::max(0, countExpectedShardsDone - countShardsDone); // Calculate the number of shards that would have been dispatched by a normal (on-schedule) // BackupSnapshotDispatchTask given the dispatch window and the start and expected-end versions of the @@ -2702,12 +2888,12 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { .detail("SnapshotIntervalSeconds", snapshotIntervalSeconds); // Track whether we dispatched any tasks in this iteration - state bool dispatchedInThisIteration = false; + bool dispatchedInThisIteration = false; // Dispatch random shards to catch up to the expected progress while (countShardsToDispatch > 0) { // First select ranges to add - state std::vector rangesToAdd; + std::vector rangesToAdd; // Limit number of tasks added per transaction int taskBatchSize = BUGGIFY ? deterministicRandom()->randomInt(1, countShardsToDispatch + 1) @@ -2735,12 +2921,13 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { } } - state int64_t oldBatchSize = snapshotBatchSize.get(); - state int64_t newBatchSize = oldBatchSize + rangesToAdd.size(); + int64_t oldBatchSize = snapshotBatchSize.get(); + int64_t newBatchSize = oldBatchSize + rangesToAdd.size(); // Now add the selected ranges in a single transaction. tr->reset(); - loop { + while (true) { + Error err; try { TraceEvent("FileBackupSnapshotDispatchAddingTasks") .suppressFor(2) @@ -2751,16 +2938,16 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { tr->setOption(FDBTransactionOptions::LOCK_AWARE); // For each range, make sure it isn't set in the dispatched range map. - state std::vector>> beginReads; - state std::vector>> endReads; + std::vector>> beginReads; + std::vector>> endReads; for (auto& range : rangesToAdd) { beginReads.push_back(config.snapshotRangeDispatchMap().get(tr, range.begin)); endReads.push_back(config.snapshotRangeDispatchMap().get(tr, range.end)); } - wait(store(snapshotBatchSize.get(), config.snapshotBatchSize().getOrThrow(tr)) && - waitForAll(beginReads) && waitForAll(endReads) && taskBucket->keepRunning(tr, task)); + co_await (store(snapshotBatchSize.get(), config.snapshotBatchSize().getOrThrow(tr)) && + waitForAll(beginReads) && waitForAll(endReads) && taskBucket->keepRunning(tr, task)); // Snapshot batch size should be either oldBatchSize or newBatchSize. If new, this transaction // is already done. @@ -2774,7 +2961,7 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { config.snapshotDispatchLastVersion().set(tr, tr->getReadVersion().get()); } - state std::vector> addTaskFutures; + std::vector> addTaskFutures; for (i = 0; i < beginReads.size(); ++i) { KeyRange& range = rangesToAdd[i]; @@ -2834,13 +3021,14 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { } } - wait(waitForAll(addTaskFutures)); - wait(tr->commit()); + co_await waitForAll(addTaskFutures); + co_await tr->commit(); dispatchedInThisIteration = true; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } @@ -2861,7 +3049,7 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { Params.snapshotFinished().set(task, true); } - return Void(); + co_return; } // This function is just a wrapper for BackupSnapshotManifest::addTask() which is defined below. @@ -2874,22 +3062,21 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { TaskCompletionKey completionKey, Reference waitFor = Reference()); - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state BackupConfig config(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + BackupConfig config(task); // Get the batch future and dispatch done keys, then clear them. - state Key snapshotBatchFutureKey; - state Key snapshotBatchDispatchDoneKey; + Key snapshotBatchFutureKey; + Key snapshotBatchDispatchDoneKey; - wait(store(snapshotBatchFutureKey, config.snapshotBatchFuture().getOrThrow(tr)) && - store(snapshotBatchDispatchDoneKey, config.snapshotBatchDispatchDoneKey().getOrThrow(tr))); + co_await (store(snapshotBatchFutureKey, config.snapshotBatchFuture().getOrThrow(tr)) && + store(snapshotBatchDispatchDoneKey, config.snapshotBatchDispatchDoneKey().getOrThrow(tr))); - state Reference snapshotBatchFuture = futureBucket->unpack(snapshotBatchFutureKey); - state Reference snapshotBatchDispatchDoneFuture = - futureBucket->unpack(snapshotBatchDispatchDoneKey); + Reference snapshotBatchFuture = futureBucket->unpack(snapshotBatchFutureKey); + Reference snapshotBatchDispatchDoneFuture = futureBucket->unpack(snapshotBatchDispatchDoneKey); config.snapshotBatchFuture().clear(tr); config.snapshotBatchDispatchDoneKey().clear(tr); config.snapshotBatchSize().clear(tr); @@ -2898,30 +3085,38 @@ struct BackupSnapshotDispatchTask : BackupTaskFuncBase { config.snapshotDispatchLastShardsBehind().set(tr, Params.shardsBehind().getOrDefault(task, 0)); config.snapshotDispatchLastVersion().set(tr, tr->getReadVersion().get()); - state Reference snapshotFinishedFuture = task->getDoneFuture(futureBucket); + Reference snapshotFinishedFuture = task->getDoneFuture(futureBucket); + + bool snapshotFinished = Params.snapshotFinished().getOrDefault(task, false); + TraceEvent("FileBackupSnapshotDispatchFinish") + .detail("BackupUID", config.getUid()) + .detail("SnapshotFinished", snapshotFinished) + .detail("ShardsBehind", Params.shardsBehind().getOrDefault(task, 0)) + .detail("NextDispatchVersion", Params.nextDispatchVersion().getOrDefault(task, -1)); // If the snapshot is finished, the next task is to write a snapshot manifest, otherwise it's another // snapshot dispatch task. In either case, the task should wait for snapshotBatchFuture. The snapshot done // key, passed to the current task, is also passed on. - if (Params.snapshotFinished().getOrDefault(task, false)) { - wait(success(addSnapshotManifestTask( - tr, taskBucket, task, TaskCompletionKey::signal(snapshotFinishedFuture), snapshotBatchFuture))); + if (snapshotFinished) { + TraceEvent("FileBackupSnapshotDispatchAddingManifestTask").detail("BackupUID", config.getUid()); + co_await addSnapshotManifestTask( + tr, taskBucket, task, TaskCompletionKey::signal(snapshotFinishedFuture), snapshotBatchFuture); } else { - wait(success(addTask(tr, - taskBucket, - task, - 1, - TaskCompletionKey::signal(snapshotFinishedFuture), - snapshotBatchFuture, - Params.nextDispatchVersion().get(task)))); + co_await addTask(tr, + taskBucket, + task, + 1, + TaskCompletionKey::signal(snapshotFinishedFuture), + snapshotBatchFuture, + Params.nextDispatchVersion().get(task)); } // This snapshot batch is finished, so set the batch done future. - wait(snapshotBatchDispatchDoneFuture->set(tr, taskBucket)); + co_await snapshotBatchDispatchDoneFuture->set(tr, taskBucket); - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); - return Void(); + co_return; } }; StringRef BackupSnapshotDispatchTask::name = "file_backup_dispatch_ranges_5.2"_sr; @@ -2953,71 +3148,77 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference lock(new FlowLock(CLIENT_KNOBS->BACKUP_LOCK_BYTES)); - wait(checkTaskVersion(cx, task, BackupLogRangeTaskFunc::name, BackupLogRangeTaskFunc::version)); + co_await checkTaskVersion(cx, task, BackupLogRangeTaskFunc::name, BackupLogRangeTaskFunc::version); - state Version beginVersion = Params.beginVersion().get(task); - state Version endVersion = Params.endVersion().get(task); + Version beginVersion = Params.beginVersion().get(task); + Version endVersion = Params.endVersion().get(task); - state BackupConfig config(task); - state Reference bc; + BackupConfig config(task); + Reference bc; - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); // Wait for the read version to pass endVersion - try { - wait(taskBucket->keepRunning(tr, task)); + { + Error err; + try { + co_await taskBucket->keepRunning(tr, task); - if (!bc) { - // Backup container must be present if we're still here - Reference _bc = wait(config.backupContainer().getOrThrow(tr)); - bc = getBackupContainerWithProxy(_bc); - } + if (!bc) { + // Backup container must be present if we're still here + Reference _bc = co_await config.backupContainer().getOrThrow(tr); + bc = getBackupContainerWithProxy(_bc); + } - Version currentVersion = tr->getReadVersion().get(); - if (endVersion < currentVersion) - break; + Version currentVersion = tr->getReadVersion().get(); + if (endVersion < currentVersion) + break; - wait(delay(std::max(CLIENT_KNOBS->BACKUP_RANGE_MINWAIT, - (double)(endVersion - currentVersion) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND))); - tr->reset(); - } catch (Error& e) { - wait(tr->onError(e)); + co_await delay( + std::max(CLIENT_KNOBS->BACKUP_RANGE_MINWAIT, + (double)(endVersion - currentVersion) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND)); + tr->reset(); + continue; + } catch (Error& e) { + err = e; + } + co_await tr->onError(err); } } - Key destUidValue = wait(config.destUidValue().getOrThrow(tr)); + Key destUidValue = co_await config.destUidValue().getOrThrow(tr); // Get the set of key ranges that hold mutations for (beginVersion, endVersion). They will be queried in // parallel below and there is a limit on how many we want to process in a single BackupLogRangeTask so if // that limit is exceeded then set the addBackupLogRangeTasks boolean in Params and stop, signalling the // finish() step to break up the (beginVersion, endVersion) range into smaller intervals which are then // processed by individual BackupLogRangeTasks. - state Standalone> ranges = getLogRanges(beginVersion, endVersion, destUidValue); + Standalone> ranges = getLogRanges(beginVersion, endVersion, destUidValue); if (ranges.size() > CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES) { Params.addBackupLogRangeTasks().set(task, true); - return Void(); + co_return; } // Block size must be at least large enough for 1 max size key, 1 max size value, and overhead, so // conservatively 125k. - state int blockSize = + int blockSize = BUGGIFY ? deterministicRandom()->randomInt(125e3, 4e6) : CLIENT_KNOBS->BACKUP_LOGFILE_BLOCK_SIZE; - state Reference outFile = wait(bc->writeLogFile(beginVersion, endVersion, blockSize)); - state LogFileWriter logFile(outFile, blockSize); + Reference outFile = co_await bc->writeLogFile(beginVersion, endVersion, blockSize); + LogFileWriter logFile(outFile, blockSize); // Query all key ranges covering (beginVersion, endVersion) in parallel, writing their results to the // results promise stream as they are received. Note that this means the records read from the results // stream are not likely to be in increasing Version order. - state PromiseStream results; - state std::vector> rc; + PromiseStream results; + std::vector> rc; for (auto& range : ranges) { rc.push_back(readCommitted(cx, @@ -3030,7 +3231,7 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { ReadLowPriority(CLIENT_KNOBS->BACKUP_READS_USE_LOW_PRIORITY))); } - state Future sendEOS = map(errorOr(waitForAll(rc)), [=](ErrorOr const& result) mutable { + Future sendEOS = map(errorOr(waitForAll(rc)), [=](ErrorOr const& result) mutable { if (result.isError()) results.sendError(result.getError()); else @@ -3038,34 +3239,42 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { return Void(); }); - state Version lastVersion; - try { - loop { - state RangeResultWithVersion r = waitNext(results.getFuture()); - lock->release(r.first.expectedSize()); - - state int i = 0; - for (; i < r.first.size(); ++i) { - // Remove the backupLogPrefix + UID bytes from the key - wait(logFile.writeKV(r.first[i].key.substr(backupLogPrefixBytes + 16), r.first[i].value)); - lastVersion = r.second; + Version lastVersion{ 0 }; + { + Error caughtErr; + bool hasCaughtErr = false; + try { + while (true) { + RangeResultWithVersion r = co_await results.getFuture(); + lock->release(r.first.expectedSize()); + + for (int i = 0; i < r.first.size(); ++i) { + // Remove the backupLogPrefix + UID bytes from the key + co_await logFile.writeKV(r.first[i].key.substr(backupLogPrefixBytes + 16), r.first[i].value); + lastVersion = r.second; + } } + } catch (Error& e) { + caughtErr = e; + hasCaughtErr = true; } - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) - throw; - - if (e.code() != error_code_end_of_stream) { - state Error err = e; - wait(config.logError(cx, err, format("Failed to write to file `%s'", outFile->getFileName().c_str()))); - throw err; + if (hasCaughtErr) { + if (caughtErr.code() == error_code_actor_cancelled) + throw caughtErr; + + if (caughtErr.code() != error_code_end_of_stream) { + Error err = caughtErr; + co_await config.logError( + cx, err, format("Failed to write to file `%s'", outFile->getFileName().c_str())); + throw err; + } } } // Make sure this task is still alive, if it's not then the data read above could be incomplete. - wait(taskBucket->keepRunning(cx, task)); + co_await taskBucket->keepRunning(cx, task); - wait(outFile->finish()); + co_await outFile->finish(); TraceEvent("FileBackupWroteLogFile") .suppressFor(60) @@ -3077,18 +3286,18 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { Params.fileSize().set(task, outFile->size()); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - int priority, - Version beginVersion, - Version endVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask( + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + int priority, + Version beginVersion, + Version endVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask( BackupLogRangeTaskFunc::name, BackupLogRangeTaskFunc::version, tr, @@ -3101,17 +3310,17 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { Params.endVersion().set(task, endVersion); Params.addBackupLogRangeTasks().set(task, false); }, - priority)); - return key; + priority); + co_return key; } - ACTOR static Future startBackupLogRangeInternal(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task, - Reference taskFuture, - Version beginVersion, - Version endVersion) { + static Future startBackupLogRangeInternal(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task, + Reference taskFuture, + Version beginVersion, + Version endVersion) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); @@ -3140,32 +3349,33 @@ struct BackupLogRangeTaskFunc : BackupTaskFuncBase { tasks++; } - wait(waitForAll(addTaskVector)); + co_await waitForAll(addTaskVector); - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Version beginVersion = Params.beginVersion().get(task); - state Version endVersion = Params.endVersion().get(task); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - state BackupConfig config(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Version beginVersion = Params.beginVersion().get(task); + Version endVersion = Params.endVersion().get(task); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + BackupConfig config(task); if (Params.fileSize().exists(task)) { config.logBytesWritten().atomicOp(tr, Params.fileSize().get(task), MutationRef::AddValue); } if (Params.addBackupLogRangeTasks().get(task)) { - wait(startBackupLogRangeInternal(tr, taskBucket, futureBucket, task, taskFuture, beginVersion, endVersion)); + co_await startBackupLogRangeInternal( + tr, taskBucket, futureBucket, task, taskFuture, beginVersion, endVersion); } else { - wait(taskFuture->set(tr, taskBucket)); + co_await taskFuture->set(tr, taskBucket); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } }; @@ -3184,14 +3394,14 @@ struct EraseLogRangeTaskFunc : BackupTaskFuncBase { static TaskParam destUidValue() { return __FUNCTION__sr; } } Params; - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - UID logUid, - TaskCompletionKey completionKey, - Key destUidValue, - Version endVersion = 0, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask( + static Future addTask(Reference tr, + Reference taskBucket, + UID logUid, + TaskCompletionKey completionKey, + Key destUidValue, + Version endVersion = 0, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask( EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version, tr, @@ -3206,30 +3416,31 @@ struct EraseLogRangeTaskFunc : BackupTaskFuncBase { Params.destUidValue().set(task, destUidValue); }, 0, - SetValidation::False)); + SetValidation::False); - return key; + co_return key; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(checkTaskVersion(tr->getDatabase(), task, EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version)); + co_await checkTaskVersion(tr->getDatabase(), task, EraseLogRangeTaskFunc::name, EraseLogRangeTaskFunc::version); - state Version endVersion = Params.endVersion().get(task); - state Key destUidValue = Params.destUidValue().get(task); + Version endVersion = Params.endVersion().get(task); + Key destUidValue = Params.destUidValue().get(task); - state BackupConfig config(task); - state Key logUidValue = config.getUidAsKey(); + BackupConfig config(task); + Key logUidValue = config.getUidAsKey(); - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task) && - eraseLogData( - tr, logUidValue, destUidValue, endVersion != 0 ? Optional(endVersion) : Optional())); + co_await ( + taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task) && + eraseLogData( + tr, logUidValue, destUidValue, endVersion != 0 ? Optional(endVersion) : Optional())); - return Void(); + co_return; } Future execute(Database cx, @@ -3257,33 +3468,34 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { static TaskParam beginVersion() { return __FUNCTION__sr; } } Params; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(checkTaskVersion(tr->getDatabase(), task, BackupLogsDispatchTask::name, BackupLogsDispatchTask::version)); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await checkTaskVersion( + tr->getDatabase(), task, BackupLogsDispatchTask::name, BackupLogsDispatchTask::version); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Reference onDone = task->getDoneFuture(futureBucket); - state Version prevBeginVersion = Params.prevBeginVersion().get(task); - state Version beginVersion = Params.beginVersion().get(task); - state BackupConfig config(task); + Reference onDone = task->getDoneFuture(futureBucket); + Version prevBeginVersion = Params.prevBeginVersion().get(task); + Version beginVersion = Params.beginVersion().get(task); + BackupConfig config(task); config.latestLogEndVersion().set(tr, beginVersion); - state bool stopWhenDone; - state Optional restorableVersion; - state EBackupState backupState; - state Optional tag; - state Optional latestSnapshotEndVersion; - state Optional partitionedLog; + bool stopWhenDone{ false }; + Optional restorableVersion; + EBackupState backupState; + Optional tag; + Optional latestSnapshotEndVersion; + Optional mutationLogType; - wait(store(stopWhenDone, config.stopWhenDone().getOrThrow(tr)) && - store(restorableVersion, config.getLatestRestorableVersion(tr)) && - store(backupState, config.stateEnum().getOrThrow(tr)) && store(tag, config.tag().get(tr)) && - store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && - store(partitionedLog, config.partitionedLogEnabled().get(tr))); + co_await (store(stopWhenDone, config.stopWhenDone().getOrThrow(tr)) && + store(restorableVersion, config.getLatestRestorableVersion(tr)) && + store(backupState, config.stateEnum().getOrThrow(tr)) && store(tag, config.tag().get(tr)) && + store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && + store(mutationLogType, config.mutationLogType().get(tr))); // If restorable, update the last restorable version for this tag if (restorableVersion.present() && tag.present()) { @@ -3297,19 +3509,19 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { // If stopWhenDone is set and there is a restorable version, set the done future and do not create further // tasks. if (stopWhenDone && restorableVersion.present()) { - wait(onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (onDone->set(tr, taskBucket) && taskBucket->finish(tr, task)); TraceEvent("FileBackupLogsDispatchDone") .detail("BackupUID", config.getUid()) .detail("BeginVersion", beginVersion) .detail("RestorableVersion", restorableVersion.orDefault(-1)); - return Void(); + co_return; } - state 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 + (CLIENT_KNOBS->BACKUP_MAX_LOG_RANGES - 1) * + CLIENT_KNOBS->LOG_RANGE_BLOCK_SIZE); TraceEvent("FileBackupLogDispatch") .suppressFor(60) @@ -3317,53 +3529,53 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { .detail("EndVersion", endVersion) .detail("RestorableVersion", restorableVersion.orDefault(-1)); - state Reference logDispatchBatchFuture = futureBucket->future(tr); + Reference logDispatchBatchFuture = futureBucket->future(tr); // If a snapshot has ended for this backup then mutations are higher priority to reduce backup lag - state int priority = latestSnapshotEndVersion.present() ? 1 : 0; + int priority = latestSnapshotEndVersion.present() ? 1 : 0; - if (!partitionedLog.present() || !partitionedLog.get()) { + if (!mutationLogType.present() || mutationLogType.get() == MutationLogType::DEFAULT) { // Add the initial log range task to read/copy the mutations and the next logs dispatch task which will // run after this batch is done // read blog/ prefix and write those (param1, param2) into files - wait(success(BackupLogRangeTaskFunc::addTask(tr, - taskBucket, - task, - priority, - beginVersion, - endVersion, - TaskCompletionKey::joinWith(logDispatchBatchFuture)))); + co_await BackupLogRangeTaskFunc::addTask(tr, + taskBucket, + task, + priority, + beginVersion, + endVersion, + TaskCompletionKey::joinWith(logDispatchBatchFuture)); // issue the next key range - wait(success(BackupLogsDispatchTask::addTask(tr, - taskBucket, - task, - priority, - beginVersion, - endVersion, - TaskCompletionKey::signal(onDone), - logDispatchBatchFuture))); + co_await BackupLogsDispatchTask::addTask(tr, + taskBucket, + task, + priority, + beginVersion, + endVersion, + TaskCompletionKey::signal(onDone), + logDispatchBatchFuture); // Do not erase at the first time if (prevBeginVersion > 0) { - state Key destUidValue = wait(config.destUidValue().getOrThrow(tr)); - wait(eraseLogData(tr, config.getUidAsKey(), destUidValue, Optional(beginVersion))); + Key destUidValue = co_await config.destUidValue().getOrThrow(tr); + co_await eraseLogData(tr, config.getUidAsKey(), destUidValue, Optional(beginVersion)); } } else { // Skip mutation copy and erase backup mutations. Just check back periodically. Version scheduledVersion = tr->getReadVersion().get() + CLIENT_KNOBS->BACKUP_POLL_PROGRESS_SECONDS * CLIENT_KNOBS->VERSIONS_PER_SECOND; - wait(success(BackupLogsDispatchTask::addTask(tr, - taskBucket, - task, - 1, - beginVersion, - endVersion, - TaskCompletionKey::signal(onDone), - Reference(), - scheduledVersion))); + co_await BackupLogsDispatchTask::addTask(tr, + taskBucket, + task, + 1, + beginVersion, + endVersion, + TaskCompletionKey::signal(onDone), + Reference(), + scheduledVersion); } - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); TraceEvent("FileBackupLogsDispatchContinuing") .suppressFor(60) @@ -3371,19 +3583,19 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { .detail("BeginVersion", beginVersion) .detail("EndVersion", endVersion); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - int priority, - Version prevBeginVersion, - Version beginVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference(), - Version scheduledVersion = invalidVersion) { - Key key = wait(addBackupTask( + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + int priority, + Version prevBeginVersion, + Version beginVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference(), + Version scheduledVersion = invalidVersion) { + Key key = co_await addBackupTask( BackupLogsDispatchTask::name, BackupLogsDispatchTask::version, tr, @@ -3398,8 +3610,8 @@ struct BackupLogsDispatchTask : BackupTaskFuncBase { ReservedTaskParams::scheduledVersion().set(task, scheduledVersion); } }, - priority)); - return key; + priority); + co_return key; } StringRef getName() const override { return name; }; @@ -3426,42 +3638,43 @@ struct FileBackupFinishedTask : BackupTaskFuncBase { StringRef getName() const override { return name; }; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(checkTaskVersion(tr->getDatabase(), task, FileBackupFinishedTask::name, FileBackupFinishedTask::version)); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await checkTaskVersion( + tr->getDatabase(), task, FileBackupFinishedTask::name, FileBackupFinishedTask::version); - state BackupConfig backup(task); - state UID uid = backup.getUid(); + BackupConfig backup(task); + UID uid = backup.getUid(); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); - state Key destUidValue = wait(backup.destUidValue().getOrThrow(tr)); + Key destUidValue = co_await backup.destUidValue().getOrThrow(tr); - wait(eraseLogData(tr, backup.getUidAsKey(), destUidValue) && clearBackupStartID(tr, uid)); + co_await (eraseLogData(tr, backup.getUidAsKey(), destUidValue) && clearBackupStartID(tr, uid)); backup.stateEnum().set(tr, EBackupState::STATE_COMPLETED); - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); TraceEvent("FileBackupFinished").detail("BackupUID", uid); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask(FileBackupFinishedTask::name, - FileBackupFinishedTask::version, - tr, - taskBucket, - completionKey, - BackupConfig(parentTask), - waitFor)); - return key; + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask(FileBackupFinishedTask::name, + FileBackupFinishedTask::version, + tr, + taskBucket, + completionKey, + BackupConfig(parentTask), + waitFor); + co_return key; } Future execute(Database cx, @@ -3485,60 +3698,64 @@ struct BackupSnapshotManifest : BackupTaskFuncBase { static constexpr uint32_t version = 1; static struct { static TaskParam endVersion() { return __FUNCTION__sr; } + static TaskParam totalBytes() { return __FUNCTION__sr; } } Params; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state BackupConfig config(task); - state Reference bc; - state DatabaseConfiguration dbConfig; + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + BackupConfig config(task); + Reference bc; + DatabaseConfiguration dbConfig; - state Reference tr(new ReadYourWritesTransaction(cx)); + Reference tr(new ReadYourWritesTransaction(cx)); // Read the entire range file map into memory, then walk it backwards from its last entry to produce a list // of non overlapping key range files - state std::map localmap; - state Key startKey; - state int batchSize = BUGGIFY ? 1 : 1000000; + std::map localmap; + Key startKey; + int batchSize = BUGGIFY ? 1 : 1000000; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(taskBucket->keepRunning(tr, task)); - wait(store(dbConfig, getDatabaseConfiguration(cx))); + co_await taskBucket->keepRunning(tr, task); + dbConfig = co_await getDatabaseConfiguration(cx); if (!bc) { // Backup container must be present if we're still here - Reference _bc = wait(config.backupContainer().getOrThrow(tr)); + Reference _bc = co_await config.backupContainer().getOrThrow(tr); bc = getBackupContainerWithProxy(_bc); } - BackupConfig::RangeFileMapT::RangeResultType rangeresults = - wait(config.snapshotRangeFileMap().getRange(tr, startKey, {}, batchSize)); + co_await config.snapshotRangeFileMap().getRange(tr, startKey, {}, batchSize); for (auto& p : rangeresults.results) { localmap.insert(p); } - if (!rangeresults.more) + if (!rangeresults.more) { break; + } startKey = keyAfter(rangeresults.results.back().first); tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state std::vector files; - state std::vector> beginEndKeys; - state Version maxVer = 0; - state Version minVer = std::numeric_limits::max(); - state int64_t totalBytes = 0; + std::vector files; + std::vector> beginEndKeys; + Version maxVer = 0; + Version minVer = std::numeric_limits::max(); + int64_t totalBytes = 0; if (!localmap.empty()) { // Get iterator that points to greatest key, start there. @@ -3575,10 +3792,20 @@ struct BackupSnapshotManifest : BackupTaskFuncBase { } } + // Log what range files were found for debugging mode=BOTH issues + TraceEvent("BackupSnapshotManifestRangeFileSummary") + .detail("BackupUID", config.getUid()) + .detail("LocalMapSize", localmap.size()) + .detail("FilesFound", files.size()) + .detail("TotalBytes", totalBytes) + .detail("MinVersion", minVer == std::numeric_limits::max() ? -1 : minVer) + .detail("MaxVersion", maxVer); + Params.endVersion().set(task, maxVer); + Params.totalBytes().set(task, totalBytes); // Avoid keyRange filtering optimization for 'manifest' files - wait(bc->writeKeyspaceSnapshotFile(files, beginEndKeys, totalBytes, IncludeKeyRangeMap::True)); + co_await bc->writeKeyspaceSnapshotFile(files, beginEndKeys, totalBytes, IncludeKeyRangeMap::True); TraceEvent(SevInfo, "FileBackupWroteSnapshotManifest") .detail("BackupUID", config.getUid()) @@ -3586,37 +3813,40 @@ struct BackupSnapshotManifest : BackupTaskFuncBase { .detail("EndVersion", maxVer) .detail("TotalBytes", totalBytes); - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(checkTaskVersion(tr->getDatabase(), task, BackupSnapshotManifest::name, BackupSnapshotManifest::version)); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await checkTaskVersion( + tr->getDatabase(), task, BackupSnapshotManifest::name, BackupSnapshotManifest::version); - state BackupConfig config(task); + BackupConfig config(task); // Set the latest snapshot end version, which was set during the execute phase config.latestSnapshotEndVersion().set(tr, Params.endVersion().get(task)); - state bool stopWhenDone; - state EBackupState backupState; - state Optional restorableVersion; - state Optional firstSnapshotEndVersion; - state Optional tag; + bool stopWhenDone{ false }; + EBackupState backupState; + Optional restorableVersion; + Optional firstSnapshotEndVersion; + Optional tag; - wait(store(stopWhenDone, config.stopWhenDone().getOrThrow(tr)) && - store(backupState, config.stateEnum().getOrThrow(tr)) && - store(restorableVersion, config.getLatestRestorableVersion(tr)) && - store(firstSnapshotEndVersion, config.firstSnapshotEndVersion().get(tr)) && - store(tag, config.tag().get(tr))); + co_await (store(stopWhenDone, config.stopWhenDone().getOrThrow(tr)) && + store(backupState, config.stateEnum().getOrThrow(tr)) && + store(restorableVersion, config.getLatestRestorableVersion(tr)) && + store(firstSnapshotEndVersion, config.firstSnapshotEndVersion().get(tr)) && + store(tag, config.tag().get(tr))); // If restorable, update the last restorable version for this tag if (restorableVersion.present() && tag.present()) { FileBackupAgent().setLastRestorable(tr, StringRef(tag.get()), restorableVersion.get()); } + // Always set firstSnapshotEndVersion if not already set + // This is required for getLatestRestorableVersion() to work correctly if (!firstSnapshotEndVersion.present()) { config.firstSnapshotEndVersion().set(tr, Params.endVersion().get(task)); } @@ -3628,33 +3858,33 @@ struct BackupSnapshotManifest : BackupTaskFuncBase { // Unless we are to stop, start the next snapshot using the default interval Reference snapshotDoneFuture = task->getDoneFuture(futureBucket); if (!stopWhenDone) { - wait(config.initNewSnapshot(tr) && - success(BackupSnapshotDispatchTask::addTask( - tr, taskBucket, task, 1, TaskCompletionKey::signal(snapshotDoneFuture)))); + co_await (config.initNewSnapshot(tr) && + success(BackupSnapshotDispatchTask::addTask( + tr, taskBucket, task, 1, TaskCompletionKey::signal(snapshotDoneFuture)))); } else { // Set the done future as the snapshot is now complete. - wait(snapshotDoneFuture->set(tr, taskBucket)); + co_await snapshotDoneFuture->set(tr, taskBucket); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask(BackupSnapshotManifest::name, - BackupSnapshotManifest::version, - tr, - taskBucket, - completionKey, - BackupConfig(parentTask), - waitFor, - NOP_SETUP_TASK_FN, - 1)); - return key; + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask(BackupSnapshotManifest::name, + BackupSnapshotManifest::version, + tr, + taskBucket, + completionKey, + BackupConfig(parentTask), + waitFor, + NOP_SETUP_TASK_FN, + 1); + co_return key; } StringRef getName() const override { return name; }; @@ -3710,13 +3940,13 @@ struct BulkDumpTaskFunc : BackupTaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state BackupConfig config(task); - state Version snapshotVersion = Params.snapshotVersion().get(task); - state std::string jobId = Params.bulkDumpJobId().getOrDefault(task, ""); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + BackupConfig config(task); + Version snapshotVersion = Params.snapshotVersion().get(task); + std::string jobId = Params.bulkDumpJobId().getOrDefault(task, ""); TraceEvent("BulkDumpTaskStart") .detail("BackupUID", config.getUid()) @@ -3724,213 +3954,252 @@ struct BulkDumpTaskFunc : BackupTaskFuncBase { .detail("BulkDumpJobId", jobId); // Declare state variables before try block so they're accessible in catch block - state std::vector backupRanges; - state Reference bc; - state int originalBulkDumpMode = 0; - state BulkLoadTransportMethod transportMethod = BulkLoadTransportMethod::CP; - state BulkDumpState bulkDumpJob; - state bool jobAlreadyRunning = false; - - try { - // Submit BulkDump job via ManagementAPI - // This is a black box delegation to the existing BulkDump system - - // Get backup ranges from config - std::vector ranges = wait(config.backupRanges().getOrThrow(cx.getReference())); - backupRanges = ranges; - Reference container = wait(config.backupContainer().getOrThrow(cx.getReference())); - bc = container; - - // Read the original BulkDump mode from config (saved by StartFullBackupTaskFunc before task creation). - // This is persisted in the database so we can restore the correct mode even after a crash. - int mode = wait(config.originalBulkDumpMode().getD(cx.getReference(), Snapshot::False, 0)); - originalBulkDumpMode = mode; - - // Determine transport method from backup URL - transportMethod = - isBlobstoreUrl(bc->getURL()) ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP; - - // Check if there's already a running BulkDump job (e.g., from a previous task attempt or another agent). - // If so, we'll monitor that job instead of submitting a new one. This prevents the "Conflict to a - // running BulkDump job" error that occurs when multiple backup agents try to run the same task. - state Transaction checkTr(cx); - loop { - try { - checkTr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - checkTr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional existingJob = wait(getSubmittedBulkDumpJob(&checkTr)); - if (existingJob.present()) { - bulkDumpJob = existingJob.get(); - jobAlreadyRunning = true; - TraceEvent("BulkDumpTaskUsingExistingJob") - .detail("BackupUID", config.getUid()) - .detail("ExistingJobId", bulkDumpJob.getJobId()); + std::vector backupRanges; + Reference bc; + int originalBulkDumpMode = 0; + BulkLoadTransportMethod transportMethod = BulkLoadTransportMethod::CP; + BulkDumpState bulkDumpJob; + bool jobAlreadyRunning = false; + + { + Error savedError; + try { + // Submit BulkDump job via ManagementAPI + // This is a black box delegation to the existing BulkDump system + + // Get backup ranges from config + std::vector ranges = co_await config.backupRanges().getOrThrow(cx.getReference()); + backupRanges = ranges; + Reference container = co_await config.backupContainer().getOrThrow(cx.getReference()); + bc = container; + + // Read the original BulkDump mode from config (saved by StartFullBackupTaskFunc before task creation). + // This is persisted in the database so we can restore the correct mode even after a crash. + int mode = co_await config.originalBulkDumpMode().getD(cx.getReference(), Snapshot::False, 0); + originalBulkDumpMode = mode; + + // Determine transport method from backup URL + transportMethod = + isBlobstoreUrl(bc->getURL()) ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP; + + // Check if there's already a running BulkDump job (e.g., from a previous task attempt or another + // agent). If so, we'll monitor that job instead of submitting a new one. This prevents the "Conflict to + // a running BulkDump job" error that occurs when multiple backup agents try to run the same task. + Transaction checkTr(cx); + while (true) { + Error err; + try { + checkTr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + checkTr.setOption(FDBTransactionOptions::LOCK_AWARE); + Optional existingJob = co_await getSubmittedBulkDumpJob(&checkTr); + if (existingJob.present()) { + bulkDumpJob = existingJob.get(); + jobAlreadyRunning = true; + TraceEvent("BulkDumpTaskUsingExistingJob") + .detail("BackupUID", config.getUid()) + .detail("ExistingJobId", bulkDumpJob.getJobId()); + } + break; + } catch (Error& e) { + err = e; } - break; - } catch (Error& e) { - wait(checkTr.onError(e)); + co_await checkTr.onError(err); } - } - if (!jobAlreadyRunning) { - // Enable BulkDump mode at the DD level before submitting the job - wait(success(setBulkDumpMode(cx, 1))); + if (!jobAlreadyRunning) { + // Enable BulkDump mode at the DD level before submitting the job + co_await setBulkDumpMode(cx, 1); - // Configure BulkDump job for the full keyspace - // BulkDump/BulkLoad requires the load range to be a subset of the dump range. - // Using normalKeys for both ensures compatibility regardless of user-specified ranges. - // Store data under data//bulkdump_data/ to be consistent with backup container layout - std::string bulkDumpRoot = getBackupDataPath(bc->getURL(), "bulkdump_data"); - bulkDumpJob = createBulkDumpJob(normalKeys, bulkDumpRoot, BulkLoadType::SST, transportMethod); + // Configure BulkDump job for the full keyspace + // BulkDump/BulkLoad requires the load range to be a subset of the dump range. + // Using normalKeys for both ensures compatibility regardless of user-specified ranges. + // Store data under data//bulkdump_data/ to be consistent with backup container layout + std::string bulkDumpRoot = getBackupDataPath(bc->getURL(), "bulkdump_data"); + bulkDumpJob = createBulkDumpJob(normalKeys, bulkDumpRoot, BulkLoadType::SST, transportMethod); - // Submit the BulkDump job - wait(submitBulkDumpJob(cx, bulkDumpJob)); - } + // Submit the BulkDump job + co_await submitBulkDumpJob(cx, bulkDumpJob); - // Store job ID for monitoring - Params.bulkDumpJobId().set(task, bulkDumpJob.getJobId().toString()); + // Set ownership so bulkdump status shows it belongs to this backup + std::string backupTag = co_await config.tag().getOrThrow(cx.getReference()); + BulkDumpOwnerInfo ownerInfo(config.getUid(), "backup", backupTag, now()); + co_await setBulkDumpOwner(cx, bulkDumpJob.getJobId(), ownerInfo); + } - // Monitor BulkDump progress - timeout is configurable for large datasets - state bool completed = wait(monitorBulkDumpJobCompletion(cx, - bulkDumpJob.getJobId(), - CLIENT_KNOBS->BULKDUMP_JOB_TIMEOUT, - 5.0)); // Poll every 5 seconds + // Store job ID for monitoring + Params.bulkDumpJobId().set(task, bulkDumpJob.getJobId().toString()); + + // Monitor BulkDump progress - timeout is configurable for large datasets + bool completed = co_await monitorBulkDumpJobCompletion(cx, + bulkDumpJob.getJobId(), + CLIENT_KNOBS->BULKDUMP_JOB_TIMEOUT, + 5.0); // Poll every 5 seconds + + if (!completed) { + TraceEvent(SevWarn, "BulkDumpTaskTimeout") + .detail("BackupUID", config.getUid()) + .detail("BulkDumpJobId", bulkDumpJob.getJobId()) + .detail("TimeoutDuration", 300.0); + Params.timeoutOccurred().set(task, true); + } - if (!completed) { - TraceEvent(SevWarn, "BulkDumpTaskTimeout") + TraceEvent("BulkDumpTaskComplete") .detail("BackupUID", config.getUid()) .detail("BulkDumpJobId", bulkDumpJob.getJobId()) - .detail("TimeoutDuration", 300.0); - Params.timeoutOccurred().set(task, true); - } + .detail("TimeoutOccurred", Params.timeoutOccurred().getOrDefault(task, false)) + .detail("JobAlreadyRunning", jobAlreadyRunning); - TraceEvent("BulkDumpTaskComplete") - .detail("BackupUID", config.getUid()) - .detail("BulkDumpJobId", bulkDumpJob.getJobId()) - .detail("TimeoutOccurred", Params.timeoutOccurred().getOrDefault(task, false)) - .detail("JobAlreadyRunning", jobAlreadyRunning); + // Restore original BulkDump mode after job completes, but only if: + // 1. We actually enabled the mode (not if we just monitored an existing job) + // 2. The original mode was different from what we set + if (!jobAlreadyRunning && originalBulkDumpMode != 1) { + co_await setBulkDumpMode(cx, originalBulkDumpMode); + } - // Restore original BulkDump mode after job completes, but only if: - // 1. We actually enabled the mode (not if we just monitored an existing job) - // 2. The original mode was different from what we set - if (!jobAlreadyRunning && originalBulkDumpMode != 1) { - wait(success(setBulkDumpMode(cx, originalBulkDumpMode))); - } + // Write the keyspace snapshot file to mark the backup as complete + // This is essential for the restore process to find the snapshot + if (completed) { + // Verify that BulkDump data was actually written before writing snapshot + bool datasetComplete = + co_await verifyBulkDumpDatasetCompleteness(bc, bulkDumpJob.getJobId().toString()); + if (!datasetComplete) { + TraceEvent(SevError, "BulkDumpTaskDatasetIncomplete") + .detail("BackupUID", config.getUid()) + .detail("BulkDumpJobId", bulkDumpJob.getJobId()); + throw backup_error(); + } + + // Build beginEndKeys from backup ranges + std::vector> beginEndKeys; + for (const auto& range : backupRanges) { + beginEndKeys.emplace_back(range.begin, range.end); + } - // Write the keyspace snapshot file to mark the backup as complete - // This is essential for the restore process to find the snapshot - if (completed) { - // Build beginEndKeys from backup ranges - std::vector> beginEndKeys; - for (const auto& range : backupRanges) { - beginEndKeys.emplace_back(range.begin, range.end); + // Create BulkDump snapshot metadata + // Note: totalBytes and totalKeys are set to 0 here. Unlike traditional range file backups + // which accumulate fileSize from each RangeSlice, BulkDump writes its data to manifest files + // in the backup container (one per shard). To get accurate byte/key counts, we would need to: + // 1. List all manifest files under bc->getURL()/bulkDumpJobId/ + // 2. Read each BulkLoadManifest and call getTotalBytes()/getKeyCount() + // 3. Aggregate the totals + // For now, zeros are acceptable since the critical field is bulkDumpJobId which BulkLoad + // uses to locate the data. TODO: Fix. The byte counts are informational for status display. + SnapshotMetadata metadata = + SnapshotMetadata::bulkDump(bulkDumpJob.getJobId().toString(), snapshotVersion, 0, 0); + + // Write the snapshot file - empty file list since BulkDump uses job manifests, not range files + co_await bc->writeKeyspaceSnapshotFile({}, // No individual range files for BulkDump + beginEndKeys, + 0, // See comment above about totalBytes + IncludeKeyRangeMap::False, + metadata); + + TraceEvent("BulkDumpTaskWroteSnapshot") + .detail("BackupUID", config.getUid()) + .detail("BulkDumpJobId", bulkDumpJob.getJobId()) + .detail("SnapshotVersion", snapshotVersion) + .detail("RangeCount", backupRanges.size()); } - // Create BulkDump snapshot metadata - // Note: totalBytes and totalKeys are set to 0 here. Unlike traditional range file backups - // which accumulate fileSize from each RangeSlice, BulkDump writes its data to manifest files - // in the backup container (one per shard). To get accurate byte/key counts, we would need to: - // 1. List all manifest files under bc->getURL()/bulkDumpJobId/ - // 2. Read each BulkLoadManifest and call getTotalBytes()/getKeyCount() - // 3. Aggregate the totals - // For now, zeros are acceptable since the critical field is bulkDumpJobId which BulkLoad - // uses to locate the data. TODO: Fix. The byte counts are informational for status display. - SnapshotMetadata metadata = - SnapshotMetadata::bulkDump(bulkDumpJob.getJobId().toString(), snapshotVersion, 0, 0); - - // Write the snapshot file - empty file list since BulkDump uses job manifests, not range files - wait(bc->writeKeyspaceSnapshotFile({}, // No individual range files for BulkDump - beginEndKeys, - 0, // See comment above about totalBytes - IncludeKeyRangeMap::False, - metadata)); - - TraceEvent("BulkDumpTaskWroteSnapshot") - .detail("BackupUID", config.getUid()) - .detail("BulkDumpJobId", bulkDumpJob.getJobId()) - .detail("SnapshotVersion", snapshotVersion) - .detail("RangeCount", backupRanges.size()); + // Increment counter for test assertions + g_bulkDumpTaskCompleteCount.fetch_add(1); + co_return; + } catch (Error& e) { + savedError = e; } - - // Increment counter for test assertions - g_bulkDumpTaskCompleteCount.fetch_add(1); - - } catch (Error& e) { - state Error savedError = e; TraceEvent(SevWarn, "BulkDumpTaskError") - .error(e) + .error(savedError) .detail("BackupUID", config.getUid()) .detail("SnapshotVersion", snapshotVersion) .detail("JobAlreadyRunning", jobAlreadyRunning); // Restore original BulkDump mode on error, but only if we were the ones who enabled it try { if (!jobAlreadyRunning && originalBulkDumpMode != 1) { - wait(success(setBulkDumpMode(cx, originalBulkDumpMode))); + co_await setBulkDumpMode(cx, originalBulkDumpMode); } } catch (Error& e2) { TraceEvent(SevWarn, "BulkDumpTaskRestoreModeError").error(e2); } throw savedError; } - - return Void(); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state BackupConfig config(task); - state Version snapshotVersion = Params.snapshotVersion().get(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + BackupConfig config(task); + Version snapshotVersion = Params.snapshotVersion().get(task); + std::string jobId = Params.bulkDumpJobId().getOrDefault(task, ""); TraceEvent("BulkDumpTaskFinishStart") .detail("BackupUID", config.getUid()) - .detail("SnapshotVersion", snapshotVersion); + .detail("SnapshotVersion", snapshotVersion) + .detail("BulkDumpJobId", jobId); // Set latestSnapshotEndVersion so BackupLogsDispatchTask knows we're restorable // This is critical for the backup to complete when using BulkDump config.latestSnapshotEndVersion().set(tr, snapshotVersion); - // Also set firstSnapshotEndVersion if not already set (first snapshot) - state Optional firstSnapshotEnd = wait(config.firstSnapshotEndVersion().get(tr)); - if (!firstSnapshotEnd.present()) { - config.firstSnapshotEndVersion().set(tr, snapshotVersion); + // Set bulkDumpSnapshotEndVersion to track that BulkDump data is available + // This is used by getLatestRestorableVersion() for mode=BOTH to ensure both + // rangefile and bulkdump data exist before marking backup as restorable + config.bulkDumpSnapshotEndVersion().set(tr, snapshotVersion); + + // CRITICAL: Set bulkDumpJobId on the backup CONFIG so that status checks + // and restore operations can find it. Without this, the backup status shows + // "BulkLoad Compatible: no" and bulkload restore fails with "Missing backup data". + if (!jobId.empty()) { + config.bulkDumpJobId().set(tr, jobId); + } + + // Set firstSnapshotEndVersion if not already set, BUT only if we're NOT in + // mode=BOTH. In mode=BOTH, firstSnapshotEndVersion tracks the rangefile snapshot + // completion and must only be set by BackupSnapshotManifest::_finish(). + // Setting it here would make getLatestRestorableVersion() think both snapshots + // are complete when only bulkdump is done. + Optional mode = co_await config.snapshotMode().get(tr); + if (!mode.present() || mode.get() != 2) { + Optional firstSnapshotEnd = co_await config.firstSnapshotEndVersion().get(tr); + if (!firstSnapshotEnd.present()) { + config.firstSnapshotEndVersion().set(tr, snapshotVersion); + } } TraceEvent("BulkDumpTaskFinishSetVersions") .detail("BackupUID", config.getUid()) .detail("SnapshotVersion", snapshotVersion) - .detail("FirstSnapshotWasSet", !firstSnapshotEnd.present()); + .detail("Mode", mode.present() ? mode.get() : 0); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket)); - wait(taskBucket->finish(tr, task)); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await taskFuture->set(tr, taskBucket); + co_await taskBucket->finish(tr, task); TraceEvent("BulkDumpTaskFinish") .detail("BackupUID", config.getUid()) .detail("SnapshotVersion", snapshotVersion); - return Void(); - } - - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version snapshotVersion, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask(BulkDumpTaskFunc::name, - BulkDumpTaskFunc::version, - tr, - taskBucket, - completionKey, - BackupConfig(parentTask), - waitFor, - [=](Reference task) { - Params.snapshotVersion().set(task, snapshotVersion); - Params.timeoutOccurred().set(task, false); - })); - return key; + co_return; + } + + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version snapshotVersion, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask(BulkDumpTaskFunc::name, + BulkDumpTaskFunc::version, + tr, + taskBucket, + completionKey, + BackupConfig(parentTask), + waitFor, + [=](Reference task) { + Params.snapshotVersion().set(task, snapshotVersion); + Params.timeoutOccurred().set(task, false); + }); + co_return key; } }; StringRef BulkDumpTaskFunc::name = "bulk_dump_snapshot_5.2"_sr; @@ -3944,70 +4213,73 @@ struct StartFullBackupTaskFunc : BackupTaskFuncBase { static TaskParam beginVersion() { return __FUNCTION__sr; } } Params; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - wait(checkTaskVersion(cx, task, StartFullBackupTaskFunc::name, StartFullBackupTaskFunc::version)); - - state Reference tr(new ReadYourWritesTransaction(cx)); - state BackupConfig config(task); - state Future> partitionedLog; - loop { + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + co_await checkTaskVersion(cx, task, StartFullBackupTaskFunc::name, StartFullBackupTaskFunc::version); + + Reference tr(new ReadYourWritesTransaction(cx)); + BackupConfig config(task); + Future> mutationLogType; + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - partitionedLog = config.partitionedLogEnabled().get(tr); - wait(success(partitionedLog)); + mutationLogType = config.mutationLogType().get(tr); + co_await mutationLogType; break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - // Check if backup worker is enabled - DatabaseConfiguration dbConfig = wait(getDatabaseConfiguration(cx)); - state bool backupWorkerEnabled = dbConfig.backupWorkerEnabled; - if (!backupWorkerEnabled && partitionedLog.get().present() && partitionedLog.get().get()) { - // Change configuration only when we set to use partitioned logs and - // the flag was not set before. - wait(success(ManagementAPI::changeConfig(cx.getReference(), "backup_worker_enabled:=1", true))); - backupWorkerEnabled = true; - // the user is responsible for manually disabling backup worker after the last backup is done + // Enable the appropriate backup worker type if not already enabled. + if (mutationLogType.get().present()) { + if (mutationLogType.get().get() == MutationLogType::PARTITIONED_LOG) { + co_await enableBackupWorker(cx); + } else if (mutationLogType.get().get() == MutationLogType::RANGE_PARTITIONED_LOG) { + co_await enableRangeBackupWorker(cx); + } } // Get start version after backup worker are enabled - loop { + while (true) { + Error err; try { tr->reset(); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future startVersionFuture = tr->getReadVersion(); - wait(success(startVersionFuture)); + Future startVersionFuture = tr->getReadVersion(); + co_await startVersionFuture; Params.beginVersion().set(task, startVersionFuture.get()); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // Set the "backupStartedKey" and wait for all backup worker started tr->reset(); - loop { - state Future watchFuture; + while (true) { + Future watchFuture; + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Future keepRunning = taskBucket->keepRunning(tr, task); + Future keepRunning = taskBucket->keepRunning(tr, task); - state Future> started = tr->get(backupStartedKey); - state Future> taskStarted = tr->get(config.allWorkerStarted().key); - partitionedLog = config.partitionedLogEnabled().get(tr); - wait(success(started) && success(taskStarted) && success(partitionedLog)); + Future> started = tr->get(backupStartedKey); + Future> taskStarted = tr->get(config.allWorkerStarted().key); + mutationLogType = config.mutationLogType().get(tr); + co_await (success(started) && success(taskStarted) && success(mutationLogType)); - if (!partitionedLog.get().present() || !partitionedLog.get().get()) { - return Void(); // Skip if not using partitioned logs + if (!mutationLogType.get().present() || mutationLogType.get().get() == MutationLogType::DEFAULT) { + co_return; // Skip if not using partitioned logs } std::vector> ids; @@ -4030,57 +4302,59 @@ struct StartFullBackupTaskFunc : BackupTaskFuncBase { watchFuture = tr->watch(config.allWorkerStarted().key); } - wait(keepRunning); - wait(tr->commit()); + co_await keepRunning; + co_await tr->commit(); if (!taskStarted.get().present()) { - wait(watchFuture); + co_await watchFuture; } - return Void(); + co_return; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state BackupConfig config(task); - state Version beginVersion = Params.beginVersion().get(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + BackupConfig config(task); + Version beginVersion = Params.beginVersion().get(task); - state Future> backupRangesFuture = config.backupRanges().getOrThrow(tr); - state Future destUidValueFuture = config.destUidValue().getOrThrow(tr); - state Future> partitionedLog = config.partitionedLogEnabled().get(tr); - state Future> incrementalBackupOnly = config.incrementalBackupOnly().get(tr); - wait(success(backupRangesFuture) && success(destUidValueFuture) && success(partitionedLog) && - success(incrementalBackupOnly)); + Future> backupRangesFuture = config.backupRanges().getOrThrow(tr); + Future destUidValueFuture = config.destUidValue().getOrThrow(tr); + Future> mutationLogType = config.mutationLogType().get(tr); + Future> incrementalBackupOnly = config.incrementalBackupOnly().get(tr); + co_await (success(backupRangesFuture) && success(destUidValueFuture) && success(mutationLogType) && + success(incrementalBackupOnly)); std::vector backupRanges = backupRangesFuture.get(); Key destUidValue = destUidValueFuture.get(); // Start logging the mutations for the specified ranges of the tag if needed - if (!partitionedLog.get().present() || !partitionedLog.get().get()) { + if (!mutationLogType.get().present() || mutationLogType.get().get() == MutationLogType::DEFAULT) { for (auto& backupRange : backupRanges) { config.startMutationLogs(tr, backupRange, destUidValue); } } - Reference bc = wait(config.backupContainer().getOrThrow(tr)); - wait(bc->writeEncryptionMetadata()); - + // Properties folder is created here to ensure backup container exists because localDirectory doesn't create + // container during in constructor. + Reference bc = co_await config.backupContainer().getOrThrow(tr); + co_await bc->writeEncryptionMetadata(bc->getEncryptionBlockSize()); config.stateEnum().set(tr, EBackupState::STATE_RUNNING); - state Reference backupFinished = futureBucket->future(tr); + Reference backupFinished = futureBucket->future(tr); // Initialize the initial snapshot and create tasks to continually write logs and snapshots. - state Optional initialSnapshotIntervalSeconds = wait(config.initialSnapshotIntervalSeconds().get(tr)); - wait(config.initNewSnapshot(tr, initialSnapshotIntervalSeconds.orDefault(0))); + Optional initialSnapshotIntervalSeconds = co_await config.initialSnapshotIntervalSeconds().get(tr); + co_await config.initNewSnapshot(tr, initialSnapshotIntervalSeconds.orDefault(0)); // Using priority 1 for both of these to at least start both tasks soon // Do not add snapshot task if we only want the incremental backup if (!incrementalBackupOnly.get().present() || !incrementalBackupOnly.get().get()) { // Check snapshot mode: 0=RANGEFILE, 1=BULKDUMP, 2=BOTH - state int snapshotModeValue = wait(config.snapshotMode().getD(tr, Snapshot::False, 0)); + int snapshotModeValue = co_await config.snapshotMode().getD(tr, Snapshot::False, 0); TraceEvent("StartFullBackupTaskSnapshotMode") .detail("BackupUID", config.getUid()) @@ -4092,46 +4366,45 @@ struct StartFullBackupTaskFunc : BackupTaskFuncBase { if (snapshotModeValue == 1 || snapshotModeValue == 2) { // Save original BulkDump mode BEFORE creating the task, so it's persisted in the database. // This allows crash recovery to restore the correct mode even if the task restarts. - int currentBulkDumpMode = wait(getBulkDumpMode(tr->getDatabase())); + int currentBulkDumpMode = co_await getBulkDumpMode(tr->getDatabase()); config.originalBulkDumpMode().set(tr, currentBulkDumpMode); - wait(success(BulkDumpTaskFunc::addTask( - tr, taskBucket, task, beginVersion, TaskCompletionKey::joinWith(backupFinished)))); + co_await BulkDumpTaskFunc::addTask( + tr, taskBucket, task, beginVersion, TaskCompletionKey::joinWith(backupFinished)); } // Add traditional range file snapshot if mode is RANGEFILE(0) or BOTH(2) if (snapshotModeValue == 0 || snapshotModeValue == 2) { - wait(success(BackupSnapshotDispatchTask::addTask( - tr, taskBucket, task, 1, TaskCompletionKey::joinWith(backupFinished)))); + co_await BackupSnapshotDispatchTask::addTask( + tr, taskBucket, task, 1, TaskCompletionKey::joinWith(backupFinished)); } } - wait(success(BackupLogsDispatchTask::addTask( - tr, taskBucket, task, 1, 0, beginVersion, TaskCompletionKey::joinWith(backupFinished)))); + co_await BackupLogsDispatchTask::addTask( + tr, taskBucket, task, 1, 0, beginVersion, TaskCompletionKey::joinWith(backupFinished)); // If a clean stop is requested, the log and snapshot tasks will quit after the backup is restorable, then // the following task will clean up and set the completed state. - wait(success( - FileBackupFinishedTask::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal(), backupFinished))); + co_await FileBackupFinishedTask::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal(), backupFinished); - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - UID uid, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key key = wait(addBackupTask(StartFullBackupTaskFunc::name, - StartFullBackupTaskFunc::version, - tr, - taskBucket, - completionKey, - BackupConfig(uid), - waitFor)); - return key; + static Future addTask(Reference tr, + Reference taskBucket, + UID uid, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key key = co_await addBackupTask(StartFullBackupTaskFunc::name, + StartFullBackupTaskFunc::version, + tr, + taskBucket, + completionKey, + BackupConfig(uid), + waitFor); + co_return key; } StringRef getName() const override { return name; }; @@ -4179,14 +4452,14 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); - state Version restoreVersion = Params.restoreVersion().get(task); - state std::string backupUrl = Params.backupUrl().get(task); - state std::string bulkDumpJobId = Params.bulkDumpJobId().getOrDefault(task, ""); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); + Version restoreVersion = Params.restoreVersion().get(task); + std::string backupUrl = Params.backupUrl().get(task); + std::string bulkDumpJobId = Params.bulkDumpJobId().getOrDefault(task, ""); TraceEvent("BulkLoadRestoreTaskStart") .detail("RestoreUID", restore.getUid()) @@ -4195,186 +4468,212 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { .detail("BulkDumpJobId", bulkDumpJobId); // Declare state variable before try block so it's accessible in catch block - state int originalBulkLoadMode = 0; + int originalBulkLoadMode = 0; - try { - // Open backup container for metadata access - state Reference bcRef = IBackupContainer::openContainer(backupUrl, {}, {}); + { + Error savedError; + try { + // Open backup container for metadata access + Reference bcRef = IBackupContainer::openContainer(backupUrl, {}, {}, 0); - // Get restore ranges using a transaction - state Reference tr(new ReadYourWritesTransaction(cx)); - tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state std::vector restoreRanges = wait(restore.getRestoreRangesOrDefault(tr)); - - // If bulkDumpJobId is empty, read it from the snapshot file metadata - if (bulkDumpJobId.empty()) { - // Use the backup container we already opened to find the snapshot file for our restore version - BackupContainerFileSystem* bcfsPtr = dynamic_cast(bcRef.getPtr()); - if (bcfsPtr != nullptr) { - state Reference bcfs = - Reference::addRef(bcfsPtr); - state std::vector snapshots = wait(bcfs->listKeyspaceSnapshots()); - state int snapIdx = snapshots.size() - 1; - while (snapIdx >= 0 && bulkDumpJobId.empty()) { - // For BulkDump snapshots: beginVersion == endVersion == snapshotVersion - // Accept any snapshot that is at or before our restore version - if (snapshots[snapIdx].endVersion <= restoreVersion) { - // Read the snapshot file to get bulkDumpJobId - state std::string snapFileName = snapshots[snapIdx].fileName; - state Reference snapFile = wait(bcfs->readFile(snapFileName)); - state int64_t snapSize = wait(snapFile->size()); - state Standalone snapBuf = makeString(snapSize); - int bytesRead = wait(snapFile->read(mutateString(snapBuf), snapSize, 0)); - if (bytesRead == snapSize) { - json_spirit::mValue json; - if (json_spirit::read_string(snapBuf.toString(), json)) { - JSONDoc doc(json); - std::string jobId; - if (doc.tryGet("bulkDumpJobId", jobId) && !jobId.empty()) { - bulkDumpJobId = jobId; - TraceEvent("BulkLoadRestoreFoundJobId") - .detail("RestoreUID", restore.getUid()) - .detail("BulkDumpJobId", bulkDumpJobId) - .detail("SnapshotFile", snapFileName); + // Get restore ranges using a transaction + Reference tr(new ReadYourWritesTransaction(cx)); + tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + tr->setOption(FDBTransactionOptions::LOCK_AWARE); + std::vector restoreRanges = co_await restore.getRestoreRangesOrDefault(tr); + + // If bulkDumpJobId is empty, read it from the snapshot file metadata + if (bulkDumpJobId.empty()) { + // Use the backup container we already opened to find the snapshot file for our restore version + auto* bcfsPtr = dynamic_cast(bcRef.getPtr()); + if (bcfsPtr != nullptr) { + Reference bcfs = + Reference::addRef(bcfsPtr); + std::vector snapshots = co_await bcfs->listKeyspaceSnapshots(); + int snapIdx = snapshots.size() - 1; + while (snapIdx >= 0 && bulkDumpJobId.empty()) { + // For BulkDump snapshots: beginVersion == endVersion == snapshotVersion + // Accept any snapshot that is at or before our restore version + if (snapshots[snapIdx].endVersion <= restoreVersion) { + // Read the snapshot file to get bulkDumpJobId + std::string snapFileName = snapshots[snapIdx].fileName; + Reference snapFile = co_await bcfs->readFile(snapFileName); + int64_t snapSize = co_await snapFile->size(); + Standalone snapBuf = makeString(snapSize); + int bytesRead = co_await snapFile->read(mutateString(snapBuf), snapSize, 0); + if (bytesRead == snapSize) { + json_spirit::mValue json; + if (json_spirit::read_string(snapBuf.toString(), json)) { + JSONDoc doc(json); + std::string jobId; + if (doc.tryGet("bulkDumpJobId", jobId) && !jobId.empty()) { + bulkDumpJobId = jobId; + TraceEvent("BulkLoadRestoreFoundJobId") + .detail("RestoreUID", restore.getUid()) + .detail("BulkDumpJobId", bulkDumpJobId) + .detail("SnapshotFile", snapFileName); + } } } } + snapIdx--; } - snapIdx--; } } - } - // Create BulkLoad job from the BulkDump data - // BulkDump stores data under data//bulkdump_data/ subdirectory. - // DD appends the jobId via getBulkLoadJobRoot(jobRoot, jobId) when accessing files. - state std::string jobRoot = getBackupDataPath(backupUrl, "bulkdump_data"); - state UID dumpJobUid; - if (!bulkDumpJobId.empty()) { - dumpJobUid = UID::fromString(bulkDumpJobId); - } else { - // No BulkDump job ID found - this is an error for BulkLoad restore - TraceEvent(SevError, "BulkLoadRestoreNoBulkDumpJobId") - .detail("RestoreUID", restore.getUid()) - .detail("BackupUrl", backupUrl); - throw restore_missing_data(); - } + // Create BulkLoad job from the BulkDump data + // BulkDump stores data under data//bulkdump_data/ subdirectory. + // DD appends the jobId via getBulkLoadJobRoot(jobRoot, jobId) when accessing files. + std::string jobRoot = getBackupDataPath(backupUrl, "bulkdump_data"); + UID dumpJobUid; + if (!bulkDumpJobId.empty()) { + dumpJobUid = UID::fromString(bulkDumpJobId); + } else { + // No BulkDump job ID found - this is a permanent error for BulkLoad restore. + // Abort the restore immediately instead of retrying forever. + TraceEvent(SevError, "BulkLoadRestoreNoBulkDumpJobId") + .detail("RestoreUID", restore.getUid()) + .detail("BackupUrl", backupUrl) + .detail("Action", "Aborting restore - backup is not BulkLoad compatible"); + co_await restore.logError( + cx, restore_missing_data(), "BulkLoad restore failed: backup has no bulkdump data", nullptr); + // Abort the restore by setting state to ABORTED + Reference abortTr(new ReadYourWritesTransaction(cx)); + abortTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + abortTr->setOption(FDBTransactionOptions::LOCK_AWARE); + restore.stateEnum().set(abortTr, ERestoreState::ABORTED); + co_await abortTr->commit(); + throw restore_missing_data(); + } - // Verify BulkDump dataset completeness before proceeding - bool datasetComplete = wait(verifyBulkDumpDatasetCompleteness(bcRef, bulkDumpJobId)); - if (!datasetComplete) { - TraceEvent(SevWarn, "BulkLoadRestoreDatasetIncomplete") + // Verify BulkDump dataset completeness before proceeding + bool datasetComplete = co_await verifyBulkDumpDatasetCompleteness(bcRef, bulkDumpJobId); + if (!datasetComplete) { + // Dataset is incomplete - abort the restore permanently + TraceEvent(SevError, "BulkLoadRestoreDatasetIncomplete") + .detail("RestoreUID", restore.getUid()) + .detail("BulkDumpJobId", bulkDumpJobId) + .detail("BackupUrl", backupUrl) + .detail("Action", "Aborting restore - bulkdump dataset is incomplete"); + co_await restore.logError( + cx, restore_missing_data(), "BulkLoad restore failed: bulkdump dataset incomplete", nullptr); + // Abort the restore by setting state to ABORTED + Reference abortTr(new ReadYourWritesTransaction(cx)); + abortTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + abortTr->setOption(FDBTransactionOptions::LOCK_AWARE); + restore.stateEnum().set(abortTr, ERestoreState::ABORTED); + co_await abortTr->commit(); + throw restore_missing_data(); + } + TraceEvent("BulkLoadRestoreDatasetVerified") .detail("RestoreUID", restore.getUid()) - .detail("BulkDumpJobId", bulkDumpJobId) - .detail("BackupUrl", backupUrl); - throw restore_missing_data(); - } - TraceEvent("BulkLoadRestoreDatasetVerified") - .detail("RestoreUID", restore.getUid()) - .detail("BulkDumpJobId", bulkDumpJobId); + .detail("BulkDumpJobId", bulkDumpJobId); - // Determine transport method from backup URL - state BulkLoadTransportMethod loadTransportMethod = - isBlobstoreUrl(backupUrl) ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP; + // Determine transport method from backup URL + BulkLoadTransportMethod loadTransportMethod = + isBlobstoreUrl(backupUrl) ? BulkLoadTransportMethod::BLOBSTORE : BulkLoadTransportMethod::CP; - // BulkLoad range must match the BulkDump range (normalKeys) - // The actual restore ranges will be applied via mutation log replay - state BulkLoadJobState bulkLoadJob = - createBulkLoadJob(dumpJobUid, normalKeys, jobRoot, loadTransportMethod); + // BulkLoad range must match the BulkDump range (normalKeys) + // The actual restore ranges will be applied via mutation log replay + BulkLoadJobState bulkLoadJob = createBulkLoadJob(dumpJobUid, normalKeys, jobRoot, loadTransportMethod); - TraceEvent("BulkLoadRestoreJobCreated") - .detail("RestoreUID", restore.getUid()) - .detail("BulkLoadJobId", bulkLoadJob.getJobId()) - .detail("JobRoot", jobRoot); + TraceEvent("BulkLoadRestoreJobCreated") + .detail("RestoreUID", restore.getUid()) + .detail("BulkLoadJobId", bulkLoadJob.getJobId()) + .detail("JobRoot", jobRoot); - // Register the BulkLoad range lock owner (required for range locking) - wait(registerRangeLockOwner(cx, "BulkLoad", "BulkLoad restore operation")); + // Register the BulkLoad range lock owner (required for range locking) + co_await registerRangeLockOwner(cx, "BulkLoad", "BulkLoad restore operation"); - TraceEvent("BulkLoadRestoreRegisteredLockOwner") - .detail("RestoreUID", restore.getUid()) - .detail("Owner", "BulkLoad"); + TraceEvent("BulkLoadRestoreRegisteredLockOwner") + .detail("RestoreUID", restore.getUid()) + .detail("Owner", "BulkLoad"); - // Note: BulkLoad configuration validation (shard_encode_location_metadata, enable_read_lock_on_range) - // is performed by the BulkLoad system on the server side when the job is submitted. + // Note: BulkLoad configuration validation (shard_encode_location_metadata, enable_read_lock_on_range) + // is performed by the BulkLoad system on the server side when the job is submitted. - // Read the original BulkLoad mode from config (saved by StartFullRestoreTaskFunc before task creation). - // This is persisted in the database so we can restore the correct mode even after a crash. - int mode = wait(restore.originalBulkLoadMode().getD(cx.getReference(), Snapshot::False, 0)); - originalBulkLoadMode = mode; + // Read the original BulkLoad mode from config (saved by StartFullRestoreTaskFunc before task creation). + // This is persisted in the database so we can restore the correct mode even after a crash. + int mode = co_await restore.originalBulkLoadMode().getD(cx.getReference(), Snapshot::False, 0); + originalBulkLoadMode = mode; - // Enable BulkLoad mode at DD level so the job will be processed - wait(success(setBulkLoadMode(cx, 1))); + // Enable BulkLoad mode at DD level so the job will be processed + co_await setBulkLoadMode(cx, 1); - TraceEvent("BulkLoadRestoreEnabledMode") - .detail("RestoreUID", restore.getUid()) - .detail("BulkLoadJobId", bulkLoadJob.getJobId()); + TraceEvent("BulkLoadRestoreEnabledMode") + .detail("RestoreUID", restore.getUid()) + .detail("BulkLoadJobId", bulkLoadJob.getJobId()); - // Submit BulkLoad job (must be lockAware since DB is locked during restore) - wait(submitBulkLoadJob(cx, bulkLoadJob, true /* lockAware */)); + // Submit BulkLoad job (must be lockAware since DB is locked during restore) + co_await submitBulkLoadJob(cx, bulkLoadJob, true /* lockAware */); - TraceEvent("BulkLoadRestoreJobSubmitted") - .detail("RestoreUID", restore.getUid()) - .detail("BulkLoadJobId", bulkLoadJob.getJobId()); - - // Monitor BulkLoad progress - timeout is configurable for large datasets - // Must be lockAware since DB is locked during restore - bool completed = wait(monitorBulkLoadJobCompletion(cx, - bulkLoadJob.getJobId(), - CLIENT_KNOBS->BULKLOAD_JOB_TIMEOUT, - 5.0, // Poll every 5 seconds - true)); // lockAware - - if (!completed) { - TraceEvent(SevWarn, "BulkLoadRestoreTimeout") + TraceEvent("BulkLoadRestoreJobSubmitted") .detail("RestoreUID", restore.getUid()) - .detail("BulkLoadJobId", bulkLoadJob.getJobId()) - .detail("TimeoutDuration", CLIENT_KNOBS->BULKLOAD_JOB_TIMEOUT); - // Restore original BulkLoad mode before throwing - if (originalBulkLoadMode != 1) { - wait(success(setBulkLoadMode(cx, originalBulkLoadMode))); + .detail("BulkLoadJobId", bulkLoadJob.getJobId()); + + // Get total block count for progress tracking + int64_t totalBlocks = co_await restore.fileBlockCount().getD(cx.getReference(), Snapshot::False, 0); + + // Monitor BulkLoad progress - timeout is configurable for large datasets + // Must be lockAware since DB is locked during restore + // Use the progress-tracking version to update restore counters + bool completed = co_await monitorBulkLoadJobCompletionWithProgress(cx, + bulkLoadJob.getJobId(), + restore.getUid(), + totalBlocks, + CLIENT_KNOBS->BULKLOAD_JOB_TIMEOUT, + 5.0, // Poll every 5 seconds + true); // lockAware + + if (!completed) { + TraceEvent(SevWarn, "BulkLoadRestoreTimeout") + .detail("RestoreUID", restore.getUid()) + .detail("BulkLoadJobId", bulkLoadJob.getJobId()) + .detail("TimeoutDuration", CLIENT_KNOBS->BULKLOAD_JOB_TIMEOUT); + // Restore original BulkLoad mode before throwing + if (originalBulkLoadMode != 1) { + co_await setBulkLoadMode(cx, originalBulkLoadMode); + } + throw timed_out(); } - throw timed_out(); - } - - // Restore original BulkLoad mode now that the job is complete - if (originalBulkLoadMode != 1) { - wait(success(setBulkLoadMode(cx, originalBulkLoadMode))); - } - TraceEvent("BulkLoadRestoreTaskComplete") - .detail("RestoreUID", restore.getUid()) - .detail("BulkLoadJobId", bulkLoadJob.getJobId()) - .detail("RestoreVersion", restoreVersion); + // Restore original BulkLoad mode now that the job is complete + if (originalBulkLoadMode != 1) { + co_await setBulkLoadMode(cx, originalBulkLoadMode); + } - // Increment counter for test assertions - g_bulkLoadRestoreTaskCompleteCount.fetch_add(1); + TraceEvent("BulkLoadRestoreTaskComplete") + .detail("RestoreUID", restore.getUid()) + .detail("BulkLoadJobId", bulkLoadJob.getJobId()) + .detail("RestoreVersion", restoreVersion); - } catch (Error& e) { - state Error savedError = e; + // Increment counter for test assertions + g_bulkLoadRestoreTaskCompleteCount.fetch_add(1); + co_return; + } catch (Error& e) { + savedError = e; + } TraceEvent(SevWarn, "BulkLoadRestoreTaskError") - .error(e) + .error(savedError) .detail("RestoreUID", restore.getUid()) .detail("BackupUrl", backupUrl); // Restore original BulkLoad mode on error try { if (originalBulkLoadMode != 1) { - wait(success(setBulkLoadMode(cx, originalBulkLoadMode))); + co_await setBulkLoadMode(cx, originalBulkLoadMode); } } catch (Error& e2) { TraceEvent(SevWarn, "BulkLoadRestoreRestoreModeError").error(e2); } throw savedError; } - - return Void(); } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); // Mark BulkLoad phase as complete restore.bulkLoadComplete().set(tr, true); @@ -4382,8 +4681,8 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { // Update fileBlocksFinished and filesBlocksDispatched to match fileBlockCount // so the restore progress tracker knows all range data has been loaded. // BulkLoad doesn't use the traditional block dispatching mechanism. - state int64_t totalBlocks = wait(restore.fileBlockCount().getD(tr, Snapshot::False, 0)); - state int64_t finishedBlocks = wait(restore.fileBlocksFinished().getD(tr, Snapshot::False, 0)); + int64_t totalBlocks = co_await restore.fileBlockCount().getD(tr, Snapshot::False, 0); + int64_t finishedBlocks = co_await restore.fileBlocksFinished().getD(tr, Snapshot::False, 0); if (finishedBlocks < totalBlocks) { // Set both dispatched and finished to match total to indicate all range data is done restore.filesBlocksDispatched().set(tr, totalBlocks); @@ -4392,11 +4691,11 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { // Set firstConsistentVersion if not already set // For BulkLoad restore, this is the snapshot version from the BulkDump - state Version firstConsistentVer = - wait(restore.firstConsistentVersion().getD(tr, Snapshot::False, invalidVersion)); + Version firstConsistentVer = + co_await restore.firstConsistentVersion().getD(tr, Snapshot::False, invalidVersion); if (firstConsistentVer == invalidVersion) { // Get the restore version as the first consistent version - state Version restoreVer = wait(restore.restoreVersion().getD(tr, Snapshot::False, invalidVersion)); + Version restoreVer = co_await restore.restoreVersion().getD(tr, Snapshot::False, invalidVersion); if (restoreVer != invalidVersion) { restore.firstConsistentVersion().set(tr, restoreVer); } @@ -4407,22 +4706,22 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { .detail("TotalBlocks", totalBlocks) .detail("FinishedBlocks", finishedBlocks); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket)); - wait(taskBucket->finish(tr, task)); - return Void(); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await taskFuture->set(tr, taskBucket); + co_await taskBucket->finish(tr, task); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version restoreVersion, - std::string backupUrl, - std::string bulkDumpJobId, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task(new Task(BulkLoadRestoreTaskFunc::name, BulkLoadRestoreTaskFunc::version, doneKey)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version restoreVersion, + std::string backupUrl, + std::string bulkDumpJobId, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(BulkLoadRestoreTaskFunc::name, BulkLoadRestoreTaskFunc::version, doneKey)); // Set task parameters Params.restoreVersion().set(task, restoreVersion); @@ -4430,30 +4729,30 @@ struct BulkLoadRestoreTaskFunc : RestoreTaskFuncBase { Params.bulkDumpJobId().set(task, bulkDumpJobId); // Get restore config from parent task and bind it to new task - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } }; StringRef BulkLoadRestoreTaskFunc::name = "bulk_load_restore_5.2"_sr; REGISTER_TASKFUNC(BulkLoadRestoreTaskFunc); struct RestoreCompleteTaskFunc : RestoreTaskFuncBase { - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { - wait(checkTaskVersion(tr->getDatabase(), task, name, version)); + co_await checkTaskVersion(tr->getDatabase(), task, name, version); - state RestoreConfig restore(task); + RestoreConfig restore(task); restore.stateEnum().set(tr, ERestoreState::COMPLETED); - state bool unlockDB = wait(restore.unlockDBAfterRestore().getD(tr, Snapshot::False, true)); + bool unlockDB = co_await restore.unlockDBAfterRestore().getD(tr, Snapshot::False, true); tr->atomicOp(metadataVersionKey, metadataVersionRequiredValue, MutationRef::SetVersionstampedValue); // Clear the file map now since it could be huge. @@ -4469,32 +4768,32 @@ struct RestoreCompleteTaskFunc : RestoreTaskFuncBase { // version. restore.clearApplyMutationsKeys(tr); - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); if (unlockDB) { - wait(unlockDatabase(tr, restore.getUid())); + co_await unlockDatabase(tr, restore.getUid()); } - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task(new Task(RestoreCompleteTaskFunc::name, RestoreCompleteTaskFunc::version, doneKey)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(RestoreCompleteTaskFunc::name, RestoreCompleteTaskFunc::version, doneKey)); // Get restore config from parent task and bind it to new task - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } static StringRef name; @@ -4558,15 +4857,15 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { return returnStr; } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); - state RestoreFile rangeFile = Params.inputFile().get(task); - state int64_t readOffset = Params.readOffset().get(task); - state int64_t readLen = Params.readLen().get(task); + RestoreFile rangeFile = Params.inputFile().get(task); + int64_t readOffset = Params.readOffset().get(task); + int64_t readLen = Params.readLen().get(task); TraceEvent("FileRestoreRangeStart") .suppressFor(60) @@ -4575,57 +4874,58 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { .detail("FileVersion", rangeFile.version) .detail("FileSize", rangeFile.fileSize) .detail("ReadOffset", readOffset) - .detail("ReadLen", readLen) - .detail("TaskInstance", THIS_ADDR); + .detail("ReadLen", readLen); - state Reference tr(new ReadYourWritesTransaction(cx)); - state Future> bc; - state Future> restoreRanges; - state Future addPrefix; - state Future removePrefix; + Reference tr(new ReadYourWritesTransaction(cx)); + Future> bc; + Future> restoreRanges; + Future addPrefix; + Future removePrefix; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Reference _bc = wait(restore.sourceContainer().getOrThrow(tr)); + Reference _bc = co_await restore.sourceContainer().getOrThrow(tr); bc = getBackupContainerWithProxy(_bc); restoreRanges = restore.getRestoreRangesOrDefault(tr); addPrefix = restore.addPrefix().getD(tr); removePrefix = restore.removePrefix().getD(tr); - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); - wait(success(bc) && success(restoreRanges) && success(addPrefix) && success(removePrefix) && - checkTaskVersion(tr->getDatabase(), task, name, version)); + co_await (success(bc) && success(restoreRanges) && success(addPrefix) && success(removePrefix) && + checkTaskVersion(tr->getDatabase(), task, name, version)); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state Reference inFile = wait(bc.get()->readFile(rangeFile.fileName)); - state Standalone> blockData; + Reference inFile = co_await bc.get()->readFile(rangeFile.fileName); + Standalone> blockData; try { // data is each real KV, not encoded mutations - Standalone> data = wait(decodeRangeFileBlock(inFile, readOffset, readLen, cx)); + Standalone> data = co_await decodeRangeFileBlock(inFile, readOffset, readLen, cx); blockData = data; } catch (Error& e) { if (e.code() == error_code_encrypt_keys_fetch_failed || e.code() == error_code_encrypt_key_not_found) { - return Void(); + co_return; } throw; } - state Arena arena; - state DatabaseConfiguration config = wait(getDatabaseConfiguration(cx)); + Arena arena; + DatabaseConfiguration config = co_await getDatabaseConfiguration(cx); // First and last key are the range for this file - state KeyRange fileRange; - state std::vector originalFileRanges; + KeyRange fileRange; + std::vector originalFileRanges; // If fileRange doesn't intersect restore range then we're done. - state int index; + int index{ 0 }; for (index = 0; index < restoreRanges.get().size(); index++) { auto& restoreRange = restoreRanges.get()[index]; fileRange = KeyRangeRef(blockData.front().key, blockData.back().key); @@ -4644,19 +4944,19 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { while (rangeEnd > rangeStart && !restoreRange.contains(blockData[rangeEnd - 1].key)) --rangeEnd; - state VectorRef data = blockData.slice(rangeStart, rangeEnd); + VectorRef data = blockData.slice(rangeStart, rangeEnd); // Shrink file range to be entirely within restoreRange and translate it to the new prefix // First, use the untranslated file range to create the shrunk original file range which must be used in // the kv range version map for applying mutations - state KeyRange originalFileRange = + KeyRange originalFileRange = KeyRangeRef(std::max(fileRange.begin, restoreRange.begin), std::min(fileRange.end, restoreRange.end)); originalFileRanges.push_back(originalFileRange); // Now shrink and translate fileRange Key fileEnd = std::min(fileRange.end, restoreRange.end); - if (fileEnd == (removePrefix.get() == StringRef() ? allKeys.end : strinc(removePrefix.get()))) { - fileEnd = addPrefix.get() == StringRef() ? allKeys.end : strinc(addPrefix.get()); + if (fileEnd == (removePrefix.get().empty() ? allKeys.end : strinc(removePrefix.get()))) { + fileEnd = addPrefix.get().empty() ? allKeys.end : strinc(addPrefix.get()); } else { fileEnd = fileEnd.removePrefix(removePrefix.get()).withPrefix(addPrefix.get()); } @@ -4665,20 +4965,21 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { .withPrefix(addPrefix.get()), fileEnd); - state int start = 0; - state int end = data.size(); - state int dataSizeLimit = + int start = 0; + int end = data.size(); + int dataSizeLimit = BUGGIFY ? deterministicRandom()->randomInt(256 * 1024, 10e6) : CLIENT_KNOBS->RESTORE_WRITE_TX_SIZE; tr->reset(); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state int i = start; - state int txBytes = 0; - state int iend = start; + int i = start; + int txBytes = 0; + int iend = start; // find iend that results in the desired transaction size for (; iend < end && txBytes < dataSizeLimit; ++iend) { @@ -4690,7 +4991,7 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { // If start == 0 then use fileBegin for the start of the range, else data[start] // If iend == end then use fileEnd for the end of the range, else data[iend] // clear the raw key(without alog prefix) - state KeyRange trRange = KeyRangeRef( + KeyRange trRange = KeyRangeRef( (start == 0) ? fileRange.begin : data[start].key.removePrefix(removePrefix.get()).withPrefix(addPrefix.get()), (iend == end) ? fileRange.end @@ -4706,13 +5007,13 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { // Add to bytes written count restore.bytesWritten().atomicOp(tr, txBytes, MutationRef::Type::AddValue); - state Future checkLock = checkDatabaseLock(tr, restore.getUid()); + Future checkLock = checkDatabaseLock(tr, restore.getUid()); - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); - wait(checkLock); + co_await checkLock; - wait(tr->commit()); + co_await tr->commit(); TraceEvent("FileRestoreCommittedRange") .suppressFor(60) @@ -4729,8 +5030,7 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { .detail("EndIndex", i) .detail("DataSize", data.size()) .detail("Bytes", txBytes) - .detail("OriginalFileRange", originalFileRange) - .detail("TaskInstance", THIS_ADDR); + .detail("OriginalFileRange", originalFileRange); // Commit succeeded, so advance starting point start = i; @@ -4738,12 +5038,14 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { if (start == end) break; tr->reset(); + continue; } catch (Error& e) { - if (e.code() == error_code_transaction_too_large) - dataSizeLimit /= 2; - else - wait(tr->onError(e)); + err = e; } + if (err.code() == error_code_transaction_too_large) + dataSizeLimit /= 2; + else + co_await tr->onError(err); } } if (!originalFileRanges.empty()) { @@ -4753,14 +5055,14 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { Params.originalFileRanges().set(task, originalFileRanges); } } - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); restore.fileBlocksFinished().atomicOp(tr, 1, MutationRef::Type::AddValue); // Update the KV range map if originalFileRange is set @@ -4772,36 +5074,36 @@ struct RestoreRangeTaskFunc : RestoreFileTaskFuncBase { updateMap.push_back(krmSetRange(tr, restore.applyMutationsMapPrefix(), range, versionEncoded)); } - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task) && waitForAll(updateMap)); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task) && waitForAll(updateMap)); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - RestoreFile rf, - int64_t offset, - int64_t len, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task(new Task(RestoreRangeTaskFunc::name, RestoreRangeTaskFunc::version, doneKey)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + RestoreFile rf, + int64_t offset, + int64_t len, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(RestoreRangeTaskFunc::name, RestoreRangeTaskFunc::version, doneKey)); // Create a restore config from the current task and bind it to the new task. - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); Params.inputFile().set(task, rf); Params.readOffset().set(task, offset); Params.readLen().set(task, len); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } static StringRef name; @@ -4990,15 +5292,15 @@ struct RestoreLogDataTaskFunc : RestoreFileTaskFuncBase { static struct : InputParams { } Params; - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); - state RestoreFile logFile = Params.inputFile().get(task); - state int64_t readOffset = Params.readOffset().get(task); - state int64_t readLen = Params.readLen().get(task); + RestoreFile logFile = Params.inputFile().get(task); + int64_t readOffset = Params.readOffset().get(task); + int64_t readLen = Params.readLen().get(task); TraceEvent("FileRestoreLogStart") .suppressFor(60) @@ -5008,57 +5310,59 @@ struct RestoreLogDataTaskFunc : RestoreFileTaskFuncBase { .detail("FileEndVersion", logFile.endVersion) .detail("FileSize", logFile.fileSize) .detail("ReadOffset", readOffset) - .detail("ReadLen", readLen) - .detail("TaskInstance", THIS_ADDR); + .detail("ReadLen", readLen); - state Reference tr(new ReadYourWritesTransaction(cx)); - state Reference bc; - state std::vector ranges; + Reference tr(new ReadYourWritesTransaction(cx)); + Reference bc; + std::vector ranges; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Reference _bc = wait(restore.sourceContainer().getOrThrow(tr)); + Reference _bc = co_await restore.sourceContainer().getOrThrow(tr); bc = getBackupContainerWithProxy(_bc); - wait(store(ranges, restore.getRestoreRangesOrDefault(tr))); + ranges = co_await restore.getRestoreRangesOrDefault(tr); - wait(checkTaskVersion(tr->getDatabase(), task, name, version)); - wait(taskBucket->keepRunning(tr, task)); + co_await checkTaskVersion(tr->getDatabase(), task, name, version); + co_await taskBucket->keepRunning(tr, task); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state Key mutationLogPrefix = restore.mutationLogPrefix(); - state Reference inFile = wait(bc->readFile(logFile.fileName)); - state Standalone> dataOriginal = - wait(decodeMutationLogFileBlock(inFile, readOffset, readLen)); + Key mutationLogPrefix = restore.mutationLogPrefix(); + Reference inFile = co_await bc->readFile(logFile.fileName); + Standalone> dataOriginal = + co_await decodeMutationLogFileBlock(inFile, readOffset, readLen); // Filter the KV pairs extracted from the log file block to remove any records known to not be needed for // this restore based on the restore range set. RangeMapFilters filters(ranges); - state std::vector dataFiltered = filterLogMutationKVPairs(dataOriginal, filters); + std::vector dataFiltered = filterLogMutationKVPairs(dataOriginal, filters); - state int start = 0; - state int end = dataFiltered.size(); - state int dataSizeLimit = + int start = 0; + int end = dataFiltered.size(); + int dataSizeLimit = BUGGIFY ? deterministicRandom()->randomInt(256 * 1024, 10e6) : CLIENT_KNOBS->RESTORE_WRITE_TX_SIZE; tr->reset(); - loop { + while (true) { + Error err; try { if (start == end) - return Void(); + co_return; tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state int i = start; - state int txBytes = 0; + int i = start; + int txBytes = 0; for (; i < end && txBytes < dataSizeLimit; ++i) { Key k = dataFiltered[i].key.withPrefix(mutationLogPrefix); ValueRef v = dataFiltered[i].value; // each KV is a [param1 with added prefix -> param2] @@ -5067,15 +5371,15 @@ struct RestoreLogDataTaskFunc : RestoreFileTaskFuncBase { txBytes += v.expectedSize(); } - state Future checkLock = checkDatabaseLock(tr, restore.getUid()); + Future checkLock = checkDatabaseLock(tr, restore.getUid()); - wait(taskBucket->keepRunning(tr, task)); - wait(checkLock); + co_await taskBucket->keepRunning(tr, task); + co_await checkLock; // Add to bytes written count restore.bytesWritten().atomicOp(tr, txBytes, MutationRef::Type::AddValue); - wait(tr->commit()); + co_await tr->commit(); TraceEvent("FileRestoreCommittedLog") .suppressFor(60) @@ -5091,60 +5395,61 @@ struct RestoreLogDataTaskFunc : RestoreFileTaskFuncBase { .detail("EndIndex", i) .detail("RecordCountOriginal", dataOriginal.size()) .detail("RecordCountFiltered", dataFiltered.size()) - .detail("Bytes", txBytes) - .detail("TaskInstance", THIS_ADDR); + .detail("Bytes", txBytes); // Commit succeeded, so advance starting point start = i; tr->reset(); + continue; } catch (Error& e) { - if (e.code() == error_code_transaction_too_large) - dataSizeLimit /= 2; - else - wait(tr->onError(e)); + err = e; } + if (err.code() == error_code_transaction_too_large) + dataSizeLimit /= 2; + else + co_await tr->onError(err); } } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { RestoreConfig(task).fileBlocksFinished().atomicOp(tr, 1, MutationRef::Type::AddValue); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); // TODO: Check to see if there is a leak in the FutureBucket since an invalid task (validation key fails) // will never set its taskFuture. - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - RestoreFile lf, - int64_t offset, - int64_t len, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task(new Task(RestoreLogDataTaskFunc::name, RestoreLogDataTaskFunc::version, doneKey)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + RestoreFile lf, + int64_t offset, + int64_t len, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(RestoreLogDataTaskFunc::name, RestoreLogDataTaskFunc::version, doneKey)); // Create a restore config from the current task and bind it to the new task. // RestoreConfig(parentTask) creates prefix of : fileRestorePrefixRange.begin/uid->config/[uid] - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); Params.inputFile().set(task, lf); Params.readOffset().set(task, offset); Params.readLen().set(task, len); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } Future execute(Database cx, @@ -5270,40 +5575,37 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { } Params; // From all iterators, find the minimum version and return it together with if this version has data. - ACTOR static Future> findNextVersion( - std::vector> iterators) { - state Version minVersion = std::numeric_limits::max(); - state bool atLeastOneIteratorHasNext = false; - state std::vector minVs(iterators.size(), 0); // can be removed or leave for debugging - - state int k = 0; - for (; k < iterators.size(); k++) { + static Future> findNextVersion(std::vector> iterators) { + Version minVersion = std::numeric_limits::max(); + bool atLeastOneIteratorHasNext = false; + + for (int k = 0; k < iterators.size(); k++) { if (!iterators[k]->hasNext()) { continue; } atLeastOneIteratorHasNext = true; - Version v = wait(iterators[k]->peekNextVersion()); - minVs[k] = v; + Version v = co_await iterators[k]->peekNextVersion(); minVersion = std::min(minVersion, v); } - return std::make_pair(minVersion, atLeastOneIteratorHasNext); + co_return std::make_pair(minVersion, atLeastOneIteratorHasNext); } // Reads from all iterators and sends a list of mutations for the given version // to the mutationStream for consumption. This actor reads as fast as possible, // which is fine since the task is working on the version range of size // RESTORE_PARTITIONED_BATCH_VERSION_SIZE, thus limiting the amount of memory used. - ACTOR static Future readLogData(PromiseStream>> mutationStream, - std::vector> iterators, - Version begin, - Version end) { + static Future readLogData(PromiseStream>> mutationStream, + std::vector> iterators, + Version begin, + Version end) { // mergeSort all iterator until all are exhausted // it stores all mutations for the next min version, in new format try { - state bool atLeastOneIteratorHasNext = true; - state Version minVersion, lastMinVersion = invalidVersion; + bool atLeastOneIteratorHasNext = true; + Version minVersion{ 0 }; + Version lastMinVersion = invalidVersion; while (atLeastOneIteratorHasNext) { - std::pair minVersionAndHasNext = wait(findNextVersion(iterators)); + std::pair minVersionAndHasNext = co_await findNextVersion(iterators); minVersion = minVersionAndHasNext.first; atLeastOneIteratorHasNext = minVersionAndHasNext.second; ASSERT_LT(lastMinVersion, minVersion); @@ -5311,7 +5613,7 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { if (atLeastOneIteratorHasNext) { std::vector>> mutationsSingleVersion = - wait(getMutationsForVersion(iterators, minVersion)); + co_await getMutationsForVersion(iterators, minVersion); if (minVersion < begin) { // skip generating mutations, because this is not within desired range @@ -5334,52 +5636,52 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { TraceEvent(SevWarn, "FileRestoreLogReadError").error(e); mutationStream.sendError(e); } - return Void(); + co_return; } // Reads from all iterators and returns a list of mutations for the given version. - ACTOR static Future>>> getMutationsForVersion( + static Future>>> getMutationsForVersion( std::vector> iterators, Version minVersion) { - state std::vector>> mutationsSingleVersion; + std::vector>> mutationsSingleVersion; - state int k = 0; - for (; k < iterators.size(); k++) { + for (int k = 0; k < iterators.size(); k++) { if (!iterators[k]->hasNext()) { continue; } - Version v = wait(iterators[k]->peekNextVersion()); + Version v = co_await iterators[k]->peekNextVersion(); if (v == minVersion) { - Standalone> tmp = wait(iterators[k]->getNext()); + Standalone> tmp = co_await iterators[k]->getNext(); mutationsSingleVersion.push_back(tmp); } } - return mutationsSingleVersion; + co_return mutationsSingleVersion; } // Writes backup mutations to the database - ACTOR static Future writeMutations(Database cx, - std::vector>> mutations, - Key mutationLogPrefix, - Reference task, - Reference taskBucket) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state Standalone> oldFormatMutations; - state int mutationIndex = 0; - state int mutationCount = 0; - state int txBytes = 0; - state int txBytesLimit = CLIENT_KNOBS->RESTORE_WRITE_TX_SIZE; + static Future writeMutations(Database cx, + std::vector>> mutations, + Key mutationLogPrefix, + Reference task, + Reference taskBucket) { + Reference tr(new ReadYourWritesTransaction(cx)); + Standalone> oldFormatMutations; + int mutationIndex = 0; + int mutationCount = 0; + int txBytes = 0; + int txBytesLimit = CLIENT_KNOBS->RESTORE_WRITE_TX_SIZE; for (const auto& data : mutations) { oldFormatMutations.append(oldFormatMutations.arena(), data.begin(), data.size()); } - state int totalMutation = oldFormatMutations.size(); + int totalMutation = oldFormatMutations.size(); // multiple transactions are needed, so this has to be in a _execute method rather than // a _finish method // this transaction does blind writes, so they are idempotent operations even if multiple instances of // the same tasks are running these transactions. - loop { + while (true) { + Error err; try { if (mutationIndex == totalMutation) { break; @@ -5398,60 +5700,63 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { txBytes += v.expectedSize(); ++mutationCount; } - wait(taskBucket->keepRunning(tr, task)); - wait(tr->commit()); + co_await taskBucket->keepRunning(tr, task); + co_await tr->commit(); int64_t oldBytes = Params.bytesWritten().get(task); Params.bytesWritten().set(task, oldBytes + txBytes); DisabledTraceEvent("FileRestorePartitionedLogCommittData") - .detail("TaskInstance", THIS_ADDR) .detail("MutationIndex", mutationIndex) .detail("MutationCount", mutationCount) .detail("TotalMutation", totalMutation) .detail("Bytes", txBytes); mutationIndex += mutationCount; // update mutationIndex after the commit succeeds + continue; } catch (Error& e) { - if (e.code() == error_code_transaction_too_large) { - txBytesLimit /= 2; - } else { - wait(tr->onError(e)); - } + err = e; + } + if (err.code() == error_code_transaction_too_large) { + txBytesLimit /= 2; + } else { + co_await tr->onError(err); } } - return Void(); + co_return; } - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); - state int64_t maxTagID = Params.maxTagID().get(task); - state std::vector logs = Params.logs().get(task); - state Version begin = Params.beginVersion().get(task); - state Version end = Params.endVersion().get(task); + int64_t maxTagID = Params.maxTagID().get(task); + std::vector logs = Params.logs().get(task); + Version begin = Params.beginVersion().get(task); + Version end = Params.endVersion().get(task); - state Reference tr(new ReadYourWritesTransaction(cx)); - state Reference bc; - state std::vector ranges; // this is the actual KV, not version - loop { + Reference tr(new ReadYourWritesTransaction(cx)); + Reference bc; + std::vector ranges; // this is the actual KV, not version + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Reference _bc = wait(restore.sourceContainer().getOrThrow(tr)); + Reference _bc = co_await restore.sourceContainer().getOrThrow(tr); bc = getBackupContainerWithProxy(_bc); - wait(store(ranges, restore.getRestoreRangesOrDefault(tr))); + ranges = co_await restore.getRestoreRangesOrDefault(tr); - wait(checkTaskVersion(tr->getDatabase(), task, name, version)); - wait(taskBucket->keepRunning(tr, task)); + co_await checkTaskVersion(tr->getDatabase(), task, name, version); + co_await taskBucket->keepRunning(tr, task); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } std::vector> filesByTag(maxTagID + 1); @@ -5483,7 +5788,7 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { } } - state std::vector> iterators(maxTagID + 1); + std::vector> iterators(maxTagID + 1); // for each tag, create an iterator for (int k = 0; k < filesByTag.size(); k++) { iterators[k] = @@ -5493,55 +5798,56 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { DisabledTraceEvent("FileRestorePartitionedLogDataExeStart") .detail("BeginVersion", begin) .detail("EndVersion", end) - .detail("Files", logs.size()) - .detail("TaskInstance", THIS_ADDR); + .detail("Files", logs.size()); // Converted backup mutations - state PromiseStream>> mutationStream; - state Future reader = readLogData(mutationStream, iterators, begin, end); - - state std::vector>> mutations; - state int64_t totalBytes = 0; - state Standalone> oneVersionData; - loop { + PromiseStream>> mutationStream; + Future reader = readLogData(mutationStream, iterators, begin, end); + + std::vector>> mutations; + int64_t totalBytes = 0; + Standalone> oneVersionData; + while (true) { + Error err; try { - Standalone> _data = waitNext(mutationStream.getFuture()); + Standalone> _data = co_await mutationStream.getFuture(); oneVersionData = _data; // batching mutations from multiple versions together before writing to the database - state int64_t bytes = oneVersionData.expectedSize(); + int64_t bytes = oneVersionData.expectedSize(); if (totalBytes + bytes > CLIENT_KNOBS->RESTORE_WRITE_TX_SIZE) { - wait(writeMutations(cx, mutations, restore.mutationLogPrefix(), task, taskBucket)); + co_await writeMutations(cx, mutations, restore.mutationLogPrefix(), task, taskBucket); mutations.clear(); totalBytes = 0; } mutations.push_back(oneVersionData); totalBytes += bytes; + continue; } catch (Error& e) { - if (e.code() == error_code_end_of_stream) { - if (mutations.size() > 0) { - wait(writeMutations(cx, mutations, restore.mutationLogPrefix(), task, taskBucket)); - } - break; - } else { - throw; + err = e; + } + if (err.code() == error_code_end_of_stream) { + if (!mutations.empty()) { + co_await writeMutations(cx, mutations, restore.mutationLogPrefix(), task, taskBucket); } + break; + } else { + throw err; } } DisabledTraceEvent("FileRestorePartitionedLogDataExeDone") .detail("BeginVersion", begin) .detail("EndVersion", end) - .detail("Files", logs.size()) - .detail("TaskInstance", THIS_ADDR); + .detail("Files", logs.size()); - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state int64_t logBytesWritten = Params.bytesWritten().get(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + int64_t logBytesWritten = Params.bytesWritten().get(task); RestoreConfig(task).bytesWritten().atomicOp(tr, logBytesWritten, MutationRef::Type::AddValue); int64_t blocks = @@ -5551,32 +5857,31 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { RestoreConfig(task).fileBlocksFinished().atomicOp(tr, blocks, MutationRef::Type::AddValue); DisabledTraceEvent("FileRestorePartitionedLogCommittedData") - .detail("TaskInstance", THIS_ADDR) .detail("Blocks", blocks) .detail("LogBytes", logBytesWritten); - state Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - wait(taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); + Reference taskFuture = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + co_await (taskFuture->set(tr, taskBucket) && taskBucket->finish(tr, task)); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - int64_t maxTagID, - std::vector logs, - Version begin, - Version end, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task( + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + int64_t maxTagID, + std::vector logs, + Version begin, + Version end, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task( new Task(RestoreLogDataPartitionedTaskFunc::name, RestoreLogDataPartitionedTaskFunc::version, doneKey)); // Create a restore config from the current task and bind it to the new task. // RestoreConfig(parentTask) createsa prefix of : fileRestorePrefixRange.begin/uid->config/[uid] - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); Params.maxTagID().set(task, maxTagID); Params.beginVersion().set(task, begin); Params.endVersion().set(task, end); @@ -5584,11 +5889,11 @@ struct RestoreLogDataPartitionedTaskFunc : RestoreFileTaskFuncBase { Params.bytesWritten().set(task, 0); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } Future execute(Database cx, @@ -5621,27 +5926,27 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { static TaskParam endVersion() { return __FUNCTION__sr; } } Params; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); - state Version beginVersion = Params.beginVersion().get(task); - state Version firstVersion = Params.firstVersion().get(task); - state Version endVersion = Params.endVersion().get(task); - Reference _bc = wait(restore.sourceContainer().getOrThrow(tr)); - state Reference bc = getBackupContainerWithProxy(_bc); - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Version beginVersion = Params.beginVersion().get(task); + Version firstVersion = Params.firstVersion().get(task); + Version endVersion = Params.endVersion().get(task); + Reference _bc = co_await restore.sourceContainer().getOrThrow(tr); + Reference bc = getBackupContainerWithProxy(_bc); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - state Version restoreVersion; - state int fileLimit = 1000; + Version restoreVersion{ 0 }; + int fileLimit = 1000; - wait(store(restoreVersion, restore.restoreVersion().getOrThrow(tr)) && - checkTaskVersion(tr->getDatabase(), task, name, version)); + co_await (store(restoreVersion, restore.restoreVersion().getOrThrow(tr)) && + checkTaskVersion(tr->getDatabase(), task, name, version)); // if current is [40, 50] and restore version is 50, we need another [50, 51] task to process data at version 50 - state Version nextEndVersion = + Version nextEndVersion = std::min(restoreVersion + 1, endVersion + CLIENT_KNOBS->RESTORE_PARTITIONED_BATCH_VERSION_SIZE); // update the apply mutations end version so the mutations from the previous batch can be applied. // Only do this once beginVersion is > 0 (it will be 0 for the initial dispatch). @@ -5652,25 +5957,24 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { } // The applyLag must be retrieved AFTER potentially updating the apply end version. - state int64_t applyLag = wait(restore.getApplyVersionLag(tr)); + int64_t applyLag = co_await restore.getApplyVersionLag(tr); // this is to guarantee commit proxy is catching up doing apply alog -> normal key // with this backupFile -> alog process // If starting a new batch and the apply lag is too large then re-queue and wait if (applyLag > (BUGGIFY ? 1 : CLIENT_KNOBS->CORE_VERSIONSPERSECOND * 300)) { // Wait a small amount of time and then re-add this same task. - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(RestoreDispatchPartitionedTaskFunc::addTask( - tr, taskBucket, task, firstVersion, beginVersion, endVersion))); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await RestoreDispatchPartitionedTaskFunc::addTask( + tr, taskBucket, task, firstVersion, beginVersion, endVersion); TraceEvent("FileRestorePartitionDispatch") .detail("RestoreUID", restore.getUid()) .detail("BeginVersion", beginVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "too_far_behind") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "too_far_behind"); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } // Get a batch of files. We're targeting batchSize blocks(30k) being dispatched so query for batchSize(150) @@ -5684,34 +5988,34 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { // begin needs to start from the beginning -- // if we have a 3 files, (log,100,200), (range, 180), (log, 200, 300), then if we want to restore[190, 250] // it would stop at (range, 180) and not looking at (log, 100, 200) - state Optional beginLogInclude = Optional{}; + Optional beginLogInclude = Optional{}; // because of the encoding of RestoreFile, we use greaterThanOrEqual(end + 1) instead of greaterThan(end) // because RestoreFile::pack has the version at the most significant position, and keyAfter(end) does not result // in a end+1 - state Optional endLogExclude = wait( - restore.logFileSet().seekGreaterOrEqual(tr, RestoreConfig::RestoreFile({ endVersion + 1, "", false }))); - state RestoreConfig::FileSetT::RangeResultType logFiles = - wait(restore.logFileSet().getRange(tr, beginLogInclude, endLogExclude, fileLimit)); - state Optional beginRangeInclude = - wait(restore.rangeFileSet().seekGreaterOrEqual(tr, RestoreConfig::RestoreFile({ beginVersion, "", true }))); + Optional endLogExclude = co_await restore.logFileSet().seekGreaterOrEqual( + tr, RestoreConfig::RestoreFile({ endVersion + 1, "", false })); + RestoreConfig::FileSetT::RangeResultType logFiles = + co_await restore.logFileSet().getRange(tr, beginLogInclude, endLogExclude, fileLimit); + Optional beginRangeInclude = co_await restore.rangeFileSet().seekGreaterOrEqual( + tr, RestoreConfig::RestoreFile({ beginVersion, "", true })); // greaterThanOrEqual(end + 1) instead of greaterThan(end) // because RestoreFile::pack has the version at the most significant position, and keyAfter(end) does not result // in a end+1 - state Optional endRangeExclude = wait( - restore.rangeFileSet().seekGreaterOrEqual(tr, RestoreConfig::RestoreFile({ endVersion + 1, "", true }))); - state RestoreConfig::FileSetT::RangeResultType rangeFiles = - wait(restore.rangeFileSet().getRange(tr, beginRangeInclude, endRangeExclude, fileLimit)); - state int64_t maxTagID = 0; - state std::vector logs; - state std::vector ranges; - for (auto f : logFiles.results) { + Optional endRangeExclude = co_await restore.rangeFileSet().seekGreaterOrEqual( + tr, RestoreConfig::RestoreFile({ endVersion + 1, "", true })); + RestoreConfig::FileSetT::RangeResultType rangeFiles = + co_await restore.rangeFileSet().getRange(tr, beginRangeInclude, endRangeExclude, fileLimit); + int64_t maxTagID = 0; + std::vector logs; + std::vector ranges; + for (const auto& f : logFiles.results) { if (f.endVersion > beginVersion) { // skip all files whose endVersion is smaller or equal to beginVersion logs.push_back(f); maxTagID = std::max(maxTagID, f.tagId); } } - for (auto f : rangeFiles.results) { + for (const auto& f : rangeFiles.results) { // the getRange might get out-of-bound range file because log files need them to work if (f.version >= beginVersion && f.version < endVersion) { ranges.push_back(f); @@ -5719,7 +6023,7 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { } // allPartsDone will be set once all block tasks in the current batch are finished. // create a new future for the new batch - state Reference allPartsDone = futureBucket->future(tr); + Reference allPartsDone = futureBucket->future(tr); restore.batchFuture().set(tr, allPartsDone->pack()); // if there are no files, if i am not the last batch, then on to the next batch @@ -5729,41 +6033,39 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { if (applyLag == 0) { // i am the last batch // If apply lag is 0 then we are done so create the completion task - wait(success(RestoreCompleteTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()))); + co_await RestoreCompleteTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()); TraceEvent("FileRestorePartitionDispatch") .detail("RestoreUID", restore.getUid()) .detail("BeginVersion", beginVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "restore_complete") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "restore_complete"); } else { // i am the last batch, and applyLag is not zero, then I will create another dummy task to wait // for apply log to be zero, then it will go into the branch above. // Applying of mutations is not yet finished so wait a small amount of time and then re-add this // same task. // this is only to create a dummy one wait for it to finish - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(RestoreDispatchPartitionedTaskFunc::addTask( - tr, taskBucket, task, firstVersion, beginVersion, endVersion))); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await RestoreDispatchPartitionedTaskFunc::addTask( + tr, taskBucket, task, firstVersion, beginVersion, endVersion); TraceEvent("FileRestorePartitionDispatch") .detail("RestoreUID", restore.getUid()) .detail("BeginVersion", beginVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "apply_still_behind") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "apply_still_behind"); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } // if we reach here, this batch is not empty(i.e. we have range and/or mutation files in this) // Start moving through the file list and queuing up blocks. Only queue up to RESTORE_DISPATCH_ADDTASK_SIZE // blocks per Dispatch task and target batchSize total per batch but a batch must end on a complete version // boundary so exceed the limit if necessary to reach the end of a version of files. - state std::vector> addTaskFutures; - state int i = 0; + std::vector> addTaskFutures; + int i = 0; // need to process all range files, keep using the same RestoreRangeTaskFunc as non-partitioned restore. // this can be done first, because they are not overlap within a restore uid // each task will read the file, restore those key to their original keys after clear that range @@ -5798,8 +6100,8 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { TaskCompletionKey::noSignal(), allPartsDone)); - wait(waitForAll(addTaskFutures)); - wait(taskBucket->finish(tr, task)); + co_await waitForAll(addTaskFutures); + co_await taskBucket->finish(tr, task); TraceEvent("FileRestorePartitionDispatch") .detail("RestoreUID", restore.getUid()) @@ -5807,39 +6109,38 @@ struct RestoreDispatchPartitionedTaskFunc : RestoreTaskFuncBase { .detail("EndVersion", endVersion) .detail("NextEndVersion", nextEndVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "dispatch_batch_complete") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "dispatch_batch_complete"); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version firstVersion, - Version beginVersion, - Version endVersion, - TaskCompletionKey completionKey = TaskCompletionKey::noSignal(), - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version firstVersion, + Version beginVersion, + Version endVersion, + TaskCompletionKey completionKey = TaskCompletionKey::noSignal(), + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); // Use high priority for dispatch tasks that have to queue more blocks for the current batch unsigned int priority = 0; - state Reference task(new Task( + Reference task(new Task( RestoreDispatchPartitionedTaskFunc::name, RestoreDispatchPartitionedTaskFunc::version, doneKey, priority)); // Create a config from the parent task and bind it to the new task - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); Params.firstVersion().set(task, firstVersion); Params.beginVersion().set(task, beginVersion); Params.endVersion().set(task, endVersion); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } Future execute(Database cx, @@ -5871,21 +6172,21 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { static TaskParam remainingInBatch() { return __FUNCTION__sr; } } Params; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); - state Version beginVersion = Params.beginVersion().get(task); - state Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); + Version beginVersion = Params.beginVersion().get(task); + Reference onDone = futureBucket->unpack(task->params[Task::reservedTaskParamKeyDone]); - state int64_t remainingInBatch = Params.remainingInBatch().get(task); - state bool addingToExistingBatch = remainingInBatch > 0; - state Version restoreVersion; + int64_t remainingInBatch = Params.remainingInBatch().get(task); + bool addingToExistingBatch = remainingInBatch > 0; + Version restoreVersion{ 0 }; - wait(store(restoreVersion, restore.restoreVersion().getOrThrow(tr)) && - checkTaskVersion(tr->getDatabase(), task, name, version)); + co_await (store(restoreVersion, restore.restoreVersion().getOrThrow(tr)) && + checkTaskVersion(tr->getDatabase(), task, name, version)); // If not adding to an existing batch then update the apply mutations end version so the mutations from the // previous batch can be applied. Only do this once beginVersion is > 0 (it will be 0 for the initial @@ -5898,43 +6199,42 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { } // The applyLag must be retrieved AFTER potentially updating the apply end version. - state int64_t applyLag = wait(restore.getApplyVersionLag(tr)); - state int64_t batchSize = Params.batchSize().get(task); + int64_t applyLag = co_await restore.getApplyVersionLag(tr); + int64_t batchSize = Params.batchSize().get(task); // If starting a new batch and the apply lag is too large then re-queue and wait if (!addingToExistingBatch && applyLag > (BUGGIFY ? 1 : CLIENT_KNOBS->CORE_VERSIONSPERSECOND * 300)) { // Wait a small amount of time and then re-add this same task. - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(RestoreDispatchTaskFunc::addTask( - tr, taskBucket, task, beginVersion, "", 0, batchSize, remainingInBatch))); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await RestoreDispatchTaskFunc::addTask( + tr, taskBucket, task, beginVersion, "", 0, batchSize, remainingInBatch); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) .detail("BeginVersion", beginVersion) .detail("ApplyLag", applyLag) .detail("BatchSize", batchSize) - .detail("Decision", "too_far_behind") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "too_far_behind"); - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } // need beginFile to handle stop in the middle of version case - state std::string beginFile = Params.beginFile().getOrDefault(task); + std::string beginFile = Params.beginFile().getOrDefault(task); // Get a batch of files. We're targeting batchSize blocks being dispatched so query for batchSize files // (each of which is 0 or more blocks). - state int taskBatchSize = BUGGIFY ? 1 : CLIENT_KNOBS->RESTORE_DISPATCH_ADDTASK_SIZE; - state RestoreConfig::FileSetT::RangeResultType files = wait(restore.fileSet().getRange( - tr, Optional({ beginVersion, beginFile }), {}, taskBatchSize)); + int taskBatchSize = BUGGIFY ? 1 : CLIENT_KNOBS->RESTORE_DISPATCH_ADDTASK_SIZE; + RestoreConfig::FileSetT::RangeResultType files = co_await restore.fileSet().getRange( + tr, Optional({ beginVersion, beginFile }), {}, taskBatchSize); // allPartsDone will be set once all block tasks in the current batch are finished. - state Reference allPartsDone; + Reference allPartsDone; // If adding to existing batch then join the new block tasks to the existing batch future if (addingToExistingBatch) { - Key fKey = wait(restore.batchFuture().getD(tr)); - allPartsDone = Reference(new TaskFuture(futureBucket, fKey)); + Key fKey = co_await restore.batchFuture().getD(tr); + allPartsDone = makeReference(futureBucket, fKey); } else { // Otherwise create a new future for the new batch allPartsDone = futureBucket->future(tr); @@ -5944,22 +6244,22 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { } // If there were no files to load then this batch is done and restore is almost done. - if (files.results.size() == 0) { + if (files.results.empty()) { // If adding to existing batch then blocks could be in progress so create a new Dispatch task that waits // for them to finish if (addingToExistingBatch) { // Setting next begin to restoreVersion + 1 so that any files in the file map at the restore version // won't be dispatched again. - wait(success(RestoreDispatchTaskFunc::addTask(tr, - taskBucket, - task, - restoreVersion + 1, - "", - 0, - batchSize, - 0, - TaskCompletionKey::noSignal(), - allPartsDone))); + co_await RestoreDispatchTaskFunc::addTask(tr, + taskBucket, + task, + restoreVersion + 1, + "", + 0, + batchSize, + 0, + TaskCompletionKey::noSignal(), + allPartsDone); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) @@ -5968,12 +6268,11 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { .detail("BeginBlock", Params.beginBlock().get(task)) .detail("RestoreVersion", restoreVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "end_of_final_batch") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "end_of_final_batch"); } else if (beginVersion < restoreVersion) { // If beginVersion is less than restoreVersion then do one more dispatch task to get there // there are no more files between beginVersion and restoreVersion - wait(success(RestoreDispatchTaskFunc::addTask(tr, taskBucket, task, restoreVersion, "", 0, batchSize))); + co_await RestoreDispatchTaskFunc::addTask(tr, taskBucket, task, restoreVersion, "", 0, batchSize); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) @@ -5982,11 +6281,10 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { .detail("BeginBlock", Params.beginBlock().get(task)) .detail("RestoreVersion", restoreVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "apply_to_restore_version") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "apply_to_restore_version"); } else if (applyLag == 0) { // If apply lag is 0 then we are done so create the completion task - wait(success(RestoreCompleteTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()))); + co_await RestoreCompleteTaskFunc::addTask(tr, taskBucket, task, TaskCompletionKey::noSignal()); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) @@ -5994,21 +6292,19 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { .detail("BeginFile", Params.beginFile().get(task)) .detail("BeginBlock", Params.beginBlock().get(task)) .detail("ApplyLag", applyLag) - .detail("Decision", "restore_complete") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "restore_complete"); } else { // Applying of mutations is not yet finished so wait a small amount of time and then re-add this // same task. // this is only to create a dummy one wait for it to finish - wait(delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - wait(success(RestoreDispatchTaskFunc::addTask(tr, taskBucket, task, beginVersion, "", 0, batchSize))); + co_await delay(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + co_await RestoreDispatchTaskFunc::addTask(tr, taskBucket, task, beginVersion, "", 0, batchSize); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) .detail("BeginVersion", beginVersion) .detail("ApplyLag", applyLag) - .detail("Decision", "apply_still_behind") - .detail("TaskInstance", THIS_ADDR); + .detail("Decision", "apply_still_behind"); } // If adding to existing batch then task is joined with a batch future so set done future @@ -6016,18 +6312,18 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { // blockers already finished. Future setDone = addingToExistingBatch ? onDone->set(tr, taskBucket) : Void(); - wait(taskBucket->finish(tr, task) && setDone); - return Void(); + co_await (taskBucket->finish(tr, task) && setDone); + co_return; } // Start moving through the file list and queuing up blocks. Only queue up to RESTORE_DISPATCH_ADDTASK_SIZE // blocks per Dispatch task and target batchSize total per batch but a batch must end on a complete version // boundary so exceed the limit if necessary to reach the end of a version of files. - state std::vector> addTaskFutures; - state Version endVersion = files.results[0].version; - state int blocksDispatched = 0; - state int64_t beginBlock = Params.beginBlock().getOrDefault(task); - state int i = 0; + std::vector> addTaskFutures; + Version endVersion = files.results[0].version; + int blocksDispatched = 0; + int64_t beginBlock = Params.beginBlock().getOrDefault(task); + int i = 0; // for each file // not creating a new task at this level because restore files are read back together -- both range and log @@ -6053,7 +6349,7 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { endVersion = f.version; beginFile = f.fileName; - state int64_t j = beginBlock * f.blockSize; + int64_t j = beginBlock * f.blockSize; // For each block of the file for (; j < f.fileSize; j += f.blockSize) { // Stop if we've reached the addtask limit @@ -6098,8 +6394,7 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { TraceEvent("FileRestoreDispatchedFile") .suppressFor(60) .detail("RestoreUID", restore.getUid()) - .detail("FileName", f.fileName) - .detail("TaskInstance", THIS_ADDR); + .detail("FileName", f.fileName); } // If no blocks were dispatched then the next dispatch task should run now and be joined with the @@ -6124,27 +6419,26 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { .detail("ApplyLag", applyLag) .detail("BatchSize", batchSize) .detail("Decision", decision) - .detail("TaskInstance", THIS_ADDR) .detail("RemainingInBatch", remainingInBatch); - wait(success(RestoreDispatchTaskFunc::addTask(tr, - taskBucket, - task, - endVersion, - beginFile, - beginBlock, - batchSize, - remainingInBatch, - TaskCompletionKey::joinWith((allPartsDone))))); + co_await RestoreDispatchTaskFunc::addTask(tr, + taskBucket, + task, + endVersion, + beginFile, + beginBlock, + batchSize, + remainingInBatch, + TaskCompletionKey::joinWith((allPartsDone))); // If adding to existing batch then task is joined with a batch future so set done future. // Note that this must be done after joining at least one task with the batch future in case all other // blockers already finished. Future setDone = addingToExistingBatch ? onDone->set(tr, taskBucket) : Void(); - wait(setDone && taskBucket->finish(tr, task)); + co_await (setDone && taskBucket->finish(tr, task)); - return Void(); + co_return; } // Increment the number of blocks dispatched in the restore config @@ -6184,12 +6478,12 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { TaskCompletionKey::noSignal(), allPartsDone)); - wait(waitForAll(addTaskFutures)); + co_await waitForAll(addTaskFutures); // If adding to existing batch then task is joined with a batch future so set done future. Future setDone = addingToExistingBatch ? onDone->set(tr, taskBucket) : Void(); - wait(setDone && taskBucket->finish(tr, task)); + co_await (setDone && taskBucket->finish(tr, task)); TraceEvent("FileRestoreDispatch") .detail("RestoreUID", restore.getUid()) @@ -6202,31 +6496,30 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { .detail("Decision", "dispatched_files") .detail("FilesDispatched", i) .detail("BlocksDispatched", blocksDispatched) - .detail("TaskInstance", THIS_ADDR) .detail("RemainingInBatch", remainingInBatch); - return Void(); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - Reference parentTask, - Version beginVersion, - std::string beginFile, - int64_t beginBlock, - int64_t batchSize, - int64_t remainingInBatch = 0, - TaskCompletionKey completionKey = TaskCompletionKey::noSignal(), - Reference waitFor = Reference()) { - Key doneKey = wait(completionKey.get(tr, taskBucket)); + static Future addTask(Reference tr, + Reference taskBucket, + Reference parentTask, + Version beginVersion, + std::string beginFile, + int64_t beginBlock, + int64_t batchSize, + int64_t remainingInBatch = 0, + TaskCompletionKey completionKey = TaskCompletionKey::noSignal(), + Reference waitFor = Reference()) { + Key doneKey = co_await completionKey.get(tr, taskBucket); // Use high priority for dispatch tasks that have to queue more blocks for the current batch - unsigned int priority = (remainingInBatch > 0) ? 1 : 0; - state Reference task( + auto priority = (remainingInBatch > 0) ? 1u : 0u; + Reference task( new Task(RestoreDispatchTaskFunc::name, RestoreDispatchTaskFunc::version, doneKey, priority)); // Create a config from the parent task and bind it to the new task - wait(RestoreConfig(parentTask).toTask(tr, task)); + co_await RestoreConfig(parentTask).toTask(tr, task); Params.beginVersion().set(task, beginVersion); Params.batchSize().set(task, batchSize); Params.remainingInBatch().set(task, remainingInBatch); @@ -6234,11 +6527,11 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { Params.beginFile().set(task, beginFile); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } Future execute(Database cx, @@ -6257,48 +6550,53 @@ struct RestoreDispatchTaskFunc : RestoreTaskFuncBase { StringRef RestoreDispatchTaskFunc::name = "restore_dispatch"_sr; REGISTER_TASKFUNC(RestoreDispatchTaskFunc); -ACTOR Future restoreStatus(Reference tr, Key tagName) { +Future restoreStatus(Reference tr, Key tagName) { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state std::vector tags; - if (tagName.size() == 0) { - std::vector t = wait(getAllRestoreTags(tr)); + std::vector tags; + if (tagName.empty()) { + std::vector t = co_await getAllRestoreTags(tr); tags = t; } else tags.push_back(makeRestoreTag(tagName.toString())); - state std::string result; - state int i = 0; + // If no tags found, return helpful message + if (tags.empty()) { + co_return "No restores found.\n\n" + "To start a restore:\n" + " fdbrestore start -r [-t ]\n"; + } - for (; i < tags.size(); ++i) { - UidAndAbortedFlagT u = wait(tags[i].getD(tr)); - std::string s = wait(RestoreConfig(u.first).getFullStatus(tr)); + std::string result; + for (int i = 0; i < tags.size(); ++i) { + UidAndAbortedFlagT u = co_await tags[i].getD(tr); + std::string s = co_await RestoreConfig(u.first).getFullStatus(tr); result.append(s); result.append("\n\n"); } - return result; + co_return result; } -ACTOR Future abortRestore(Reference tr, Key tagName) { +Future abortRestore(Reference tr, Key tagName) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); - state KeyBackedTag tag = makeRestoreTag(tagName.toString()); - state Optional current = wait(tag.get(tr)); + KeyBackedTag tag = makeRestoreTag(tagName.toString()); + Optional current = co_await tag.get(tr); if (!current.present()) - return ERestoreState::UNINITIALIZED; + co_return ERestoreState::UNINITIALIZED; - state RestoreConfig restore(current.get().first); + RestoreConfig restore(current.get().first); - state ERestoreState status = wait(restore.stateEnum().getD(tr)); - state bool runnable = wait(restore.isRunnable(tr)); + ERestoreState status = co_await restore.stateEnum().getD(tr); + bool runnable = co_await restore.isRunnable(tr); if (!runnable) - return status; + co_return status; restore.stateEnum().set(tr, ERestoreState::ABORTED); @@ -6306,45 +6604,48 @@ ACTOR Future abortRestore(Reference tr restore.clearApplyMutationsKeys(tr); // Cancel the backup tasks on this tag - wait(tag.cancel(tr)); + co_await tag.cancel(tr); - wait(unlockDatabase(tr, current.get().first)); - return ERestoreState::ABORTED; + co_await unlockDatabase(tr, current.get().first); + co_return ERestoreState::ABORTED; } -ACTOR Future abortRestore(Database cx, Key tagName) { - state Reference tr = - Reference(new ReadYourWritesTransaction(cx)); +Future abortRestore(Database cx, Key tagName) { + auto tr = makeReference(cx); - loop { + while (true) { + Error err; try { - ERestoreState estate = wait(abortRestore(tr, tagName)); + ERestoreState estate = co_await abortRestore(tr, tagName); if (estate != ERestoreState::ABORTED) { - return estate; + co_return estate; } - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - tr = Reference(new ReadYourWritesTransaction(cx)); + tr = makeReference(cx); // Commit a dummy transaction before returning success, to ensure the mutation applier has stopped submitting // mutations - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); tr->addReadConflictRange(singleKeyRange(KeyRef())); tr->addWriteConflictRange(singleKeyRange(KeyRef())); - wait(tr->commit()); - return ERestoreState::ABORTED; + co_await tr->commit(); + co_return ERestoreState::ABORTED; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } @@ -6358,95 +6659,100 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { // Find all files needed for the restore and save them in the RestoreConfig for the task. // Update the total number of files and blocks and change state to starting. - ACTOR static Future _execute(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state RestoreConfig restore(task); - state Version restoreVersion; - state Version beginVersion; - state Reference bc; - state std::vector ranges; - state bool logsOnly; - state bool inconsistentSnapshotOnly; - - loop { + static Future _execute(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference tr(new ReadYourWritesTransaction(cx)); + RestoreConfig restore(task); + Version restoreVersion{ 0 }; + Version beginVersion{ 0 }; + Reference bc; + std::vector ranges; + bool logsOnly{ false }; + bool inconsistentSnapshotOnly{ false }; + + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(checkTaskVersion(tr->getDatabase(), task, name, version)); - wait(store(beginVersion, restore.beginVersion().getD(tr, Snapshot::False, ::invalidVersion))); + co_await checkTaskVersion(tr->getDatabase(), task, name, version); + beginVersion = co_await restore.beginVersion().getD(tr, Snapshot::False, ::invalidVersion); - wait(store(restoreVersion, restore.restoreVersion().getOrThrow(tr))); - wait(store(ranges, restore.getRestoreRangesOrDefault(tr))); - wait(store(logsOnly, restore.onlyApplyMutationLogs().getD(tr, Snapshot::False, false))); - wait(store(inconsistentSnapshotOnly, - restore.inconsistentSnapshotOnly().getD(tr, Snapshot::False, false))); - wait(taskBucket->keepRunning(tr, task)); + restoreVersion = co_await restore.restoreVersion().getOrThrow(tr); + ranges = co_await restore.getRestoreRangesOrDefault(tr); + logsOnly = co_await restore.onlyApplyMutationLogs().getD(tr, Snapshot::False, false); + inconsistentSnapshotOnly = co_await restore.inconsistentSnapshotOnly().getD(tr, Snapshot::False, false); + co_await taskBucket->keepRunning(tr, task); - ERestoreState oldState = wait(restore.stateEnum().getD(tr)); + ERestoreState oldState = co_await restore.stateEnum().getD(tr); if (oldState != ERestoreState::QUEUED && oldState != ERestoreState::STARTING) { - wait(restore.logError(cx, - restore_error(), - format("StartFullRestore: Encountered unexpected state(%d)", oldState), - THIS)); - return Void(); + co_await restore.logError(cx, + restore_error(), + format("StartFullRestore: Encountered unexpected state(%d)", oldState), + nullptr); + co_return; } restore.stateEnum().set(tr, ERestoreState::STARTING); restore.fileSet().clear(tr); restore.fileBlockCount().clear(tr); restore.fileCount().clear(tr); - Reference _bc = wait(restore.sourceContainer().getOrThrow(tr)); + Reference _bc = co_await restore.sourceContainer().getOrThrow(tr); bc = getBackupContainerWithProxy(_bc); - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } tr->reset(); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Version destVersion = wait(tr->getReadVersion()); + Version destVersion = co_await tr->getReadVersion(); TraceEvent("FileRestoreVersionUpgrade") .detail("RestoreVersion", restoreVersion) .detail("Dest", destVersion); if (destVersion <= restoreVersion) { CODE_PROBE(true, "Forcing restored cluster to higher version"); tr->set(minRequiredCommitVersionKey, BinaryWriter::toValue(restoreVersion + 1, Unversioned())); - wait(tr->commit()); + co_await tr->commit(); + tr->reset(); + continue; } else { break; } } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state Version firstConsistentVersion = invalidVersion; + Version firstConsistentVersion = invalidVersion; if (beginVersion == invalidVersion) { beginVersion = 0; } - state Standalone> keyRangesFilter; + Standalone> keyRangesFilter; for (auto const& r : ranges) { keyRangesFilter.push_back_deep(keyRangesFilter.arena(), KeyRangeRef(r)); } - state Optional restorable = - wait(bc->getRestoreSet(restoreVersion, keyRangesFilter, logsOnly, beginVersion)); + Optional restorable = + co_await bc->getRestoreSet(restoreVersion, keyRangesFilter, logsOnly, beginVersion); if (!restorable.present()) throw restore_missing_data(); // Convert the two lists in restorable (logs and ranges) to a single list of RestoreFiles. // Order does not matter, they will be put in order when written to the restoreFileMap below. - state std::vector files; - state std::vector logFiles; - state std::vector rangeFiles; + std::vector files; + std::vector logFiles; + std::vector rangeFiles; if (!logsOnly) { beginVersion = restorable.get().snapshot.beginVersion; @@ -6488,35 +6794,38 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { Params.firstVersion().set(task, beginVersion); tr->reset(); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); restore.firstConsistentVersion().set(tr, firstConsistentVersion); - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // add log files - state std::vector::iterator logStart = logFiles.begin(); - state std::vector::iterator logEnd = logFiles.end(); - state int txBytes = 0; + auto logStart = logFiles.begin(); + auto logEnd = logFiles.end(); + int txBytes = 0; tr->reset(); while (logStart != logEnd) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); - state std::vector::iterator logIt = logStart; + auto logIt = logStart; txBytes = 0; - state int logFileCount = 0; + int logFileCount = 0; auto fileSet = restore.logFileSet(); // TODO: split files into multiple keys, because files can be in the order of 10k or 100k, which // probably can't fit due to size limit for a value in FDB. as a result, fileSet has everything, @@ -6525,36 +6834,38 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { txBytes += fileSet.insert(tr, *logIt); ++logFileCount; } - wait(tr->commit()); + co_await tr->commit(); TraceEvent("FileRestoreLoadedLogFiles") .detail("RestoreUID", restore.getUid()) .detail("FileCount", logFileCount) - .detail("TransactionBytes", txBytes) - .detail("TaskInstance", THIS_ADDR); + .detail("TransactionBytes", txBytes); logStart = logIt; tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state std::vector::iterator rangeStart = rangeFiles.begin(); - state std::vector::iterator rangeEnd = rangeFiles.end(); + auto rangeStart = rangeFiles.begin(); + auto rangeEnd = rangeFiles.end(); tr->reset(); while (rangeStart != rangeEnd) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); - state std::vector::iterator rangeIt = rangeStart; + auto rangeIt = rangeStart; txBytes = 0; - state int rangeFileCount = 0; + int rangeFileCount = 0; auto fileSet = restore.rangeFileSet(); // as a result, fileSet has everything, including [beginVersion, endVersion] for each tag for (; rangeIt != rangeEnd && txBytes < 1e6; ++rangeIt) { @@ -6562,38 +6873,40 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { // handle the remaining ++rangeFileCount; } - wait(tr->commit()); + co_await tr->commit(); TraceEvent("FileRestoreLoadedRangeFiles") .detail("RestoreUID", restore.getUid()) .detail("FileCount", rangeFileCount) - .detail("TransactionBytes", txBytes) - .detail("TaskInstance", THIS_ADDR); + .detail("TransactionBytes", txBytes); rangeStart = rangeIt; tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } // add files - state std::vector::iterator start = files.begin(); - state std::vector::iterator end = files.end(); + auto start = files.begin(); + auto end = files.end(); tr->reset(); while (start != end) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); - state std::vector::iterator it = start; + auto it = start; txBytes = 0; - state int nFileBlocks = 0; - state int nFiles = 0; + int nFileBlocks = 0; + int nFiles = 0; auto fileSet = restore.fileSet(); // as a result, fileSet has everything, including [beginVersion, endVersion] for each tag for (; it != end && txBytes < 1e6; ++it) { @@ -6606,48 +6919,49 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { restore.fileCount().atomicOp(tr, nFiles, MutationRef::Type::AddValue); restore.fileBlockCount().atomicOp(tr, nFileBlocks, MutationRef::Type::AddValue); - wait(tr->commit()); + co_await tr->commit(); TraceEvent("FileRestoreLoadedFiles") .detail("RestoreUID", restore.getUid()) .detail("FileCount", nFiles) .detail("FileBlockCount", nFileBlocks) - .detail("TransactionBytes", txBytes) - .detail("TaskInstance", THIS_ADDR); + .detail("TransactionBytes", txBytes); start = it; tr->reset(); + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return Void(); + co_return; } - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state RestoreConfig restore(task); - state bool transformPartitionedLog; - state Version restoreVersion; - state Version firstVersion = Params.firstVersion().getOrDefault(task, invalidVersion); - state bool useRangeFileRestore = false; + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + RestoreConfig restore(task); + MutationLogType mutationLogType{ MutationLogType::DEFAULT }; + Version restoreVersion{ 0 }; + Version firstVersion = Params.firstVersion().getOrDefault(task, invalidVersion); + bool useRangeFileRestore = false; if (firstVersion == invalidVersion) { - wait(restore.logError( - tr->getDatabase(), restore_missing_data(), "StartFullRestore: The backup had no data.", THIS)); - std::string tag = wait(restore.tag().getD(tr)); - wait(success(abortRestore(tr, StringRef(tag)))); - return Void(); + co_await restore.logError( + tr->getDatabase(), restore_missing_data(), "StartFullRestore: The backup had no data.", nullptr); + std::string tag = co_await restore.tag().getD(tr); + co_await abortRestore(tr, StringRef(tag)); + co_return; } restore.stateEnum().set(tr, ERestoreState::RUNNING); // Check if using traditional rangefile restore instead of BulkLoad // Default to true (traditional rangefile restore) for backward compatibility - state Optional rangeFileRestore = wait(restore.useRangeFileRestore().get(tr)); + Optional rangeFileRestore = co_await restore.useRangeFileRestore().get(tr); useRangeFileRestore = !rangeFileRestore.present() || rangeFileRestore.get(); // Set applyMutation versions @@ -6655,21 +6969,21 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { restore.setApplyEndVersion(tr, firstVersion); // Apply range data using either BulkLoad or traditional range file restore - wait(store(transformPartitionedLog, restore.transformPartitionedLog().getD(tr, Snapshot::False, false))); - wait(store(restoreVersion, restore.restoreVersion().getOrThrow(tr))); + mutationLogType = co_await restore.mutationLogType().getD(tr, Snapshot::False, MutationLogType::DEFAULT); + restoreVersion = co_await restore.restoreVersion().getOrThrow(tr); if (!useRangeFileRestore) { // Use BulkLoad for range data restoration, then apply logs - state Reference bc = wait(restore.sourceContainer().getOrThrow(tr)); + Reference bc = co_await restore.sourceContainer().getOrThrow(tr); // Get the BulkDump job ID - this should have been stored by the backup // when it completed the BulkDump phase - state Optional bulkDumpJobIdOpt = wait(restore.bulkDumpJobId().get(tr)); - state std::string bulkDumpJobId = bulkDumpJobIdOpt.present() ? bulkDumpJobIdOpt.get() : ""; + Optional bulkDumpJobIdOpt = co_await restore.bulkDumpJobId().get(tr); + std::string bulkDumpJobId = bulkDumpJobIdOpt.present() ? bulkDumpJobIdOpt.get() : ""; // Save original BulkLoad mode BEFORE creating the task, so it's persisted in the database. // This allows crash recovery to restore the correct mode even if the task restarts. - int currentBulkLoadMode = wait(getBulkLoadMode(tr->getDatabase())); + int currentBulkLoadMode = co_await getBulkLoadMode(tr->getDatabase()); restore.originalBulkLoadMode().set(tr, currentBulkLoadMode); TraceEvent("StartFullRestoreUsingBulkLoad") @@ -6679,80 +6993,79 @@ struct StartFullRestoreTaskFunc : RestoreTaskFuncBase { .detail("RestoreVersion", restoreVersion); // Create a future that BulkLoad will signal when done - state Reference bulkLoadDone = futureBucket->future(tr); + Reference bulkLoadDone = futureBucket->future(tr); // Add BulkLoad task for range data - wait(success(BulkLoadRestoreTaskFunc::addTask(tr, - taskBucket, - task, - restoreVersion, - bc->getURL(), - bulkDumpJobId, - TaskCompletionKey::signal(bulkLoadDone)))); + co_await BulkLoadRestoreTaskFunc::addTask(tr, + taskBucket, + task, + restoreVersion, + bc->getURL(), + bulkDumpJobId, + TaskCompletionKey::signal(bulkLoadDone)); // After BulkLoad completes, run RestoreDispatch to apply mutation logs // Set onlyApplyMutationLogs so it only processes logs, not range files restore.onlyApplyMutationLogs().set(tr, true); // Add RestoreDispatch task that waits for BulkLoad to complete - wait(success(RestoreDispatchTaskFunc::addTask(tr, - taskBucket, - task, - 0, - "", - 0, - CLIENT_KNOBS->RESTORE_DISPATCH_BATCH_SIZE, - 0, - TaskCompletionKey::noSignal(), - bulkLoadDone))); - } else if (transformPartitionedLog) { + co_await RestoreDispatchTaskFunc::addTask(tr, + taskBucket, + task, + 0, + "", + 0, + CLIENT_KNOBS->RESTORE_DISPATCH_BATCH_SIZE, + 0, + TaskCompletionKey::noSignal(), + bulkLoadDone); + } else if (mutationLogType == MutationLogType::PARTITIONED_LOG) { // Traditional restore with partitioned logs Version endVersion = std::min(firstVersion + CLIENT_KNOBS->RESTORE_PARTITIONED_BATCH_VERSION_SIZE, restoreVersion); - wait(success(RestoreDispatchPartitionedTaskFunc::addTask( - tr, taskBucket, task, firstVersion, firstVersion, endVersion))); + co_await RestoreDispatchPartitionedTaskFunc::addTask( + tr, taskBucket, task, firstVersion, firstVersion, endVersion); } else { // Traditional restore with non-partitioned logs - wait(success(RestoreDispatchTaskFunc::addTask( - tr, taskBucket, task, 0, "", 0, CLIENT_KNOBS->RESTORE_DISPATCH_BATCH_SIZE))); + co_await RestoreDispatchTaskFunc::addTask( + tr, taskBucket, task, 0, "", 0, CLIENT_KNOBS->RESTORE_DISPATCH_BATCH_SIZE); } // Initialize apply mutations map. - state Future> logsOnly = restore.onlyApplyMutationLogs().get(tr); - wait(success(logsOnly)); + Future> logsOnly = restore.onlyApplyMutationLogs().get(tr); + co_await logsOnly; if (logsOnly.get().present() && logsOnly.get().get()) { // If this is an incremental restore, we need to set the applyMutationsMapPrefix // to the earliest log version so no mutations are missed Value versionEncoded = BinaryWriter::toValue(Params.firstVersion().get(task), Unversioned()); - wait(krmSetRange(tr, restore.applyMutationsMapPrefix(), normalKeys, versionEncoded)); + co_await krmSetRange(tr, restore.applyMutationsMapPrefix(), normalKeys, versionEncoded); } - wait(taskBucket->finish(tr, task)); - return Void(); + co_await taskBucket->finish(tr, task); + co_return; } - ACTOR static Future addTask(Reference tr, - Reference taskBucket, - UID uid, - TaskCompletionKey completionKey, - Reference waitFor = Reference()) { + static Future addTask(Reference tr, + Reference taskBucket, + UID uid, + TaskCompletionKey completionKey, + Reference waitFor = Reference()) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Key doneKey = wait(completionKey.get(tr, taskBucket)); - state Reference task( - new Task(StartFullRestoreTaskFunc::name, StartFullRestoreTaskFunc::version, doneKey)); + Key doneKey = co_await completionKey.get(tr, taskBucket); + Reference task(new Task(StartFullRestoreTaskFunc::name, StartFullRestoreTaskFunc::version, doneKey)); - state RestoreConfig restore(uid); + RestoreConfig restore(uid); // Bind the restore config to the new task - wait(restore.toTask(tr, task)); + co_await restore.toTask(tr, task); if (!waitFor) { - return taskBucket->addTask(tr, task); + co_return taskBucket->addTask(tr, task); } - wait(waitFor->onSetAddTask(tr, taskBucket, task)); - return "OnSetAddTask"_sr; + co_await waitFor->onSetAddTask(tr, taskBucket, task); + co_return "OnSetAddTask"_sr; } StringRef getName() const override { return name; }; @@ -6786,176 +7099,32 @@ struct LogInfo : public ReferenceCounted { class FileBackupAgentImpl { public: - // Parallel restore - ACTOR static Future parallelRestoreFinish(Database cx, UID randomUID, UnlockDB unlockDB = UnlockDB::True) { - state ReadYourWritesTransaction tr(cx); - state Optional restoreRequestDoneKeyValue; - TraceEvent("FastRestoreToolWaitForRestoreToFinish").detail("DBLock", randomUID); - // TODO: register watch first and then check if the key exist - loop { - try { - tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr.setOption(FDBTransactionOptions::LOCK_AWARE); - Optional _restoreRequestDoneKeyValue = wait(tr.get(restoreRequestDoneKey)); - restoreRequestDoneKeyValue = _restoreRequestDoneKeyValue; - // Restore may finish before restoreTool waits on the restore finish event. - if (restoreRequestDoneKeyValue.present()) { - break; - } else { - state Future watchForRestoreRequestDone = tr.watch(restoreRequestDoneKey); - wait(tr.commit()); - wait(watchForRestoreRequestDone); - break; - } - } catch (Error& e) { - wait(tr.onError(e)); - } - } - - TraceEvent("FastRestoreToolRestoreFinished") - .detail("ClearRestoreRequestDoneKey", restoreRequestDoneKeyValue.present()); - // Only this agent can clear the restoreRequestDoneKey - wait(runRYWTransaction(cx, [](Reference tr) -> Future { - tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr->setOption(FDBTransactionOptions::LOCK_AWARE); - tr->clear(restoreRequestDoneKey); - return Void(); - })); - - if (unlockDB) { - TraceEvent("FastRestoreToolRestoreFinished").detail("UnlockDBStart", randomUID); - wait(unlockDatabase(cx, randomUID)); - TraceEvent("FastRestoreToolRestoreFinished").detail("UnlockDBFinish", randomUID); - } else { - TraceEvent("FastRestoreToolRestoreFinished").detail("DBLeftLockedAfterRestore", randomUID); - } - - return Void(); - } - - ACTOR static Future submitParallelRestore(Database cx, - Key backupTag, - Standalone> backupRanges, - Key bcUrl, - Optional proxy, - Version targetVersion, - LockDB lockDB, - UID randomUID, - Key addPrefix, - Key removePrefix) { - // Sanity check backup is valid - state std::string bcUrlStr = bcUrl.toString(); - state Reference bc = IBackupContainer::openContainer(bcUrlStr, proxy, {}); - // For blobstore:// URLs, use invalidVersion to allow describeBackup to write missing version properties - // This is needed for S3 where metadata may not be immediately consistent - state BackupDescription desc = wait(bc->describeBackup(false, isBlobstoreUrl(bcUrlStr) ? invalidVersion : 0)); - wait(desc.resolveVersionTimes(cx)); - - if (targetVersion == invalidVersion && desc.maxRestorableVersion.present()) { - targetVersion = desc.maxRestorableVersion.get(); - TraceEvent(SevWarn, "FastRestoreSubmitRestoreRequestWithInvalidTargetVersion") - .detail("OverrideTargetVersion", targetVersion); - } - - Optional restoreSet = wait(bc->getRestoreSet(targetVersion)); - - if (!restoreSet.present()) { - TraceEvent(SevWarn, "FileBackupAgentRestoreNotPossible") - .detail("BackupContainer", bc->getURL()) - .detail("TargetVersion", targetVersion); - throw restore_invalid_version(); - } - - TraceEvent("FastRestoreSubmitRestoreRequest") - .detail("BackupDesc", desc.toString()) - .detail("TargetVersion", targetVersion); - - state Reference tr(new ReadYourWritesTransaction(cx)); - state int restoreIndex = 0; - state int numTries = 0; - // lock DB for restore - loop { - try { - if (lockDB) { - wait(lockDatabase(cx, randomUID)); - } - wait(checkDatabaseLock(tr, randomUID)); - - TraceEvent("FastRestoreToolSubmitRestoreRequests").detail("DBIsLocked", randomUID); - break; - } catch (Error& e) { - TraceEvent(numTries > 50 ? SevError : SevInfo, "FastRestoreToolSubmitRestoreRequestsMayFail") - .error(e) - .detail("Reason", "DB is not properly locked") - .detail("ExpectedLockID", randomUID); - numTries++; - wait(tr->onError(e)); - } - } - - // set up restore request - tr->reset(); - numTries = 0; - loop { - tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr->setOption(FDBTransactionOptions::LOCK_AWARE); - try { - // Note: we always lock DB here in case DB is modified at the bacupRanges boundary. - for (restoreIndex = 0; restoreIndex < backupRanges.size(); restoreIndex++) { - auto range = backupRanges[restoreIndex]; - Standalone restoreTag(backupTag.toString() + "_" + std::to_string(restoreIndex)); - // Register the request request in DB, which will be picked up by restore worker leader - struct RestoreRequest restoreRequest(restoreIndex, - restoreTag, - bcUrl, - proxy, - targetVersion, - range, - deterministicRandom()->randomUniqueID(), - addPrefix, - removePrefix); - tr->set(restoreRequestKeyFor(restoreRequest.index), restoreRequestValue(restoreRequest)); - } - tr->set(restoreRequestTriggerKey, - restoreRequestTriggerValue(deterministicRandom()->randomUniqueID(), backupRanges.size())); - wait(tr->commit()); // Trigger restore - break; - } catch (Error& e) { - TraceEvent(numTries > 50 ? SevError : SevInfo, "FastRestoreToolSubmitRestoreRequestsRetry") - .error(e) - .detail("RestoreIndex", restoreIndex); - numTries++; - wait(tr->onError(e)); - } - } - return Void(); - } - // This method will return the final status of the backup at tag, and return the URL that was used on the tag // when that status value was read. - ACTOR static Future waitBackup(FileBackupAgent* backupAgent, - Database cx, - std::string tagName, - StopWhenDone stopWhenDone, - Reference* pContainer = nullptr, - UID* pUID = nullptr) { - state std::string backTrace; - state KeyBackedTag tag = makeBackupTag(tagName); - - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future waitBackup(FileBackupAgent* backupAgent, + Database cx, + std::string tagName, + StopWhenDone stopWhenDone, + Reference* pContainer = nullptr, + UID* pUID = nullptr) { + std::string backTrace; + KeyBackedTag tag = makeBackupTag(tagName); + + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; try { - state Optional oldUidAndAborted = wait(tag.get(tr)); + Optional oldUidAndAborted = co_await tag.get(tr); if (!oldUidAndAborted.present()) { - return EBackupState::STATE_NEVERRAN; + co_return EBackupState::STATE_NEVERRAN; } - state BackupConfig config(oldUidAndAborted.get().first); - state EBackupState status = - wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + BackupConfig config(oldUidAndAborted.get().first); + EBackupState status = + co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); // Break, if one of the following is true // - no longer runnable @@ -6965,7 +7134,7 @@ class FileBackupAgentImpl { if (pContainer != nullptr) { Reference c = - wait(config.backupContainer().getOrThrow(tr, Snapshot::False, backup_invalid_info())); + co_await config.backupContainer().getOrThrow(tr, Snapshot::False, backup_invalid_info()); *pContainer = fileBackup::getBackupContainerWithProxy(c); } @@ -6973,31 +7142,34 @@ class FileBackupAgentImpl { *pUID = oldUidAndAborted.get().first; } - return status; + co_return status; } - state Future watchFuture = tr->watch(config.stateEnum().key); - wait(tr->commit()); - wait(watchFuture); + Future watchFuture = tr->watch(config.stateEnum().key); + co_await tr->commit(); + co_await watchFuture; + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } - } - } - - ACTOR static Future submitBackup(FileBackupAgent* backupAgent, - Reference tr, - Key outContainer, - Optional proxy, - int initialSnapshotIntervalSeconds, - int snapshotIntervalSeconds, - std::string tagName, - Standalone> backupRanges, - StopWhenDone stopWhenDone, - UsePartitionedLog partitionedLog, - IncrementalBackupOnly incrementalBackupOnly, - Optional encryptionKeyFileName, - int snapshotMode) { + co_await tr->onError(err); + } + } + + static Future submitBackup(FileBackupAgent* backupAgent, + Reference tr, + Key outContainer, + Optional proxy, + int initialSnapshotIntervalSeconds, + int snapshotIntervalSeconds, + std::string tagName, + Standalone> backupRanges, + StopWhenDone stopWhenDone, + MutationLogType mutationLogType, + IncrementalBackupOnly incrementalBackupOnly, + Optional encryptionKeyFileName, + int encryptionBlockSize, + int snapshotMode) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); @@ -7005,15 +7177,17 @@ class FileBackupAgentImpl { TraceEvent(SevInfo, "FBA_SubmitBackup") .detail("TagName", tagName.c_str()) .detail("StopWhenDone", stopWhenDone) - .detail("UsePartitionedLog", partitionedLog) - .detail("OutContainer", outContainer.toString()); + .detail("MutationLogType", mutationLogType) + .detail("OutContainer", outContainer.toString()) + .detail("EncryptionKeyFileName", encryptionKeyFileName.present() ? encryptionKeyFileName.get() : "None") + .detail("EncryptionBlockSize", encryptionBlockSize); - state KeyBackedTag tag = makeBackupTag(tagName); - Optional uidAndAbortedFlag = wait(tag.get(tr)); + KeyBackedTag tag = makeBackupTag(tagName); + Optional uidAndAbortedFlag = co_await tag.get(tr); if (uidAndAbortedFlag.present()) { - state BackupConfig prevConfig(uidAndAbortedFlag.get().first); - state EBackupState prevBackupStatus = - wait(prevConfig.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + BackupConfig prevConfig(uidAndAbortedFlag.get().first); + EBackupState prevBackupStatus = + co_await prevConfig.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); if (FileBackupAgent::isRunnable(prevBackupStatus)) { throw backup_duplicate(); } @@ -7022,12 +7196,38 @@ class FileBackupAgentImpl { prevConfig.clear(tr); } - state BackupConfig config(deterministicRandom()->randomUniqueID()); - state UID uid = config.getUid(); + // PARTITIONED_LOG and RANGE_PARTITIONED_LOG backups are mutually exclusive: backup worker + // recruitment is determined by the active non-default type, so both cannot run concurrently. + if (mutationLogType == MutationLogType::PARTITIONED_LOG) { + if (co_await anyRangePartitionedBackupRunning(tr)) { + TraceEvent(SevError, "FBA_SubmitBackupMutationLogTypeConflict") + .detail("TagName", tagName) + .detail("RequestedType", mutationLogTypeToString(MutationLogType::PARTITIONED_LOG)) + .detail("ConflictingType", mutationLogTypeToString(MutationLogType::RANGE_PARTITIONED_LOG)); + fprintf(stderr, + "ERROR: Cannot start a backup with mutation-log-type `partitioned-log' while a " + "range-partitioned-log backup is running.\n"); + throw backup_error(); + } + } else if (mutationLogType == MutationLogType::RANGE_PARTITIONED_LOG) { + if (co_await anyPartitionedBackupRunning(tr)) { + TraceEvent(SevError, "FBA_SubmitBackupMutationLogTypeConflict") + .detail("TagName", tagName) + .detail("RequestedType", mutationLogTypeToString(MutationLogType::RANGE_PARTITIONED_LOG)) + .detail("ConflictingType", mutationLogTypeToString(MutationLogType::PARTITIONED_LOG)); + fprintf(stderr, + "ERROR: Cannot start a backup with mutation-log-type `range-partitioned-log' while a " + "partitioned-log backup is running.\n"); + throw backup_error(); + } + } + + BackupConfig config(deterministicRandom()->randomUniqueID()); + UID uid = config.getUid(); // This check will ensure that current backupUid is later than the last backup Uid - state Standalone nowStr = BackupAgentBase::getCurrentTime(); - state std::string backupContainer = outContainer.toString(); + Standalone nowStr = BackupAgentBase::getCurrentTime(); + std::string backupContainer = outContainer.toString(); // To be consistent with directory handling behavior since FDB backup was first released, if the container // string describes a local directory then "/backup-" will be added to it. @@ -7035,12 +7235,12 @@ class FileBackupAgentImpl { backupContainer = joinPath(backupContainer, std::string("backup-") + nowStr.toString()); } - state Reference bc = - IBackupContainer::openContainer(backupContainer, proxy, encryptionKeyFileName); + Reference bc = + IBackupContainer::openContainer(backupContainer, proxy, encryptionKeyFileName, encryptionBlockSize); try { // Use longer timeout for blobstore:// URLs in simulation to handle slow S3 mock operations - state double createTimeout = (g_network->isSimulated() && isBlobstoreUrl(backupContainer)) ? 300.0 : 30.0; - wait(timeoutError(bc->create(), createTimeout)); + double createTimeout = (g_network->isSimulated() && isBlobstoreUrl(backupContainer)) ? 300.0 : 30.0; + co_await timeoutError(bc->create(), createTimeout); } catch (Error& e) { if (e.code() == error_code_actor_cancelled) throw; @@ -7048,7 +7248,7 @@ class FileBackupAgentImpl { throw backup_error(); } - Optional lastBackupTimestamp = wait(backupAgent->lastBackupTimestamp().get(tr)); + Optional lastBackupTimestamp = co_await backupAgent->lastBackupTimestamp().get(tr); if ((lastBackupTimestamp.present()) && (lastBackupTimestamp.get() >= nowStr)) { fprintf(stderr, @@ -7063,7 +7263,7 @@ class FileBackupAgentImpl { } backupRangeSet.coalesce(allKeys); - state std::vector normalizedRanges; + std::vector normalizedRanges; for (auto& backupRange : backupRangeSet.ranges()) { if (backupRange.value()) { @@ -7073,10 +7273,10 @@ class FileBackupAgentImpl { config.clear(tr); - state Key destUidValue(BinaryWriter::toValue(uid, Unversioned())); + Key destUidValue(BinaryWriter::toValue(uid, Unversioned())); if (normalizedRanges.size() == 1 || isDefaultBackup(normalizedRanges)) { - RangeResult existingDestUidValues = wait( - tr->getRange(KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY)); + RangeResult existingDestUidValues = co_await tr->getRange( + KeyRangeRef(destUidLookupPrefix, strinc(destUidLookupPrefix)), CLIENT_KNOBS->TOO_MANY); bool found = false; KeyRangeRef targetRange = normalizedRanges.size() == 1 ? normalizedRanges[0] : getDefaultBackupSharedRange(); @@ -7116,39 +7316,40 @@ class FileBackupAgentImpl { config.backupRanges().set(tr, normalizedRanges); config.initialSnapshotIntervalSeconds().set(tr, initialSnapshotIntervalSeconds); config.snapshotIntervalSeconds().set(tr, snapshotIntervalSeconds); - config.partitionedLogEnabled().set(tr, partitionedLog); + config.mutationLogType().set(tr, mutationLogType); config.incrementalBackupOnly().set(tr, incrementalBackupOnly); config.snapshotMode().set(tr, snapshotMode); - - Key taskKey = wait(fileBackup::StartFullBackupTaskFunc::addTask( - tr, backupAgent->taskBucket, uid, TaskCompletionKey::noSignal())); - - return Void(); - } - - ACTOR static Future submitRestore(FileBackupAgent* backupAgent, - Reference tr, - Key tagName, - Key backupURL, - Optional proxy, - Standalone> ranges, - Version restoreVersion, - Key addPrefix, - Key removePrefix, - LockDB lockDB, - UnlockDB unlockDB, - OnlyApplyMutationLogs onlyApplyMutationLogs, - InconsistentSnapshotOnly inconsistentSnapshotOnly, - Version beginVersion, - UID uid, - TransformPartitionedLog transformPartitionedLog, - bool useRangeFileRestore = true) { + Key taskKey = co_await fileBackup::StartFullBackupTaskFunc::addTask( + tr, backupAgent->taskBucket, uid, TaskCompletionKey::noSignal()); + + co_return; + } + + static Future submitRestore(FileBackupAgent* backupAgent, + Reference tr, + Key tagName, + Key backupURL, + Optional proxy, + Standalone> ranges, + Version restoreVersion, + Key addPrefix, + Key removePrefix, + LockDB lockDB, + UnlockDB unlockDB, + OnlyApplyMutationLogs onlyApplyMutationLogs, + InconsistentSnapshotOnly inconsistentSnapshotOnly, + Version beginVersion, + UID uid, + MutationLogType mutationLogType, + Optional encryptionKeyFileName, + int encryptionBlockSize, + bool useRangeFileRestore = true) { KeyRangeMap restoreRangeSet; for (auto& range : ranges) { restoreRangeSet.insert(range, 1); } restoreRangeSet.coalesce(allKeys); - state std::vector restoreRanges; + std::vector restoreRanges; for (auto& restoreRange : restoreRangeSet.ranges()) { if (restoreRange.value()) { restoreRanges.push_back(KeyRange(KeyRangeRef(restoreRange.range().begin, restoreRange.range().end))); @@ -7162,21 +7363,21 @@ class FileBackupAgentImpl { tr->setOption(FDBTransactionOptions::LOCK_AWARE); // Get old restore config for this tag - state KeyBackedTag tag = makeRestoreTag(tagName.toString()); - state Optional oldUidAndAborted = wait(tag.get(tr)); + KeyBackedTag tag = makeRestoreTag(tagName.toString()); + Optional oldUidAndAborted = co_await tag.get(tr); if (oldUidAndAborted.present()) { if (oldUidAndAborted.get().first == uid) { if (oldUidAndAborted.get().second) { throw restore_duplicate_uid(); } else { - return Void(); + co_return; } } - state RestoreConfig oldRestore(oldUidAndAborted.get().first); + RestoreConfig oldRestore(oldUidAndAborted.get().first); // Make sure old restore for this tag is not runnable - bool runnable = wait(oldRestore.isRunnable(tr)); + bool runnable = co_await oldRestore.isRunnable(tr); if (runnable) { throw restore_duplicate_tag(); @@ -7187,27 +7388,28 @@ class FileBackupAgentImpl { } if (!onlyApplyMutationLogs) { - state int index; + int index{ 0 }; for (index = 0; index < restoreRanges.size(); index++) { KeyRange restoreIntoRange = KeyRangeRef(restoreRanges[index].begin, restoreRanges[index].end) .removePrefix(removePrefix) .withPrefix(addPrefix); - RangeResult existingRows = wait(tr->getRange(restoreIntoRange, 1)); + RangeResult existingRows = co_await tr->getRange(restoreIntoRange, 1); // Allow restoring over existing data only when using the validation restore prefix. // validateRestoreLogKeys.begin (\xff\x02/rlog/) is the designated prefix for validation restores. // Using any other prefix with existing data could corrupt user data. - if (existingRows.size() > 0 && addPrefix != validateRestoreLogKeys.begin) { + if (!existingRows.empty() && addPrefix != validateRestoreLogKeys.begin) { throw restore_destination_not_empty(); } } } // Make new restore config - state RestoreConfig restore(uid); + RestoreConfig restore(uid); // Point the tag to the new uid tag.set(tr, { uid, false }); - Reference bc = IBackupContainer::openContainer(backupURL.toString(), proxy, {}); + Reference bc = + IBackupContainer::openContainer(backupURL.toString(), proxy, encryptionKeyFileName, encryptionBlockSize); // Configure the new restore restore.tag().set(tr, tagName.toString()); @@ -7218,7 +7420,7 @@ class FileBackupAgentImpl { restore.inconsistentSnapshotOnly().set(tr, inconsistentSnapshotOnly); restore.beginVersion().set(tr, beginVersion); restore.unlockDBAfterRestore().set(tr, unlockDB); - restore.transformPartitionedLog().set(tr, transformPartitionedLog); + restore.mutationLogType().set(tr, mutationLogType); restore.useRangeFileRestore().set(tr, useRangeFileRestore); if (BUGGIFY && restoreRanges.size() == 1) { restore.restoreRange().set(tr, restoreRanges[0]); @@ -7229,78 +7431,80 @@ class FileBackupAgentImpl { } // this also sets restore.add/removePrefix. restore.initApplyMutations(tr, addPrefix, removePrefix, onlyApplyMutationLogs); - - Key taskKey = wait(fileBackup::StartFullRestoreTaskFunc::addTask( - tr, backupAgent->taskBucket, uid, TaskCompletionKey::noSignal())); + Key taskKey = co_await fileBackup::StartFullRestoreTaskFunc::addTask( + tr, backupAgent->taskBucket, uid, TaskCompletionKey::noSignal()); if (lockDB) - wait(lockDatabase(tr, uid)); + co_await lockDatabase(tr, uid); else - wait(checkDatabaseLock(tr, uid)); + co_await checkDatabaseLock(tr, uid); - return Void(); + co_return; } // This method will return the final status of the backup - ACTOR static Future waitRestore(Database cx, Key tagName, Verbose verbose) { - state ERestoreState status; - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future waitRestore(Database cx, Key tagName, Verbose verbose) { + ERestoreState status; + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); + Error err; try { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeRestoreTag(tagName.toString()); - Optional current = wait(tag.get(tr)); + KeyBackedTag tag = makeRestoreTag(tagName.toString()); + Optional current = co_await tag.get(tr); if (!current.present()) { if (verbose) printf("waitRestore: Tag: %s State: %s\n", tagName.toString().c_str(), FileBackupAgent::restoreStateText(ERestoreState::UNINITIALIZED).toString().c_str()); - return ERestoreState::UNINITIALIZED; + co_return ERestoreState::UNINITIALIZED; } - state RestoreConfig restore(current.get().first); + RestoreConfig restore(current.get().first); if (verbose) { - state std::string details = wait(restore.getProgress(tr)); + std::string details = co_await restore.getProgress(tr); printf("%s\n", details.c_str()); } - ERestoreState status_ = wait(restore.stateEnum().getD(tr)); + ERestoreState status_ = co_await restore.stateEnum().getD(tr); status = status_; - state bool runnable = wait(restore.isRunnable(tr)); + bool runnable = co_await restore.isRunnable(tr); // State won't change from here if (!runnable) break; // Wait for a change - state Future watchFuture = tr->watch(restore.stateEnum().key); - wait(tr->commit()); + Future watchFuture = tr->watch(restore.stateEnum().key); + co_await tr->commit(); if (verbose) - wait(watchFuture || delay(1)); + co_await (watchFuture || delay(1)); else - wait(watchFuture); + co_await watchFuture; + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return status; + co_return status; } - ACTOR static Future discontinueBackup(FileBackupAgent* backupAgent, - Reference tr, - Key tagName) { + static Future discontinueBackup(FileBackupAgent* backupAgent, + Reference tr, + Key tagName) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeBackupTag(tagName.toString()); - state UidAndAbortedFlagT current = wait(tag.getOrThrow(tr, Snapshot::False, backup_unneeded())); - state BackupConfig config(current.first); - state EBackupState status = wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + KeyBackedTag tag = makeBackupTag(tagName.toString()); + UidAndAbortedFlagT current = co_await tag.getOrThrow(tr, Snapshot::False, backup_unneeded()); + BackupConfig config(current.first); + EBackupState status = co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); if (!FileBackupAgent::isRunnable(status)) { throw backup_unneeded(); @@ -7308,7 +7512,7 @@ class FileBackupAgentImpl { // If the backup is already restorable then 'mostly' abort it - cancel all tasks via the tag // and clear the mutation logging config and data - but set its state as COMPLETED instead of ABORTED. - state Optional latestRestorableVersion = wait(config.getLatestRestorableVersion(tr)); + Optional latestRestorableVersion = co_await config.getLatestRestorableVersion(tr); TraceEvent(SevInfo, "FBA_DiscontinueBackup") .detail("AlreadyRestorable", latestRestorableVersion.present() ? "Yes" : "No") @@ -7317,21 +7521,21 @@ class FileBackupAgentImpl { if (latestRestorableVersion.present()) { // Cancel all backup tasks through tag - wait(tag.cancel(tr)); + co_await tag.cancel(tr); tr->setOption(FDBTransactionOptions::COMMIT_ON_FIRST_PROXY); - state Key destUidValue = wait(config.destUidValue().getOrThrow(tr)); - wait(success(tr->getReadVersion())); - wait(eraseLogData(tr, config.getUidAsKey(), destUidValue) && - fileBackup::clearBackupStartID(tr, config.getUid())); + Key destUidValue = co_await config.destUidValue().getOrThrow(tr); + co_await tr->getReadVersion(); + co_await (eraseLogData(tr, config.getUidAsKey(), destUidValue) && + fileBackup::clearBackupStartID(tr, config.getUid())); config.stateEnum().set(tr, EBackupState::STATE_COMPLETED); - return Void(); + co_return; } - state bool stopWhenDone = wait(config.stopWhenDone().getOrThrow(tr)); + bool stopWhenDone = co_await config.stopWhenDone().getOrThrow(tr); if (stopWhenDone) { throw backup_duplicate(); @@ -7339,21 +7543,21 @@ class FileBackupAgentImpl { config.stopWhenDone().set(tr, true); - return Void(); + co_return; } - ACTOR static Future abortBackup(FileBackupAgent* backupAgent, - Reference tr, - std::string tagName) { + static Future abortBackup(FileBackupAgent* backupAgent, + Reference tr, + std::string tagName) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeBackupTag(tagName); - state UidAndAbortedFlagT current = wait(tag.getOrThrow(tr, Snapshot::False, backup_unneeded())); + KeyBackedTag tag = makeBackupTag(tagName); + UidAndAbortedFlagT current = co_await tag.getOrThrow(tr, Snapshot::False, backup_unneeded()); - state BackupConfig config(current.first); - state Key destUidValue = wait(config.destUidValue().getOrThrow(tr)); - EBackupState status = wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + BackupConfig config(current.first); + Key destUidValue = co_await config.destUidValue().getOrThrow(tr); + EBackupState status = co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); if (!backupAgent->isRunnable(status)) { throw backup_unneeded(); @@ -7363,29 +7567,39 @@ class FileBackupAgentImpl { .detail("TagName", tagName.c_str()) .detail("Status", BackupAgentBase::getStateText(status)); - wait(tag.cancel(tr)); + co_await tag.cancel(tr); - wait(eraseLogData(tr, config.getUidAsKey(), destUidValue) && - fileBackup::clearBackupStartID(tr, config.getUid())); + co_await (eraseLogData(tr, config.getUidAsKey(), destUidValue) && + fileBackup::clearBackupStartID(tr, config.getUid())); config.stateEnum().set(tr, EBackupState::STATE_ABORTED); - return Void(); + co_return; } - ACTOR static Future checkAndDisableBackupWorkers(Database cx) { - state bool running = wait(runRYWTransaction( - cx, [=](Reference tr) { return anyPartitionedBackupRunning(tr); })); + static Future checkAndDisableBackupWorkers(Database cx) { + bool running = co_await runRYWTransaction( + cx, [=](Reference tr) { return anyPartitionedBackupRunning(tr); }); if (!running) { - wait(disableBackupWorker(cx)); + co_await disableBackupWorker(cx); } - return Void(); + co_return; } - ACTOR static Future changePause(FileBackupAgent* backupAgent, Database db, bool pause) { - state Reference tr(new ReadYourWritesTransaction(db)); + static Future checkAndDisableRangeBackupWorkers(Database cx) { + bool running = co_await runRYWTransaction( + cx, [=](Reference tr) { return anyRangePartitionedBackupRunning(tr); }); + if (!running) { + co_await disableRangeBackupWorker(cx); + } + co_return; + } + + static Future changePause(FileBackupAgent* backupAgent, Database db, bool pause) { + Reference tr(new ReadYourWritesTransaction(db)); - loop { + while (true) { + Error err; tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); @@ -7395,14 +7609,15 @@ class FileBackupAgentImpl { backupAgent->taskBucket->changePause(tr, pause); // This is backup workers' pause key. tr->set(backupPausedKey, pause ? "1"_sr : "0"_sr); - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } TraceEvent("FileBackupAgentChangePaused").detail("Action", pause ? "Paused" : "Resumed"); - return Void(); + co_return; } struct TimestampedVersion { @@ -7426,34 +7641,35 @@ class FileBackupAgentImpl { // Helper actor for generating status // If f is present, lookup epochs using timekeeper and tr, return TimestampedVersion - ACTOR static Future getTimestampedVersion(Reference tr, - Future> f) { - state TimestampedVersion tv; - wait(store(tv.version, f)); + static Future getTimestampedVersion(Reference tr, + Future> f) { + TimestampedVersion tv; + tv.version = co_await f; if (tv.version.present()) { - wait(store(tv.epochs, timeKeeperEpochsFromVersion(tv.version.get(), tr))); + tv.epochs = co_await timeKeeperEpochsFromVersion(tv.version.get(), tr); } - return tv; + co_return tv; } - ACTOR static Future getStatusJSON(FileBackupAgent* backupAgent, Database cx, std::string tagName) { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future getStatusJSON(FileBackupAgent* backupAgent, Database cx, std::string tagName) { + Reference tr(new ReadYourWritesTransaction(cx)); - loop { + while (true) { + Error err; try { - state JsonBuilderObject doc; + JsonBuilderObject doc; doc.setKey("SchemaVersion", "1.0.0"); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeBackupTag(tagName); - state Optional uidAndAbortedFlag; - state Optional paused; - state Version recentReadVersion; + KeyBackedTag tag = makeBackupTag(tagName); + Optional uidAndAbortedFlag; + Optional paused; + Version recentReadVersion{ 0 }; - wait(store(paused, tr->get(backupAgent->taskBucket->getPauseKey())) && - store(uidAndAbortedFlag, tag.get(tr)) && store(recentReadVersion, tr->getReadVersion())); + co_await (store(paused, tr->get(backupAgent->taskBucket->getPauseKey())) && + store(uidAndAbortedFlag, tag.get(tr)) && store(recentReadVersion, tr->getReadVersion())); doc.setKey("BackupAgentsPaused", paused.present()); doc.setKey("Tag", tag.tagName); @@ -7461,10 +7677,10 @@ class FileBackupAgentImpl { if (uidAndAbortedFlag.present()) { doc.setKey("UID", uidAndAbortedFlag.get().first.toString()); - state BackupConfig config(uidAndAbortedFlag.get().first); + BackupConfig config(uidAndAbortedFlag.get().first); - state EBackupState backupState = - wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + EBackupState backupState = + co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); JsonBuilderObject statusDoc; statusDoc.setKey("Name", BackupAgentBase::getStateName(backupState)); statusDoc.setKey("Description", BackupAgentBase::getStateText(backupState)); @@ -7472,13 +7688,13 @@ class FileBackupAgentImpl { statusDoc.setKey("Running", BackupAgentBase::isRunnable(backupState)); doc.setKey("Status", statusDoc); - state Future done = Void(); + Future done = Void(); if (backupState != EBackupState::STATE_NEVERRAN) { - state Reference bc; - state TimestampedVersion latestRestorable; + Reference bc; + TimestampedVersion latestRestorable; - wait( + co_await ( store(latestRestorable, getTimestampedVersion(tr, config.getLatestRestorableVersion(tr))) && store(bc, config.backupContainer().getOrThrow(tr))); bc = fileBackup::getBackupContainerWithProxy(bc); @@ -7499,18 +7715,18 @@ class FileBackupAgentImpl { if (backupState == EBackupState::STATE_RUNNING_DIFFERENTIAL || backupState == EBackupState::STATE_RUNNING) { - state int64_t snapshotInterval; - state int64_t logBytesWritten; - state int64_t rangeBytesWritten; - state bool stopWhenDone; - state TimestampedVersion snapshotBegin; - state TimestampedVersion snapshotTargetEnd; - state TimestampedVersion latestLogEnd; - state TimestampedVersion latestSnapshotEnd; - state TimestampedVersion snapshotLastDispatch; - state Optional snapshotLastDispatchShardsBehind; - - wait( + int64_t snapshotInterval{ 0 }; + int64_t logBytesWritten{ 0 }; + int64_t rangeBytesWritten{ 0 }; + bool stopWhenDone{ false }; + TimestampedVersion snapshotBegin; + TimestampedVersion snapshotTargetEnd; + TimestampedVersion latestLogEnd; + TimestampedVersion latestSnapshotEnd; + TimestampedVersion snapshotLastDispatch; + Optional snapshotLastDispatchShardsBehind; + + co_await ( store(snapshotInterval, config.snapshotIntervalSeconds().getOrThrow(tr)) && store(logBytesWritten, config.logBytesWritten().getD(tr)) && store(rangeBytesWritten, config.rangeBytesWritten().getD(tr)) && @@ -7564,9 +7780,77 @@ class FileBackupAgentImpl { doc.setKey("CurrentSnapshot", snapshot); } + // Add snapshot mode information + Optional snapshotModeOpt = co_await config.snapshotMode().get(tr); + int snapshotModeValue = snapshotModeOpt.present() ? snapshotModeOpt.get() : 0; + std::string snapshotModeText; + switch (snapshotModeValue) { + case 0: + snapshotModeText = "rangefile"; + break; + case 1: + snapshotModeText = "bulkdump"; + break; + case 2: + snapshotModeText = "both"; + break; + default: + TraceEvent(SevError, "BackupInvalidSnapshotMode") + .detail("BackupUID", config.getUid()) + .detail("SnapshotModeValue", snapshotModeValue) + .detail("ValidValues", "0=rangefile, 1=bulkdump, 2=both"); + throw backup_error(); + } + doc.setKey("SnapshotMode", snapshotModeText); + + // Check if backup is BulkLoad compatible + Optional bulkDumpJobIdOpt = co_await config.bulkDumpJobId().get(tr); + doc.setKey("BulkLoadCompatible", bulkDumpJobIdOpt.present() && !bulkDumpJobIdOpt.get().empty()); + + // If using bulkdump mode, get bulkdump progress + if (snapshotModeValue == 1 || snapshotModeValue == 2) { + Optional bulkDumpProgressOpt = co_await getBulkDumpProgress(cx); + if (bulkDumpProgressOpt.present()) { + BulkDumpProgress bdProgress = bulkDumpProgressOpt.get(); + JsonBuilderObject bdDoc; + bdDoc.setKey("JobId", bdProgress.jobId.toString()); + bdDoc.setKey("TotalTasks", bdProgress.totalTasks); + bdDoc.setKey("CompleteTasks", bdProgress.completeTasks); + bdDoc.setKey("RunningTasks", bdProgress.runningTasks); + bdDoc.setKey("ErrorTasks", bdProgress.errorTasks); + bdDoc.setKey("ProgressPercent", bdProgress.progressPercent()); + bdDoc.setKey("TotalBytes", bdProgress.totalBytes); + bdDoc.setKey("CompletedBytes", bdProgress.completedBytes); + bdDoc.setKey("AvgBytesPerSecond", bdProgress.avgBytesPerSecond()); + if (bdProgress.etaSeconds().present()) { + bdDoc.setKey("EstimatedSecondsRemaining", bdProgress.etaSeconds().get()); + } + bdDoc.setKey("ElapsedSeconds", bdProgress.elapsedSeconds); + + // Add stalled tasks if any + if (!bdProgress.stalledTasks.empty()) { + JsonBuilderArray stalledArray; + for (const auto& stalled : bdProgress.stalledTasks) { + JsonBuilderObject stalledDoc; + stalledDoc.setKey("TaskId", stalled.taskId.toString()); + stalledDoc.setKey("Range", stalled.range.toString()); + stalledDoc.setKey("StalledSeconds", stalled.stalledSeconds); + stalledDoc.setKey("RestartCount", stalled.restartCount); + if (!stalled.lastError.empty()) { + stalledDoc.setKey("LastError", stalled.lastError); + } + stalledArray.push_back(stalledDoc); + } + bdDoc.setKey("StalledTasks", stalledArray); + } + + doc.setKey("BulkDumpProgress", bdDoc); + } + } + KeyBackedMap>::RangeResultType errors = - wait(config.lastErrorPerType().getRange( - tr, 0, std::numeric_limits::max(), CLIENT_KNOBS->TOO_MANY)); + co_await config.lastErrorPerType().getRange( + tr, 0, std::numeric_limits::max(), CLIENT_KNOBS->TOO_MANY); JsonBuilderArray errorList; for (auto& e : errors.results) { std::string msg = e.second.first; @@ -7580,54 +7864,57 @@ class FileBackupAgentImpl { doc.setKey("Errors", errorList); } - return doc.getJson(); + co_return doc.getJson(); } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - ACTOR static Future getStatus(FileBackupAgent* backupAgent, - Database cx, - ShowErrors showErrors, - std::string tagName) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state std::string statusText; + static Future getStatus(FileBackupAgent* backupAgent, + Database cx, + ShowErrors showErrors, + std::string tagName) { + Reference tr(new ReadYourWritesTransaction(cx)); + std::string statusText; - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag; - state BackupConfig config; - state EBackupState backupState; + KeyBackedTag tag; + BackupConfig config; + EBackupState backupState; statusText = ""; tag = makeBackupTag(tagName); - state Optional uidAndAbortedFlag = wait(tag.get(tr)); - state Future> fPaused = tr->get(backupAgent->taskBucket->getPauseKey()); + Optional uidAndAbortedFlag = co_await tag.get(tr); + Future> fPaused = tr->get(backupAgent->taskBucket->getPauseKey()); if (uidAndAbortedFlag.present()) { config = BackupConfig(uidAndAbortedFlag.get().first); EBackupState status = - wait(config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN)); + co_await config.stateEnum().getD(tr, Snapshot::False, EBackupState::STATE_NEVERRAN); backupState = status; } if (!uidAndAbortedFlag.present() || backupState == EBackupState::STATE_NEVERRAN) { - statusText += "No previous backups found.\n"; + statusText += "No previous backups found on tag '" + tagName + "'.\n"; + statusText += "Use 'fdbbackup tags' to list all backup tags.\n"; } else { - state std::string backupStatus(BackupAgentBase::getStateText(backupState)); - state Reference bc; - state Optional latestRestorableVersion; - state Version recentReadVersion; - - wait(store(latestRestorableVersion, config.getLatestRestorableVersion(tr)) && - store(bc, config.backupContainer().getOrThrow(tr)) && - store(recentReadVersion, tr->getReadVersion())); + std::string backupStatus(BackupAgentBase::getStateText(backupState)); + Reference bc; + Optional latestRestorableVersion; + Version recentReadVersion{ 0 }; + + co_await (store(latestRestorableVersion, config.getLatestRestorableVersion(tr)) && + store(bc, config.backupContainer().getOrThrow(tr)) && + store(recentReadVersion, tr->getReadVersion())); bc = fileBackup::getBackupContainerWithProxy(bc); - state bool snapshotProgress = false; + bool snapshotProgress = false; switch (backupState) { case EBackupState::STATE_SUBMITTED: @@ -7657,7 +7944,7 @@ class FileBackupAgentImpl { statusText += format("BackupURL: %s\n", bc->getURL().c_str()); // Add enhanced status fields for BulkLoad integration - state Optional snapshotModeOpt = wait(config.snapshotMode().get(tr)); + Optional snapshotModeOpt = co_await config.snapshotMode().get(tr); int snapshotModeValue = snapshotModeOpt.present() ? snapshotModeOpt.get() : 0; std::string snapshotModeText; switch (snapshotModeValue) { @@ -7671,80 +7958,135 @@ class FileBackupAgentImpl { snapshotModeText = "both"; break; default: - snapshotModeText = "unknown"; - break; + TraceEvent(SevError, "BackupInvalidSnapshotMode") + .detail("BackupUID", config.getUid()) + .detail("SnapshotModeValue", snapshotModeValue) + .detail("ValidValues", "0=rangefile, 1=bulkdump, 2=both"); + throw backup_error(); } statusText += format("Snapshot Mode: %s\n", snapshotModeText.c_str()); // Check if backup is BulkLoad compatible (has bulkdump_data/) - state Optional bulkDumpJobIdOpt = wait(config.bulkDumpJobId().get(tr)); + Optional bulkDumpJobIdOpt = co_await config.bulkDumpJobId().get(tr); bool bulkLoadCompatible = bulkDumpJobIdOpt.present() && !bulkDumpJobIdOpt.get().empty(); statusText += format("BulkLoad Compatible: %s\n", bulkLoadCompatible ? "yes" : "no"); + bool showBulkDump = (snapshotModeValue == 1 || snapshotModeValue == 2); + bool showRangeFile = (snapshotModeValue == 0 || snapshotModeValue == 2); + if (snapshotProgress) { - state int64_t snapshotInterval; - state Version snapshotBeginVersion; - state Version snapshotTargetEndVersion; - state Optional latestSnapshotEndVersion; - state Optional latestLogEndVersion; - state Optional logBytesWritten; - state Optional rangeBytesWritten; - state Optional latestSnapshotEndVersionTimestamp; - state Optional latestLogEndVersionTimestamp; - state Optional snapshotBeginVersionTimestamp; - state Optional snapshotTargetEndVersionTimestamp; - state bool stopWhenDone; - - wait(store(snapshotBeginVersion, config.snapshotBeginVersion().getOrThrow(tr)) && - store(snapshotTargetEndVersion, config.snapshotTargetEndVersion().getOrThrow(tr)) && - store(snapshotInterval, config.snapshotIntervalSeconds().getOrThrow(tr)) && - store(logBytesWritten, config.logBytesWritten().get(tr)) && - store(rangeBytesWritten, config.rangeBytesWritten().get(tr)) && - store(latestLogEndVersion, config.latestLogEndVersion().get(tr)) && - store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && - store(stopWhenDone, config.stopWhenDone().getOrThrow(tr))); - - wait(store(latestSnapshotEndVersionTimestamp, - getTimestampFromVersion(latestSnapshotEndVersion, tr)) && - store(latestLogEndVersionTimestamp, getTimestampFromVersion(latestLogEndVersion, tr)) && - store(snapshotBeginVersionTimestamp, - timeKeeperEpochsFromVersion(snapshotBeginVersion, tr)) && - store(snapshotTargetEndVersionTimestamp, - timeKeeperEpochsFromVersion(snapshotTargetEndVersion, tr))); - - statusText += format("Snapshot interval is %lld seconds. ", snapshotInterval); - if (backupState == EBackupState::STATE_RUNNING_DIFFERENTIAL) - statusText += format("Current snapshot progress target is %3.2f%% (>100%% means the " - "snapshot is supposed to be done)\n", - 100.0 * (recentReadVersion - snapshotBeginVersion) / - (snapshotTargetEndVersion - snapshotBeginVersion)); - else - statusText += "The initial snapshot is still running.\n"; - - statusText += format("\nDetails:\n LogBytes written - %lld\n RangeBytes written - %lld\n " - "Last complete log version and timestamp - %s, %s\n " - "Last complete snapshot version and timestamp - %s, %s\n " - "Current Snapshot start version and timestamp - %s, %s\n " - "Expected snapshot end version and timestamp - %s, %s\n " - "Backup supposed to stop at next snapshot completion - %s\n", - logBytesWritten.orDefault(0), - rangeBytesWritten.orDefault(0), + int64_t snapshotInterval{ 0 }; + Version snapshotBeginVersion{ 0 }; + Version snapshotTargetEndVersion{ 0 }; + Optional latestSnapshotEndVersion; + Optional latestLogEndVersion; + Optional logBytesWritten; + Optional rangeBytesWritten; + Optional latestSnapshotEndVersionTimestamp; + Optional latestLogEndVersionTimestamp; + Optional snapshotBeginVersionTimestamp; + Optional snapshotTargetEndVersionTimestamp; + bool stopWhenDone{ false }; + + co_await (store(snapshotBeginVersion, config.snapshotBeginVersion().getOrThrow(tr)) && + store(snapshotTargetEndVersion, config.snapshotTargetEndVersion().getOrThrow(tr)) && + store(snapshotInterval, config.snapshotIntervalSeconds().getOrThrow(tr)) && + store(logBytesWritten, config.logBytesWritten().get(tr)) && + store(rangeBytesWritten, config.rangeBytesWritten().get(tr)) && + store(latestLogEndVersion, config.latestLogEndVersion().get(tr)) && + store(latestSnapshotEndVersion, config.latestSnapshotEndVersion().get(tr)) && + store(stopWhenDone, config.stopWhenDone().getOrThrow(tr))); + + co_await ( + store(latestSnapshotEndVersionTimestamp, + getTimestampFromVersion(latestSnapshotEndVersion, tr)) && + store(latestLogEndVersionTimestamp, getTimestampFromVersion(latestLogEndVersion, tr)) && + store(snapshotBeginVersionTimestamp, + timeKeeperEpochsFromVersion(snapshotBeginVersion, tr)) && + store(snapshotTargetEndVersionTimestamp, + timeKeeperEpochsFromVersion(snapshotTargetEndVersion, tr))); + + if (showBulkDump) { + Optional bulkDumpProgressOpt = co_await getBulkDumpProgress(cx); + statusText += "\nBulkDump Snapshot:\n"; + if (bulkDumpProgressOpt.present()) { + BulkDumpProgress bdProgress = bulkDumpProgressOpt.get(); + statusText += format(" Tasks: %d/%d complete (%.1f%%)\n", + bdProgress.completeTasks, + bdProgress.totalTasks, + bdProgress.progressPercent()); + statusText += format( + " Bytes: %s\n", + formatBytesProgress(bdProgress.completedBytes, bdProgress.totalBytes).c_str()); + + double throughput = bdProgress.avgBytesPerSecond(); + if (throughput > 0) { + statusText += format(" Throughput: %.1f MB/s\n", throughput / 1048576.0); + } + + if (bdProgress.elapsedSeconds > 0) { + statusText += + format(" Elapsed: %s\n", + formatDurationHumanReadable((int)bdProgress.elapsedSeconds).c_str()); + } + + if (bdProgress.etaSeconds().present()) { + statusText += + format(" ETA: %s\n", + formatDurationHumanReadable((int)bdProgress.etaSeconds().get()).c_str()); + } + + if (!bdProgress.stalledTasks.empty()) { + statusText += format("\nWARNING: %zu stalled tasks (no progress > 60s):\n", + bdProgress.stalledTasks.size()); + for (const auto& stalled : bdProgress.stalledTasks) { + statusText += format(" Task %s: %s, stalled %.0fs, %d restarts\n", + stalled.taskId.shortString().c_str(), + stalled.range.toString().c_str(), + stalled.stalledSeconds, + stalled.restartCount); + if (!stalled.lastError.empty()) { + statusText += + format(" Last error: %s\n", stalled.lastError.c_str()); + } + } + } + } else { + statusText += " Status: pending\n"; + } + } + + if (showRangeFile) { + statusText += "\nRangefile Snapshot:\n"; + statusText += format(" Bytes written: %s\n", + formatBytesHumanReadable(rangeBytesWritten.orDefault(0)).c_str()); + + if (backupState == EBackupState::STATE_RUNNING_DIFFERENTIAL) { + double pct = 100.0 * (recentReadVersion - snapshotBeginVersion) / + (snapshotTargetEndVersion - snapshotBeginVersion); + statusText += format(" Progress: %.2f%%\n", pct); + } else { + statusText += " Status: Initial snapshot still running\n"; + } + statusText += + format(" Started: %s\n", timeStampToString(snapshotBeginVersionTimestamp).c_str()); + } + + statusText += format("\nMutation Logs:\n"); + statusText += format(" Bytes written: %s\n", + formatBytesHumanReadable(logBytesWritten.orDefault(0)).c_str()); + statusText += format(" Last complete version: %s (%s)\n", versionToString(latestLogEndVersion).c_str(), - timeStampToString(latestLogEndVersionTimestamp).c_str(), - versionToString(latestSnapshotEndVersion).c_str(), - timeStampToString(latestSnapshotEndVersionTimestamp).c_str(), - versionToString(snapshotBeginVersion).c_str(), - timeStampToString(snapshotBeginVersionTimestamp).c_str(), - versionToString(snapshotTargetEndVersion).c_str(), - timeStampToString(snapshotTargetEndVersionTimestamp).c_str(), - boolToYesOrNo(stopWhenDone).c_str()); + timeStampToString(latestLogEndVersionTimestamp).c_str()); + + statusText += format("\nSnapshot interval is %lld seconds.\n", snapshotInterval); } // Append the errors, if requested if (showErrors) { KeyBackedMap>::RangeResultType errors = - wait(config.lastErrorPerType().getRange( - tr, 0, std::numeric_limits::max(), CLIENT_KNOBS->TOO_MANY)); + co_await config.lastErrorPerType().getRange( + tr, 0, std::numeric_limits::max(), CLIENT_KNOBS->TOO_MANY); std::string recentErrors; std::string pastErrors; @@ -7781,31 +8123,32 @@ class FileBackupAgentImpl { } } - Optional paused = wait(fPaused); + Optional paused = co_await fPaused; if (paused.present()) { statusText += format("\nAll backup agents have been paused.\n"); } break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return statusText; + co_return statusText; } - ACTOR static Future> getLastRestorable(FileBackupAgent* backupAgent, - Reference tr, - Key tagName, - Snapshot snapshot) { + static Future> getLastRestorable(FileBackupAgent* backupAgent, + Reference tr, + Key tagName, + Snapshot snapshot) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state Optional version = wait(tr->get(backupAgent->lastRestorable.pack(tagName), snapshot)); + Optional version = co_await tr->get(backupAgent->lastRestorable.pack(tagName), snapshot); - return (version.present()) - ? Optional(BinaryReader::fromStringRef(version.get(), Unversioned())) - : Optional(); + co_return (version.present()) + ? Optional(BinaryReader::fromStringRef(version.get(), Unversioned())) + : Optional(); } static StringRef read(StringRef& data, int bytes) { @@ -7836,37 +8179,38 @@ class FileBackupAgentImpl { // When set to true, gives an inconsistent snapshot, thus not recommended // beginVersions: restore's begin version for each range // randomUid: the UID for lock the database - ACTOR static Future restore(FileBackupAgent* backupAgent, - Database cx, - Optional cxOrig, - Key tagName, - Key url, - Optional proxy, - Standalone> ranges, - Standalone> beginVersions, - WaitForComplete waitForComplete, - Version targetVersion, - Verbose verbose, - Key addPrefix, - Key removePrefix, - LockDB lockDB, - UnlockDB unlockDB, - OnlyApplyMutationLogs onlyApplyMutationLogs, - InconsistentSnapshotOnly inconsistentSnapshotOnly, - Optional encryptionKeyFileName, - UID randomUid, - bool useRangeFileRestore = true) { + static Future restore(FileBackupAgent* backupAgent, + Database cx, + Optional cxOrig, + Key tagName, + Key url, + Optional proxy, + Standalone> ranges, + Standalone> beginVersions, + WaitForComplete waitForComplete, + Version targetVersion, + Verbose verbose, + Key addPrefix, + Key removePrefix, + LockDB lockDB, + UnlockDB unlockDB, + OnlyApplyMutationLogs onlyApplyMutationLogs, + InconsistentSnapshotOnly inconsistentSnapshotOnly, + Optional encryptionKeyFileName, + UID randomUid, + bool useRangeFileRestore = true) { // The restore command line tool won't allow ranges to be empty, but correctness workloads somehow might. if (ranges.empty()) { throw restore_error(); } - state std::string urlStr = url.toString(); - state Reference bc = IBackupContainer::openContainer(urlStr, proxy, encryptionKeyFileName); + std::string urlStr = url.toString(); + Reference bc = + IBackupContainer::openContainer(urlStr, proxy, /*encryptionKeyFileName=*/{}, /*encryptionBlockSize=*/0); // For blobstore:// URLs, use invalidVersion to allow describeBackup to write missing version properties // This is needed for S3 where metadata may not be immediately consistent - state BackupDescription desc = wait(bc->describeBackup(true, isBlobstoreUrl(urlStr) ? invalidVersion : 0)); + BackupDescription desc = co_await bc->describeBackup(true, isBlobstoreUrl(urlStr) ? invalidVersion : 0); if (desc.fileLevelEncryption && !encryptionKeyFileName.present()) { fprintf(stderr, "ERROR: Backup is encrypted, please provide the encryption key file path.\n"); @@ -7876,8 +8220,15 @@ class FileBackupAgentImpl { throw restore_error(); } + if (desc.fileLevelEncryption) { + bc = IBackupContainer::openContainer(urlStr, proxy, encryptionKeyFileName, desc.encryptionBlockSize); + // openContainer may return a cached container that has blockSize=0 (seeded earlier without blockSize). + // Set it explicitly to ensure the correct value is used. + bc->setEncryptionBlockSize(desc.encryptionBlockSize); + } + if (cxOrig.present()) { - wait(desc.resolveVersionTimes(cxOrig.get())); + co_await desc.resolveVersionTimes(cxOrig.get()); } printf("Backup Description\n%s", desc.toString().c_str()); @@ -7888,12 +8239,12 @@ class FileBackupAgentImpl { targetVersion = desc.contiguousLogEnd.get() - 1; } - state Version beginVersion = invalidVersion; // min begin version for all ranges + Version beginVersion = invalidVersion; // min begin version for all ranges if (!beginVersions.empty()) { beginVersion = *std::min_element(beginVersions.begin(), beginVersions.end()); } Optional restoreSet = - wait(bc->getRestoreSet(targetVersion, ranges, onlyApplyMutationLogs, beginVersion)); + co_await bc->getRestoreSet(targetVersion, ranges, onlyApplyMutationLogs, beginVersion); if (!restoreSet.present()) { TraceEvent(SevWarn, "FileBackupAgentRestoreNotPossible") @@ -7907,68 +8258,71 @@ class FileBackupAgentImpl { printf("Restoring backup to version: %lld\n", (long long)targetVersion); } - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - wait(submitRestore(backupAgent, - tr, - tagName, - url, - proxy, - ranges, - targetVersion, - addPrefix, - removePrefix, - lockDB, - unlockDB, - onlyApplyMutationLogs, - inconsistentSnapshotOnly, - beginVersion, - randomUid, - TransformPartitionedLog(desc.partitioned), - useRangeFileRestore)); - wait(tr->commit()); + co_await submitRestore(backupAgent, + tr, + tagName, + url, + proxy, + ranges, + targetVersion, + addPrefix, + removePrefix, + lockDB, + unlockDB, + onlyApplyMutationLogs, + inconsistentSnapshotOnly, + beginVersion, + randomUid, + desc.mutationLogType, + encryptionKeyFileName, + desc.encryptionBlockSize, + useRangeFileRestore); + co_await tr->commit(); break; } catch (Error& e) { - if (e.code() == error_code_restore_duplicate_tag) { - throw; - } - wait(tr->onError(e)); + err = e; } + if (err.code() == error_code_restore_duplicate_tag) { + throw err; + } + co_await tr->onError(err); } if (waitForComplete) { - ERestoreState finalState = wait(waitRestore(cx, tagName, verbose)); + ERestoreState finalState = co_await waitRestore(cx, tagName, verbose); if (finalState != ERestoreState::COMPLETED) throw restore_error(); } - return targetVersion; + co_return targetVersion; } // used for correctness only, locks the database before discontinuing the backup and that same lock is then used // while doing the restore. the tagname of the backup must be the same as the restore. - ACTOR static Future atomicRestore(FileBackupAgent* backupAgent, - Database cx, - Key tagName, - Standalone> ranges, - Key addPrefix, - Key removePrefix, - UsePartitionedLog fastRestore) { - state Reference ryw_tr = - Reference(new ReadYourWritesTransaction(cx)); - state BackupConfig backupConfig; - state DatabaseConfiguration config = wait(getDatabaseConfiguration(cx)); - loop { + static Future atomicRestore(FileBackupAgent* backupAgent, + Database cx, + Key tagName, + Standalone> ranges, + Key addPrefix, + Key removePrefix) { + auto ryw_tr = makeReference(cx); + BackupConfig backupConfig; + DatabaseConfiguration config = co_await getDatabaseConfiguration(cx); + while (true) { + Error err; try { ryw_tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); ryw_tr->setOption(FDBTransactionOptions::LOCK_AWARE); - state KeyBackedTag tag = makeBackupTag(tagName.toString()); - UidAndAbortedFlagT uidFlag = wait(tag.getOrThrow(ryw_tr)); + KeyBackedTag tag = makeBackupTag(tagName.toString()); + UidAndAbortedFlagT uidFlag = co_await tag.getOrThrow(ryw_tr); backupConfig = BackupConfig(uidFlag.first); - state EBackupState status = wait(backupConfig.stateEnum().getOrThrow(ryw_tr)); + EBackupState status = co_await backupConfig.stateEnum().getOrThrow(ryw_tr); if (status != EBackupState::STATE_RUNNING_DIFFERENTIAL) { throw backup_duplicate(); @@ -7976,66 +8330,74 @@ class FileBackupAgentImpl { break; } catch (Error& e) { - wait(ryw_tr->onError(e)); + err = e; } + co_await ryw_tr->onError(err); } // Lock src, record commit version - state Transaction tr(cx); - state Version commitVersion; - state UID randomUid = deterministicRandom()->randomUniqueID(); - loop { + Transaction tr(cx); + Version commitVersion{ 0 }; + UID randomUid = deterministicRandom()->randomUniqueID(); + while (true) { + Error err; try { // We must get a commit version so add a conflict range that won't likely cause conflicts // but will ensure that the transaction is actually submitted. tr.addWriteConflictRange(backupConfig.snapshotRangeDispatchMap().subspace); - wait(lockDatabase(&tr, randomUid)); - wait(tr.commit()); + co_await lockDatabase(&tr, randomUid); + co_await tr.commit(); commitVersion = tr.getCommittedVersion(); TraceEvent("AS_Locked").detail("CommitVer", commitVersion); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } ryw_tr->reset(); - loop { + while (true) { + Error err; try { - Optional restoreVersion = wait(backupConfig.getLatestRestorableVersion(ryw_tr)); + Optional restoreVersion = co_await backupConfig.getLatestRestorableVersion(ryw_tr); if (restoreVersion.present() && restoreVersion.get() >= commitVersion) { TraceEvent("AS_RestoreVersion").detail("RestoreVer", restoreVersion.get()); break; } else { ryw_tr->reset(); - wait(delay(0.2)); + co_await delay(0.2); + continue; } } catch (Error& e) { - wait(ryw_tr->onError(e)); + err = e; } + co_await ryw_tr->onError(err); } ryw_tr->reset(); - loop { + while (true) { + Error err; try { - wait(discontinueBackup(backupAgent, ryw_tr, tagName)); - wait(ryw_tr->commit()); + co_await discontinueBackup(backupAgent, ryw_tr, tagName); + co_await ryw_tr->commit(); TraceEvent("AS_DiscontinuedBackup").log(); break; } catch (Error& e) { - if (e.code() == error_code_backup_unneeded || e.code() == error_code_backup_duplicate) { - break; - } - wait(ryw_tr->onError(e)); + err = e; } + if (err.code() == error_code_backup_unneeded || err.code() == error_code_backup_duplicate) { + break; + } + co_await ryw_tr->onError(err); } - wait(success(waitBackup(backupAgent, cx, tagName.toString(), StopWhenDone::True))); + co_await waitBackup(backupAgent, cx, tagName.toString(), StopWhenDone::True); TraceEvent("AS_BackupStopped").log(); ryw_tr->reset(); - loop { - + while (true) { + Error err; try { ryw_tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); ryw_tr->setOption(FDBTransactionOptions::LOCK_AWARE); @@ -8043,151 +8405,89 @@ class FileBackupAgentImpl { ryw_tr->addReadConflictRange(range); ryw_tr->clear(range); } - wait(ryw_tr->commit()); + co_await ryw_tr->commit(); TraceEvent("AS_ClearedRange").log(); break; } catch (Error& e) { - wait(ryw_tr->onError(e)); + err = e; } + co_await ryw_tr->onError(err); } - state Reference bc = wait(backupConfig.backupContainer().getOrThrow(cx.getReference())); + Reference bc = co_await backupConfig.backupContainer().getOrThrow(cx.getReference()); bc = fileBackup::getBackupContainerWithProxy(bc); - if (fastRestore) { - TraceEvent("AtomicParallelRestoreStartRestore").log(); - Version targetVersion = ::invalidVersion; - wait(submitParallelRestore(cx, - tagName, - ranges, - KeyRef(bc->getURL()), - bc->getProxy(), - targetVersion, - LockDB::True, - randomUid, - addPrefix, - removePrefix)); - state bool hasPrefix = (addPrefix.size() > 0 || removePrefix.size() > 0); - TraceEvent("AtomicParallelRestoreWaitForRestoreFinish").detail("HasPrefix", hasPrefix); - wait(parallelRestoreFinish(cx, randomUid, UnlockDB{ !hasPrefix })); - // If addPrefix or removePrefix set, we want to transform the effect by copying data - if (hasPrefix) { - wait(transformRestoredDatabase(cx, ranges, addPrefix, removePrefix)); - wait(unlockDatabase(cx, randomUid)); - } - return -1; - } else { - TraceEvent("AS_StartRestore").log(); - state Standalone> restoreRange; - state Standalone> systemRestoreRange; - for (auto r : ranges) { - restoreRange.push_back_deep(restoreRange.arena(), r); - } - if (!systemRestoreRange.empty()) { - // restore system keys - wait(success(restore(backupAgent, - cx, - cx, - "system_restore"_sr, - KeyRef(bc->getURL()), - bc->getProxy(), - systemRestoreRange, - {}, - WaitForComplete::True, - ::invalidVersion, - Verbose::True, - addPrefix, - removePrefix, - LockDB::True, - UnlockDB::False, - OnlyApplyMutationLogs::False, - InconsistentSnapshotOnly::False, - {}, - randomUid))); - state Reference rywTransaction = - Reference(new ReadYourWritesTransaction(cx)); - // clear old restore config associated with system keys - loop { - try { - rywTransaction->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - rywTransaction->setOption(FDBTransactionOptions::LOCK_AWARE); - state RestoreConfig oldRestore(randomUid); - oldRestore.clear(rywTransaction); - wait(rywTransaction->commit()); - break; - } catch (Error& e) { - wait(rywTransaction->onError(e)); - } + TraceEvent("AS_StartRestore").log(); + Standalone> restoreRange; + Standalone> systemRestoreRange; + for (auto r : ranges) { + restoreRange.push_back_deep(restoreRange.arena(), r); + } + if (!systemRestoreRange.empty()) { + // restore system keys + co_await restore(backupAgent, + cx, + cx, + "system_restore"_sr, + KeyRef(bc->getURL()), + bc->getProxy(), + systemRestoreRange, + {}, + WaitForComplete::True, + ::invalidVersion, + Verbose::True, + addPrefix, + removePrefix, + LockDB::True, + UnlockDB::False, + OnlyApplyMutationLogs::False, + InconsistentSnapshotOnly::False, + {}, + randomUid); + auto rywTransaction = makeReference(cx); + // clear old restore config associated with system keys + while (true) { + Error err; + try { + rywTransaction->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + rywTransaction->setOption(FDBTransactionOptions::LOCK_AWARE); + RestoreConfig oldRestore(randomUid); + oldRestore.clear(rywTransaction); + co_await rywTransaction->commit(); + break; + } catch (Error& e) { + err = e; } + co_await rywTransaction->onError(err); } - // restore user data - state Version ver = wait(restore(backupAgent, - cx, - cx, - tagName, - KeyRef(bc->getURL()), - bc->getProxy(), - restoreRange, - {}, - WaitForComplete::True, - ::invalidVersion, - Verbose::True, - addPrefix, - removePrefix, - LockDB::True, - UnlockDB::True, - OnlyApplyMutationLogs::False, - InconsistentSnapshotOnly::False, - {}, - randomUid)); - return ver; - } - } - - // Similar to atomicRestore, only used in simulation test. - // locks the database before discontinuing the backup and that same lock is then used while doing the restore. - // the tagname of the backup must be the same as the restore. - static Future atomicParallelRestore(FileBackupAgent* backupAgent, - Database cx, - Key tagName, - Standalone> ranges, - Key addPrefix, - Key removePrefix) { - return success( - atomicRestore(backupAgent, cx, tagName, ranges, addPrefix, removePrefix, UsePartitionedLog::True)); + } + // restore user data + Version ver = co_await restore(backupAgent, + cx, + cx, + tagName, + KeyRef(bc->getURL()), + bc->getProxy(), + restoreRange, + {}, + WaitForComplete::True, + ::invalidVersion, + Verbose::True, + addPrefix, + removePrefix, + LockDB::True, + UnlockDB::True, + OnlyApplyMutationLogs::False, + InconsistentSnapshotOnly::False, + {}, + randomUid); + co_return ver; } }; const int FileBackupAgent::dataFooterSize = 20; -// Return if parallel restore has finished -Future FileBackupAgent::parallelRestoreFinish(Database cx, UID randomUID, UnlockDB unlockDB) { - return FileBackupAgentImpl::parallelRestoreFinish(cx, randomUID, unlockDB); -} - -Future FileBackupAgent::submitParallelRestore(Database cx, - Key backupTag, - Standalone> backupRanges, - Key bcUrl, - Optional proxy, - Version targetVersion, - LockDB lockDB, - UID randomUID, - Key addPrefix, - Key removePrefix) { - return FileBackupAgentImpl::submitParallelRestore( - cx, backupTag, backupRanges, bcUrl, proxy, targetVersion, lockDB, randomUID, addPrefix, removePrefix); -} - -Future FileBackupAgent::atomicParallelRestore(Database cx, - Key tagName, - Standalone> ranges, - Key addPrefix, - Key removePrefix) { - return FileBackupAgentImpl::atomicParallelRestore(this, cx, tagName, ranges, addPrefix, removePrefix); -} - Future FileBackupAgent::restore(Database cx, Optional cxOrig, Key tagName, @@ -8339,8 +8639,7 @@ Future FileBackupAgent::atomicRestore(Database cx, Standalone> ranges, Key addPrefix, Key removePrefix) { - return FileBackupAgentImpl::atomicRestore( - this, cx, tagName, ranges, addPrefix, removePrefix, UsePartitionedLog::False); + return FileBackupAgentImpl::atomicRestore(this, cx, tagName, ranges, addPrefix, removePrefix); } Future FileBackupAgent::abortRestore(Reference tr, Key tagName) { @@ -8367,9 +8666,10 @@ Future FileBackupAgent::submitBackup(Reference std::string const& tagName, Standalone> backupRanges, StopWhenDone stopWhenDone, - UsePartitionedLog partitionedLog, + MutationLogType mutationLogType, IncrementalBackupOnly incrementalBackupOnly, Optional const& encryptionKeyFileName, + int encryptionBlockSize, int snapshotMode) { return FileBackupAgentImpl::submitBackup(this, tr, @@ -8380,9 +8680,10 @@ Future FileBackupAgent::submitBackup(Reference tagName, backupRanges, stopWhenDone, - partitionedLog, + mutationLogType, incrementalBackupOnly, encryptionKeyFileName, + encryptionBlockSize, snapshotMode); } @@ -8398,6 +8699,10 @@ Future FileBackupAgent::checkAndDisableBackupWorkers(Database cx) { return FileBackupAgentImpl::checkAndDisableBackupWorkers(cx); } +Future FileBackupAgent::checkAndDisableRangeBackupWorkers(Database cx) { + return FileBackupAgentImpl::checkAndDisableRangeBackupWorkers(cx); +} + Future FileBackupAgent::getStatus(Database cx, ShowErrors showErrors, std::string tagName) { return FileBackupAgentImpl::getStatus(this, cx, showErrors, tagName); } @@ -8460,8 +8765,8 @@ static std::pair insideValidRange(KeyValueRef kv, } // Write [begin, end) in kvs to DB -ACTOR static Future writeKVs(Database cx, Standalone> kvs, int begin, int end) { - wait(runRYWTransaction(cx, [=](Reference tr) -> Future { +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; @@ -8475,11 +8780,12 @@ ACTOR static Future writeKVs(Database cx, Standalone writeKVs(Database cx, StandaloneTOO_MANY)); - ASSERT(readKVs.size() > 0 || begin == end); + RangeResult readKVs = co_await tr.getRange(KeyRangeRef(k1, k2), CLIENT_KNOBS->TOO_MANY); + ASSERT(!readKVs.empty() || begin == end); break; } catch (Error& e) { - TraceEvent("TransformDatabaseContentsWriteKVReadBackError").error(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("TransformDatabaseContentsWriteKVReadBackError").error(err); + co_await tr.onError(err); } TraceEvent(SevFRTestInfo, "TransformDatabaseContentsWriteKVDone").detail("Begin", begin).detail("End", end); - - return Void(); -} - -// restoreRanges is the actual range that has applied removePrefix and addPrefix processed by restore system -// Assume: restoreRanges do not overlap which is achieved by ensuring backup ranges do not overlap -ACTOR static Future transformDatabaseContents(Database cx, - Key addPrefix, - Key removePrefix, - Standalone> restoreRanges) { - state ReadYourWritesTransaction tr(cx); - state Standalone> oldData; - - TraceEvent("FastRestoreWorkloadTransformDatabaseContents") - .detail("AddPrefix", addPrefix) - .detail("RemovePrefix", removePrefix); - state int i = 0; - loop { // Read all data from DB - try { - tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr.setOption(FDBTransactionOptions::LOCK_AWARE); - for (i = 0; i < restoreRanges.size(); ++i) { - RangeResult kvs = wait(tr.getRange(restoreRanges[i], CLIENT_KNOBS->TOO_MANY)); - ASSERT(!kvs.more); - for (auto kv : kvs) { - oldData.push_back_deep(oldData.arena(), KeyValueRef(kv.key, kv.value)); - } - } - break; - } catch (Error& e) { - TraceEvent("FastRestoreWorkloadTransformDatabaseContentsGetAllKeys") - .error(e) - .detail("Index", i) - .detail("RestoreRange", restoreRanges[i]); - oldData = Standalone>(); // clear the vector - wait(tr.onError(e)); - } - } - - // Convert data by removePrefix and addPrefix in memory - state Standalone> newKVs; - for (int i = 0; i < oldData.size(); ++i) { - Key newKey(oldData[i].key); - TraceEvent(SevFRTestInfo, "TransformDatabaseContents") - .detail("Keys", oldData.size()) - .detail("Index", i) - .detail("GetKey", oldData[i].key) - .detail("GetValue", oldData[i].value); - if (newKey.size() < removePrefix.size()) { // If true, must check why. - TraceEvent(SevError, "TransformDatabaseContents") - .detail("Key", newKey) - .detail("RemovePrefix", removePrefix); - continue; - } - newKey = newKey.removePrefix(removePrefix).withPrefix(addPrefix); - newKVs.push_back_deep(newKVs.arena(), KeyValueRef(newKey.contents(), oldData[i].value)); - TraceEvent(SevFRTestInfo, "TransformDatabaseContents") - .detail("Keys", newKVs.size()) - .detail("Index", i) - .detail("NewKey", newKVs.back().key) - .detail("NewValue", newKVs.back().value); - } - - state Standalone> backupRanges; // dest. ranges - for (auto& range : restoreRanges) { - KeyRange tmpRange = range; - backupRanges.push_back_deep(backupRanges.arena(), tmpRange.removePrefix(removePrefix).withPrefix(addPrefix)); - } - - // Clear the transformed data (original data with removePrefix and addPrefix) in restoreRanges - wait(runRYWTransaction(cx, [=](Reference tr) -> Future { - tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr->setOption(FDBTransactionOptions::LOCK_AWARE); - for (int i = 0; i < restoreRanges.size(); i++) { - TraceEvent(SevFRTestInfo, "TransformDatabaseContents") - .detail("ClearRestoreRange", restoreRanges[i]) - .detail("ClearBackupRange", backupRanges[i]); - tr->clear(restoreRanges[i]); // Clear the range.removePrefix().withPrefix() - tr->clear(backupRanges[i]); - } - return Void(); - })); - - // Sanity check to ensure no data in the ranges - tr.reset(); - loop { - try { - tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult emptyData = wait(tr.getRange(normalKeys, CLIENT_KNOBS->TOO_MANY)); - for (int i = 0; i < emptyData.size(); ++i) { - TraceEvent(SevError, "ExpectEmptyData") - .detail("Index", i) - .detail("Key", emptyData[i].key) - .detail("Value", emptyData[i].value); - } - break; - } catch (Error& e) { - wait(tr.onError(e)); - } - } - - // Write transformed KVs (i.e., kv backup took) back to DB - state std::vector> fwrites; - loop { - try { - state int begin = 0; - state int len = 0; - while (begin < newKVs.size()) { - len = std::min(100, newKVs.size() - begin); - fwrites.push_back(writeKVs(cx, newKVs, begin, begin + len)); - begin = begin + len; - } - wait(waitForAll(fwrites)); - break; - } catch (Error& e) { - TraceEvent(SevError, "FastRestoreWorkloadTransformDatabaseContentsUnexpectedErrorOnWriteKVs").error(e); - wait(tr.onError(e)); - } - } - - // Sanity check - tr.reset(); - loop { - try { - tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult allData = wait(tr.getRange(normalKeys, CLIENT_KNOBS->TOO_MANY)); - TraceEvent(SevFRTestInfo, "SanityCheckData").detail("Size", allData.size()); - for (int i = 0; i < allData.size(); ++i) { - std::pair backupRestoreValid = insideValidRange(allData[i], restoreRanges, backupRanges); - TraceEvent(backupRestoreValid.first ? SevFRTestInfo : SevError, "SanityCheckData") - .detail("Index", i) - .detail("Key", allData[i].key) - .detail("Value", allData[i].value) - .detail("InsideBackupRange", backupRestoreValid.first) - .detail("InsideRestoreRange", backupRestoreValid.second); - } - break; - } catch (Error& e) { - wait(tr.onError(e)); - } - } - - TraceEvent("FastRestoreWorkloadTransformDatabaseContentsFinish") - .detail("AddPrefix", addPrefix) - .detail("RemovePrefix", removePrefix); - - return Void(); -} - -// addPrefix and removePrefix are the options used in the restore request: -// every backup key applied removePrefix and addPrefix in restore; -// transformRestoredDatabase actor will revert it by remove addPrefix and add removePrefix. -ACTOR Future transformRestoredDatabase(Database cx, - Standalone> backupRanges, - Key addPrefix, - Key removePrefix) { - try { - Standalone> restoreRanges; - for (int i = 0; i < backupRanges.size(); ++i) { - KeyRange range(backupRanges[i]); - Key begin = range.begin.removePrefix(removePrefix).withPrefix(addPrefix); - Key end = range.end.removePrefix(removePrefix).withPrefix(addPrefix); - TraceEvent("FastRestoreTransformRestoredDatabase") - .detail("From", KeyRangeRef(begin.contents(), end.contents())) - .detail("To", range); - restoreRanges.push_back_deep(restoreRanges.arena(), KeyRangeRef(begin.contents(), end.contents())); - } - wait(transformDatabaseContents(cx, removePrefix, addPrefix, restoreRanges)); - } catch (Error& e) { - TraceEvent(SevError, "FastRestoreTransformRestoredDatabaseUnexpectedError").error(e); - throw; - } - - return Void(); } void simulateBlobFailure() { diff --git a/fdbclient/FluentDSampleIngestor.cpp b/fdbclient/FluentDSampleIngestor.cpp index f440a1f2967..de9261909e7 100644 --- a/fdbclient/FluentDSampleIngestor.cpp +++ b/fdbclient/FluentDSampleIngestor.cpp @@ -39,7 +39,7 @@ boost::asio::ip::basic_endpoint toEndpoint(NetworkAddress const n) { } struct FluentDSocket { - virtual ~FluentDSocket() {} + virtual ~FluentDSocket() = default; virtual void connect(NetworkAddress const& endpoint) = 0; virtual void send(std::shared_ptr const& sample) = 0; virtual const boost::system::error_code& failed() const = 0; @@ -137,7 +137,7 @@ struct FluentDSocketImpl : FluentDSocket, std::enable_shared_from_this> queue; boost::system::error_code _failed; diff --git a/fdbclient/GCSBlobStore.cpp b/fdbclient/GCSBlobStore.cpp new file mode 100644 index 00000000000..5621de59d3e --- /dev/null +++ b/fdbclient/GCSBlobStore.cpp @@ -0,0 +1,289 @@ +/* + * GCSBlobStore.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. + */ + +#include "GCSBlobStore.h" +#include "fdbclient/JSONDoc.h" +#include "flow/Trace.h" + +GCSBlobStoreEndpoint::GCSBlobStoreEndpoint(std::string const& host, + std::string const& service, + Optional const& proxyHost, + Optional const& proxyPort, + Optional const& creds, + std::string const& projectId, + BlobKnobs const& knobs, + HTTP::Headers extraHeaders) + : S3BlobStoreEndpoint(host, + service, + "auto", + proxyHost, + proxyPort, + Optional(), // Don't pass creds to S3 — we handle auth ourselves + knobs, + extraHeaders), + projectId(projectId) { + if (creds.present()) { + token = creds.get().toString(); + } + // Mirror S3's lookupKey/lookupSecret pattern: if the URL didn't supply a token, we must load it from + // credential files on each new connection and omit it from the serialized URL. + lookupToken = token.empty(); +} + +void GCSBlobStoreEndpoint::setRequestHeaders(std::string const& verb, + std::string const& resource, + HTTP::Headers& headers) { + headers["Accept"] = "application/xml"; + if (!token.empty()) { + headers["Authorization"] = "Bearer " + token; + } + if (!projectId.empty()) { + headers["x-goog-project-id"] = projectId; + } +} + +Future GCSBlobStoreEndpoint::updateSecret() { + // S3BlobStoreEndpoint::updateSecret() short-circuits when credentials are absent, which is always the case for + // GCS (we carry the bearer token in `token`, not in S3's `credentials`). Skip the S3 override and go straight to + // the generic credential-file loop in the base class. + return IBlobStoreEndpoint::updateSecret(); +} + +bool GCSBlobStoreEndpoint::extractCredentialFields(JSONDoc& account) { + std::string newToken; + if (!account.tryGet("token", newToken)) { + return false; + } + token = newToken; + return true; +} + +bool GCSBlobStoreEndpoint::lookupSecretOnEachRequest() { + return lookupToken; +} + +std::string GCSBlobStoreEndpoint::credentialFileKey() const { + return "@" + host; +} + +std::string GCSBlobStoreEndpoint::getResourceURL(std::string resource, std::string params) const { + if (!params.empty()) + params.append("&"); + params.append("p=gcs"); + if (!projectId.empty()) { + params.append("&gcspid=").append(projectId); + } + std::string url = S3BlobStoreEndpoint::getResourceURL(resource, params); + + // Preserve an inline token in the URL so it round-trips through fromString. Tokens loaded from credential + // files (lookupToken == true) are omitted to avoid baking a refreshed value into a serialized URL. + if (!lookupToken && !token.empty()) { + const std::string placeholder = "blobstore://@"; + size_t pos = url.find(placeholder); + if (pos != std::string::npos) { + url.replace(pos, placeholder.size(), "blobstore://" + token + "@"); + } + } + return url; +} + +Future createBucket_gcs_impl(Reference b, std::string bucket) { + co_await b->requestRateWrite->getAllowance(1); + + bool exists = co_await b->bucketExists(bucket); + if (exists) { + co_return; + } + + if (b->projectId.empty()) { + TraceEvent(SevError, "GCSBucketCreateMissingProject") + .detail("Bucket", bucket) + .detail("Hint", "Set gcs_project_id (or gcspid) URL parameter"); + throw backup_invalid_url(); + } + + // GCS XML API accepts the same PUT request as S3 for bucket creation. + // x-goog-project-id is added by setRequestHeaders on every request. + std::string resource = b->constructResourcePath(bucket, ""); + HTTP::Headers headers; + co_await b->doRequest("PUT", resource, headers, nullptr, 0, { 200, 409 }); +} + +Future GCSBlobStoreEndpoint::createBucket(std::string const& bucket) { + return createBucket_gcs_impl(Reference::addRef(this), bucket); +} + +TEST_CASE("/backup/gcs/fromString") { + // URL with p=gcs should produce a GCSBlobStoreEndpoint + { + std::string url = + "blobstore://my-token@storage.googleapis.com/resource?bucket=my-bucket&p=gcs&gcspid=my-project"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + ASSERT(endpoint.isValid()); + ASSERT(resource == "resource"); + + auto* gcs = dynamic_cast(endpoint.getPtr()); + ASSERT(gcs != nullptr); + ASSERT(gcs->projectId == "my-project"); + ASSERT(gcs->token == "my-token"); + } + + // provider=gcs long form should also work + { + std::string url = "blobstore://tok@storage.googleapis.com/r?bucket=b&provider=gcs&gcs_project_id=proj"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + ASSERT(endpoint.isValid()); + + auto* gcs = dynamic_cast(endpoint.getPtr()); + ASSERT(gcs != nullptr); + ASSERT(gcs->projectId == "proj"); + ASSERT(gcs->token == "tok"); + } + + // URL without p=gcs should NOT produce a GCSBlobStoreEndpoint + { + std::string url = "blobstore://s3.us-west-2.amazonaws.com/resource?bucket=b&sa=1"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + ASSERT(endpoint.isValid()); + ASSERT(dynamic_cast(endpoint.getPtr()) == nullptr); + } + + return Void(); +} + +TEST_CASE("/backup/gcs/setRequestHeaders") { + // With token and projectId — all headers set + { + GCSBlobStoreEndpoint gcs("storage.googleapis.com", "443", {}, {}, "my-bearer-token"_sr, "my-project"); + HTTP::Headers headers; + gcs.setRequestHeaders("GET", "/bucket/object", headers); + + ASSERT(headers["Authorization"] == "Bearer my-bearer-token"); + ASSERT(headers["Accept"] == "application/xml"); + ASSERT(headers["x-goog-project-id"] == "my-project"); + } + + // Empty token — no Authorization header + { + GCSBlobStoreEndpoint gcs("storage.googleapis.com", "443", {}, {}, Optional(), "my-project"); + HTTP::Headers headers; + gcs.setRequestHeaders("PUT", "/bucket", headers); + + ASSERT(!headers.contains("Authorization")); + ASSERT(headers["Accept"] == "application/xml"); + ASSERT(headers["x-goog-project-id"] == "my-project"); + } + + // Empty projectId — no x-goog-project-id header + { + GCSBlobStoreEndpoint gcs("storage.googleapis.com", "443", {}, {}, "tok"_sr, ""); + HTTP::Headers headers; + gcs.setRequestHeaders("GET", "/bucket/object", headers); + + ASSERT(headers["Authorization"] == "Bearer tok"); + ASSERT(!headers.contains("x-goog-project-id")); + } + + return Void(); +} + +TEST_CASE("/backup/gcs/getResourceURL") { + // getResourceURL should include p=gcs and gcspid + { + std::string url = "blobstore://tok@storage.googleapis.com/resource?bucket=b&p=gcs&gcspid=my-project"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + ASSERT(endpoint.isValid()); + + std::string resURL = endpoint->getResourceURL("test-resource", ""); + ASSERT(resURL.find("p=gcs") != std::string::npos); + ASSERT(resURL.find("gcspid=my-project") != std::string::npos); + } + + // Without projectId — no gcspid in URL + { + std::string url = "blobstore://tok@storage.googleapis.com/resource?bucket=b&p=gcs"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + ASSERT(endpoint.isValid()); + + std::string resURL = endpoint->getResourceURL("test-resource", ""); + ASSERT(resURL.find("p=gcs") != std::string::npos); + ASSERT(resURL.find("gcspid") == std::string::npos); + } + + // Round-trip: an inline token survives parse → serialize → parse. + { + std::string url = "blobstore://tok@storage.googleapis.com/resource?bucket=b&p=gcs&gcspid=proj"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + + std::string serialized = endpoint->getResourceURL("new-resource", ""); + ASSERT(serialized.find("tok@") != std::string::npos); + + std::string resource2; + std::string error2; + S3BlobStoreEndpoint::ParametersT params2; + Reference endpoint2 = + S3BlobStoreEndpoint::fromString(serialized, {}, &resource2, &error2, ¶ms2); + ASSERT(endpoint2.isValid()); + + auto* gcs2 = dynamic_cast(endpoint2.getPtr()); + ASSERT(gcs2 != nullptr); + ASSERT(gcs2->projectId == "proj"); + ASSERT(gcs2->token == "tok"); + ASSERT(!gcs2->lookupToken); + } + + // Tokens loaded from credential files are not embedded in the serialized URL. + { + std::string url = "blobstore://@storage.googleapis.com/resource?bucket=b&p=gcs&gcspid=proj"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT params; + Reference endpoint = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶ms); + + auto* gcs = dynamic_cast(endpoint.getPtr()); + ASSERT(gcs != nullptr); + ASSERT(gcs->lookupToken); + + // Simulate a credential-file refresh populating the token. + gcs->token = "refreshed-token"; + std::string serialized = gcs->getResourceURL("r", ""); + ASSERT(serialized.find("refreshed-token") == std::string::npos); + } + + return Void(); +} diff --git a/fdbclient/GCSBlobStore.h b/fdbclient/GCSBlobStore.h new file mode 100644 index 00000000000..3dfc49c497a --- /dev/null +++ b/fdbclient/GCSBlobStore.h @@ -0,0 +1,59 @@ +/* + * GCSBlobStore.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/S3BlobStore.h" + +// GCS blob store endpoint using the S3-compatible XML API with OAuth2 Bearer token authentication. +// Inherits all blob operations (list, read, write, multipart) from S3BlobStoreEndpoint since the +// GCS XML API uses the same protocol. Only auth headers and credential handling differ. +class GCSBlobStoreEndpoint : public S3BlobStoreEndpoint { +public: + GCSBlobStoreEndpoint(std::string const& host, + std::string const& service, + Optional const& proxyHost, + Optional const& proxyPort, + Optional const& creds, + std::string const& projectId, + BlobKnobs const& knobs = BlobKnobs(), + HTTP::Headers extraHeaders = HTTP::Headers()); + + // OAuth2 Bearer token instead of SigV4 + void setRequestHeaders(std::string const& verb, std::string const& resource, HTTP::Headers& headers) override; + + Future updateSecret() override; + bool extractCredentialFields(JSONDoc& account) override; + + // Only consult credential files when the URL didn't supply an inline token. + bool lookupSecretOnEachRequest() override; + + // GCS credential files are keyed by "@host" — no access-key prefix like S3's "accessKey@host". + std::string credentialFileKey() const override; + + std::string getResourceURL(std::string resource, std::string params) const override; + + // GCS bucket creation needs x-goog-project-id header + Future createBucket(std::string const& bucket) override; + + std::string projectId; + std::string token; + bool lookupToken; +}; diff --git a/fdbclient/GlobalConfig.actor.cpp b/fdbclient/GlobalConfig.cpp similarity index 62% rename from fdbclient/GlobalConfig.actor.cpp rename to fdbclient/GlobalConfig.cpp index 4588e63e912..a95ff40ab1b 100644 --- a/fdbclient/GlobalConfig.actor.cpp +++ b/fdbclient/GlobalConfig.cpp @@ -1,5 +1,5 @@ /* - * GlobalConfig.actor.cpp + * GlobalConfig.cpp * * This source file is part of the FoundationDB open source project * @@ -19,8 +19,8 @@ */ #include "fdbclient/DatabaseContext.h" -#include "fdbclient/GlobalConfig.actor.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/GlobalConfig.h" +#include "fdbclient/SpecialKeySpace.h" #include "fdbclient/SystemData.h" #include "fdbclient/Tuple.h" #include "flow/Error.h" @@ -71,7 +71,7 @@ Key GlobalConfig::prefixedKey(KeyRef key) { return key.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::GLOBALCONFIG).begin); } -const Reference GlobalConfig::get(KeyRef name) { +Reference GlobalConfig::get(KeyRef name) { auto it = data.find(name); if (it == data.end()) { return Reference(); @@ -79,7 +79,7 @@ const Reference GlobalConfig::get(KeyRef name) { return it->second; } -const std::map> GlobalConfig::get(KeyRangeRef range) { +std::map> GlobalConfig::get(KeyRangeRef range) { std::map> results; for (const auto& [key, value] : data) { if (range.contains(key)) { @@ -154,113 +154,109 @@ void GlobalConfig::erase(KeyRangeRef range) { } } -ACTOR Future GlobalConfig::refresh(GlobalConfig* self, Version lastKnown, Version largestSeen) { +Future GlobalConfig::refresh(Version lastKnown, Version largestSeen) { // TraceEvent trace(SevInfo, "GlobalConfigRefresh"); - self->erase(KeyRangeRef(""_sr, "\xff"_sr)); + erase(KeyRangeRef(""_sr, "\xff"_sr)); - state Backoff backoff(CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_BACKOFF, CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_MAX_BACKOFF); + Backoff backoff(CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_BACKOFF, CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_MAX_BACKOFF); loop { + Error err; try { GlobalConfigRefreshReply reply = - wait(timeoutError(basicLoadBalance(self->cx->getGrvProxies(UseProvisionalProxies::False), - &GrvProxyInterface::refreshGlobalConfig, - GlobalConfigRefreshRequest{ lastKnown }), - CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_TIMEOUT)); + co_await timeoutError(basicLoadBalance(cx->getGrvProxies(UseProvisionalProxies::False), + &GrvProxyInterface::refreshGlobalConfig, + GlobalConfigRefreshRequest{ lastKnown }), + CLIENT_KNOBS->GLOBAL_CONFIG_REFRESH_TIMEOUT); for (const auto& kv : reply.result) { KeyRef systemKey = kv.key.removePrefix(globalConfigKeysPrefix); - self->insert(systemKey, kv.value); + insert(systemKey, kv.value); } if (reply.version >= largestSeen || largestSeen == std::numeric_limits::max()) { - return reply.version; - } else { - wait(delay(0.25)); + co_return reply.version; } + co_await delay(0.25); + continue; } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw; - } - - wait(backoff.onError()); + err = e; + } + if (err.code() == error_code_actor_cancelled) { + throw err; } + co_await backoff.onError(); } } // Applies updates to the local copy of the global configuration when this // process receives an updated history. -ACTOR Future GlobalConfig::updater(GlobalConfig* self, const ClientDBInfo* dbInfo) { +Future GlobalConfig::updater(const ClientDBInfo* dbInfo) { loop { + Error err; try { - if (self->initialized.canBeSet()) { - wait(self->cx->onConnected()); + if (initialized.canBeSet()) { + co_await cx->onConnected(); - Version version = wait(self->refresh(self, -1, 0)); - self->lastUpdate = version; + lastUpdate = co_await refresh(-1, 0); - self->cx->addref(); - self->initialized.send(Void()); - self->cx->delref(); + cx->addref(); + initialized.send(Void()); + cx->delref(); } loop { - try { - // run one iteration at the beginning - wait(delay(0)); - if (dbInfo->history.size() > 0) { - if (self->lastUpdate < dbInfo->history[0].version) { - // This process missed too many global configuration - // history updates or the protocol version changed, so it - // must re-read the entire configuration range. - Version version = - wait(self->refresh(self, self->lastUpdate, dbInfo->history.back().version)); - self->lastUpdate = version; - // DBInfo could have changed after the wait. If - // changes are present, re-run the loop to make - // sure they are applied. - if (dbInfo->history.size() > 0 && - dbInfo->history[0].version != std::numeric_limits::max()) { - continue; + // run one iteration at the beginning + co_await delay(0); + if (!dbInfo->history.empty()) { + if (lastUpdate < dbInfo->history[0].version) { + // This process missed too many global configuration + // history updates or the protocol version changed, so it + // must re-read the entire configuration range. + lastUpdate = co_await refresh(lastUpdate, dbInfo->history.back().version); + // DBInfo could have changed after the wait. If + // changes are present, re-run the loop to make + // sure they are applied. + if (!dbInfo->history.empty() && + dbInfo->history[0].version != std::numeric_limits::max()) { + continue; + } + } else { + // Apply history in order, from lowest version to highest + // version. Mutation history should already be stored in + // ascending version order. + for (const auto& vh : dbInfo->history) { + if (vh.version <= lastUpdate) { + continue; // already applied this mutation } - } else { - // Apply history in order, from lowest version to highest - // version. Mutation history should already be stored in - // ascending version order. - for (const auto& vh : dbInfo->history) { - if (vh.version <= self->lastUpdate) { - continue; // already applied this mutation - } - for (const auto& mutation : vh.mutations.contents()) { - if (mutation.type == MutationRef::SetValue) { - self->insert(mutation.param1, mutation.param2); - } else if (mutation.type == MutationRef::ClearRange) { - self->erase(KeyRangeRef(mutation.param1, mutation.param2)); - } else { - UNREACHABLE(); - } + for (const auto& mutation : vh.mutations.contents()) { + if (mutation.type == MutationRef::SetValue) { + insert(mutation.param1, mutation.param2); + } else if (mutation.type == MutationRef::ClearRange) { + erase(KeyRangeRef(mutation.param1, mutation.param2)); + } else { + UNREACHABLE(); } - - ASSERT(vh.version > self->lastUpdate); - self->lastUpdate = vh.version; } + + ASSERT(vh.version > lastUpdate); + lastUpdate = vh.version; } - self->configChanged.trigger(); } - // In case this actor is canceled in the d'tor of GlobalConfig we can exit here. - wait(delay(0)); - wait(self->dbInfoChanged.onTrigger()); - } catch (Error& e) { - throw; + configChanged.trigger(); } + // In case this actor is canceled in the d'tor of GlobalConfig we can exit here. + co_await delay(0); + co_await dbInfoChanged.onTrigger(); } } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw; - } - - // There shouldn't be any uncaught errors that fall to this point, - // but in case there are, catch them and restart the updater. - TraceEvent("GlobalConfigUpdaterError").error(e); - wait(delay(1.0)); + err = e; } + if (err.code() == error_code_actor_cancelled) { + throw err; + } + + // There shouldn't be any uncaught errors that fall to this point, + // but in case there are, catch them and restart the updater. + TraceEvent("GlobalConfigUpdaterError").error(err); + co_await delay(1.0); } } diff --git a/fdbclient/IKnobCollection.cpp b/fdbclient/IKnobCollection.cpp deleted file mode 100644 index 1531991b626..00000000000 --- a/fdbclient/IKnobCollection.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * IKnobCollection.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 "fdbclient/IKnobCollection.h" -#include "fdbclient/ClientKnobCollection.h" -#include "fdbclient/ServerKnobCollection.h" -#include "fdbclient/TestKnobCollection.h" - -std::unique_ptr IKnobCollection::create(Type type, Randomize randomize, IsSimulated isSimulated) { - if (type == Type::CLIENT) { - return std::make_unique(randomize, isSimulated); - } else if (type == Type::SERVER) { - return std::make_unique(randomize, isSimulated); - } else if (type == Type::TEST) { - return std::make_unique(randomize, isSimulated); - } - UNSTOPPABLE_ASSERT(false); -} - -KnobValue IKnobCollection::parseKnobValue(std::string const& knobName, std::string const& knobValue) const { - auto result = tryParseKnobValue(knobName, knobValue); - if (!result.present()) { - throw invalid_option(); - } - return result.get(); -} - -void IKnobCollection::setKnob(std::string const& knobName, KnobValueRef const& knobValue) { - if (!trySetKnob(knobName, knobValue)) { - TraceEvent(SevWarnAlways, "FailedToSetKnob") - .detail("KnobName", knobName) - .detail("KnobValue", knobValue.toString()); - throw invalid_option_value(); - } -} - -KnobValue IKnobCollection::parseKnobValue(std::string const& knobName, std::string const& knobValue, Type type) { - // TODO: Ideally it should not be necessary to create a template object to parse knobs - static std::unique_ptr clientKnobCollection, serverKnobCollection, testKnobCollection; - if (type == Type::CLIENT) { - if (!clientKnobCollection) { - clientKnobCollection = create(type, Randomize::False, IsSimulated::False); - } - return clientKnobCollection->parseKnobValue(knobName, knobValue); - } else if (type == Type::SERVER) { - if (!serverKnobCollection) { - serverKnobCollection = create(type, Randomize::False, IsSimulated::False); - } - return serverKnobCollection->parseKnobValue(knobName, knobValue); - } else if (type == Type::TEST) { - if (!testKnobCollection) { - testKnobCollection = create(type, Randomize::False, IsSimulated::False); - } - return testKnobCollection->parseKnobValue(knobName, knobValue); - } - UNSTOPPABLE_ASSERT(false); -} - -std::unique_ptr& IKnobCollection::globalKnobCollection() { - static std::unique_ptr res; - if (!res) { - res = IKnobCollection::create(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False); - } - return res; -} - -void IKnobCollection::setGlobalKnobCollection(Type type, Randomize randomize, IsSimulated isSimulated) { - globalKnobCollection() = create(type, randomize, isSimulated); - ASSERT(FLOW_KNOBS == &bootstrapGlobalFlowKnobs); - FLOW_KNOBS = &globalKnobCollection()->getFlowKnobs(); -} - -IKnobCollection const& IKnobCollection::getGlobalKnobCollection() { - return *globalKnobCollection(); -} - -IKnobCollection& IKnobCollection::getMutableGlobalKnobCollection() { - return *globalKnobCollection(); -} - -void IKnobCollection::setupKnobs(const std::vector>& knobs) { - auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection(); - for (const auto& [knobName, knobValueString] : knobs) { - try { - auto knobValue = g_knobs.parseKnobValue(knobName, knobValueString); - g_knobs.setKnob(knobName, knobValue); - } catch (Error& e) { - if (e.code() == error_code_invalid_option_value) { - std::cerr << "WARNING: Invalid value '" << knobValueString << "' for knob option '" << knobName - << "'\n"; - TraceEvent(SevWarnAlways, "InvalidKnobValue") - .detail("Knob", printable(knobName)) - .detail("Value", printable(knobValueString)); - } else if (e.code() == error_code_invalid_option) { - std::cerr << "WARNING: Invalid knob option '" << knobName << "'\n"; - TraceEvent(SevWarnAlways, "InvalidKnobName") - .detail("Knob", printable(knobName)) - .detail("Value", printable(knobValueString)); - } else { - std::cerr << "ERROR: Failed to set knob option '" << knobName << "': " << e.what() << "\n"; - TraceEvent(SevError, "FailedToSetKnob") - .errorUnsuppressed(e) - .detail("Knob", printable(knobName)) - .detail("Value", printable(knobValueString)); - throw e; - } - } - } -} diff --git a/fdbclient/IdempotencyId.actor.cpp b/fdbclient/IdempotencyId.cpp similarity index 81% rename from fdbclient/IdempotencyId.actor.cpp rename to fdbclient/IdempotencyId.cpp index f9110f9181b..c52099c9806 100644 --- a/fdbclient/IdempotencyId.actor.cpp +++ b/fdbclient/IdempotencyId.cpp @@ -1,5 +1,5 @@ /* - * IdempotencyId.actor.cpp + * IdempotencyId.cpp * * This source file is part of the FoundationDB open source project * @@ -18,13 +18,12 @@ * limitations under the License. */ -#include "fdbclient/IdempotencyId.actor.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/IdempotencyId.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/ReadYourWrites.h" #include "fdbclient/SystemData.h" #include "flow/BooleanParam.h" #include "flow/UnitTest.h" -#include "flow/actorcompiler.h" // this has to be the last include struct IdempotencyIdKVBuilderImpl { Optional commitVersion; @@ -153,7 +152,7 @@ TEST_CASE("/fdbclient/IdempotencyId/basic") { idSet.erase(id); ASSERT(idSet.find(id) == idSet.end()); } - ASSERT(idSet.size() == 0); + ASSERT(idSet.empty()); ASSERT(!kvContainsIdempotencyId(kv, generate(arena)).present()); @@ -210,15 +209,15 @@ FDB_BOOLEAN_PARAM(Oldest); // Find the youngest or oldest idempotency id key in `range` (depending on `oldest`) // Write the timestamp to `*time` and the version to `*version` when non-null. -ACTOR static Future> getBoundary(Reference tr, - KeyRange range, - Oldest oldest, - Version* version, - int64_t* time) { +static Future> getBoundary(Reference tr, + KeyRange range, + Oldest oldest, + Version* version, + int64_t* time) { RangeResult result = - wait(tr->getRange(range, /*limit*/ 1, Snapshot::False, oldest ? Reverse::False : Reverse::True)); - if (!result.size()) { - return Optional(); + co_await tr->getRange(range, /*limit*/ 1, Snapshot::False, oldest ? Reverse::False : Reverse::True); + if (result.empty()) { + co_return Optional(); } if (version != nullptr) { BinaryReader rd(result.front().key, Unversioned()); @@ -230,25 +229,26 @@ ACTOR static Future> getBoundary(Reference> *time; } - return result.front().key; + co_return result.front().key; } -ACTOR Future getIdmpKeyStatus(Database db) { - state Reference tr = makeReference(db); - state int64_t size; - state IdempotencyIdsExpiredVersion expired; - state KeyBackedObjectProperty expiredKey(idempotencyIdsExpiredVersion, - Unversioned()); - state int64_t oldestIdVersion = 0; - state int64_t oldestIdTime = 0; - loop { +AsyncResult getIdmpKeyStatus(Database db) { + auto tr = makeReference(db); + int64_t size{ 0 }; + IdempotencyIdsExpiredVersion expired; + KeyBackedObjectProperty expiredKey(idempotencyIdsExpiredVersion, + Unversioned()); + int64_t oldestIdVersion = 0; + int64_t oldestIdTime = 0; + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE); - wait(store(size, tr->getEstimatedRangeSizeBytes(idempotencyIdKeys)) && - store(expired, expiredKey.getD(tr)) && - success(getBoundary(tr, idempotencyIdKeys, Oldest::True, &oldestIdVersion, &oldestIdTime))); + co_await (store(size, tr->getEstimatedRangeSizeBytes(idempotencyIdKeys)) && + store(expired, expiredKey.getD(tr)) && + success(getBoundary(tr, idempotencyIdKeys, Oldest::True, &oldestIdVersion, &oldestIdTime))); JsonBuilderObject result; result["size_bytes"] = size; if (expired.expired != 0) { @@ -263,38 +263,40 @@ ACTOR Future getIdmpKeyStatus(Database db) { if (oldestIdTime != 0) { result["oldest_id_age"] = int64_t(now()) - oldestIdTime; } - return result; + co_return result; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } -ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds) { - state int64_t idmpKeySize; - state int64_t candidateDeleteSize; - state KeyRange finalRange; - state Reference tr; +Future cleanIdempotencyIds(Database db, double minAgeSeconds) { + int64_t idmpKeySize{ 0 }; + int64_t candidateDeleteSize{ 0 }; + KeyRange finalRange; + Reference tr; // Only assigned to once - state Key oldestKey; - state Version oldestVersion; - state int64_t oldestTime; + Key oldestKey; + Version oldestVersion{ 0 }; + int64_t oldestTime{ 0 }; // Assigned to multiple times looking for a suitable range - state Version candidateDeleteVersion; - state int64_t candidateDeleteTime; - state KeyRange candidateRangeToClean; + Version candidateDeleteVersion{ 0 }; + int64_t candidateDeleteTime{ 0 }; + KeyRange candidateRangeToClean; tr = makeReference(db); - loop { + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); // Check if any keys are older than minAgeSeconds Optional oldestKey_ = - wait(getBoundary(tr, idempotencyIdKeys, Oldest::True, &oldestVersion, &oldestTime)); + co_await getBoundary(tr, idempotencyIdKeys, Oldest::True, &oldestVersion, &oldestTime); if (!oldestKey_.present()) { break; } @@ -304,15 +306,14 @@ ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds) { } // Only used for a trace event - wait(store(idmpKeySize, tr->getEstimatedRangeSizeBytes(idempotencyIdKeys))); + idmpKeySize = co_await tr->getEstimatedRangeSizeBytes(idempotencyIdKeys); // Get the version of the most recent idempotency ID - wait(success( - getBoundary(tr, idempotencyIdKeys, Oldest::False, &candidateDeleteVersion, &candidateDeleteTime))); + co_await getBoundary(tr, idempotencyIdKeys, Oldest::False, &candidateDeleteVersion, &candidateDeleteTime); // Keep dividing the candidate range until clearing it would not delete something younger than // minAgeSeconds - loop { + while (true) { candidateRangeToClean = KeyRangeRef(oldestKey, @@ -325,8 +326,8 @@ ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds) { } // Find the youngest key in candidate range - wait(success(getBoundary( - tr, candidateRangeToClean, Oldest::False, &candidateDeleteVersion, &candidateDeleteTime))); + co_await getBoundary( + tr, candidateRangeToClean, Oldest::False, &candidateDeleteVersion, &candidateDeleteTime); // Update the range so that it ends at an idempotency id key. Since we're binary searching, the // candidate range was probably too large before. @@ -335,7 +336,7 @@ ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds) { BinaryWriter::toValue(bigEndian64(candidateDeleteVersion + 1), Unversioned()) .withPrefix(idempotencyIdKeys.begin)); - wait(store(candidateDeleteSize, tr->getEstimatedRangeSizeBytes(candidateRangeToClean))); + candidateDeleteSize = co_await tr->getEstimatedRangeSizeBytes(candidateRangeToClean); int64_t youngestAge = int64_t(now()) - candidateDeleteTime; TraceEvent("IdempotencyIdsCleanerCandidateDelete") @@ -363,13 +364,13 @@ ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds) { .detail("ClearRangeSizeEstimate", candidateDeleteSize) .detail("ExpiredVersion", candidateDeleteVersion) .detail("ExpiredVersionAgeEstimate", static_cast(now()) - candidateDeleteTime); - wait(tr->commit()); + co_await tr->commit(); } break; } catch (Error& e) { - TraceEvent("IdempotencyIdsCleanerError").error(e); - wait(tr->onError(e)); + err = e; } + TraceEvent("IdempotencyIdsCleanerError").error(err); + co_await tr->onError(err); } - return Void(); } diff --git a/fdbclient/JsonBuilder.cpp b/fdbclient/JsonBuilder.cpp index cc3073fe699..2eb458cdca7 100644 --- a/fdbclient/JsonBuilder.cpp +++ b/fdbclient/JsonBuilder.cpp @@ -19,7 +19,6 @@ */ #include "fdbclient/JsonBuilder.h" -#include JsonBuilderObject JsonBuilder::makeMessage(const char* name, const char* description) { JsonBuilderObject out; diff --git a/fdbclient/KeyRangeMap.actor.cpp b/fdbclient/KeyRangeMap.cpp similarity index 80% rename from fdbclient/KeyRangeMap.actor.cpp rename to fdbclient/KeyRangeMap.cpp index 94fb3c33ea8..a74c2c70276 100644 --- a/fdbclient/KeyRangeMap.actor.cpp +++ b/fdbclient/KeyRangeMap.cpp @@ -1,5 +1,5 @@ /* - * KeyRangeMap.actor.cpp + * KeyRangeMap.cpp * * This source file is part of the FoundationDB open source project * @@ -24,7 +24,6 @@ #include "fdbclient/FDBTypes.h" #include "fdbclient/ReadYourWrites.h" #include "flow/UnitTest.h" -#include "flow/actorcompiler.h" // has to be last include void KeyRangeActorMap::getRangesAffectedByInsertion(const KeyRangeRef& keys, std::vector& affectedRanges) { auto s = map.rangeContaining(keys.begin); @@ -90,67 +89,63 @@ RangeResult krmDecodeRanges(KeyRef mapPrefix, KeyRange keys, RangeResult kv, boo } // Returns keys.begin, all transitional points in keys, and keys.end, and their values -ACTOR Future krmGetRanges(Transaction* tr, Key mapPrefix, KeyRange keys, int limit, int limitBytes) { +Future krmGetRanges(Transaction* tr, Key mapPrefix, KeyRange keys, int limit, int limitBytes) { KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + keys.begin.toString(), mapPrefix.toString() + keys.end.toString()); - state GetRangeLimits limits(limit, limitBytes); + GetRangeLimits limits(limit, limitBytes); limits.minRows = 2; - RangeResult kv = wait(tr->getRange(lastLessOrEqual(withPrefix.begin), firstGreaterThan(withPrefix.end), limits)); + RangeResult kv = co_await tr->getRange(lastLessOrEqual(withPrefix.begin), firstGreaterThan(withPrefix.end), limits); - return krmDecodeRanges(mapPrefix, keys, kv); + co_return krmDecodeRanges(mapPrefix, keys, kv); } -ACTOR Future krmGetRanges(Reference tr, - Key mapPrefix, - KeyRange keys, - int limit, - int limitBytes) { +Future krmGetRanges(Reference tr, + Key mapPrefix, + KeyRange keys, + int limit, + int limitBytes) { KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + keys.begin.toString(), mapPrefix.toString() + keys.end.toString()); - state GetRangeLimits limits(limit, limitBytes); + GetRangeLimits limits(limit, limitBytes); limits.minRows = 2; - RangeResult kv = wait(tr->getRange(lastLessOrEqual(withPrefix.begin), firstGreaterThan(withPrefix.end), limits)); + RangeResult kv = co_await tr->getRange(lastLessOrEqual(withPrefix.begin), firstGreaterThan(withPrefix.end), limits); - return krmDecodeRanges(mapPrefix, keys, kv); + co_return krmDecodeRanges(mapPrefix, keys, kv); } // Returns keys.begin, all transitional points in keys, and keys.end, and their values -ACTOR Future krmGetRangesUnaligned(Transaction* tr, - Key mapPrefix, - KeyRange keys, - int limit, - int limitBytes) { +Future krmGetRangesUnaligned(Transaction* tr, Key mapPrefix, KeyRange keys, int limit, int limitBytes) { KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + keys.begin.toString(), mapPrefix.toString() + keys.end.toString()); - state GetRangeLimits limits(limit, limitBytes); + GetRangeLimits limits(limit, limitBytes); limits.minRows = 2; // wait to include the next highest row >= keys.end in the result, so since end is exclusive, we need +2 and // !orEqual RangeResult kv = - wait(tr->getRange(lastLessOrEqual(withPrefix.begin), KeySelectorRef(withPrefix.end, false, +2), limits)); + co_await tr->getRange(lastLessOrEqual(withPrefix.begin), KeySelectorRef(withPrefix.end, false, +2), limits); - return krmDecodeRanges(mapPrefix, keys, kv, false); + co_return krmDecodeRanges(mapPrefix, keys, kv, false); } -ACTOR Future krmGetRangesUnaligned(Reference tr, - Key mapPrefix, - KeyRange keys, - int limit, - int limitBytes) { +Future krmGetRangesUnaligned(Reference tr, + Key mapPrefix, + KeyRange keys, + int limit, + int limitBytes) { KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + keys.begin.toString(), mapPrefix.toString() + keys.end.toString()); - state GetRangeLimits limits(limit, limitBytes); + GetRangeLimits limits(limit, limitBytes); limits.minRows = 2; // wait to include the next highest row >= keys.end in the result, so since end is exclusive, we need +2 and // !orEqual RangeResult kv = - wait(tr->getRange(lastLessOrEqual(withPrefix.begin), KeySelectorRef(withPrefix.end, false, +2), limits)); + co_await tr->getRange(lastLessOrEqual(withPrefix.begin), KeySelectorRef(withPrefix.end, false, +2), limits); - return krmDecodeRanges(mapPrefix, keys, kv, false); + co_return krmDecodeRanges(mapPrefix, keys, kv, false); } void krmSetPreviouslyEmptyRange(Transaction* tr, @@ -176,14 +171,14 @@ void krmSetPreviouslyEmptyRange(CommitTransactionRef& tr, tr.set(trArena, withPrefix.end, oldEndValue); } -ACTOR Future krmSetRange(Transaction* tr, Key mapPrefix, KeyRange range, Value value) { - state KeyRange withPrefix = +Future krmSetRange(Transaction* tr, Key mapPrefix, KeyRange range, Value value) { + KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + range.begin.toString(), mapPrefix.toString() + range.end.toString()); RangeResult old = - wait(tr->getRange(lastLessOrEqual(withPrefix.end), firstGreaterThan(withPrefix.end), 1, Snapshot::True)); + co_await tr->getRange(lastLessOrEqual(withPrefix.end), firstGreaterThan(withPrefix.end), 1, Snapshot::True); Value oldValue; - bool hasResult = old.size() > 0 && old[0].key.startsWith(mapPrefix); + bool hasResult = !old.empty() && old[0].key.startsWith(mapPrefix); if (hasResult) oldValue = old[0].value; @@ -194,18 +189,16 @@ ACTOR Future krmSetRange(Transaction* tr, Key mapPrefix, KeyRange range, V tr->clear(withPrefix); tr->set(withPrefix.begin, value); tr->set(withPrefix.end, oldValue); - - return Void(); } -ACTOR Future krmSetRange(Reference tr, Key mapPrefix, KeyRange range, Value value) { - state KeyRange withPrefix = +Future krmSetRange(Reference tr, Key mapPrefix, KeyRange range, Value value) { + KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + range.begin.toString(), mapPrefix.toString() + range.end.toString()); RangeResult old = - wait(tr->getRange(lastLessOrEqual(withPrefix.end), firstGreaterThan(withPrefix.end), 1, Snapshot::True)); + co_await tr->getRange(lastLessOrEqual(withPrefix.end), firstGreaterThan(withPrefix.end), 1, Snapshot::True); Value oldValue; - bool hasResult = old.size() > 0 && old[0].key.startsWith(mapPrefix); + bool hasResult = !old.empty() && old[0].key.startsWith(mapPrefix); if (hasResult) oldValue = old[0].value; @@ -218,14 +211,12 @@ ACTOR Future krmSetRange(Reference tr, Key mapP // set keyVersionMap/end to oldValue: because end is exclusive here, // but starting from end it might be covered by another range file, so set it to old value tr->set(withPrefix.end, oldValue); - - return Void(); } // Sets a range of keys in a key range map, coalescing with adjacent regions if the values match // Ranges outside of maxRange will not be coalesced // CAUTION: use care when attempting to coalesce multiple ranges in the same prefix in a single transaction -ACTOR template +template static Future krmSetRangeCoalescing_(Transaction* tr, Key mapPrefix, KeyRange range, @@ -233,24 +224,24 @@ static Future krmSetRangeCoalescing_(Transaction* tr, Value value) { ASSERT(maxRange.contains(range)); - state KeyRange withPrefix = + KeyRange withPrefix = KeyRangeRef(mapPrefix.toString() + range.begin.toString(), mapPrefix.toString() + range.end.toString()); - state KeyRange maxWithPrefix = + KeyRange maxWithPrefix = KeyRangeRef(mapPrefix.toString() + maxRange.begin.toString(), mapPrefix.toString() + maxRange.end.toString()); - state std::vector> keys; + std::vector> keys; keys.push_back( tr->getRange(lastLessThan(withPrefix.begin), firstGreaterOrEqual(withPrefix.begin), 1, Snapshot::True)); keys.push_back( tr->getRange(lastLessOrEqual(withPrefix.end), firstGreaterThan(withPrefix.end) + 1, 2, Snapshot::True)); - wait(waitForAll(keys)); + co_await waitForAll(keys); // Determine how far to extend this range at the beginning auto beginRange = keys[0].get(); - bool hasBegin = beginRange.size() > 0 && beginRange[0].key.startsWith(mapPrefix); + bool hasBegin = !beginRange.empty() && beginRange[0].key.startsWith(mapPrefix); Value beginValue = hasBegin ? beginRange[0].value : ""_sr; - state Key beginKey = withPrefix.begin; + Key beginKey = withPrefix.begin; if (beginValue == value) { bool outsideRange = !hasBegin || beginRange[0].key < maxWithPrefix.begin; beginKey = outsideRange ? maxWithPrefix.begin : beginRange[0].key; @@ -258,7 +249,7 @@ static Future krmSetRangeCoalescing_(Transaction* tr, // Determine how far to extend this range at the end auto endRange = keys[1].get(); - bool hasEnd = endRange.size() >= 1 && endRange[0].key.startsWith(mapPrefix) && endRange[0].key <= withPrefix.end; + bool hasEnd = !endRange.empty() && endRange[0].key.startsWith(mapPrefix) && endRange[0].key <= withPrefix.end; bool hasNext = (endRange.size() == 2 && endRange[1].key.startsWith(mapPrefix)) || (endRange.size() == 1 && withPrefix.end < endRange[0].key && endRange[0].key.startsWith(mapPrefix)); Value existingValue = hasEnd ? endRange[0].value : ""_sr; @@ -273,8 +264,8 @@ static Future krmSetRangeCoalescing_(Transaction* tr, if (!conflictRange.empty()) tr->addReadConflictRange(conflictRange); - state Key endKey; - state Value endValue; + Key endKey; + Value endValue; // Case 1: Coalesce completely with the following range if (hasNext && endRange.end()[-1].key <= maxWithPrefix.end && valueMatches) { @@ -299,8 +290,6 @@ static Future krmSetRangeCoalescing_(Transaction* tr, ASSERT(value != endValue || endKey == maxWithPrefix.end); tr->set(beginKey, value); tr->set(endKey, endValue); - - return Void(); } Future krmSetRangeCoalescing(Transaction* const& tr, Key const& mapPrefix, @@ -361,8 +350,8 @@ TEST_CASE("/keyrangemap/decoderange/aligned") { // [""(start), A, B, C, D, E(end)] decodedRanges = krmDecodeRanges(prefix, KeyRangeRef(StringRef(), keyE), kv); ASSERT(decodedRanges.size() == 6); - ASSERT(decodedRanges.front().key == StringRef()); - ASSERT(decodedRanges.front().value == StringRef()); + ASSERT(decodedRanges.front().key.empty()); + ASSERT(decodedRanges.front().value.empty()); ASSERT(decodedRanges.back().key == keyE); ASSERT(decodedRanges.back().value == keyD); @@ -413,10 +402,10 @@ TEST_CASE("/keyrangemap/decoderange/unaligned") { // [""(start), A, B, C, D, E(end)] decodedRanges = krmDecodeRanges(prefix, KeyRangeRef(StringRef(), keyE), kv, false); ASSERT(decodedRanges.size() == 6); - ASSERT(decodedRanges.front().key == StringRef()); - ASSERT(decodedRanges.front().value == StringRef()); + ASSERT(decodedRanges.front().key.empty()); + ASSERT(decodedRanges.front().value.empty()); ASSERT(decodedRanges.back().key == keyE); ASSERT(decodedRanges.back().value == keyD); return Void(); -} \ No newline at end of file +} diff --git a/fdbclient/KnobValue.cpp b/fdbclient/KnobValue.cpp index c63473af1af..bb466855ed2 100644 --- a/fdbclient/KnobValue.cpp +++ b/fdbclient/KnobValue.cpp @@ -21,7 +21,6 @@ #include "fdbclient/KnobValue.h" #include "fdbclient/Tuple.h" #include "flow/flow.h" -#include "flow/Trace.h" namespace { diff --git a/fdbclient/LocalClientAPI.cpp b/fdbclient/LocalClientAPI.cpp index d404ede86d2..1e6597a9e4b 100644 --- a/fdbclient/LocalClientAPI.cpp +++ b/fdbclient/LocalClientAPI.cpp @@ -18,10 +18,10 @@ * limitations under the License. */ -#include "fdbclient/LocalClientAPI.h" +#include "LocalClientAPI.h" #include "fdbclient/ThreadSafeTransaction.h" IClientApi* getLocalClientAPI() { static IClientApi* api = new ThreadSafeApi(); return api; -} \ No newline at end of file +} diff --git a/fdbclient/include/fdbclient/LocalClientAPI.h b/fdbclient/LocalClientAPI.h similarity index 99% rename from fdbclient/include/fdbclient/LocalClientAPI.h rename to fdbclient/LocalClientAPI.h index 8569cd40485..4d2a17cd73f 100644 --- a/fdbclient/include/fdbclient/LocalClientAPI.h +++ b/fdbclient/LocalClientAPI.h @@ -25,4 +25,4 @@ #include "fdbclient/IClientApi.h" IClientApi* getLocalClientAPI(); -#endif \ No newline at end of file +#endif diff --git a/fdbclient/ManagementAPI.actor.cpp b/fdbclient/ManagementAPI.cpp similarity index 68% rename from fdbclient/ManagementAPI.actor.cpp rename to fdbclient/ManagementAPI.cpp index ae7b713d126..dfec09d5d13 100644 --- a/fdbclient/ManagementAPI.actor.cpp +++ b/fdbclient/ManagementAPI.cpp @@ -1,5 +1,5 @@ /* - * ManagementAPI.actor.cpp + * ManagementAPI.cpp * * This source file is part of the FoundationDB open source project * @@ -25,7 +25,7 @@ #include "fdbclient/BulkDumping.h" #include "fdbclient/BulkLoading.h" -#include "fdbclient/GenericManagementAPI.actor.h" +#include "fdbclient/GenericManagementAPI.h" #include "fdbclient/KeyRangeMap.h" #include "fdbclient/RangeLock.h" #include "flow/Error.h" @@ -36,7 +36,7 @@ #include "fdbclient/FDBOptions.g.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/SystemData.h" #include "fdbclient/NativeAPI.actor.h" @@ -51,7 +51,7 @@ #include "fdbclient/Schemas.h" #include "fdbrpc/SimulatorProcessInfo.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "flow/CoroUtils.h" bool isInteger(const std::string& s) { if (s.empty()) @@ -270,59 +270,40 @@ std::map configForToken(std::string const& mode) { if (mode == "single") { redundancy = "1"; log_replicas = "1"; - storagePolicy = tLogPolicy = Reference(new PolicyOne()); + storagePolicy = tLogPolicy = makeReference(); } else if (mode == "double" || mode == "fast_recovery_double") { redundancy = "2"; log_replicas = "2"; - storagePolicy = tLogPolicy = Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))); + storagePolicy = tLogPolicy = makeReference(2, "zoneid", makeReference()); } else if (mode == "triple" || mode == "fast_recovery_triple") { redundancy = "3"; log_replicas = "3"; - storagePolicy = tLogPolicy = Reference( - new PolicyAcross(3, "zoneid", Reference(new PolicyOne()))); + storagePolicy = tLogPolicy = makeReference(3, "zoneid", makeReference()); } else if (mode == "three_datacenter" || mode == "multi_dc") { redundancy = "6"; log_replicas = "4"; - storagePolicy = Reference( - new PolicyAcross(3, - "dcid", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); - tLogPolicy = Reference( - new PolicyAcross(2, - "dcid", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); + storagePolicy = makeReference( + 3, "dcid", makeReference(2, "zoneid", makeReference())); + tLogPolicy = makeReference( + 2, "dcid", makeReference(2, "zoneid", makeReference())); } else if (mode == "three_datacenter_fallback") { redundancy = "4"; log_replicas = "4"; - storagePolicy = tLogPolicy = Reference( - new PolicyAcross(2, - "dcid", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); + storagePolicy = tLogPolicy = makeReference( + 2, "dcid", makeReference(2, "zoneid", makeReference())); } else if (mode == "three_data_hall") { redundancy = "3"; log_replicas = "4"; - storagePolicy = Reference( - new PolicyAcross(3, "data_hall", Reference(new PolicyOne()))); - tLogPolicy = Reference( - new PolicyAcross(2, - "data_hall", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); + storagePolicy = makeReference(3, "data_hall", makeReference()); + tLogPolicy = makeReference( + 2, "data_hall", makeReference(2, "zoneid", makeReference())); } else if (mode == "three_data_hall_fallback") { redundancy = "2"; log_replicas = "4"; - storagePolicy = Reference( - new PolicyAcross(2, "data_hall", Reference(new PolicyOne()))); - tLogPolicy = Reference( - new PolicyAcross(2, - "data_hall", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); + storagePolicy = makeReference(2, "data_hall", makeReference()); + tLogPolicy = makeReference( + 2, "data_hall", makeReference(2, "zoneid", makeReference())); } else redundancySpecified = false; if (redundancySpecified) { @@ -350,25 +331,20 @@ std::map configForToken(std::string const& mode) { } else if (mode == "remote_single") { remote_redundancy = "1"; remote_log_replicas = "1"; - remoteTLogPolicy = Reference(new PolicyOne()); + remoteTLogPolicy = makeReference(); } else if (mode == "remote_double") { remote_redundancy = "2"; remote_log_replicas = "2"; - remoteTLogPolicy = Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))); + remoteTLogPolicy = makeReference(2, "zoneid", makeReference()); } else if (mode == "remote_triple") { remote_redundancy = "3"; remote_log_replicas = "3"; - remoteTLogPolicy = Reference( - new PolicyAcross(3, "zoneid", Reference(new PolicyOne()))); + remoteTLogPolicy = makeReference(3, "zoneid", makeReference()); } else if (mode == "remote_three_data_hall") { // FIXME: not tested in simulation remote_redundancy = "3"; remote_log_replicas = "4"; - remoteTLogPolicy = Reference( - new PolicyAcross(2, - "data_hall", - Reference( - new PolicyAcross(2, "zoneid", Reference(new PolicyOne()))))); + remoteTLogPolicy = makeReference( + 2, "data_hall", makeReference(2, "zoneid", makeReference())); } else remoteRedundancySpecified = false; if (remoteRedundancySpecified) { @@ -388,13 +364,13 @@ ConfigurationResult buildConfiguration(std::vector const& modeTokens, for (auto it : modeTokens) { std::string mode = it.toString(); auto m = configForToken(mode); - if (!m.size()) { + if (m.empty()) { TraceEvent(SevWarnAlways, "UnknownOption").detail("Option", mode); return ConfigurationResult::UNKNOWN_OPTION; } for (auto t = m.begin(); t != m.end(); ++t) { - if (outConf.count(t->first)) { + if (outConf.contains(t->first)) { TraceEvent(SevWarnAlways, "ConflictingOption") .detail("Option", t->first) .detail("Value", t->second) @@ -405,34 +381,34 @@ ConfigurationResult buildConfiguration(std::vector const& modeTokens, } } auto p = configKeysPrefix.toString(); - if (!outConf.count(p + "storage_replication_policy") && outConf.count(p + "storage_replicas")) { + if (!outConf.contains(p + "storage_replication_policy") && outConf.contains(p + "storage_replicas")) { int storageCount = stoi(outConf[p + "storage_replicas"]); - Reference storagePolicy = Reference( - new PolicyAcross(storageCount, "zoneid", Reference(new PolicyOne()))); + Reference storagePolicy = + makeReference(storageCount, "zoneid", makeReference()); BinaryWriter policyWriter(IncludeVersion(ProtocolVersion::withReplicationPolicy())); serializeReplicationPolicy(policyWriter, storagePolicy); outConf[p + "storage_replication_policy"] = policyWriter.toValue().toString(); } - if (!outConf.count(p + "log_replication_policy") && outConf.count(p + "log_replicas")) { + if (!outConf.contains(p + "log_replication_policy") && outConf.contains(p + "log_replicas")) { int logCount = stoi(outConf[p + "log_replicas"]); - Reference logPolicy = Reference( - new PolicyAcross(logCount, "zoneid", Reference(new PolicyOne()))); + Reference logPolicy = + makeReference(logCount, "zoneid", makeReference()); BinaryWriter policyWriter(IncludeVersion(ProtocolVersion::withReplicationPolicy())); serializeReplicationPolicy(policyWriter, logPolicy); outConf[p + "log_replication_policy"] = policyWriter.toValue().toString(); } - if (outConf.count(p + "istss")) { + if (outConf.contains(p + "istss")) { // redo config parameters to be tss config instead of normal config // save param values from parsing as a normal config - bool isNew = outConf.count(p + "initialized"); + bool isNew = outConf.contains(p + "initialized"); Optional count; Optional storageEngine; - if (outConf.count(p + "count")) { + if (outConf.contains(p + "count")) { count = Optional(outConf[p + "count"]); } - if (outConf.count(p + "storage_engine")) { + if (outConf.contains(p + "storage_engine")) { storageEngine = Optional(outConf[p + "storage_engine"]); } @@ -477,56 +453,84 @@ bool isCompleteConfiguration(std::map const& options) options.count(p + "storage_engine") == 1; } -ACTOR Future disableBackupWorker(Database cx) { - DatabaseConfiguration configuration = wait(getDatabaseConfiguration(cx)); +Future disableBackupWorker(Database cx) { + DatabaseConfiguration configuration = co_await getDatabaseConfiguration(cx); if (!configuration.backupWorkerEnabled) { TraceEvent("BackupWorkerAlreadyDisabled"); - return Void(); + co_return; } - ConfigurationResult res = wait(ManagementAPI::changeConfig(cx.getReference(), "backup_worker_enabled:=0", true)); + ConfigurationResult res = co_await ManagementAPI::changeConfig(cx.getReference(), "backup_worker_enabled:=0", true); if (res != ConfigurationResult::SUCCESS) { TraceEvent("BackupWorkerDisableFailed").detail("Result", res); throw operation_failed(); } - return Void(); } -ACTOR Future enableBackupWorker(Database cx) { - DatabaseConfiguration configuration = wait(getDatabaseConfiguration(cx)); +Future enableBackupWorker(Database cx) { + DatabaseConfiguration configuration = co_await getDatabaseConfiguration(cx); if (configuration.backupWorkerEnabled) { TraceEvent("BackupWorkerAlreadyEnabled"); - return Void(); + co_return; } - ConfigurationResult res = wait(ManagementAPI::changeConfig(cx.getReference(), "backup_worker_enabled:=1", true)); + ConfigurationResult res = co_await ManagementAPI::changeConfig(cx.getReference(), "backup_worker_enabled:=1", true); if (res != ConfigurationResult::SUCCESS) { TraceEvent("BackupWorkerEnableFailed").detail("Result", res); throw operation_failed(); } - return Void(); } -ACTOR Future getDatabaseConfiguration(Transaction* tr, bool useSystemPriority) { +Future enableRangeBackupWorker(Database cx) { + DatabaseConfiguration configuration = co_await getDatabaseConfiguration(cx); + if (configuration.rangeBackupWorkerEnabled) { + TraceEvent("RangeBackupWorkerAlreadyEnabled"); + co_return; + } + ConfigurationResult res = + co_await ManagementAPI::changeConfig(cx.getReference(), "range_backup_worker_enabled:=1", true); + if (res != ConfigurationResult::SUCCESS) { + TraceEvent("RangeBackupWorkerEnableFailed").detail("Result", res); + throw operation_failed(); + } +} + +Future disableRangeBackupWorker(Database cx) { + DatabaseConfiguration configuration = co_await getDatabaseConfiguration(cx); + if (!configuration.rangeBackupWorkerEnabled) { + TraceEvent("RangeBackupWorkerAlreadyDisabled"); + co_return; + } + ConfigurationResult res = + co_await ManagementAPI::changeConfig(cx.getReference(), "range_backup_worker_enabled:=0", true); + if (res != ConfigurationResult::SUCCESS) { + TraceEvent("RangeBackupWorkerDisableFailed").detail("Result", res); + throw operation_failed(); + } +} + +Future getDatabaseConfiguration(Transaction* tr, bool useSystemPriority) { if (useSystemPriority) { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); } tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - RangeResult res = wait(tr->getRange(configKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult res = co_await tr->getRange(configKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(res.size() < CLIENT_KNOBS->TOO_MANY); DatabaseConfiguration config; config.fromKeyValues((VectorRef)res); - return config; + co_return config; } -ACTOR Future getDatabaseConfiguration(Database cx, bool useSystemPriority) { - state Transaction tr(cx); - loop { +Future getDatabaseConfiguration(Database cx, bool useSystemPriority) { + Transaction tr(cx); + while (true) { + Error err; try { - DatabaseConfiguration config = wait(getDatabaseConfiguration(&tr, useSystemPriority)); - return config; + DatabaseConfiguration config = co_await getDatabaseConfiguration(&tr, useSystemPriority); + co_return config; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -754,7 +758,7 @@ ConfigureAutoResult parseConfig(StatusObject const& status) { // if one process on a machine is transaction class, make them all transaction class for (auto& it : count_processes) { - if (machinesWithTransaction.count(it.first.second) && !machinesWithStorage.count(it.first.second)) { + if (machinesWithTransaction.contains(it.first.second) && !machinesWithStorage.contains(it.first.second)) { for (auto& proc : it.second) { if (proc.second == ProcessClass::UnsetClass && proc.second.classSource() == ProcessClass::CommandLineSource) { @@ -773,7 +777,7 @@ ConfigureAutoResult parseConfig(StatusObject const& status) { if (machinesWithTransaction.size() >= logCount && totalTransactionProcesses >= desiredTotalTransactionProcesses) break; - if (!machinesWithTransaction.count(it.first.second) && !machinesWithStorage.count(it.first.second)) { + if (!machinesWithTransaction.contains(it.first.second) && !machinesWithStorage.contains(it.first.second)) { for (auto& proc : it.second) { if (proc.second == ProcessClass::UnsetClass && proc.second.classSource() == ProcessClass::CommandLineSource) { @@ -799,11 +803,11 @@ ConfigureAutoResult parseConfig(StatusObject const& status) { return result; } -ACTOR Future> getWorkers(Transaction* tr) { - state Future processClasses = tr->getRange(processClassKeys, CLIENT_KNOBS->TOO_MANY); - state Future processData = tr->getRange(workerListKeys, CLIENT_KNOBS->TOO_MANY); +Future> getWorkers(Transaction* tr) { + Future processClasses = tr->getRange(processClassKeys, CLIENT_KNOBS->TOO_MANY); + Future processData = tr->getRange(workerListKeys, CLIENT_KNOBS->TOO_MANY); - wait(success(processClasses) && success(processData)); + co_await (success(processClasses) && success(processData)); ASSERT(!processClasses.get().more && processClasses.get().size() < CLIENT_KNOBS->TOO_MANY); ASSERT(!processData.get().more && processData.get().size() < CLIENT_KNOBS->TOO_MANY); @@ -827,37 +831,41 @@ ACTOR Future> getWorkers(Transaction* tr) { results.push_back(data); } - return results; + co_return results; } -ACTOR Future> getWorkers(Database cx) { - state Transaction tr(cx); - loop { +Future> getWorkers(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); // necessary? tr.setOption(FDBTransactionOptions::LOCK_AWARE); - std::vector workers = wait(getWorkers(&tr)); - return workers; + std::vector workers = co_await getWorkers(&tr); + co_return workers; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future> getConnectionString(Database cx) { - state Transaction tr(cx); - loop { +Future> getConnectionString(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - Optional currentKey = wait(tr.get(coordinatorsKey)); + Optional currentKey = co_await tr.get(coordinatorsKey); if (!currentKey.present()) - return Optional(); - return ClusterConnectionString(currentKey.get().toString()); + co_return Optional(); + co_return ClusterConnectionString(currentKey.get().toString()); } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -865,20 +873,20 @@ static std::vector connectionStrings; namespace { -ACTOR Future> getClusterConnectionStringFromStorageServer(Transaction* tr) { +Future> getClusterConnectionStringFromStorageServer(Transaction* tr) { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - state int retryTimes = 0; - loop { + int retryTimes = 0; + while (true) { if (retryTimes >= CLIENT_KNOBS->CHANGE_QUORUM_BAD_STATE_RETRY_TIMES) { - return Optional(); + co_return Optional(); } - state Optional currentKey = wait(tr->get(coordinatorsKey)); + Optional currentKey = co_await tr->get(coordinatorsKey); if (g_network->isSimulated() && currentKey.present()) { // If the change coordinators request succeeded, the coordinators // should have changed to the connection string of the most @@ -887,7 +895,7 @@ ACTOR Future> getClusterConnectionStringFromSt // and we are breaking the promises we make with // commit_unknown_result (the transaction must no longer be in // progress when receiving commit_unknown_result). - int n = connectionStrings.size() > 0 ? connectionStrings.size() - 1 : 0; // avoid underflow + int n = !connectionStrings.empty() ? connectionStrings.size() - 1 : 0; // avoid underflow for (int i = 0; i < n; ++i) { ASSERT(currentKey.get() != connectionStrings.at(i)); } @@ -896,69 +904,71 @@ ACTOR Future> getClusterConnectionStringFromSt if (!currentKey.present()) { // Someone deleted this key entirely? ++retryTimes; - wait(delay(CLIENT_KNOBS->CHANGE_QUORUM_BAD_STATE_RETRY_DELAY)); + co_await delay(CLIENT_KNOBS->CHANGE_QUORUM_BAD_STATE_RETRY_DELAY); continue; } - state ClusterConnectionString clusterConnectionString(currentKey.get().toString()); + ClusterConnectionString clusterConnectionString(currentKey.get().toString()); if (tr->getDatabase()->getConnectionRecord() && clusterConnectionString.clusterKeyName().toString() != tr->getDatabase()->getConnectionRecord()->getConnectionString().clusterKeyName()) { // Someone changed the "name" of the database?? ++retryTimes; - wait(delay(CLIENT_KNOBS->CHANGE_QUORUM_BAD_STATE_RETRY_DELAY)); + co_await delay(CLIENT_KNOBS->CHANGE_QUORUM_BAD_STATE_RETRY_DELAY); continue; } - return clusterConnectionString; + co_return clusterConnectionString; } } -ACTOR Future resetPreviousCoordinatorsKey(Database cx) { - loop { +Future resetPreviousCoordinatorsKey(Database cx) { + while (true) { // When the change coordinators transaction succeeds, it uses the // special key space error message to return a message to the client. // This causes the underlying transaction to not be committed. In order // to make sure we clear the previous coordinators key, we have to use // a new transaction here. - state Reference clearTr = makeReference(cx); + auto clearTr = makeReference(cx); + Error err; try { clearTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); clearTr->clear(previousCoordinatorsKey); - wait(clearTr->commit()); - return Void(); + co_await clearTr->commit(); + co_return; } catch (Error& e2) { - wait(clearTr->onError(e2)); + err = e2; } + co_await clearTr->onError(err); } } } // namespace -ACTOR Future> changeQuorumChecker(Transaction* tr, - ClusterConnectionString* conn, - std::string newName) { +Future> changeQuorumChecker(Transaction* tr, + ClusterConnectionString* conn, + std::string newName) { TraceEvent("ChangeQuorumCheckerStart").detail("NewConnectionString", conn->toString()); - state Optional clusterConnectionStringOptional = - wait(getClusterConnectionStringFromStorageServer(tr)); + Optional clusterConnectionStringOptional = + co_await getClusterConnectionStringFromStorageServer(tr); if (!clusterConnectionStringOptional.present()) { - return CoordinatorsResult::BAD_DATABASE_STATE; + co_return CoordinatorsResult::BAD_DATABASE_STATE; } // The cluster connection string stored in the storage server - state ClusterConnectionString old = clusterConnectionStringOptional.get(); + ClusterConnectionString old = clusterConnectionStringOptional.get(); if (conn->hostnames.size() + conn->coords.size() == 0) { conn->hostnames = old.hostnames; conn->coords = old.coords; } - state std::vector desiredCoordinators = wait(conn->tryResolveHostnames()); + std::vector desiredCoordinators = co_await conn->tryResolveHostnames(); if (desiredCoordinators.size() != conn->hostnames.size() + conn->coords.size()) { TraceEvent("ChangeQuorumCheckerEarlyTermination") .detail("Reason", "One or more hostnames are unresolvable") .backtrace(); - return CoordinatorsResult::COORDINATOR_UNREACHABLE; + co_return CoordinatorsResult::COORDINATOR_UNREACHABLE; } if (newName.empty()) { @@ -974,10 +984,10 @@ ACTOR Future> changeQuorumChecker(Transaction* tr, // Introduce a random delay in simulation to allow processes to be // killed before previousCoordinatorKeys has been reset. This helps // exercise coordinator change edge cases around key cleanup. - wait(delay(deterministicRandom()->random01() * 10)); + co_await delay(deterministicRandom()->random01() * 10); } - wait(resetPreviousCoordinatorsKey(tr->getDatabase())); - return CoordinatorsResult::SAME_NETWORK_ADDRESSES; + co_await resetPreviousCoordinatorsKey(tr->getDatabase()); + co_return CoordinatorsResult::SAME_NETWORK_ADDRESSES; } conn->parseKey(newName + ':' + deterministicRandom()->randomAlphaNumeric(32)); @@ -996,9 +1006,9 @@ ACTOR Future> changeQuorumChecker(Transaction* tr, continue; } - g_simulator->protectedAddresses.insert(process->addresses.address); + g_simulator->protectAddress(process->addresses.address); if (addresses.secondaryAddress.present()) { - g_simulator->protectedAddresses.insert(process->addresses.secondaryAddress.get()); + g_simulator->protectAddress(process->addresses.secondaryAddress.get()); } TraceEvent("ProtectCoordinator").detail("Address", desiredCoordinators[i]).backtrace(); protectedCount++; @@ -1011,12 +1021,12 @@ ACTOR Future> changeQuorumChecker(Transaction* tr, .detail("MinimumRequired", minimumCoordinators) .detail("ConnectionString", conn->toString()); - return CoordinatorsResult::COORDINATOR_UNREACHABLE; + co_return CoordinatorsResult::COORDINATOR_UNREACHABLE; } } std::vector>> leaderServers; - ClientCoordinators coord(Reference(new ClusterConnectionMemoryRecord(*conn))); + ClientCoordinators coord(makeReference(*conn)); leaderServers.reserve(coord.clientLeaderServers.size()); for (int i = 0; i < coord.clientLeaderServers.size(); i++) { @@ -1032,47 +1042,45 @@ ACTOR Future> changeQuorumChecker(Transaction* tr, } } - choose { - when(wait(waitForAll(leaderServers))) {} - when(wait(delay(5.0))) { - return CoordinatorsResult::COORDINATOR_UNREACHABLE; - } + auto leaderServersResult = co_await timeout(waitForAll(leaderServers), 5.0); + if (!leaderServersResult.present()) { + co_return CoordinatorsResult::COORDINATOR_UNREACHABLE; } TraceEvent("ChangeQuorumCheckerSetCoordinatorsKey") .detail("CurrentCoordinators", old.toString()) .detail("NewCoordinators", conn->toString()); tr->set(coordinatorsKey, conn->toString()); - return Optional(); + co_return Optional(); } -ACTOR Future changeQuorum(Database cx, Reference change) { - state Transaction tr(cx); - state int retries = 0; - state std::vector desiredCoordinators; - state int notEnoughMachineResults = 0; +Future changeQuorum(Database cx, Reference change) { + Transaction tr(cx); + int retries = 0; + std::vector desiredCoordinators; + int notEnoughMachineResults = 0; - loop { + while (true) { + Error err; try { - state Optional clusterConnectionStringOptional = - wait(getClusterConnectionStringFromStorageServer(&tr)); + Optional clusterConnectionStringOptional = + co_await getClusterConnectionStringFromStorageServer(&tr); if (!clusterConnectionStringOptional.present()) { - return CoordinatorsResult::BAD_DATABASE_STATE; + co_return CoordinatorsResult::BAD_DATABASE_STATE; } // The cluster connection string stored in the storage server - state ClusterConnectionString oldClusterConnectionString = clusterConnectionStringOptional.get(); - state Key oldClusterKeyName = oldClusterConnectionString.clusterKeyName(); - - state std::vector oldCoordinators = wait(oldClusterConnectionString.tryResolveHostnames()); - state CoordinatorsResult result = CoordinatorsResult::SUCCESS; - if (!desiredCoordinators.size()) { - std::vector _desiredCoordinators = wait( - change->getDesiredCoordinators(&tr, - oldCoordinators, - Reference( - new ClusterConnectionMemoryRecord(oldClusterConnectionString)), - result)); + ClusterConnectionString oldClusterConnectionString = clusterConnectionStringOptional.get(); + Key oldClusterKeyName = oldClusterConnectionString.clusterKeyName(); + + std::vector oldCoordinators = co_await oldClusterConnectionString.tryResolveHostnames(); + CoordinatorsResult result = CoordinatorsResult::SUCCESS; + if (desiredCoordinators.empty()) { + std::vector _desiredCoordinators = co_await change->getDesiredCoordinators( + &tr, + oldCoordinators, + makeReference(oldClusterConnectionString), + result); desiredCoordinators = _desiredCoordinators; } @@ -1080,14 +1088,14 @@ ACTOR Future changeQuorum(Database cx, ReferencegetDesiredClusterKeyName(); @@ -1095,20 +1103,20 @@ ACTOR Future changeQuorum(Database cx, ReferencerandomAlphaNumeric(32))); - state Key newClusterKeyName = newClusterConnectionString.clusterKeyName(); + Key newClusterKeyName = newClusterConnectionString.clusterKeyName(); if (g_network->isSimulated()) { for (int i = 0; i < (desiredCoordinators.size() / 2) + 1; i++) { auto process = g_simulator->getProcessByAddress(desiredCoordinators[i]); ASSERT(process->isReliable() || process->rebooting); - g_simulator->protectedAddresses.insert(process->addresses.address); + g_simulator->protectAddress(process->addresses.address); if (process->addresses.secondaryAddress.present()) { - g_simulator->protectedAddresses.insert(process->addresses.secondaryAddress.get()); + g_simulator->protectAddress(process->addresses.secondaryAddress.get()); } TraceEvent("ProtectCoordinator").detail("Address", desiredCoordinators[i]).backtrace(); } @@ -1120,15 +1128,16 @@ ACTOR Future changeQuorum(Database cx, Reference>> leaderServers; - state ClientCoordinators coord(Reference( + std::vector>> leaderServers; + ClientCoordinators coord(Reference( new ClusterConnectionMemoryRecord(newClusterConnectionString))); // check if allowed to modify the cluster descriptor if (!change->getDesiredClusterKeyName().empty()) { CheckDescriptorMutableReply mutabilityReply = - wait(coord.clientLeaderServers[0].checkDescriptorMutable.getReply(CheckDescriptorMutableRequest())); + co_await coord.clientLeaderServers[0].checkDescriptorMutable.getReply( + CheckDescriptorMutableRequest()); if (!mutabilityReply.isMutable) { - return CoordinatorsResult::BAD_DATABASE_STATE; + co_return CoordinatorsResult::BAD_DATABASE_STATE; } } leaderServers.reserve(coord.clientLeaderServers.size()); @@ -1136,22 +1145,21 @@ ACTOR Future changeQuorum(Database cx, Reference nameQuorumChange(std::string const& name, Reference const& other) { - return Reference(new NameQuorumChange(name, other)); + return makeReference(name, other); } struct AutoQuorumChange final : IQuorumChange { @@ -1183,37 +1191,37 @@ struct AutoQuorumChange final : IQuorumChange { return getDesired(Reference::addRef(this), tr, oldCoordinators, ccr, &err); } - ACTOR static Future getRedundancy(AutoQuorumChange* self, Transaction* tr) { - state Future> fStorageReplicas = tr->get("storage_replicas"_sr.withPrefix(configKeysPrefix)); - state Future> fLogReplicas = tr->get("log_replicas"_sr.withPrefix(configKeysPrefix)); - wait(success(fStorageReplicas) && success(fLogReplicas)); + static Future getRedundancy(AutoQuorumChange* self, Transaction* tr) { + Future> fStorageReplicas = tr->get("storage_replicas"_sr.withPrefix(configKeysPrefix)); + Future> fLogReplicas = tr->get("log_replicas"_sr.withPrefix(configKeysPrefix)); + co_await (success(fStorageReplicas) && success(fLogReplicas)); int redundancy = std::min(atoi(fStorageReplicas.get().get().toString().c_str()), atoi(fLogReplicas.get().get().toString().c_str())); - return redundancy; + co_return redundancy; } - ACTOR static Future isAcceptable(AutoQuorumChange* self, - Transaction* tr, - std::vector oldCoordinators, - Reference ccr, - int desiredCount, - std::set* excluded) { + static Future isAcceptable(AutoQuorumChange* self, + Transaction* tr, + std::vector oldCoordinators, + Reference ccr, + int desiredCount, + std::set* excluded) { ClusterConnectionString cs = ccr->getConnectionString(); if (oldCoordinators.size() != cs.hostnames.size() + cs.coords.size()) { - return false; + co_return false; } // Are there enough coordinators for the redundancy level? if (oldCoordinators.size() < desiredCount) - return false; + co_return false; if (oldCoordinators.size() % 2 != 1) - return false; + co_return false; // Check exclusions for (auto& c : oldCoordinators) { if (addressExcluded(*excluded, c)) - return false; + co_return false; } // Check locality @@ -1221,7 +1229,7 @@ struct AutoQuorumChange final : IQuorumChange { std::sort(oldCoordinators.begin(), oldCoordinators.end()); for (int i = 1; i < oldCoordinators.size(); i++) if (oldCoordinators[i - 1].ip == oldCoordinators[i].ip) - return false; // Multiple coordinators share an IP + co_return false; // Multiple coordinators share an IP // Check availability ClientCoordinators coord(ccr); @@ -1240,39 +1248,39 @@ struct AutoQuorumChange final : IQuorumChange { } } Optional>> results = - wait(timeout(getAll(leaderServers), CLIENT_KNOBS->IS_ACCEPTABLE_DELAY)); + co_await timeout(getAll(leaderServers), CLIENT_KNOBS->IS_ACCEPTABLE_DELAY); if (!results.present()) { - return false; + co_return false; } // Not all responded for (auto& r : results.get()) { if (!r.present()) { - return false; // Coordinator doesn't know about this database? + co_return false; // Coordinator doesn't know about this database? } } - return true; // The status quo seems fine + co_return true; // The status quo seems fine } - ACTOR static Future> getDesired(Reference self, - Transaction* tr, - std::vector oldCoordinators, - Reference ccr, - CoordinatorsResult* err) { - state int desiredCount = self->desired; + static Future> getDesired(Reference self, + Transaction* tr, + std::vector oldCoordinators, + Reference ccr, + CoordinatorsResult* err) { + int desiredCount = self->desired; if (desiredCount == -1) { - int redundancy = wait(getRedundancy(self.getPtr(), tr)); + int redundancy = co_await getRedundancy(self.getPtr(), tr); desiredCount = redundancy * 2 - 1; } - std::vector excl = wait(getAllExcludedServers(tr)); - state std::set excluded(excl.begin(), excl.end()); + std::vector excl = co_await getAllExcludedServers(tr); + std::set excluded(excl.begin(), excl.end()); - std::vector _workers = wait(getWorkers(tr)); - state std::vector workers = _workers; + std::vector _workers = co_await getWorkers(tr); + std::vector workers = _workers; std::map addr_locality; - for (auto w : workers) + for (const auto& w : workers) addr_locality[w.address] = w.locality; // since we don't have the locality data for oldCoordinators: @@ -1282,7 +1290,7 @@ struct AutoQuorumChange final : IQuorumChange { std::set>> checkDuplicates; for (auto addr : oldCoordinators) { auto findResult = addr_locality.find(addr); - if (findResult == addr_locality.end() || checkDuplicates.count(findResult->second.zoneId())) { + if (findResult == addr_locality.end() || checkDuplicates.contains(findResult->second.zoneId())) { checkAcceptable = false; break; } @@ -1290,10 +1298,10 @@ struct AutoQuorumChange final : IQuorumChange { } if (checkAcceptable) { - bool ok = wait(isAcceptable(self.getPtr(), tr, oldCoordinators, ccr, desiredCount, &excluded)); + bool ok = co_await isAcceptable(self.getPtr(), tr, oldCoordinators, ccr, desiredCount, &excluded); if (ok) { *err = CoordinatorsResult::SAME_NETWORK_ADDRESSES; - return oldCoordinators; + co_return oldCoordinators; } } @@ -1308,12 +1316,12 @@ struct AutoQuorumChange final : IQuorumChange { .detail("DesiredCoordinators", desiredCount) .detail("CurrentCoordinators", oldCoordinators.size()); *err = CoordinatorsResult::NOT_ENOUGH_MACHINES; - return std::vector(); + co_return std::vector(); } chosen.resize((chosen.size() - 1) | 1); } - return chosen; + co_return chosen; } // Select a desired set of workers such that @@ -1408,15 +1416,16 @@ struct AutoQuorumChange final : IQuorumChange { } }; Reference autoQuorumChange(int desired) { - return Reference(new AutoQuorumChange(desired)); + return makeReference(desired); } -ACTOR Future excludeServers(Transaction* tr, std::vector servers, bool failed) { +Future excludeServers(Transaction* tr, std::vector servers, bool failed) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - std::vector excl = wait(failed ? getExcludedFailedServerList(tr) : getExcludedServerList(tr)); + std::vector excl = + co_await (failed ? getExcludedFailedServerList(tr) : getExcludedServerList(tr)); std::set exclusions(excl.begin(), excl.end()); bool containNewExclusion = false; for (auto& s : servers) { @@ -1441,13 +1450,13 @@ ACTOR Future excludeServers(Transaction* tr, std::vector .detail("Servers", describe(servers)) .detail("ExcludeFailed", failed) .detail("ExclusionUpdated", containNewExclusion); - return Void(); } -ACTOR Future excludeServers(Database cx, std::vector servers, bool failed) { +Future excludeServers(Database cx, std::vector servers, bool failed) { if (cx->apiVersionAtLeast(700)) { - state ReadYourWritesTransaction ryw(cx); - loop { + ReadYourWritesTransaction ryw(cx); + while (true) { + Error err; try { ryw.setOption(FDBTransactionOptions::RAW_ACCESS); ryw.setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); @@ -1463,35 +1472,38 @@ ACTOR Future excludeServers(Database cx, std::vector ser TraceEvent("ExcludeServersSpecialKeySpaceCommit") .detail("Servers", describe(servers)) .detail("ExcludeFailed", failed); - wait(ryw.commit()); - return Void(); + co_await ryw.commit(); + co_return; } catch (Error& e) { - TraceEvent("ExcludeServersError").errorUnsuppressed(e); - wait(ryw.onError(e)); + err = e; } + TraceEvent("ExcludeServersError").errorUnsuppressed(err); + co_await ryw.onError(err); } } else { - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { - wait(excludeServers(&tr, servers, failed)); - wait(tr.commit()); - return Void(); + co_await excludeServers(&tr, servers, failed); + co_await tr.commit(); + co_return; } catch (Error& e) { - TraceEvent("ExcludeServersError").errorUnsuppressed(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("ExcludeServersError").errorUnsuppressed(err); + co_await tr.onError(err); } } } // excludes localities by setting the keys in api version below 7.0 -ACTOR Future excludeLocalities(Transaction* tr, std::unordered_set localities, bool failed) { +Future excludeLocalities(Transaction* tr, std::unordered_set localities, bool failed) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - std::vector excl = wait(failed ? getExcludedFailedLocalityList(tr) : getExcludedLocalityList(tr)); + std::vector excl = co_await (failed ? getExcludedFailedLocalityList(tr) : getExcludedLocalityList(tr)); std::set exclusion(excl.begin(), excl.end()); bool containNewExclusion = false; for (const auto& l : localities) { @@ -1515,15 +1527,15 @@ ACTOR Future excludeLocalities(Transaction* tr, std::unordered_set excludeLocalities(Database cx, std::unordered_set localities, bool failed) { +Future excludeLocalities(Database cx, std::unordered_set localities, bool failed) { if (cx->apiVersionAtLeast(700)) { - state ReadYourWritesTransaction ryw(cx); - loop { + ReadYourWritesTransaction ryw(cx); + while (true) { + Error err; try { ryw.setOption(FDBTransactionOptions::RAW_ACCESS); ryw.setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); @@ -1540,33 +1552,37 @@ ACTOR Future excludeLocalities(Database cx, std::unordered_set includeServers(Database cx, std::vector servers, bool failed) { - state std::string versionKey = deterministicRandom()->randomUniqueID().toString(); +Future includeServers(Database cx, std::vector servers, bool failed) { + std::string versionKey = deterministicRandom()->randomUniqueID().toString(); if (cx->apiVersionAtLeast(700)) { - state ReadYourWritesTransaction ryw(cx); - loop { + ReadYourWritesTransaction ryw(cx); + while (true) { + Error err; try { ryw.setOption(FDBTransactionOptions::RAW_ACCESS); ryw.setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); @@ -1596,24 +1612,26 @@ ACTOR Future includeServers(Database cx, std::vector ser } TraceEvent("IncludeServersCommit").detail("Servers", describe(servers)).detail("Failed", failed); - wait(ryw.commit()); - return Void(); + co_await ryw.commit(); + co_return; } catch (Error& e) { - TraceEvent("IncludeServersError").errorUnsuppressed(e); - wait(ryw.onError(e)); + err = e; } + TraceEvent("IncludeServersError").errorUnsuppressed(err); + co_await ryw.onError(err); } } else { - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - // includeServers might be used in an emergency transaction, so make sure it is retry-self-conflicting - // and CAUSAL_WRITE_RISKY + // includeServers might be used in an emergency transaction, so make sure it is + // retry-self-conflicting and CAUSAL_WRITE_RISKY tr.setOption(FDBTransactionOptions::CAUSAL_WRITE_RISKY); if (failed) { tr.addReadConflictRange(singleKeyRange(failedServersVersionKey)); @@ -1653,23 +1671,25 @@ ACTOR Future includeServers(Database cx, std::vector ser TraceEvent("IncludeServersCommit").detail("Servers", describe(servers)).detail("Failed", failed); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - TraceEvent("IncludeServersError").errorUnsuppressed(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("IncludeServersError").errorUnsuppressed(err); + co_await tr.onError(err); } } } // Remove the given localities from the exclusion list. // include localities by clearing the keys. -ACTOR Future includeLocalities(Database cx, std::vector localities, bool failed, bool includeAll) { - state std::string versionKey = deterministicRandom()->randomUniqueID().toString(); +Future includeLocalities(Database cx, std::vector localities, bool failed, bool includeAll) { + std::string versionKey = deterministicRandom()->randomUniqueID().toString(); if (cx->apiVersionAtLeast(700)) { - state ReadYourWritesTransaction ryw(cx); - loop { + ReadYourWritesTransaction ryw(cx); + while (true) { + Error err; try { ryw.setOption(FDBTransactionOptions::RAW_ACCESS); ryw.setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES); @@ -1692,16 +1712,18 @@ ACTOR Future includeLocalities(Database cx, std::vector local .detail("Failed", failed) .detail("IncludeAll", includeAll); - wait(ryw.commit()); - return Void(); + co_await ryw.commit(); + co_return; } catch (Error& e) { - TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e); - wait(ryw.onError(e)); + err = e; } + TraceEvent("IncludeLocalitiesError").errorUnsuppressed(err); + co_await ryw.onError(err); } } else { - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); @@ -1740,27 +1762,29 @@ ACTOR Future includeLocalities(Database cx, std::vector local .detail("Failed", failed) .detail("IncludeAll", includeAll); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("IncludeLocalitiesError").errorUnsuppressed(err); + co_await tr.onError(err); } } } -ACTOR Future setClass(Database cx, AddressExclusion server, ProcessClass processClass) { - state Transaction tr(cx); +Future setClass(Database cx, AddressExclusion server, ProcessClass processClass) { + Transaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - std::vector workers = wait(getWorkers(&tr)); + std::vector workers = co_await getWorkers(&tr); bool foundChange = false; for (int i = 0; i < workers.size(); i++) { @@ -1777,16 +1801,17 @@ ACTOR Future setClass(Database cx, AddressExclusion server, ProcessClass p if (foundChange) tr.set(processClassChangeKey, deterministicRandom()->randomUniqueID().toString()); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future> getExcludedServerList(Transaction* tr) { - state RangeResult r = wait(tr->getRange(excludedServersKeys, CLIENT_KNOBS->TOO_MANY)); +Future> getExcludedServerList(Transaction* tr) { + RangeResult r = co_await tr->getRange(excludedServersKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector exclusions; @@ -1796,11 +1821,11 @@ ACTOR Future> getExcludedServerList(Transaction* t exclusions.push_back(a); } uniquify(exclusions); - return exclusions; + co_return exclusions; } -ACTOR Future> getExcludedFailedServerList(Transaction* tr) { - state RangeResult r = wait(tr->getRange(failedServersKeys, CLIENT_KNOBS->TOO_MANY)); +Future> getExcludedFailedServerList(Transaction* tr) { + RangeResult r = co_await tr->getRange(failedServersKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector exclusions; @@ -1810,20 +1835,20 @@ ACTOR Future> getExcludedFailedServerList(Transact exclusions.push_back(a); } uniquify(exclusions); - return exclusions; + co_return exclusions; } -ACTOR Future> getAllExcludedServers(Transaction* tr) { - state std::vector exclusions; +Future> getAllExcludedServers(Transaction* tr) { + std::vector exclusions; // Request all exclusion based information concurrently. - state Future> fExcludedServers = getExcludedServerList(tr); - state Future> fExcludedFailed = getExcludedFailedServerList(tr); - state Future> fExcludedLocalities = getAllExcludedLocalities(tr); - state Future> fWorkers = getWorkers(tr); + Future> fExcludedServers = getExcludedServerList(tr); + Future> fExcludedFailed = getExcludedFailedServerList(tr); + Future> fExcludedLocalities = getAllExcludedLocalities(tr); + Future> fWorkers = getWorkers(tr); // Wait until all data is gathered, we are not waiting here for the workers future to return // instead we wait for the worker future only if we need the data. - wait(success(fExcludedServers) && success(fExcludedFailed) && success(fExcludedLocalities)); + co_await (success(fExcludedServers) && success(fExcludedFailed) && success(fExcludedLocalities)); // Update the exclusions vector with all excluded servers. auto excludedServers = fExcludedServers.get(); exclusions.insert(exclusions.end(), excludedServers.begin(), excludedServers.end()); @@ -1832,13 +1857,13 @@ ACTOR Future> getAllExcludedServers(Transaction* t // We have to return all servers that are excluded, this includes servers that are excluded // based on the locality. Otherwise those excluded servers might be used, even if they shouldn't. - state std::vector excludedLocalities = fExcludedLocalities.get(); + std::vector excludedLocalities = fExcludedLocalities.get(); // Only if at least one locality was found we have to perform this check. if (!excludedLocalities.empty()) { // First we have to fetch all workers to match the localities of each worker against the excluded localities. - wait(success(fWorkers)); - state std::vector workers = fWorkers.get(); + co_await fWorkers; + std::vector workers = fWorkers.get(); for (const auto& locality : excludedLocalities) { std::set localityAddresses = getAddressesByLocality(workers, locality); @@ -1850,26 +1875,28 @@ ACTOR Future> getAllExcludedServers(Transaction* t } uniquify(exclusions); - return exclusions; + co_return exclusions; } -ACTOR Future> getAllExcludedServers(Database cx) { - state Transaction tr(cx); - loop { +Future> getAllExcludedServers(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); // necessary? tr.setOption(FDBTransactionOptions::LOCK_AWARE); - std::vector exclusions = wait(getAllExcludedServers(&tr)); - return exclusions; + std::vector exclusions = co_await getAllExcludedServers(&tr); + co_return exclusions; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future> getExcludedLocalityList(Transaction* tr) { - state RangeResult r = wait(tr->getRange(excludedLocalityKeys, CLIENT_KNOBS->TOO_MANY)); +Future> getExcludedLocalityList(Transaction* tr) { + RangeResult r = co_await tr->getRange(excludedLocalityKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector excludedLocalities; @@ -1878,11 +1905,11 @@ ACTOR Future> getExcludedLocalityList(Transaction* tr) excludedLocalities.push_back(a); } uniquify(excludedLocalities); - return excludedLocalities; + co_return excludedLocalities; } -ACTOR Future> getExcludedFailedLocalityList(Transaction* tr) { - state RangeResult r = wait(tr->getRange(failedLocalityKeys, CLIENT_KNOBS->TOO_MANY)); +Future> getExcludedFailedLocalityList(Transaction* tr) { + RangeResult r = co_await tr->getRange(failedLocalityKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!r.more && r.size() < CLIENT_KNOBS->TOO_MANY); std::vector excludedLocalities; @@ -1891,16 +1918,16 @@ ACTOR Future> getExcludedFailedLocalityList(Transaction excludedLocalities.push_back(a); } uniquify(excludedLocalities); - return excludedLocalities; + co_return excludedLocalities; } -ACTOR Future> getAllExcludedLocalities(Transaction* tr) { - state std::vector exclusions; - state Future> fExcludedLocalities = getExcludedLocalityList(tr); - state Future> fFailedLocalities = getExcludedFailedLocalityList(tr); +Future> getAllExcludedLocalities(Transaction* tr) { + std::vector exclusions; + Future> fExcludedLocalities = getExcludedLocalityList(tr); + Future> fFailedLocalities = getExcludedFailedLocalityList(tr); // Wait until all data is gathered. - wait(success(fExcludedLocalities) && success(fFailedLocalities)); + co_await (success(fExcludedLocalities) && success(fFailedLocalities)); auto excludedLocalities = fExcludedLocalities.get(); exclusions.insert(exclusions.end(), excludedLocalities.begin(), excludedLocalities.end()); @@ -1908,22 +1935,24 @@ ACTOR Future> getAllExcludedLocalities(Transaction* tr) exclusions.insert(exclusions.end(), failedLocalities.begin(), failedLocalities.end()); uniquify(exclusions); - return exclusions; + co_return exclusions; } // Get the list of excluded localities by reading the keys. -ACTOR Future> getAllExcludedLocalities(Database cx) { - state Transaction tr(cx); - loop { +Future> getAllExcludedLocalities(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - std::vector exclusions = wait(getAllExcludedLocalities(&tr)); - return exclusions; + std::vector exclusions = co_await getAllExcludedLocalities(&tr); + co_return exclusions; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -1993,13 +2022,14 @@ std::set getAddressesByLocality(const std::vector return locality_addresses; } -ACTOR Future printHealthyZone(Database cx) { - state Transaction tr(cx); - loop { +Future printHealthyZone(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - Optional val = wait(tr.get(healthyZoneKey)); + Optional val = co_await tr.get(healthyZoneKey); if (val.present() && decodeHealthyZoneValue(val.get()).first == ignoreSSFailuresZoneString) { printf("Data distribution has been disabled for all storage server failures in this cluster and thus " "maintenance mode is not active.\n"); @@ -2011,79 +2041,85 @@ ACTOR Future printHealthyZone(Database cx) { healthyZone.first.toString(), (healthyZone.second - tr.getReadVersion().get()) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND); } - return Void(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future clearHealthyZone(Database cx, bool printWarning, bool clearSSFailureZoneString) { - state Transaction tr(cx); +Future clearHealthyZone(Database cx, bool printWarning, bool clearSSFailureZoneString) { + Transaction tr(cx); TraceEvent("ClearHealthyZone").detail("ClearSSFailureZoneString", clearSSFailureZoneString); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional val = wait(tr.get(healthyZoneKey)); + Optional val = co_await tr.get(healthyZoneKey); if (!clearSSFailureZoneString && val.present() && decodeHealthyZoneValue(val.get()).first == ignoreSSFailuresZoneString) { if (printWarning) { printf("ERROR: Maintenance mode cannot be used while data distribution is disabled for storage " "server failures. Use 'datadistribution on' to reenable data distribution.\n"); } - return false; + co_return false; } tr.clear(healthyZoneKey); - wait(tr.commit()); - return true; + co_await tr.commit(); + co_return true; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future setHealthyZone(Database cx, StringRef zoneId, double seconds, bool printWarning) { - state Transaction tr(cx); +Future setHealthyZone(Database cx, StringRef zoneId, double seconds, bool printWarning) { + Transaction tr(cx); TraceEvent("SetHealthyZone").detail("Zone", zoneId).detail("DurationSeconds", seconds); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional val = wait(tr.get(healthyZoneKey)); + Optional val = co_await tr.get(healthyZoneKey); if (val.present() && decodeHealthyZoneValue(val.get()).first == ignoreSSFailuresZoneString) { if (printWarning) { printf("ERROR: Maintenance mode cannot be used while data distribution is disabled for storage " "server failures. Use 'datadistribution on' to reenable data distribution.\n"); } - return false; + co_return false; } - Version readVersion = wait(tr.getReadVersion()); + Version readVersion = co_await tr.getReadVersion(); tr.set(healthyZoneKey, healthyZoneValue(zoneId, readVersion + (seconds * CLIENT_KNOBS->CORE_VERSIONSPERSECOND))); - wait(tr.commit()); - return true; + co_await tr.commit(); + co_return true; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future setDDMode(Database cx, int mode) { - state Transaction tr(cx); - state int oldMode = -1; - state BinaryWriter wr(Unversioned()); +Future setDDMode(Database cx, int mode) { + Transaction tr(cx); + int oldMode = -1; + BinaryWriter wr(Unversioned()); wr << mode; - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional old = wait(tr.get(dataDistributionModeKey)); + Optional old = co_await tr.get(dataDistributionModeKey); if (oldMode < 0) { oldMode = 1; if (old.present()) { @@ -2103,7 +2139,7 @@ ACTOR Future setDDMode(Database cx, int mode) { // set DDMode to 1 will enable all disabled parts, for instance the SS failure monitors. // set DDMode to 2 is a security mode which disables data moves but allows auditStorage part // DDMode=2 is set when shard location metadata inconsistency is detected - Optional currentHealthyZoneValue = wait(tr.get(healthyZoneKey)); + Optional currentHealthyZoneValue = co_await tr.get(healthyZoneKey); if (currentHealthyZoneValue.present() && decodeHealthyZoneValue(currentHealthyZoneValue.get()).first == ignoreSSFailuresZoneString) { // only clear the key if it is currently being used to disable all SS failure data movement @@ -2111,22 +2147,23 @@ ACTOR Future setDDMode(Database cx, int mode) { } tr.clear(rebalanceDDIgnoreKey); } - wait(tr.commit()); - return oldMode; + co_await tr.commit(); + co_return oldMode; } catch (Error& e) { - TraceEvent("SetDDModeRetrying").error(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("SetDDModeRetrying").error(err); + co_await tr.onError(err); } } -ACTOR Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr, - std::set* exclusions, - std::set* inProgressExclusion) { +Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr, + std::set* exclusions, + std::set* inProgressExclusion) { // TODO : replace using ExclusionInProgressRangeImpl in special key space - ASSERT(inProgressExclusion->size() == 0); // Make sure every time it is cleared beforehand - if (!exclusions->size()) - return true; + ASSERT(inProgressExclusion->empty()); // Make sure every time it is cleared beforehand + if (exclusions->empty()) + co_return true; tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); // necessary? @@ -2136,10 +2173,10 @@ ACTOR Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr // recovery // Check that there aren't any storage servers with addresses violating the exclusions - RangeResult serverList = wait(tr->getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult serverList = co_await tr->getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!serverList.more && serverList.size() < CLIENT_KNOBS->TOO_MANY); - state bool ok = true; + bool ok = true; for (auto& s : serverList) { auto addresses = decodeServerListValue(s.value).getKeyValues.getEndpoint().addresses; if (addressExcluded(*exclusions, addresses.address)) { @@ -2153,82 +2190,85 @@ ACTOR Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr } if (ok) { - Optional> value = wait(tr->get(logsKey)); + Optional> value = co_await tr->get(logsKey); ASSERT(value.present()); auto logs = decodeLogsValue(value.get()); - for (auto const& log : logs.first) { - if (log.second == NetworkAddress() || addressExcluded(*exclusions, log.second)) { + for (const auto& [_logId, logAddress] : logs.first) { + if (logAddress == NetworkAddress() || addressExcluded(*exclusions, logAddress)) { ok = false; - inProgressExclusion->insert(log.second); + inProgressExclusion->insert(logAddress); } } - for (auto const& log : logs.second) { - if (log.second == NetworkAddress() || addressExcluded(*exclusions, log.second)) { + for (const auto& [_logId, logAddress] : logs.second) { + if (logAddress == NetworkAddress() || addressExcluded(*exclusions, logAddress)) { ok = false; - inProgressExclusion->insert(log.second); + inProgressExclusion->insert(logAddress); } } } - return ok; + co_return ok; } -ACTOR Future> checkForExcludingServers(Database cx, - std::vector excl, - bool waitForAllExcluded) { - state std::set exclusions(excl.begin(), excl.end()); - state std::set inProgressExclusion; +Future> checkForExcludingServers(Database cx, + std::vector excl, + bool waitForAllExcluded) { + std::set exclusions(excl.begin(), excl.end()); + std::set inProgressExclusion; - loop { - state ReadYourWritesTransaction tr(cx); + while (true) { + ReadYourWritesTransaction tr(cx); inProgressExclusion.clear(); + Error err; try { - bool ok = wait(checkForExcludingServersTxActor(&tr, &exclusions, &inProgressExclusion)); + bool ok = co_await checkForExcludingServersTxActor(&tr, &exclusions, &inProgressExclusion); if (ok) - return inProgressExclusion; + co_return inProgressExclusion; if (!waitForAllExcluded) break; - wait(delayJittered(1.0)); // SOMEDAY: watches! + co_await delayJittered(1.0); // SOMEDAY: watches! + continue; } catch (Error& e) { - TraceEvent("CheckForExcludingServersError").error(e); - wait(tr.onError(e)); + err = e; } + TraceEvent("CheckForExcludingServersError").error(err); + co_await tr.onError(err); } - return inProgressExclusion; + co_return inProgressExclusion; } -ACTOR Future mgmtSnapCreate(Database cx, Standalone snapCmd, UID snapUID) { +Future mgmtSnapCreate(Database cx, Standalone snapCmd, UID snapUID) { try { - wait(snapCreate(cx, snapCmd, snapUID)); + co_await snapCreate(cx, snapCmd, snapUID); TraceEvent("SnapCreateSucceeded").detail("snapUID", snapUID); - return Void(); } catch (Error& e) { TraceEvent(SevWarn, "SnapCreateFailed").error(e).detail("snapUID", snapUID); throw; } } -ACTOR Future waitForFullReplication(Database cx) { - state ReadYourWritesTransaction tr(cx); - loop { +Future waitForFullReplication(Database cx) { + ReadYourWritesTransaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult confResults = wait(tr.getRange(configKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult confResults = co_await tr.getRange(configKeys, CLIENT_KNOBS->TOO_MANY); ASSERT(!confResults.more && confResults.size() < CLIENT_KNOBS->TOO_MANY); - state DatabaseConfiguration config; + DatabaseConfiguration config; config.fromKeyValues((VectorRef)confResults); - state std::vector>> replicasFutures; + std::vector>> replicasFutures; for (auto& region : config.regions) { replicasFutures.push_back(tr.get(datacenterReplicasKeyFor(region.dcId))); } - wait(waitForAll(replicasFutures)); + co_await waitForAll(replicasFutures); - state std::vector> watchFutures; + std::vector> watchFutures; for (int i = 0; i < config.regions.size(); i++) { if (!replicasFutures[i].get().present() || decodeDatacenterReplicasValue(replicasFutures[i].get().get()) < config.storageTeamSize) { @@ -2236,42 +2276,46 @@ ACTOR Future waitForFullReplication(Database cx) { } } - if (!watchFutures.size() || (config.usableRegions == 1 && watchFutures.size() < config.regions.size())) { - return Void(); + if (watchFutures.empty() || (config.usableRegions == 1 && watchFutures.size() < config.regions.size())) { + co_return; } - wait(tr.commit()); - wait(waitForAny(watchFutures)); + co_await tr.commit(); + co_await waitForAny(watchFutures); tr.reset(); + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future timeKeeperSetDisable(Database cx) { - loop { - state Transaction tr(cx); +Future timeKeeperSetDisable(Database cx) { + while (true) { + Transaction tr(cx); + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.set(timeKeeperDisableKey, StringRef()); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future lockDatabase(Transaction* tr, UID id) { +Future lockDatabase(Transaction* tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (val.present()) { if (BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) == id) { - return Void(); + co_return; } else { //TraceEvent("DBA_LockLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())); throw database_locked(); @@ -2282,17 +2326,16 @@ ACTOR Future lockDatabase(Transaction* tr, UID id) { BinaryWriter::toValue(id, Unversioned()).withPrefix("0123456789"_sr).withSuffix("\x00\x00\x00\x00"_sr), MutationRef::SetVersionstampedValue); tr->addWriteConflictRange(normalKeys); - return Void(); } -ACTOR Future lockDatabase(Reference tr, UID id) { +Future lockDatabase(Reference tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (val.present()) { if (BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) == id) { - return Void(); + co_return; } else { //TraceEvent("DBA_LockLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())); throw database_locked(); @@ -2303,34 +2346,35 @@ ACTOR Future lockDatabase(Reference tr, UID id) BinaryWriter::toValue(id, Unversioned()).withPrefix("0123456789"_sr).withSuffix("\x00\x00\x00\x00"_sr), MutationRef::SetVersionstampedValue); tr->addWriteConflictRange(normalKeys); - return Void(); } -ACTOR Future lockDatabase(Database cx, UID id) { - state Transaction tr(cx); +Future lockDatabase(Database cx, UID id) { + Transaction tr(cx); UID debugID = deterministicRandom()->randomUniqueID(); TraceEvent("LockDatabaseTransaction", debugID).log(); tr.debugTransaction(debugID); - loop { + while (true) { + Error err; try { - wait(lockDatabase(&tr, id)); - wait(tr.commit()); - return Void(); + co_await lockDatabase(&tr, id); + co_await tr.commit(); + co_return; } catch (Error& e) { - if (e.code() == error_code_database_locked) - throw e; - wait(tr.onError(e)); + err = e; } + if (err.code() == error_code_database_locked) + throw err; + co_await tr.onError(err); } } -ACTOR Future unlockDatabase(Transaction* tr, UID id) { +Future unlockDatabase(Transaction* tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (!val.present()) - return Void(); + co_return; if (val.present() && BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) != id) { //TraceEvent("DBA_UnlockLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())); @@ -2338,16 +2382,15 @@ ACTOR Future unlockDatabase(Transaction* tr, UID id) { } tr->clear(singleKeyRange(databaseLockedKey)); - return Void(); } -ACTOR Future unlockDatabase(Reference tr, UID id) { +Future unlockDatabase(Reference tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (!val.present()) - return Void(); + co_return; if (val.present() && BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) != id) { //TraceEvent("DBA_UnlockLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())); @@ -2355,103 +2398,102 @@ ACTOR Future unlockDatabase(Reference tr, UID i } tr->clear(singleKeyRange(databaseLockedKey)); - return Void(); } -ACTOR Future unlockDatabase(Database cx, UID id) { - state Transaction tr(cx); - loop { +Future unlockDatabase(Database cx, UID id) { + Transaction tr(cx); + while (true) { + Error err; try { - wait(unlockDatabase(&tr, id)); - wait(tr.commit()); - return Void(); + co_await unlockDatabase(&tr, id); + co_await tr.commit(); + co_return; } catch (Error& e) { - if (e.code() == error_code_database_locked) - throw e; - wait(tr.onError(e)); + err = e; } + if (err.code() == error_code_database_locked) + throw err; + co_await tr.onError(err); } } -ACTOR Future checkDatabaseLock(Transaction* tr, UID id) { +Future checkDatabaseLock(Transaction* tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (val.present() && BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) != id) { //TraceEvent("DBA_CheckLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())).backtrace(); throw database_locked(); } - - return Void(); } -ACTOR Future checkDatabaseLock(Reference tr, UID id) { +Future checkDatabaseLock(Reference tr, UID id) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional val = wait(tr->get(databaseLockedKey)); + Optional val = co_await tr->get(databaseLockedKey); if (val.present() && BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) != id) { //TraceEvent("DBA_CheckLocked").detail("Expecting", id).detail("Lock", BinaryReader::fromStringRef(val.get().substr(10), Unversioned())).backtrace(); throw database_locked(); } - - return Void(); } -ACTOR Future advanceVersion(Database cx, Version v) { - state Transaction tr(cx); - loop { +Future advanceVersion(Database cx, Version v) { + Transaction tr(cx); + while (true) { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Error err; try { - Version rv = wait(tr.getReadVersion()); + Version rv = co_await tr.getReadVersion(); if (rv <= v) { tr.set(minRequiredCommitVersionKey, BinaryWriter::toValue(v + 1, Unversioned())); - wait(tr.commit()); + co_await tr.commit(); + continue; } else { fmt::print("Current read version is {}\n", rv); - return Void(); + co_return; } } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future forceRecovery(Reference clusterFile, Key dcId) { - state Reference>> clusterInterface(new AsyncVar>); - state Future leaderMon = monitorLeader(clusterFile, clusterInterface); +Future forceRecovery(Reference clusterFile, Key dcId) { + Reference>> clusterInterface(new AsyncVar>); + Future leaderMon = monitorLeader(clusterFile, clusterInterface); - loop { - choose { - when(wait(clusterInterface->get().present() - ? brokenPromiseToNever( - clusterInterface->get().get().forceRecovery.getReply(ForceRecoveryRequest(dcId))) - : Never())) { - return Void(); - } - when(wait(clusterInterface->onChange())) {} + while (true) { + Future forceRecoveryFuture = Never(); + if (clusterInterface->get().present()) { + forceRecoveryFuture = + brokenPromiseToNever(clusterInterface->get().get().forceRecovery.getReply(ForceRecoveryRequest(dcId))); + } + if (auto const res = co_await race(forceRecoveryFuture, clusterInterface->onChange()); res.index() == 0) { + co_return; } } } -ACTOR Future auditStorage(Reference clusterFile, - KeyRange range, - AuditType type, - KeyValueStoreType engineType, - double timeoutSeconds) { - state Reference>> clusterInterface(new AsyncVar>); - state Future leaderMon = monitorLeader(clusterFile, clusterInterface); +Future auditStorage(Reference clusterFile, + KeyRange range, + AuditType type, + KeyValueStoreType engineType, + double timeoutSeconds) { + Reference>> clusterInterface(new AsyncVar>); + Future leaderMon = monitorLeader(clusterFile, clusterInterface); TraceEvent(SevVerbose, "ManagementAPIAuditStorageTrigger").detail("AuditType", type).detail("Range", range); - state UID auditId; + UID auditId; try { while (!clusterInterface->get().present()) { - wait(clusterInterface->onChange()); + co_await clusterInterface->onChange(); } TraceEvent(SevVerbose, "ManagementAPIAuditStorageBegin").detail("AuditType", type).detail("Range", range); TriggerAuditRequest req(type, range, engineType); - UID auditId_ = wait(timeoutError(clusterInterface->get().get().triggerAudit.getReply(req), timeoutSeconds)); + UID auditId_ = co_await timeoutError(clusterInterface->get().get().triggerAudit.getReply(req), timeoutSeconds); auditId = auditId_; TraceEvent(SevVerbose, "ManagementAPIAuditStorageEnd") .detail("AuditType", type) @@ -2466,27 +2508,27 @@ ACTOR Future auditStorage(Reference clusterFile, throw e; } - return auditId; + co_return auditId; } -ACTOR Future cancelAuditStorage(Reference clusterFile, - AuditType type, - UID auditId, - double timeoutSeconds) { - state Reference>> clusterInterface(new AsyncVar>); - state Future leaderMon = monitorLeader(clusterFile, clusterInterface); +Future cancelAuditStorage(Reference clusterFile, + AuditType type, + UID auditId, + double timeoutSeconds) { + Reference>> clusterInterface(new AsyncVar>); + Future leaderMon = monitorLeader(clusterFile, clusterInterface); TraceEvent(SevVerbose, "ManagementAPICancelAuditStorageTrigger") .detail("AuditType", type) .detail("AuditId", auditId); try { while (!clusterInterface->get().present()) { - wait(clusterInterface->onChange()); + co_await clusterInterface->onChange(); } TraceEvent(SevVerbose, "ManagementAPICancelAuditStorageBegin") .detail("AuditType", type) .detail("AuditId", auditId); TriggerAuditRequest req(type, auditId); - UID auditId_ = wait(timeoutError(clusterInterface->get().get().triggerAudit.getReply(req), timeoutSeconds)); + UID auditId_ = co_await timeoutError(clusterInterface->get().get().triggerAudit.getReply(req), timeoutSeconds); ASSERT(auditId_ == auditId); TraceEvent(SevVerbose, "ManagementAPICancelAuditStorageEnd") .detail("AuditType", type) @@ -2499,20 +2541,21 @@ ACTOR Future cancelAuditStorage(Reference cluster throw e; } - return auditId; + co_return auditId; } -ACTOR Future setBulkLoadMode(Database cx, int mode) { - state Transaction tr(cx); - state BinaryWriter wr(Unversioned()); +Future setBulkLoadMode(Database cx, int mode) { + Transaction tr(cx); + BinaryWriter wr(Unversioned()); wr << mode; - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - state int oldMode = 0; - Optional oldModeValue = wait(tr.get(bulkLoadModeKey)); + int oldMode = 0; + Optional oldModeValue = co_await tr.get(bulkLoadModeKey); if (oldModeValue.present()) { BinaryReader rd(oldModeValue.get(), Unversioned()); rd >> oldMode; @@ -2525,59 +2568,61 @@ ACTOR Future setBulkLoadMode(Database cx, int mode) { wrLastWrite << deterministicRandom()->randomUniqueID(); // triger DD restarts tr.set(moveKeysLockWriteKey, wrLastWrite.toValue()); tr.set(bulkLoadModeKey, wr.toValue()); - wait(tr.commit()); + co_await tr.commit(); TraceEvent(bulkLoadVerboseEventSev(), "DDBulkLoadEngineModeKeyChanged") .detail("NewMode", mode) .detail("OldMode", oldMode); } - return oldMode; + co_return oldMode; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future getBulkLoadMode(Database cx) { - state Transaction tr(cx); - loop { +Future getBulkLoadMode(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - state int oldMode = 0; - Optional oldModeValue = wait(tr.get(bulkLoadModeKey)); + int oldMode = 0; + Optional oldModeValue = co_await tr.get(bulkLoadModeKey); if (oldModeValue.present()) { BinaryReader rd(oldModeValue.get(), Unversioned()); rd >> oldMode; } - return oldMode; + co_return oldMode; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future setBulkLoadSubmissionTransaction(Transaction* tr, BulkLoadTaskState bulkLoadTask) { +Future setBulkLoadSubmissionTransaction(Transaction* tr, BulkLoadTaskState bulkLoadTask) { ASSERT(normalKeys.contains(bulkLoadTask.getRange()) && (bulkLoadTask.phase == BulkLoadPhase::Submitted || (bulkLoadTask.phase == BulkLoadPhase::Complete && bulkLoadTask.hasEmptyData()))); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); bulkLoadTask.submitTime = now(); - wait(krmSetRange(tr, bulkLoadTaskPrefix, bulkLoadTask.getRange(), bulkLoadTaskStateValue(bulkLoadTask))); - return Void(); + co_await krmSetRange(tr, bulkLoadTaskPrefix, bulkLoadTask.getRange(), bulkLoadTaskStateValue(bulkLoadTask)); } // Get bulk load task metadata with range and taskId and phase selector // Throw error if the task is outdated or the task is not in any input phase at the tr read version // TODO: check jobId -ACTOR Future getBulkLoadTask(Transaction* tr, - KeyRange range, - UID taskId, - std::vector phases) { - state BulkLoadTaskState bulkLoadTaskState; +Future getBulkLoadTask(Transaction* tr, + KeyRange range, + UID taskId, + std::vector phases) { + BulkLoadTaskState bulkLoadTaskState; tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - RangeResult result = wait(krmGetRanges(tr, bulkLoadTaskPrefix, range)); + RangeResult result = co_await krmGetRanges(tr, bulkLoadTaskPrefix, range); if (result.size() > 2) { TraceEvent(SevWarn, "GetBulkLoadTaskError") .detail("Reason", "TooManyRanges") @@ -2628,7 +2673,7 @@ ACTOR Future getBulkLoadTask(Transaction* tr, .backtrace(); throw bulkload_task_outdated(); } - if (phases.size() > 0 && !bulkLoadTaskState.onAnyPhase(phases)) { + if (!phases.empty() && !bulkLoadTaskState.onAnyPhase(phases)) { TraceEvent(SevWarn, "GetBulkLoadTaskError") .detail("Reason", "PhaseMismatch") .detail("Range", printable(range)) @@ -2637,16 +2682,15 @@ ACTOR Future getBulkLoadTask(Transaction* tr, .backtrace(); throw bulkload_task_outdated(); } - return bulkLoadTaskState; + co_return bulkLoadTaskState; } -ACTOR Future setBulkLoadFinalizeTransaction(Transaction* tr, KeyRange range, UID taskId) { - state BulkLoadTaskState bulkLoadTaskState; +Future setBulkLoadFinalizeTransaction(Transaction* tr, KeyRange range, UID taskId) { + BulkLoadTaskState bulkLoadTaskState; tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - wait(store(bulkLoadTaskState, - getBulkLoadTask( - tr, range, taskId, { BulkLoadPhase::Complete, BulkLoadPhase::Acknowledged, BulkLoadPhase::Error }))); + bulkLoadTaskState = co_await getBulkLoadTask( + tr, range, taskId, { BulkLoadPhase::Complete, BulkLoadPhase::Acknowledged, BulkLoadPhase::Error }); if (bulkLoadTaskState.phase == BulkLoadPhase::Error) { TraceEvent(SevWarnAlways, "ManagementAPIAcknowledgeErrorTask") .detail("TaskId", taskId.toString()) @@ -2655,22 +2699,22 @@ ACTOR Future setBulkLoadFinalizeTransaction(Transaction* tr, KeyRange rang bulkLoadTaskState.phase = BulkLoadPhase::Acknowledged; ASSERT(range == bulkLoadTaskState.getRange() && taskId == bulkLoadTaskState.getTaskId()); ASSERT(normalKeys.contains(range)); - wait(krmSetRange(tr, bulkLoadTaskPrefix, bulkLoadTaskState.getRange(), bulkLoadTaskStateValue(bulkLoadTaskState))); - return Void(); + co_await krmSetRange( + tr, bulkLoadTaskPrefix, bulkLoadTaskState.getRange(), bulkLoadTaskStateValue(bulkLoadTaskState)); } // This is the only place to update job history map. So, we check the number of job history entries here is sufficient // to maintain that the number of jobs in the history is no more than BULKLOAD_JOB_HISTORY_COUNT_MAX. -ACTOR Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState jobState) { - state Key newJobKey = bulkLoadJobHistoryKeyFor(jobState.getJobId()); - state RangeResult jobHistoryResult; - state Optional oldestJobState; // Set to remove when the job history is full. - state Key beginKey = bulkLoadJobHistoryKeys.begin; - state Key endKey = bulkLoadJobHistoryKeys.end; - loop { +Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState jobState) { + Key newJobKey = bulkLoadJobHistoryKeyFor(jobState.getJobId()); + RangeResult jobHistoryResult; + Optional oldestJobState; // Set to remove when the job history is full. + Key beginKey = bulkLoadJobHistoryKeys.begin; + Key endKey = bulkLoadJobHistoryKeys.end; + while (true) { jobHistoryResult.clear(); - wait(store(jobHistoryResult, - tr->getRange(KeyRangeRef(beginKey, endKey), CLIENT_KNOBS->BULKLOAD_JOB_HISTORY_COUNT_MAX * 2))); + jobHistoryResult = + co_await tr->getRange(KeyRangeRef(beginKey, endKey), CLIENT_KNOBS->BULKLOAD_JOB_HISTORY_COUNT_MAX * 2); // Set limit twice the max count to check the number of jobs in the history is no more than the max // count. for (int i = 0; i < jobHistoryResult.size(); i++) { @@ -2690,7 +2734,7 @@ ACTOR Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState job tr->set(newJobKey, bulkLoadJobValue(jobState)); // BulkLoad job with the same jobId can run for multiple times, we only keep the latest one // in the history. - return Void(); + co_return; } if (jobHistoryResult.size() > CLIENT_KNOBS->BULKLOAD_JOB_HISTORY_COUNT_MAX) { TraceEvent(SevError, "DDBulkLoadJobHistoryCountExceed", jobState.getJobId()) @@ -2704,6 +2748,7 @@ ACTOR Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState job } if (jobHistoryResult.more) { beginKey = keyAfter(jobHistoryResult.back().key); + continue; } else { break; } @@ -2712,20 +2757,20 @@ ACTOR Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState job tr->clear(bulkLoadJobHistoryKeyFor(oldestJobState.get().getJobId())); } tr->set(newJobKey, bulkLoadJobValue(jobState)); - return Void(); } -ACTOR Future> getBulkLoadJobFromHistory(Database cx) { - state RangeResult jobHistoryResult; - state Key beginKey = bulkLoadJobHistoryKeys.begin; - state Key endKey = bulkLoadJobHistoryKeys.end; - state Transaction tr(cx); - state std::vector res; - loop { +AsyncResult> getBulkLoadJobFromHistory(Database cx) { + RangeResult jobHistoryResult; + Key beginKey = bulkLoadJobHistoryKeys.begin; + Key endKey = bulkLoadJobHistoryKeys.end; + Transaction tr(cx); + std::vector res; + while (true) { + Error err; try { jobHistoryResult.clear(); - wait(store(jobHistoryResult, - tr.getRange(KeyRangeRef(beginKey, endKey), CLIENT_KNOBS->BULKLOAD_JOB_HISTORY_COUNT_MAX))); + jobHistoryResult = + co_await tr.getRange(KeyRangeRef(beginKey, endKey), CLIENT_KNOBS->BULKLOAD_JOB_HISTORY_COUNT_MAX); for (int i = 0; i < jobHistoryResult.size(); i++) { BulkLoadJobState jobState = decodeBulkLoadJobState(jobHistoryResult[i].value); ASSERT_WE_THINK(jobState.isValid()); @@ -2737,39 +2782,45 @@ ACTOR Future> getBulkLoadJobFromHistory(Database c } if (jobHistoryResult.more) { beginKey = keyAfter(jobHistoryResult.back().key); + continue; } else { break; } } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return res; + co_return res; } -ACTOR Future clearBulkLoadJobHistory(Database cx, Optional jobId) { - state Transaction tr(cx); - loop { +Future clearBulkLoadJobHistory(Database cx, Optional jobId) { + Transaction tr(cx); + while (true) { + Error err; try { if (jobId.present()) { tr.clear(bulkLoadJobHistoryKeyFor(jobId.get())); } else { tr.clear(bulkLoadJobHistoryKeys); } - wait(tr.commit()); + co_await tr.commit(); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } -ACTOR Future> getSubmittedBulkLoadJob(Transaction* tr) { - state RangeResult rangeResult; +Future> getSubmittedBulkLoadJob(Transaction* tr) { + RangeResult rangeResult; // At most one job at a time, so looking at the first returned range is sufficient - wait(store(rangeResult, krmGetRanges(tr, bulkLoadJobPrefix, normalKeys))); - for (int i = 0; i < rangeResult.size() - 1; ++i) { + rangeResult = co_await krmGetRanges(tr, bulkLoadJobPrefix, normalKeys); + if (rangeResult.empty()) { + co_return Optional(); + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { if (rangeResult[i].value.empty()) { continue; } @@ -2777,19 +2828,20 @@ ACTOR Future> getSubmittedBulkLoadJob(Transaction* tr if (!jobState.isValid()) { continue; } - return jobState; + co_return jobState; } - return Optional(); + co_return Optional(); } -ACTOR Future cancelBulkLoadJob(Database cx, UID jobId) { - state Transaction tr(cx); - state Optional aliveJob; - loop { +Future cancelBulkLoadJob(Database cx, UID jobId) { + Transaction tr(cx); + Optional aliveJob; + while (true) { + Error err; try { - wait(store(aliveJob, getSubmittedBulkLoadJob(&tr))); + aliveJob = co_await getSubmittedBulkLoadJob(&tr); if (!aliveJob.present()) { - return Void(); // Has been cancelled + co_return; // Has been cancelled } if (aliveJob.get().getJobId() != jobId) { throw bulkload_task_outdated(); // jobId is outdated @@ -2803,46 +2855,46 @@ ACTOR Future cancelBulkLoadJob(Database cx, UID jobId) { tr.set(moveKeysLockWriteKey, wrLastWrite.toValue()); // Clear all metadata of the job ASSERT(!aliveJob.get().getJobRange().empty()); - wait(krmSetRangeCoalescing(&tr, - bulkLoadJobPrefix, - aliveJob.get().getJobRange(), - normalKeys, - bulkLoadJobValue(BulkLoadJobState()))); + co_await krmSetRangeCoalescing( + &tr, bulkLoadJobPrefix, aliveJob.get().getJobRange(), normalKeys, bulkLoadJobValue(BulkLoadJobState())); // Clear all metadata of the task. The task and the job is guaranteed to be consistent. - wait(krmSetRangeCoalescing(&tr, - bulkLoadTaskPrefix, - aliveJob.get().getJobRange(), - normalKeys, - bulkLoadTaskStateValue(BulkLoadTaskState()))); + co_await krmSetRangeCoalescing(&tr, + bulkLoadTaskPrefix, + aliveJob.get().getJobRange(), + normalKeys, + bulkLoadTaskStateValue(BulkLoadTaskState())); // Add cancelled job to history aliveJob.get().setEndTime(now()); aliveJob.get().setCancelledPhase(); - wait(addBulkLoadJobToHistory(&tr, aliveJob.get())); - wait(releaseExclusiveReadLockOnRange(&tr, aliveJob.get().getJobRange(), rangeLockNameForBulkLoad)); - wait(tr.commit()); + co_await addBulkLoadJobToHistory(&tr, aliveJob.get()); + co_await releaseExclusiveReadLockOnRange(&tr, aliveJob.get().getJobRange(), rangeLockNameForBulkLoad); + // Clean up BulkLoad owner info when clearing job metadata + tr.clear(bulkLoadOwnerKeyFor(jobId)); + co_await tr.commit(); break; } catch (Error& e) { - // Currently, only bulkload job uses the range lock, and one job exists at a time. - // TODO(BulkLoad): support multiple jobs at a time - ASSERT(e.code() != error_code_range_unlock_reject); - wait(tr.onError(e)); + err = e; } + // Currently, only bulkload job uses the range lock, and one job exists at a time. + // TODO(BulkLoad): support multiple jobs at a time + ASSERT(err.code() != error_code_range_unlock_reject); + co_await tr.onError(err); } - return Void(); } // TODO(Zhe): clear bulkload task metadata within the input range -ACTOR Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, bool lockAware) { +Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, bool lockAware) { ASSERT(jobState.getPhase() == BulkLoadJobPhase::Submitted); - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { if (lockAware) { tr.setOption(FDBTransactionOptions::LOCK_AWARE); } // There is at most one bulkLoad job or bulkDump job at a time globally - Optional aliveBulkDumpJob = wait(getSubmittedBulkDumpJob(&tr)); + Optional aliveBulkDumpJob = co_await getSubmittedBulkDumpJob(&tr); if (aliveBulkDumpJob.present()) { TraceEvent(SevWarn, "SubmitBulkLoadJobFailed") .setMaxEventLength(-1) @@ -2852,10 +2904,10 @@ ACTOR Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, boo .detail("ExistBulkDumpJob", aliveBulkDumpJob.get().toString()); throw bulkload_task_failed(); } - Optional aliveJob = wait(getSubmittedBulkLoadJob(&tr)); + Optional aliveJob = co_await getSubmittedBulkLoadJob(&tr); if (aliveJob.present()) { if (aliveJob.get().getJobId() == jobState.getJobId()) { - return Void(); // The job has been submitted. + co_return; // The job has been submitted. } TraceEvent(SevWarn, "SubmitBulkLoadJobFailed") .setMaxEventLength(-1) @@ -2883,41 +2935,45 @@ ACTOR Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, boo } ASSERT(!jobState.getJobRange().empty()); // Init the map of task states - wait(krmSetRange( - &tr, bulkLoadTaskPrefix, jobState.getJobRange(), bulkLoadTaskStateValue(BulkLoadTaskState()))); + co_await krmSetRange( + &tr, bulkLoadTaskPrefix, jobState.getJobRange(), bulkLoadTaskStateValue(BulkLoadTaskState())); // Persist job metadata - wait(krmSetRange(&tr, bulkLoadJobPrefix, jobState.getJobRange(), bulkLoadJobValue(jobState))); + co_await krmSetRange(&tr, bulkLoadJobPrefix, jobState.getJobRange(), bulkLoadJobValue(jobState)); // Take lock on the job range - wait(takeExclusiveReadLockOnRange(&tr, jobState.getJobRange(), rangeLockNameForBulkLoad)); - wait(tr.commit()); + co_await takeExclusiveReadLockOnRange(&tr, jobState.getJobRange(), rangeLockNameForBulkLoad); + co_await tr.commit(); TraceEvent(SevInfo, "BulkLoadJobSubmitted") .setMaxEventLength(-1) .setMaxFieldLength(-1) .detail("SubmitBulkLoadJob", jobState.toString()); break; } catch (Error& e) { - // Currently, only bulkload job uses the range lock, and one job exists at a time. - // TODO(BulkLoad): support multiple jobs at a time - ASSERT(e.code() != error_code_range_lock_reject); - wait(tr.onError(e)); + err = e; } + // Currently, only bulkload job uses the range lock, and one job exists at a time. + // TODO(BulkLoad): support multiple jobs at a time + ASSERT(err.code() != error_code_range_lock_reject); + co_await tr.onError(err); } - return Void(); } -ACTOR Future> getRunningBulkLoadJob(Database cx, bool lockAware) { - state RangeResult rangeResult; - state Transaction tr(cx); - state Key beginKey = normalKeys.begin; - state Key endKey = normalKeys.end; +Future> getRunningBulkLoadJob(Database cx, bool lockAware) { + RangeResult rangeResult; + Transaction tr(cx); + Key beginKey = normalKeys.begin; + Key endKey = normalKeys.end; while (beginKey < endKey) { + Error err; try { if (lockAware) { tr.setOption(FDBTransactionOptions::LOCK_AWARE); } rangeResult.clear(); - wait(store(rangeResult, krmGetRanges(&tr, bulkLoadJobPrefix, KeyRangeRef(beginKey, endKey)))); - for (int i = 0; i < rangeResult.size() - 1; i++) { + rangeResult = co_await krmGetRanges(&tr, bulkLoadJobPrefix, KeyRangeRef(beginKey, endKey)); + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; i++) { if (rangeResult[i].value.empty()) { continue; } @@ -2927,31 +2983,37 @@ ACTOR Future> getRunningBulkLoadJob(Database cx, bool } // If a job is fully completed, the metadata should be removed from bulkLoadJobKeys // The metadata is added to bulkload history. - return jobState; + co_return jobState; } beginKey = rangeResult.back().key; + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Optional(); + co_return Optional(); } -ACTOR Future acknowledgeAllErrorBulkLoadTasks(Database cx, UID jobId, KeyRange jobRange) { - state Transaction tr(cx); - state Key beginKey = jobRange.begin; - state Key endKey = jobRange.end; - state Optional lastKey; - state BulkLoadTaskState existTask; - state RangeResult bulkLoadTaskResult; - state int i = 0; +Future acknowledgeAllErrorBulkLoadTasks(Database cx, UID jobId, KeyRange jobRange) { + Transaction tr(cx); + Key beginKey = jobRange.begin; + Key endKey = jobRange.end; + Optional lastKey; + BulkLoadTaskState existTask; + RangeResult bulkLoadTaskResult; + int i = 0; while (beginKey < endKey) { + Error err; try { tr.reset(); bulkLoadTaskResult.clear(); - wait(store(bulkLoadTaskResult, krmGetRanges(&tr, bulkLoadTaskPrefix, KeyRangeRef(beginKey, endKey)))); + bulkLoadTaskResult = co_await krmGetRanges(&tr, bulkLoadTaskPrefix, KeyRangeRef(beginKey, endKey)); + if (bulkLoadTaskResult.empty()) { + break; + } i = 0; - for (; i < bulkLoadTaskResult.size() - 1; i++) { + for (; i < static_cast(bulkLoadTaskResult.size()) - 1; i++) { if (bulkLoadTaskResult[i].value.empty()) { lastKey = bulkLoadTaskResult[i + 1].key; continue; @@ -2974,32 +3036,34 @@ ACTOR Future acknowledgeAllErrorBulkLoadTasks(Database cx, UID jobId, KeyR .detail("ExistTaskID", existTask.getTaskId()) .detail("ExistTaskRange", existTask.getRange()) .detail("ExistTaskJobId", existTask.getJobId()); - wait(setBulkLoadFinalizeTransaction(&tr, existTask.getRange(), existTask.getTaskId())); + co_await setBulkLoadFinalizeTransaction(&tr, existTask.getRange(), existTask.getTaskId()); } lastKey = bulkLoadTaskResult[i + 1].key; break; // We actively break because we do not want transaction large } - wait(tr.commit()); + co_await tr.commit(); ASSERT(lastKey.present()); beginKey = lastKey.get(); + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } -ACTOR Future setBulkDumpMode(Database cx, int mode) { - state Transaction tr(cx); - state BinaryWriter wr(Unversioned()); +Future setBulkDumpMode(Database cx, int mode) { + Transaction tr(cx); + BinaryWriter wr(Unversioned()); wr << mode; - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - state int oldMode = 0; - Optional oldModeValue = wait(tr.get(bulkDumpModeKey)); + int oldMode = 0; + Optional oldModeValue = co_await tr.get(bulkDumpModeKey); if (oldModeValue.present()) { BinaryReader rd(oldModeValue.get(), Unversioned()); rd >> oldMode; @@ -3012,33 +3076,36 @@ ACTOR Future setBulkDumpMode(Database cx, int mode) { wrLastWrite << deterministicRandom()->randomUniqueID(); // triger DD restarts tr.set(moveKeysLockWriteKey, wrLastWrite.toValue()); tr.set(bulkDumpModeKey, wr.toValue()); - wait(tr.commit()); + co_await tr.commit(); TraceEvent(SevInfo, "DDBulkDumpModeKeyChanged").detail("NewMode", mode).detail("OldMode", oldMode); } - return oldMode; + co_return oldMode; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future getBulkDumpMode(Database cx) { - state Transaction tr(cx); - loop { +Future getBulkDumpMode(Database cx) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - state int oldMode = 0; - Optional oldModeValue = wait(tr.get(bulkDumpModeKey)); + int oldMode = 0; + Optional oldModeValue = co_await tr.get(bulkDumpModeKey); if (oldModeValue.present()) { BinaryReader rd(oldModeValue.get(), Unversioned()); rd >> oldMode; } - return oldMode; + co_return oldMode; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -3046,22 +3113,25 @@ ACTOR Future getBulkDumpMode(Database cx) { // There is at most one bulk dump job at any time on the entire key space. // A job of a range can spawn multiple tasks according to the shard boundary. // Those tasks share the same job Id (aka belonging to the same job). -ACTOR Future> getSubmittedBulkDumpJob(Transaction* tr) { - state RangeResult rangeResult; - state KeyRange rangeToRead = normalKeys; - state Key beginKey = normalKeys.begin; +Future> getSubmittedBulkDumpJob(Transaction* tr) { + RangeResult rangeResult; + KeyRange rangeToRead = normalKeys; + Key beginKey = normalKeys.begin; while (beginKey < normalKeys.end) { + Error err; try { rangeResult.clear(); - wait(store(rangeResult, - krmGetRanges(tr, - bulkDumpPrefix, - KeyRangeRef(beginKey, normalKeys.end), - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES))); + rangeResult = co_await krmGetRanges(tr, + bulkDumpPrefix, + KeyRangeRef(beginKey, normalKeys.end), + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); // krmGetRanges splits the result into batches. // Check first batch is enough since we only check if any task exists - for (int i = 0; i < rangeResult.size() - 1; ++i) { + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { if (rangeResult[i].value.empty()) { continue; } @@ -3069,24 +3139,27 @@ ACTOR Future> getSubmittedBulkDumpJob(Transaction* tr) { if (!bulkDumpState.isValid()) { continue; } - return bulkDumpState; + co_return bulkDumpState; } beginKey = rangeResult.back().key; + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return Optional(); + co_return Optional(); } -ACTOR Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob) { - state Transaction tr(cx); - loop { +Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); // There is at most one bulkLoad job or bulkDump job at a time globally - Optional aliveBulkLoadJob = wait(getSubmittedBulkLoadJob(&tr)); + Optional aliveBulkLoadJob = co_await getSubmittedBulkLoadJob(&tr); if (aliveBulkLoadJob.present()) { TraceEvent(SevWarn, "SubmitBulkDumpJobFailed") .setMaxEventLength(-1) @@ -3096,10 +3169,10 @@ ACTOR Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob) { .detail("NewJob", bulkDumpJob.toString()); throw bulkdump_task_failed(); } - Optional aliveJob = wait(getSubmittedBulkDumpJob(&tr)); + Optional aliveJob = co_await getSubmittedBulkDumpJob(&tr); if (aliveJob.present()) { if (aliveJob.get().getJobId() == bulkDumpJob.getJobId()) { - return Void(); // The job has been persisted + co_return; // The job has been persisted } TraceEvent(SevWarn, "SubmitBulkDumpJobFailed") .setMaxEventLength(-1) @@ -3125,29 +3198,30 @@ ACTOR Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob) { .detail("NewJob", bulkDumpJob.toString()); throw bulkdump_task_failed(); } - wait(krmSetRange(&tr, bulkDumpPrefix, bulkDumpJob.getJobRange(), bulkDumpStateValue(bulkDumpJob))); - wait(tr.commit()); + co_await krmSetRange(&tr, bulkDumpPrefix, bulkDumpJob.getJobRange(), bulkDumpStateValue(bulkDumpJob)); + co_await tr.commit(); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } -ACTOR Future cancelBulkDumpJob(Database cx, UID jobId) { - state Transaction tr(cx); - state Key beginKey = normalKeys.begin; - state Key endKey = normalKeys.end; - state BulkDumpState existJob; - state KeyRange rangeToRead; - state RangeResult bulkDumpResult; +Future cancelBulkDumpJob(Database cx, UID jobId) { + Transaction tr(cx); + Key beginKey = normalKeys.begin; + Key endKey = normalKeys.end; + BulkDumpState existJob; + KeyRange rangeToRead; + RangeResult bulkDumpResult; while (beginKey < endKey) { + Error err; try { bulkDumpResult.clear(); rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); - wait(store(bulkDumpResult, krmGetRanges(&tr, bulkDumpPrefix, rangeToRead))); - for (int i = 0; i < bulkDumpResult.size() - 1; i++) { + bulkDumpResult = co_await krmGetRanges(&tr, bulkDumpPrefix, rangeToRead); + for (int i = 0; i < static_cast(bulkDumpResult.size()) - 1; i++) { if (bulkDumpResult[i].value.empty()) { continue; } @@ -3157,8 +3231,9 @@ ACTOR Future cancelBulkDumpJob(Database cx, UID jobId) { } // We only clear the metadata if it has the same jobId as the input Id. // When there is a new jobId persisted different than the input Id, - // a new job has been submitted successfully. Since a new job can be submitted successfully if and only - // if no old metadata exists (the old job metadata has been cleared). So, we can stop at this point. + // a new job has been submitted successfully. Since a new job can be submitted successfully if and + // only if no old metadata exists (the old job metadata has been cleared). So, we can stop at this + // point. if (existJob.getJobId() != jobId) { TraceEvent(SevWarn, "DDBulkDumpJobHasChanged") .detail("InputJobID", jobId.toString()) @@ -3166,48 +3241,117 @@ ACTOR Future cancelBulkDumpJob(Database cx, UID jobId) { throw bulkload_task_outdated(); } } - wait(krmSetRangeCoalescing( - &tr, bulkDumpPrefix, rangeToRead, normalKeys, bulkDumpStateValue(BulkDumpState()))); - wait(tr.commit()); + co_await krmSetRangeCoalescing( + &tr, bulkDumpPrefix, rangeToRead, normalKeys, bulkDumpStateValue(BulkDumpState())); + // Clean up owner info when clearing job metadata + tr.clear(bulkDumpOwnerKeyFor(jobId)); + co_await tr.commit(); tr.reset(); beginKey = bulkDumpResult.back().key; + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); + } +} + +// Generic owner tracking implementation for bulk operations +Future setBulkOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo, bool isBulkDump) { + Transaction tr(cx); + while (true) { + Error err; + try { + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + Key ownerKey = isBulkDump ? bulkDumpOwnerKeyFor(jobId) : bulkLoadOwnerKeyFor(jobId); + tr.set(ownerKey, ObjectWriter::toValue(ownerInfo, IncludeVersion())); + co_await tr.commit(); + co_return; + } catch (Error& e) { + err = e; + } + co_await tr.onError(err); } - return Void(); } -ACTOR Future getBulkDumpCompleteTaskCount(Database cx, KeyRange rangeToRead) { - state Transaction tr(cx); - state Key readBegin = rangeToRead.begin; - state Key readEnd = rangeToRead.end; - state RangeResult rangeResult; - state size_t completeTaskCount = 0; +Future> getBulkOwner(Database cx, UID jobId, bool isBulkDump) { + Transaction tr(cx); + while (true) { + Error err; + try { + tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + Key ownerKey = isBulkDump ? bulkDumpOwnerKeyFor(jobId) : bulkLoadOwnerKeyFor(jobId); + Optional value = co_await tr.get(ownerKey); + if (!value.present()) { + co_return Optional(); + } + BulkDumpOwnerInfo info; + ObjectReader reader(value.get().begin(), IncludeVersion()); + reader.deserialize(info); + co_return info; + } catch (Error& e) { + err = e; + } + co_await tr.onError(err); + } +} + +// Public API wrappers for backward compatibility +Future setBulkDumpOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo) { + co_await setBulkOwner(cx, jobId, ownerInfo, true); +} + +Future> getBulkDumpOwner(Database cx, UID jobId) { + Optional result = co_await getBulkOwner(cx, jobId, true); + co_return result; +} + +Future setBulkLoadOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo) { + co_await setBulkOwner(cx, jobId, ownerInfo, false); +} + +Future> getBulkLoadOwner(Database cx, UID jobId) { + Optional result = co_await getBulkOwner(cx, jobId, false); + co_return result; +} + +Future getBulkDumpCompleteTaskCount(Database cx, KeyRange rangeToRead) { + Transaction tr(cx); + Key readBegin = rangeToRead.begin; + Key readEnd = rangeToRead.end; + RangeResult rangeResult; + size_t completeTaskCount = 0; while (readBegin < readEnd) { - state int retryCount = 0; - loop { + int retryCount = 0; + while (true) { + Error err; try { rangeResult.clear(); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - wait(store(rangeResult, - krmGetRanges(&tr, - bulkDumpPrefix, - KeyRangeRef(readBegin, readEnd), - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES))); + rangeResult = co_await krmGetRanges(&tr, + bulkDumpPrefix, + KeyRangeRef(readBegin, readEnd), + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); break; } catch (Error& e) { - if (retryCount > 30) { - throw timed_out(); - } - wait(tr.onError(e)); - retryCount++; + err = e; + } + if (retryCount > 30) { + throw timed_out(); } + co_await tr.onError(err); + retryCount++; } - for (int i = 0; i < rangeResult.size() - 1; ++i) { + // Guard against empty results (can happen during cluster instability) + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { if (rangeResult[i].value.empty()) { continue; } @@ -3218,94 +3362,296 @@ ACTOR Future getBulkDumpCompleteTaskCount(Database cx, KeyRange rangeToR } readBegin = rangeResult.back().key; } - return completeTaskCount; + co_return completeTaskCount; +} + +Future> getBulkDumpProgress(Database cx) { + Transaction tr(cx); + BulkDumpProgress progress; + double currentTime = now(); + + Optional submittedJob; + while (true) { + Error err; + try { + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Optional job = co_await getSubmittedBulkDumpJob(&tr); + submittedJob = job; + if (!submittedJob.present()) { + co_return Optional(); + } + progress.jobId = submittedJob.get().getJobId(); + progress.jobRange = submittedJob.get().getJobRange(); + break; + } catch (Error& e) { + err = e; + } + co_await tr.onError(err); + } + + // Get start time from owner info (stored separately from BulkDumpState) + Optional ownerInfo = co_await getBulkDumpOwner(cx, progress.jobId); + if (ownerInfo.present()) { + progress.startTime = ownerInfo.get().submitTime; + } else { + // Fallback if no owner info (standalone bulkdump): use current time (elapsed will be ~0) + progress.startTime = currentTime; + } + + Key readBegin = progress.jobRange.begin; + Key readEnd = progress.jobRange.end; + RangeResult rangeResult; + + while (readBegin < readEnd) { + int retryCount = 0; + while (true) { + Error err; + try { + rangeResult.clear(); + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + rangeResult = co_await krmGetRanges(&tr, + bulkDumpPrefix, + KeyRangeRef(readBegin, readEnd), + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); + break; + } catch (Error& e) { + err = e; + } + if (retryCount > 30) { + throw timed_out(); + } + co_await tr.onError(err); + retryCount++; + } + + // Guard against empty results (can happen during cluster instability) + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { + if (rangeResult[i].value.empty()) { + continue; + } + BulkDumpState taskState = decodeBulkDumpState(rangeResult[i].value); + progress.totalTasks++; + + if (taskState.getPhase() == BulkDumpPhase::Complete) { + progress.completeTasks++; + progress.completedBytes += taskState.getManifest().getTotalBytes(); + } else if (taskState.getPhase() == BulkDumpPhase::Submitted) { + progress.runningTasks++; + } + + progress.totalBytes += taskState.getManifest().getTotalBytes(); + } + readBegin = rangeResult.back().key; + } + + progress.elapsedSeconds = currentTime - progress.startTime; + + co_return progress; +} + +Future> getBulkLoadProgress(Database cx) { + Transaction tr(cx); + BulkLoadProgress progress; + double currentTime = now(); + + while (true) { + Error err; + try { + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + Optional runningJob = co_await getRunningBulkLoadJob(cx); + if (!runningJob.present()) { + co_return Optional(); + } + progress.jobId = runningJob.get().getJobId(); + progress.jobRange = runningJob.get().getJobRange(); + Optional taskCount = runningJob.get().getTaskCount(); + progress.totalTasks = taskCount.present() ? (int)taskCount.get() : 0; + progress.startTime = runningJob.get().getSubmitTime(); + progress.elapsedSeconds = currentTime - progress.startTime; + break; + } catch (Error& e) { + err = e; + } + co_await tr.onError(err); + } + + Key readBegin = progress.jobRange.begin; + Key readEnd = progress.jobRange.end; + RangeResult rangeResult; + + while (readBegin < readEnd) { + int retryCount = 0; + while (true) { + Error err; + try { + rangeResult.clear(); + tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); + tr.setOption(FDBTransactionOptions::LOCK_AWARE); + rangeResult = co_await krmGetRanges(&tr, + bulkLoadTaskPrefix, + KeyRangeRef(readBegin, readEnd), + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); + break; + } catch (Error& e) { + err = e; + } + if (retryCount > 30) { + throw timed_out(); + } + co_await tr.onError(err); + retryCount++; + } + + // Guard against empty results (can happen during cluster instability) + if (rangeResult.empty()) { + break; + } + for (int i = 0; i < static_cast(rangeResult.size()) - 1; ++i) { + if (rangeResult[i].value.empty()) { + continue; + } + BulkLoadTaskState taskState = decodeBulkLoadTaskState(rangeResult[i].value); + + switch (taskState.phase) { + case BulkLoadPhase::Submitted: + progress.submittedTasks++; + break; + case BulkLoadPhase::Triggered: + progress.triggeredTasks++; + break; + case BulkLoadPhase::Running: + progress.runningTasks++; + if (taskState.startTime > 0 && + (currentTime - taskState.startTime) > BULK_TASK_STALL_THRESHOLD_SECONDS) { + BulkLoadStalledTask stalledTask; + stalledTask.taskId = taskState.getTaskId(); + stalledTask.range = taskState.getRange(); + stalledTask.stalledSeconds = currentTime - taskState.startTime; + stalledTask.restartCount = taskState.restartCount; + // TODO: Get storage server ID and last error when available + progress.stalledTasks.push_back(stalledTask); + } + break; + case BulkLoadPhase::Complete: + case BulkLoadPhase::Acknowledged: + progress.completeTasks++; + progress.completedBytes += taskState.getTotalBytes(); + break; + case BulkLoadPhase::Error: + progress.errorTasks++; + break; + default: + break; + } + + progress.totalBytes += taskState.getTotalBytes(); + } + readBegin = rangeResult.back().key; + } + + co_return progress; } // Persist a new owner if input ownerUniqueID is not existing; Update description if input ownerUniqueID exists -ACTOR Future registerRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID, std::string description) { +Future registerRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID, std::string description) { if (ownerUniqueID.empty() || description.empty()) { throw range_lock_failed(); } - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - Optional res = wait(tr.get(rangeLockOwnerKeyFor(ownerUniqueID))); + Optional res = co_await tr.get(rangeLockOwnerKeyFor(ownerUniqueID)); RangeLockOwner owner; if (res.present()) { owner = decodeRangeLockOwner(res.get()); ASSERT(owner.isValid()); if (owner.getDescription() == description) { - return Void(); + co_return; } owner.setDescription(description); } else { owner = RangeLockOwner(ownerUniqueID, description); } tr.set(rangeLockOwnerKeyFor(ownerUniqueID), rangeLockOwnerValue(owner)); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future removeRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID) { +Future removeRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID) { if (ownerUniqueID.empty()) { throw range_lock_failed(); } - state Transaction tr(cx); - loop { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - Optional res = wait(tr.get(rangeLockOwnerKeyFor(ownerUniqueID))); + Optional res = co_await tr.get(rangeLockOwnerKeyFor(ownerUniqueID)); if (!res.present()) { - return Void(); + co_return; } RangeLockOwner owner = decodeRangeLockOwner(res.get()); ASSERT(owner.isValid()); tr.clear(rangeLockOwnerKeyFor(ownerUniqueID)); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future> getRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID) { - state Transaction tr(cx); - loop { +Future> getRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID) { + Transaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - Optional res = wait(tr.get(rangeLockOwnerKeyFor(ownerUniqueID))); + Optional res = co_await tr.get(rangeLockOwnerKeyFor(ownerUniqueID)); if (!res.present()) { - return Optional(); + co_return Optional(); } RangeLockOwner owner = decodeRangeLockOwner(res.get()); ASSERT(owner.isValid()); - return owner; + co_return owner; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } -ACTOR Future> getAllRangeLockOwners(Database cx) { - state std::vector res; - state Key beginKey = rangeLockOwnerKeys.begin; - state Key endKey = rangeLockOwnerKeys.end; - state Transaction tr(cx); +AsyncResult> getAllRangeLockOwners(Database cx) { + std::vector res; + Key beginKey = rangeLockOwnerKeys.begin; + Key endKey = rangeLockOwnerKeys.end; + Transaction tr(cx); while (beginKey < endKey) { - state KeyRange rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); + KeyRange rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); + Error err; try { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - RangeResult result = wait(tr.getRange(rangeToRead, CLIENT_KNOBS->TOO_MANY)); + RangeResult result = co_await tr.getRange(rangeToRead, CLIENT_KNOBS->TOO_MANY); for (const auto& kv : result) { RangeLockOwner owner = decodeRangeLockOwner(kv.value); ASSERT(owner.isValid()); @@ -3315,30 +3661,36 @@ ACTOR Future> getAllRangeLockOwners(Database cx) { if (!result.more) { break; } + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return res; + co_return res; } // Not transactional -ACTOR Future>> +Future>> findExclusiveReadLockOnRange(Database cx, KeyRange range, Optional ownerName) { if (range.end > normalKeys.end) { throw range_lock_failed(); } - state std::vector> lockedRanges; - state Key beginKey = range.begin; - state Key endKey = range.end; - state Transaction tr(cx); + std::vector> lockedRanges; + Key beginKey = range.begin; + Key endKey = range.end; + Transaction tr(cx); while (beginKey < endKey) { - state KeyRange rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); + KeyRange rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); + Error err; try { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - RangeResult result = wait(krmGetRanges(&tr, rangeLockPrefix, rangeToRead)); - for (int i = 0; i < result.size() - 1; i++) { + RangeResult result = co_await krmGetRanges(&tr, rangeLockPrefix, rangeToRead); + if (result.empty()) { + break; + } + for (int i = 0; i < static_cast(result.size()) - 1; i++) { if (result[i].value.empty()) { continue; } @@ -3353,19 +3705,19 @@ findExclusiveReadLockOnRange(Database cx, KeyRange range, Optional prepareExclusiveRangeLockOperation(Transaction* tr, - KeyRange range, - RangeLockOwnerName ownerUniqueID) { +Future prepareExclusiveRangeLockOperation(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { // Check input range if (range.end > normalKeys.end) { TraceEvent(SevDebug, "PrepareExclusiveRangeLockOperationFailed") @@ -3374,7 +3726,7 @@ ACTOR Future prepareExclusiveRangeLockOperation(Transaction* tr, throw range_lock_failed(); } // Check owner - state Optional ownerValue = wait(tr->get(rangeLockOwnerKeyFor(ownerUniqueID))); + Optional ownerValue = co_await tr->get(rangeLockOwnerKeyFor(ownerUniqueID)); if (!ownerValue.present()) { TraceEvent(SevDebug, "PrepareExclusiveRangeLockOperationFailed") .detail("Reason", "Owner not found") @@ -3382,16 +3734,19 @@ ACTOR Future prepareExclusiveRangeLockOperation(Transaction* tr, .detail("Range", range); throw range_lock_failed(); } - state RangeLockOwner owner = decodeRangeLockOwner(ownerValue.get()); + RangeLockOwner owner = decodeRangeLockOwner(ownerValue.get()); ASSERT(owner.isValid()); // Check lock state on the entire input range. Throw exception if the range has been locked by a different owner. - state Key beginKey = range.begin; - state Key endKey = range.end; - state KeyRange rangeToRead; + Key beginKey = range.begin; + Key endKey = range.end; + KeyRange rangeToRead; while (beginKey < endKey) { rangeToRead = KeyRangeRef(beginKey, endKey); - RangeResult res = wait(krmGetRanges(tr, rangeLockPrefix, rangeToRead)); - for (int i = 0; i < res.size() - 1; i++) { + RangeResult res = co_await krmGetRanges(tr, rangeLockPrefix, rangeToRead); + if (res.empty()) { + break; + } + for (int i = 0; i < static_cast(res.size()) - 1; i++) { if (res[i].value.empty()) { continue; } @@ -3410,14 +3765,11 @@ ACTOR Future prepareExclusiveRangeLockOperation(Transaction* tr, throw range_lock_reject(); // Has been locked } } - beginKey = res[res.size() - 1].key; + beginKey = res.back().key; } - return Void(); } -ACTOR Future prepareExclusiveRangeUnlockOperation(Transaction* tr, - KeyRange range, - RangeLockOwnerName ownerUniqueID) { +Future prepareExclusiveRangeUnlockOperation(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { // Check input range if (range.end > normalKeys.end) { TraceEvent(SevDebug, "PrepareExclusiveRangeUnlockOperationFailed") @@ -3426,7 +3778,7 @@ ACTOR Future prepareExclusiveRangeUnlockOperation(Transaction* tr, throw range_lock_failed(); } // Check owner - state Optional ownerValue = wait(tr->get(rangeLockOwnerKeyFor(ownerUniqueID))); + Optional ownerValue = co_await tr->get(rangeLockOwnerKeyFor(ownerUniqueID)); if (!ownerValue.present()) { TraceEvent(SevDebug, "PrepareExclusiveRangeUnlockOperationFailed") .detail("Reason", "Owner not found") @@ -3434,17 +3786,20 @@ ACTOR Future prepareExclusiveRangeUnlockOperation(Transaction* tr, .detail("Range", range); throw range_lock_failed(); } - state RangeLockOwner owner = decodeRangeLockOwner(ownerValue.get()); + RangeLockOwner owner = decodeRangeLockOwner(ownerValue.get()); ASSERT(owner.isValid()); // Check lock state on the entire input range. Throw exception if the range has been locked by a different owner. - state Key beginKey = range.begin; - state Key endKey = range.end; - state KeyRange rangeToRead; + Key beginKey = range.begin; + Key endKey = range.end; + KeyRange rangeToRead; while (beginKey < endKey) { rangeToRead = KeyRangeRef(beginKey, endKey); - RangeResult res = wait(krmGetRanges(tr, rangeLockPrefix, rangeToRead)); - for (int i = 0; i < res.size() - 1; i++) { + RangeResult res = co_await krmGetRanges(tr, rangeLockPrefix, rangeToRead); + if (res.empty()) { + break; + } + for (int i = 0; i < static_cast(res.size()) - 1; i++) { if (res[i].value.empty()) { continue; } @@ -3462,64 +3817,65 @@ ACTOR Future prepareExclusiveRangeUnlockOperation(Transaction* tr, throw range_unlock_reject(); } } - beginKey = res[res.size() - 1].key; + beginKey = res.back().key; } - return Void(); } // Transactional. One transaction can call takeExclusiveReadLockOnRange at most for one time. // This is the limitation of the krmSetRangeCoalescing. -ACTOR Future takeExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { +Future takeExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); // Add conflict range tr->addWriteConflictRange(range); - wait(prepareExclusiveRangeLockOperation(tr, range, ownerUniqueID)); + co_await prepareExclusiveRangeLockOperation(tr, range, ownerUniqueID); // At this point, no lock presents on the range. // Lock range by writting the range. RangeLockStateSet rangeLockStateSet; rangeLockStateSet.insertIfNotExist(RangeLockState(RangeLockType::ExclusiveReadLock, ownerUniqueID, range)); - wait(krmSetRange(tr, rangeLockPrefix, range, rangeLockStateSetValue(rangeLockStateSet))); + co_await krmSetRange(tr, rangeLockPrefix, range, rangeLockStateSetValue(rangeLockStateSet)); TraceEvent(SevInfo, "TakeExclusiveReadLockTransactionOnRange").detail("Range", range); - return Void(); } // Transactional. One transaction can call releaseExclusiveReadLockOnRange at most for one time. // This is the limitation of the krmSetRangeCoalescing. -ACTOR Future releaseExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { +Future releaseExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID) { tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - wait(prepareExclusiveRangeUnlockOperation(tr, range, ownerUniqueID)); + co_await prepareExclusiveRangeUnlockOperation(tr, range, ownerUniqueID); // At this point, no lock presents on the range. // Unlock by overwiting the range. - wait(krmSetRangeCoalescing(tr, rangeLockPrefix, range, normalKeys, rangeLockStateSetValue(RangeLockStateSet()))); + co_await krmSetRangeCoalescing(tr, rangeLockPrefix, range, normalKeys, rangeLockStateSetValue(RangeLockStateSet())); TraceEvent(SevInfo, "ReleaseExclusiveReadLockTransactionOnRange").detail("Range", range); - return Void(); } -ACTOR Future releaseExclusiveReadLockByUser(Database cx, RangeLockOwnerName ownerUniqueID) { - state Key beginKey = normalKeys.begin; - state Key endKey = normalKeys.end; - state Transaction tr(cx); - state int i = 0; - state RangeResult result; - state KeyRange rangeToRead; - state RangeLockStateSet currentRangeLockStateSet; - state KeyRange currentRange; - state Key beginKeyToClear; - state Key endKeyToClear; +Future releaseExclusiveReadLockByUser(Database cx, RangeLockOwnerName ownerUniqueID) { + Key beginKey = normalKeys.begin; + Key endKey = normalKeys.end; + Transaction tr(cx); + int i = 0; + RangeResult result; + KeyRange rangeToRead; + RangeLockStateSet currentRangeLockStateSet; + KeyRange currentRange; + Key beginKeyToClear; + Key endKeyToClear; while (beginKey < endKey) { rangeToRead = Standalone(KeyRangeRef(beginKey, endKey)); + Error err; try { tr.reset(); tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); result.clear(); - wait(store(result, krmGetRanges(&tr, rangeLockPrefix, rangeToRead))); + result = co_await krmGetRanges(&tr, rangeLockPrefix, rangeToRead); + if (result.empty()) { + break; + } i = 0; beginKeyToClear = result[0].key; endKeyToClear = result[0].key; // Expanding when currentRange is valid to clear - for (; i < result.size() - 1; i++) { + for (; i < static_cast(result.size()) - 1; i++) { currentRange = KeyRangeRef(result[i].key, result[i + 1].key); if (result[i].value.empty()) { endKeyToClear = currentRange.end; @@ -3537,71 +3893,77 @@ ACTOR Future releaseExclusiveReadLockByUser(Database cx, RangeLockOwnerNam } if (beginKeyToClear != endKeyToClear) { ASSERT(endKeyToClear > beginKeyToClear); - wait(krmSetRangeCoalescing(&tr, - rangeLockPrefix, - KeyRangeRef(beginKeyToClear, endKeyToClear), - normalKeys, - rangeLockStateSetValue(RangeLockStateSet()))); - wait(tr.commit()); + co_await krmSetRangeCoalescing(&tr, + rangeLockPrefix, + KeyRangeRef(beginKeyToClear, endKeyToClear), + normalKeys, + rangeLockStateSetValue(RangeLockStateSet())); + co_await tr.commit(); } beginKey = currentRange.end; // We skip the currentRange if it is not locked by the input owner. + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } // Transactional -ACTOR Future takeExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID) { - state Transaction tr(cx); - loop { +Future takeExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID) { + Transaction tr(cx); + while (true) { + Error err; try { - wait(takeExclusiveReadLockOnRange(&tr, range, ownerUniqueID)); - wait(tr.commit()); + co_await takeExclusiveReadLockOnRange(&tr, range, ownerUniqueID); + co_await tr.commit(); TraceEvent(SevInfo, "TakeExclusiveReadLockOnRange").detail("Range", range); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } // Transactional -ACTOR Future releaseExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID) { - state Transaction tr(cx); - loop { +Future releaseExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID) { + Transaction tr(cx); + while (true) { + Error err; try { - wait(releaseExclusiveReadLockOnRange(&tr, range, ownerUniqueID)); - wait(tr.commit()); + co_await releaseExclusiveReadLockOnRange(&tr, range, ownerUniqueID); + co_await tr.commit(); TraceEvent(SevInfo, "ReleaseExclusiveReadLockOnRange").detail("Range", range); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return Void(); } -ACTOR Future waitForPrimaryDC(Database cx, StringRef dcId) { - state ReadYourWritesTransaction tr(cx); +Future waitForPrimaryDC(Database cx, StringRef dcId) { + ReadYourWritesTransaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); - Optional res = wait(tr.get(primaryDatacenterKey)); + Optional res = co_await tr.get(primaryDatacenterKey); if (res.present() && res.get() == dcId) { - return Void(); + co_return; } - state Future watchFuture = tr.watch(primaryDatacenterKey); - wait(tr.commit()); - wait(watchFuture); + Future watchFuture = tr.watch(primaryDatacenterKey); + co_await tr.commit(); + co_await watchFuture; tr.reset(); + continue; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -3656,7 +4018,7 @@ bool schemaMatch(json_spirit::mValue const& schemaValue, schemaCoverage(spath); } - if (!schema.count(key)) { + if (!schema.contains(key)) { errorStr += format("ERROR: Unknown key `%s'\n", kpath.c_str()); TraceEvent(sev, "SchemaMismatch").detail("Path", kpath).detail("SchemaPath", spath); ok = false; @@ -3664,7 +4026,7 @@ bool schemaMatch(json_spirit::mValue const& schemaValue, } auto& sv = schema.at(key); - if (sv.type() == json_spirit::obj_type && sv.get_obj().count("$enum")) { + if (sv.type() == json_spirit::obj_type && sv.get_obj().contains("$enum")) { auto& enum_values = sv.get_obj().at("$enum").get_array(); bool any_match = false; @@ -3689,7 +4051,7 @@ bool schemaMatch(json_spirit::mValue const& schemaValue, } ok = false; } - } else if (sv.type() == json_spirit::obj_type && sv.get_obj().count("$map")) { + } else if (sv.type() == json_spirit::obj_type && sv.get_obj().contains("$map")) { if (rv.type() != json_spirit::obj_type) { errorStr += format("ERROR: Expected an object as the value for key `%s'\n", kpath.c_str()); TraceEvent(sev, "SchemaMismatch") @@ -3733,9 +4095,9 @@ bool schemaMatch(json_spirit::mValue const& schemaValue, } else if (resultValue.type() == json_spirit::array_type) { auto& valueArray = resultValue.get_array(); auto& schemaArray = schemaValue.get_array(); - if (!schemaArray.size()) { + if (schemaArray.empty()) { // An empty schema array means that the value array is required to be empty - if (valueArray.size()) { + if (!valueArray.empty()) { errorStr += format("ERROR: Expected an empty array for key `%s'\n", path.c_str()); TraceEvent(sev, "SchemaMismatch") .detail("Path", path) @@ -3807,8 +4169,6 @@ std::string ManagementAPI::generateErrorMessage(const CoordinatorsResult& res) { } TEST_CASE("/ManagementAPI/AutoQuorumChange/checkLocality") { - wait(Future(Void())); - std::vector workers; std::vector chosen; std::set excluded; diff --git a/fdbclient/MonitorLeader.actor.cpp b/fdbclient/MonitorLeader.cpp similarity index 80% rename from fdbclient/MonitorLeader.actor.cpp rename to fdbclient/MonitorLeader.cpp index 83c2d434050..86e58a2d26f 100644 --- a/fdbclient/MonitorLeader.actor.cpp +++ b/fdbclient/MonitorLeader.cpp @@ -1,5 +1,5 @@ /* - * MonitorLeader.actor.cpp + * MonitorLeader.cpp * * This source file is part of the FoundationDB open source project * @@ -24,10 +24,10 @@ #include "fdbclient/NativeAPI.actor.h" #include "flow/ActorCollection.h" #include "flow/UnitTest.h" -#include "fdbrpc/genericactors.actor.h" +#include "fdbrpc/genericactors.h" #include "flow/Platform.h" #include "flow/IConnection.h" -#include "flow/actorcompiler.h" // has to be last include +#include "flow/CoroUtils.h" namespace { @@ -126,7 +126,7 @@ ClusterConnectionString::ClusterConnectionString(const std::string& connectionSt } TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/addresses") { - state std::string input; + std::string input; { input = "asdf:2345@1.1.1.1:345"; @@ -186,7 +186,7 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/addresses") { } TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/hostnames") { - state std::string input; + std::string input; { input = "asdf:2345@localhost:1234"; @@ -267,8 +267,8 @@ TEST_CASE("/fdbclient/MonitorLeader/ConnectionString/hostname") { return Void(); } -ACTOR Future> tryResolveHostnamesImpl(ClusterConnectionString* self) { - state std::set allCoordinatorsSet; +Future> tryResolveHostnamesImpl(ClusterConnectionString* self) { + std::set allCoordinatorsSet; for (const auto& coord : self->coords) { allCoordinatorsSet.insert(coord); } @@ -281,10 +281,10 @@ ACTOR Future> tryResolveHostnamesImpl(ClusterConnect return Void(); })); } - wait(waitForAll(fs)); + co_await waitForAll(fs); std::vector allCoordinators(allCoordinatorsSet.begin(), allCoordinatorsSet.end()); std::sort(allCoordinators.begin(), allCoordinators.end()); - return allCoordinators; + co_return allCoordinators; } Future> ClusterConnectionString::tryResolveHostnames() { @@ -295,16 +295,14 @@ TEST_CASE("/fdbclient/MonitorLeader/PartialResolve") { std::string connectionString = "TestCluster:0@host.name:1234,host-name:5678"; std::string hn = "host-name", port = "5678"; - state NetworkAddress address = NetworkAddress::parse("1.0.0.0:5678"); + NetworkAddress address = NetworkAddress::parse("1.0.0.0:5678"); INetworkConnections::net()->addMockTCPEndpoint(hn, port, { address }); ClusterConnectionString cs(connectionString); - std::vector allCoordinators = wait(cs.tryResolveHostnames()); + std::vector allCoordinators = co_await cs.tryResolveHostnames(); ASSERT(allCoordinators.size() == 1 && std::find(allCoordinators.begin(), allCoordinators.end(), address) != allCoordinators.end()); - - return Void(); } TEST_CASE("/flow/FlatBuffers/LeaderInfo") { @@ -494,27 +492,26 @@ std::string ClientLeaderRegInterface::getAddressString() const { // Nominee is the worker among all workers that are considered as leader by one coordinator // This function contacts a coordinator coord to ask who is its nominee. -ACTOR Future monitorNominee(Key key, - ClientLeaderRegInterface coord, - AsyncTrigger* nomineeChange, - Optional* info) { - loop { - state Optional li; - wait(Future(Void())); // Make sure we weren't cancelled +Future monitorNominee(Key key, + ClientLeaderRegInterface coord, + AsyncTrigger* nomineeChange, + Optional* info) { + while (true) { + Optional li; + co_await Future(Void()); // Make sure we weren't cancelled if (coord.hostname.present()) { - wait(store(li, - retryGetReplyFromHostname(GetLeaderRequest(key, info->present() ? info->get().changeID : UID()), - coord.hostname.get(), - WLTOKEN_CLIENTLEADERREG_GETLEADER, - TaskPriority::CoordinationReply))); + li = co_await retryGetReplyFromHostname( + GetLeaderRequest(key, info->present() ? info->get().changeID : UID()), + coord.hostname.get(), + WLTOKEN_CLIENTLEADERREG_GETLEADER, + TaskPriority::CoordinationReply); } else { - wait(store(li, - retryBrokenPromise(coord.getLeader, - GetLeaderRequest(key, info->present() ? info->get().changeID : UID()), - TaskPriority::CoordinationReply))); + li = co_await retryBrokenPromise(coord.getLeader, + GetLeaderRequest(key, info->present() ? info->get().changeID : UID()), + TaskPriority::CoordinationReply); } - wait(Future(Void())); // Make sure we weren't cancelled + co_await Future(Void()); // Make sure we weren't cancelled TraceEvent("GetLeaderReply") .suppressFor(1.0) @@ -527,7 +524,7 @@ ACTOR Future monitorNominee(Key key, nomineeChange->trigger(); if (li.present() && li.get().forward) - wait(Future(Never())); + co_await Future(Never()); } } } @@ -580,18 +577,18 @@ Optional> getLeader(const std::vector monitorLeaderOneGeneration(Reference connRecord, - Reference> outSerializedLeaderInfo, - MonitorLeaderInfo info) { - state ClientCoordinators coordinators(info.intermediateConnRecord); - state AsyncTrigger nomineeChange; - state std::vector> nominees; - state Future allActors; - state Optional> leader; +Future monitorLeaderOneGeneration(Reference connRecord, + Reference> outSerializedLeaderInfo, + MonitorLeaderInfo info) { + ClientCoordinators coordinators(info.intermediateConnRecord); + AsyncTrigger nomineeChange; + std::vector> nominees; + Future allActors; + Optional> leader; nominees.resize(coordinators.clientLeaderServers.size()); - state std::vector> actors; + std::vector> actors; // Ask all coordinators if the worker is considered as a leader (leader nominee) by the coordinator. actors.reserve(coordinators.clientLeaderServers.size()); for (int i = 0; i < coordinators.clientLeaderServers.size(); i++) { @@ -600,7 +597,7 @@ ACTOR Future monitorLeaderOneGeneration(Reference monitorLeaderOneGeneration(ReferencemakeIntermediateRecord( ClusterConnectionString(leader.get().first.serializedInfo.toString())); - return info; + co_return info; } if (connRecord != info.intermediateConnRecord) { if (!info.hasConnected) { @@ -622,7 +619,7 @@ ACTOR Future monitorLeaderOneGeneration(ReferencegetConnectionString().toString()); } - wait(connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString())); + co_await connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString()); info.intermediateConnRecord = connRecord; } @@ -631,36 +628,36 @@ ACTOR Future monitorLeaderOneGeneration(Referenceset(leader.get().first.serializedInfo); } - wait(nomineeChange.onTrigger() || allActors); + co_await (nomineeChange.onTrigger() || allActors); } } -ACTOR Future monitorLeaderInternal(Reference connRecord, - Reference> outSerializedLeaderInfo) { - state MonitorLeaderInfo info(connRecord); - loop { - MonitorLeaderInfo _info = wait(monitorLeaderOneGeneration(connRecord, outSerializedLeaderInfo, info)); +Future monitorLeaderInternal(Reference connRecord, + Reference> outSerializedLeaderInfo) { + MonitorLeaderInfo info(connRecord); + while (true) { + MonitorLeaderInfo _info = co_await monitorLeaderOneGeneration(connRecord, outSerializedLeaderInfo, info); info = _info; } } -ACTOR Future asyncDeserializeClusterInterface(Reference> serializedInfo, - Reference>> outKnownLeader) { - state Reference>> knownLeader( +Future asyncDeserializeClusterInterface(Reference> serializedInfo, + Reference>> outKnownLeader) { + Reference>> knownLeader( new AsyncVar>{}); - state Future deserializer = asyncDeserialize(serializedInfo, knownLeader); - loop { - choose { - when(wait(deserializer)) { - UNSTOPPABLE_ASSERT(false); - } - when(wait(knownLeader->onChange())) { - if (knownLeader->get().present()) { - outKnownLeader->set(knownLeader->get().get().clientInterface); - } else { - outKnownLeader->set(Optional{}); - } + Future deserializer = asyncDeserialize(serializedInfo, knownLeader); + while (true) { + auto res = co_await race(deserializer, knownLeader->onChange()); + if (res.index() == 0) { + UNSTOPPABLE_ASSERT(false); + } else if (res.index() == 1) { + if (knownLeader->get().present()) { + outKnownLeader->set(knownLeader->get().get().clientInterface); + } else { + outKnownLeader->set(Optional{}); } + } else { + UNREACHABLE(); } } } @@ -706,16 +703,16 @@ OpenDatabaseRequest ClientData::getRequest() { return req; } -ACTOR Future getClientInfoFromLeader(Reference>> knownLeader, - ClientData* clientData) { +Future getClientInfoFromLeader(Reference>> knownLeader, + ClientData* clientData) { while (!knownLeader->get().present()) { - wait(knownLeader->onChange()); + co_await knownLeader->onChange(); } - state double lastRequestTime = now(); - state OpenDatabaseRequest req = clientData->getRequest(); + double lastRequestTime = now(); + OpenDatabaseRequest req = clientData->getRequest(); - loop { + while (true) { if (now() - lastRequestTime > CLIENT_KNOBS->MAX_CLIENT_STATUS_AGE) { lastRequestTime = now(); req = clientData->getRequest(); @@ -723,30 +720,30 @@ ACTOR Future getClientInfoFromLeader(ReferenceclientInfo->get().read().id; - choose { - when(ClientDBInfo ni = - wait(brokenPromiseToNever(knownLeader->get().get().clientInterface.openDatabase.getReply(req)))) { - TraceEvent("GetClientInfoFromLeaderGotClientInfo", knownLeader->get().get().clientInterface.id()) - .detail("CommitProxy0", ni.commitProxies.size() ? ni.commitProxies[0].address().toString() : "") - .detail("GrvProxy0", ni.grvProxies.size() ? ni.grvProxies[0].address().toString() : "") - .detail("ClientID", ni.id); - clientData->clientInfo->set(CachedSerialization(ni)); - } - when(wait(knownLeader->onChange())) {} - } - } -} - -ACTOR Future monitorLeaderAndGetClientInfo(Key clusterKey, - std::vector hostnames, - std::vector coordinators, - ClientData* clientData, - Reference>> leaderInfo) { - state std::vector clientLeaderServers; - state AsyncTrigger nomineeChange; - state std::vector> nominees; - state Future allActors; - state Reference>> knownLeader( + auto res = + co_await race(brokenPromiseToNever(knownLeader->get().get().clientInterface.openDatabase.getReply(req)), + knownLeader->onChange()); + if (res.index() == 0) { + ClientDBInfo ni = std::get<0>(std::move(res)); + TraceEvent("GetClientInfoFromLeaderGotClientInfo", knownLeader->get().get().clientInterface.id()) + .detail("CommitProxy0", ni.commitProxies.size() ? ni.commitProxies[0].address().toString() : "") + .detail("GrvProxy0", ni.grvProxies.size() ? ni.grvProxies[0].address().toString() : "") + .detail("ClientID", ni.id); + clientData->clientInfo->set(CachedSerialization(ni)); + } + } +} + +Future monitorLeaderAndGetClientInfo(Key clusterKey, + std::vector hostnames, + std::vector coordinators, + ClientData* clientData, + Reference>> leaderInfo) { + std::vector clientLeaderServers; + AsyncTrigger nomineeChange; + std::vector> nominees; + Future allActors; + Reference>> knownLeader( new AsyncVar>{}); clientLeaderServers.reserve(hostnames.size() + coordinators.size()); @@ -768,7 +765,7 @@ ACTOR Future monitorLeaderAndGetClientInfo(Key clusterKey, actors.push_back(getClientInfoFromLeader(knownLeader, clientData)); allActors = waitForAll(actors); - loop { + while (true) { Optional> leader = getLeader(nominees); TraceEvent("MonitorLeaderAndGetClientInfoLeaderChange") .detail("NewLeader", leader.present() ? leader.get().first.changeID : UID(1, 1)) @@ -782,7 +779,7 @@ ACTOR Future monitorLeaderAndGetClientInfo(Key clusterKey, leaderInfo->set(leader.get().first); TraceEvent("MonitorLeaderAndGetClientInfoForwarding") .detail("NewConnStr", leader.get().first.serializedInfo.toString()); - return Void(); + co_return; } if (leader.get().first.serializedInfo.size()) { @@ -793,7 +790,7 @@ ACTOR Future monitorLeaderAndGetClientInfo(Key clusterKey, leaderInfo->set(leader.get().first); } } - wait(nomineeChange.onTrigger() || allActors); + co_await (nomineeChange.onTrigger() || allActors); } } @@ -837,7 +834,7 @@ void shrinkProxyList(ClientDBInfo& ni, } } -ACTOR Future monitorProxiesOneGeneration( +Future monitorProxiesOneGeneration( Reference connRecord, Reference> clientInfo, Reference>> coordinator, @@ -845,17 +842,17 @@ ACTOR Future monitorProxiesOneGeneration( Reference>>> supportedVersions, Key traceLogGroup, IsInternal internal) { - state ClusterConnectionString cs = info.intermediateConnRecord->getConnectionString(); - state int coordinatorsSize = cs.hostnames.size() + cs.coords.size(); - state int index = 0; - state int successIndex = 0; - state Optional incorrectTime; - state std::vector lastCommitProxyUIDs; - state std::vector lastCommitProxies; - state std::vector lastGrvProxyUIDs; - state std::vector lastGrvProxies; - state std::vector clientLeaderServers; - state bool allConnectionsFailed = false; + ClusterConnectionString cs = info.intermediateConnRecord->getConnectionString(); + int coordinatorsSize = cs.hostnames.size() + cs.coords.size(); + int index = 0; + int successIndex = 0; + Optional incorrectTime; + std::vector lastCommitProxyUIDs; + std::vector lastCommitProxies; + std::vector lastGrvProxyUIDs; + std::vector lastGrvProxies; + std::vector clientLeaderServers; + bool allConnectionsFailed = false; clientLeaderServers.reserve(coordinatorsSize); for (const auto& h : cs.hostnames) { @@ -868,9 +865,9 @@ ACTOR Future monitorProxiesOneGeneration( deterministicRandom()->randomShuffle(clientLeaderServers); - loop { - state ClientLeaderRegInterface clientLeaderServer = clientLeaderServers[index]; - state OpenDatabaseCoordRequest req; + while (true) { + ClientLeaderRegInterface clientLeaderServer = clientLeaderServers[index]; + OpenDatabaseCoordRequest req; req.clusterKey = cs.clusterKey(); req.hostnames = cs.hostnames; @@ -880,9 +877,9 @@ ACTOR Future monitorProxiesOneGeneration( req.traceLogGroup = traceLogGroup; req.internal = internal; - state ClusterConnectionString storedConnectionString; + ClusterConnectionString storedConnectionString; if (connRecord) { - bool upToDate = wait(connRecord->upToDate(storedConnectionString)); + bool upToDate = co_await connRecord->upToDate(storedConnectionString); if (upToDate) { incorrectTime = Optional(); } else if (allConnectionsFailed && storedConnectionString.getNumberOfCoordinators() > 0) { @@ -895,9 +892,9 @@ ACTOR Future monitorProxiesOneGeneration( .detail("ClusterFile", connRecord->toString()) .detail("StoredConnectionString", storedConnectionString.toString()) .detail("CurrentConnectionString", connRecord->getConnectionString().toString()); - wait(connRecord->setAndPersistConnectionString(storedConnectionString)); + co_await connRecord->setAndPersistConnectionString(storedConnectionString); info.intermediateConnRecord = connRecord; - return info; + co_return info; } else { req.issues.push_back_deep(req.issues.arena(), "incorrect_cluster_file_contents"_sr); std::string connectionString = connRecord->getConnectionString().toString(); @@ -916,7 +913,7 @@ ACTOR Future monitorProxiesOneGeneration( incorrectTime = Optional(); } - state Future>> repFuture; + Future>> repFuture; if (clientLeaderServer.hostname.present()) { repFuture = tryGetReplyFromHostname(req, clientLeaderServer.hostname.get(), @@ -934,7 +931,7 @@ ACTOR Future monitorProxiesOneGeneration( // object being created in FlowTransport. Having this peer is a prerequisite to us signaling the AsyncVar. coordinator->setUnconditional(clientLeaderServer); - state ErrorOr> rep = wait(repFuture); + ErrorOr> rep = co_await repFuture; if (rep.present()) { if (rep.get().read().forward.present()) { @@ -944,7 +941,7 @@ ACTOR Future monitorProxiesOneGeneration( info.intermediateConnRecord = connRecord->makeIntermediateRecord( ClusterConnectionString(rep.get().read().forward.get().toString())); ASSERT(info.intermediateConnRecord->getConnectionString().getNumberOfCoordinators() > 0); - return info; + co_return info; } if (connRecord != info.intermediateConnRecord) { if (!info.hasConnected) { @@ -954,7 +951,7 @@ ACTOR Future monitorProxiesOneGeneration( .detail("CurrentConnectionString", info.intermediateConnRecord->getConnectionString().toString()); } - wait(connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString())); + co_await connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString()); info.intermediateConnRecord = connRecord; } @@ -975,31 +972,30 @@ ACTOR Future monitorProxiesOneGeneration( index = (index + 1) % coordinatorsSize; if (index == successIndex) { allConnectionsFailed = true; - wait(delay(CLIENT_KNOBS->COORDINATOR_RECONNECTION_DELAY)); + co_await delay(CLIENT_KNOBS->COORDINATOR_RECONNECTION_DELAY); } } } } -ACTOR Future monitorProxies( - Reference>> connRecord, - Reference> clientInfo, - Reference>> coordinator, - Reference>>> supportedVersions, - Key traceLogGroup, - IsInternal internal) { - state MonitorLeaderInfo info(connRecord->get()); - loop { +Future monitorProxies(Reference>> connRecord, + Reference> clientInfo, + Reference>> coordinator, + Reference>>> supportedVersions, + Key traceLogGroup, + IsInternal internal) { + MonitorLeaderInfo info(connRecord->get()); + while (true) { ASSERT(connRecord->get().isValid()); - choose { - when(MonitorLeaderInfo _info = wait(monitorProxiesOneGeneration( - connRecord->get(), clientInfo, coordinator, info, supportedVersions, traceLogGroup, internal))) { - info = _info; - } - when(wait(connRecord->onChange())) { - info.hasConnected = false; - info.intermediateConnRecord = connRecord->get(); - } + auto res = co_await race( + monitorProxiesOneGeneration( + connRecord->get(), clientInfo, coordinator, info, supportedVersions, traceLogGroup, internal), + connRecord->onChange()); + if (res.index() == 0) { + info = std::get<0>(std::move(res)); + } else if (res.index() == 1) { + info.hasConnected = false; + info.intermediateConnRecord = connRecord->get(); } } } diff --git a/fdbclient/MultiVersionTransaction.actor.cpp b/fdbclient/MultiVersionTransaction.cpp similarity index 94% rename from fdbclient/MultiVersionTransaction.actor.cpp rename to fdbclient/MultiVersionTransaction.cpp index 14c9fdd3bd1..e93bc507d4e 100644 --- a/fdbclient/MultiVersionTransaction.actor.cpp +++ b/fdbclient/MultiVersionTransaction.cpp @@ -1,5 +1,5 @@ /* - * MultiVersionTransaction.actor.cpp + * MultiVersionTransaction.cpp * * This source file is part of the FoundationDB open source project * @@ -34,11 +34,11 @@ #include "fdbclient/FDBOptions.g.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/GenericManagementAPI.actor.h" +#include "fdbclient/GenericManagementAPI.h" #include "fdbclient/MultiVersionTransaction.h" #include "fdbclient/MultiVersionAssignmentVars.h" #include "fdbclient/ClientVersion.h" -#include "fdbclient/LocalClientAPI.h" +#include "LocalClientAPI.h" #include "fdbclient/VersionVector.h" #include "flow/ThreadPrimitives.h" @@ -46,14 +46,11 @@ #include "flow/Platform.h" #include "flow/ProtocolVersion.h" #include "flow/UnitTest.h" -#include "flow/Trace.h" #ifdef __unixish__ #include #endif // __unixish__ -#include "flow/actorcompiler.h" // This must be the last #include. - #ifdef FDBCLIENT_NATIVEAPI_ACTOR_H #error "MVC should not depend on the Native API" #endif @@ -412,7 +409,7 @@ ThreadFuture DLDatabase::onReady() { Reference DLDatabase::createTransaction() { FdbCApi::FDBTransaction* tr; throwIfError(api->databaseCreateTransaction(db, &tr)); - return Reference(new DLTransaction(api, tr)); + return makeReference(api, tr); } void DLDatabase::setOption(FDBDatabaseOptions::Option option, Optional value) { @@ -808,7 +805,7 @@ Reference DLApi::createDatabase(const char* clusterFilePath) { if (headerVersion >= 610) { FdbCApi::FDBDatabase* db; throwIfError(api->createDatabase(clusterFilePath, &db)); - return Reference(new DLDatabase(api, db)); + return makeReference(api, db); } else { return DLApi::createDatabase609(clusterFilePath); } @@ -821,7 +818,7 @@ Reference DLApi::createDatabaseFromConnectionString(const char* conne FdbCApi::FDBDatabase* db; throwIfError(api->createDatabaseFromConnectionString(connectionString, &db)); - return Reference(new DLDatabase(api, db)); + return makeReference(api, db); } void DLApi::addNetworkThreadCompletionHook(void (*hook)(void*), void* hookParameter) { @@ -856,16 +853,16 @@ void MultiVersionTransaction::updateTransaction(bool setPersistentOptions) { // need to set it. if (setPersistentOptions || !newTr.transaction) { Optional timeout; - for (auto const& option : persistentOptions) { - if (option.first == FDBTransactionOptions::TIMEOUT) { - timeout = option.second.castTo(); + for (auto const& [option, value] : persistentOptions) { + if (option == FDBTransactionOptions::TIMEOUT) { + timeout = value.castTo(); } else if (newTr.transaction) { - newTr.transaction->setOption(option.first, option.second.castTo()); + newTr.transaction->setOption(option, value.castTo()); } } if (newTr.transaction) { - for (auto const& option : sensitivePersistentOptions) { - newTr.transaction->setOption(option.first, option.second.castTo()); + for (auto const& [option, value] : sensitivePersistentOptions) { + newTr.transaction->setOption(option, value.castTo()); } } @@ -1163,14 +1160,13 @@ ThreadFuture MultiVersionTransaction::onError(Error const& e) { // Waits for the specified duration and signals the assignment variable with a timed out error // This will be canceled if a new timeout is set, in which case the tsav will not be signaled. -ACTOR Future timeoutImpl(Reference> tsav, double duration) { - state double endTime = now() + duration; +Future timeoutImpl(Reference> tsav, double duration) { + double endTime = now() + duration; while (now() < endTime) { - wait(delayUntil(std::min(endTime + 0.0001, now() + CLIENT_KNOBS->TRANSACTION_TIMEOUT_DELAY_INTERVAL))); + co_await delayUntil(std::min(endTime + 0.0001, now() + CLIENT_KNOBS->TRANSACTION_TIMEOUT_DELAY_INTERVAL)); } tsav->trySendError(transaction_timed_out()); - return Void(); } namespace { @@ -1375,13 +1371,13 @@ MultiVersionDatabase::~MultiVersionDatabase() { // Create a MultiVersionDatabase that wraps an already created IDatabase object // For internal use in testing Reference MultiVersionDatabase::debugCreateFromExistingDatabase(Reference db) { - return Reference(new MultiVersionDatabase( - MultiVersionApi::api, 0, ClusterConnectionRecord::fromConnectionString(""), db, db, false)); + return makeReference( + MultiVersionApi::api, 0, ClusterConnectionRecord::fromConnectionString(""), db, db, false); } Reference MultiVersionDatabase::createTransaction() { - return Reference( - new MultiVersionTransaction(Reference::addRef(this), dbState->transactionDefaultOptions)); + return makeReference(Reference::addRef(this), + dbState->transactionDefaultOptions); } void MultiVersionDatabase::setOption(FDBDatabaseOptions::Option option, Optional value) { @@ -1663,19 +1659,19 @@ void MultiVersionDatabase::DatabaseState::updateDatabase(Reference ne // Reapply database options on the new database if (newDb) { optionLock.enter(); - for (auto option : options) { + for (const auto& [option, value] : options) { try { // In practice, this will set a deferred error instead of throwing. If that happens, the database // will be unusable (attempts to use it will throw errors). - newDb->setOption(option.first, option.second.castTo()); + newDb->setOption(option, value.castTo()); } catch (Error& e) { optionLock.leave(); // If we can't set all of the options on a cluster, we abandon the client TraceEvent(SevError, "ClusterVersionChangeOptionError") .error(e) - .detail("Option", option.first) - .detail("OptionValue", option.second) + .detail("Option", option) + .detail("OptionValue", value) .detail("LibPath", client->libPath); client->failed = true; MultiVersionApi::api->updateSupportedVersions(); @@ -1797,7 +1793,7 @@ Standalone MultiVersionDatabase::DatabaseState::getClientStatus( statusObj["InitializationError"] = initializationError.code(); } json_spirit::mArray clientArr; - for (auto [protocolVersion, client] : this->clients) { + for (const auto& [protocolVersion, client] : this->clients) { json_spirit::mObject clientDesc; clientDesc["ProtocolVersion"] = format("%llx", client->protocolVersion.version()); clientDesc["ReleaseVersion"] = client->releaseVersion; @@ -1912,13 +1908,21 @@ void MultiVersionApi::useFutureProtocolVersion() { } namespace { -void validateOption(Optional value, bool canBePresent, bool canBeAbsent, bool canBeEmpty = true) { + +FDB_BOOLEAN_PARAM(CanBePresent); +FDB_BOOLEAN_PARAM(CanBeAbsent); +FDB_BOOLEAN_PARAM(CanBeEmpty); + +void validateOption(Optional value, + CanBePresent canBePresent, + CanBeAbsent canBeAbsent, + CanBeEmpty canBeEmpty = CanBeEmpty::True) { ASSERT(canBePresent || canBeAbsent); - if (!canBePresent && value.present() && (!canBeEmpty || value.get().size() > 0)) { + if (!canBePresent && value.present() && (!canBeEmpty || !value.get().empty())) { throw invalid_option_value(); } - if (!canBeAbsent && (!value.present() || (!canBeEmpty && value.get().size() == 0))) { + if (!canBeAbsent && (!value.present() || (!canBeEmpty && value.get().empty()))) { throw invalid_option_value(); } } @@ -1959,7 +1963,7 @@ void MultiVersionApi::addExternalLibrary(std::string path, bool useFutureVersion // library. threadCount = std::max(threadCount, 1); - if (externalClientDescriptions.count(filename) == 0) { + if (!externalClientDescriptions.contains(filename)) { TraceEvent("AddingExternalClient").detail("LibraryPath", filename).detail("UseFutureVersion", useFutureVersion); externalClientDescriptions.emplace(std::make_pair(filename, ClientDesc(path, true, useFutureVersion))); } @@ -1978,9 +1982,9 @@ void MultiVersionApi::addExternalLibraryDirectory(std::string path) { // library. threadCount = std::max(threadCount, 1); - for (auto filename : files) { + for (const auto& filename : files) { std::string lib = abspath(joinPath(path, filename)); - if (externalClientDescriptions.count(filename) == 0) { + if (!externalClientDescriptions.contains(filename)) { TraceEvent("AddingExternalClient").detail("LibraryPath", filename); externalClientDescriptions.emplace(std::make_pair(filename, ClientDesc(lib, true, false))); } @@ -2113,19 +2117,19 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option, } if (option == FDBNetworkOptions::DISABLE_MULTI_VERSION_CLIENT_API) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); disableMultiVersionClientApi(); } else if (option == FDBNetworkOptions::CALLBACKS_ON_EXTERNAL_THREADS) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); setCallbacksOnExternalThreads(); } else if (option == FDBNetworkOptions::EXTERNAL_CLIENT_LIBRARY) { - validateOption(value, true, false, false); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::False); addExternalLibrary(abspath(value.get().toString()), false); } else if (option == FDBNetworkOptions::EXTERNAL_CLIENT_DIRECTORY) { - validateOption(value, true, false, false); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::False); addExternalLibraryDirectory(value.get().toString()); } else if (option == FDBNetworkOptions::DISABLE_LOCAL_CLIENT) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); disableLocalClient(); } else if (option == FDBNetworkOptions::SUPPORTED_CLIENT_VERSIONS) { ASSERT(value.present()); @@ -2145,7 +2149,7 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option, disableBypass = true; } else if (option == FDBNetworkOptions::CLIENT_THREADS_PER_VERSION) { MutexHolder holder(lock); - validateOption(value, true, false, false); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::False); if (networkStartSetup) { throw invalid_option(); } @@ -2156,13 +2160,13 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option, threadCount = extractIntOption(value, 1, 1); #endif } else if (option == FDBNetworkOptions::CLIENT_TMP_DIR) { - validateOption(value, true, false, false); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::False); tmpDir = abspath(value.get().toString()); } else if (option == FDBNetworkOptions::FUTURE_VERSION_CLIENT_LIBRARY) { - validateOption(value, true, false, false); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::False); addExternalLibrary(abspath(value.get().toString()), true); } else if (option == FDBNetworkOptions::TRACE_FILE_IDENTIFIER) { - validateOption(value, true, false, true); + validateOption(value, CanBePresent::True, CanBeAbsent::False, CanBeEmpty::True); traceFileIdentifier = value.get().toString(); { MutexHolder holder(lock); @@ -2171,16 +2175,16 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option, localClient->api->setNetworkOption(option, value); } } else if (option == FDBNetworkOptions::TRACE_SHARE_AMONG_CLIENT_THREADS) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); traceShareBaseNameAmongThreads = true; } else if (option == FDBNetworkOptions::IGNORE_EXTERNAL_CLIENT_FAILURES) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); ignoreExternalClientFailures = true; } else if (option == FDBNetworkOptions::FAIL_INCOMPATIBLE_CLIENT) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); failIncompatibleClient = true; } else if (option == FDBNetworkOptions::RETAIN_CLIENT_LIBRARY_COPIES) { - validateOption(value, false, true); + validateOption(value, CanBePresent::False, CanBeAbsent::True); retainClientLibCopies = true; } else { forwardOption = true; @@ -2233,8 +2237,7 @@ void MultiVersionApi::setupNetwork() { } if (!bypassMultiClientApi) { - transportId = - (uint64_t(uint32_t(platform::getRandomSeed())) << 32) ^ uint32_t(platform::getRandomSeed()); + transportId = platform::getRandomSeed(); if (transportId <= 1) transportId += 2; localClient->api->setNetworkOption(FDBNetworkOptions::EXTERNAL_CLIENT_TRANSPORT_ID, @@ -2246,22 +2249,22 @@ void MultiVersionApi::setupNetwork() { ignoreExternalClientFailures = true; } - for (auto i : externalClientDescriptions) { + for (const auto& i : externalClientDescriptions) { std::string path = i.second.libPath; std::string filename = basename(path); bool useFutureVersion = i.second.useFutureVersion; // Copy external lib for each thread - if (externalClients.count(filename) == 0) { + if (!externalClients.contains(filename)) { externalClients[filename] = {}; auto libCopies = copyExternalLibraryPerThread(path); for (int idx = 0; idx < libCopies.size(); ++idx) { bool unlinkOnLoad = libCopies[idx].second && !retainClientLibCopies; - externalClients[filename].push_back(Reference( - new ClientInfo(new DLApi(libCopies[idx].first, unlinkOnLoad /*unlink on load*/), - path, - useFutureVersion, - idx))); + externalClients[filename].push_back( + makeReference(new DLApi(libCopies[idx].first, unlinkOnLoad /*unlink on load*/), + path, + useFutureVersion, + idx)); } } } @@ -2293,8 +2296,8 @@ void MultiVersionApi::setupNetwork() { MutexHolder holder(lock); runOnExternalClientsAllThreads( [this, transportId, baseTraceFileId](Reference client) { - for (auto option : options) { - client->api->setNetworkOption(option.first, option.second.castTo()); + for (const auto& [option, value] : options) { + client->api->setNetworkOption(option, value.castTo()); } client->api->setNetworkOption(FDBNetworkOptions::EXTERNAL_CLIENT_TRANSPORT_ID, std::to_string(transportId)); @@ -2429,8 +2432,7 @@ Reference MultiVersionApi::createDatabase(ClusterConnectionRecord con lock.leave(); Reference localDb = connectionRecord.createDatabase(localClient->api); - return Reference( - new MultiVersionDatabase(this, threadIdx, connectionRecord, Reference(), localDb)); + return makeReference(this, threadIdx, connectionRecord, Reference(), localDb); } lock.leave(); @@ -2441,8 +2443,7 @@ Reference MultiVersionApi::createDatabase(ClusterConnectionRecord con if (bypassMultiClientApi) { return localDb; } else { - return Reference( - new MultiVersionDatabase(this, 0, connectionRecord, Reference(), localDb)); + return makeReference(this, 0, connectionRecord, Reference(), localDb); } } @@ -2479,13 +2480,13 @@ void MultiVersionApi::updateSupportedVersions() { } // Must be called from the main thread -ACTOR Future updateClusterSharedStateMapImpl(MultiVersionApi* self, - ClusterConnectionRecord connectionRecord, - ProtocolVersion dbProtocolVersion, - Reference db) { +Future updateClusterSharedStateMapImpl(MultiVersionApi* self, + ClusterConnectionRecord connectionRecord, + ProtocolVersion dbProtocolVersion, + Reference db) { // The cluster ID will be the connection record string (either a filename or the connection string itself) // in versions before we could read the cluster ID. - state std::string clusterId = connectionRecord.toString(); + std::string clusterId = connectionRecord.toString(); if (self->clusterSharedStateMap.find(clusterId) == self->clusterSharedStateMap.end()) { TraceEvent("CreatingClusterSharedState") @@ -2501,19 +2502,19 @@ ACTOR Future updateClusterSharedStateMapImpl(MultiVersionApi* self, .detail("ClusterId", clusterId) .detail("ProtocolVersionExpected", dbProtocolVersion) .detail("ProtocolVersionFound", sharedStateInfo.protocolVersion); - return clusterId; + co_return clusterId; } TraceEvent("SettingClusterSharedState") .detail("ClusterId", clusterId) .detail("ProtocolVersion", dbProtocolVersion); - state ThreadFuture entry = sharedStateInfo.sharedStateFuture; - DatabaseSharedState* sharedState = wait(safeThreadFutureToFuture(entry)); + ThreadFuture entry = sharedStateInfo.sharedStateFuture; + DatabaseSharedState* sharedState = co_await safeThreadFutureToFuture(entry); db->setSharedState(sharedState); } - return clusterId; + co_return clusterId; } // Must be called from the main thread @@ -2593,13 +2594,13 @@ void MultiVersionApi::loadEnvironmentVariableNetworkOptions() { return; } - for (auto option : FDBNetworkOptions::optionInfo) { + for (const auto& option : FDBNetworkOptions::optionInfo) { if (!option.second.hidden) { std::string valueStr; try { if (platform::getEnvironmentVar(("FDB_NETWORK_OPTION_" + option.second.name).c_str(), valueStr)) { FDBOptionInfo::ParamType curParamType = option.second.paramType; - for (auto value : parseOptionValues(valueStr)) { + for (const auto& value : parseOptionValues(valueStr)) { Standalone currentValue; int64_t intParamVal; if (curParamType == FDBOptionInfo::ParamType::Int) { @@ -2622,7 +2623,7 @@ void MultiVersionApi::loadEnvironmentVariableNetworkOptions() { } { // lock scope MutexHolder holder(lock); - if (setEnvOptions[option.first].count(currentValue) == 0) { + if (!setEnvOptions[option.first].contains(currentValue)) { setNetworkOptionInternal(option.first, currentValue); setEnvOptions[option.first].insert(currentValue); } @@ -2699,7 +2700,7 @@ TEST_CASE("/fdbclient/multiversionclient/EnvironmentVariableParsing") { ASSERT(vals.size() == 1 && vals[0] == "abcde"); vals = parseOptionValues(""); - ASSERT(vals.size() == 1 && vals[0] == ""); + ASSERT(vals.size() == 1 && vals[0].empty()); vals = parseOptionValues("a:b:c:d:e"); ASSERT(vals.size() == 5 && vals[0] == "a" && vals[1] == "b" && vals[2] == "c" && vals[3] == "d" && vals[4] == "e"); @@ -2708,13 +2709,13 @@ TEST_CASE("/fdbclient/multiversionclient/EnvironmentVariableParsing") { ASSERT(vals.size() == 3 && vals[0] == "\\a:" && vals[1] == ":b" && vals[2] == "\\"); vals = parseOptionValues("abcd:"); - ASSERT(vals.size() == 2 && vals[0] == "abcd" && vals[1] == ""); + ASSERT(vals.size() == 2 && vals[0] == "abcd" && vals[1].empty()); vals = parseOptionValues(":abcd"); - ASSERT(vals.size() == 2 && vals[0] == "" && vals[1] == "abcd"); + ASSERT(vals.size() == 2 && vals[0].empty() && vals[1] == "abcd"); vals = parseOptionValues(":"); - ASSERT(vals.size() == 2 && vals[0] == "" && vals[1] == ""); + ASSERT(vals.size() == 2 && vals[0].empty() && vals[1].empty()); try { vals = parseOptionValues("\\x"); @@ -2739,7 +2740,7 @@ class ValidateFuture final : public ThreadCallback { } void error(const Error& e, int& userParam) override { - ASSERT(legalErrors.count(e.code()) > 0 || + ASSERT(legalErrors.contains(e.code()) || (f.isError() && expectedValue.isError() && f.getError().code() == expectedValue.getError().code())); delete this; } @@ -2852,33 +2853,25 @@ THREAD_FUNC cancel(void* arg) { THREAD_RETURN; } -ACTOR Future checkUndestroyedFutures(std::vector*> undestroyed) { - state int fNum; - state ThreadSingleAssignmentVar* f; - state double start = now(); +Future checkUndestroyedFutures(std::vector*> undestroyed) { + double start = now(); - for (fNum = 0; fNum < undestroyed.size(); ++fNum) { - f = undestroyed[fNum]; - - while (!f->isReady() && start + 5 >= now()) { - wait(delay(1.0)); + for (auto* undestroyedFuture : undestroyed) { + while (!undestroyedFuture->isReady() && start + 5 >= now()) { + co_await delay(1.0); } - ASSERT(f->isReady()); + ASSERT(undestroyedFuture->isReady()); } - wait(delay(1.0)); - - for (fNum = 0; fNum < undestroyed.size(); ++fNum) { - f = undestroyed[fNum]; + co_await delay(1.0); - ASSERT_EQ(f->debugGetReferenceCount(), 1); - ASSERT(f->isReady()); + for (auto* undestroyedFuture : undestroyed) { + ASSERT_EQ(undestroyedFuture->debugGetReferenceCount(), 1); + ASSERT(undestroyedFuture->isReady()); - f->cancel(); + undestroyedFuture->cancel(); } - - return Void(); } // Common code for tests of single assignment vars. Tests both correctness and thread safety. @@ -2959,7 +2952,7 @@ THREAD_FUNC runSingleAssignmentVarTest(void* arg) { struct AbortableTest { static FutureInfo createThreadFuture(FutureInfo f) { - ThreadSingleAssignmentVar* abort = new ThreadSingleAssignmentVar(); + auto* abort = new ThreadSingleAssignmentVar(); abort->addref(); // this leaks if abort is never set auto newFuture = @@ -2976,16 +2969,14 @@ struct AbortableTest { }; TEST_CASE("fdbclient/multiversionclient/AbortableSingleAssignmentVar") { - state volatile bool done = false; - state THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); + volatile bool done = false; + THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); while (!done) { - wait(delay(1.0)); + co_await delay(1.0); } waitThread(thread); - - return Void(); } class CAPICallback final : public ThreadCallback { @@ -3030,7 +3021,7 @@ struct DLTest { // Functions needed for DLSingleAssignmentVar api->futureSetCallback = [](FdbCApi::FDBFuture* f, FdbCApi::FDBCallback callback, void* callbackParameter) { try { - CAPICallback* cb = new CAPICallback(callback, f, callbackParameter); + auto* cb = new CAPICallback(callback, f, callbackParameter); int ignore; ((ThreadSingleAssignmentVarBase*)f)->callOrSetAsCallback(cb, ignore, 0); return FdbCApi::fdb_error_t(error_code_success); @@ -3053,13 +3044,13 @@ struct DLTest { }; TEST_CASE("fdbclient/multiversionclient/DLSingleAssignmentVar") { - state volatile bool done = false; + volatile bool done = false; MultiVersionApi::api->callbackOnMainThread = true; - state THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); + THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); while (!done) { - wait(delay(1.0)); + co_await delay(1.0); } waitThread(thread); @@ -3069,12 +3060,10 @@ TEST_CASE("fdbclient/multiversionclient/DLSingleAssignmentVar") { thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); while (!done) { - wait(delay(1.0)); + co_await delay(1.0); } waitThread(thread); - - return Void(); } struct MapTest { @@ -3083,7 +3072,7 @@ struct MapTest { newFuture.legalErrors = f.legalErrors; newFuture.future = mapThreadFuture(f.future, [f, newFuture](ErrorOr v) { if (v.isError()) { - ASSERT(f.legalErrors.count(v.getError().code()) > 0 || + ASSERT(f.legalErrors.contains(v.getError().code()) || (f.expectedValue.isError() && f.expectedValue.getError().code() == v.getError().code())); } else { ASSERT(!f.expectedValue.isError() && f.expectedValue.get() == v.get()); @@ -3097,16 +3086,14 @@ struct MapTest { }; TEST_CASE("fdbclient/multiversionclient/MapSingleAssignmentVar") { - state volatile bool done = false; - state THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); + volatile bool done = false; + THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); while (!done) { - wait(delay(1.0)); + co_await delay(1.0); } waitThread(thread); - - return Void(); } struct FlatMapTest { @@ -3118,7 +3105,7 @@ struct FlatMapTest { f.future, [f, mapFuture](ErrorOr v) { if (v.isError()) { - ASSERT(f.legalErrors.count(v.getError().code()) > 0 || + ASSERT(f.legalErrors.contains(v.getError().code()) || (f.expectedValue.isError() && f.expectedValue.getError().code() == v.getError().code())); } else { ASSERT(!f.expectedValue.isError() && f.expectedValue.get() == v.get()); @@ -3136,14 +3123,12 @@ struct FlatMapTest { }; TEST_CASE("fdbclient/multiversionclient/FlatMapSingleAssignmentVar") { - state volatile bool done = false; - state THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); + volatile bool done = false; + THREAD_HANDLE thread = g_network->startThread(runSingleAssignmentVarTest, (void*)&done); while (!done) { - wait(delay(1.0)); + co_await delay(1.0); } waitThread(thread); - - return Void(); } diff --git a/fdbclient/MutationLogReader.actor.cpp b/fdbclient/MutationLogReader.cpp similarity index 73% rename from fdbclient/MutationLogReader.actor.cpp rename to fdbclient/MutationLogReader.cpp index ce9b077f269..73f260aa46c 100644 --- a/fdbclient/MutationLogReader.actor.cpp +++ b/fdbclient/MutationLogReader.cpp @@ -1,5 +1,5 @@ /* - * MutationLogReader.actor.cpp + * MutationLogReader.cpp * * This source file is part of the FoundationDB open source project * @@ -18,11 +18,10 @@ * limitations under the License. */ -#include "fdbclient/MutationLogReader.actor.h" +#include "fdbclient/MutationLogReader.h" #include "fdbrpc/simulator.h" #include "flow/UnitTest.h" #include "flow/flow.h" -#include "flow/actorcompiler.h" // This must be the last #include. namespace { @@ -63,28 +62,29 @@ Future PipelinedReader::getNext(Database cx) { return getNext_impl(this, cx); } -ACTOR Future PipelinedReader::getNext_impl(PipelinedReader* self, Database cx) { - state Transaction tr(cx); +Future PipelinedReader::getNext_impl(PipelinedReader* self, Database cx) { + Transaction tr(cx); - state GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, - (g_network->isSimulated() && !g_simulator->speedUpSimulation) - ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES - : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); + GetRangeLimits limits(GetRangeLimits::ROW_LIMIT_UNLIMITED, + (g_network->isSimulated() && !g_simulator->speedUpSimulation) + ? CLIENT_KNOBS->BACKUP_SIMULATED_LIMIT_BYTES + : CLIENT_KNOBS->BACKUP_GET_RANGE_LIMIT_BYTES); - state Key begin = versionToKey(self->currentBeginVersion, self->prefix); - state Key end = versionToKey(self->endVersion, self->prefix); + Key begin = versionToKey(self->currentBeginVersion, self->prefix); + Key end = versionToKey(self->endVersion, self->prefix); - loop { + while (true) { // Get the lock - wait(self->readerLimit.take()); + co_await self->readerLimit.take(); // Read begin to end forever until successful - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr.setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult kvs = wait(tr.getRange(KeyRangeRef(begin, end), limits)); + RangeResult kvs = co_await tr.getRange(KeyRangeRef(begin, end), limits); // No more results, send end of stream if (!kvs.empty()) { @@ -100,20 +100,21 @@ ACTOR Future PipelinedReader::getNext_impl(PipelinedReader* self, Database if (!kvs.more) { self->reads.sendError(end_of_stream()); - return Void(); + co_return; } begin = kvs.getReadThrough(); break; } catch (Error& e) { - if (e.code() == error_code_transaction_too_old) { - // We are using this transaction until it's too old and then resetting to a fresh one, - // so we don't need to delay. - tr.fullReset(); - } else { - wait(tr.onError(e)); - } + err = e; + } + if (err.code() == error_code_transaction_too_old) { + // We are using this transaction until it's too old and then resetting to a fresh one, + // so we don't need to delay. + tr.fullReset(); + } else { + co_await tr.onError(err); } } } @@ -121,11 +122,11 @@ ACTOR Future PipelinedReader::getNext_impl(PipelinedReader* self, Database } // namespace mutation_log_reader -ACTOR Future MutationLogReader::initializePQ(MutationLogReader* self) { - state int h; +Future MutationLogReader::initializePQ(MutationLogReader* self) { + int h{ 0 }; for (h = 0; h < 256; ++h) { try { - mutation_log_reader::RangeResultBlock front = waitNext(self->pipelinedReaders[h]->reads.getFuture()); + mutation_log_reader::RangeResultBlock front = co_await self->pipelinedReaders[h]->reads.getFuture(); self->priorityQueue.push(front); } catch (Error& e) { if (e.code() != error_code_end_of_stream) { @@ -134,31 +135,30 @@ ACTOR Future MutationLogReader::initializePQ(MutationLogReader* self) { ++self->finished; } } - return Void(); } Future> MutationLogReader::getNext() { return getNext_impl(this); } -ACTOR Future> MutationLogReader::getNext_impl(MutationLogReader* self) { - loop { +Future> MutationLogReader::getNext_impl(MutationLogReader* self) { + while (true) { if (self->finished == 256) { - state int i; + int i{ 0 }; for (i = 0; i < self->pipelinedReaders.size(); ++i) { - wait(self->pipelinedReaders[i]->done()); + co_await self->pipelinedReaders[i]->done(); } throw end_of_stream(); } mutation_log_reader::RangeResultBlock top = self->priorityQueue.top(); self->priorityQueue.pop(); uint8_t hash = top.hash; - state Standalone ret = top.consume(); + Standalone ret = top.consume(); if (top.empty()) { self->pipelinedReaders[(int)hash]->release(); try { mutation_log_reader::RangeResultBlock next = - waitNext(self->pipelinedReaders[(int)hash]->reads.getFuture()); + co_await self->pipelinedReaders[(int)hash]->reads.getFuture(); self->priorityQueue.push(next); } catch (Error& e) { if (e.code() == error_code_end_of_stream) { @@ -170,8 +170,8 @@ ACTOR Future> MutationLogReader::getNext_impl(Mutatio } else { self->priorityQueue.push(top); } - if (ret.size() != 0) { - return ret; + if (!ret.empty()) { + co_return ret; } } } diff --git a/fdbclient/NameLineage.cpp b/fdbclient/NameLineage.cpp index 466b5ecb95b..ae68d7360ca 100644 --- a/fdbclient/NameLineage.cpp +++ b/fdbclient/NameLineage.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbclient/NameLineage.h" +#include "NameLineage.h" namespace { NameLineageCollector nameLineageCollector; diff --git a/fdbclient/include/fdbclient/NameLineage.h b/fdbclient/NameLineage.h similarity index 100% rename from fdbclient/include/fdbclient/NameLineage.h rename to fdbclient/NameLineage.h diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 7a0b24a56b6..58b23c4d625 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -52,19 +53,17 @@ #include "fdbclient/CoordinationInterface.h" #include "fdbclient/CommitTransaction.h" #include "fdbclient/DatabaseContext.h" -#include "fdbclient/GlobalConfig.actor.h" -#include "fdbclient/IKnobCollection.h" +#include "fdbclient/GlobalConfig.h" #include "fdbclient/JsonBuilder.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/KeyRangeMap.h" -#include "fdbclient/ManagementAPI.actor.h" -#include "fdbclient/NameLineage.h" +#include "fdbclient/ManagementAPI.h" +#include "NameLineage.h" #include "fdbclient/CommitProxyInterface.h" #include "fdbclient/MonitorLeader.h" #include "fdbclient/MutationList.h" -#include "fdbclient/ParallelStream.actor.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/SpecialKeySpace.h" #include "fdbclient/StorageServerInterface.h" #include "fdbclient/SystemData.h" #include "fdbclient/TransactionLineage.h" @@ -76,6 +75,7 @@ #include "fdbrpc/sim_validation.h" #include "flow/Arena.h" #include "flow/ActorCollection.h" +#include "flow/CoroUtils.h" #include "flow/DeterministicRandom.h" #include "flow/Error.h" #include "flow/FastRef.h" @@ -88,12 +88,14 @@ #include "flow/Knobs.h" #include "flow/Platform.h" #include "flow/SystemMonitor.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "fdbclient/Tracing.h" #include "flow/UnitTest.h" #include "flow/network.h" #include "flow/serialize.h" +#include "ProxyLoadBalance.h" + #ifdef ADDRESS_SANITIZER #include #endif @@ -139,16 +141,12 @@ void delref(DatabaseContext* ptr) { ptr->delref(); } -ACTOR Future refreshTransaction(DatabaseContext* self, Transaction* tr) { +Future refreshTransaction(DatabaseContext* self, Transaction* tr) { *tr = Transaction(); - wait(delay(0)); // Give ourselves the chance to get cancelled if self was destroyed + co_await delay(0); // Give ourselves the chance to get cancelled if self was destroyed *tr = Transaction(Database(Reference::addRef(self))); - return Void(); } -// FIXME: get a prototype in a header file, ugh. -ACTOR Future getWorkerInterfaces(Reference clusterRecord); - Optional DatabaseContext::getCachedLocation(const KeyRef& key, Reverse isBackward) { Arena arena; @@ -384,8 +382,7 @@ Future DatabaseContext::onConnected() const { return connected; } -ACTOR static Future switchConnectionRecordImpl(Reference connRecord, - DatabaseContext* self) { +static Future switchConnectionRecordImpl(Reference connRecord, DatabaseContext* self) { CODE_PROBE(true, "Switch connection file"); TraceEvent("SwitchConnectionRecord") .detail("ClusterFile", connRecord->toString()) @@ -406,23 +403,25 @@ ACTOR static Future switchConnectionRecordImpl(ReferenceclientInfo->set(clearedClientInfo); self->connectionRecord->set(connRecord); - state Database db(Reference::addRef(self)); - state Transaction tr(db); - loop { + Database db(Reference::addRef(self)); + Transaction tr(db); + while (true) { tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE); + Error err; try { TraceEvent("SwitchConnectionRecordAttemptingGRV").log(); - Version v = wait(tr.getReadVersion()); + Version v = co_await tr.getReadVersion(); TraceEvent("SwitchConnectionRecordGotRV") .detail("ReadVersion", v) .detail("MinAcceptableReadVersion", self->minAcceptableReadVersion); ASSERT(self->minAcceptableReadVersion != std::numeric_limits::max()); self->connectionFileChangedTrigger.trigger(); - return Void(); + co_return; } catch (Error& e) { - TraceEvent("SwitchConnectionRecordError").detail("Error", e.what()); - wait(tr.onError(e)); + err = e; } + TraceEvent("SwitchConnectionRecordError").detail("Error", err.what()); + co_await tr.onError(err); } } @@ -733,9 +732,9 @@ void setNetworkOption(FDBNetworkOptions::Option option, Optional valu std::string knobValueString = optionValue.substr(eq + 1); try { - auto knobValue = IKnobCollection::parseKnobValue(knobName, knobValueString, IKnobCollection::Type::CLIENT); + auto knobValue = parseClientKnobValue(knobName, knobValueString); if (g_network) { - IKnobCollection::getMutableGlobalKnobCollection().setKnob(knobName, knobValue); + setClientKnob(knobName, knobValue); } else { networkOptions.knobs[knobName] = knobValue; } @@ -860,10 +859,10 @@ void setNetworkOption(FDBNetworkOptions::Option option, Optional valu } // update the network busyness on a 1s cadence -ACTOR Future monitorNetworkBusyness() { - state double prevTime = now(); - loop { - wait(delay(CLIENT_KNOBS->NETWORK_BUSYNESS_MONITOR_INTERVAL, TaskPriority::FlushTrace)); +Future monitorNetworkBusyness() { + double prevTime = now(); + while (true) { + co_await delay(CLIENT_KNOBS->NETWORK_BUSYNESS_MONITOR_INTERVAL, TaskPriority::FlushTrace); double elapsed = now() - prevTime; // get elapsed time from last execution prevTime = now(); struct NetworkMetrics::PriorityStats& tracker = g_network->networkInfo.metrics.starvationTrackerNetworkBusyness; @@ -900,9 +899,9 @@ ACTOR Future monitorNetworkBusyness() { } static void setupGlobalKnobs() { - IKnobCollection::setGlobalKnobCollection(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False); + resetClientKnobs(Randomize::False, IsSimulated::False); for (const auto& [knobName, knobValue] : networkOptions.knobs) { - IKnobCollection::getMutableGlobalKnobCollection().setKnob(knobName, knobValue); + setClientKnob(knobName, knobValue); } } @@ -1007,13 +1006,13 @@ bool DatabaseContext::isCurrentGrvProxy(UID proxyId) const { // Actor which will wait until the MultiInterface returned by the DatabaseContext cx is not // nullptr -ACTOR Future> getCommitProxiesFuture(DatabaseContext* cx, - UseProvisionalProxies useProvisionalProxies) { - loop { +Future> getCommitProxiesFuture(DatabaseContext* cx, + UseProvisionalProxies useProvisionalProxies) { + while (true) { Reference commitProxies = cx->getCommitProxies(useProvisionalProxies); if (commitProxies) - return commitProxies; - wait(cx->onProxiesChanged()); + co_return commitProxies; + co_await cx->onProxiesChanged(); } } @@ -1124,36 +1123,36 @@ Future getRange(Reference const& trState, GetRangeLimits const& limits, Reverse const& reverse); -ACTOR Future> fetchServerInterface(Reference trState, UID id) { - Optional val = wait(getValue(trState, serverListKeyFor(id), TransactionRecordLogInfo::False)); +Future> fetchServerInterface(Reference trState, UID id) { + Optional val = co_await getValue(trState, serverListKeyFor(id), TransactionRecordLogInfo::False); if (!val.present()) { // A storage server has been removed from serverList since we read keyServers - return Optional(); + co_return Optional(); } - return decodeServerListValue(val.get()); + co_return decodeServerListValue(val.get()); } -ACTOR Future>> transactionalGetServerInterfaces( +Future>> transactionalGetServerInterfaces( Reference trState, std::vector ids) { - state std::vector>> serverListEntries; + std::vector>> serverListEntries; serverListEntries.reserve(ids.size()); - for (int s = 0; s < ids.size(); s++) { - serverListEntries.push_back(fetchServerInterface(trState, ids[s])); + for (const auto& id : ids) { + serverListEntries.push_back(fetchServerInterface(trState, id)); } - std::vector> serverListValues = wait(getAll(serverListEntries)); + std::vector> serverListValues = co_await getAll(serverListEntries); std::vector serverInterfaces; - for (int s = 0; s < serverListValues.size(); s++) { - if (!serverListValues[s].present()) { + for (const auto& serverListValue : serverListValues) { + if (!serverListValue.present()) { // A storage server has been removed from ServerList since we read keyServers - return Optional>(); + co_return Optional>(); } - serverInterfaces.push_back(serverListValues[s].get()); + serverInterfaces.push_back(serverListValue.get()); } - return serverInterfaces; + co_return serverInterfaces; } void updateTssMappings(Database cx, const GetKeyServerLocationsReply& reply) { @@ -1166,11 +1165,11 @@ void updateTssMappings(Database cx, const GetKeyServerLocationsReply& reply) { } } - for (const auto& mapping : reply.resultsTssMapping) { - auto ssi = ssiById.find(mapping.first); + for (const auto& [storageServerId, tss] : reply.resultsTssMapping) { + auto ssi = ssiById.find(storageServerId); ASSERT(ssi != ssiById.end()); - cx->addTssMapping(*ssi->second, mapping.second); - ssiById.erase(mapping.first); + cx->addTssMapping(*ssi->second, tss); + ssiById.erase(storageServerId); } // if SS didn't have a mapping above, it's still in the ssiById map, so remove its tss mapping @@ -1180,22 +1179,22 @@ void updateTssMappings(Database cx, const GetKeyServerLocationsReply& reply) { } void updateTagMappings(Database cx, const GetKeyServerLocationsReply& reply) { - for (const auto& mapping : reply.resultsTagMapping) { - cx->addSSIdTagMapping(mapping.first, mapping.second); + for (const auto& [storageServerId, tag] : reply.resultsTagMapping) { + cx->addSSIdTagMapping(storageServerId, tag); } } // If isBackward == true, returns the shard containing the key before 'key' (an infinitely long, inexpressible key). // Otherwise returns the shard containing key -ACTOR Future getKeyLocation_internal(Database cx, - Key key, - SpanContext spanContext, - Optional debugID, - UseProvisionalProxies useProvisionalProxies, - Reverse isBackward, - Version version) { - - state Span span("NAPI:getKeyLocation"_loc, spanContext); +Future getKeyLocation_internal(Database cx, + Key key, + SpanContext spanContext, + Optional debugID, + UseProvisionalProxies useProvisionalProxies, + Reverse isBackward, + Version version) { + + Span span("NAPI:getKeyLocation"_loc, spanContext); if (isBackward) { ASSERT(key != allKeys.begin && key <= allKeys.end); } else { @@ -1205,32 +1204,28 @@ ACTOR Future getKeyLocation_internal(Database cx, if (debugID.present()) g_traceBatch.addEvent("TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocation.Before"); - loop { + while (true) { try { - wait(cx->getBackoff()); + co_await cx->getBackoff(); ++cx->transactionKeyServerLocationRequests; - choose { - when(wait(cx->onProxiesChanged())) {} - when(GetKeyServerLocationsReply rep = wait(basicLoadBalance( - cx->getCommitProxies(useProvisionalProxies), - &CommitProxyInterface::getKeyServersLocations, - GetKeyServerLocationsRequest( - span.context, key, Optional(), /*limit=*/100, isBackward, version, key.arena()), - TaskPriority::DefaultPromiseEndpoint))) { - ++cx->transactionKeyServerLocationRequestsCompleted; - if (debugID.present()) - g_traceBatch.addEvent( - "TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocation.After"); - ASSERT(rep.results.size() == 1); - - auto locationInfo = cx->setCachedLocation(rep.results[0].first, rep.results[0].second); - updateTssMappings(cx, rep); - updateTagMappings(cx, rep); - - cx->updateBackoff(success()); - return KeyRangeLocationInfo(KeyRange(rep.results[0].first, rep.arena), locationInfo); - } - } + GetKeyServerLocationsReply rep = co_await commitProxyLoadBalance( + cx, + makeReqBuilder( + span.context, key, Optional(), /*limit=*/100, isBackward, version, key.arena()), + &CommitProxyInterface::getKeyServersLocations, + useProvisionalProxies, + TaskPriority::DefaultPromiseEndpoint); + ++cx->transactionKeyServerLocationRequestsCompleted; + if (debugID.present()) + g_traceBatch.addEvent("TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocation.After"); + ASSERT(rep.results.size() == 1); + + auto locationInfo = cx->setCachedLocation(rep.results[0].first, rep.results[0].second); + updateTssMappings(cx, rep); + updateTagMappings(cx, rep); + + cx->updateBackoff(success()); + co_return KeyRangeLocationInfo(KeyRange(rep.results[0].first, rep.arena), locationInfo); } catch (Error& e) { if (e.code() == error_code_commit_proxy_memory_limit_exceeded) { // Eats commit_proxy_memory_limit_exceeded error from commit proxies @@ -1346,55 +1341,47 @@ void DatabaseContext::updateBackoff(const Error& err) { } } -ACTOR Future> getKeyRangeLocations_internal( - Database cx, - KeyRange keys, - int limit, - Reverse reverse, - SpanContext spanContext, - Optional debugID, - UseProvisionalProxies useProvisionalProxies, - Version version) { - state Span span("NAPI:getKeyRangeLocations"_loc, spanContext); +Future> getKeyRangeLocations_internal(Database cx, + KeyRange keys, + int limit, + Reverse reverse, + SpanContext spanContext, + Optional debugID, + UseProvisionalProxies useProvisionalProxies, + Version version) { + Span span("NAPI:getKeyRangeLocations"_loc, spanContext); if (debugID.present()) g_traceBatch.addEvent("TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocations.Before"); - loop { + while (true) { try { - wait(cx->getBackoff()); + co_await cx->getBackoff(); ++cx->transactionKeyServerLocationRequests; - choose { - when(wait(cx->onProxiesChanged())) {} - when(GetKeyServerLocationsReply _rep = wait(basicLoadBalance( - cx->getCommitProxies(useProvisionalProxies), - &CommitProxyInterface::getKeyServersLocations, - GetKeyServerLocationsRequest( - span.context, keys.begin, keys.end, limit, reverse, version, keys.arena()), - TaskPriority::DefaultPromiseEndpoint))) { - ++cx->transactionKeyServerLocationRequestsCompleted; - state GetKeyServerLocationsReply rep = _rep; - if (debugID.present()) - g_traceBatch.addEvent( - "TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocations.After"); - ASSERT(rep.results.size()); - - state std::vector results; - state int shard = 0; - for (; shard < rep.results.size(); shard++) { - // FIXME: these shards are being inserted into the map sequentially, it would be much more CPU - // efficient to save the map pairs and insert them all at once. - results.emplace_back( - (rep.results[shard].first & keys), - cx->setCachedLocation(rep.results[shard].first, rep.results[shard].second)); - wait(yield()); - } - updateTssMappings(cx, rep); - updateTagMappings(cx, rep); - - cx->updateBackoff(success()); - return results; - } + GetKeyServerLocationsReply rep = co_await commitProxyLoadBalance( + cx, + makeReqBuilder( + span.context, keys.begin, keys.end, limit, reverse, version, keys.arena()), + &CommitProxyInterface::getKeyServersLocations, + useProvisionalProxies, + TaskPriority::DefaultPromiseEndpoint); + ++cx->transactionKeyServerLocationRequestsCompleted; + if (debugID.present()) + g_traceBatch.addEvent("TransactionDebug", debugID.get().first(), "NativeAPI.getKeyLocations.After"); + ASSERT(rep.results.size()); + + std::vector results; + for (int shard = 0; shard < rep.results.size(); ++shard) { + // FIXME: these shards are being inserted into the map sequentially, it would be much more CPU + // efficient to save the map pairs and insert them all at once. + results.emplace_back((rep.results[shard].first & keys), + cx->setCachedLocation(rep.results[shard].first, rep.results[shard].second)); + co_await yield(); } + updateTssMappings(cx, rep); + updateTagMappings(cx, rep); + + cx->updateBackoff(success()); + co_return results; } catch (Error& e) { if (e.code() == error_code_commit_proxy_memory_limit_exceeded) { // Eats commit_proxy_memory_limit_exceeded error from commit proxies @@ -1476,14 +1463,14 @@ Future> getKeyRangeLocations(Reference warmRange_impl(Reference trState, KeyRange keys) { - state int totalRanges = 0; - state int totalRequests = 0; +Future warmRange_impl(Reference trState, KeyRange keys) { + int totalRanges = 0; + int totalRequests = 0; - wait(trState->startTransaction()); + co_await trState->startTransaction(); - loop { - std::vector locations = wait(getKeyRangeLocations_internal( + while (true) { + std::vector locations = co_await getKeyRangeLocations_internal( trState->cx, keys, CLIENT_KNOBS->WARM_RANGE_SHARD_LIMIT, @@ -1491,7 +1478,7 @@ ACTOR Future warmRange_impl(Reference trState, KeyRange trState->spanContext, trState->readOptions.present() ? trState->readOptions.get().debugID : Optional(), trState->useProvisionalProxies, - trState->readVersion())); + trState->readVersion()); totalRanges += CLIENT_KNOBS->WARM_RANGE_SHARD_LIMIT; totalRequests++; if (locations.size() == 0 || totalRanges >= trState->cx->locationCacheSize || @@ -1502,21 +1489,21 @@ ACTOR Future warmRange_impl(Reference trState, KeyRange if (totalRequests % 20 == 0) { // To avoid blocking the proxies from starting other transactions, occasionally get a read version. - state Transaction tr(trState->cx); - loop { + Transaction tr(trState->cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::CAUSAL_READ_RISKY); - wait(success(tr.getReadVersion())); + co_await tr.getReadVersion(); break; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } } - - return Void(); } SpanContext generateSpanID(bool transactionTracingSample, SpanContext parentContext = SpanContext()) { @@ -1545,8 +1532,7 @@ Reference TransactionState::cloneAndReset(ReferencetransactionTracingSample) : spanContext; - Reference newState = - makeReference(cx, cx->taskID, newSpanContext, newTrLogInfo); + auto newState = makeReference(cx, cx->taskID, newSpanContext, newTrLogInfo); if (!cx->apiVersionAtLeast(16)) { newState->options = options; @@ -2004,19 +1990,19 @@ ACTOR Future watchValue(Database cx, Reference p } } -ACTOR Future watchStorageServerResp(Key key, Database cx) { - loop { +Future watchStorageServerResp(Key key, Database cx) { + while (true) { try { - state Reference metadata = cx->getWatchMetadata(key); + Reference metadata = cx->getWatchMetadata(key); if (!metadata.isValid()) { - return Void(); + co_return; } - Version watchVersion = wait(watchValue(cx, metadata->parameters)); + Version watchVersion = co_await watchValue(cx, metadata->parameters); metadata = cx->getWatchMetadata(key); if (!metadata.isValid()) - return Void(); + co_return; // case 1: version_1 (SS) >= version_2 (map) if (watchVersion >= metadata->parameters->version) { @@ -2041,10 +2027,10 @@ ACTOR Future watchStorageServerResp(Key key, Database cx) { Reference metadata = cx->getWatchMetadata(key); if (!metadata.isValid()) { - return Void(); + co_return; } else if (metadata->watchPromise.getFutureReferenceCount() == 1) { cx->deleteWatchMetadata(key); - return Void(); + co_return; } else if (e.code() == error_code_future_version) { continue; } @@ -2055,13 +2041,14 @@ ACTOR Future watchStorageServerResp(Key key, Database cx) { } } -ACTOR Future sameVersionDiffValue(Database cx, Reference parameters) { - state ReadYourWritesTransaction tr(cx); +Future sameVersionDiffValue(Database cx, Reference parameters) { + ReadYourWritesTransaction tr(cx); - loop { + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - state Optional valSS = wait(tr.get(parameters->key)); + Optional valSS = co_await tr.get(parameters->key); Reference metadata = cx->getWatchMetadata(parameters->key); // val_3 != val_1 (storage server value doesn't match value in map) @@ -2078,13 +2065,14 @@ ACTOR Future sameVersionDiffValue(Database cx, Reference cx->setWatchMetadata(metadata); metadata->watchFutureSS = watchStorageServerResp(parameters->key, cx); - wait(success(metadata->watchPromise.getFuture())); + co_await metadata->watchPromise.getFuture(); } - return Void(); + co_return; } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } } @@ -2856,9 +2844,9 @@ struct TSSDuplicateStreamData { Promise tssComparisonDone; // empty constructor for optional? - TSSDuplicateStreamData() {} + TSSDuplicateStreamData() = default; - TSSDuplicateStreamData(PromiseStream stream) : stream(stream) {} + explicit TSSDuplicateStreamData(PromiseStream stream) : stream(stream) {} bool done() { return tssComparisonDone.getFuture().isReady(); } @@ -2868,7 +2856,7 @@ struct TSSDuplicateStreamData { } } - ~TSSDuplicateStreamData() {} + ~TSSDuplicateStreamData() = default; }; // Error tracking here is weird, and latency doesn't really mean the same thing here as it does with normal tss @@ -2950,7 +2938,7 @@ static Future tssStreamComparison(Request request, if ((!ssEndOfStream || !tssEndOfStream) && !TSS_doCompare(ssReply.get(), tssReply.get())) { CODE_PROBE(true, "TSS mismatch in stream comparison"); TraceEvent mismatchEvent( - (g_network->isSimulated() && g_simulator->tssMode == ISimulator::TSSMode::EnabledDropMutations) + (simulationPolicyHasCapability(ISimulationPolicy::Capability::WarnOnStorageMismatch)) ? SevWarnAlways : SevError, LB_mismatchTraceName(request, TSS_COMPARISON)); @@ -2972,11 +2960,12 @@ static Future tssStreamComparison(Request request, tssData.metrics->recordDetailedMismatchData(mismatchUID, mismatchEvent.getFields().toString()); // record a summarized trace event instead - TraceEvent summaryEvent((g_network->isSimulated() && - g_simulator->tssMode == ISimulator::TSSMode::EnabledDropMutations) - ? SevWarnAlways - : SevError, - LB_mismatchTraceName(request, TSS_COMPARISON)); + TraceEvent summaryEvent( + (g_network->isSimulated() && + simulationPolicyHasCapability(ISimulationPolicy::Capability::WarnOnStorageMismatch)) + ? SevWarnAlways + : SevError, + LB_mismatchTraceName(request, TSS_COMPARISON)); summaryEvent.detail("TSSID", tssData.tssId).detail("MismatchId", mismatchUID); } } else { @@ -3018,14 +3007,14 @@ maybeDuplicateTSSStreamFragment(Request& req, QueueModel* model, RequestStream>(); } -// Streams all of the KV pairs in a target key range into a ParallelStream fragment -ACTOR Future getRangeStreamFragment(Reference trState, - ParallelStream::Fragment* results, - KeyRange keys, - GetRangeLimits limits, - Snapshot snapshot, - Reverse reverse, - SpanContext spanContext) { +// Streams all of the KV pairs in a target key range directly to the client in order. +ACTOR Future getRangeStreamImpl(Reference trState, + PromiseStream results, + KeyRange keys, + GetRangeLimits limits, + Snapshot snapshot, + Reverse reverse, + SpanContext spanContext) { loop { state std::vector locations = wait(getKeyRangeLocations( trState, keys, CLIENT_KNOBS->GET_RANGE_SHARD_LIMIT, reverse, &StorageServerInterface::getKeyValuesStream)); @@ -3065,7 +3054,7 @@ ACTOR Future getRangeStreamFragment(Reference trState, if (locations[shard].locations->size() == 0) { wait(trState->cx->connectionFileChanged()); - results->sendError(transaction_too_old()); + results.sendError(transaction_too_old()); return Void(); } @@ -3122,11 +3111,11 @@ ACTOR Future getRangeStreamFragment(Reference trState, state bool breakAgain = false; loop { - wait(results->onEmpty()); + wait(results.onEmpty()); try { choose { when(wait(trState->cx->connectionFileChanged())) { - results->sendError(transaction_too_old()); + results.sendError(transaction_too_old()); if (tssDuplicateStream.present() && !tssDuplicateStream.get().done()) { tssDuplicateStream.get().stream.sendError(transaction_too_old()); } @@ -3225,12 +3214,11 @@ ACTOR Future getRangeStreamFragment(Reference trState, output.readThroughEnd = true; } output.arena().dependsOn(keys.arena()); - // for getRangeStreamFragment, one fragment end doesn't mean it's the end of getRange - // so set 'more' to true + // getRangeStream() uses end_of_stream to indicate exhaustion, so keep 'more' true. output.more = true; output.setReadThrough(reverse ? keys.begin : keys.end); - results->send(std::move(output)); - results->finish(); + results.send(std::move(output)); + results.sendError(end_of_stream()); if (tssDuplicateStream.present() && !tssDuplicateStream.get().done()) { tssDuplicateStream.get().stream.sendError(end_of_stream()); } @@ -3242,10 +3230,10 @@ ACTOR Future getRangeStreamFragment(Reference trState, ++shard; } output.arena().dependsOn(range.arena()); - // if it's not the last shard, set more to true and readThrough to the shard boundary + // If it's not the last shard, set more to true and readThrough to the shard boundary. output.more = true; output.setReadThrough(reverse ? range.begin : range.end); - results->send(std::move(output)); + results.send(std::move(output)); break; } @@ -3256,7 +3244,7 @@ ACTOR Future getRangeStreamFragment(Reference trState, if (keys.end == allKeys.end && reverse) { output.readThroughEnd = true; } - results->send(std::move(output)); + results.send(std::move(output)); } if (breakAgain) { break; @@ -3283,7 +3271,7 @@ ACTOR Future getRangeStreamFragment(Reference trState, wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, trState->taskID)); break; } else { - results->sendError(e); + results.sendError(e); return Void(); } } @@ -3294,13 +3282,7 @@ ACTOR Future getRangeStreamFragment(Reference trState, ACTOR Future>> getRangeSplitPoints(Reference trState, KeyRange keys, int64_t chunkSize); - -static KeyRange intersect(KeyRangeRef lhs, KeyRangeRef rhs) { - return KeyRange(KeyRangeRef(std::max(lhs.begin, rhs.begin), std::min(lhs.end, rhs.end))); -} - -// Divides the requested key range into 1MB fragments, create range streams for each fragment, and merges the results so -// the client get them in order +// Streams the requested key range directly from storage servers without fragment-level parallelism. ACTOR Future getRangeStream(Reference trState, PromiseStream _results, KeySelector begin, @@ -3309,8 +3291,6 @@ ACTOR Future getRangeStream(Reference trState, Promise> conflictRange, Snapshot snapshot, Reverse reverse) { - state ParallelStream results(_results, CLIENT_KNOBS->RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT); - // FIXME: better handling to disable row limits ASSERT(!limits.hasRowLimit()); state Span span("NAPI:getRangeStream"_loc, trState->spanContext); @@ -3332,52 +3312,11 @@ ACTOR Future getRangeStream(Reference trState, } if (b >= e) { - wait(results.finish()); + _results.sendError(end_of_stream()); return Void(); } - // if e is allKeys.end, we have read through the end of the database - // if b is allKeys.begin, we have either read through the beginning of the database, - // or allKeys.begin exists in the database and will be part of the conflict range anyways - - state std::vector> outstandingRequests; - while (b < e) { - state KeyRangeLocationInfo locationInfo = - wait(getKeyLocation(trState, reverse ? e : b, &StorageServerInterface::getKeyValuesStream, reverse)); - state KeyRange shardIntersection = intersect(locationInfo.range, KeyRangeRef(b, e)); - state Standalone> splitPoints = - wait(getRangeSplitPoints(trState, shardIntersection, CLIENT_KNOBS->RANGESTREAM_FRAGMENT_SIZE)); - state std::vector toSend; - // state std::vector::iterator>> outstandingRequests; - - if (!splitPoints.empty()) { - toSend.push_back(KeyRange(KeyRangeRef(shardIntersection.begin, splitPoints.front()), splitPoints.arena())); - for (int i = 0; i < splitPoints.size() - 1; ++i) { - toSend.push_back(KeyRange(KeyRangeRef(splitPoints[i], splitPoints[i + 1]), splitPoints.arena())); - } - toSend.push_back(KeyRange(KeyRangeRef(splitPoints.back(), shardIntersection.end), splitPoints.arena())); - } else { - toSend.push_back(KeyRange(KeyRangeRef(shardIntersection.begin, shardIntersection.end))); - } - - state int idx = 0; - state int useIdx = 0; - for (; idx < toSend.size(); ++idx) { - useIdx = reverse ? toSend.size() - idx - 1 : idx; - if (toSend[useIdx].empty()) { - continue; - } - ParallelStream::Fragment* fragment = wait(results.createFragment()); - outstandingRequests.push_back( - getRangeStreamFragment(trState, fragment, toSend[useIdx], limits, snapshot, reverse, span.context)); - } - if (reverse) { - e = shardIntersection.begin; - } else { - b = shardIntersection.end; - } - } - wait(waitForAll(outstandingRequests) && results.finish()); + wait(getRangeStreamImpl(trState, _results, KeyRange(KeyRangeRef(b, e)), limits, snapshot, reverse, span.context)); return Void(); } @@ -3536,21 +3475,19 @@ void Watch::setWatch(Future watchFuture) { } // Restarts a watch after a database switch -ACTOR Future restartWatch(Database cx, - Key key, - Optional value, - TagSet tags, - SpanContext spanContext, - TaskPriority taskID, - Optional debugID, - UseProvisionalProxies useProvisionalProxies) { +Future restartWatch(Database cx, + Key key, + Optional value, + TagSet tags, + SpanContext spanContext, + TaskPriority taskID, + Optional debugID, + UseProvisionalProxies useProvisionalProxies) { // Remove the reference count as the old watches should be all dropped when switching connectionFile. cx->deleteWatchMetadata(key, /* removeReferenceCount */ true); - wait(watchValueMap( - cx->minAcceptableReadVersion, key, value, cx, tags, spanContext, taskID, debugID, useProvisionalProxies)); - - return Void(); + co_await watchValueMap( + cx->minAcceptableReadVersion, key, value, cx, tags, spanContext, taskID, debugID, useProvisionalProxies); } // FIXME: This seems pretty horrible. Now a Database can't die until all of its watches do... @@ -3620,25 +3557,25 @@ Future Transaction::watch(Reference watch) { trState->useProvisionalProxies); } -ACTOR Future>> getAddressesForKeyActor(Reference trState, Key key) { - state std::vector ssi; +Future>> getAddressesForKeyActor(Reference trState, Key key) { + std::vector ssi; - wait(trState->startTransaction()); + co_await trState->startTransaction(); - state Key resolvedKey = key; + Key resolvedKey = key; // If key >= allKeys.end, then getRange will return a kv-pair with an empty value. This will result in our // serverInterfaces vector being empty, which will cause us to return an empty addresses list. - state Key ksKey = keyServersKey(resolvedKey); - state RangeResult serverTagResult = wait(getRange(trState, - lastLessOrEqual(serverTagKeys.begin), - firstGreaterThan(serverTagKeys.end), - GetRangeLimits(CLIENT_KNOBS->TOO_MANY), - Reverse::False)); + Key ksKey = keyServersKey(resolvedKey); + RangeResult serverTagResult = co_await getRange(trState, + lastLessOrEqual(serverTagKeys.begin), + firstGreaterThan(serverTagKeys.end), + GetRangeLimits(CLIENT_KNOBS->TOO_MANY), + Reverse::False); ASSERT(!serverTagResult.more && serverTagResult.size() < CLIENT_KNOBS->TOO_MANY); Future futureServerUids = getRange(trState, lastLessOrEqual(ksKey), firstGreaterThan(ksKey), GetRangeLimits(1), Reverse::False); - RangeResult serverUids = wait(futureServerUids); + RangeResult serverUids = co_await futureServerUids; ASSERT(serverUids.size()); // every shard needs to have a team @@ -3648,7 +3585,7 @@ ACTOR Future>> getAddressesForKeyActor(Referen // the move is finished, because it could be cancelled at any time. decodeKeyServersValue(serverTagResult, serverUids[0].value, src, ignore); Optional> serverInterfaces = - wait(transactionalGetServerInterfaces(trState, src)); + co_await transactionalGetServerInterfaces(trState, src); ASSERT(serverInterfaces.present()); // since this is happening transactionally, /FF/keyServers and /FF/serverList // need to be consistent with one another @@ -3661,7 +3598,7 @@ ACTOR Future>> getAddressesForKeyActor(Referen strcpy(c_string, ipString.c_str()); addresses.push_back(addresses.arena(), c_string); } - return addresses; + co_return addresses; } Future>> Transaction::getAddressesForKey(const Key& key) { @@ -4069,6 +4006,7 @@ void TransactionOptions::clear() { rawAccess = false; bypassStorageQuota = false; enableReplicaConsistencyCheck = false; + maxGrvQueueDelayMS = Optional(); requiredReplicas = 0; } @@ -4119,8 +4057,8 @@ class MutationBlock { ValueRef setValue; MutationBlock() : mutated(false) {} - MutationBlock(bool _cleared) : mutated(true), cleared(_cleared) {} - MutationBlock(ValueRef value) : mutated(true), cleared(false), setValue(value) {} + explicit MutationBlock(bool _cleared) : mutated(true), cleared(_cleared) {} + explicit MutationBlock(ValueRef value) : mutated(true), cleared(false), setValue(value) {} }; bool compareBegin(KeyRangeRef lhs, KeyRangeRef rhs) { @@ -4148,57 +4086,57 @@ Optional intersects(VectorRef lhs, VectorRef(); } -ACTOR void checkWrites(Reference trState, - Future committed, - Promise outCommitted, - CommitTransactionRequest req) { - state Version version; +Future checkWrites(Uncancellable, + Reference trState, + Future committed, + Promise outCommitted, + CommitTransactionRequest req) { + Version version{ 0 }; try { - wait(committed); + co_await committed; // If the commit is successful, by definition the transaction still exists for now. Grab the version, and don't // use it again. version = trState->committedVersion; outCommitted.send(Void()); } catch (Error& e) { outCommitted.sendError(e); - return; + co_return; } - wait(delay(deterministicRandom()->random01())); // delay between 0 and 1 seconds + co_await delay(deterministicRandom()->random01()); // delay between 0 and 1 seconds - state KeyRangeMap expectedValues; + KeyRangeMap expectedValues; auto& mutations = req.transaction.mutations; - state int mCount = mutations.size(); // debugging info for traceEvent + const int mCount = mutations.size(); // debugging info for traceEvent - for (int idx = 0; idx < mutations.size(); idx++) { - if (mutations[idx].type == MutationRef::SetValue) - expectedValues.insert(singleKeyRange(mutations[idx].param1), MutationBlock(mutations[idx].param2)); - else if (mutations[idx].type == MutationRef::ClearRange) - expectedValues.insert(KeyRangeRef(mutations[idx].param1, mutations[idx].param2), MutationBlock(true)); + for (const auto& mutation : mutations) { + if (mutation.type == MutationRef::SetValue) + expectedValues.insert(singleKeyRange(mutation.param1), MutationBlock(mutation.param2)); + else if (mutation.type == MutationRef::ClearRange) + expectedValues.insert(KeyRangeRef(mutation.param1, mutation.param2), MutationBlock(true)); } try { - state Transaction tr(trState->cx); + Transaction tr(trState->cx); tr.setVersion(version); - state int checkedRanges = 0; - state KeyRangeMap::Ranges ranges = expectedValues.ranges(); - state KeyRangeMap::iterator it = ranges.begin(); - for (; it != ranges.end(); ++it) { - state MutationBlock m = it->value(); + int checkedRanges = 0; + auto ranges = expectedValues.ranges(); + for (auto it = ranges.begin(); it != ranges.end(); ++it) { + MutationBlock m = it->value(); if (m.mutated) { checkedRanges++; if (m.cleared) { - RangeResult shouldBeEmpty = wait(tr.getRange(it->range(), 1)); + RangeResult shouldBeEmpty = co_await tr.getRange(it->range(), 1); if (shouldBeEmpty.size()) { TraceEvent(SevError, "CheckWritesFailed") .detail("Class", "Clear") .detail("KeyBegin", it->range().begin) .detail("KeyEnd", it->range().end); - return; + co_return; } } else { - Optional val = wait(tr.get(it->range().begin)); + Optional val = co_await tr.get(it->range().begin); if (!val.present() || val.get() != m.setValue) { TraceEvent evt(SevError, "CheckWritesFailed"); evt.detail("Class", "Set").detail("Key", it->range().begin).detail("Expected", m.setValue); @@ -4206,7 +4144,7 @@ ACTOR void checkWrites(Reference trState, evt.detail("Actual", "_Value Missing_"); else evt.detail("Actual", val.get()); - return; + co_return; } } } @@ -4222,12 +4160,13 @@ ACTOR void checkWrites(Reference trState, } } -ACTOR static Future commitDummyTransaction(Reference trState, KeyRange range) { - state Transaction tr(trState->cx); - state int retries = 0; - state Span span("NAPI:dummyTransaction"_loc, trState->spanContext); +static Future commitDummyTransaction(Reference trState, KeyRange range) { + Transaction tr(trState->cx); + int retries = 0; + Span span("NAPI:dummyTransaction"_loc, trState->spanContext); tr.span.setParent(span.context); - loop { + while (true) { + Error err; try { TraceEvent("CommitDummyTransaction").detail("Key", range.begin).detail("Retries", retries); tr.trState->options = trState->options; @@ -4238,29 +4177,31 @@ ACTOR static Future commitDummyTransaction(Reference trS tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.addReadConflictRange(range); tr.addWriteConflictRange(range); - wait(tr.commit()); - return Void(); + co_await tr.commit(); + co_return; } catch (Error& e) { - TraceEvent("CommitDummyTransactionError") - .errorUnsuppressed(e) - .detail("Key", range.begin) - .detail("Retries", retries); - wait(tr.onError(e)); + err = e; } + TraceEvent("CommitDummyTransactionError") + .errorUnsuppressed(err) + .detail("Key", range.begin) + .detail("Retries", retries); + co_await tr.onError(err); ++retries; } } -ACTOR static Future> determineCommitStatus(Reference trState, - Version minPossibleCommitVersion, - Version maxPossibleCommitVersion, - IdempotencyIdRef idempotencyId) { - state Transaction tr(trState->cx); - state int retries = 0; - state Version expiredVersion; - state Span span("NAPI:determineCommitStatus"_loc, trState->spanContext); +static Future> determineCommitStatus(Reference trState, + Version minPossibleCommitVersion, + Version maxPossibleCommitVersion, + IdempotencyIdRef idempotencyId) { + Transaction tr(trState->cx); + int retries = 0; + Version expiredVersion{ 0 }; + Span span("NAPI:determineCommitStatus"_loc, trState->spanContext); tr.span.setParent(span.context); - loop { + while (true) { + Error err; try { tr.trState->options = trState->options; tr.trState->taskID = trState->taskID; @@ -4269,12 +4210,12 @@ ACTOR static Future> determineCommitStatus(Reference expiredKey(idempotencyIdsExpiredVersion, Unversioned()); - IdempotencyIdsExpiredVersion expiredVal = wait(expiredKey.getD(&tr)); + IdempotencyIdsExpiredVersion expiredVal = co_await expiredKey.getD(&tr); expiredVersion = expiredVal.expired; if (expiredVersion >= minPossibleCommitVersion) { throw commit_unknown_result_fatal(); } - Version rv = wait(tr.getReadVersion()); + Version rv = co_await tr.getReadVersion(); TraceEvent("DetermineCommitStatusAttempt") .detail("IdempotencyId", idempotencyId.asStringRefUnsafe()) .detail("Retries", retries) @@ -4287,7 +4228,7 @@ ACTOR static Future> determineCommitStatus(ReferenceTOO_MANY)); + RangeResult range = co_await tr.getRange(possibleRange, CLIENT_KNOBS->TOO_MANY); ASSERT(!range.more); for (const auto& kv : range) { auto commitResult = kvContainsIdempotencyId(kv, idempotencyId); @@ -4296,29 +4237,30 @@ ACTOR static Future> determineCommitStatus(Reference(); + co_return Optional(); } catch (Error& e) { - TraceEvent("DetermineCommitStatusError") - .errorUnsuppressed(e) - .detail("IdempotencyId", idempotencyId.asStringRefUnsafe()) - .detail("Retries", retries); - wait(tr.onError(e)); + err = e; } + TraceEvent("DetermineCommitStatusError") + .errorUnsuppressed(err) + .detail("IdempotencyId", idempotencyId.asStringRefUnsafe()) + .detail("Retries", retries); + co_await tr.onError(err); ++retries; } } void Transaction::cancelWatches(Error const& e) { - for (int i = 0; i < watches.size(); ++i) - if (!watches[i]->onChangeTrigger.isSet()) - watches[i]->onChangeTrigger.sendError(e); + for (auto& watch : watches) + if (!watch->onChangeTrigger.isSet()) + watch->onChangeTrigger.sendError(e); watches.clear(); } @@ -4327,11 +4269,11 @@ void Transaction::setupWatches() { try { Future watchVersion = getCommittedVersion() > 0 ? getCommittedVersion() : getReadVersion(); - for (int i = 0; i < watches.size(); ++i) - watches[i]->setWatch( + for (auto& watch : watches) + watch->setWatch( watchValueMap(watchVersion, - watches[i]->key, - watches[i]->value, + watch->key, + watch->value, trState->cx, trState->options.readTags, trState->spanContext, @@ -4569,7 +4511,8 @@ ACTOR static Future tryCommit(Reference trState, CommitT } } } catch (Error& e) { - if (e.code() == error_code_request_maybe_delivered || e.code() == error_code_commit_unknown_result) { + if (e.code() == error_code_request_maybe_delivered || e.code() == error_code_commit_unknown_result || + e.code() == error_code_never_reply) { // We don't know if the commit happened, and it might even still be in flight. if (!trState->options.causalWriteRisky || req.idempotencyId.valid()) { @@ -4595,6 +4538,7 @@ ACTOR static Future tryCommit(Reference trState, CommitT req.transaction.read_snapshot + CLIENT_KNOBS->MAX_WRITE_TRANSACTION_LIFE_VERSIONS, req.idempotencyId)); if (commitResult.present()) { + trState->committedVersion = commitResult.get().commitVersion; Standalone ret = makeString(10); placeVersionstamp( mutateString(ret), commitResult.get().commitVersion, commitResult.get().batchIndex); @@ -4617,7 +4561,7 @@ ACTOR static Future tryCommit(Reference trState, CommitT e.code() != error_code_grv_proxy_memory_limit_exceeded && e.code() != error_code_batch_transaction_throttled && e.code() != error_code_tag_throttled && e.code() != error_code_process_behind && e.code() != error_code_future_version && - e.code() != error_code_proxy_tag_throttled && e.code() != error_code_transaction_throttled_hot_shard && + e.code() != error_code_transaction_throttled_hot_shard && e.code() != error_code_transaction_rejected_range_locked) { TraceEvent(SevError, "TryCommitError").error(e); } @@ -4672,11 +4616,10 @@ Future Transaction::commitMutations() { } bool isCheckingWrites = trState->options.checkWritesEnabled && deterministicRandom()->random01() < 0.01; - for (int i = 0; i < extraConflictRanges.size(); i++) - if (extraConflictRanges[i].isReady() && - extraConflictRanges[i].get().first < extraConflictRanges[i].get().second) + for (const auto& extraConflictRange : extraConflictRanges) + if (extraConflictRange.isReady() && extraConflictRange.get().first < extraConflictRange.get().second) tr.transaction.read_conflict_ranges.emplace_back( - tr.arena, extraConflictRanges[i].get().first, extraConflictRanges[i].get().second); + tr.arena, extraConflictRange.get().first, extraConflictRange.get().second); if (tr.idempotencyId.valid()) { // We need to be able confirm that this transaction is no longer in @@ -4727,7 +4670,7 @@ Future Transaction::commitMutations() { if (isCheckingWrites) { Promise committed; - checkWrites(trState, commitResult, committed, tr); + checkWrites(Uncancellable(), trState, commitResult, committed, tr); return committed.getFuture(); } return commitResult; @@ -4785,6 +4728,18 @@ Future Transaction::commit() { return committing; } +// Returns a thread-local mt19937_64 seeded once with 32 bytes of OS entropy. +// Used for AUTOMATIC_IDEMPOTENCY ID generation in non-simulation runs. +static std::mt19937_64& getIdempotencyRng() { + static thread_local std::mt19937_64 rng = []() { + uint32_t seed_data[8]; + platform::getRandomBytes(seed_data, sizeof(seed_data)); + std::seed_seq seq(seed_data, seed_data + 8); + return std::mt19937_64(seq); + }(); + return rng; +} + void Transaction::setOption(FDBTransactionOptions::Option option, Optional value) { switch (option) { case FDBTransactionOptions::INITIALIZE_NEW_DATABASE: @@ -4904,6 +4859,11 @@ void Transaction::setOption(FDBTransactionOptions::Option option, Optionaloptions.maxBackoff = extractIntOption(value, 0, std::numeric_limits::max()) / 1000.0; break; + case FDBTransactionOptions::MAX_GRV_QUEUE_DELAY: + validateOptionValuePresent(value); + trState->options.maxGrvQueueDelayMS = extractIntOption(value, 0, std::numeric_limits::max()); + break; + case FDBTransactionOptions::SIZE_LIMIT: validateOptionValuePresent(value); trState->options.sizeLimit = extractIntOption(value, 32, CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT); @@ -5026,9 +4986,15 @@ void Transaction::setOption(FDBTransactionOptions::Option option, OptionalrandomUniqueID(), Unversioned()))); + StringRef id = makeString(16, tr.arena); + if (g_network->isSimulated()) { + deterministicRandom()->randomBytes(mutateString(id), 16); + } else { + auto& rng = getIdempotencyRng(); + uint64_t buf[2] = { rng(), rng() }; + memcpy(mutateString(id), buf, 16); + } + tr.idempotencyId = IdempotencyIdRef(id); } trState->automaticIdempotency = true; break; @@ -5073,7 +5039,8 @@ ACTOR Future getConsistentReadVersion(SpanContext parentSpa TransactionPriority priority, uint32_t flags, TransactionTagMap tags, - Optional debugID) { + Optional debugID, + Optional maxGrvQueueDelayMS) { state Span span("NAPI:getConsistentReadVersion"_loc, parentSpan); ++cx->transactionReadVersionBatches; @@ -5087,7 +5054,8 @@ ACTOR Future getConsistentReadVersion(SpanContext parentSpa cx->ssVersionVectorCache.getMaxVersion(), flags, tags, - debugID); + debugID, + maxGrvQueueDelayMS); state Future onProxiesChanged = cx->onProxiesChanged(); choose { @@ -5100,8 +5068,6 @@ ACTOR Future getConsistentReadVersion(SpanContext parentSpa &GrvProxyInterface::getConsistentReadVersion, req, cx->taskID))) { - CODE_PROBE(v.proxyTagThrottledDuration > 0.0, - "getConsistentReadVersion received GetReadVersionReply delayed by proxy tag throttling"); if (tags.size() != 0) { auto& priorityThrottledTags = cx->throttledTags[priority]; for (auto& tag : tags) { @@ -5136,7 +5102,8 @@ ACTOR Future getConsistentReadVersion(SpanContext parentSpa } } catch (Error& e) { if (e.code() != error_code_broken_promise && e.code() != error_code_batch_transaction_throttled && - e.code() != error_code_grv_proxy_memory_limit_exceeded && e.code() != error_code_proxy_tag_throttled) + e.code() != error_code_grv_proxy_memory_limit_exceeded && + e.code() != error_code_transaction_grv_queue_rejected) TraceEvent(SevError, "GetConsistentReadVersionError").error(e); throw; } @@ -5146,7 +5113,8 @@ ACTOR Future getConsistentReadVersion(SpanContext parentSpa ACTOR Future readVersionBatcher(DatabaseContext* cx, FutureStream versionStream, TransactionPriority priority, - uint32_t flags) { + uint32_t flags, + Optional maxGrvQueueDelayMS) { state std::vector> requests; state PromiseStream> addActor; state Future collection = actorCollection(addActor.getFuture()); @@ -5221,7 +5189,8 @@ ACTOR Future readVersionBatcher(DatabaseContext* cx, addActor.send(ready(timeReply(GRVReply.getFuture(), replyTimes))); Future batch = incrementalBroadcastWithError( - getConsistentReadVersion(span.context, cx, count, priority, flags, std::move(tags), std::move(debugID)), + getConsistentReadVersion( + span.context, cx, count, priority, flags, std::move(tags), std::move(debugID), maxGrvQueueDelayMS), std::move(requests), CLIENT_KNOBS->BROADCAST_BATCH_SIZE); @@ -5246,7 +5215,6 @@ ACTOR Future extractReadVersion(Reference trState, double latency = replyTime - trState->startTime; trState->cx->lastProxyRequestTime = trState->startTime; trState->cx->updateCachedReadVersion(trState->startTime, rep.version); - trState->proxyTagThrottledDuration += rep.proxyTagThrottledDuration; if (rep.rkBatchThrottled) { trState->cx->lastRkBatchThrottleTime = replyTime; } @@ -5453,14 +5421,18 @@ Future TransactionState::getReadVersion(uint32_t flags) { } } - auto& batcher = cx->versionBatcher[flags]; - if (!batcher.actor.isValid()) { - batcher.actor = readVersionBatcher(cx.getPtr(), batcher.stream.getFuture(), options.priority, flags); - } - Location location = "NAPI:getReadVersion"_loc; SpanContext derivedSpanContext = generateSpanID(cx->transactionTracingSample, spanContext); Optional versionDebugID = readOptions.present() ? readOptions.get().debugID : Optional(); + + // Include the max GRV queue delay in the batcher key so coalesced requests + // share the same proxy-side admission threshold. + auto& batcher = cx->versionBatcher[DatabaseContext::VersionBatcherKey(flags, options.maxGrvQueueDelayMS)]; + if (!batcher.actor.isValid()) { + batcher.actor = readVersionBatcher( + cx.getPtr(), batcher.stream.getFuture(), options.priority, flags, options.maxGrvQueueDelayMS); + } + auto const req = DatabaseContext::VersionRequest(derivedSpanContext, options.tags, versionDebugID); batcher.stream.send(req); startTime = now(); @@ -5477,7 +5449,7 @@ Optional Transaction::getCachedReadVersion() const { } double Transaction::getTagThrottledDuration() const { - return trState->proxyTagThrottledDuration; + return 0.0; } Future> Transaction::getVersionstamp() { @@ -5488,32 +5460,31 @@ Future> Transaction::getVersionstamp() { } // Gets the protocol version reported by a coordinator via the protocol info interface -ACTOR Future getCoordinatorProtocol(NetworkAddress coordinatorAddress) { +Future getCoordinatorProtocol(NetworkAddress coordinatorAddress) { RequestStream requestStream( Endpoint::wellKnown({ coordinatorAddress }, WLTOKEN_PROTOCOL_INFO)); - ProtocolInfoReply reply = wait(retryBrokenPromise(requestStream, ProtocolInfoRequest{})); - return reply.version; + ProtocolInfoReply reply = co_await retryBrokenPromise(requestStream, ProtocolInfoRequest{}); + co_return reply.version; } // Gets the protocol version reported by a coordinator in its connect packet // If we are unable to get a version from the connect packet (e.g. because we lost connection with the peer), then this // function will return with an unset result. // If an expected version is given, this future won't return if the actual protocol version matches the expected version -ACTOR Future> getCoordinatorProtocolFromConnectPacket( - NetworkAddress coordinatorAddress, - Optional expectedVersion) { - state Optional> const>> protocolVersion = +Future> getCoordinatorProtocolFromConnectPacket(NetworkAddress coordinatorAddress, + Optional expectedVersion) { + Optional> const>> protocolVersion = FlowTransport::transport().getPeerProtocolAsyncVar(coordinatorAddress); if (!protocolVersion.present()) { TraceEvent(SevWarnAlways, "GetCoordinatorProtocolPeerMissing").detail("Address", coordinatorAddress); - wait(delay(FLOW_KNOBS->CONNECTION_MONITOR_TIMEOUT)); - return Optional(); + co_await delay(FLOW_KNOBS->CONNECTION_MONITOR_TIMEOUT); + co_return Optional(); } - loop { + while (true) { if (protocolVersion.get()->get().present() && protocolVersion.get()->get() != expectedVersion) { - return protocolVersion.get()->get(); + co_return protocolVersion.get()->get(); } Future change = protocolVersion.get()->onChange(); @@ -5522,10 +5493,10 @@ ACTOR Future> getCoordinatorProtocolFromConnectPacket( change = timeout(change, FLOW_KNOBS->CONNECTION_MONITOR_TIMEOUT, Void()); } - wait(change); + co_await change; if (!protocolVersion.get()->get().present()) { - return protocolVersion.get()->get(); + co_return protocolVersion.get()->get(); } } } @@ -5629,7 +5600,7 @@ Future Transaction::onError(Error const& e) { e.code() == error_code_database_locked || e.code() == error_code_commit_proxy_memory_limit_exceeded || e.code() == error_code_grv_proxy_memory_limit_exceeded || e.code() == error_code_process_behind || e.code() == error_code_batch_transaction_throttled || e.code() == error_code_tag_throttled || - e.code() == error_code_proxy_tag_throttled || e.code() == error_code_transaction_throttled_hot_shard || + e.code() == error_code_transaction_throttled_hot_shard || (e.code() == error_code_transaction_rejected_range_locked && CLIENT_KNOBS->TRANSACTION_LOCK_REJECTION_RETRIABLE)) { if (e.code() == error_code_not_committed) @@ -5644,9 +5615,6 @@ Future Transaction::onError(Error const& e) { else if (e.code() == error_code_batch_transaction_throttled || e.code() == error_code_tag_throttled || e.code() == error_code_transaction_throttled_hot_shard) { ++trState->cx->transactionsThrottled; - } else if (e.code() == error_code_proxy_tag_throttled) { - ++trState->cx->transactionsThrottled; - trState->proxyTagThrottledDuration += CLIENT_KNOBS->PROXY_MAX_TAG_THROTTLE_DURATION; } else if (e.code() == error_code_transaction_rejected_range_locked) { ++trState->cx->transactionsLockRejected; } @@ -5671,83 +5639,85 @@ Future Transaction::onError(Error const& e) { return e; } -ACTOR Future getStorageMetricsLargeKeyRange(Database cx, - KeyRange keys, - Optional> trState); +Future getStorageMetricsLargeKeyRange(Database cx, + KeyRange keys, + Optional> trState); -ACTOR Future doGetStorageMetrics(Database cx, - Version version, - KeyRange keys, - Reference locationInfo, - Optional> trState) { +Future doGetStorageMetrics(Database cx, + Version version, + KeyRange keys, + Reference locationInfo, + Optional> trState) { + Error err; try { WaitMetricsRequest req(version, keys, StorageMetrics(), StorageMetrics()); req.min.bytes = 0; req.max.bytes = -1; - StorageMetrics m = wait(loadBalance( - locationInfo->locations(), &StorageServerInterface::waitMetrics, req, TaskPriority::DataDistribution)); - return m; + StorageMetrics m = co_await loadBalance( + locationInfo->locations(), &StorageServerInterface::waitMetrics, req, TaskPriority::DataDistribution); + co_return m; } catch (Error& e) { - if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed) { - cx->invalidateCache(keys); - wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution)); - } else if (e.code() == error_code_future_version) { - wait(delay(CLIENT_KNOBS->FUTURE_VERSION_RETRY_DELAY, TaskPriority::DataDistribution)); - } else { - TraceEvent(SevError, "DoGetStorageMetricsError").error(e); - throw; - } - - StorageMetrics m = wait(getStorageMetricsLargeKeyRange(cx, keys, trState)); - return m; + err = e; + } + if (err.code() == error_code_wrong_shard_server || err.code() == error_code_all_alternatives_failed) { + cx->invalidateCache(keys); + co_await delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution); + } else if (err.code() == error_code_future_version) { + co_await delay(CLIENT_KNOBS->FUTURE_VERSION_RETRY_DELAY, TaskPriority::DataDistribution); + } else { + TraceEvent(SevError, "DoGetStorageMetricsError").error(err); + throw err; } + + co_return co_await getStorageMetricsLargeKeyRange(cx, keys, trState); } -ACTOR Future getStorageMetricsLargeKeyRange(Database cx, - KeyRange keys, - Optional> trState) { - state Span span("NAPI:GetStorageMetricsLargeKeyRange"_loc); +Future getStorageMetricsLargeKeyRange(Database cx, + KeyRange keys, + Optional> trState) { + Span span("NAPI:GetStorageMetricsLargeKeyRange"_loc); if (trState.present()) { - wait(trState.get()->startTransaction()); - } - - state Version version = trState.present() ? trState.get()->readVersion() : latestVersion; - std::vector locations = wait(getKeyRangeLocations(cx, - keys, - std::numeric_limits::max(), - Reverse::False, - &StorageServerInterface::waitMetrics, - span.context, - Optional(), - UseProvisionalProxies::False, - version)); - state int nLocs = locations.size(); - state std::vector> fx(nLocs); - state StorageMetrics total; + co_await trState.get()->startTransaction(); + } + + Version version = trState.present() ? trState.get()->readVersion() : latestVersion; + std::vector locations = co_await getKeyRangeLocations(cx, + keys, + std::numeric_limits::max(), + Reverse::False, + &StorageServerInterface::waitMetrics, + span.context, + Optional(), + UseProvisionalProxies::False, + version); + int nLocs = locations.size(); + std::vector> fx(nLocs); + StorageMetrics total; 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; fx[i] = doGetStorageMetrics(cx, version, KeyRangeRef(partBegin, partEnd), locations[i].locations, trState); } - wait(waitForAll(fx)); + co_await waitForAll(fx); for (int i = 0; i < nLocs; i++) { total += fx[i].get(); } - return total; + co_return total; } -ACTOR Future trackBoundedStorageMetrics(Version version, - KeyRange keys, - Reference location, - StorageMetrics x, - StorageMetrics halfError, - PromiseStream deltaStream) { +Future trackBoundedStorageMetrics(Version version, + KeyRange keys, + Reference location, + StorageMetrics x, + StorageMetrics halfError, + PromiseStream deltaStream) { try { - loop { + while (true) { WaitMetricsRequest req(version, keys, x - halfError, x + halfError); - StorageMetrics nextX = wait(loadBalance(location->locations(), &StorageServerInterface::waitMetrics, req)); + StorageMetrics nextX = + co_await loadBalance(location->locations(), &StorageServerInterface::waitMetrics, req); deltaStream.send(nextX - x); x = nextX; } @@ -5757,19 +5727,19 @@ ACTOR Future trackBoundedStorageMetrics(Version version, } } -ACTOR Future waitStorageMetricsMultipleLocations(Version version, - std::vector locations, - StorageMetrics min, - StorageMetrics max, - StorageMetrics permittedError) { - state int nLocs = locations.size(); - state std::vector> fx(nLocs); - state StorageMetrics total; - state PromiseStream deltas; - state std::vector> wx(fx.size()); - state StorageMetrics halfErrorPerMachine = permittedError * (0.5 / nLocs); - state StorageMetrics maxPlus = max + halfErrorPerMachine * (nLocs - 1); - state StorageMetrics minMinus = min - halfErrorPerMachine * (nLocs - 1); +Future waitStorageMetricsMultipleLocations(Version version, + std::vector locations, + StorageMetrics min, + StorageMetrics max, + StorageMetrics permittedError) { + int nLocs = locations.size(); + std::vector> fx(nLocs); + StorageMetrics total; + PromiseStream deltas; + std::vector> wx(fx.size()); + StorageMetrics halfErrorPerMachine = permittedError * (0.5 / nLocs); + StorageMetrics maxPlus = max + halfErrorPerMachine * (nLocs - 1); + StorageMetrics minMinus = min - halfErrorPerMachine * (nLocs - 1); for (int i = 0; i < nLocs; i++) { WaitMetricsRequest req(version, locations[i].range, StorageMetrics(), StorageMetrics()); @@ -5780,64 +5750,65 @@ ACTOR Future waitStorageMetricsMultipleLocations(Version version req, TaskPriority::DataDistribution); } - wait(waitForAll(fx)); + co_await waitForAll(fx); // invariant: true total is between (total-permittedError/2, total+permittedError/2) for (int i = 0; i < nLocs; i++) total += fx[i].get(); if (!total.allLessOrEqual(maxPlus)) - return total; + co_return total; if (!minMinus.allLessOrEqual(total)) - return total; + co_return total; for (int i = 0; i < nLocs; i++) wx[i] = trackBoundedStorageMetrics( version, locations[i].range, locations[i].locations, fx[i].get(), halfErrorPerMachine, deltas); - loop { - StorageMetrics delta = waitNext(deltas.getFuture()); + while (true) { + StorageMetrics delta = co_await deltas.getFuture(); total += delta; if (!total.allLessOrEqual(maxPlus)) - return total; + co_return total; if (!minMinus.allLessOrEqual(total)) - return total; + co_return total; } } -ACTOR Future extractMetrics(Future, int>> fMetrics) { - std::pair, int> x = wait(fMetrics); - return x.first.get(); +Future extractMetrics(Future, int>> fMetrics) { + std::pair, int> x = co_await fMetrics; + co_return x.first.get(); } -ACTOR Future>> getReadHotRanges(Database cx, KeyRange keys) { - state Span span("NAPI:GetReadHotRanges"_loc); - loop { +Future>> getReadHotRanges(Database cx, KeyRange keys) { + Span span("NAPI:GetReadHotRanges"_loc); + while (true) { int64_t shardLimit = 100; // Shard limit here does not really matter since this function is currently only used // to find the read-hot sub ranges within a read-hot shard. std::vector locations = - wait(getKeyRangeLocations(cx, - keys, - shardLimit, - Reverse::False, - &StorageServerInterface::getReadHotRanges, - span.context, - Optional(), - UseProvisionalProxies::False, - latestVersion)); + co_await getKeyRangeLocations(cx, + keys, + shardLimit, + Reverse::False, + &StorageServerInterface::getReadHotRanges, + span.context, + Optional(), + UseProvisionalProxies::False, + latestVersion); + Error err; try { // TODO: how to handle this? // This function is called whenever a shard becomes read-hot. But somehow the shard was split across more // than one storage server after becoming read-hot and before this function is called, i.e. a race // condition. Should we abort and wait for the newly split shards to be hot again? - state int nLocs = locations.size(); + int nLocs = locations.size(); // if (nLocs > 1) { // TraceEvent("RHDDebug") // .detail("NumSSIs", nLocs) // .detail("KeysBegin", keys.begin.printable().c_str()) // .detail("KeysEnd", keys.end.printable().c_str()); // } - state std::vector> fReplies(nLocs); + std::vector> fReplies(nLocs); KeyRef partBegin, partEnd; for (int i = 0; i < nLocs; i++) { partBegin = (i == 0) ? keys.begin : locations[i].range.begin; @@ -5849,11 +5820,11 @@ ACTOR Future>> getReadHotRanges(Da TaskPriority::DataDistribution); } - wait(waitForAll(fReplies)); + co_await waitForAll(fReplies); if (nLocs == 1) { CODE_PROBE(true, "Single-shard read hot range request"); - return fReplies[0].get().readHotRanges; + co_return fReplies[0].get().readHotRanges; } else { CODE_PROBE(true, "Multi-shard read hot range request"); Standalone> results; @@ -5864,25 +5835,26 @@ ACTOR Future>> getReadHotRanges(Da results.arena().dependsOn(fReplies[i].get().readHotRanges.arena()); } - return results; + co_return results; } } catch (Error& e) { - if (e.code() != error_code_wrong_shard_server && e.code() != error_code_all_alternatives_failed) { - TraceEvent(SevError, "GetReadHotSubRangesError").error(e); - throw; - } - cx->invalidateCache(keys); - wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution)); + err = e; + } + if (err.code() != error_code_wrong_shard_server && err.code() != error_code_all_alternatives_failed) { + TraceEvent(SevError, "GetReadHotSubRangesError").error(err); + throw err; } + cx->invalidateCache(keys); + co_await delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution); } } -ACTOR Future> waitStorageMetricsWithLocation(Version version, - KeyRange keys, - std::vector locations, - StorageMetrics min, - StorageMetrics max, - StorageMetrics permittedError) { +Future> waitStorageMetricsWithLocation(Version version, + KeyRange keys, + std::vector locations, + StorageMetrics min, + StorageMetrics max, + StorageMetrics permittedError) { Future fx; if (locations.size() > 1) { fx = waitStorageMetricsMultipleLocations(version, locations, min, max, permittedError); @@ -5893,39 +5865,40 @@ ACTOR Future> waitStorageMetricsWithLocation(Version ve req, TaskPriority::DataDistribution); } - StorageMetrics x = wait(fx); - return x; -} - -ACTOR Future, int>> waitStorageMetrics( - Database cx, - KeyRange keys, - StorageMetrics min, - StorageMetrics max, - StorageMetrics permittedError, - int shardLimit, - int expectedShardCount, - Optional> trState) { - state Span span("NAPI:WaitStorageMetrics"_loc, generateSpanID(cx->transactionTracingSample)); - loop { + StorageMetrics x = co_await fx; + co_return x; +} + +Future, int>> waitStorageMetrics(Database cx, + KeyRange keys, + StorageMetrics min, + StorageMetrics max, + StorageMetrics permittedError, + int shardLimit, + int expectedShardCount, + Optional> trState) { + Span span("NAPI:WaitStorageMetrics"_loc, generateSpanID(cx->transactionTracingSample)); + double startTime = now(); + int retryCount = 0; + while (true) { if (trState.present()) { - wait(trState.get()->startTransaction()); + co_await trState.get()->startTransaction(); } - state Version version = trState.present() ? trState.get()->readVersion() : latestVersion; - state std::vector locations = - wait(getKeyRangeLocations(cx, - keys, - shardLimit, - Reverse::False, - &StorageServerInterface::waitMetrics, - span.context, - Optional(), - UseProvisionalProxies::False, - version)); + Version version = trState.present() ? trState.get()->readVersion() : latestVersion; + std::vector locations = + co_await getKeyRangeLocations(cx, + keys, + shardLimit, + Reverse::False, + &StorageServerInterface::waitMetrics, + span.context, + Optional(), + UseProvisionalProxies::False, + version); if (expectedShardCount >= 0 && locations.size() != expectedShardCount) { // NOTE(xwang): This happens only when a split shard haven't been moved to another location. We may need to // change this if we allow split shard stay the same location. - return std::make_pair(Optional(), locations.size()); + co_return std::make_pair(Optional(), locations.size()); } // SOMEDAY: Right now, if there are too many shards we delay and check again later. There may be a better @@ -5936,29 +5909,39 @@ ACTOR Future, int>> waitStorageMetrics( .detail("Limit", shardLimit) .detail("LocationSize", locations.size()) .detail("JitteredSecondsOfPenitence", CLIENT_KNOBS->STORAGE_METRICS_TOO_MANY_SHARDS_DELAY); - wait(delayJittered(CLIENT_KNOBS->STORAGE_METRICS_TOO_MANY_SHARDS_DELAY, TaskPriority::DataDistribution)); + co_await delayJittered(CLIENT_KNOBS->STORAGE_METRICS_TOO_MANY_SHARDS_DELAY, TaskPriority::DataDistribution); // make sure that the next getKeyRangeLocations() call will actually re-fetch the range cx->invalidateCache(keys); continue; } + Error err; try { Optional res = - wait(waitStorageMetricsWithLocation(version, keys, locations, min, max, permittedError)); + co_await waitStorageMetricsWithLocation(version, keys, locations, min, max, permittedError); if (res.present()) { - return std::make_pair(res, -1); + co_return std::make_pair(res, -1); } + continue; } catch (Error& e) { - TraceEvent(SevDebug, "WaitStorageMetricsHandleError").error(e); - if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed) { - cx->invalidateCache(keys); - wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution)); - } else if (e.code() == error_code_future_version) { - wait(delay(CLIENT_KNOBS->FUTURE_VERSION_RETRY_DELAY, TaskPriority::DataDistribution)); - } else { - TraceEvent(SevError, "WaitStorageMetricsError").error(e); - throw; - } + err = e; + } + retryCount++; + // Upgrade from SevDebug to SevWarn after 60 seconds of retrying + Severity sev = (now() - startTime > 60.0) ? SevWarn : SevDebug; + TraceEvent(sev, "WaitStorageMetricsHandleError") + .error(err) + .detail("Keys", keys) + .detail("Elapsed", now() - startTime) + .detail("Retries", retryCount); + if (err.code() == error_code_wrong_shard_server || err.code() == error_code_all_alternatives_failed) { + cx->invalidateCache(keys); + co_await delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY, TaskPriority::DataDistribution); + } else if (err.code() == error_code_future_version) { + co_await delay(CLIENT_KNOBS->FUTURE_VERSION_RETRY_DELAY, TaskPriority::DataDistribution); + } else { + TraceEvent(SevError, "WaitStorageMetricsError").error(err); + throw err; } } } @@ -6000,23 +5983,12 @@ Future DatabaseContext::getStorageMetrics(KeyRange const& keys, } } -ACTOR Future>> waitDataDistributionMetricsList(Database cx, - KeyRange keys, - int shardLimit) { - loop { - choose { - when(wait(cx->onProxiesChanged())) {} - when(ErrorOr rep = - wait(errorOr(basicLoadBalance(cx->getCommitProxies(UseProvisionalProxies::False), - &CommitProxyInterface::getDDMetrics, - GetDDMetricsRequest(keys, shardLimit))))) { - if (rep.isError()) { - throw rep.getError(); - } - return rep.get().storageMetricsList; - } - } - } +Future>> waitDataDistributionMetricsList(Database cx, + KeyRange keys, + int shardLimit) { + GetDDMetricsReply rep = co_await commitProxyLoadBalance( + cx, makeReqBuilder(keys, shardLimit), &CommitProxyInterface::getDDMetrics); + co_return rep.storageMetricsList; } Future>> DatabaseContext::getReadHotRanges(KeyRange const& keys) { @@ -6081,10 +6053,10 @@ Future>> Transaction::getRangeSplitPoints(KeyRange return ::getRangeSplitPoints(trState, keys, chunkSize); } -ACTOR Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware) { - state ReadYourWritesTransaction tr(cx); - state Version version = invalidVersion; - loop { +Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware) { + ReadYourWritesTransaction tr(cx); + while (true) { + Error err; try { tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); if (lockAware) { @@ -6092,42 +6064,39 @@ ACTOR Future setPerpetualStorageWiggle(Database cx, bool enable, LockAw } tr.set(perpetualStorageWiggleKey, enable ? "1"_sr : "0"_sr); - wait(tr.commit()); - version = tr.getCommittedVersion(); - break; + co_await tr.commit(); + co_return tr.getCommittedVersion(); } catch (Error& e) { - wait(tr.onError(e)); + err = e; } + co_await tr.onError(err); } - return version; } -ACTOR Future>> readStorageWiggleValues(Database cx, - bool primary, - bool use_system_priority) { - state StorageWiggleData wiggleState; - state KeyBackedObjectMap metadataMap = - wiggleState.wigglingStorageServer(PrimaryRegion(primary)); +Future>> readStorageWiggleValues(Database cx, + bool primary, + bool use_system_priority) { + StorageWiggleData wiggleState; + auto metadataMap = wiggleState.wigglingStorageServer(PrimaryRegion(primary)); + auto tr = makeReference(cx); - state Reference tr(new ReadYourWritesTransaction(cx)); - state KeyBackedRangeResult> res; - - // read the wiggling pairs - loop { + while (true) { + Error err; try { + KeyBackedRangeResult> res; tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE); if (use_system_priority) { tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); } - wait(store(res, metadataMap.getRange(tr, UID(0, 0), Optional(), CLIENT_KNOBS->TOO_MANY))); - wait(tr->commit()); - break; + res = co_await metadataMap.getRange(tr, UID(0, 0), Optional(), CLIENT_KNOBS->TOO_MANY); + co_await tr->commit(); + co_return res.results; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - return res.results; } ACTOR Future splitStorageMetricsStream(PromiseStream resultStream, @@ -6383,22 +6352,14 @@ void enableClientInfoLogging() { TraceEvent(SevInfo, "ClientInfoLoggingEnabled").log(); } -ACTOR Future snapCreate(Database cx, Standalone snapCmd, UID snapUID) { +Future snapCreate(Database cx, Standalone snapCmd, UID snapUID) { TraceEvent("SnapCreateEnter").detail("SnapCmd", snapCmd).detail("UID", snapUID); try { - loop { - choose { - when(wait(cx->onProxiesChanged())) {} - when(wait(basicLoadBalance(cx->getCommitProxies(UseProvisionalProxies::False), - &CommitProxyInterface::proxySnapReq, - ProxySnapRequest(snapCmd, snapUID, snapUID), - cx->taskID, - AtMostOnce::True))) { - TraceEvent("SnapCreateExit").detail("SnapCmd", snapCmd).detail("UID", snapUID); - return Void(); - } - } - } + co_await commitProxyLoadBalance(cx, + makeReqBuilder(snapCmd, snapUID, snapUID), + &CommitProxyInterface::proxySnapReq, + AtMostOnce::True); + TraceEvent("SnapCreateExit").detail("SnapCmd", snapCmd).detail("UID", snapUID); } catch (Error& e) { TraceEvent("SnapCreateError").error(e).detail("SnapCmd", snapCmd.toString()).detail("UID", snapUID); throw; @@ -6470,20 +6431,20 @@ Future createCheckpoint(Transaction* tr, // Gets CheckpointMetaData of the specific keyrange, version and format from one of the storage servers, if none of the // servers have the checkpoint, a checkpoint_not_found error is returned. -ACTOR static Future getCheckpointMetaDataInternal(KeyRange range, - Version version, - CheckpointFormat format, - Optional actionId, - Reference alternatives, - double timeout) { +static Future getCheckpointMetaDataInternal(KeyRange range, + Version version, + CheckpointFormat format, + Optional actionId, + Reference alternatives, + double timeout) { TraceEvent(SevDebug, "GetCheckpointMetaDataInternalBegin") .detail("Range", range) .detail("Version", version) .detail("Format", static_cast(format)) .detail("Locations", alternatives->description()); - state std::vector>> futures; - state int index = 0; + std::vector>> futures; + int index = 0; for (index = 0; index < alternatives->size(); ++index) { // For each shard, all storage servers are checked, only one is required. futures.push_back(errorOr(timeoutError(alternatives->getInterface(index).checkpoint.getReply( @@ -6491,8 +6452,8 @@ ACTOR static Future getCheckpointMetaDataInternal(KeyRange r timeout))); } - state Optional error; - wait(waitForAll(futures)); + Optional error; + co_await waitForAll(futures); TraceEvent(SevDebug, "GetCheckpointMetaDataInternalWaitEnd").detail("Range", range).detail("Version", version); for (index = 0; index < futures.size(); ++index) { @@ -6516,7 +6477,7 @@ ACTOR static Future getCheckpointMetaDataInternal(KeyRange r error = e; } } else { - return futures[index].get().get(); + co_return futures[index].get().get(); } } @@ -6598,20 +6559,20 @@ ACTOR static Future>> getChe return res; } -ACTOR Future>> getCheckpointMetaData(Database cx, - std::vector ranges, - Version version, - CheckpointFormat format, - Optional actionId, - double timeout) { - state std::vector>>> futures; +Future>> getCheckpointMetaData(Database cx, + std::vector ranges, + Version version, + CheckpointFormat format, + Optional actionId, + double timeout) { + std::vector>>> futures; // TODO(heliu): Avoid send requests to the same shard. for (const auto& range : ranges) { futures.push_back(getCheckpointMetaDataForRange(cx, range, version, format, actionId, timeout)); } - std::vector>> results = wait(getAll(futures)); + std::vector>> results = co_await getAll(futures); std::vector> res; @@ -6620,28 +6581,20 @@ ACTOR Future>> getCheckpoint res.insert(res.end(), r.begin(), r.end()); } - return res; + co_return res; } -ACTOR Future checkSafeExclusions(Database cx, std::vector exclusions) { +Future checkSafeExclusions(Database cx, std::vector exclusions) { TraceEvent("ExclusionSafetyCheckBegin") .detail("NumExclusion", exclusions.size()) .detail("Exclusions", describe(exclusions)); - state bool ddCheck; + bool ddCheck{ false }; try { - loop { - choose { - when(wait(cx->onProxiesChanged())) {} - when(ExclusionSafetyCheckReply _ddCheck = - wait(basicLoadBalance(cx->getCommitProxies(UseProvisionalProxies::False), - &CommitProxyInterface::exclusionSafetyCheckReq, - ExclusionSafetyCheckRequest(exclusions), - cx->taskID))) { - ddCheck = _ddCheck.safe; - break; - } - } - } + ExclusionSafetyCheckReply _ddCheck = + co_await commitProxyLoadBalance(cx, + makeReqBuilder(exclusions), + &CommitProxyInterface::exclusionSafetyCheckReq); + ddCheck = _ddCheck.safe; } catch (Error& e) { if (e.code() != error_code_actor_cancelled) { TraceEvent("ExclusionSafetyCheckError") @@ -6652,32 +6605,30 @@ ACTOR Future checkSafeExclusions(Database cx, std::vectorgetConnectionRecord()); - state std::vector>> leaderServers; + ClientCoordinators coordinatorList(cx->getConnectionRecord()); + std::vector>> leaderServers; leaderServers.reserve(coordinatorList.clientLeaderServers.size()); - for (int i = 0; i < coordinatorList.clientLeaderServers.size(); i++) { - if (coordinatorList.clientLeaderServers[i].hostname.present()) { + for (const auto& clientLeaderServer : coordinatorList.clientLeaderServers) { + if (clientLeaderServer.hostname.present()) { leaderServers.push_back(retryGetReplyFromHostname(GetLeaderRequest(coordinatorList.clusterKey, UID()), - coordinatorList.clientLeaderServers[i].hostname.get(), + clientLeaderServer.hostname.get(), WLTOKEN_CLIENTLEADERREG_GETLEADER, TaskPriority::CoordinationReply)); } else { - leaderServers.push_back(retryBrokenPromise(coordinatorList.clientLeaderServers[i].getLeader, + leaderServers.push_back(retryBrokenPromise(clientLeaderServer.getLeader, GetLeaderRequest(coordinatorList.clusterKey, UID()), TaskPriority::CoordinationReply)); } } // Wait for quorum so we don't dismiss live coordinators as unreachable by acting too fast - choose { - when(wait(smartQuorum(leaderServers, leaderServers.size() / 2 + 1, 1.0))) {} - when(wait(delay(3.0))) { - TraceEvent("ExclusionSafetyCheckNoCoordinatorQuorum").log(); - return false; - } + auto res = co_await race(smartQuorum(leaderServers, leaderServers.size() / 2 + 1, 1.0), delay(3.0)); + if (res.index() == 1) { + TraceEvent("ExclusionSafetyCheckNoCoordinatorQuorum").log(); + co_return false; } int attemptCoordinatorExclude = 0; int coordinatorsUnavailable = 0; - for (int i = 0; i < leaderServers.size(); i++) { + for (int i = 0; i < leaderServers.size(); ++i) { NetworkAddress leaderAddress = coordinatorList.clientLeaderServers[i].getLeader.getEndpoint().getPrimaryAddress(); if (leaderServers[i].isReady()) { @@ -6700,7 +6651,7 @@ ACTOR Future checkSafeExclusions(Database cx, std::vector verifyInterfaceActor(Reference connectLock, ClientW } } -ACTOR static Future rebootWorkerActor(DatabaseContext* cx, ValueRef addr, bool check, int duration) { +static Future rebootWorkerActor(DatabaseContext* cx, ValueRef addr, bool check, int duration) { // ignore negative value if (duration < 0) duration = 0; if (!cx->getConnectionRecord()) - return 0; + co_return 0; // fetch all workers' addresses and interfaces from CC - RangeResult kvs = wait(getWorkerInterfaces(cx->getConnectionRecord())); + RangeResult kvs = co_await getWorkerInterfaces(cx->getConnectionRecord()); ASSERT(!kvs.more); // map worker network address to its interface - state std::map workerInterfaces; + std::map workerInterfaces; for (const auto& it : kvs) { - ClientWorkerInterface workerInterf = - BinaryReader::fromStringRef(it.value, IncludeVersion()); + auto workerInterf = BinaryReader::fromStringRef(it.value, IncludeVersion()); Key primaryAddress = it.key.endsWith(":tls"_sr) ? it.key.removeSuffix(":tls"_sr) : it.key; workerInterfaces[primaryAddress] = workerInterf; // Also add mapping from a worker's second address(if present) to its interface @@ -6745,30 +6695,30 @@ ACTOR static Future rebootWorkerActor(DatabaseContext* cx, ValueRef add } } // split and get all the requested addresses to send reboot requests - state std::vector addressesVec; + std::vector addressesVec; boost::algorithm::split(addressesVec, addr.toString(), boost::is_any_of(",")); // Note: reuse this knob from fdbcli, change it if necessary Reference connectLock(new FlowLock(CLIENT_KNOBS->CLI_CONNECT_PARALLELISM)); - state std::vector> verifyInterfs; + std::vector> verifyInterfs; for (const auto& requestedAddress : addressesVec) { // step 1: check that the requested address is in the worker list provided by CC if (!workerInterfaces.count(Key(requestedAddress))) - return 0; + co_return 0; // step 2: try to establish connections to the requested worker verifyInterfs.push_back(verifyInterfaceActor(connectLock, workerInterfaces[Key(requestedAddress)])); } // step 3: check if we can establish connections to all requested workers, return if not - wait(waitForAll(verifyInterfs)); + co_await waitForAll(verifyInterfs); for (const auto& f : verifyInterfs) { if (!f.get()) - return 0; + co_return 0; } // step 4: After verifying we can connect to all requested workers, send reboot requests together for (const auto& address : addressesVec) { // Note: We want to make sure these requests are sent in parallel workerInterfaces[Key(address)].reboot.send(RebootRequest(false, check, duration)); } - return 1; + co_return 1; } Future DatabaseContext::rebootWorker(StringRef addr, bool check, int duration) { @@ -6779,9 +6729,8 @@ Future DatabaseContext::forceRecoveryWithDataLoss(StringRef dcId) { return forceRecovery(getConnectionRecord(), dcId); } -ACTOR static Future createSnapshotActor(DatabaseContext* cx, UID snapUID, StringRef snapCmd) { - wait(mgmtSnapCreate(cx->clone(), snapCmd, snapUID)); - return Void(); +static Future createSnapshotActor(DatabaseContext* cx, UID snapUID, StringRef snapCmd) { + co_await mgmtSnapCreate(cx->clone(), snapCmd, snapUID); } Future DatabaseContext::createSnapshot(StringRef uid, StringRef snapshot_command) { @@ -6802,7 +6751,7 @@ void sharedStateDelRef(DatabaseSharedState* ssPtr) { Future DatabaseContext::initSharedState() { ASSERT(!sharedStatePtr); // Don't re-initialize shared state if a pointer already exists - DatabaseSharedState* newState = new DatabaseSharedState(); + auto* newState = new DatabaseSharedState(); // Increment refcount by 1 on creation to account for the one held in MultiVersionApi map // Therefore, on initialization, refCount should be 2 (after also going to setSharedState) newState->refCount++; @@ -6821,17 +6770,18 @@ Reference DatabaseContext::createTransaction() { return makeReference(Database(Reference::addRef(this))); } -ACTOR static Future>> -getHotRangeMetricsActor(Reference db, StorageServerInterface ssi, ReadHotSubRangeRequest req) { +static Future>> getHotRangeMetricsActor(Reference db, + StorageServerInterface ssi, + ReadHotSubRangeRequest req) { - ErrorOr fs = wait(ssi.getReadHotRanges.tryGetReply(req)); + ErrorOr fs = co_await ssi.getReadHotRanges.tryGetReply(req); if (fs.isError()) { fmt::print("Error({}): cannot get read hot metrics from storage server {}.\n", fs.getError().what(), ssi.address().toString()); - return Standalone>(); + co_return Standalone>(); } else { - return fs.get().readHotRanges; + co_return fs.get().readHotRanges; } } @@ -6863,24 +6813,23 @@ int64_t getMaxClearKeySize(KeyRef const& key) { namespace NativeAPI { -ACTOR Future>> getServerListAndProcessClasses( - Transaction* tr) { - state Future> workers = getWorkers(tr); - state Future serverList = tr->getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); - wait(success(workers) && success(serverList)); +Future>> getServerListAndProcessClasses(Transaction* tr) { + Future> workers = getWorkers(tr); + Future serverList = tr->getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); + co_await (success(workers) && success(serverList)); ASSERT(!serverList.get().more && serverList.get().size() < CLIENT_KNOBS->TOO_MANY); std::map>, ProcessData> id_data; - for (int i = 0; i < workers.get().size(); i++) - id_data[workers.get()[i].locality.processId()] = workers.get()[i]; + for (const auto& worker : workers.get()) + id_data[worker.locality.processId()] = worker; std::vector> results; - for (int i = 0; i < serverList.get().size(); i++) { - auto ssi = decodeServerListValue(serverList.get()[i].value); + for (const auto& server : serverList.get()) { + auto ssi = decodeServerListValue(server.value); results.emplace_back(ssi, id_data[ssi.locality.processId()].processClass); } - return results; + co_return results; } } // namespace NativeAPI diff --git a/fdbclient/ParallelStream.actor.cpp b/fdbclient/ParallelStream.actor.cpp deleted file mode 100644 index 147c66f0161..00000000000 --- a/fdbclient/ParallelStream.actor.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * ParallelStreamCorrectness.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. - */ - -#include - -#include "fdbclient/ParallelStream.actor.h" -#include "flow/UnitTest.h" -#include "flow/actorcompiler.h" // This must be the last #include. - -namespace ParallelStreamTest { - -struct TestValue { - int x; - TestValue(int x) : x(x) {} - int expectedSize() const { return sizeof(int); } -}; - -ACTOR static Future produce(ParallelStream::Fragment* fragment, - ParallelStreamTest::TestValue value) { - wait(delay(deterministicRandom()->random01())); - fragment->send(value); - wait(delay(deterministicRandom()->random01())); - fragment->finish(); - return Void(); -} - -ACTOR static Future consume(FutureStream stream, int expected) { - state int next = 0; - try { - loop { - ParallelStreamTest::TestValue value = waitNext(stream); - ASSERT(value.x == next++); - } - } catch (Error& e) { - ASSERT(e.code() == error_code_end_of_stream); - ASSERT(next == expected); - return Void(); - } -} - -} // namespace ParallelStreamTest - -TEST_CASE("/fdbclient/ParallelStream") { - state PromiseStream results; - state size_t bufferLimit = deterministicRandom()->randomInt(0, 21); - state size_t numProducers = deterministicRandom()->randomInt(1, 1001); - state ParallelStream parallelStream(results, bufferLimit); - state Future consumer = ParallelStreamTest::consume(results.getFuture(), numProducers); - state std::vector> producers; - TraceEvent("StartingParallelStreamTest").detail("BufferLimit", bufferLimit).detail("NumProducers", numProducers); - state int i = 0; - for (; i < numProducers; ++i) { - ParallelStream::Fragment* fragment = wait(parallelStream.createFragment()); - producers.push_back(ParallelStreamTest::produce(fragment, ParallelStreamTest::TestValue(i))); - } - wait(parallelStream.finish()); - wait(consumer); - return Void(); -} - -void forceLinkParallelStreamTests() {} diff --git a/fdbclient/include/fdbclient/PartitionedLogIterator.h b/fdbclient/PartitionedLogIterator.h similarity index 99% rename from fdbclient/include/fdbclient/PartitionedLogIterator.h rename to fdbclient/PartitionedLogIterator.h index 03c6f6d194a..67e76f1698c 100644 --- a/fdbclient/include/fdbclient/PartitionedLogIterator.h +++ b/fdbclient/PartitionedLogIterator.h @@ -25,4 +25,4 @@ class PartitionedLogIterator : public ReferenceCounted { virtual ~PartitionedLogIterator() = default; }; -#endif \ No newline at end of file +#endif diff --git a/fdbclient/ProxyLoadBalance.h b/fdbclient/ProxyLoadBalance.h new file mode 100644 index 00000000000..895a5635eb2 --- /dev/null +++ b/fdbclient/ProxyLoadBalance.h @@ -0,0 +1,95 @@ +/* + * ProxyLoadBalance.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 +#include +#include + +#include "fdbclient/CommitProxyInterface.h" +#include "fdbclient/GrvProxyInterface.h" +#include "fdbclient/NativeAPI.actor.h" +#include "fdbrpc/LoadBalance.actor.h" + +// Stores constructor arguments so a request can be rebuilt on each retry. +template +class ReqBuilder { + std::tuple args; + +public: + explicit ReqBuilder(Args... args) : args(std::move(args)...) {} + + Req build() const { + return std::apply([](auto const&... unpackedArgs) { return Req(unpackedArgs...); }, args); + } +}; + +// Infers the stored argument types while ensuring the request is constructible. +template +ReqBuilder...> makeReqBuilder(Args&&... args) { + static_assert(std::is_constructible_v...>); + return ReqBuilder...>(std::forward(args)...); +} + +// Retries a commit-proxy request whenever the proxy set changes before a reply arrives. +template +AsyncResult commitProxyLoadBalance(Database cx, + Builder reqBuilder, + RequestStream CommitProxyInterface::* channel, + UseProvisionalProxies useProvisionalProxies, + TaskPriority taskID, + AtMostOnce atMostOnce = AtMostOnce::False, + ExplicitVoid = {}) { + while (true) { + Future replyFuture = basicLoadBalance( + cx->getCommitProxies(useProvisionalProxies), channel, reqBuilder.build(), taskID, atMostOnce); + auto res = co_await race(replyFuture, cx->onProxiesChanged()); + if (res.index() == 0) { + co_return std::get<0>(std::move(res)); + } + } +} + +template +AsyncResult commitProxyLoadBalance(Database cx, + Builder reqBuilder, + RequestStream CommitProxyInterface::* channel, + AtMostOnce atMostOnce = AtMostOnce::False, + ExplicitVoid = {}) { + return commitProxyLoadBalance(cx, reqBuilder, channel, UseProvisionalProxies::False, cx->taskID, atMostOnce); +} + +// Retries a GRV-proxy request whenever the proxy set changes before a reply arrives. +template +AsyncResult grvProxyLoadBalance(Database cx, + Builder reqBuilder, + RequestStream GrvProxyInterface::* channel, + AtMostOnce atMostOnce = AtMostOnce::False, + ExplicitVoid = {}) { + while (true) { + Future replyFuture = basicLoadBalance( + cx->getGrvProxies(UseProvisionalProxies::False), channel, reqBuilder.build(), cx->taskID, atMostOnce); + auto res = co_await race(replyFuture, cx->onProxiesChanged()); + if (res.index() == 0) { + co_return std::get<0>(std::move(res)); + } + } +} diff --git a/fdbclient/RESTClient.actor.cpp b/fdbclient/RESTClient.cpp similarity index 88% rename from fdbclient/RESTClient.actor.cpp rename to fdbclient/RESTClient.cpp index 4fa5ffe9614..e46ae6ec101 100644 --- a/fdbclient/RESTClient.actor.cpp +++ b/fdbclient/RESTClient.cpp @@ -1,5 +1,5 @@ /* - * RESTClient.actor.cpp + * RESTClient.cpp * * This source file is part of the FoundationDB open source project * @@ -18,11 +18,11 @@ * limitations under the License. */ -#include "fdbclient/RESTClient.h" +#include "RESTClient.h" #include "fdbrpc/HTTP.h" #include "flow/IRateControl.h" -#include "fdbclient/RESTUtils.h" +#include "RESTUtils.h" #include "flow/Arena.h" #include "flow/Error.h" #include "flow/FastRef.h" @@ -38,8 +38,6 @@ #include #include -#include "flow/actorcompiler.h" // always the last include - #define TRACE_REST_OP(opName, url) \ do { \ if (FLOW_KNOBS->REST_LOG_LEVEL >= RESTLogSeverity::DEBUG) { \ @@ -94,14 +92,14 @@ bool isErrorRetryable(const Error& e) { return e.code() != error_code_timed_out && e.code() != error_code_connection_failed; } -ACTOR Future> doRequest_impl(Reference client, - std::string verb, - HTTP::Headers headers, - RESTUrl url, - std::set successCodes) { +Future> doRequest_impl(Reference client, + std::string verb, + HTTP::Headers headers, + RESTUrl url, + std::set successCodes) { - state Reference req = makeReference(); - state UnsentPacketQueue content; + auto req = makeReference(); + UnsentPacketQueue content; req->data.content = &content; req->data.contentLen = url.body.size(); req->data.headers = headers; @@ -113,7 +111,7 @@ ACTOR Future> doRequest_impl(Reference 0) { + if (!url.body.empty()) { PacketWriter pw(req->data.content->getWriteBuffer(url.body.size()), nullptr, Unversioned()); pw.serializeBytes(url.body); } @@ -124,21 +122,21 @@ ACTOR Future> doRequest_impl(ReferencestatsMap.emplace(statsKey, std::make_unique(statsKey)); } - state int maxTries = std::min(client->knobs.request_tries, client->knobs.connect_tries); - state int thisTry = 1; - state double nextRetryDelay = 2.0; - state Reference sendReceiveRate = makeReference(); - state double reqTimeout = (client->knobs.request_timeout_secs * 1.0); - state RESTConnectionPoolKey connectPoolKey = RESTConnectionPool::getConnectionPoolKey(url.host, url.service); - state RESTClient::Stats* statsPtr = client->statsMap[statsKey].get(); + int maxTries = std::min(client->knobs.request_tries, client->knobs.connect_tries); + int thisTry = 1; + double nextRetryDelay = 2.0; + Reference sendReceiveRate = makeReference(); + double reqTimeout = (client->knobs.request_timeout_secs * 1.0); + RESTConnectionPoolKey connectPoolKey = RESTConnectionPool::getConnectionPoolKey(url.host, url.service); + RESTClient::Stats* statsPtr = client->statsMap[statsKey].get(); - loop { - state Optional err; - state Optional remoteAddress; - state bool connectionEstablished = false; + while (true) { + Optional err; + Optional remoteAddress; + bool connectionEstablished = false; - state Reference r; - state RESTConnectionPool::ReusableConnection rconn; + Reference r; + RESTConnectionPool::ReusableConnection rconn; try { // Start connecting @@ -146,14 +144,12 @@ ACTOR Future> doRequest_impl(ReferenceconectionPool->connect(connectPoolKey, url.connType.secure, client->knobs.max_connection_life); // Finish connecting, do request - wait(store(rconn, timeoutError(frconn, client->knobs.connect_timeout))); + rconn = co_await timeoutError(frconn, client->knobs.connect_timeout); connectionEstablished = true; remoteAddress = rconn.conn->getPeerAddress(); - wait(store( - r, - timeoutError(HTTP::doRequest(rconn.conn, req, sendReceiveRate, &statsPtr->bytes_sent, sendReceiveRate), - reqTimeout))); + r = co_await timeoutError( + HTTP::doRequest(rconn.conn, req, sendReceiveRate, &statsPtr->bytes_sent, sendReceiveRate), reqTimeout); // Since the response was parsed successfully (which is why we are here) reuse the connection unless we // received the "Connection: close" header. @@ -178,9 +174,9 @@ ACTOR Future> doRequest_impl(Referencecode is in successCodes then record the successful request and return r. - if (!err.present() && successCodes.count(r->code) != 0) { + if (!err.present() && successCodes.contains(r->code)) { statsPtr->requests_successful++; - return r; + co_return r; } // Otherwise, this request is considered failed. Update failure count. @@ -249,7 +245,7 @@ ACTOR Future> doRequest_impl(Reference #include -#include "flow/actorcompiler.h" // always the last include - // RESTConnectionPool destructor implementation RESTConnectionPool::~RESTConnectionPool() { // In simulation, explicitly close all pooled connections before destruction. @@ -125,10 +123,10 @@ std::unordered_map RESTClientKnobs::get() const { return details; } -ACTOR Future connect_impl(Reference connectionPool, - RESTConnectionPoolKey connectKey, - bool isSecure, - int maxConnLife) { +Future connect_impl(Reference connectionPool, + RESTConnectionPoolKey connectKey, + bool isSecure, + int maxConnLife) { if (FLOW_KNOBS->REST_LOG_LEVEL >= RESTLogSeverity::VERBOSE) { TraceEvent("RESTUtilConnectStart") @@ -152,16 +150,16 @@ ACTOR Future connect_impl(Referencesecond.size()); } - return rconn; + co_return rconn; } } ASSERT(poolItr == connectionPool->connectionPoolMap.end() || poolItr->second.empty()); // No valid connection exists, create a new one - state Reference conn = - wait(INetworkConnections::net()->connect(connectKey.first, connectKey.second, isSecure)); - wait(conn->connectHandshake()); + Reference conn = + co_await INetworkConnections::net()->connect(connectKey.first, connectKey.second, isSecure); + co_await conn->connectHandshake(); TraceEvent("RESTTUilCreateNewConn") .suppressFor(60) @@ -170,7 +168,7 @@ ACTOR Future connect_impl(ReferencegetPeerAddress()) .detail("ConnPoolSize", connectionPool->connectionPoolMap.size()); - return RESTConnectionPool::ReusableConnection({ conn, now() + maxConnLife }); + co_return RESTConnectionPool::ReusableConnection({ conn, now() + maxConnLife }); } Future RESTConnectionPool::connect(RESTConnectionPoolKey connectKey, @@ -252,7 +250,7 @@ void RESTUrl::parseUrl(const std::string& fullUrl) { // hostPort is at least a host or IP address, optionally followed by :portNumber or :serviceName StringRef hRef(hostPort); StringRef h = hRef.eat(":"); - if (h.size() == 0) { + if (h.empty()) { CODE_PROBE(true, "REST URI empty host"); throw std::string("host cannot be empty"); } diff --git a/fdbclient/include/fdbclient/RESTUtils.h b/fdbclient/RESTUtils.h similarity index 97% rename from fdbclient/include/fdbclient/RESTUtils.h rename to fdbclient/RESTUtils.h index 08ab804cfba..af8c65f4b7a 100644 --- a/fdbclient/include/fdbclient/RESTUtils.h +++ b/fdbclient/RESTUtils.h @@ -59,9 +59,9 @@ class RESTConnectionPool : public ReferenceCounted { std::unordered_map, boost::hash> connectionPoolMap; - RESTConnectionPool(const int maxConnsPerKey) : maxConnPerConnectKey(maxConnsPerKey) {} + explicit RESTConnectionPool(const int maxConnsPerKey) : maxConnPerConnectKey(maxConnsPerKey) {} - // Destructor implementation in RESTUtils.actor.cpp + // Destructor implementation in RESTUtils.cpp // In simulation, explicitly closes all pooled connections before destruction ~RESTConnectionPool(); diff --git a/fdbclient/ReadYourWrites.actor.cpp b/fdbclient/ReadYourWrites.actor.cpp index 196a22d5427..8483b287766 100644 --- a/fdbclient/ReadYourWrites.actor.cpp +++ b/fdbclient/ReadYourWrites.actor.cpp @@ -22,9 +22,10 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/Atomic.h" #include "fdbclient/DatabaseContext.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/SpecialKeySpace.h" #include "fdbclient/StatusClient.h" #include "fdbclient/MonitorLeader.h" +#include "flow/CoroUtils.h" #include "flow/Util.h" #include "flow/actorcompiler.h" // This must be the last #include. @@ -57,13 +58,13 @@ class RYWImpl { struct GetValueReq { explicit GetValueReq(Key key) : key(key) {} Key key; - typedef Optional Result; + using Result = Optional; }; struct GetKeyReq { explicit GetKeyReq(KeySelector key) : key(key) {} KeySelector key; - typedef Key Result; + using Result = Key; }; template @@ -91,7 +92,7 @@ class RYWImpl { // to update the given *it to point to the key that was read, so that the corresponding overload of // addConflictRange() can make use of it. - ACTOR template + template static Future> read(ReadYourWritesTransaction* ryw, GetValueReq read, Iter* it) { // This overload is required to provide postcondition: it->extractWriteMapIterator().segmentContains(read.key) @@ -99,29 +100,29 @@ class RYWImpl { it->bypassUnreadableProtection(); } it->skip(read.key); - state bool dependent = it->is_dependent(); + bool dependent = it->is_dependent(); if (it->is_kv()) { const KeyValueRef* result = it->kv(ryw->arena); if (result != nullptr) { - return result->value; + co_return result->value; } else { - return Optional(); + co_return Optional(); } } else if (it->is_empty_range()) { - return Optional(); + co_return Optional(); } else { - Optional res = wait(ryw->tr.get(read.key, Snapshot::True)); + Optional res = co_await ryw->tr.get(read.key, Snapshot::True); KeyRef k(ryw->arena, read.key); if (res.present()) { if (ryw->cache.insert(k, res.get())) ryw->arena.dependsOn(res.get().arena()); if (!dependent) - return res; + co_return res; } else { ryw->cache.insert(k, Optional()); if (!dependent) - return Optional(); + co_return Optional(); } // There was a dependent write at the key, so we need to lookup the iterator again @@ -130,32 +131,32 @@ class RYWImpl { ASSERT(it->is_kv()); const KeyValueRef* result = it->kv(ryw->arena); if (result != nullptr) { - return result->value; + co_return result->value; } else { - return Optional(); + co_return Optional(); } } } - ACTOR template + template static Future read(ReadYourWritesTransaction* ryw, GetKeyReq read, Iter* it) { if (read.key.offset > 0) { RangeResult result = - wait(getRangeValue(ryw, read.key, firstGreaterOrEqual(ryw->getMaxReadKey()), GetRangeLimits(1), it)); + co_await getRangeValue(ryw, read.key, firstGreaterOrEqual(ryw->getMaxReadKey()), GetRangeLimits(1), it); if (result.readToBegin) - return allKeys.begin; + co_return allKeys.begin; if (result.readThroughEnd || !result.size()) - return ryw->getMaxReadKey(); - return result[0].key; + co_return ryw->getMaxReadKey(); + co_return result[0].key; } else { read.key.offset++; RangeResult result = - wait(getRangeValueBack(ryw, firstGreaterOrEqual(allKeys.begin), read.key, GetRangeLimits(1), it)); + co_await getRangeValueBack(ryw, firstGreaterOrEqual(allKeys.begin), read.key, GetRangeLimits(1), it); if (result.readThroughEnd) - return ryw->getMaxReadKey(); + co_return ryw->getMaxReadKey(); if (result.readToBegin || !result.size()) - return allKeys.begin; - return result[0].key; + co_return allKeys.begin; + co_return result[0].key; } }; @@ -177,41 +178,41 @@ class RYWImpl { return ryw->tr.get(read.key, snapshot); } - ACTOR static Future readThrough(ReadYourWritesTransaction* ryw, GetKeyReq read, Snapshot snapshot) { - Key key = wait(ryw->tr.getKey(read.key, snapshot)); + static Future readThrough(ReadYourWritesTransaction* ryw, GetKeyReq read, Snapshot snapshot) { + Key key = co_await ryw->tr.getKey(read.key, snapshot); if (ryw->getMaxReadKey() < key) - return ryw->getMaxReadKey(); // Filter out results in the system keys if they are not accessible - return key; + co_return ryw->getMaxReadKey(); // Filter out results in the system keys if they are not accessible + co_return key; } - ACTOR template + template static Future readThrough(ReadYourWritesTransaction* ryw, GetRangeReq read, Snapshot snapshot) { if (backwards && read.end.offset > 1) { // FIXME: Optimistically assume that this will not run into the system keys, and only reissue if the result // actually does. - Key key = wait(ryw->tr.getKey(read.end, snapshot)); + Key key = co_await ryw->tr.getKey(read.end, snapshot); if (key > ryw->getMaxReadKey()) read.end = firstGreaterOrEqual(ryw->getMaxReadKey()); else read.end = KeySelector(firstGreaterOrEqual(key), key.arena()); } - RangeResult v = wait( - ryw->tr.getRange(read.begin, read.end, read.limits, snapshot, backwards ? Reverse::True : Reverse::False)); + 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 (!backwards && v[v.size() - 1].key >= maxKey) { - state RangeResult _v = v; + RangeResult _v = v; int i = _v.size() - 2; for (; i >= 0 && _v[i].key >= maxKey; --i) { } - return RangeResult(RangeResultRef(VectorRef(&_v[0], i + 1), false), _v.arena()); + co_return RangeResult(RangeResultRef(VectorRef(&_v[0], i + 1), false), _v.arena()); } } - return v; + co_return v; } // addConflictRange(ryw,read,result) is called after a serializable read and is responsible for adding the relevant @@ -590,45 +591,45 @@ class RYWImpl { } // TODO: read to begin, read through end flags for result - ACTOR template + template static Future getRangeValue(ReadYourWritesTransaction* ryw, KeySelector begin, KeySelector end, GetRangeLimits limits, Iter* pit) { - state Iter& it(*pit); - state Iter itEnd(*pit); - state RangeResult result; - state int64_t additionalRows = 0; - state int itemsPastEnd = 0; - state int requestCount = 0; - state bool readToBegin = false; - state bool readThroughEnd = false; - state int actualBeginOffset = begin.offset; - state int actualEndOffset = end.offset; + Iter& it(*pit); + Iter itEnd(*pit); + RangeResult result; + int64_t additionalRows = 0; + int itemsPastEnd = 0; + int requestCount = 0; + bool readToBegin = false; + bool readThroughEnd = false; + int actualBeginOffset = begin.offset; + int actualEndOffset = end.offset; // state UID randomID = nondeterministicRandom()->randomUniqueID(); resolveKeySelectorFromCache(begin, it, ryw->getMaxReadKey(), &readToBegin, &readThroughEnd, &actualBeginOffset); resolveKeySelectorFromCache(end, itEnd, ryw->getMaxReadKey(), &readToBegin, &readThroughEnd, &actualEndOffset); if (actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } else if ((begin.isFirstGreaterOrEqual() && begin.getKey() == ryw->getMaxReadKey()) || (end.isFirstGreaterOrEqual() && end.getKey() == allKeys.begin)) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if (!end.isFirstGreaterOrEqual() && begin.getKey() > end.getKey()) { - Key resolvedEnd = wait(read(ryw, GetKeyReq(end), pit)); + Key resolvedEnd = co_await read(ryw, GetKeyReq(end), pit); if (resolvedEnd == allKeys.begin) readToBegin = true; if (resolvedEnd == ryw->getMaxReadKey()) readThroughEnd = true; if (begin.getKey() >= resolvedEnd && !begin.isBackward()) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } else if (resolvedEnd == allKeys.begin) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } resolveKeySelectorFromCache( @@ -639,7 +640,7 @@ class RYWImpl { //TraceEvent("RYWSelectorsStartForward", randomID).detail("ByteLimit", limits.bytes).detail("RowLimit", limits.rows); - loop { + while (true) { /*TraceEvent("RYWSelectors", randomID).detail("Begin", begin.toString()) .detail("End", end.toString()) .detail("Reached", limits.isReached()) @@ -651,11 +652,11 @@ class RYWImpl { .detail("Requests", requestCount);*/ if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } if (end.offset <= 1 && end.getKey() == allKeys.begin) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if ((begin.offset >= end.offset && begin.getKey() >= end.getKey()) || @@ -664,10 +665,10 @@ class RYWImpl { break; if (!result.size()) break; - Key resolvedEnd = - wait(read(ryw, - GetKeyReq(end), - pit)); // do not worry about iterator invalidation, because we are breaking for the loop + Key resolvedEnd = co_await read( + ryw, + GetKeyReq(end), + pit); // do not worry about iterator invalidation, because we are breaking for the loop if (resolvedEnd == allKeys.begin) readToBegin = true; if (resolvedEnd == ryw->getMaxReadKey()) @@ -679,7 +680,7 @@ class RYWImpl { if (!it.is_unreadable() && !it.is_unknown_range() && it.beginKey() > itEnd.beginKey()) { if (end.isFirstGreaterOrEqual()) break; - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if (limits.isReached() && itemsPastEnd >= 1 - end.offset) @@ -702,7 +703,7 @@ class RYWImpl { if (it.beginKey() < itEnd.beginKey()) singleClears = std::min(skipUncached(ucEnd, itEnd, BUGGIFY ? 0 : clearLimit + 100), clearLimit); - state KeySelector read_end; + KeySelector read_end; if (ucEnd != itEnd) { Key k = ucEnd.endKey().toStandaloneStringRef(); read_end = KeySelector(firstGreaterOrEqual(k), k.arena()); @@ -722,7 +723,7 @@ class RYWImpl { additionalRows += singleClears; - state KeySelector read_begin; + KeySelector read_begin; if (begin.isFirstGreaterOrEqual()) { Key k = it.beginKey() > begin.getKey() ? it.beginKey().toStandaloneStringRef() : Key(begin.getKey(), begin.arena()); @@ -745,7 +746,7 @@ class RYWImpl { read_end.arena().dependsOn(read_begin.arena()); } - state GetRangeLimits requestLimit = limits; + GetRangeLimits requestLimit = limits; setRequestLimits(requestLimit, additionalRows, 2 - read_begin.offset, requestCount); requestCount++; @@ -756,7 +757,7 @@ class RYWImpl { additionalRows = 0; RangeResult snapshot_read = - wait(ryw->tr.getRange(read_begin, read_end, requestLimit, Snapshot::True, Reverse::False)); + co_await ryw->tr.getRange(read_begin, read_end, requestLimit, Snapshot::True, Reverse::False); KeyRangeRef range = getKnownKeyRange(snapshot_read, read_begin, read_end, ryw->arena); //TraceEvent("RYWCacheInsert", randomID).detail("Range", range).detail("ExpectedSize", snapshot_read.expectedSize()).detail("Rows", snapshot_read.size()).detail("Results", snapshot_read).detail("More", snapshot_read.more).detail("ReadToBegin", snapshot_read.readToBegin).detail("ReadThroughEnd", snapshot_read.readThroughEnd).detail("ReadThrough", snapshot_read.readThrough); @@ -810,7 +811,7 @@ class RYWImpl { result.readThroughEnd = !result.more && readThroughEnd; result.arena().dependsOn(ryw->arena); - return result; + co_return result; } static KeyRangeRef getKnownKeyRangeBack(RangeResultRef data, KeySelector begin, KeySelector end, Arena& arena) { @@ -893,22 +894,22 @@ class RYWImpl { return singleEmpty; } - ACTOR template + template static Future getRangeValueBack(ReadYourWritesTransaction* ryw, KeySelector begin, KeySelector end, GetRangeLimits limits, Iter* pit) { - state Iter& it(*pit); - state Iter itEnd(*pit); - state RangeResult result; - state int64_t additionalRows = 0; - state int itemsPastBegin = 0; - state int requestCount = 0; - state bool readToBegin = false; - state bool readThroughEnd = false; - state int actualBeginOffset = begin.offset; - state int actualEndOffset = end.offset; + Iter& it(*pit); + Iter itEnd(*pit); + RangeResult result; + int64_t additionalRows = 0; + int itemsPastBegin = 0; + int requestCount = 0; + bool readToBegin = false; + bool readThroughEnd = false; + int actualBeginOffset = begin.offset; + int actualEndOffset = end.offset; // state UID randomID = nondeterministicRandom()->randomUniqueID(); resolveKeySelectorFromCache(end, it, ryw->getMaxReadKey(), &readToBegin, &readThroughEnd, &actualEndOffset); @@ -916,23 +917,23 @@ class RYWImpl { begin, itEnd, ryw->getMaxReadKey(), &readToBegin, &readThroughEnd, &actualBeginOffset); if (actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } else if ((begin.isFirstGreaterOrEqual() && begin.getKey() == ryw->getMaxReadKey()) || (end.isFirstGreaterOrEqual() && end.getKey() == allKeys.begin)) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if (!begin.isFirstGreaterOrEqual() && begin.getKey() > end.getKey()) { - Key resolvedBegin = wait(read(ryw, GetKeyReq(begin), pit)); + Key resolvedBegin = co_await read(ryw, GetKeyReq(begin), pit); if (resolvedBegin == allKeys.begin) readToBegin = true; if (resolvedBegin == ryw->getMaxReadKey()) readThroughEnd = true; if (resolvedBegin >= end.getKey() && end.offset <= 1) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } else if (resolvedBegin == ryw->getMaxReadKey()) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } resolveKeySelectorFromCache(end, it, ryw->getMaxReadKey(), &readToBegin, &readThroughEnd, &actualEndOffset); @@ -942,7 +943,7 @@ class RYWImpl { //TraceEvent("RYWSelectorsStartReverse", randomID).detail("ByteLimit", limits.bytes).detail("RowLimit", limits.rows); - loop { + while (true) { /*TraceEvent("RYWSelectors", randomID).detail("Begin", begin.toString()) .detail("End", end.toString()) .detail("Reached", limits.isReached()) @@ -955,11 +956,11 @@ class RYWImpl { .detail("Requests", requestCount);*/ if (!result.size() && actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } if (!begin.isBackward() && begin.getKey() >= ryw->getMaxReadKey()) { - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if ((begin.offset >= end.offset && begin.getKey() >= end.getKey()) || @@ -968,10 +969,10 @@ class RYWImpl { break; if (!result.size()) break; - Key resolvedBegin = - wait(read(ryw, - GetKeyReq(begin), - pit)); // do not worry about iterator invalidation, because we are breaking for the loop + Key resolvedBegin = co_await read( + ryw, + GetKeyReq(begin), + pit); // do not worry about iterator invalidation, because we are breaking for the loop if (resolvedBegin == allKeys.begin) readToBegin = true; if (resolvedBegin == ryw->getMaxReadKey()) @@ -984,7 +985,7 @@ class RYWImpl { it.beginKey() < itEnd.beginKey()) { if (begin.isFirstGreaterOrEqual()) break; - return RangeResultRef(readToBegin, readThroughEnd); + co_return RangeResultRef(readToBegin, readThroughEnd); } if (limits.isReached() && itemsPastBegin >= begin.offset - 1) @@ -1008,7 +1009,7 @@ class RYWImpl { if (it.beginKey() > itEnd.beginKey()) singleClears = std::min(skipUncachedBack(ucEnd, itEnd, BUGGIFY ? 0 : clearLimit + 100), clearLimit); - state KeySelector read_begin; + KeySelector read_begin; if (ucEnd != itEnd) { Key k = ucEnd.beginKey().toStandaloneStringRef(); read_begin = KeySelector(firstGreaterOrEqual(k), k.arena()); @@ -1027,7 +1028,7 @@ class RYWImpl { additionalRows += singleClears; - state KeySelector read_end; + KeySelector read_end; if (end.isFirstGreaterOrEqual()) { Key k = it.endKey() < end.getKey() ? it.endKey().toStandaloneStringRef() : end.getKey(); end = KeySelector(firstGreaterOrEqual(k), k.arena()); @@ -1049,7 +1050,7 @@ class RYWImpl { read_begin.arena().dependsOn(read_end.arena()); } - state GetRangeLimits requestLimit = limits; + GetRangeLimits requestLimit = limits; setRequestLimits(requestLimit, additionalRows, read_end.offset, requestCount); requestCount++; @@ -1060,7 +1061,7 @@ class RYWImpl { additionalRows = 0; RangeResult snapshot_read = - wait(ryw->tr.getRange(read_begin, read_end, requestLimit, Snapshot::True, Reverse::True)); + co_await ryw->tr.getRange(read_begin, read_end, requestLimit, Snapshot::True, Reverse::True); KeyRangeRef range = getKnownKeyRangeBack(snapshot_read, read_begin, read_end, ryw->arena); //TraceEvent("RYWCacheInsert", randomID).detail("Range", range).detail("ExpectedSize", snapshot_read.expectedSize()).detail("Rows", snapshot_read.size()).detail("Results", snapshot_read).detail("More", snapshot_read.more).detail("ReadToBegin", snapshot_read.readToBegin).detail("ReadThroughEnd", snapshot_read.readThroughEnd).detail("ReadThrough", snapshot_read.readThrough); @@ -1123,7 +1124,7 @@ class RYWImpl { result.readThroughEnd = readThroughEnd; result.arena().dependsOn(ryw->arena); - return result; + co_return result; } #ifndef __INTEL_COMPILER @@ -1142,22 +1143,22 @@ class RYWImpl { // read.limits, it); }; - ACTOR template + template static Future readThrough(ReadYourWritesTransaction* ryw, GetMappedRangeReq read, Snapshot snapshot) { if (backwards && read.end.offset > 1) { // FIXME: Optimistically assume that this will not run into the system keys, and only reissue if the result // actually does. - Key key = wait(ryw->tr.getKey(read.end, snapshot)); + Key key = co_await ryw->tr.getKey(read.end, snapshot); if (key > ryw->getMaxReadKey()) read.end = firstGreaterOrEqual(ryw->getMaxReadKey()); else read.end = KeySelector(firstGreaterOrEqual(key), key.arena()); } - MappedRangeResult v = wait(ryw->tr.getMappedRange( - read.begin, read.end, read.mapper, read.limits, snapshot, backwards ? Reverse::True : Reverse::False)); - return v; + MappedRangeResult v = co_await ryw->tr.getMappedRange( + read.begin, read.end, read.mapper, read.limits, snapshot, backwards ? Reverse::True : Reverse::False); + co_return v; } template @@ -1281,11 +1282,11 @@ class RYWImpl { triggerWatches(ryw, singleKeyRange(key), val, valueKnown); } - ACTOR static Future watch(ReadYourWritesTransaction* ryw, Key key) { - state Future> val; - state Future watchFuture; - state Reference watch(new Watch(key)); - state Promise done; + static Future watch(ReadYourWritesTransaction* ryw, Key key) { + Future> val; + Future watchFuture; + Reference watch(new Watch(key)); + Promise done; ryw->reading.add(done.getFuture()); @@ -1298,7 +1299,7 @@ class RYWImpl { } try { - wait(ryw->resetPromise.getFuture() || success(val) || watch->onChangeTrigger.getFuture()); + co_await (ryw->resetPromise.getFuture() || success(val) || watch->onChangeTrigger.getFuture()); } catch (Error& e) { done.send(Void()); throw; @@ -1308,7 +1309,7 @@ class RYWImpl { done.send(Void()); if (watch->onChangeTrigger.getFuture().isError()) throw watch->onChangeTrigger.getFuture().getError(); - return Void(); + co_return; } watch->valuePresent = true; @@ -1318,7 +1319,7 @@ class RYWImpl { (watch->value.present() && watch->setValue.get() != watch->value.get()))) { watch->onChangeTrigger.send(Void()); done.send(Void()); - return Void(); + co_return; } try { @@ -1329,35 +1330,33 @@ class RYWImpl { } done.send(Void()); - wait(watchFuture); - - return Void(); + co_await watchFuture; } - ACTOR static void simulateTimeoutInFlightCommit(ReadYourWritesTransaction* ryw_) { - state Reference ryw = Reference::addRef(ryw_); + static Future simulateTimeoutInFlightCommit(Uncancellable, ReadYourWritesTransaction* ryw_) { + Reference ryw = Reference::addRef(ryw_); ASSERT(ryw->options.timeoutInSeconds > 0); // An actual in-flight commit (i.e. one that's past the point where cancelling the transaction would stop it) // would already have a read version. We need to get a read version too, otherwise committing a conflicting // transaction may not ensure this transaction is no longer in-flight, since this transaction could get a read // version _after_. - wait(success(ryw->getReadVersion())); + co_await success(ryw->getReadVersion()); if (!ryw->resetPromise.isSet()) ryw->resetPromise.sendError(transaction_timed_out()); - wait(delay(deterministicRandom()->random01() * 5)); + co_await delay(deterministicRandom()->random01() * 5); TraceEvent("ClientBuggifyInFlightCommit").log(); - wait(ryw->tr.commit()); + co_await ryw->tr.commit(); } - ACTOR static Future commit(ReadYourWritesTransaction* ryw) { + static Future commit(ReadYourWritesTransaction* ryw) { try { ryw->commitStarted = true; if (ryw->options.specialKeySpaceChangeConfiguration) - wait(ryw->getDatabase()->specialKeySpace->commit(ryw)); + co_await ryw->getDatabase()->specialKeySpace->commit(ryw); Future ready = ryw->reading; - wait(ryw->resetPromise.getFuture() || ready); + co_await (ryw->resetPromise.getFuture() || ready); if (ryw->options.readYourWritesDisabled) { @@ -1375,10 +1374,10 @@ class RYWImpl { if (ryw->resetPromise.isSet()) throw ryw->resetPromise.getFuture().getError(); if (CLIENT_BUGGIFY && ryw->options.timeoutInSeconds > 0) { - simulateTimeoutInFlightCommit(ryw); + simulateTimeoutInFlightCommit(Uncancellable(), ryw); throw transaction_timed_out(); } - wait(ryw->resetPromise.getFuture() || ryw->tr.commit()); + co_await (ryw->resetPromise.getFuture() || ryw->tr.commit()); ryw->debugLogRetries(); @@ -1386,7 +1385,7 @@ class RYWImpl { ryw->reset(); } - return Void(); + co_return; } ryw->writeRangeToNativeTransaction(KeyRangeRef(StringRef(), allKeys.end)); @@ -1399,17 +1398,17 @@ class RYWImpl { } if (CLIENT_BUGGIFY && ryw->options.timeoutInSeconds > 0) { - simulateTimeoutInFlightCommit(ryw); + simulateTimeoutInFlightCommit(Uncancellable(), ryw); throw transaction_timed_out(); } - wait(ryw->resetPromise.getFuture() || ryw->tr.commit()); + co_await (ryw->resetPromise.getFuture() || ryw->tr.commit()); ryw->debugLogRetries(); if (!ryw->tr.apiVersionAtLeast(410)) { ryw->reset(); } - return Void(); + co_return; } catch (Error& e) { if (!ryw->tr.apiVersionAtLeast(410)) { ryw->commitStarted = false; @@ -1428,21 +1427,21 @@ class RYWImpl { // the future in the output. If commitFuture isn't specified, then the transaction will // be reported uncommitted. In that case, an optional error can be provided to indicate // why the transaction was uncommitted. - ACTOR static Future printDebugMessages(ReadYourWritesTransaction* self, - Optional> commitFuture, - Optional error = Optional()) { - state std::string prefix; - state std::string commitResult; - state ErrorOr result; - state std::vector debugTraces = std::move(self->debugTraces); - state std::vector debugMessages = std::move(self->debugMessages); + static Future printDebugMessages(ReadYourWritesTransaction* self, + Optional> commitFuture, + Optional error = Optional()) { + std::string prefix; + std::string commitResult; + ErrorOr result; + std::vector debugTraces = std::move(self->debugTraces); + std::vector debugMessages = std::move(self->debugMessages); self->debugTraces.clear(); self->debugMessages.clear(); if (commitFuture.present()) { try { - wait(store(result, errorOr(commitFuture.get()))); + result = co_await errorOr(commitFuture.get()); } catch (Error& e) { result = e; } @@ -1492,11 +1491,9 @@ class RYWImpl { if (result.isError()) { throw result.getError(); } - - return Void(); } - ACTOR static Future onError(ReadYourWritesTransaction* ryw, Error e) { + static Future onError(ReadYourWritesTransaction* ryw, Error e) { if (ryw->debugTraces.size() > 0 || ryw->debugMessages.size() > 0) { // printDebugMessages returns a future but will not block if called with an empty second argument ASSERT(printDebugMessages(ryw, {}, e).isReady()); @@ -1514,12 +1511,12 @@ class RYWImpl { throw e; } - wait(ryw->resetPromise.getFuture() || ryw->tr.onError(e)); + co_await (ryw->resetPromise.getFuture() || ryw->tr.onError(e)); ryw->debugLogRetries(e); ryw->resetRyow(); - return Void(); + co_return; } catch (Error& e) { if (!ryw->resetPromise.isSet()) { if (ryw->tr.apiVersionAtLeast(610)) { @@ -1556,9 +1553,9 @@ ReadYourWritesTransaction::ReadYourWritesTransaction(Database const& cx) applyPersistentOptions(); } -ACTOR Future timebomb(double endTime, Promise resetPromise) { +Future timebomb(double endTime, Promise resetPromise) { while (now() < endTime) { - wait(delayUntil(std::min(endTime + 0.0001, now() + CLIENT_KNOBS->TRANSACTION_TIMEOUT_DELAY_INTERVAL))); + co_await delayUntil(std::min(endTime + 0.0001, now() + CLIENT_KNOBS->TRANSACTION_TIMEOUT_DELAY_INTERVAL)); } if (!resetPromise.isSet()) resetPromise.sendError(transaction_timed_out()); @@ -1590,32 +1587,32 @@ Optional getValueFromJSON(StatusObject statusObj) { } } -ACTOR Future> getJSON(Database db, std::string jsonField = "") { - StatusObject statusObj = wait(StatusClient::statusFetcher(db, jsonField)); - return getValueFromJSON(statusObj); +Future> getJSON(Database db, std::string jsonField) { + StatusObject statusObj = co_await StatusClient::statusFetcher(db, jsonField); + co_return getValueFromJSON(statusObj); } -ACTOR Future getWorkerInterfaces(Reference connRecord) { - state Reference>> clusterInterface(new AsyncVar>); - state Future leaderMon = monitorLeader(connRecord, clusterInterface); - - loop { - choose { - when(std::vector workers = - wait(clusterInterface->get().present() - ? brokenPromiseToNever( - clusterInterface->get().get().getClientWorkers.getReply(GetClientWorkersRequest())) - : Never())) { - RangeResult result; - for (auto& it : workers) { - result.push_back_deep( - result.arena(), - KeyValueRef(it.address().toString(), BinaryWriter::toValue(it, IncludeVersion()))); - } - - return result; +Future getWorkerInterfaces(Reference connRecord) { + Reference>> clusterInterface(new AsyncVar>); + Future leaderMon = monitorLeader(connRecord, clusterInterface); + + while (true) { + Future> workersFuture = + clusterInterface->get().present() + ? brokenPromiseToNever( + clusterInterface->get().get().getClientWorkers.getReply(GetClientWorkersRequest())) + : Never(); + auto res = co_await race(workersFuture, clusterInterface->onChange()); + if (res.index() == 0) { + std::vector workers = std::get<0>(std::move(res)); + + RangeResult result; + for (auto& it : workers) { + result.push_back_deep( + result.arena(), KeyValueRef(it.address().toString(), BinaryWriter::toValue(it, IncludeVersion()))); } - when(wait(clusterInterface->onChange())) {} + + co_return result; } } } diff --git a/fdbclient/RestoreInterface.cpp b/fdbclient/RestoreInterface.cpp index 5e52c708768..96d81225bb4 100644 --- a/fdbclient/RestoreInterface.cpp +++ b/fdbclient/RestoreInterface.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "fdbclient/RestoreInterface.h" +#include "RestoreInterface.h" #include "flow/serialize.h" const KeyRef restoreRequestDoneKey = "\xff\x02/restoreRequestDone"_sr; diff --git a/fdbclient/include/fdbclient/RestoreInterface.h b/fdbclient/RestoreInterface.h similarity index 100% rename from fdbclient/include/fdbclient/RestoreInterface.h rename to fdbclient/RestoreInterface.h diff --git a/fdbclient/S3BlobStore.actor.cpp b/fdbclient/S3BlobStore.actor.cpp deleted file mode 100644 index e7dc5918ac5..00000000000 --- a/fdbclient/S3BlobStore.actor.cpp +++ /dev/null @@ -1,2600 +0,0 @@ -/* - * S3BlobStore.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. - */ - -#include "fdbclient/S3BlobStore.h" - -#include -#include "fdbrpc/HTTP.h" -#include "fdbclient/ClientKnobs.h" -#include "fdbclient/Knobs.h" -#include "flow/FastRef.h" -#include "flow/IConnection.h" -#include "flow/Trace.h" -#include "flow/flow.h" -#include "flow/genericactors.actor.h" - -// S3BlobStoreEndpoint::ConnectionPoolData destructor implementation -S3BlobStoreEndpoint::ConnectionPoolData::~ConnectionPoolData() { - // In simulation, explicitly close all pooled connections before destruction. - // This satisfies Sim2Conn's assertion: !opened || closedByCaller - // Without this, connections would be destroyed without being closed, causing assertion failures. - if (g_network && g_network->isSimulated()) { - while (!pool.empty()) { - ReusableConnection& rconn = pool.front(); - if (rconn.conn.isValid()) { - rconn.conn->close(); - } - pool.pop(); - } - } -} - -#include "md5/md5.h" -#include "libb64/encode.h" -#include "fdbclient/sha1/SHA1.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "flow/IAsyncFile.h" -#include "flow/Hostname.h" -#include "flow/UnitTest.h" -#include "rapidxml/rapidxml.hpp" -#ifdef WITH_AWS_BACKUP -#include "fdbclient/FDBAWSCredentialsProvider.h" -#endif - -#include "flow/actorcompiler.h" // has to be last include - -using namespace rapidxml; - -json_spirit::mObject S3BlobStoreEndpoint::Stats::getJSON() { - json_spirit::mObject o; - - o["requests_failed"] = requests_failed; - o["requests_successful"] = requests_successful; - o["bytes_sent"] = bytes_sent; - - return o; -} - -S3BlobStoreEndpoint::Stats S3BlobStoreEndpoint::Stats::operator-(const Stats& rhs) { - Stats r; - r.requests_failed = requests_failed - rhs.requests_failed; - r.requests_successful = requests_successful - rhs.requests_successful; - r.bytes_sent = bytes_sent - rhs.bytes_sent; - return r; -} - -S3BlobStoreEndpoint::Stats S3BlobStoreEndpoint::s_stats; - -S3BlobStoreEndpoint::BlobKnobs::BlobKnobs() { - secure_connection = 1; - connect_tries = CLIENT_KNOBS->BLOBSTORE_CONNECT_TRIES; - connect_timeout = CLIENT_KNOBS->BLOBSTORE_CONNECT_TIMEOUT; - max_connection_life = CLIENT_KNOBS->BLOBSTORE_MAX_CONNECTION_LIFE; - request_tries = CLIENT_KNOBS->BLOBSTORE_REQUEST_TRIES; - request_timeout_min = CLIENT_KNOBS->BLOBSTORE_REQUEST_TIMEOUT_MIN; - requests_per_second = CLIENT_KNOBS->BLOBSTORE_REQUESTS_PER_SECOND; - concurrent_requests = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_REQUESTS; - list_requests_per_second = CLIENT_KNOBS->BLOBSTORE_LIST_REQUESTS_PER_SECOND; - write_requests_per_second = CLIENT_KNOBS->BLOBSTORE_WRITE_REQUESTS_PER_SECOND; - read_requests_per_second = CLIENT_KNOBS->BLOBSTORE_READ_REQUESTS_PER_SECOND; - delete_requests_per_second = CLIENT_KNOBS->BLOBSTORE_DELETE_REQUESTS_PER_SECOND; - multipart_max_part_size = CLIENT_KNOBS->BLOBSTORE_MULTIPART_MAX_PART_SIZE; - multipart_min_part_size = CLIENT_KNOBS->BLOBSTORE_MULTIPART_MIN_PART_SIZE; - concurrent_uploads = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_UPLOADS; - concurrent_lists = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_LISTS; - concurrent_reads_per_file = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_READS_PER_FILE; - concurrent_writes_per_file = CLIENT_KNOBS->BLOBSTORE_CONCURRENT_WRITES_PER_FILE; - enable_read_cache = CLIENT_KNOBS->BLOBSTORE_ENABLE_READ_CACHE; - read_block_size = CLIENT_KNOBS->BLOBSTORE_READ_BLOCK_SIZE; - read_ahead_blocks = CLIENT_KNOBS->BLOBSTORE_READ_AHEAD_BLOCKS; - read_cache_blocks_per_file = CLIENT_KNOBS->BLOBSTORE_READ_CACHE_BLOCKS_PER_FILE; - max_send_bytes_per_second = CLIENT_KNOBS->BLOBSTORE_MAX_SEND_BYTES_PER_SECOND; - max_recv_bytes_per_second = CLIENT_KNOBS->BLOBSTORE_MAX_RECV_BYTES_PER_SECOND; - max_delay_retryable_error = CLIENT_KNOBS->BLOBSTORE_MAX_DELAY_RETRYABLE_ERROR; - max_delay_connection_failed = CLIENT_KNOBS->BLOBSTORE_MAX_DELAY_CONNECTION_FAILED; - sdk_auth = false; - enable_object_integrity_check = CLIENT_KNOBS->BLOBSTORE_ENABLE_OBJECT_INTEGRITY_CHECK; - global_connection_pool = CLIENT_KNOBS->BLOBSTORE_GLOBAL_CONNECTION_POOL; -} - -bool S3BlobStoreEndpoint::BlobKnobs::set(StringRef name, int value) { -#define TRY_PARAM(n, sn) \ - if (name == #n || name == #sn) { \ - n = value; \ - return true; \ - } - TRY_PARAM(secure_connection, sc) - TRY_PARAM(connect_tries, ct); - TRY_PARAM(connect_timeout, cto); - TRY_PARAM(max_connection_life, mcl); - TRY_PARAM(request_tries, rt); - TRY_PARAM(request_timeout_min, rtom); - // TODO: For backward compatibility because request_timeout was renamed to request_timeout_min - if (name == "request_timeout"_sr || name == "rto"_sr) { - request_timeout_min = value; - return true; - } - TRY_PARAM(requests_per_second, rps); - TRY_PARAM(list_requests_per_second, lrps); - TRY_PARAM(write_requests_per_second, wrps); - TRY_PARAM(read_requests_per_second, rrps); - TRY_PARAM(delete_requests_per_second, drps); - TRY_PARAM(concurrent_requests, cr); - TRY_PARAM(multipart_max_part_size, maxps); - TRY_PARAM(multipart_min_part_size, minps); - TRY_PARAM(multipart_retry_delay_ms, mrd); - TRY_PARAM(concurrent_uploads, cu); - TRY_PARAM(concurrent_lists, cl); - TRY_PARAM(concurrent_reads_per_file, crpf); - TRY_PARAM(concurrent_writes_per_file, cwpf); - TRY_PARAM(enable_read_cache, erc); - TRY_PARAM(read_block_size, rbs); - TRY_PARAM(read_ahead_blocks, rab); - TRY_PARAM(read_cache_blocks_per_file, rcb); - TRY_PARAM(max_send_bytes_per_second, sbps); - TRY_PARAM(max_recv_bytes_per_second, rbps); - TRY_PARAM(max_delay_retryable_error, dre); - TRY_PARAM(max_delay_connection_failed, dcf); - TRY_PARAM(sdk_auth, sa); - TRY_PARAM(enable_object_integrity_check, eoic); - TRY_PARAM(global_connection_pool, gcp); -#undef TRY_PARAM - return false; -} - -// Returns an S3 Blob URL parameter string that specifies all of the non-default options for the endpoint using option -// short names. -std::string S3BlobStoreEndpoint::BlobKnobs::getURLParameters() const { - static BlobKnobs defaults; - std::string r; -#define _CHECK_PARAM(n, sn) \ - if (n != defaults.n) { \ - r += format("%s%s=%d", r.empty() ? "" : "&", #sn, n); \ - } - _CHECK_PARAM(secure_connection, sc); - _CHECK_PARAM(connect_tries, ct); - _CHECK_PARAM(connect_timeout, cto); - _CHECK_PARAM(max_connection_life, mcl); - _CHECK_PARAM(request_tries, rt); - _CHECK_PARAM(request_timeout_min, rto); - _CHECK_PARAM(requests_per_second, rps); - _CHECK_PARAM(list_requests_per_second, lrps); - _CHECK_PARAM(write_requests_per_second, wrps); - _CHECK_PARAM(read_requests_per_second, rrps); - _CHECK_PARAM(delete_requests_per_second, drps); - _CHECK_PARAM(concurrent_requests, cr); - _CHECK_PARAM(multipart_max_part_size, maxps); - _CHECK_PARAM(multipart_min_part_size, minps); - _CHECK_PARAM(concurrent_uploads, cu); - _CHECK_PARAM(concurrent_lists, cl); - _CHECK_PARAM(concurrent_reads_per_file, crpf); - _CHECK_PARAM(concurrent_writes_per_file, cwpf); - _CHECK_PARAM(enable_read_cache, erc); - _CHECK_PARAM(read_block_size, rbs); - _CHECK_PARAM(read_ahead_blocks, rab); - _CHECK_PARAM(read_cache_blocks_per_file, rcb); - _CHECK_PARAM(max_send_bytes_per_second, sbps); - _CHECK_PARAM(max_recv_bytes_per_second, rbps); - _CHECK_PARAM(sdk_auth, sa); - _CHECK_PARAM(enable_object_integrity_check, eoic); - _CHECK_PARAM(global_connection_pool, gcp); - _CHECK_PARAM(max_delay_retryable_error, dre); - _CHECK_PARAM(max_delay_connection_failed, dcf); -#undef _CHECK_PARAM - return r; -} - -std::string guessRegionFromDomain(std::string domain) { - // Special case for localhost/127.0.0.1 to prevent basic_string exception - if (domain == "127.0.0.1" || domain == "localhost") { - return "us-east-1"; - } - - static const std::vector knownServices = { "s3.", "cos.", "oss-", "obs." }; - boost::algorithm::to_lower(domain); - - for (int i = 0; i < knownServices.size(); ++i) { - const char* service = knownServices[i]; - - std::size_t p = domain.find(service); - if (p == std::string::npos || (p >= 1 && domain[p - 1] != '.')) { - // eg. 127.0.0.1, example.com, s3-service.example.com, mys3.example.com - continue; - } - - StringRef h = StringRef(domain).substr(p); - - if (!h.startsWith("oss-"_sr)) { - h.eat(service); // ignore s3 service - } - - return h.eat(".").toString(); - } - - return ""; -} - -Reference S3BlobStoreEndpoint::fromString(const std::string& url, - const Optional& proxy, - std::string* resourceFromURL, - std::string* error, - ParametersT* ignored_parameters) { - if (resourceFromURL) - resourceFromURL->clear(); - - try { - // Replace HTML-encoded ampersands with raw ampersands - std::string decoded_url = url; - size_t pos = 0; - while ((pos = decoded_url.find("&", pos)) != std::string::npos) { - decoded_url.replace(pos, 5, "&"); - pos += 1; - } - - // Also handle double-encoded ampersands - pos = 0; - while ((pos = decoded_url.find("&amp;", pos)) != std::string::npos) { - decoded_url.replace(pos, 9, "&"); - pos += 1; - } - - StringRef t(decoded_url); - StringRef prefix = t.eat("://"); - if (prefix != "blobstore"_sr) - throw format("Invalid blobstore URL prefix '%s'", prefix.toString().c_str()); - - Optional proxyHost, proxyPort; - if (proxy.present()) { - StringRef proxyRef(proxy.get()); - if (proxy.get().find("://") != std::string::npos) { - StringRef proxyPrefix = proxyRef.eat("://"); - if (proxyPrefix != "http"_sr) { - throw format("Invalid proxy URL prefix '%s'. Either don't use a prefix, or use http://", - proxyPrefix.toString().c_str()); - } - } - std::string proxyBody = proxyRef.eat().toString(); - if (!Hostname::isHostname(proxyBody) && !NetworkAddress::parseOptional(proxyBody).present()) { - throw format("'%s' is not a valid value for proxy. Format should be either IP:port or host:port.", - proxyBody.c_str()); - } - StringRef p(proxyBody); - proxyHost = p.eat(":").toString(); - proxyPort = p.eat().toString(); - } - - Optional cred; - if (url.find("@") != std::string::npos) { - cred = t.eat("@"); - } - uint8_t foundSeparator = 0; - StringRef hostPort = t.eatAny("/?", &foundSeparator); - StringRef resource; - if (foundSeparator == '/') { - resource = t.eat("?"); - } - - // hostPort is at least a host or IP address, optionally followed by :portNumber or :serviceName - StringRef h(hostPort); - StringRef host = h.eat(":"); - if (host.size() == 0) - throw std::string("host cannot be empty"); - - StringRef service = h.eat(); - - std::string region = guessRegionFromDomain(host.toString()); - - BlobKnobs knobs; - HTTP::Headers extraHeaders; - while (1) { - StringRef name = t.eat("="); - if (name.size() == 0) - break; - StringRef value = t.eat("&"); - - // Special case for header - if (name == "header"_sr) { - StringRef originalValue = value; - StringRef headerFieldName = value.eat(":"); - StringRef headerFieldValue = value; - if (headerFieldName.size() == 0 || headerFieldValue.size() == 0) { - throw format("'%s' is not a valid value for '%s' parameter. Format is : " - "where strings are not empty.", - originalValue.toString().c_str(), - name.toString().c_str()); - } - std::string& fieldValue = extraHeaders[headerFieldName.toString()]; - // RFC 2616 section 4.2 says header field names can repeat but only if it is valid to concatenate their - // values with comma separation - if (!fieldValue.empty()) { - fieldValue.append(","); - } - fieldValue.append(headerFieldValue.toString()); - continue; - } - - // overwrite s3 region from parameter - if (name == "region"_sr) { - region = value.toString(); - continue; - } - - // See if the parameter is a knob - // First try setting a dummy value (all knobs are currently numeric) just to see if this parameter is known - // to S3BlobStoreEndpoint. If it is, then we will set it to a good value or throw below, so the dummy set - // has no bad side effects. - bool known = knobs.set(name, 0); - - // If the parameter is not known to S3BlobStoreEndpoint then throw unless there is an ignored_parameters set - // to add it to - if (!known) { - if (ignored_parameters == nullptr) { - throw format("%s is not a valid parameter name", name.toString().c_str()); - } - (*ignored_parameters)[name.toString()] = value.toString(); - continue; - } - - // The parameter is known to S3BlobStoreEndpoint so it must be numeric and valid. - char* valueEnd = nullptr; - std::string s = value.toString(); - long int ivalue = strtol(s.c_str(), &valueEnd, 10); - if (*valueEnd || (ivalue == 0 && s != "0") || - (((ivalue == LONG_MAX) || (ivalue == LONG_MIN)) && errno == ERANGE)) - throw format("%s is not a valid value for %s", s.c_str(), name.toString().c_str()); - - // It should not be possible for this set to fail now since the dummy set above had to have worked. - ASSERT(knobs.set(name, ivalue)); - } - - if (resourceFromURL != nullptr) - *resourceFromURL = resource.toString(); - - Optional creds; - if (cred.present()) { - StringRef c(cred.get()); - StringRef key = c.eat(":"); - StringRef secret = c.eat(":"); - StringRef securityToken = c.eat(); - creds = S3BlobStoreEndpoint::Credentials{ key.toString(), secret.toString(), securityToken.toString() }; - } - - if (region.empty() && CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER) { - throw std::string( - "Failed to get region from host or parameter in url, region is required for aws v4 signature"); - } - - return makeReference( - host.toString(), service.toString(), region, proxyHost, proxyPort, creds, knobs, extraHeaders); - - } catch (std::string& err) { - if (error != nullptr) - *error = err; - TraceEvent(SevWarnAlways, "S3BlobStoreEndpointBadURL") - .suppressFor(60) - .detail("Description", err) - .detail("Format", getURLFormat()) - .detail("URL", url); - throw backup_invalid_url(); - } -} - -std::string S3BlobStoreEndpoint::getResourceURL(std::string resource, std::string params) const { - std::string hostPort = host; - if (!service.empty()) { - hostPort.append(":"); - hostPort.append(service); - } - - // If secret isn't being looked up from credentials files then it was passed explicitly in the URL so show it here. - std::string credsString; - if (credentials.present()) { - if (!lookupKey) { - credsString = credentials.get().key; - } - if (!lookupSecret) { - credsString += ":" + credentials.get().secret; - if (!credentials.get().securityToken.empty()) { - credsString += ":" + credentials.get().securityToken; - } - } - credsString += "@"; - } - - std::string r = format("blobstore://%s%s/%s", credsString.c_str(), hostPort.c_str(), resource.c_str()); - - // Get params that are deviations from knob defaults - std::string knobParams = knobs.getURLParameters(); - if (!knobParams.empty()) { - if (!params.empty()) { - params.append("&"); - } - params.append(knobParams); - } - - for (const auto& [k, v] : extraHeaders) { - if (!params.empty()) { - params.append("&"); - } - params.append("header="); - params.append(k); - params.append(":"); - params.append(v); - } - - if (!params.empty()) { - r.append("?").append(params); - } - - return r; -} - -std::string S3BlobStoreEndpoint::constructResourcePath(const std::string& bucket, const std::string& object) const { - std::string resource; - - if (host.find(bucket + ".") != 0) { - resource += std::string("/") + bucket; // not virtual hosting mode - } - - if (!object.empty()) { - // S3 object keys should not start with '/'. Strip any leading slashes. - std::string cleanedObject = object; - while (!cleanedObject.empty() && cleanedObject[0] == '/') { - cleanedObject = cleanedObject.substr(1); - } - if (!cleanedObject.empty()) { - resource += "/"; - resource += cleanedObject; - } - } - - return resource; -} - -// Forward declaration for doRequest_impl to fix compilation order issue -ACTOR Future> doRequest_impl(Reference bstore, - std::string verb, - std::string resource, - HTTP::Headers headers, - UnsentPacketQueue* pContent, - int contentLen, - std::set successCodes); - -ACTOR Future bucketExists_impl(Reference b, std::string bucket) { - wait(b->requestRateRead->getAllowance(1)); - - state std::string resource = b->constructResourcePath(bucket, ""); - state HTTP::Headers headers; - - try { - Reference r = - wait(doRequest_impl(b, "HEAD", resource, headers, nullptr, 0, { 200, 404 })); - return r->code == 200; - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) - throw; - TraceEvent(SevError, "S3ClientBucketExistsError") - .detail("Bucket", bucket) - .detail("Host", b->host) - .errorUnsuppressed(e); - throw; - } -} - -Future S3BlobStoreEndpoint::bucketExists(std::string const& bucket) { - return bucketExists_impl(Reference::addRef(this), bucket); -} - -ACTOR Future objectExists_impl(Reference b, std::string bucket, std::string object) { - wait(b->requestRateRead->getAllowance(1)); - - state std::string resource = b->constructResourcePath(bucket, object); - state HTTP::Headers headers; - - Reference r = wait(doRequest_impl(b, "HEAD", resource, headers, nullptr, 0, { 200, 404 })); - return r->code == 200; -} - -Future S3BlobStoreEndpoint::objectExists(std::string const& bucket, std::string const& object) { - return objectExists_impl(Reference::addRef(this), bucket, object); -} - -ACTOR Future deleteObject_impl(Reference b, std::string bucket, std::string object) { - wait(b->requestRateDelete->getAllowance(1)); - - state std::string resource = b->constructResourcePath(bucket, object); - state HTTP::Headers headers; - // 200 or 204 means object successfully deleted, 404 means it already doesn't exist, so any of those are considered - // successful - Reference r = - wait(doRequest_impl(b, "DELETE", resource, headers, nullptr, 0, { 200, 204, 404 })); - - // But if the object already did not exist then the 'delete' is assumed to be successful but a warning is logged. - if (r->code == 404) { - TraceEvent(SevWarnAlways, "S3BlobStoreEndpointDeleteObjectMissing") - .detail("Host", b->host) - .detail("Bucket", bucket) - .detail("Object", object); - } - - return Void(); -} - -Future S3BlobStoreEndpoint::deleteObject(std::string const& bucket, std::string const& object) { - return deleteObject_impl(Reference::addRef(this), bucket, object); -} - -ACTOR Future deleteRecursively_impl(Reference b, - std::string bucket, - std::string prefix, - int* pNumDeleted, - int64_t* pBytesDeleted) { - state PromiseStream resultStream; - // Start a recursive parallel listing which will send results to resultStream as they are received - state Future done = b->listObjectsStream(bucket, resultStream, prefix, '/', std::numeric_limits::max()); - // Wrap done in an actor which will send end_of_stream since listObjectsStream() does not (so that many calls can - // write to the same stream) - done = map(done, [=](Void) mutable { - resultStream.sendError(end_of_stream()); - return Void(); - }); - - state std::list> deleteFutures; - try { - loop { - choose { - // Throw if done throws, otherwise don't stop until end_of_stream - when(wait(done)) { - done = Never(); - } - - when(S3BlobStoreEndpoint::ListResult list = waitNext(resultStream.getFuture())) { - for (auto& object : list.objects) { - deleteFutures.push_back(map(b->deleteObject(bucket, object.name), [=](Void) -> Void { - if (pNumDeleted != nullptr) { - ++*pNumDeleted; - } - if (pBytesDeleted != nullptr) { - *pBytesDeleted += object.size; - } - return Void(); - })); - } - } - } - - // This is just a precaution to avoid having too many outstanding delete actors waiting to run - while (deleteFutures.size() > CLIENT_KNOBS->BLOBSTORE_CONCURRENT_REQUESTS) { - wait(deleteFutures.front()); - deleteFutures.pop_front(); - } - } - } catch (Error& e) { - if (e.code() != error_code_end_of_stream) - throw; - } - - while (deleteFutures.size() > 0) { - wait(deleteFutures.front()); - deleteFutures.pop_front(); - } - - return Void(); -} - -Future S3BlobStoreEndpoint::deleteRecursively(std::string const& bucket, - std::string prefix, - int* pNumDeleted, - int64_t* pBytesDeleted) { - return deleteRecursively_impl( - Reference::addRef(this), bucket, prefix, pNumDeleted, pBytesDeleted); -} - -ACTOR Future createBucket_impl(Reference b, std::string bucket) { - state UnsentPacketQueue packets; - wait(b->requestRateWrite->getAllowance(1)); - - bool exists = wait(b->bucketExists(bucket)); - if (!exists) { - state std::string resource = b->constructResourcePath(bucket, ""); - state HTTP::Headers headers; - - std::string region = b->getRegion(); - if (region.empty()) { - Reference r = - wait(doRequest_impl(b, "PUT", resource, headers, nullptr, 0, { 200, 409 })); - } else { - Standalone body( - format("" - " %s" - "", - region.c_str())); - PacketWriter pw(packets.getWriteBuffer(), nullptr, Unversioned()); - pw.serializeBytes(body); - - Reference r = - wait(doRequest_impl(b, "PUT", resource, headers, &packets, body.size(), { 200, 409 })); - } - } - return Void(); -} - -Future S3BlobStoreEndpoint::createBucket(std::string const& bucket) { - return createBucket_impl(Reference::addRef(this), bucket); -} - -ACTOR Future objectSize_impl(Reference b, std::string bucket, std::string object) { - wait(b->requestRateRead->getAllowance(1)); - - state std::string resource = b->constructResourcePath(bucket, object); - state HTTP::Headers headers; - - Reference r = wait(doRequest_impl(b, "HEAD", resource, headers, nullptr, 0, { 200, 404 })); - if (r->code == 404) - throw file_not_found(); - return r->data.contentLen; -} - -Future S3BlobStoreEndpoint::objectSize(std::string const& bucket, std::string const& object) { - return objectSize_impl(Reference::addRef(this), bucket, object); -} - -// Try to read a file, parse it as JSON, and return the resulting document. -// It will NOT throw if any errors are encountered, it will just return an empty -// JSON object and will log trace events for the errors encountered. -ACTOR Future> tryReadJSONFile(std::string path) { - state std::string content; - - // Event type to be logged in the event of an exception - state const char* errorEventType = "BlobCredentialFileError"; - - try { - state Reference f = wait(IAsyncFileSystem::filesystem()->open( - path, IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED, 0)); - state int64_t size = wait(f->size()); - state Standalone buf = makeString(size); - int r = wait(f->read(mutateString(buf), size, 0)); - ASSERT(r == size); - content = buf.toString(); - - // Any exceptions from hehre forward are parse failures - errorEventType = "BlobCredentialFileParseFailed"; - json_spirit::mValue json; - json_spirit::read_string(content, json); - if (json.type() == json_spirit::obj_type) - return json.get_obj(); - else - TraceEvent(SevWarn, "BlobCredentialFileNotJSONObject").suppressFor(60).detail("File", path); - - } catch (Error& e) { - if (e.code() != error_code_actor_cancelled) - TraceEvent(SevWarn, errorEventType).errorUnsuppressed(e).suppressFor(60).detail("File", path); - } - - return Optional(); -} - -// If the credentials expire, the connection will eventually fail and be discarded from the pool, and then a new -// connection will be constructed, which will call this again to get updated credentials -static S3BlobStoreEndpoint::Credentials getSecretSdk() { -#ifdef WITH_AWS_BACKUP - double elapsed = -timer_monotonic(); - Aws::Auth::AWSCredentials awsCreds = FDBAWSCredentialsProvider::getAwsCredentials(); - elapsed += timer_monotonic(); - - if (awsCreds.IsEmpty()) { - TraceEvent(SevWarn, "S3BlobStoreAWSCredsEmpty"); - throw backup_auth_missing(); - } - - S3BlobStoreEndpoint::Credentials fdbCreds; - fdbCreds.key = awsCreds.GetAWSAccessKeyId(); - fdbCreds.secret = awsCreds.GetAWSSecretKey(); - fdbCreds.securityToken = awsCreds.GetSessionToken(); - - TraceEvent("S3BlobStoreGotSdkCredentials").suppressFor(60).detail("Duration", elapsed); - - return fdbCreds; -#else - TraceEvent(SevError, "S3BlobStoreNoSDK").log(); - throw backup_auth_missing(); -#endif -} - -ACTOR Future updateSecret_impl(Reference b) { - if (b->knobs.sdk_auth) { - b->credentials = getSecretSdk(); - return Void(); - } - std::vector* pFiles = (std::vector*)g_network->global(INetwork::enBlobCredentialFiles); - if (pFiles == nullptr) - return Void(); - - if (!b->credentials.present()) { - return Void(); - } - - state std::vector>> reads; - for (auto& f : *pFiles) - reads.push_back(tryReadJSONFile(f)); - - wait(waitForAll(reads)); - - std::string accessKey = b->lookupKey ? "" : b->credentials.get().key; - std::string credentialsFileKey = accessKey + "@" + b->host; - - int invalid = 0; - - for (auto& f : reads) { - // If value not present then the credentials file wasn't readable or valid. Continue to check other results. - if (!f.get().present()) { - TraceEvent(SevWarn, "S3BlobStoreAuthMissingNotPresent") - .detail("Endpoint", b->host) - .detail("Service", b->service); - ++invalid; - continue; - } - - JSONDoc doc(f.get().get()); - if (doc.has("accounts") && doc.last().type() == json_spirit::obj_type) { - JSONDoc accounts(doc.last().get_obj()); - if (accounts.has(credentialsFileKey, false) && accounts.last().type() == json_spirit::obj_type) { - JSONDoc account(accounts.last()); - S3BlobStoreEndpoint::Credentials creds = b->credentials.get(); - if (b->lookupKey) { - std::string apiKey; - if (account.tryGet("api_key", apiKey)) - creds.key = apiKey; - else - continue; - } - if (b->lookupSecret) { - std::string secret; - if (account.tryGet("secret", secret)) - creds.secret = secret; - else - continue; - } - std::string token; - if (account.tryGet("token", token)) - creds.securityToken = token; - b->credentials = creds; - return Void(); - } else { - TraceEvent(SevWarn, "S3BlobStoreAuthFoundAccountFAILED") - .detail("CredentialFileKey", credentialsFileKey); - } - } - } - - // If any sources were invalid - if (invalid > 0) { - TraceEvent(SevWarn, "S3BlobStoreInvalidCredentials") - .detail("Endpoint", b->host) - .detail("Service", b->service) - .detail("Invalid", invalid); - throw backup_auth_unreadable(); - } - - // All sources were valid but didn't contain the desired info - TraceEvent(SevWarn, "S3BlobStoreAuthMissing") - .detail("Endpoint", b->host) - .detail("Service", b->service) - .detail("Reason", "No valid credentials found"); - throw backup_auth_missing(); -} - -Future S3BlobStoreEndpoint::updateSecret() { - return updateSecret_impl(Reference::addRef(this)); -} - -ACTOR Future connect_impl(Reference b, - bool* reusingConn) { - // First try to get a connection from the pool - *reusingConn = false; - while (!b->connectionPool->pool.empty()) { - S3BlobStoreEndpoint::ReusableConnection rconn = b->connectionPool->pool.front(); - b->connectionPool->pool.pop(); - - // If the connection expires in the future then return it - if (rconn.expirationTime > now()) { - *reusingConn = true; - ++b->blobStats->reusedConnections; - TraceEvent("S3BlobStoreEndpointReusingConnected") - .suppressFor(60) - .detail("RemoteEndpoint", rconn.conn->getPeerAddress()) - .detail("ExpiresIn", rconn.expirationTime - now()) - .detail("Proxy", b->proxyHost.orDefault("")); - return rconn; - } - ++b->blobStats->expiredConnections; - } - ++b->blobStats->newConnections; - std::string host = b->host, service = b->service; - TraceEvent(SevDebug, "S3BlobStoreEndpointBuildingNewConnection") - .detail("UseProxy", b->useProxy) - .detail("TLS", b->knobs.secure_connection == 1) - .detail("Host", host) - .detail("Service", service) - .log(); - if (service.empty()) { - if (b->useProxy) { - fprintf(stderr, "ERROR: Port can't be empty when using HTTP proxy.\n"); - throw connection_failed(); - } - service = b->knobs.secure_connection ? "https" : "http"; - } - bool isTLS = b->knobs.isTLS(); - state Reference conn; - if (b->useProxy) { - if (isTLS) { - Reference _conn = - wait(HTTP::proxyConnect(host, service, b->proxyHost.get(), b->proxyPort.get())); - conn = _conn; - } else { - host = b->proxyHost.get(); - service = b->proxyPort.get(); - Reference _conn = wait(INetworkConnections::net()->connect(host, service, false)); - conn = _conn; - } - } else { - wait(store(conn, INetworkConnections::net()->connect(host, service, isTLS))); - } - - // Ensure connection is valid before handshake - ASSERT(conn.isValid()); - - wait(conn->connectHandshake()); - - TraceEvent("S3BlobStoreEndpointNewConnectionSuccess") - .suppressFor(60) - .detail("RemoteEndpoint", conn->getPeerAddress()) - .detail("ExpiresIn", b->knobs.max_connection_life) - .detail("Proxy", b->proxyHost.orDefault("")); - - if (b->lookupKey || b->lookupSecret || b->knobs.sdk_auth) - wait(b->updateSecret()); - - return S3BlobStoreEndpoint::ReusableConnection({ conn, now() + b->knobs.max_connection_life }); -} - -Future S3BlobStoreEndpoint::connect(bool* reusing) { - return connect_impl(Reference::addRef(this), reusing); -} - -void S3BlobStoreEndpoint::returnConnection(ReusableConnection& rconn) { - // If it expires in the future then add it to the pool in the front - if (rconn.expirationTime > now()) { - connectionPool->pool.push(rconn); - } else { - ++blobStats->expiredConnections; - } - rconn.conn = Reference(); -} - -std::string awsCanonicalURI(const std::string& resource, std::vector& queryParameters, bool isV4) { - StringRef resourceRef(resource); - resourceRef.eat("/"); - std::string canonicalURI("/" + resourceRef.toString()); - size_t q = canonicalURI.find_last_of('?'); - if (q != canonicalURI.npos) - canonicalURI.resize(q); - if (isV4) { - canonicalURI = HTTP::awsV4URIEncode(canonicalURI, false); - } else { - canonicalURI = HTTP::urlEncode(canonicalURI); - } - - // Create the canonical query string - std::string queryString; - q = resource.find_last_of('?'); - if (q != queryString.npos) - queryString = resource.substr(q + 1); - - StringRef qStr(queryString); - StringRef queryParameter; - while ((queryParameter = qStr.eat("&")) != StringRef()) { - StringRef param = queryParameter.eat("="); - StringRef value = queryParameter.eat(); - - if (isV4) { - queryParameters.push_back(HTTP::awsV4URIEncode(param.toString(), true) + "=" + - HTTP::awsV4URIEncode(value.toString(), true)); - } else { - queryParameters.push_back(HTTP::urlEncode(param.toString()) + "=" + HTTP::urlEncode(value.toString())); - } - } - - return canonicalURI; -} - -// ref: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html -std::string parseErrorCodeFromS3(std::string xmlResponse) { - // Copy XML string to a modifiable buffer - try { - std::vector xmlBuffer(xmlResponse.begin(), xmlResponse.end()); - xmlBuffer.push_back('\0'); // Ensure null-terminated string - // Parse the XML - xml_document<> doc; - doc.parse<0>(&xmlBuffer[0]); - // Find the root node - xml_node<>* root = doc.first_node("Error"); - if (!root) { - TraceEvent(SevWarn, "ParseS3XMLResponseNoError").detail("Response", xmlResponse).log(); - return ""; - } - // Find the node - xml_node<>* codeNode = root->first_node("Code"); - if (!codeNode) { - TraceEvent(SevWarn, "ParseS3XMLResponseNoErrorCode").detail("Response", xmlResponse).log(); - return ""; - } - return std::string(codeNode->value()); - } catch (Error e) { - TraceEvent("BackupParseS3ErrorCodeFailure").errorUnsuppressed(e); - throw backup_parse_s3_response_failure(); - } catch (...) { - throw backup_parse_s3_response_failure(); - } -} - -bool isS3TokenError(const std::string& s3Error) { - return s3Error == "InvalidToken" || s3Error == "ExpiredToken"; -} - -void setHeaders(Reference bstore, Reference req) { - // Finish/update the request headers (which includes Date header) - // This must be done AFTER the connection is ready because if credentials are coming from disk they are - // refreshed when a new connection is established and setAuthHeaders() would need the updated secret. - if (bstore->credentials.present() && !bstore->credentials.get().securityToken.empty()) - req->data.headers["x-amz-security-token"] = bstore->credentials.get().securityToken; - if (CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER) { - bstore->setV4AuthHeaders(req->verb, req->resource, req->data.headers); - } else { - bstore->setAuthHeaders(req->verb, req->resource, req->data.headers); - } -} - -std::string getCanonicalURI(Reference bstore, Reference req) { - std::vector queryParameters; - std::string canonicalURI = - awsCanonicalURI(req->resource, queryParameters, CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER); - if (!queryParameters.empty()) { - canonicalURI += "?"; - // Use raw ampersands when joining query parameters - for (size_t i = 0; i < queryParameters.size(); ++i) { - if (i > 0) { - canonicalURI += "&"; - } - canonicalURI += queryParameters[i]; - } - } - - if (bstore->useProxy && bstore->knobs.secure_connection == 0) { - // Has to be in absolute-form. - canonicalURI = "http://" + bstore->host + ":" + bstore->service + canonicalURI; - } - return canonicalURI; -} - -void populateDryrunRequest(Reference dryrunRequest, - Reference bstore, - std::string bucket) { - // dryrun with a check bucket exist request, to avoid sending duplicate data - HTTP::Headers headers; - dryrunRequest->verb = "GET"; - dryrunRequest->data.contentLen = 0; - dryrunRequest->data.headers = headers; - dryrunRequest->data.headers["Host"] = bstore->host; - dryrunRequest->data.headers["Accept"] = "application/xml"; - - dryrunRequest->resource = bstore->constructResourcePath(bucket, ""); -} - -bool isWriteRequest(std::string verb) { - return verb == "POST" || verb == "PUT"; -} - -std::string parseBucketFromURI(std::string uri) { - if (uri.size() <= 1 || uri[0] != '/') { - // there is no bucket in the uri - return ""; - } - uri = uri.substr(1); - size_t secondSlash = uri.find('/'); - if (secondSlash == std::string::npos) { - return uri; - } - return uri.substr(0, secondSlash); -} - -// Do a request, get a Response. -// Request content is provided as UnsentPacketQueue *pContent which will be depleted as bytes are sent but the queue -// itself must live for the life of this actor and be destroyed by the caller -ACTOR Future> doRequest_impl(Reference bstore, - std::string verb, - std::string resource, - HTTP::Headers headers, - UnsentPacketQueue* pContent, - int contentLen, - std::set successCodes) { - - state UnsentPacketQueue contentCopy; - state UnsentPacketQueue dryrunContentCopy; // NonCopyable state var so must be declared at top of actor - state Reference req = makeReference(); - state Reference dryrunRequest = makeReference(); - req->verb = verb; - req->data.content = &contentCopy; - req->data.contentLen = contentLen; - - req->data.headers = headers; - req->data.headers["Host"] = bstore->host; - req->data.headers["Accept"] = "application/xml"; - - // Avoid to send request with an empty resource. - if (resource.empty()) { - resource = "/"; - } - - // Merge extraHeaders into headers - for (const auto& [k, v] : bstore->extraHeaders) { - std::string& fieldValue = req->data.headers[k]; - if (!fieldValue.empty()) { - fieldValue.append(","); - } - fieldValue.append(v); - } - - // For requests with content to upload, the request timeout should be at least twice the amount of time - // it would take to upload the content given the upload bandwidth and concurrency limits. - int bandwidthThisRequest = 1 + bstore->knobs.max_send_bytes_per_second / bstore->knobs.concurrent_uploads; - int contentUploadSeconds = contentLen / bandwidthThisRequest; - state int requestTimeout = std::max(bstore->knobs.request_timeout_min, 3 * contentUploadSeconds); - - wait(bstore->concurrentRequests.take()); - state FlowLock::Releaser globalReleaser(bstore->concurrentRequests, 1); - - state int maxTries = std::min(bstore->knobs.request_tries, bstore->knobs.connect_tries); - state int thisTry = 1; - state int badRequestCode = 400; - state bool s3TokenError = false; - state double nextRetryDelay = 2.0; - - loop { - state Optional err; - state Optional remoteAddress; - state bool connectionEstablished = false; - state Reference r; - state Reference dryrunR; - state std::string canonicalURI = resource; - // Set the resource on each loop so we don't double-encode when we set it to `getCanonicalURI` below. - req->resource = resource; - - // Reset headers to initial state for this retry attempt to prevent header accumulation - // across retries and potential map corruption - req->data.headers = headers; - req->data.headers["Host"] = bstore->host; - req->data.headers["Accept"] = "application/xml"; - // Re-merge extraHeaders - for (const auto& [k, v] : bstore->extraHeaders) { - std::string& fieldValue = req->data.headers[k]; - if (!fieldValue.empty()) { - fieldValue.append(","); - } - fieldValue.append(v); - } - - state UID connID = UID(); - state double reqStartTimer; - state double connectStartTimer = g_network->timer(); - state bool reusingConn = false; - state bool fastRetry = false; - state bool simulateS3TokenError = false; - state S3BlobStoreEndpoint::ReusableConnection rconn; // Moved outside try block for error handler access - - try { - // Start connecting - Future frconn = bstore->connect(&reusingConn); - - // Make a shallow copy of the queue by calling addref() on each buffer in the chain and then prepending that - // chain to contentCopy - req->data.content->discardAll(); - if (pContent != nullptr) { - PacketBuffer* pFirst = pContent->getUnsent(); - PacketBuffer* pLast = nullptr; - for (PacketBuffer* p = pFirst; p != nullptr; p = p->nextPacketBuffer()) { - p->addref(); - // Also reset the sent count on each buffer - p->bytes_sent = 0; - pLast = p; - } - req->data.content->prependWriteBuffer(pFirst, pLast); - } - - // Finish connecting, do request - wait(store(rconn, timeoutError(frconn, bstore->knobs.connect_timeout))); - connectionEstablished = true; - connID = rconn.conn->getDebugID(); - reqStartTimer = g_network->timer(); - TraceEvent(SevDebug, "S3BlobStoreEndpointConnected") - .suppressFor(60) - .detail("RemoteEndpoint", rconn.conn->getPeerAddress()) - .detail("Reusing", reusingConn) - .detail("ConnID", connID) - .detail("Verb", req->verb) - .detail("Resource", resource) - .detail("Proxy", bstore->proxyHost.orDefault("")); - - try { - if (s3TokenError && isWriteRequest(req->verb) && CLIENT_KNOBS->BACKUP_ALLOW_DRYRUN) { - // if it is a write request with s3TokenError, retry with a HEAD dryrun request - // to avoid sending duplicate data indefinitly to save network bandwidth - // because it might due to expired or invalid S3 token from the disk - state std::string bucket = parseBucketFromURI(resource); - if (bucket.empty()) { - TraceEvent(SevError, "EmptyBucketRequest") - .detail("S3TokenError", s3TokenError) - .detail("Verb", req->verb) - .detail("Resource", resource) - .log(); - throw bucket_not_in_url(); - } - dryrunRequest->data.content = &dryrunContentCopy; - dryrunRequest->data.content->discardAll(); // this should always be empty - populateDryrunRequest(dryrunRequest, bstore, bucket); - setHeaders(bstore, dryrunRequest); - dryrunRequest->resource = getCanonicalURI(bstore, dryrunRequest); - TraceEvent("RetryS3RequestDueToTokenIssue") - .detail("S3TokenError", s3TokenError) - .detail("OriginalResource", resource) - .detail("DryrunResource", dryrunRequest->resource) - .detail("Bucket", bucket) - .detail("V4", CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER) - .log(); - wait(bstore->requestRate->getAllowance(1)); - Future> dryrunResponse = HTTP::doRequest( - rconn.conn, dryrunRequest, bstore->sendRate, &bstore->s_stats.bytes_sent, bstore->recvRate); - Reference _dryrunR = wait(timeoutError(dryrunResponse, requestTimeout)); - dryrunR = _dryrunR; - // Only parse S3 error code for error responses (4xx/5xx), not successful responses (2xx) - std::string s3Error; - if (dryrunR->code >= 400) { - s3Error = parseErrorCodeFromS3(dryrunR->data.content); - } - if (dryrunR->code == badRequestCode && isS3TokenError(s3Error)) { - // authentication fails and s3 token error persists, retry with a HEAD dryrun request - // to avoid sending duplicate data indefinitly to save network bandwidth - // because it might be due to expired or invalid S3 token from the disk - wait(delay(bstore->knobs.max_delay_retryable_error)); - } else if (dryrunR->code == 200 || dryrunR->code == 404) { - // authentication has passed, and bucket existence has been verified(200 or 404) - // it might work now(or it might be another error) thus reset s3TokenError. - TraceEvent("S3TokenIssueResolved") - .detail("HttpCode", dryrunR->code) - .detail("HttpResponseContent", dryrunR->data.content) - .detail("S3Error", s3Error) - .detail("URI", dryrunRequest->resource) - .log(); - s3TokenError = false; - } else { - TraceEvent(SevError, "S3UnexpectedError") - .detail("HttpCode", dryrunR->code) - .detail("HttpResponseContent", dryrunR->data.content) - .detail("S3Error", s3Error) - .detail("URI", dryrunRequest->resource) - .log(); - throw http_bad_response(); - } - continue; - } - } catch (Error& e) { - // retry with GET failed, but continue to do original request anyway - if (e.code() != error_code_actor_cancelled) { - TraceEvent(SevError, "ErrorDuringRetryS3TokenIssue").errorUnsuppressed(e); - } else { - throw; - } - } - setHeaders(bstore, req); - req->resource = getCanonicalURI(bstore, req); - - remoteAddress = rconn.conn->getPeerAddress(); - wait(bstore->requestRate->getAllowance(1)); - - state Future> reqF = - HTTP::doRequest(rconn.conn, req, bstore->sendRate, &bstore->s_stats.bytes_sent, bstore->recvRate); - // if we reused a connection from the pool, and immediately got an error, retry immediately discarding - // the connection - if (reqF.isReady() && reusingConn) { - fastRetry = true; - } - - Reference _r = wait(timeoutError(reqF, requestTimeout)); - // Don't simulate token errors for multipart complete operations (POST with uploadId but no partNumber) - // because changing a successful 200 to 400 after the server has already completed and removed - // the upload causes the client to infinitely retry with a phantom upload ID. This seems too much of - // an artifical manufacture. - bool isMultipartComplete = verb == "POST" && resource.find("uploadId=") != std::string::npos && - resource.find("partNumber=") == std::string::npos; - if (g_network->isSimulated() && BUGGIFY && deterministicRandom()->random01() < 0.1 && - !isMultipartComplete) { - // simulate an error from s3 - _r->code = badRequestCode; - simulateS3TokenError = true; - } - r = _r; - - // Since the response was parsed successfully (which is why we are here) reuse the connection unless we - // received the "Connection: close" header. - if (r->data.headers["Connection"] != "close") { - bstore->returnConnection(rconn); - } else { - ++bstore->blobStats->expiredConnections; - } - rconn.conn.clear(); - } catch (Error& e) { - TraceEvent(SevWarn, "S3BlobStoreDoRequestError") - .errorUnsuppressed(e) - .detail("Verb", verb) - .detail("Resource", resource); - // Close the connection on error to satisfy Sim2Conn's assertion in simulation. - // If rconn holds a connection that hasn't been returned to the pool, - // we must close it before it's destroyed (when rconn goes out of scope). - // This sets closedByCaller = true in Sim2Conn, preventing assertion failures. - if (connectionEstablished && rconn.conn.isValid()) { - rconn.conn->close(); - rconn.conn.clear(); - } - if (e.code() == error_code_actor_cancelled) - throw; - err = e; - } - - double end = g_network->timer(); - double connectDuration = reqStartTimer - connectStartTimer; - double reqDuration = end - reqStartTimer; - bstore->blobStats->requestLatency.addMeasurement(reqDuration); - - // If err is not present then r is valid. - // If r->code is in successCodes then record the successful request and return r. - if (!err.present() && successCodes.count(r->code) != 0) { - TraceEvent(SevDebug, "S3BlobStoreDoRequestSuccessful") - .detail("Verb", verb) - .detail("Error", err.present()) - .detail("ErrorString", err.present() ? err.get().name() : "") - .detail("Resource", resource) - .detail("ResponseCode", r->code) - .detail("ResponseContentSize", r->data.content.size()) - .log(); - bstore->s_stats.requests_successful++; - ++bstore->blobStats->requestsSuccessful; - return r; - } - - // Otherwise, this request is considered failed. Update failure count. - bstore->s_stats.requests_failed++; - ++bstore->blobStats->requestsFailed; - - // All errors in err are potentially retryable as well as certain HTTP response codes... - bool retryable = err.present() || r->code == 500 || r->code == 502 || r->code == 503 || r->code == 429; - // But only if our previous attempt was not the last allowable try. - retryable = retryable && (thisTry < maxTries); - - if (!retryable || !err.present()) { - fastRetry = false; - } - - TraceEvent event(SevWarn, - retryable ? (fastRetry ? "S3BlobStoreEndpointRequestFailedFastRetryable" - : "S3BlobStoreEndpointRequestFailedRetryable") - : "S3BlobStoreEndpointRequestFailed"); - - bool connectionFailed = false; - // Attach err to trace event if present, otherwise extract some stuff from the response - if (err.present()) { - event.errorUnsuppressed(err.get()); - if (err.get().code() == error_code_connection_failed) { - connectionFailed = true; - } - } - event.suppressFor(60); - - if (!err.present()) { - event.detail("ResponseCode", r->code); - // Only parse S3 error code for real error responses (4xx/5xx), not successful responses (2xx) - // Skip parsing for simulated errors where response content is still binary data - std::string s3Error; - if (r->code >= 400 && !simulateS3TokenError) { - s3Error = parseErrorCodeFromS3(r->data.content); - } - event.detail("S3ErrorCode", s3Error); - if (r->code == badRequestCode) { - if (isS3TokenError(s3Error) || simulateS3TokenError) { - s3TokenError = true; - } - TraceEvent(SevWarnAlways, "S3BlobStoreBadRequest") - .detail("HttpCode", r->code) - .detail("HttpResponseContent", r->data.content) - .detail("S3Error", s3Error) - .log(); - } - } - - event.detail("ConnectionEstablished", connectionEstablished); - event.detail("ReusingConn", reusingConn); - if (connectionEstablished) { - event.detail("ConnID", connID); - event.detail("ConnectDuration", connectDuration); - event.detail("ReqDuration", reqDuration); - } - - if (remoteAddress.present()) - event.detail("RemoteEndpoint", remoteAddress.get()); - else - event.detail("RemoteHost", bstore->host); - - event.detail("Verb", verb) - .detail("Resource", resource) - .detail("ThisTry", thisTry) - .detail("URI", canonicalURI) - .detail("Proxy", bstore->proxyHost.orDefault("")); - - // If r is not valid or not code 429 then increment the try count. 429's will not count against the attempt - // limit. Also skip incrementing the retry count for fast retries - if (!fastRetry && (!r || r->code != 429)) - ++thisTry; - - if (fastRetry) { - ++bstore->blobStats->fastRetries; - wait(delay(0)); - } else if (retryable || s3TokenError) { - // We will wait delay seconds before the next retry, start with nextRetryDelay. - double delay = nextRetryDelay; - // conenctionFailed is treated specially as we know proxy to AWS can only serve 1 request per connection - // so there is no point of waiting too long, instead retry more aggressively - double limit = - connectionFailed ? bstore->knobs.max_delay_connection_failed : bstore->knobs.max_delay_retryable_error; - // Double but limit the *next* nextRetryDelay. - nextRetryDelay = std::min(nextRetryDelay * 2, limit); - // If r is valid then obey the Retry-After response header if present. - if (r) { - auto iRetryAfter = r->data.headers.find("Retry-After"); - if (iRetryAfter != r->data.headers.end()) { - event.detail("RetryAfterHeader", iRetryAfter->second); - char* pEnd; - double retryAfter = strtod(iRetryAfter->second.c_str(), &pEnd); - if (*pEnd) // If there were other characters then don't trust the parsed value, use a probably safe - // value of 5 minutes. - retryAfter = 300; - // Update delay - delay = std::max(delay, retryAfter); - } - } - // Log the delay then wait. - - event.detail("RetryDelay", delay); - wait(::delay(delay)); - } else { - // We can't retry, so throw something. - - // This error code means the authentication header was not accepted, likely the account or key is wrong. - if (r && r->code == 406) - throw http_not_accepted(); - - if (r && r->code == 401) - throw http_auth_failed(); - - // Recognize and throw specific errors - if (err.present()) { - int code = err.get().code(); - - // If we get a timed_out error during the the connect() phase, we'll call that connection_failed despite - // the fact that there was technically never a 'connection' to begin with. It differentiates between an - // active connection timing out vs a connection timing out, though not between an active connection - // failing vs connection attempt failing. - // TODO: Add more error types? - if (code == error_code_timed_out && !connectionEstablished) { - TraceEvent(SevWarn, "S3BlobStoreEndpointConnectTimeout") - .suppressFor(60) - .detail("Timeout", requestTimeout); - throw connection_failed(); - } - - if (code == error_code_timed_out || code == error_code_connection_failed || - code == error_code_lookup_failed) - throw err.get(); - } - - throw http_request_failed(); - } - } -} - -Future> S3BlobStoreEndpoint::doRequest(std::string const& verb, - std::string const& resource, - const HTTP::Headers& headers, - UnsentPacketQueue* pContent, - int contentLen, - std::set successCodes) { - return doRequest_impl( - Reference::addRef(this), verb, resource, headers, pContent, contentLen, successCodes); -} - -ACTOR Future listObjectsStream_impl(Reference bstore, - std::string bucket, - PromiseStream results, - Optional prefix, - Optional delimiter, - int maxDepth, - std::function recurseFilter) { - state std::string resource = bstore->constructResourcePath(bucket, ""); - // In virtual hosting mode, constructResourcePath returns "" for empty object. - // We need to ensure the query string starts with "/" to form a valid HTTP request. - // Commit 15dd76a7f9 switched from ListObjectsV1 to V2 and accidentally removed the leading "/". - if (resource.empty()) { - resource = "/"; - } - resource.append("?list-type=2&max-keys=").append(std::to_string(CLIENT_KNOBS->BLOBSTORE_LIST_MAX_KEYS_PER_PAGE)); - - if (prefix.present()) - resource.append("&prefix=").append(prefix.get()); - if (delimiter.present()) - resource.append("&delimiter=").append(std::string(1, delimiter.get())); - - state std::string continuationToken; - state bool more = true; - state std::vector> subLists; - - while (more) { - wait(bstore->concurrentLists.take()); - state FlowLock::Releaser listReleaser(bstore->concurrentLists, 1); - - state HTTP::Headers headers; - state std::string fullResource = resource; - if (!continuationToken.empty()) { - fullResource.append("&continuation-token=").append(continuationToken); - } - - Reference r = - wait(doRequest_impl(bstore, "GET", fullResource, headers, nullptr, 0, { 200, 404 })); - listReleaser.release(); - - try { - S3BlobStoreEndpoint::ListResult listResult; - - // If we got a 404, throw an error to indicate the resource doesn't exist - if (r->code == 404) { - TraceEvent(SevError, "S3BlobStoreResourceNotFound") - .detail("Bucket", bucket) - .detail("Prefix", prefix.present() ? prefix.get() : "") - .detail("Resource", fullResource); - throw resource_not_found(); - } - - xml_document<> doc; - - // Copy content because rapidxml will modify it during parse - std::string content = r->data.content; - doc.parse<0>((char*)content.c_str()); - - // There should be exactly one node - xml_node<>* result = doc.first_node(); - if (result == nullptr || strcmp(result->name(), "ListBucketResult") != 0) { - throw http_bad_response(); - } - - xml_node<>* n = result->first_node(); - more = false; - continuationToken.clear(); - - while (n != nullptr) { - const char* name = n->name(); - if (strcmp(name, "IsTruncated") == 0) { - const char* val = n->value(); - if (strcmp(val, "true") == 0) { - more = true; - } else if (strcmp(val, "false") == 0) { - more = false; - } else { - throw http_bad_response(); - } - } else if (strcmp(name, "NextContinuationToken") == 0) { - if (n->value() != nullptr) { - continuationToken = n->value(); - } - } else if (strcmp(name, "Contents") == 0) { - S3BlobStoreEndpoint::ObjectInfo object; - - xml_node<>* key = n->first_node("Key"); - if (key == nullptr) { - throw http_bad_response(); - } - // URL decode the object name since S3 XML responses contain URL-encoded names - object.name = HTTP::urlDecode(key->value()); - - xml_node<>* size = n->first_node("Size"); - if (size == nullptr) { - throw http_bad_response(); - } - object.size = strtoull(size->value(), nullptr, 10); - - listResult.objects.push_back(object); - } else if (strcmp(name, "CommonPrefixes") == 0) { - xml_node<>* prefixNode = n->first_node("Prefix"); - while (prefixNode != nullptr) { - const char* prefix = prefixNode->value(); - // If recursing, queue a sub-request, otherwise add the common prefix to the result. - if (maxDepth > 0) { - if (!recurseFilter || recurseFilter(prefix)) { - // For recursive listing, don't use delimiter in sub-requests to get individual files - subLists.push_back(bstore->listObjectsStream( - bucket, results, prefix, Optional(), maxDepth - 1, recurseFilter)); - } - } else { - listResult.commonPrefixes.push_back(prefix); - } - prefixNode = prefixNode->next_sibling("Prefix"); - } - } - n = n->next_sibling(); - } - - results.send(listResult); - } catch (Error& e) { - if (e.code() != error_code_actor_cancelled) { - TraceEvent(SevWarn, "S3BlobStoreEndpointListResultParseError") - .errorUnsuppressed(e) - .suppressFor(60) - .detail("Resource", fullResource); - } - throw http_bad_response(); - } - } - - wait(waitForAll(subLists)); - return Void(); -} - -Future S3BlobStoreEndpoint::listObjectsStream(std::string const& bucket, - PromiseStream results, - Optional prefix, - Optional delimiter, - int maxDepth, - std::function recurseFilter) { - return listObjectsStream_impl( - Reference::addRef(this), bucket, results, prefix, delimiter, maxDepth, recurseFilter); -} - -ACTOR Future listObjects_impl(Reference bstore, - std::string bucket, - Optional prefix, - Optional delimiter, - int maxDepth, - std::function recurseFilter) { - state S3BlobStoreEndpoint::ListResult results; - state PromiseStream resultStream; - state Future done = - bstore->listObjectsStream(bucket, resultStream, prefix, delimiter, maxDepth, recurseFilter); - // Wrap done in an actor which sends end_of_stream because list does not so that many lists can write to the same - // stream - done = map(done, [=](Void) mutable { - resultStream.sendError(end_of_stream()); - return Void(); - }); - - try { - loop { - choose { - // Throw if done throws, otherwise don't stop until end_of_stream - when(wait(done)) { - done = Never(); - } - - when(S3BlobStoreEndpoint::ListResult info = waitNext(resultStream.getFuture())) { - results.commonPrefixes.insert( - results.commonPrefixes.end(), info.commonPrefixes.begin(), info.commonPrefixes.end()); - results.objects.insert(results.objects.end(), info.objects.begin(), info.objects.end()); - } - } - } - } catch (Error& e) { - if (e.code() != error_code_end_of_stream) - throw; - } - - return results; -} - -Future S3BlobStoreEndpoint::listObjects( - std::string const& bucket, - Optional prefix, - Optional delimiter, - int maxDepth, - std::function recurseFilter) { - return listObjects_impl( - Reference::addRef(this), bucket, prefix, delimiter, maxDepth, recurseFilter); -} - -ACTOR Future> listBuckets_impl(Reference bstore) { - state std::string resource = "/?marker="; - state std::string lastName; - state bool more = true; - state std::vector buckets; - - while (more) { - wait(bstore->concurrentLists.take()); - state FlowLock::Releaser listReleaser(bstore->concurrentLists, 1); - - state HTTP::Headers headers; - state std::string fullResource = resource + lastName; - Reference r = - wait(doRequest_impl(bstore, "GET", fullResource, headers, nullptr, 0, { 200 })); - listReleaser.release(); - - try { - xml_document<> doc; - - // Copy content because rapidxml will modify it during parse - std::string content = r->data.content; - doc.parse<0>((char*)content.c_str()); - - // There should be exactly one node - xml_node<>* result = doc.first_node(); - if (result == nullptr || strcmp(result->name(), "ListAllMyBucketsResult") != 0) { - throw http_bad_response(); - } - - more = false; - xml_node<>* truncated = result->first_node("IsTruncated"); - if (truncated != nullptr && strcmp(truncated->value(), "true") == 0) { - more = true; - } - - xml_node<>* bucketsNode = result->first_node("Buckets"); - if (bucketsNode != nullptr) { - xml_node<>* bucketNode = bucketsNode->first_node("Bucket"); - while (bucketNode != nullptr) { - xml_node<>* nameNode = bucketNode->first_node("Name"); - if (nameNode == nullptr) { - throw http_bad_response(); - } - const char* name = nameNode->value(); - buckets.push_back(name); - - bucketNode = bucketNode->next_sibling("Bucket"); - } - } - - if (more) { - lastName = buckets.back(); - } - - } catch (Error& e) { - if (e.code() != error_code_actor_cancelled) - TraceEvent(SevWarn, "S3BlobStoreEndpointListBucketResultParseError") - .errorUnsuppressed(e) - .suppressFor(60) - .detail("Resource", fullResource); - throw http_bad_response(); - } - } - - return buckets; -} - -Future> S3BlobStoreEndpoint::listBuckets() { - return listBuckets_impl(Reference::addRef(this)); -} - -std::string S3BlobStoreEndpoint::hmac_sha1(Credentials const& creds, std::string const& msg) { - std::string key = creds.secret; - - // Hash key to shorten it if it is longer than SHA1 block size - if (key.size() > 64) { - key = SHA1::from_string(key); - } - - // Pad key up to SHA1 block size if needed - key.append(64 - key.size(), '\0'); - - std::string kipad = key; - for (int i = 0; i < 64; ++i) - kipad[i] ^= '\x36'; - - std::string kopad = key; - for (int i = 0; i < 64; ++i) - kopad[i] ^= '\x5c'; - - kipad.append(msg); - std::string hkipad = SHA1::from_string(kipad); - kopad.append(hkipad); - return SHA1::from_string(kopad); -} - -static void sha256(const unsigned char* data, const size_t len, unsigned char* hash) { - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, data, len); - SHA256_Final(hash, &sha256); -} - -std::string sha256_hex(std::string str) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - sha256((const unsigned char*)str.c_str(), str.size(), hash); - std::stringstream ss; - for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { - ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; - } - return ss.str(); -} - -// Return base64'd SHA256 hash of input string. -std::string sha256_base64(std::string str) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - sha256((const unsigned char*)str.c_str(), str.size(), hash); - std::string hashAsStr = std::string((char*)hash, SHA256_DIGEST_LENGTH); - std::string sig = base64::encoder::from_string(hashAsStr); - // base64 encoded blocks end in \n so remove last character. - sig.resize(sig.size() - 1); - return sig; -} - -std::string hmac_sha256_hex(std::string key, std::string msg) { - unsigned char hash[32]; - - HMAC_CTX* hmac = HMAC_CTX_new(); - HMAC_Init_ex(hmac, &key[0], key.length(), EVP_sha256(), NULL); - HMAC_Update(hmac, (unsigned char*)&msg[0], msg.length()); - unsigned int len = 32; - HMAC_Final(hmac, hash, &len); - HMAC_CTX_free(hmac); - - std::stringstream ss; - ss << std::hex << std::setfill('0'); - for (int i = 0; i < len; i++) { - ss << std::hex << std::setw(2) << (unsigned int)hash[i]; - } - return (ss.str()); -} - -std::string hmac_sha256(std::string key, std::string msg) { - unsigned char hash[32]; - - HMAC_CTX* hmac = HMAC_CTX_new(); - HMAC_Init_ex(hmac, &key[0], key.length(), EVP_sha256(), NULL); - HMAC_Update(hmac, (unsigned char*)&msg[0], msg.length()); - unsigned int len = 32; - HMAC_Final(hmac, hash, &len); - HMAC_CTX_free(hmac); - - std::stringstream ss; - ss << std::setfill('0'); - for (int i = 0; i < len; i++) { - ss << hash[i]; - } - return (ss.str()); -} - -// Date and Time parameters are used for unit testing -void S3BlobStoreEndpoint::setV4AuthHeaders(std::string const& verb, - std::string const& resource, - HTTP::Headers& headers, - std::string date, - std::string datestamp) { - if (!credentials.present()) { - return; - } - Credentials creds = credentials.get(); - // std::cout << "========== Starting===========" << std::endl; - std::string accessKey = creds.key; - std::string secretKey = creds.secret; - // Create a date for headers and the credential string - std::string amzDate; - std::string dateStamp; - if (date.empty() || datestamp.empty()) { - time_t ts; - time(&ts); - char dateBuf[20]; - // ISO 8601 format YYYYMMDD'T'HHMMSS'Z' - strftime(dateBuf, 20, "%Y%m%dT%H%M%SZ", gmtime(&ts)); - amzDate = dateBuf; - strftime(dateBuf, 20, "%Y%m%d", gmtime(&ts)); - dateStamp = dateBuf; - } else { - amzDate = date; - dateStamp = datestamp; - } - - // ************* TASK 1: CREATE A CANONICAL REQUEST ************* - // Create Create canonical URI--the part of the URI from domain to query string (use '/' if no path) - std::vector queryParameters; - std::string canonicalURI = awsCanonicalURI(resource, queryParameters, true); - - std::string canonicalQueryString; - if (!queryParameters.empty()) { - std::sort(queryParameters.begin(), queryParameters.end()); - canonicalQueryString = boost::algorithm::join(queryParameters, "&"); - } - - using namespace boost::algorithm; - // Create the canonical headers and signed headers - ASSERT(!headers["Host"].empty()); - // Be careful. There is x-amz-content-sha256 for auth and then - // x-amz-checksum-sha256 for object integrity check. - headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; - headers["x-amz-date"] = amzDate; - std::vector> headersList; - headersList.push_back({ "host", trim_copy(headers["Host"]) + "\n" }); - if (headers.find("Content-Type") != headers.end()) - headersList.push_back({ "content-type", trim_copy(headers["Content-Type"]) + "\n" }); - if (headers.find("Content-MD5") != headers.end()) - headersList.push_back({ "content-md5", trim_copy(headers["Content-MD5"]) + "\n" }); - for (auto h : headers) { - if (StringRef(h.first).startsWith("x-amz"_sr)) - headersList.push_back({ to_lower_copy(h.first), trim_copy(h.second) + "\n" }); - } - std::sort(headersList.begin(), headersList.end()); - std::string canonicalHeaders; - std::string signedHeaders; - for (auto& i : headersList) { - canonicalHeaders += i.first + ":" + i.second; - signedHeaders += i.first + ";"; - } - signedHeaders.pop_back(); - std::string canonicalRequest = verb + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + - "\n" + signedHeaders + "\n" + headers["x-amz-content-sha256"]; - - // ************* TASK 2: CREATE THE STRING TO SIGN************* - std::string algorithm = "AWS4-HMAC-SHA256"; - std::string credentialScope = dateStamp + "/" + region + "/s3/" + "aws4_request"; - std::string stringToSign = - algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + sha256_hex(canonicalRequest); - - // ************* TASK 3: CALCULATE THE SIGNATURE ************* - // Create the signing key using the function defined above. - std::string signingKey = - hmac_sha256(hmac_sha256(hmac_sha256(hmac_sha256("AWS4" + secretKey, dateStamp), region), "s3"), "aws4_request"); - // Sign the string_to_sign using the signing_key - std::string signature = hmac_sha256_hex(signingKey, stringToSign); - // ************* TASK 4: ADD SIGNING INFORMATION TO THE Header ************* - std::string authorizationHeader = algorithm + " " + "Credential=" + accessKey + "/" + credentialScope + ", " + - "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; - headers["Authorization"] = authorizationHeader; -} - -void S3BlobStoreEndpoint::setAuthHeaders(std::string const& verb, std::string const& resource, HTTP::Headers& headers) { - if (!credentials.present()) { - return; - } - Credentials creds = credentials.get(); - - std::string& date = headers["Date"]; - - char dateBuf[64]; - time_t ts; - time(&ts); - strftime(dateBuf, 64, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&ts)); - date = dateBuf; - - std::string msg; - msg.append(verb); - msg.append("\n"); - auto contentMD5 = headers.find("Content-MD5"); - if (contentMD5 != headers.end()) - msg.append(contentMD5->second); - msg.append("\n"); - auto contentType = headers.find("Content-Type"); - if (contentType != headers.end()) - msg.append(contentType->second); - msg.append("\n"); - msg.append(date); - msg.append("\n"); - for (auto h : headers) { - StringRef name = h.first; - if (name.startsWith("x-amz"_sr) || name.startsWith("x-icloud"_sr)) { - msg.append(h.first); - msg.append(":"); - msg.append(h.second); - msg.append("\n"); - } - } - - msg.append(resource); - if (verb == "GET") { - size_t q = resource.find_last_of('?'); - if (q != resource.npos) - msg.resize(msg.size() - (resource.size() - q)); - } - - std::string sig = base64::encoder::from_string(hmac_sha1(creds, msg)); - // base64 encoded blocks end in \n so remove it. - sig.resize(sig.size() - 1); - std::string auth = "AWS "; - auth.append(creds.key); - auth.append(":"); - auth.append(sig); - headers["Authorization"] = auth; -} - -ACTOR Future readEntireFile_impl(Reference bstore, - std::string bucket, - std::string object) { - wait(bstore->requestRateRead->getAllowance(1)); - - state std::string resource = bstore->constructResourcePath(bucket, object); - state HTTP::Headers headers; - // Set this header on the GET for it to volunteer saved checksum in the response headers. - // See https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html#API_GetObject_RequestSyntax - headers["x-amz-checksum-mode"] = "ENABLED"; - Reference r = - wait(doRequest_impl(bstore, "GET", resource, headers, nullptr, 0, { 200, 404 })); - if (r->code == 404) - throw file_not_found(); - if (bstore->knobs.enable_object_integrity_check) { - // Verify the content. We set 'x-amz-checksum-mode' on the GET request above so - // the server will return the sha256 checksum we set when we uploaded the object in the - // GET response headers. See - // https://stackoverflow.com/questions/36540234/what-is-the-difference-getcontentmd5-and-getetag-of-aws-s3-putobjectresult - std::string checksumSHA256 = r->data.headers["x-amz-checksum-sha256"]; - if (checksumSHA256.empty()) { - // This is what is thrown elsewhere when no expected etag. - throw http_bad_response(); - } - // Calculate the sha256 checksum of the content and compare it to the checksum returned by the server. - std::string contentSHA256 = sha256_base64(r->data.content); - if (checksumSHA256 != contentSHA256) { - throw checksum_failed(); - } - } - return r->data.content; -} - -Future S3BlobStoreEndpoint::readEntireFile(std::string const& bucket, std::string const& object) { - return readEntireFile_impl(Reference::addRef(this), bucket, object); -} - -ACTOR Future writeEntireFileFromBuffer_impl(Reference bstore, - std::string bucket, - std::string object, - UnsentPacketQueue* pContent, - int contentLen, - std::string contentHash) { - if (contentLen > bstore->knobs.multipart_max_part_size) - throw file_too_large(); - - wait(bstore->requestRateWrite->getAllowance(1)); - wait(bstore->concurrentUploads.take()); - state FlowLock::Releaser uploadReleaser(bstore->concurrentUploads, 1); - - state std::string resource = bstore->constructResourcePath(bucket, object); - state HTTP::Headers headers; - // contentHash is calculated by the caller. It is md5 or sha256 dependent on - // enable_object_integrity_check setting. If the hash (md5 or sha256) we - // volunteer doesn't match that calculated serverside, the upload fails with: - // InvalidDigestThe Content-MD5 you specified was invalid. - if (bstore->knobs.enable_object_integrity_check) { - headers["x-amz-checksum-sha256"] = contentHash; - headers["x-amz-checksum-algorithm"] = "SHA256"; - } else { - headers["Content-MD5"] = contentHash; - } - if (!CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE.empty()) - headers["x-amz-server-side-encryption"] = CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE; - Reference r = - wait(doRequest_impl(bstore, "PUT", resource, headers, pContent, contentLen, { 200 })); - return Void(); -} - -ACTOR Future writeEntireFile_impl(Reference bstore, - std::string bucket, - std::string object, - std::string content) { - state UnsentPacketQueue packets; - if (content.size() > bstore->knobs.multipart_max_part_size) - throw file_too_large(); - - PacketWriter pw(packets.getWriteBuffer(content.size()), nullptr, Unversioned()); - pw.serializeBytes(content); - - // Yield because we may have just had to copy several MB's into packet buffer chain and next we have to calculate an - // MD5 sum of it. - // TODO: If this actor is used to send large files then combine the summing and packetization into a loop with a - // yield() every 20k or so. - wait(yield()); - - // If enable_object_integrity_check is true, calculate the sha256 sum of the content. - // Otherwise, do md5. Save the calculated hash to contentHash. Whichever, when we - // upload to the server, it will check the hash -- md5 or sha256 -- and if a mismatch, - // the upload will fail. The difference is that sha256 is a better hash than md5 and - // when enable_object_integrity_check is set, we will verify the sha256 hash of the - // content when we download it too; we do not do this latter when - // enable_object_integrity_check is false (the etag returned in the GET response is an - // md5 most of the time but NOT always so it can't be relied upon. See - // https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html - std::string contentHash; - if (CLIENT_KNOBS->BLOBSTORE_ENABLE_OBJECT_INTEGRITY_CHECK) { - // If the content is small enough to fit in a single packet then we can calculate the sha256 sum now. - contentHash = sha256_base64(content); - } else { - MD5_CTX sum; - ::MD5_Init(&sum); - ::MD5_Update(&sum, content.data(), content.size()); - std::string sumBytes; - sumBytes.resize(16); - ::MD5_Final((unsigned char*)sumBytes.data(), &sum); - contentHash = base64::encoder::from_string(sumBytes); - contentHash.resize(contentHash.size() - 1); - } - - wait(writeEntireFileFromBuffer_impl(bstore, bucket, object, &packets, content.size(), contentHash)); - return Void(); -} - -Future S3BlobStoreEndpoint::writeEntireFile(std::string const& bucket, - std::string const& object, - std::string const& content) { - return writeEntireFile_impl(Reference::addRef(this), bucket, object, content); -} - -Future S3BlobStoreEndpoint::writeEntireFileFromBuffer(std::string const& bucket, - std::string const& object, - UnsentPacketQueue* pContent, - int contentLen, - std::string const& contentHash) { - return writeEntireFileFromBuffer_impl( - Reference::addRef(this), bucket, object, pContent, contentLen, contentHash); -} - -ACTOR Future readObject_impl(Reference bstore, - std::string bucket, - std::string object, - void* data, - int length, - int64_t offset) { - try { - if (length <= 0) { - TraceEvent(SevWarn, "S3BlobStoreReadObjectEmptyRead").detail("Length", length); - return 0; - } - - // Log rate limiter state - wait(bstore->requestRateRead->getAllowance(1)); - - state std::string resource = bstore->constructResourcePath(bucket, object); - state HTTP::Headers headers; - headers["Range"] = format("bytes=%lld-%lld", offset, offset + length - 1); - - // Attempt the request - state Reference r; - Reference _r = - wait(doRequest_impl(bstore, "GET", resource, headers, nullptr, 0, { 200, 206, 404 })); - r = _r; - - if (r->code == 404) { - throw file_not_found(); - } - - // Verify response has content - if (r->data.contentLen != r->data.content.size()) { - TraceEvent(SevWarn, "S3BlobStoreReadObjectContentLengthMismatch") - .detail("Expected", r->data.contentLen) - .detail("Actual", r->data.content.size()); - throw io_error(); - } - - // Copy the output bytes, server could have sent more or less bytes than requested so copy at most length - // bytes - int bytesToCopy = std::min(r->data.contentLen, length); - memcpy(data, r->data.content.data(), bytesToCopy); - // Return the number of bytes actually copied - return bytesToCopy; - } catch (Error& e) { - TraceEvent(SevWarn, "S3BlobStoreEndpoint_ReadError") - .error(e) - .detail("Bucket", bucket) - .detail("Object", object) - .detail("Length", length) - .detail("Offset", offset); - throw; - } -} - -Future S3BlobStoreEndpoint::readObject(std::string const& bucket, - std::string const& object, - void* data, - int length, - int64_t offset) { - return readObject_impl(Reference::addRef(this), bucket, object, data, length, offset); -} - -ACTOR static Future beginMultiPartUpload_impl(Reference bstore, - std::string bucket, - std::string object) { - wait(bstore->requestRateWrite->getAllowance(1)); - - state std::string resource = bstore->constructResourcePath(bucket, object); - resource += "?uploads"; - state HTTP::Headers headers; - if (!CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE.empty()) - headers["x-amz-server-side-encryption"] = CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE; - if (bstore->knobs.enable_object_integrity_check) { - // CRITICAL: Adding x-amz-checksum-algorithm to multipart initiation means ALL parts - // must include checksums and completion request must include ChecksumSHA256 tags. - // AWS S3 will reject completion if any part checksum is missing. - headers["x-amz-checksum-algorithm"] = "SHA256"; - } - Reference r = wait(doRequest_impl(bstore, "POST", resource, headers, nullptr, 0, { 200 })); - - try { - xml_document<> doc; - // Copy content because rapidxml will modify it during parse - std::string content = r->data.content; - - doc.parse<0>((char*)content.c_str()); - - // There should be exactly one node - xml_node<>* result = doc.first_node(); - if (result != nullptr && strcmp(result->name(), "InitiateMultipartUploadResult") == 0) { - xml_node<>* id = result->first_node("UploadId"); - if (id != nullptr) { - return id->value(); - } - } - } catch (...) { - } - throw http_bad_response(); -} - -Future S3BlobStoreEndpoint::beginMultiPartUpload(std::string const& bucket, std::string const& object) { - return beginMultiPartUpload_impl(Reference::addRef(this), bucket, object); -} - -ACTOR Future uploadPart_impl(Reference bstore, - std::string bucket, - std::string object, - std::string uploadID, - unsigned int partNumber, - UnsentPacketQueue* pContent, - int contentLen, - std::string contentMD5) { - wait(bstore->requestRateWrite->getAllowance(1)); - wait(bstore->concurrentUploads.take()); - state FlowLock::Releaser uploadReleaser(bstore->concurrentUploads, 1); - - state std::string resource = bstore->constructResourcePath(bucket, object); - resource += format("?partNumber=%d&uploadId=%s", partNumber, uploadID.c_str()); - state HTTP::Headers headers; - // Send hash for content so blobstore can verify it - // Use SHA256 if integrity check is enabled, otherwise use MD5 - if (bstore->knobs.enable_object_integrity_check) { - headers["x-amz-checksum-sha256"] = contentMD5; - headers["x-amz-checksum-algorithm"] = "SHA256"; - } else { - headers["Content-MD5"] = contentMD5; - } - state Reference r = - wait(doRequest_impl(bstore, "PUT", resource, headers, pContent, contentLen, { 200 })); - // TODO: In the event that the client times out just before the request completes (so the client is unaware) then - // the next retry will see error 400. That could be detected and handled gracefully by retrieving the etag for the - // successful request. - - // For uploads, Blobstore returns an MD5 sum of uploaded content so check it. - // Only verify MD5 when not using integrity check (SHA256 verification is done by AWS) - if (!bstore->knobs.enable_object_integrity_check) { - if (!HTTP::verifyMD5(&r->data, false, contentMD5)) - throw checksum_failed(); - } - // Note: When using SHA256, AWS handles the verification internally - // and will return an error if the checksum doesn't match - - // No etag -> bad response. - std::string etag = r->data.headers["ETag"]; - if (etag.empty()) - throw http_bad_response(); - - return etag; -} - -Future S3BlobStoreEndpoint::uploadPart(std::string const& bucket, - std::string const& object, - std::string const& uploadID, - unsigned int partNumber, - UnsentPacketQueue* pContent, - int contentLen, - std::string const& contentMD5) { - return uploadPart_impl(Reference::addRef(this), - bucket, - object, - uploadID, - partNumber, - pContent, - contentLen, - contentMD5); -} - -ACTOR Future> finishMultiPartUpload_impl(Reference bstore, - std::string bucket, - std::string object, - std::string uploadID, - S3BlobStoreEndpoint::MultiPartSetT parts) { - state UnsentPacketQueue part_list; // NonCopyable state var so must be declared at top of actor - wait(bstore->requestRateWrite->getAllowance(1)); - - std::string manifest = ""; - for (auto& p : parts) { - manifest += format("%d%s", p.first, p.second.etag.c_str()); - // Include checksum if integrity check is enabled and checksum is present - if (bstore->knobs.enable_object_integrity_check && !p.second.checksum.empty()) { - manifest += format("%s", p.second.checksum.c_str()); - } - manifest += "\n"; - } - manifest += ""; - - state std::string resource = bstore->constructResourcePath(bucket, object); - resource += format("?uploadId=%s", uploadID.c_str()); - state HTTP::Headers headers; - PacketWriter pw(part_list.getWriteBuffer(manifest.size()), nullptr, Unversioned()); - pw.serializeBytes(manifest); - Reference r = - wait(doRequest_impl(bstore, "POST", resource, headers, &part_list, manifest.size(), { 200 })); - - // The XML response contains a ChecksumSHA256 field, but this is just a hash of the multipart - // structure, not the actual object content, so it's useless for integrity verification. - // We skip parsing it to avoid wasted CPU cycles. - - // TODO: In the event that the client times out just before the request completes (so the client is unaware) then - // the next retry will see error 400. That could be detected and handled gracefully by HEAD'ing the object before - // upload to get its (possibly nonexistent) eTag, then if an error 400 is seen then retrieve the eTag again and if - // it has changed then consider the finish complete. - return Optional(); -} - -Future> S3BlobStoreEndpoint::finishMultiPartUpload(std::string const& bucket, - std::string const& object, - std::string const& uploadID, - MultiPartSetT const& parts) { - return finishMultiPartUpload_impl(Reference::addRef(this), bucket, object, uploadID, parts); -} - -ACTOR Future abortMultiPartUpload_impl(Reference bstore, - std::string bucket, - std::string object, - std::string uploadID) { - wait(bstore->requestRateWrite->getAllowance(1)); - - std::string resource = bstore->constructResourcePath(bucket, object); - resource += format("?uploadId=%s", uploadID.c_str()); - - HTTP::Headers headers; - Reference r = - wait(doRequest_impl(bstore, "DELETE", resource, headers, nullptr, 0, { 200, 204 })); - return Void(); -} - -Future S3BlobStoreEndpoint::abortMultiPartUpload(std::string const& bucket, - std::string const& object, - std::string const& uploadID) { - return abortMultiPartUpload_impl(Reference::addRef(this), bucket, object, uploadID); -} - -// Forward declarations -ACTOR Future> getObjectTags_impl(Reference bstore, - std::string bucket, - std::string object); - -ACTOR Future putObjectTags_impl(Reference bstore, - std::string bucket, - std::string object, - std::map tags) { - state UnsentPacketQueue packets; - wait(bstore->requestRateWrite->getAllowance(1)); - state std::string resource = bstore->constructResourcePath(bucket, object); - resource += "?tagging"; - state int maxRetries = 5; - state int retryCount = 0; - state double backoff = 1.0; - state double maxBackoff = 8.0; - - loop { - try { - std::string manifest = ""; - for (auto itr = tags.begin(); itr != tags.end(); ++itr) { - manifest += "" + itr->first + "" + itr->second + ""; - } - manifest += ""; - - PacketWriter pw(packets.getWriteBuffer(manifest.size()), nullptr, Unversioned()); - pw.serializeBytes(manifest); - - HTTP::Headers headers; - headers["Content-Type"] = "application/xml"; - headers["Content-Length"] = format("%d", manifest.size()); - - Reference r = - wait(doRequest_impl(bstore, "PUT", resource, headers, &packets, manifest.size(), { 200 })); - - // Verify tags were written correctly - std::map verifyTags = wait(getObjectTags_impl(bstore, bucket, object)); - if (verifyTags == tags) { - return Void(); - } - - if (++retryCount >= maxRetries) { - TraceEvent(SevWarn, "S3BlobStorePutTagsMaxRetriesExceeded") - .detail("Bucket", bucket) - .detail("Object", object); - throw operation_failed(); - } - - // Implement exponential backoff with jitter - wait(delay(backoff * (0.9 + 0.2 * deterministicRandom()->random01()))); - backoff = std::min(backoff * 2, maxBackoff); - - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw; - } - - TraceEvent(SevWarn, "S3BlobStorePutTagsError") - .error(e) - .detail("Bucket", bucket) - .detail("Object", object) - .detail("RetryCount", retryCount); - - if (++retryCount >= maxRetries) { - throw; - } - - // Implement exponential backoff with jitter for errors - wait(delay(backoff * (0.9 + 0.2 * deterministicRandom()->random01()))); - backoff = std::min(backoff * 2, maxBackoff); - } - } -} - -Future S3BlobStoreEndpoint::putObjectTags(std::string const& bucket, - std::string const& object, - std::map const& tags) { - return putObjectTags_impl(Reference::addRef(this), bucket, object, tags); -} - -ACTOR Future> getObjectTags_impl(Reference bstore, - std::string bucket, - std::string object) { - wait(bstore->requestRateRead->getAllowance(1)); - - state std::string resource = bstore->constructResourcePath(bucket, object); - resource += "?tagging"; - state HTTP::Headers headers; - - Reference r = wait(doRequest_impl(bstore, "GET", resource, headers, nullptr, 0, { 200 })); - - rapidxml::xml_document<> doc; - doc.parse((char*)r->data.content.c_str()); - - std::map tags; - - // Find the Tagging node (with or without namespace) - rapidxml::xml_node<>* tagging = doc.first_node(); - while (tagging && strcmp(tagging->name(), "Tagging") != 0) { - tagging = tagging->next_sibling(); - } - - if (tagging) { - // Find TagSet node - rapidxml::xml_node<>* tagSet = tagging->first_node(); - while (tagSet && strcmp(tagSet->name(), "TagSet") != 0) { - tagSet = tagSet->next_sibling(); - } - - if (tagSet) { - // Iterate through Tag nodes - for (rapidxml::xml_node<>* tag = tagSet->first_node(); tag; tag = tag->next_sibling()) { - if (strcmp(tag->name(), "Tag") == 0) { - std::string key, value; - // Find Key and Value nodes - for (rapidxml::xml_node<>* node = tag->first_node(); node; node = node->next_sibling()) { - if (strcmp(node->name(), "Key") == 0) { - key = node->value(); - } else if (strcmp(node->name(), "Value") == 0) { - value = node->value(); - } - } - if (!key.empty()) { - tags[key] = value; - } - } - } - } - } - - return tags; -} - -Future> S3BlobStoreEndpoint::getObjectTags(std::string const& bucket, - std::string const& object) { - return getObjectTags_impl(Reference::addRef(this), bucket, object); -} - -TEST_CASE("/backup/s3/v4headers") { - S3BlobStoreEndpoint::Credentials creds{ "AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "" }; - // GET without query parameters - { - S3BlobStoreEndpoint s3("s3.amazonaws.com", "443", "amazonaws", "proxy", "port", creds); - std::string verb("GET"); - std::string resource("/test.txt"); - HTTP::Headers headers; - headers["Host"] = "s3.amazonaws.com"; - s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); - ASSERT(headers["Authorization"] == - "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/amazonaws/s3/aws4_request, " - "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " - "Signature=c6037f4b174f2019d02d7085a611cef8adfe1efe583e220954dc85d59cd31ba3"); - ASSERT(headers["x-amz-date"] == "20130524T000000Z"); - } - - // GET with query parameters - { - S3BlobStoreEndpoint s3("s3.amazonaws.com", "443", "amazonaws", "proxy", "port", creds); - std::string verb("GET"); - std::string resource("/test/examplebucket?Action=DescribeRegions&Version=2013-10-15"); - HTTP::Headers headers; - headers["Host"] = "s3.amazonaws.com"; - s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); - ASSERT(headers["Authorization"] == - "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/amazonaws/s3/aws4_request, " - "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " - "Signature=426f04e71e191fbc30096c306fe1b11ce8f026a7be374541862bbee320cce71c"); - ASSERT(headers["x-amz-date"] == "20130524T000000Z"); - } - - // POST - { - S3BlobStoreEndpoint s3("s3.us-west-2.amazonaws.com", "443", "us-west-2", "proxy", "port", creds); - std::string verb("POST"); - std::string resource("/simple.json"); - HTTP::Headers headers; - headers["Host"] = "s3.us-west-2.amazonaws.com"; - headers["Content-Type"] = "Application/x-amz-json-1.0"; - s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); - ASSERT(headers["Authorization"] == - "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-west-2/s3/aws4_request, " - "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " - "Signature=cf095e36bed9cd3139c2e8b3e20c296a79d8540987711bf3a0d816b19ae00314"); - ASSERT(headers["x-amz-date"] == "20130524T000000Z"); - ASSERT(headers["Host"] == "s3.us-west-2.amazonaws.com"); - ASSERT(headers["Content-Type"] == "Application/x-amz-json-1.0"); - } - - return Void(); -} - -TEST_CASE("/backup/s3/guess_region") { - std::string url = "blobstore://s3.us-west-2.amazonaws.com/resource_name?bucket=bucket_name&sa=1"; - - std::string resource; - std::string error; - S3BlobStoreEndpoint::ParametersT parameters; - Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - ASSERT(s3->getRegion() == "us-west-2"); - - url = "blobstore://s3.us-west-2.amazonaws.com/resource_name?bucket=bucket_name&sc=922337203685477580700"; - try { - s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - ASSERT(false); // not reached - } catch (Error& e) { - // conversion of 922337203685477580700 to long int will overflow - ASSERT_EQ(e.code(), error_code_backup_invalid_url); - } - return Void(); -} - -TEST_CASE("/backup/s3/virtual_hosting_list_resource_path") { - // Test that listObjectsStream_impl constructs valid HTTP resource paths - // for virtual hosting mode URLs with path prefixes. - // - // Regression test for commit 15dd76a7f9 which accidentally removed the leading "/" - // from the query string when switching from ListObjectsV1 to ListObjectsV2. - // - // For URL: blobstore://@bucket.s3.region.amazonaws.com/prefix/path?bucket=... - // constructResourcePath(bucket, "") returns "" in virtual hosting mode. - // We must ensure the final resource starts with "/" before the query string. - - std::string url = "blobstore://@test-bucket.s3.us-west-2.amazonaws.com/bulkload/path?bucket=test-bucket"; - std::string resource; - std::string error; - S3BlobStoreEndpoint::ParametersT parameters; - Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - - ASSERT(s3.isValid()); - ASSERT(resource == "bulkload/path"); // Path prefix extracted from URL (without leading '/') - - // In virtual hosting mode, constructResourcePath returns "" for empty object - std::string listResource = s3->constructResourcePath("test-bucket", ""); - ASSERT(listResource == ""); // Virtual hosting mode doesn't include bucket in path - - // listObjectsStream_impl should add "/" before query string to form valid HTTP request - // Expected: GET /bulkload/path?list-type=2&... (but we can't test the full actor here) - // We can only verify that constructResourcePath returns "" as expected. - // The fix in listObjectsStream_impl checks if resource.empty() and sets it to "/" before - // appending the query string, ensuring the final request is well-formed. - - // Test non-virtual hosting mode for comparison - url = "blobstore://s3.us-west-2.amazonaws.com/resource_path?bucket=test-bucket"; - s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - ASSERT(s3.isValid()); - ASSERT(resource == "resource_path"); // Path extracted from URL (without leading '/') - - // In non-virtual hosting mode, constructResourcePath includes bucket - listResource = s3->constructResourcePath("test-bucket", ""); - ASSERT(listResource == "/test-bucket"); // Bucket is part of path - - return Void(); -} - -TEST_CASE("/backup/s3/constructResourcePath") { - // Test that leading slashes in object keys are properly stripped - // S3 object keys should not start with '/', but if passed, we normalize them - - std::string url = "blobstore://s3.us-west-2.amazonaws.com?bucket=test-bucket"; - std::string resource; - std::string error; - S3BlobStoreEndpoint::ParametersT parameters; - Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - - // Test normal object key (no leading slash) - ASSERT(s3->constructResourcePath("test-bucket", "normal/path/file.txt") == "/test-bucket/normal/path/file.txt"); - - // Test single leading slash - should be stripped - ASSERT(s3->constructResourcePath("test-bucket", "/leading/slash/file.txt") == - "/test-bucket/leading/slash/file.txt"); - - // Test multiple leading slashes - all should be stripped - ASSERT(s3->constructResourcePath("test-bucket", "///multiple/slashes.txt") == "/test-bucket/multiple/slashes.txt"); - - // Test object key that is only slashes - should result in empty object path - ASSERT(s3->constructResourcePath("test-bucket", "///") == "/test-bucket"); - - // Test empty object key - ASSERT(s3->constructResourcePath("test-bucket", "") == "/test-bucket"); - - // Test virtual hosting mode (bucket in hostname) - url = "blobstore://test-bucket.s3.us-west-2.amazonaws.com"; - s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); - - // Virtual hosting mode doesn't include bucket in path - ASSERT(s3->constructResourcePath("test-bucket", "normal/file.txt") == "/normal/file.txt"); - ASSERT(s3->constructResourcePath("test-bucket", "/leading/slash.txt") == "/leading/slash.txt"); - ASSERT(s3->constructResourcePath("test-bucket", "") == ""); - - return Void(); -} diff --git a/fdbclient/S3BlobStore.cpp b/fdbclient/S3BlobStore.cpp new file mode 100644 index 00000000000..e87791466c7 --- /dev/null +++ b/fdbclient/S3BlobStore.cpp @@ -0,0 +1,1698 @@ +/* + * S3BlobStore.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. + */ + +#include "fdbclient/S3BlobStore.h" + +#include +#include "fdbrpc/HTTP.h" +#include "fdbclient/Knobs.h" +#include "flow/FastRef.h" +#include "flow/IConnection.h" +#include "flow/Trace.h" +#include "flow/flow.h" +#include "flow/genericactors.actor.h" + +#include "md5/md5.h" +#include "libb64/encode.h" +#include "sha1/SHA1.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "flow/IAsyncFile.h" +#include "flow/Hostname.h" +#include "flow/UnitTest.h" +#include "rapidxml/rapidxml.hpp" +#ifdef WITH_AWS_BACKUP +#include "FDBAWSCredentialsProvider.h" +#endif + +#include "flow/CoroUtils.h" + +using namespace rapidxml; + +std::string S3BlobStoreEndpoint::guessRegionFromDomain(std::string domain) { + // Special case for localhost/127.0.0.1 to prevent basic_string exception + if (domain == "127.0.0.1" || domain == "localhost") { + return "us-east-1"; + } + + static const std::vector knownServices = { "s3.", "cos.", "oss-", "obs." }; + boost::algorithm::to_lower(domain); + + for (const auto& service : knownServices) { + std::size_t p = domain.find(service); + if (p == std::string::npos || (p >= 1 && domain[p - 1] != '.')) { + // eg. 127.0.0.1, example.com, s3-service.example.com, mys3.example.com + continue; + } + + StringRef h = StringRef(domain).substr(p); + + if (!h.startsWith("oss-"_sr)) { + h.eat(service); // ignore s3 service + } + + return h.eat(".").toString(); + } + + return ""; +} + +static Optional parseS3Credentials(Optional const& credString) { + if (!credString.present()) { + return Optional(); + } + StringRef c = credString.get(); + StringRef key = c.eat(":"); + StringRef secret = c.eat(":"); + StringRef securityToken = c.eat(); + return S3BlobStoreEndpoint::Credentials{ key.toString(), secret.toString(), securityToken.toString() }; +} + +S3BlobStoreEndpoint::S3BlobStoreEndpoint(std::string const& host, + std::string const& service, + std::string region, + Optional const& proxyHost, + Optional const& proxyPort, + Optional const& creds, + BlobKnobs const& knobs, + HTTP::Headers extraHeaders) + : IBlobStoreEndpoint(host, + service, + region.empty() ? guessRegionFromDomain(host) : region, + proxyHost, + proxyPort, + knobs, + extraHeaders), + credentials(parseS3Credentials(creds)), lookupKey(credentials.present() && credentials.get().key.empty()), + lookupSecret(credentials.present() && credentials.get().secret.empty()) { + if (this->region.empty() && CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER) { + throw std::string( + "Failed to get region from host or parameter in url, region is required for aws v4 signature"); + } +} + +std::string S3BlobStoreEndpoint::getResourceURL(std::string resource, std::string params) const { + // The base class produces blobstore://@host/resource?params. + // For S3, we replace the empty credential slot with explicit credentials when they weren't + // looked up from credential files (i.e., they were passed in the URL). + std::string url = IBlobStoreEndpoint::getResourceURL(resource, params); + + // Build the S3 credential prefix to replace the bare "@" + std::string credsString; + if (credentials.present()) { + if (!lookupKey) { + credsString = credentials.get().key; + } + if (!lookupSecret) { + credsString += ":" + credentials.get().secret; + if (!credentials.get().securityToken.empty()) { + credsString += ":" + credentials.get().securityToken; + } + } + } + + // Replace "blobstore://@" with "blobstore://@" + const std::string placeholder = "blobstore://@"; + size_t pos = url.find(placeholder); + if (pos != std::string::npos) { + url.replace(pos, placeholder.size(), "blobstore://" + credsString + "@"); + } + + return url; +} + +std::string S3BlobStoreEndpoint::constructResourcePath(const std::string& bucket, const std::string& object) const { + std::string resource; + + if (host.find(bucket + ".") != 0) { + resource += std::string("/") + bucket; // not virtual hosting mode + } + + if (!object.empty()) { + // S3 object keys should not start with '/'. Strip any leading slashes. + std::string cleanedObject = object; + while (!cleanedObject.empty() && cleanedObject[0] == '/') { + cleanedObject = cleanedObject.substr(1); + } + if (!cleanedObject.empty()) { + resource += "/"; + resource += cleanedObject; + } + } + + return resource; +} + +Future bucketExists_impl(Reference b, std::string bucket) { + co_await b->requestRateRead->getAllowance(1); + + std::string resource = b->constructResourcePath(bucket, ""); + HTTP::Headers headers; + + try { + Reference r = + co_await b->doRequest("HEAD", resource, headers, nullptr, 0, { 200, 404 }); + co_return r->code == 200; + } catch (Error& e) { + if (e.code() == error_code_actor_cancelled) + throw; + TraceEvent(SevError, "S3ClientBucketExistsError") + .detail("Bucket", bucket) + .detail("Host", b->host) + .errorUnsuppressed(e); + throw; + } +} + +Future S3BlobStoreEndpoint::bucketExists(std::string const& bucket) { + return bucketExists_impl(Reference::addRef(this), bucket); +} + +Future objectExists_impl(Reference b, std::string bucket, std::string object) { + co_await b->requestRateRead->getAllowance(1); + + std::string resource = b->constructResourcePath(bucket, object); + HTTP::Headers headers; + + Reference r = co_await b->doRequest("HEAD", resource, headers, nullptr, 0, { 200, 404 }); + co_return r->code == 200; +} + +Future S3BlobStoreEndpoint::objectExists(std::string const& bucket, std::string const& object) { + return objectExists_impl(Reference::addRef(this), bucket, object); +} + +Future deleteObject_impl(Reference b, std::string bucket, std::string object) { + co_await b->requestRateDelete->getAllowance(1); + + std::string resource = b->constructResourcePath(bucket, object); + HTTP::Headers headers; + // 200 or 204 means object successfully deleted, 404 means it already doesn't exist, so any of those are considered + // successful + Reference r = + co_await b->doRequest("DELETE", resource, headers, nullptr, 0, { 200, 204, 404 }); + + // But if the object already did not exist then the 'delete' is assumed to be successful but a warning is logged. + if (r->code == 404) { + TraceEvent(SevWarnAlways, "S3BlobStoreEndpointDeleteObjectMissing") + .detail("Host", b->host) + .detail("Bucket", bucket) + .detail("Object", object); + } +} + +Future S3BlobStoreEndpoint::deleteObject(std::string const& bucket, std::string const& object) { + return deleteObject_impl(Reference::addRef(this), bucket, object); +} + +Future createBucket_impl(Reference b, std::string bucket) { + UnsentPacketQueue packets; + co_await b->requestRateWrite->getAllowance(1); + + bool exists = co_await b->bucketExists(bucket); + if (!exists) { + std::string resource = b->constructResourcePath(bucket, ""); + HTTP::Headers headers; + + std::string region = b->getRegion(); + if (region.empty()) { + Reference r = + co_await b->doRequest("PUT", resource, headers, nullptr, 0, { 200, 409 }); + } else { + Standalone body( + format("" + " %s" + "", + region.c_str())); + PacketWriter pw(packets.getWriteBuffer(), nullptr, Unversioned()); + pw.serializeBytes(body); + + Reference r = + co_await b->doRequest("PUT", resource, headers, &packets, body.size(), { 200, 409 }); + } + } +} + +Future S3BlobStoreEndpoint::createBucket(std::string const& bucket) { + return createBucket_impl(Reference::addRef(this), bucket); +} + +Future objectSize_impl(Reference b, std::string bucket, std::string object) { + co_await b->requestRateRead->getAllowance(1); + + std::string resource = b->constructResourcePath(bucket, object); + HTTP::Headers headers; + + Reference r = co_await b->doRequest("HEAD", resource, headers, nullptr, 0, { 200, 404 }); + if (r->code == 404) + throw file_not_found(); + co_return r->data.contentLen; +} + +Future S3BlobStoreEndpoint::objectSize(std::string const& bucket, std::string const& object) { + return objectSize_impl(Reference::addRef(this), bucket, object); +} + +// If the credentials expire, the connection will eventually fail and be discarded from the pool, and then a new +// connection will be constructed, which will call this again to get updated credentials +static S3BlobStoreEndpoint::Credentials getSecretSdk() { +#ifdef WITH_AWS_BACKUP + double elapsed = -timer_monotonic(); + Aws::Auth::AWSCredentials awsCreds = FDBAWSCredentialsProvider::getAwsCredentials(); + elapsed += timer_monotonic(); + + if (awsCreds.IsEmpty()) { + TraceEvent(SevWarn, "S3BlobStoreAWSCredsEmpty"); + throw backup_auth_missing(); + } + + S3BlobStoreEndpoint::Credentials fdbCreds; + fdbCreds.key = awsCreds.GetAWSAccessKeyId(); + fdbCreds.secret = awsCreds.GetAWSSecretKey(); + fdbCreds.securityToken = awsCreds.GetSessionToken(); + + TraceEvent("S3BlobStoreGotSdkCredentials").suppressFor(60).detail("Duration", elapsed); + + return fdbCreds; +#else + TraceEvent(SevError, "S3BlobStoreNoSDK").log(); + throw backup_auth_missing(); +#endif +} + +Future S3BlobStoreEndpoint::updateSecret() { + if (knobs.sdk_auth) { + credentials = getSecretSdk(); + co_return; + } + if (!credentials.present()) { + co_return; + } + co_await IBlobStoreEndpoint::updateSecret(); +} + +bool S3BlobStoreEndpoint::extractCredentialFields(JSONDoc& account) { + Credentials creds = credentials.get(); + if (lookupKey) { + std::string apiKey; + if (account.tryGet("api_key", apiKey)) + creds.key = apiKey; + else + return false; + } + if (lookupSecret) { + std::string secret; + if (account.tryGet("secret", secret)) + creds.secret = secret; + else + return false; + } + std::string token; + if (account.tryGet("token", token)) + creds.securityToken = token; + credentials = creds; + return true; +} + +std::string S3BlobStoreEndpoint::credentialFileKey() const { + std::string accessKey = lookupKey ? "" : credentials.get().key; + return accessKey + "@" + host; +} + +bool S3BlobStoreEndpoint::lookupSecretOnEachRequest() { + return lookupKey || lookupSecret || knobs.sdk_auth; +} + +std::string awsCanonicalURI(const std::string& resource, std::vector& queryParameters, bool isV4) { + StringRef resourceRef(resource); + resourceRef.eat("/"); + std::string canonicalURI("/" + resourceRef.toString()); + size_t q = canonicalURI.find_last_of('?'); + if (q != canonicalURI.npos) + canonicalURI.resize(q); + if (isV4) { + canonicalURI = HTTP::awsV4URIEncode(canonicalURI, false); + } else { + canonicalURI = HTTP::urlEncode(canonicalURI); + } + + // Create the canonical query string + std::string queryString; + q = resource.find_last_of('?'); + if (q != queryString.npos) + queryString = resource.substr(q + 1); + + StringRef qStr(queryString); + StringRef queryParameter; + while (!(queryParameter = qStr.eat("&")).empty()) { + StringRef param = queryParameter.eat("="); + StringRef value = queryParameter.eat(); + + if (isV4) { + queryParameters.push_back(HTTP::awsV4URIEncode(param.toString(), true) + "=" + + HTTP::awsV4URIEncode(value.toString(), true)); + } else { + queryParameters.push_back(HTTP::urlEncode(param.toString()) + "=" + HTTP::urlEncode(value.toString())); + } + } + + return canonicalURI; +} + +// ref: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html +// Returns the S3 error code string from an XML error response, or "" if the response +// cannot be parsed. This is best-effort for logging/diagnostics only — many HTTP error +// responses (502/503 from load balancers, empty bodies, HTML responses) are not XML. +std::string parseErrorCodeFromS3(const std::string& response) { + if (response.empty()) { + return ""; + } + try { + std::vector xmlBuffer(response.begin(), response.end()); + xmlBuffer.push_back('\0'); + xml_document<> doc; + doc.parse<0>(&xmlBuffer[0]); + xml_node<>* root = doc.first_node("Error"); + if (!root) { + return ""; + } + xml_node<>* codeNode = root->first_node("Code"); + if (!codeNode) { + return ""; + } + return std::string(codeNode->value()); + } catch (...) { + // Parse failures are expected for non-XML responses (HTML error pages, empty bodies, etc.) + TraceEvent("ParseS3ErrorCodeNonXML") + .suppressFor(60) + .detail("ResponseSize", response.size()) + .detail("ResponseHead", response.substr(0, 200)); + return ""; + } +} + +bool isS3TokenError(const std::string& s3Error) { + return s3Error == "InvalidToken" || s3Error == "ExpiredToken"; +} + +// S3 returns HTTP 400 for token/auth errors (InvalidToken, ExpiredToken). +// Used by processRequestFailure, preRetryCheck, and simulateRequestFailure. +constexpr int s3BadRequestCode = 400; + +void S3BlobStoreEndpoint::setRequestHeaders(std::string const& verb, + std::string const& resource, + HTTP::Headers& headers) { + headers["Accept"] = "application/xml"; + if (credentials.present() && !credentials.get().securityToken.empty()) + headers["x-amz-security-token"] = credentials.get().securityToken; + if (CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER) { + setV4AuthHeaders(verb, resource, headers); + } else { + setAuthHeaders(verb, resource, headers); + } +} + +std::string S3BlobStoreEndpoint::normalizeResourceForRequest(std::string const& resource) { + std::vector queryParameters; + std::string canonicalURI = awsCanonicalURI(resource, queryParameters, CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER); + if (!queryParameters.empty()) { + canonicalURI += "?"; + for (size_t i = 0; i < queryParameters.size(); ++i) { + if (i > 0) { + canonicalURI += "&"; + } + canonicalURI += queryParameters[i]; + } + } + return canonicalURI; +} + +void populateDryrunRequest(Reference dryrunRequest, + Reference bstore, + std::string bucket) { + // dryrun with a check bucket exist request, to avoid sending duplicate data + HTTP::Headers headers; + dryrunRequest->verb = "GET"; + dryrunRequest->data.contentLen = 0; + dryrunRequest->data.headers = headers; + dryrunRequest->data.headers["Host"] = bstore->host; + dryrunRequest->data.headers["Accept"] = "application/xml"; + + dryrunRequest->resource = bstore->constructResourcePath(bucket, ""); +} + +bool isWriteRequest(std::string verb) { + return verb == "POST" || verb == "PUT"; +} + +std::string parseBucketFromURI(std::string uri) { + if (uri.size() <= 1 || uri[0] != '/') { + // there is no bucket in the uri + return ""; + } + uri = uri.substr(1); + size_t secondSlash = uri.find('/'); + if (secondSlash == std::string::npos) { + return uri; + } + return uri.substr(0, secondSlash); +} + +void S3BlobStoreEndpoint::simulateRequestFailure(std::string const& verb, + std::string const& resource, + Reference& r) { + simulatedTokenError = false; + if (!g_network->isSimulated() || !BUGGIFY || deterministicRandom()->random01() >= 0.1) { + return; + } + // Don't simulate token errors for multipart complete operations (POST with uploadId but no partNumber) + // because changing a successful 200 to 400 after the server has already completed and removed + // the upload causes the client to infinitely retry with a phantom upload ID. + bool isMultipartComplete = verb == "POST" && resource.find("uploadId=") != std::string::npos && + resource.find("partNumber=") == std::string::npos; + if (!isMultipartComplete) { + r->code = s3BadRequestCode; + simulatedTokenError = true; + } +} + +void S3BlobStoreEndpoint::processRequestFailure(Reference const& r, + TraceEvent& event, + bool& retryExtended) { + // Only parse S3 error code for error responses (4xx/5xx), not successful responses (2xx). + // Skip parsing for simulated errors where response content is still binary data. + std::string s3Error; + if (r->code >= 400 && !simulatedTokenError) { + s3Error = parseErrorCodeFromS3(r->data.content); + } + event.detail("S3ErrorCode", s3Error); + event.detail("HttpResponseContent", r->data.content); + + if (r->code == s3BadRequestCode && (isS3TokenError(s3Error) || simulatedTokenError)) { + retryExtended = true; + } +} + +Future S3BlobStoreEndpoint::preRetryCheck(std::string const& verb, + std::string const& resource, + ReusableConnection& rconn, + int requestTimeout, + bool& retryExtended) { + if (!isWriteRequest(verb) || !CLIENT_KNOBS->BACKUP_ALLOW_DRYRUN) { + co_return true; + } + + std::string bucket = parseBucketFromURI(resource); + if (bucket.empty()) { + TraceEvent(SevError, "EmptyBucketRequest").detail("Verb", verb).detail("Resource", resource); + throw bucket_not_in_url(); + } + + // Send a cheap HEAD request to verify credentials before resending a potentially large upload + UnsentPacketQueue dryrunContentCopy; + auto dryrunRequest = makeReference(); + dryrunRequest->data.content = &dryrunContentCopy; + populateDryrunRequest(dryrunRequest, Reference::addRef(this), bucket); + setRequestHeaders(dryrunRequest->verb, dryrunRequest->resource, dryrunRequest->data.headers); + dryrunRequest->resource = normalizeResourceForRequest(dryrunRequest->resource); + + TraceEvent("RetryS3RequestDueToTokenIssue") + .detail("OriginalResource", resource) + .detail("DryrunResource", dryrunRequest->resource) + .detail("Bucket", bucket) + .detail("V4", CLIENT_KNOBS->HTTP_REQUEST_AWS_V4_HEADER); + + co_await requestRate->getAllowance(1); + Future> dryrunResponse = + HTTP::doRequest(rconn.conn, dryrunRequest, sendRate, &s_stats.bytes_sent, recvRate); + Reference dryrunR = co_await timeoutError(dryrunResponse, requestTimeout); + + std::string s3Error; + if (dryrunR->code >= 400) { + s3Error = parseErrorCodeFromS3(dryrunR->data.content); + } + + if (dryrunR->code == s3BadRequestCode && isS3TokenError(s3Error)) { + // Token still bad — delay and skip this iteration + co_await ::delay(knobs.max_delay_retryable_error); + co_return false; + } else if (dryrunR->code == 200 || dryrunR->code == 404) { + // Credentials are good now — proceed with the real request + TraceEvent("S3TokenIssueResolved").detail("HttpCode", dryrunR->code).detail("URI", dryrunRequest->resource); + retryExtended = false; + co_return true; + } else { + TraceEvent(SevError, "S3UnexpectedError") + .detail("HttpCode", dryrunR->code) + .detail("HttpResponseContent", dryrunR->data.content) + .detail("S3Error", s3Error) + .detail("URI", dryrunRequest->resource); + throw http_bad_response(); + } +} + +Future listObjectsStream_impl(Reference bstore, + std::string bucket, + PromiseStream results, + Optional prefix, + Optional delimiter, + int maxDepth, + std::function recurseFilterUnsafe) { + // C++20 coroutine safety: copy const& params to survive across suspension. + auto recurseFilter = recurseFilterUnsafe; + + std::string resource = bstore->constructResourcePath(bucket, ""); + // In virtual hosting mode, constructResourcePath returns "" for empty object. + // We need to ensure the query string starts with "/" to form a valid HTTP request. + // Commit 15dd76a7f9 switched from ListObjectsV1 to V2 and accidentally removed the leading "/". + if (resource.empty()) { + resource = "/"; + } + resource.append("?list-type=2&max-keys=").append(std::to_string(CLIENT_KNOBS->BLOBSTORE_LIST_MAX_KEYS_PER_PAGE)); + + if (prefix.present()) + resource.append("&prefix=").append(prefix.get()); + if (delimiter.present()) + resource.append("&delimiter=").append(std::string(1, delimiter.get())); + + std::string continuationToken; + bool more = true; + std::vector> subLists; + + while (more) { + co_await bstore->concurrentLists.take(); + FlowLock::Releaser listReleaser(bstore->concurrentLists, 1); + + HTTP::Headers headers; + std::string fullResource = resource; + if (!continuationToken.empty()) { + fullResource.append("&continuation-token=").append(continuationToken); + } + + Reference r = + co_await bstore->doRequest("GET", fullResource, headers, nullptr, 0, { 200, 404 }); + listReleaser.release(); + + try { + S3BlobStoreEndpoint::ListResult listResult; + + // If we got a 404, throw an error to indicate the resource doesn't exist + if (r->code == 404) { + TraceEvent(SevError, "S3BlobStoreResourceNotFound") + .detail("Bucket", bucket) + .detail("Prefix", prefix.present() ? prefix.get() : "") + .detail("Resource", fullResource); + throw resource_not_found(); + } + + xml_document<> doc; + + // Copy content because rapidxml will modify it during parse + std::string content = r->data.content; + doc.parse<0>((char*)content.c_str()); + + // There should be exactly one node + xml_node<>* result = doc.first_node(); + if (result == nullptr || strcmp(result->name(), "ListBucketResult") != 0) { + throw http_bad_response(); + } + + xml_node<>* n = result->first_node(); + more = false; + continuationToken.clear(); + + while (n != nullptr) { + const char* name = n->name(); + if (strcmp(name, "IsTruncated") == 0) { + const char* val = n->value(); + if (strcmp(val, "true") == 0) { + more = true; + } else if (strcmp(val, "false") == 0) { + more = false; + } else { + throw http_bad_response(); + } + } else if (strcmp(name, "NextContinuationToken") == 0) { + if (n->value() != nullptr) { + continuationToken = n->value(); + } + } else if (strcmp(name, "Contents") == 0) { + S3BlobStoreEndpoint::ObjectInfo object; + + xml_node<>* key = n->first_node("Key"); + if (key == nullptr) { + throw http_bad_response(); + } + // URL decode the object name since S3 XML responses contain URL-encoded names + object.name = HTTP::urlDecode(key->value()); + + xml_node<>* size = n->first_node("Size"); + if (size == nullptr) { + throw http_bad_response(); + } + object.size = strtoull(size->value(), nullptr, 10); + + listResult.objects.push_back(object); + } else if (strcmp(name, "CommonPrefixes") == 0) { + xml_node<>* prefixNode = n->first_node("Prefix"); + while (prefixNode != nullptr) { + const char* prefix = prefixNode->value(); + // If recursing, queue a sub-request, otherwise add the common prefix to the result. + if (maxDepth > 0) { + if (!recurseFilter || recurseFilter(prefix)) { + // For recursive listing, don't use delimiter in sub-requests to get individual files + subLists.push_back(bstore->listObjectsStream( + bucket, results, prefix, Optional(), maxDepth - 1, recurseFilter)); + } + } else { + listResult.commonPrefixes.push_back(prefix); + } + prefixNode = prefixNode->next_sibling("Prefix"); + } + } + n = n->next_sibling(); + } + + results.send(listResult); + } catch (Error& e) { + if (e.code() != error_code_actor_cancelled) { + TraceEvent(SevWarn, "S3BlobStoreEndpointListResultParseError") + .errorUnsuppressed(e) + .suppressFor(60) + .detail("Resource", fullResource); + } + throw http_bad_response(); + } + } + + co_await waitForAll(subLists); +} + +Future S3BlobStoreEndpoint::listObjectsStream(std::string const& bucket, + PromiseStream results, + Optional prefix, + Optional delimiter, + int maxDepth, + std::function recurseFilter) { + return listObjectsStream_impl( + Reference::addRef(this), bucket, results, prefix, delimiter, maxDepth, recurseFilter); +} + +AsyncResult> listBuckets_impl(Reference bstore) { + std::string resource = "/?marker="; + std::string lastName; + bool more = true; + std::vector buckets; + + while (more) { + co_await bstore->concurrentLists.take(); + FlowLock::Releaser listReleaser(bstore->concurrentLists, 1); + + HTTP::Headers headers; + std::string fullResource = resource + lastName; + Reference r = + co_await bstore->doRequest("GET", fullResource, headers, nullptr, 0, { 200 }); + listReleaser.release(); + + try { + xml_document<> doc; + + // Copy content because rapidxml will modify it during parse + std::string content = r->data.content; + doc.parse<0>((char*)content.c_str()); + + // There should be exactly one node + xml_node<>* result = doc.first_node(); + if (result == nullptr || strcmp(result->name(), "ListAllMyBucketsResult") != 0) { + throw http_bad_response(); + } + + more = false; + xml_node<>* truncated = result->first_node("IsTruncated"); + if (truncated != nullptr && strcmp(truncated->value(), "true") == 0) { + more = true; + } + + xml_node<>* bucketsNode = result->first_node("Buckets"); + if (bucketsNode != nullptr) { + xml_node<>* bucketNode = bucketsNode->first_node("Bucket"); + while (bucketNode != nullptr) { + xml_node<>* nameNode = bucketNode->first_node("Name"); + if (nameNode == nullptr) { + throw http_bad_response(); + } + const char* name = nameNode->value(); + buckets.push_back(name); + + bucketNode = bucketNode->next_sibling("Bucket"); + } + } + + if (more) { + lastName = buckets.back(); + } + + } catch (Error& e) { + if (e.code() != error_code_actor_cancelled) + TraceEvent(SevWarn, "S3BlobStoreEndpointListBucketResultParseError") + .errorUnsuppressed(e) + .suppressFor(60) + .detail("Resource", fullResource); + throw http_bad_response(); + } + } + + co_return buckets; +} + +AsyncResult> S3BlobStoreEndpoint::listBuckets() { + return listBuckets_impl(Reference::addRef(this)); +} + +std::string S3BlobStoreEndpoint::hmac_sha1(Credentials const& creds, std::string const& msg) { + std::string key = creds.secret; + + // Hash key to shorten it if it is longer than SHA1 block size + if (key.size() > 64) { + key = SHA1::from_string(key); + } + + // Pad key up to SHA1 block size if needed + key.append(64 - key.size(), '\0'); + + std::string kipad = key; + for (int i = 0; i < 64; ++i) + kipad[i] ^= '\x36'; + + std::string kopad = key; + for (int i = 0; i < 64; ++i) + kopad[i] ^= '\x5c'; + + kipad.append(msg); + std::string hkipad = SHA1::from_string(kipad); + kopad.append(hkipad); + return SHA1::from_string(kopad); +} + +static void sha256(const unsigned char* data, const size_t len, unsigned char* hash) { + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, data, len); + SHA256_Final(hash, &sha256); +} + +std::string sha256_hex(std::string str) { + unsigned char hash[SHA256_DIGEST_LENGTH]; + sha256((const unsigned char*)str.c_str(), str.size(), hash); + std::stringstream ss; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + return ss.str(); +} + +// Return base64'd SHA256 hash of input string. +std::string sha256_base64(std::string str) { + unsigned char hash[SHA256_DIGEST_LENGTH]; + sha256((const unsigned char*)str.c_str(), str.size(), hash); + std::string hashAsStr = std::string((char*)hash, SHA256_DIGEST_LENGTH); + std::string sig = base64::encoder::from_string(hashAsStr); + // base64 encoded blocks end in \n so remove last character. + sig.resize(sig.size() - 1); + return sig; +} + +std::string hmac_sha256_hex(std::string key, std::string msg) { + unsigned char hash[32]; + + HMAC_CTX* hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, &key[0], key.length(), EVP_sha256(), nullptr); + HMAC_Update(hmac, (unsigned char*)&msg[0], msg.length()); + unsigned int len = 32; + HMAC_Final(hmac, hash, &len); + HMAC_CTX_free(hmac); + + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (int i = 0; i < len; i++) { + ss << std::hex << std::setw(2) << (unsigned int)hash[i]; + } + return (ss.str()); +} + +std::string hmac_sha256(std::string key, std::string msg) { + unsigned char hash[32]; + + HMAC_CTX* hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, &key[0], key.length(), EVP_sha256(), nullptr); + HMAC_Update(hmac, (unsigned char*)&msg[0], msg.length()); + unsigned int len = 32; + HMAC_Final(hmac, hash, &len); + HMAC_CTX_free(hmac); + + std::stringstream ss; + ss << std::setfill('0'); + for (int i = 0; i < len; i++) { + ss << hash[i]; + } + return (ss.str()); +} + +// Date and Time parameters are used for unit testing +void S3BlobStoreEndpoint::setV4AuthHeaders(std::string const& verb, + std::string const& resource, + HTTP::Headers& headers, + std::string date, + std::string datestamp) { + if (!credentials.present()) { + return; + } + Credentials creds = credentials.get(); + // std::cout << "========== Starting===========" << std::endl; + std::string accessKey = creds.key; + std::string secretKey = creds.secret; + // Create a date for headers and the credential string + std::string amzDate; + std::string dateStamp; + if (date.empty() || datestamp.empty()) { + time_t ts; + time(&ts); + char dateBuf[20]; + // ISO 8601 format YYYYMMDD'T'HHMMSS'Z' + strftime(dateBuf, 20, "%Y%m%dT%H%M%SZ", gmtime(&ts)); + amzDate = dateBuf; + strftime(dateBuf, 20, "%Y%m%d", gmtime(&ts)); + dateStamp = dateBuf; + } else { + amzDate = date; + dateStamp = datestamp; + } + + // ************* TASK 1: CREATE A CANONICAL REQUEST ************* + // Create Create canonical URI--the part of the URI from domain to query string (use '/' if no path) + std::vector queryParameters; + std::string canonicalURI = awsCanonicalURI(resource, queryParameters, true); + + std::string canonicalQueryString; + if (!queryParameters.empty()) { + std::sort(queryParameters.begin(), queryParameters.end()); + canonicalQueryString = boost::algorithm::join(queryParameters, "&"); + } + + using namespace boost::algorithm; + // Create the canonical headers and signed headers + ASSERT(!headers["Host"].empty()); + // Be careful. There is x-amz-content-sha256 for auth and then + // x-amz-checksum-sha256 for object integrity check. + headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + headers["x-amz-date"] = amzDate; + std::vector> headersList; + headersList.push_back({ "host", trim_copy(headers["Host"]) + "\n" }); + if (headers.contains("Content-Type")) + headersList.push_back({ "content-type", trim_copy(headers["Content-Type"]) + "\n" }); + if (headers.contains("Content-MD5")) + headersList.push_back({ "content-md5", trim_copy(headers["Content-MD5"]) + "\n" }); + for (const auto& [headerName, headerValue] : headers) { + if (StringRef(headerName).startsWith("x-amz"_sr)) + headersList.push_back({ to_lower_copy(headerName), trim_copy(headerValue) + "\n" }); + } + std::sort(headersList.begin(), headersList.end()); + std::string canonicalHeaders; + std::string signedHeaders; + for (const auto& [headerName, headerValue] : headersList) { + canonicalHeaders += headerName + ":" + headerValue; + signedHeaders += headerName + ";"; + } + signedHeaders.pop_back(); + std::string canonicalRequest = verb + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + + "\n" + signedHeaders + "\n" + headers["x-amz-content-sha256"]; + + // ************* TASK 2: CREATE THE STRING TO SIGN************* + std::string algorithm = "AWS4-HMAC-SHA256"; + std::string credentialScope = dateStamp + "/" + region + "/s3/" + "aws4_request"; + std::string stringToSign = + algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + sha256_hex(canonicalRequest); + + // ************* TASK 3: CALCULATE THE SIGNATURE ************* + // Create the signing key using the function defined above. + std::string signingKey = + hmac_sha256(hmac_sha256(hmac_sha256(hmac_sha256("AWS4" + secretKey, dateStamp), region), "s3"), "aws4_request"); + // Sign the string_to_sign using the signing_key + std::string signature = hmac_sha256_hex(signingKey, stringToSign); + // ************* TASK 4: ADD SIGNING INFORMATION TO THE Header ************* + std::string authorizationHeader = algorithm + " " + "Credential=" + accessKey + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + headers["Authorization"] = authorizationHeader; +} + +void S3BlobStoreEndpoint::setAuthHeaders(std::string const& verb, std::string const& resource, HTTP::Headers& headers) { + if (!credentials.present()) { + return; + } + Credentials creds = credentials.get(); + + std::string& date = headers["Date"]; + + char dateBuf[64]; + time_t ts; + time(&ts); + strftime(dateBuf, 64, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&ts)); + date = dateBuf; + + std::string msg; + msg.append(verb); + msg.append("\n"); + auto contentMD5 = headers.find("Content-MD5"); + if (contentMD5 != headers.end()) + msg.append(contentMD5->second); + msg.append("\n"); + auto contentType = headers.find("Content-Type"); + if (contentType != headers.end()) + msg.append(contentType->second); + msg.append("\n"); + msg.append(date); + msg.append("\n"); + for (const auto& [headerName, headerValue] : headers) { + StringRef name = headerName; + if (name.startsWith("x-amz"_sr) || name.startsWith("x-icloud"_sr)) { + msg.append(headerName); + msg.append(":"); + msg.append(headerValue); + msg.append("\n"); + } + } + + msg.append(resource); + if (verb == "GET") { + size_t q = resource.find_last_of('?'); + if (q != resource.npos) + msg.resize(msg.size() - (resource.size() - q)); + } + + std::string sig = base64::encoder::from_string(hmac_sha1(creds, msg)); + // base64 encoded blocks end in \n so remove it. + sig.resize(sig.size() - 1); + std::string auth = "AWS "; + auth.append(creds.key); + auth.append(":"); + auth.append(sig); + headers["Authorization"] = auth; +} + +AsyncResult readEntireFile_impl(Reference bstore, + std::string bucket, + std::string object) { + co_await bstore->requestRateRead->getAllowance(1); + + std::string resource = bstore->constructResourcePath(bucket, object); + HTTP::Headers headers; + // Set this header on the GET for it to volunteer saved checksum in the response headers. + // See https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html#API_GetObject_RequestSyntax + headers["x-amz-checksum-mode"] = "ENABLED"; + Reference r = + co_await bstore->doRequest("GET", resource, headers, nullptr, 0, { 200, 404 }); + if (r->code == 404) + throw file_not_found(); + if (bstore->knobs.enable_object_integrity_check) { + // Verify the content. We set 'x-amz-checksum-mode' on the GET request above so + // the server will return the sha256 checksum we set when we uploaded the object in the + // GET response headers. See + // https://stackoverflow.com/questions/36540234/what-is-the-difference-getcontentmd5-and-getetag-of-aws-s3-putobjectresult + std::string checksumSHA256 = r->data.headers["x-amz-checksum-sha256"]; + if (checksumSHA256.empty()) { + // This is what is thrown elsewhere when no expected etag. + throw http_bad_response(); + } + // Calculate the sha256 checksum of the content and compare it to the checksum returned by the server. + std::string contentSHA256 = sha256_base64(r->data.content); + if (checksumSHA256 != contentSHA256) { + throw checksum_failed(); + } + } + co_return r->data.content; +} + +AsyncResult S3BlobStoreEndpoint::readEntireFile(std::string const& bucket, std::string const& object) { + return readEntireFile_impl(Reference::addRef(this), bucket, object); +} + +Future writeEntireFileFromBuffer_impl(Reference bstore, + std::string bucket, + std::string object, + UnsentPacketQueue* pContent, + int contentLen, + std::string contentHash) { + if (contentLen > bstore->knobs.multipart_max_part_size) + throw file_too_large(); + + co_await bstore->requestRateWrite->getAllowance(1); + co_await bstore->concurrentUploads.take(); + FlowLock::Releaser uploadReleaser(bstore->concurrentUploads, 1); + + std::string resource = bstore->constructResourcePath(bucket, object); + HTTP::Headers headers; + // contentHash is calculated by the caller. It is md5 or sha256 dependent on + // enable_object_integrity_check setting. If the hash (md5 or sha256) we + // volunteer doesn't match that calculated serverside, the upload fails with: + // InvalidDigestThe Content-MD5 you specified was invalid. + if (bstore->knobs.enable_object_integrity_check) { + headers["x-amz-checksum-sha256"] = contentHash; + headers["x-amz-checksum-algorithm"] = "SHA256"; + } else { + headers["Content-MD5"] = contentHash; + } + if (!CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE.empty()) + headers["x-amz-server-side-encryption"] = CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE; + Reference r = + co_await bstore->doRequest("PUT", resource, headers, pContent, contentLen, { 200 }); +} + +Future S3BlobStoreEndpoint::writeEntireFileFromBuffer(std::string const& bucket, + std::string const& object, + UnsentPacketQueue* pContent, + int contentLen, + std::string const& contentHash) { + return writeEntireFileFromBuffer_impl( + Reference::addRef(this), bucket, object, pContent, contentLen, contentHash); +} + +Future readObject_impl(Reference bstore, + std::string bucket, + std::string object, + void* data, + int length, + int64_t offset) { + try { + if (length <= 0) { + TraceEvent(SevWarn, "S3BlobStoreReadObjectEmptyRead").detail("Length", length); + co_return 0; + } + + // Log rate limiter state + co_await bstore->requestRateRead->getAllowance(1); + + std::string resource = bstore->constructResourcePath(bucket, object); + HTTP::Headers headers; + headers["Range"] = format("bytes=%lld-%lld", offset, offset + length - 1); + + // Attempt the request + Reference r; + Reference _r = + co_await bstore->doRequest("GET", resource, headers, nullptr, 0, { 200, 206, 404 }); + r = _r; + + if (r->code == 404) { + throw file_not_found(); + } + + // Verify response has content + if (r->data.contentLen != r->data.content.size()) { + TraceEvent(SevWarn, "S3BlobStoreReadObjectContentLengthMismatch") + .detail("Expected", r->data.contentLen) + .detail("Actual", r->data.content.size()); + throw io_error(); + } + + // Copy the output bytes, server could have sent more or less bytes than requested so copy at most length + // bytes + int bytesToCopy = std::min(r->data.contentLen, length); + memcpy(data, r->data.content.data(), bytesToCopy); + // Return the number of bytes actually copied + co_return bytesToCopy; + } catch (Error& e) { + TraceEvent(SevWarn, "S3BlobStoreEndpoint_ReadError") + .error(e) + .detail("Bucket", bucket) + .detail("Object", object) + .detail("Length", length) + .detail("Offset", offset); + throw; + } +} + +Future S3BlobStoreEndpoint::readObject(std::string const& bucket, + std::string const& object, + void* data, + int length, + int64_t offset) { + return readObject_impl(Reference::addRef(this), bucket, object, data, length, offset); +} + +static Future beginMultiPartUpload_impl(Reference bstore, + std::string bucket, + std::string object) { + co_await bstore->requestRateWrite->getAllowance(1); + + std::string resource = bstore->constructResourcePath(bucket, object); + resource += "?uploads"; + HTTP::Headers headers; + if (!CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE.empty()) + headers["x-amz-server-side-encryption"] = CLIENT_KNOBS->BLOBSTORE_ENCRYPTION_TYPE; + if (bstore->knobs.enable_object_integrity_check) { + // CRITICAL: Adding x-amz-checksum-algorithm to multipart initiation means ALL parts + // must include checksums and completion request must include ChecksumSHA256 tags. + // AWS S3 will reject completion if any part checksum is missing. + headers["x-amz-checksum-algorithm"] = "SHA256"; + } + Reference r = co_await bstore->doRequest("POST", resource, headers, nullptr, 0, { 200 }); + + try { + xml_document<> doc; + // Copy content because rapidxml will modify it during parse + std::string content = r->data.content; + + doc.parse<0>((char*)content.c_str()); + + // There should be exactly one node + xml_node<>* result = doc.first_node(); + if (result != nullptr && strcmp(result->name(), "InitiateMultipartUploadResult") == 0) { + xml_node<>* id = result->first_node("UploadId"); + if (id != nullptr) { + co_return id->value(); + } + } + } catch (...) { + } + throw http_bad_response(); +} + +Future S3BlobStoreEndpoint::beginMultiPartUpload(std::string const& bucket, std::string const& object) { + return beginMultiPartUpload_impl(Reference::addRef(this), bucket, object); +} + +Future uploadPart_impl(Reference bstore, + std::string bucket, + std::string object, + std::string uploadID, + unsigned int partNumber, + UnsentPacketQueue* pContent, + int contentLen, + std::string contentMD5) { + co_await bstore->requestRateWrite->getAllowance(1); + co_await bstore->concurrentUploads.take(); + FlowLock::Releaser uploadReleaser(bstore->concurrentUploads, 1); + + std::string resource = bstore->constructResourcePath(bucket, object); + resource += format("?partNumber=%d&uploadId=%s", partNumber, uploadID.c_str()); + HTTP::Headers headers; + // Send hash for content so blobstore can verify it + // Use SHA256 if integrity check is enabled, otherwise use MD5 + if (bstore->knobs.enable_object_integrity_check) { + headers["x-amz-checksum-sha256"] = contentMD5; + headers["x-amz-checksum-algorithm"] = "SHA256"; + } else { + headers["Content-MD5"] = contentMD5; + } + Reference r = + co_await bstore->doRequest("PUT", resource, headers, pContent, contentLen, { 200 }); + // TODO: In the event that the client times out just before the request completes (so the client is unaware) then + // the next retry will see error 400. That could be detected and handled gracefully by retrieving the etag for the + // successful request. + + // For uploads, Blobstore returns an MD5 sum of uploaded content so check it. + // Only verify MD5 when not using integrity check (SHA256 verification is done by AWS) + if (!bstore->knobs.enable_object_integrity_check) { + if (!HTTP::verifyMD5(&r->data, false, contentMD5)) + throw checksum_failed(); + } + // Note: When using SHA256, AWS handles the verification internally + // and will return an error if the checksum doesn't match + + // No etag -> bad response. + std::string etag = r->data.headers["ETag"]; + if (etag.empty()) + throw http_bad_response(); + + co_return etag; +} + +Future S3BlobStoreEndpoint::uploadPart(std::string const& bucket, + std::string const& object, + std::string const& uploadID, + unsigned int partNumber, + UnsentPacketQueue* pContent, + int contentLen, + std::string const& contentMD5) { + return uploadPart_impl(Reference::addRef(this), + bucket, + object, + uploadID, + partNumber, + pContent, + contentLen, + contentMD5); +} + +Future> finishMultiPartUpload_impl(Reference bstore, + std::string bucket, + std::string object, + std::string uploadID, + S3BlobStoreEndpoint::MultiPartSetT parts, + int64_t /*totalSize*/) { + UnsentPacketQueue part_list; // NonCopyable state var so must be declared at top of actor + co_await bstore->requestRateWrite->getAllowance(1); + + std::string manifest = ""; + for (auto& p : parts) { + manifest += format("%d%s", p.first, p.second.etag.c_str()); + // Include checksum if integrity check is enabled and checksum is present + if (bstore->knobs.enable_object_integrity_check && !p.second.checksum.empty()) { + manifest += format("%s", p.second.checksum.c_str()); + } + manifest += "\n"; + } + manifest += ""; + + std::string resource = bstore->constructResourcePath(bucket, object); + resource += format("?uploadId=%s", uploadID.c_str()); + HTTP::Headers headers; + PacketWriter pw(part_list.getWriteBuffer(manifest.size()), nullptr, Unversioned()); + pw.serializeBytes(manifest); + Reference r = + co_await bstore->doRequest("POST", resource, headers, &part_list, manifest.size(), { 200 }); + + // The XML response contains a ChecksumSHA256 field, but this is just a hash of the multipart + // structure, not the actual object content, so it's useless for integrity verification. + // We skip parsing it to avoid wasted CPU cycles. + + // TODO: In the event that the client times out just before the request completes (so the client is unaware) then + // the next retry will see error 400. That could be detected and handled gracefully by HEAD'ing the object before + // upload to get its (possibly nonexistent) eTag, then if an error 400 is seen then retrieve the eTag again and if + // it has changed then consider the finish complete. + co_return Optional(); +} + +Future> S3BlobStoreEndpoint::finishMultiPartUpload(std::string const& bucket, + std::string const& object, + std::string const& uploadID, + MultiPartSetT const& parts, + int64_t totalSize) { + return finishMultiPartUpload_impl( + Reference::addRef(this), bucket, object, uploadID, parts, totalSize); +} + +Future abortMultiPartUpload_impl(Reference bstore, + std::string bucket, + std::string object, + std::string uploadID) { + co_await bstore->requestRateWrite->getAllowance(1); + + std::string resource = bstore->constructResourcePath(bucket, object); + resource += format("?uploadId=%s", uploadID.c_str()); + + HTTP::Headers headers; + Reference r = + co_await bstore->doRequest("DELETE", resource, headers, nullptr, 0, { 200, 204 }); +} + +Future S3BlobStoreEndpoint::abortMultiPartUpload(std::string const& bucket, + std::string const& object, + std::string const& uploadID) { + return abortMultiPartUpload_impl(Reference::addRef(this), bucket, object, uploadID); +} + +// Forward declarations +Future> getObjectTags_impl(Reference bstore, + std::string bucket, + std::string object); + +Future putObjectTags_impl(Reference bstore, + std::string bucket, + std::string object, + std::map tags) { + UnsentPacketQueue packets; + co_await bstore->requestRateWrite->getAllowance(1); + std::string resource = bstore->constructResourcePath(bucket, object); + resource += "?tagging"; + int maxRetries = 5; + int retryCount = 0; + double backoff = 1.0; + double maxBackoff = 8.0; + + while (true) { + Error err; + try { + std::string manifest = ""; + for (auto itr = tags.begin(); itr != tags.end(); ++itr) { + manifest += "" + itr->first + "" + itr->second + ""; + } + manifest += ""; + + PacketWriter pw(packets.getWriteBuffer(manifest.size()), nullptr, Unversioned()); + pw.serializeBytes(manifest); + + HTTP::Headers headers; + headers["Content-Type"] = "application/xml"; + headers["Content-Length"] = format("%d", manifest.size()); + + Reference r = + co_await bstore->doRequest("PUT", resource, headers, &packets, manifest.size(), { 200 }); + + // Verify tags were written correctly + std::map verifyTags = co_await getObjectTags_impl(bstore, bucket, object); + if (verifyTags == tags) { + co_return; + } + + if (++retryCount >= maxRetries) { + TraceEvent(SevWarn, "S3BlobStorePutTagsMaxRetriesExceeded") + .detail("Bucket", bucket) + .detail("Object", object); + throw operation_failed(); + } + + // Implement exponential backoff with jitter + co_await delay(backoff * (0.9 + 0.2 * deterministicRandom()->random01())); + backoff = std::min(backoff * 2, maxBackoff); + continue; + } catch (Error& e) { + err = e; + } + + if (err.code() == error_code_actor_cancelled) { + throw err; + } + + TraceEvent(SevWarn, "S3BlobStorePutTagsError") + .error(err) + .detail("Bucket", bucket) + .detail("Object", object) + .detail("RetryCount", retryCount); + + if (++retryCount >= maxRetries) { + throw err; + } + + // Implement exponential backoff with jitter for errors + co_await delay(backoff * (0.9 + 0.2 * deterministicRandom()->random01())); + backoff = std::min(backoff * 2, maxBackoff); + } +} + +Future S3BlobStoreEndpoint::putObjectTags(std::string const& bucket, + std::string const& object, + std::map const& tags) { + return putObjectTags_impl(Reference::addRef(this), bucket, object, tags); +} + +Future> getObjectTags_impl(Reference bstore, + std::string bucket, + std::string object) { + co_await bstore->requestRateRead->getAllowance(1); + + std::string resource = bstore->constructResourcePath(bucket, object); + resource += "?tagging"; + HTTP::Headers headers; + + Reference r = co_await bstore->doRequest("GET", resource, headers, nullptr, 0, { 200 }); + + rapidxml::xml_document<> doc; + doc.parse((char*)r->data.content.c_str()); + + std::map tags; + + // Find the Tagging node (with or without namespace) + rapidxml::xml_node<>* tagging = doc.first_node(); + while (tagging && strcmp(tagging->name(), "Tagging") != 0) { + tagging = tagging->next_sibling(); + } + + if (tagging) { + // Find TagSet node + rapidxml::xml_node<>* tagSet = tagging->first_node(); + while (tagSet && strcmp(tagSet->name(), "TagSet") != 0) { + tagSet = tagSet->next_sibling(); + } + + if (tagSet) { + // Iterate through Tag nodes + for (rapidxml::xml_node<>* tag = tagSet->first_node(); tag; tag = tag->next_sibling()) { + if (strcmp(tag->name(), "Tag") == 0) { + std::string key, value; + // Find Key and Value nodes + for (rapidxml::xml_node<>* node = tag->first_node(); node; node = node->next_sibling()) { + if (strcmp(node->name(), "Key") == 0) { + key = node->value(); + } else if (strcmp(node->name(), "Value") == 0) { + value = node->value(); + } + } + if (!key.empty()) { + tags[key] = value; + } + } + } + } + } + + co_return tags; +} + +Future> S3BlobStoreEndpoint::getObjectTags(std::string const& bucket, + std::string const& object) { + return getObjectTags_impl(Reference::addRef(this), bucket, object); +} + +TEST_CASE("/backup/s3/v4headers") { + std::string credStr = "AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY:"; + Optional creds = StringRef(credStr); + // GET without query parameters + { + S3BlobStoreEndpoint s3("s3.amazonaws.com", "443", "amazonaws", "proxy", "port", creds); + std::string verb("GET"); + std::string resource("/test.txt"); + HTTP::Headers headers; + headers["Host"] = "s3.amazonaws.com"; + s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); + ASSERT(headers["Authorization"] == + "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/amazonaws/s3/aws4_request, " + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + "Signature=c6037f4b174f2019d02d7085a611cef8adfe1efe583e220954dc85d59cd31ba3"); + ASSERT(headers["x-amz-date"] == "20130524T000000Z"); + } + + // GET with query parameters + { + S3BlobStoreEndpoint s3("s3.amazonaws.com", "443", "amazonaws", "proxy", "port", creds); + std::string verb("GET"); + std::string resource("/test/examplebucket?Action=DescribeRegions&Version=2013-10-15"); + HTTP::Headers headers; + headers["Host"] = "s3.amazonaws.com"; + s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); + ASSERT(headers["Authorization"] == + "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/amazonaws/s3/aws4_request, " + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + "Signature=426f04e71e191fbc30096c306fe1b11ce8f026a7be374541862bbee320cce71c"); + ASSERT(headers["x-amz-date"] == "20130524T000000Z"); + } + + // POST + { + S3BlobStoreEndpoint s3("s3.us-west-2.amazonaws.com", "443", "us-west-2", "proxy", "port", creds); + std::string verb("POST"); + std::string resource("/simple.json"); + HTTP::Headers headers; + headers["Host"] = "s3.us-west-2.amazonaws.com"; + headers["Content-Type"] = "Application/x-amz-json-1.0"; + s3.setV4AuthHeaders(verb, resource, headers, "20130524T000000Z", "20130524"); + ASSERT(headers["Authorization"] == + "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-west-2/s3/aws4_request, " + "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " + "Signature=cf095e36bed9cd3139c2e8b3e20c296a79d8540987711bf3a0d816b19ae00314"); + ASSERT(headers["x-amz-date"] == "20130524T000000Z"); + ASSERT(headers["Host"] == "s3.us-west-2.amazonaws.com"); + ASSERT(headers["Content-Type"] == "Application/x-amz-json-1.0"); + } + + return Void(); +} + +TEST_CASE("/backup/s3/guess_region") { + std::string url = "blobstore://s3.us-west-2.amazonaws.com/resource_name?bucket=bucket_name&sa=1"; + + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT parameters; + Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + ASSERT(s3->getRegion() == "us-west-2"); + + url = "blobstore://s3.us-west-2.amazonaws.com/resource_name?bucket=bucket_name&sc=922337203685477580700"; + try { + s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + ASSERT(false); // not reached + } catch (Error& e) { + // conversion of 922337203685477580700 to long int will overflow + ASSERT_EQ(e.code(), error_code_backup_invalid_url); + } + return Void(); +} + +TEST_CASE("/backup/s3/virtual_hosting_list_resource_path") { + // Test that listObjectsStream_impl constructs valid HTTP resource paths + // for virtual hosting mode URLs with path prefixes. + // + // Regression test for commit 15dd76a7f9 which accidentally removed the leading "/" + // from the query string when switching from ListObjectsV1 to ListObjectsV2. + // + // For URL: blobstore://@bucket.s3.region.amazonaws.com/prefix/path?bucket=... + // constructResourcePath(bucket, "") returns "" in virtual hosting mode. + // We must ensure the final resource starts with "/" before the query string. + + std::string url = "blobstore://@test-bucket.s3.us-west-2.amazonaws.com/bulkload/path?bucket=test-bucket"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT parameters; + Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + + ASSERT(s3.isValid()); + ASSERT(resource == "bulkload/path"); // Path prefix extracted from URL (without leading '/') + + // In virtual hosting mode, constructResourcePath returns "" for empty object + std::string listResource = s3->constructResourcePath("test-bucket", ""); + ASSERT(listResource.empty()); // Virtual hosting mode doesn't include bucket in path + + // listObjectsStream_impl should add "/" before query string to form valid HTTP request + // Expected: GET /bulkload/path?list-type=2&... (but we can't test the full actor here) + // We can only verify that constructResourcePath returns "" as expected. + // The fix in listObjectsStream_impl checks if resource.empty() and sets it to "/" before + // appending the query string, ensuring the final request is well-formed. + + // Test non-virtual hosting mode for comparison + url = "blobstore://s3.us-west-2.amazonaws.com/resource_path?bucket=test-bucket"; + s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + ASSERT(s3.isValid()); + ASSERT(resource == "resource_path"); // Path extracted from URL (without leading '/') + + // In non-virtual hosting mode, constructResourcePath includes bucket + listResource = s3->constructResourcePath("test-bucket", ""); + ASSERT(listResource == "/test-bucket"); // Bucket is part of path + + return Void(); +} + +TEST_CASE("/backup/s3/constructResourcePath") { + // Test that leading slashes in object keys are properly stripped + // S3 object keys should not start with '/', but if passed, we normalize them + + std::string url = "blobstore://s3.us-west-2.amazonaws.com?bucket=test-bucket"; + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT parameters; + Reference s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + + // Test normal object key (no leading slash) + ASSERT(s3->constructResourcePath("test-bucket", "normal/path/file.txt") == "/test-bucket/normal/path/file.txt"); + + // Test single leading slash - should be stripped + ASSERT(s3->constructResourcePath("test-bucket", "/leading/slash/file.txt") == + "/test-bucket/leading/slash/file.txt"); + + // Test multiple leading slashes - all should be stripped + ASSERT(s3->constructResourcePath("test-bucket", "///multiple/slashes.txt") == "/test-bucket/multiple/slashes.txt"); + + // Test object key that is only slashes - should result in empty object path + ASSERT(s3->constructResourcePath("test-bucket", "///") == "/test-bucket"); + + // Test empty object key + ASSERT(s3->constructResourcePath("test-bucket", "") == "/test-bucket"); + + // Test virtual hosting mode (bucket in hostname) + url = "blobstore://test-bucket.s3.us-west-2.amazonaws.com"; + s3 = S3BlobStoreEndpoint::fromString(url, {}, &resource, &error, ¶meters); + + // Virtual hosting mode doesn't include bucket in path + ASSERT(s3->constructResourcePath("test-bucket", "normal/file.txt") == "/normal/file.txt"); + ASSERT(s3->constructResourcePath("test-bucket", "/leading/slash.txt") == "/leading/slash.txt"); + ASSERT(s3->constructResourcePath("test-bucket", "").empty()); + + return Void(); +} + +TEST_CASE("/backup/s3/parseErrorCodeFromS3") { + // Valid S3 error XML — should extract the error code + { + std::string xml = "" + "AccessDenied" + "Access Denied"; + ASSERT(parseErrorCodeFromS3(xml) == "AccessDenied"); + } + + // InvalidToken error + { + std::string xml = "InvalidToken" + "The provided token is malformed or otherwise invalid."; + ASSERT(parseErrorCodeFromS3(xml) == "InvalidToken"); + } + + // ExpiredToken error + { + std::string xml = "ExpiredTokenToken expired"; + ASSERT(parseErrorCodeFromS3(xml) == "ExpiredToken"); + } + + // Missing node — should return "" + { + std::string xml = "Something went wrong"; + ASSERT(parseErrorCodeFromS3(xml).empty()); + } + + // No root — should return "" + { + std::string xml = "bucket"; + ASSERT(parseErrorCodeFromS3(xml).empty()); + } + + // Empty response — should return "" + { + ASSERT(parseErrorCodeFromS3("").empty()); + } + + // HTML error page from load balancer (502/503) — should return "", not throw + { + std::string html = "

502 Bad Gateway

"; + ASSERT(parseErrorCodeFromS3(html).empty()); + } + + // Completely invalid / garbage — should return "", not throw + { + ASSERT(parseErrorCodeFromS3("not xml at all {{{").empty()); + } + + // Partial XML — should return "", not throw + { + ASSERT(parseErrorCodeFromS3("Incomple").empty()); + } + + // Plain text error — should return "", not throw + { + ASSERT(parseErrorCodeFromS3("Internal Server Error").empty()); + } + + return Void(); +} diff --git a/fdbclient/S3Client.actor.cpp b/fdbclient/S3Client.cpp similarity index 62% rename from fdbclient/S3Client.actor.cpp rename to fdbclient/S3Client.cpp index 7854d46594c..629051580f8 100644 --- a/fdbclient/S3Client.actor.cpp +++ b/fdbclient/S3Client.cpp @@ -1,5 +1,5 @@ /* - * S3Client.actor.cpp + * S3Client.cpp * * This source file is part of the FoundationDB open source project * @@ -20,19 +20,15 @@ #include #include -#include #include -#include -#include #ifdef _WIN32 #include #endif -#include "fdbclient/S3Client.actor.h" +#include "fdbclient/S3Client.h" #include "flow/IAsyncFile.h" #include "flow/Trace.h" -#include "flow/Traceable.h" #include "flow/flow.h" #include "flow/xxhash.h" #include "flow/Error.h" @@ -40,13 +36,11 @@ #include #include "libb64/encode.h" -#include "flow/actorcompiler.h" // has to be last include - // Configuration constants #define S3_CHECKSUM_TAG_NAME "xxhash64" #define S3_CHECKSUM_FILE_SUFFIX ".checksum" -typedef XXH64_state_t XXHashState; +using XXHashState = XXH64_state_t; using ::format; // Use FoundationDB's format, not std::format @@ -87,23 +81,23 @@ struct PartConfig { // Uses xxhash library because it's fast (supposedly) and used elsewhere in fdb. // If size is -1, the function will determine the file size automatically. // Returns a hex string representation of the xxhash64 checksum. -ACTOR Future calculateFileChecksum(Reference file, int64_t size) { - state int64_t pos = 0; - state XXH64_state_t* hashState = XXH64_createState(); - state std::shared_ptr> buffer = std::make_shared>(65536); - state int readSize; +AsyncResult calculateFileChecksum(Reference file, int64_t size) { + int64_t pos = 0; + XXH64_state_t* hashState = XXH64_createState(); + std::shared_ptr> buffer = std::make_shared>(65536); + int readSize{ 0 }; XXH64_reset(hashState, 0); try { if (size == -1) { - int64_t s = wait(file->size()); + int64_t s = co_await file->size(); size = s; } while (pos < size) { readSize = std::min(buffer->size(), size - pos); - int bytesRead = wait(uncancellable(holdWhile(buffer, file->read(buffer->data(), readSize, pos)))); + int bytesRead = co_await uncancellable(holdWhile(buffer, file->read(buffer->data(), readSize, pos))); if (bytesRead != readSize) { XXH64_freeState(hashState); TraceEvent(SevError, "S3ClientCalculateChecksumReadError") @@ -118,7 +112,7 @@ ACTOR Future calculateFileChecksum(Reference file, int6 uint64_t hash = XXH64_digest(hashState); XXH64_freeState(hashState); - return format("%016llx", hash); + co_return format("%016llx", hash); } catch (Error& e) { XXH64_freeState(hashState); throw; @@ -185,29 +179,29 @@ bool isRetryableError(int errorCode) { } // Write checksum with configurable fallback strategy -ACTOR static Future writeChecksumWithFallback(Reference endpoint, - std::string bucket, - std::string objectName, - std::string checksum, - PartConfig config) { +static Future writeChecksumWithFallback(Reference endpoint, + std::string bucket, + std::string objectName, + std::string checksum, + PartConfig config) { if (!config.enableChecksumValidation) { TraceEvent(SevWarn, "S3ClientChecksumValidationDisabled") .suppressFor(60) .detail("Bucket", bucket) .detail("Object", objectName); - return Void(); // Skip checksum storage if disabled + co_return; // Skip checksum storage if disabled } // Always try tags first try { - state std::map tags; + std::map tags; tags[S3_CHECKSUM_TAG_NAME] = checksum; - wait(endpoint->putObjectTags(bucket, objectName, tags)); + co_await endpoint->putObjectTags(bucket, objectName, tags); TraceEvent(SevDebug, "S3ClientChecksumStoredAsTags") .detail("Bucket", bucket) .detail("Object", objectName) .detail("Checksum", checksum); - return Void(); + co_return; } catch (Error& e) { if (e.code() != error_code_http_bad_response && e.code() != error_code_file_not_found) { throw; @@ -219,37 +213,36 @@ ACTOR static Future writeChecksumWithFallback(ReferencewriteEntireFile(bucket, objectName + S3_CHECKSUM_FILE_SUFFIX, checksum)); + co_await endpoint->writeEntireFile(bucket, objectName + S3_CHECKSUM_FILE_SUFFIX, checksum); TraceEvent(SevDebug, "S3ClientChecksumStoredAsFile") .detail("Bucket", bucket) .detail("Object", objectName) .detail("ChecksumFile", objectName + S3_CHECKSUM_FILE_SUFFIX) .detail("Checksum", checksum); - return Void(); } // Read checksum with configurable fallback strategy -ACTOR static Future> readChecksumWithFallback(Reference endpoint, - std::string bucket, - std::string objectName, - PartConfig config) { +static AsyncResult> readChecksumWithFallback(Reference endpoint, + std::string bucket, + std::string objectName, + PartConfig config) { if (!config.enableChecksumValidation) { TraceEvent(SevDebug, "S3ClientChecksumValidationDisabled") .detail("Bucket", bucket) .detail("Object", objectName); - return Optional(); // Skip checksum validation if disabled + co_return Optional(); // Skip checksum validation if disabled } // Always try tags first try { - state std::map tags = wait(endpoint->getObjectTags(bucket, objectName)); + std::map tags = co_await endpoint->getObjectTags(bucket, objectName); auto it = tags.find(S3_CHECKSUM_TAG_NAME); if (it != tags.end() && !it->second.empty()) { TraceEvent(SevDebug, "S3ClientChecksumFoundInTags") .detail("Bucket", bucket) .detail("Object", objectName) .detail("Checksum", it->second); - return Optional(it->second); + co_return Optional(it->second); } } catch (Error& e) { if (e.code() != error_code_http_bad_response && e.code() != error_code_file_not_found) { @@ -263,13 +256,13 @@ ACTOR static Future> readChecksumWithFallback(ReferencereadEntireFile(bucket, objectName + S3_CHECKSUM_FILE_SUFFIX)); + std::string checksum = co_await endpoint->readEntireFile(bucket, objectName + S3_CHECKSUM_FILE_SUFFIX); TraceEvent(SevDebug, "S3ClientChecksumFoundInFile") .detail("Bucket", bucket) .detail("Object", objectName) .detail("ChecksumFile", objectName + S3_CHECKSUM_FILE_SUFFIX) .detail("Checksum", checksum); - return Optional(checksum); + co_return Optional(checksum); } catch (Error& e) { if (e.code() != error_code_file_not_found) { throw; @@ -280,24 +273,24 @@ ACTOR static Future> readChecksumWithFallback(Reference(); + co_return Optional(); } // Upload a part of a multipart upload with configurable retry logic. -ACTOR static Future uploadPart(Reference endpoint, - std::string bucket, - std::string objectName, - std::string uploadID, - Reference file, - PartState part, - PartConfig config) { - state double startTime = now(); - state PartState resultPart = part; - state int attempt = 0; - state int maxRetries = config.maxPartRetries; - state int delayMs = config.baseRetryDelayMs; - state UnsentPacketQueue packets; +static Future uploadPart(Reference endpoint, + std::string bucket, + std::string objectName, + std::string uploadID, + Reference file, + PartState part, + PartConfig config) { + double startTime = now(); + PartState resultPart = part; + int attempt = 0; + int maxRetries = config.maxPartRetries; + int delayMs = config.baseRetryDelayMs; + UnsentPacketQueue packets; TraceEvent(SevDebug, "S3ClientUploadPartStart") .detail("Bucket", StringRef(bucket)) @@ -307,13 +300,14 @@ ACTOR static Future uploadPart(Reference endpoin .detail("Size", resultPart.size) .detail("MaxRetries", maxRetries); - loop { + while (true) { + Error err; try { // Read part data from file (automatic memory management) - state std::shared_ptr partData = std::make_shared(resultPart.size, '\0'); + std::shared_ptr partData = std::make_shared(resultPart.size, '\0'); - int bytesRead = wait( - uncancellable(holdWhile(partData, file->read(&(*partData)[0], resultPart.size, resultPart.offset)))); + int bytesRead = co_await uncancellable( + holdWhile(partData, file->read(&(*partData)[0], resultPart.size, resultPart.offset))); if (bytesRead != resultPart.size) { TraceEvent(SevError, "S3ClientUploadPartReadError") .detail("Expected", resultPart.size) @@ -354,13 +348,13 @@ ACTOR static Future uploadPart(Reference endpoin PacketWriter pw(packets.getWriteBuffer(resultPart.partData.size()), nullptr, Unversioned()); pw.serializeBytes(resultPart.partData); - std::string etag = wait(endpoint->uploadPart(bucket, - objectName, - uploadID, - resultPart.partNumber, - &packets, - resultPart.partData.size(), - resultPart.checksum)); + std::string etag = co_await endpoint->uploadPart(bucket, + objectName, + uploadID, + resultPart.partNumber, + &packets, + resultPart.partData.size(), + resultPart.checksum); resultPart.etag = etag; resultPart.completed = true; @@ -372,60 +366,60 @@ ACTOR static Future uploadPart(Reference endpoin .detail("Duration", now() - startTime) .detail("Size", resultPart.size) .detail("Attempts", attempt + 1); - return resultPart; + co_return resultPart; } catch (Error& e) { - attempt++; - if (attempt >= maxRetries || !isRetryableError(e.code())) { - TraceEvent(SevWarnAlways, "S3ClientUploadPartFailed") - .detail("Bucket", StringRef(bucket)) - .detail("Object", StringRef(objectName)) - .detail("PartNumber", part.partNumber) - .detail("ErrorCode", e.code()) - .detail("Attempts", attempt) - .detail("MaxRetries", maxRetries) - .detail("FinalError", e.what()); - throw; - } + err = e; + } - TraceEvent(SevDebug, "S3ClientUploadPartRetry") + attempt++; + if (attempt >= maxRetries || !isRetryableError(err.code())) { + TraceEvent(SevWarnAlways, "S3ClientUploadPartFailed") .detail("Bucket", StringRef(bucket)) .detail("Object", StringRef(objectName)) .detail("PartNumber", part.partNumber) - .detail("Attempt", attempt) - .detail("Error", e.what()) - .detail("DelayMs", delayMs); - - wait(delay(delayMs / 1000.0)); - delayMs = std::min(delayMs * 2, config.maxRetryDelayMs); + .detail("ErrorCode", err.code()) + .detail("Attempts", attempt) + .detail("MaxRetries", maxRetries) + .detail("FinalError", err.what()); + throw err; } + + TraceEvent(SevDebug, "S3ClientUploadPartRetry") + .detail("Bucket", StringRef(bucket)) + .detail("Object", StringRef(objectName)) + .detail("PartNumber", part.partNumber) + .detail("Attempt", attempt) + .detail("Error", err.what()) + .detail("DelayMs", delayMs); + + co_await delay(delayMs / 1000.0); + delayMs = std::min(delayMs * 2, config.maxRetryDelayMs); } } // Copy filepath to bucket at resource in s3. -ACTOR static Future copyUpFile(Reference endpoint, - std::string bucket, - std::string objectName, - std::string filepath, - PartConfig config = PartConfig()) { - state double startTime = now(); - state Reference file; - state std::string uploadID; - state std::vector parts; - state int64_t size; - state XXH64_state_t* hashState = XXH64_createState(); - state int retries = 0; - state int64_t offset; - state int partNumber; - state int maxConcurrentUploads; - state std::vector> activeFutures; - state std::vector activePartIndices; - state int64_t partSize; - state int numParts; - state std::string checksum; - state PartState part; - state std::map tags; - - loop { +static Future copyUpFile(Reference endpoint, + std::string bucket, + std::string objectName, + std::string filepath, + PartConfig config = PartConfig()) { + double startTime = now(); + Reference file; + std::string uploadID; + std::vector parts; + int64_t size{ 0 }; + XXH64_state_t* hashState = XXH64_createState(); + int retries = 0; + int64_t offset{ 0 }; + int partNumber{ 0 }; + int maxConcurrentUploads{ 0 }; + std::vector> activeFutures; + std::vector activePartIndices; + int numParts{ 0 }; + std::string checksum; + + while (true) { + Error err; try { TraceEvent(s3VerboseEventSev(), "S3ClientCopyUpFileStart") .detail("Bucket", bucket) @@ -439,14 +433,14 @@ ACTOR static Future copyUpFile(Reference endpoint, } XXH64_reset(hashState, 0); - Reference f = wait(IAsyncFileSystem::filesystem()->open( - filepath, IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_NO_AIO, 0644)); + Reference f = co_await IAsyncFileSystem::filesystem()->open( + filepath, IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_NO_AIO, 0644); file = f; - int64_t fileSize = wait(file->size()); + int64_t fileSize = co_await file->size(); size = fileSize; // Start multipart upload - std::string id = wait(endpoint->beginMultiPartUpload(bucket, objectName)); + std::string id = co_await endpoint->beginMultiPartUpload(bucket, objectName); uploadID = id; offset = 0; @@ -459,24 +453,20 @@ ACTOR static Future copyUpFile(Reference endpoint, while (offset < size) { // Fill up to maxConcurrentUploads active uploads while (activeFutures.size() < maxConcurrentUploads && offset < size) { - partSize = std::min(config.partSizeBytes, size - offset); - - part = PartState(); - part.partNumber = partNumber; - part.offset = offset; - part.size = partSize; + const int64_t currentPartSize = std::min(config.partSizeBytes, size - offset); + PartState part(partNumber, offset, currentPartSize); parts.push_back(part); activeFutures.push_back(uploadPart(endpoint, bucket, objectName, uploadID, file, part, config)); activePartIndices.push_back(partNumber - 1); // Store index into parts array - offset += partSize; + offset += currentPartSize; partNumber++; } // Wait for all active uploads to complete if (!activeFutures.empty()) { - std::vector completedParts = wait(getAll(activeFutures)); + std::vector completedParts = co_await getAll(activeFutures); // Update parts with completion status for (int i = 0; i < completedParts.size(); i++) { parts[activePartIndices[i]] = completedParts[i]; @@ -502,7 +492,7 @@ ACTOR static Future copyUpFile(Reference endpoint, } Optional s3Checksum = - wait(endpoint->finishMultiPartUpload(bucket, objectName, uploadID, etagMap)); + co_await endpoint->finishMultiPartUpload(bucket, objectName, uploadID, etagMap); // Log the S3 checksum if present if (s3Checksum.present()) { @@ -538,7 +528,7 @@ ACTOR static Future copyUpFile(Reference endpoint, file = Reference(); // Store the checksum using configurable fallback strategy - wait(writeChecksumWithFallback(endpoint, bucket, objectName, checksum, config)); + co_await writeChecksumWithFallback(endpoint, bucket, objectName, checksum, config); TraceEvent(s3VerboseEventSev(), "S3ClientCopyUpFileEnd") .detail("Bucket", bucket) @@ -551,100 +541,99 @@ ACTOR static Future copyUpFile(Reference endpoint, break; // Success - exit retry loop } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw; - } - // File-level retry for specific errors, matching download behavior - if ((e.code() == error_code_file_not_found || e.code() == error_code_http_request_failed || - e.code() == error_code_io_error) && - retries < config.maxFileRetries) { // Use configurable retry limit - state Error retryError = e; - TraceEvent(SevWarn, "S3ClientCopyUpFileRetry") - .errorUnsuppressed(retryError) - .detail("Bucket", bucket) - .detail("Object", objectName) - .detail("FilePath", filepath) - .detail("Retries", retries); - retries++; + err = e; + } - // Cleanup before retry - XXH64_freeState(hashState); - hashState = nullptr; - parts.clear(); - activeFutures.clear(); - activePartIndices.clear(); + if (err.code() == error_code_actor_cancelled) { + throw err; + } + // File-level retry for specific errors, matching download behavior + if ((err.code() == error_code_file_not_found || err.code() == error_code_http_request_failed || + err.code() == error_code_io_error) && + retries < config.maxFileRetries) { // Use configurable retry limit + Error retryError = err; + TraceEvent(SevWarn, "S3ClientCopyUpFileRetry") + .errorUnsuppressed(retryError) + .detail("Bucket", bucket) + .detail("Object", objectName) + .detail("FilePath", filepath) + .detail("Retries", retries); + retries++; - if (file) { - file = Reference(); - } + // Cleanup before retry + XXH64_freeState(hashState); + hashState = nullptr; + parts.clear(); + activeFutures.clear(); + activePartIndices.clear(); - // Attempt to abort the upload but only if we have a valid uploadID - if (!uploadID.empty()) { - try { - wait(endpoint->abortMultiPartUpload(bucket, objectName, uploadID)); - } catch (Error& abortError) { - TraceEvent(SevWarn, "S3ClientCopyUpFileAbortError") - .error(abortError) - .detail("Bucket", bucket) - .detail("Object", objectName) - .detail("UploadID", uploadID) - .detail("OriginalError", retryError.what()); - } - uploadID = ""; - } + if (file) { + file = Reference(); + } - if (g_network->isSimulated()) { - wait(delay(0)); - continue; + // Attempt to abort the upload but only if we have a valid uploadID + if (!uploadID.empty()) { + try { + co_await endpoint->abortMultiPartUpload(bucket, objectName, uploadID); + } catch (Error& abortError) { + TraceEvent(SevWarn, "S3ClientCopyUpFileAbortError") + .error(abortError) + .detail("Bucket", bucket) + .detail("Object", objectName) + .detail("UploadID", uploadID) + .detail("OriginalError", retryError.what()); } - wait(delay(1.0 * retries)); // Linear backoff like download - } else { - state Error err = e; - XXH64_freeState(hashState); - hashState = nullptr; - TraceEvent(SevWarnAlways, "S3ClientCopyUpFileError") - .detail("Filepath", filepath) - .detail("Bucket", bucket) - .detail("ObjectName", objectName) - .detail("Error", err.what()) - .detail("Attempts", retries + 1); + uploadID = ""; + } - // Close file before abort attempt - file = Reference(); + if (g_network->isSimulated()) { + co_await delay(0); + continue; + } + co_await delay(1.0 * retries); // Linear backoff like download + } else { + XXH64_freeState(hashState); + hashState = nullptr; + TraceEvent(SevWarnAlways, "S3ClientCopyUpFileError") + .detail("Filepath", filepath) + .detail("Bucket", bucket) + .detail("ObjectName", objectName) + .detail("Error", err.what()) + .detail("Attempts", retries + 1); - // Attempt to abort the upload but do not wait for it - if (!uploadID.empty()) { - try { - wait(endpoint->abortMultiPartUpload(bucket, objectName, uploadID)); - } catch (Error& abortError) { - // Log abort failure but throw original error - TraceEvent(SevWarnAlways, "S3ClientCopyUpFileAbortError") - .error(abortError) - .detail("Bucket", bucket) - .detail("Object", objectName) - .detail("OriginalError", err.what()); - } + // Close file before abort attempt + file = Reference(); + + // Attempt to abort the upload but do not wait for it + if (!uploadID.empty()) { + try { + co_await endpoint->abortMultiPartUpload(bucket, objectName, uploadID); + } catch (Error& abortError) { + // Log abort failure but throw original error + TraceEvent(SevWarnAlways, "S3ClientCopyUpFileAbortError") + .error(abortError) + .detail("Bucket", bucket) + .detail("Object", objectName) + .detail("OriginalError", err.what()); } } } } - return Void(); } -ACTOR Future copyUpFile(std::string filepath, std::string s3url) { +Future copyUpFile(std::string filepath, std::string s3url) { std::string resource; S3BlobStoreEndpoint::ParametersT parameters; Reference endpoint = getEndpoint(s3url, resource, parameters); - wait(copyUpFile(endpoint, parameters["bucket"], resource, filepath)); - return Void(); + co_await copyUpFile(endpoint, parameters["bucket"], resource, filepath); } -ACTOR Future copyUpDirectory(std::string dirpath, std::string s3url) { - state std::string resource; +Future copyUpDirectory(std::string dirpath, std::string s3url) { + std::string resource; S3BlobStoreEndpoint::ParametersT parameters; - state Reference endpoint = getEndpoint(s3url, resource, parameters); - state std::string bucket = parameters["bucket"]; - state std::vector files; + Reference endpoint = getEndpoint(s3url, resource, parameters); + std::string bucket = parameters["bucket"]; + std::vector files; platform::findFilesRecursively(dirpath, files); TraceEvent(s3VerboseEventSev(), "S3ClientUploadDirStart") .detail("Filecount", files.size()) @@ -653,62 +642,60 @@ ACTOR Future copyUpDirectory(std::string dirpath, std::string s3url) { for (const auto& file : files) { std::string filepath = file; std::string s3path = resource + "/" + file.substr(dirpath.size() + 1); - wait(copyUpFile(endpoint, bucket, s3path, filepath)); + co_await copyUpFile(endpoint, bucket, s3path, filepath); } TraceEvent(s3VerboseEventSev(), "S3ClientUploadDirEnd").detail("Bucket", bucket).detail("Resource", resource); - return Void(); } -ACTOR Future copyUpBulkDumpFileSet(std::string s3url, - BulkLoadFileSet sourceFileSet, - BulkLoadFileSet destinationFileSet) { - state std::string resource; +Future copyUpBulkDumpFileSet(std::string s3url, + BulkLoadFileSet sourceFileSet, + BulkLoadFileSet destinationFileSet) { + std::string resource; S3BlobStoreEndpoint::ParametersT parameters; - state Reference endpoint = getEndpoint(s3url, resource, parameters); - state std::string bucket = parameters["bucket"]; + Reference endpoint = getEndpoint(s3url, resource, parameters); + std::string bucket = parameters["bucket"]; TraceEvent(s3VerboseEventSev(), "S3ClientCopyUpBulkDumpFileSetStart") .detail("Bucket", bucket) .detail("SourceFileSet", sourceFileSet.toString()) .detail("DestinationFileSet", destinationFileSet.toString()); - state int pNumDeleted = 0; - state int64_t pBytesDeleted = 0; - state std::string batch_dir = joinPath(getPath(s3url), destinationFileSet.getRelativePath()); + int pNumDeleted = 0; + int64_t pBytesDeleted = 0; + std::string batch_dir = joinPath(getPath(s3url), destinationFileSet.getRelativePath()); // Delete the batch dir if it exists already (need to check bucket exists else 404 and s3blobstore errors out). - bool exists = wait(endpoint->bucketExists(bucket)); + bool exists = co_await endpoint->bucketExists(bucket); if (exists) { - wait(endpoint->deleteRecursively(bucket, batch_dir, &pNumDeleted, &pBytesDeleted)); + co_await endpoint->deleteRecursively(bucket, batch_dir, &pNumDeleted, &pBytesDeleted); } // Destination for manifest file. auto destinationManifestPath = joinPath(batch_dir, destinationFileSet.getManifestFileName()); - wait(copyUpFile(endpoint, bucket, destinationManifestPath, sourceFileSet.getManifestFileFullPath())); + co_await copyUpFile(endpoint, bucket, destinationManifestPath, sourceFileSet.getManifestFileFullPath()); if (sourceFileSet.hasDataFile()) { auto destinationDataPath = joinPath(batch_dir, destinationFileSet.getDataFileName()); - wait(copyUpFile(endpoint, bucket, destinationDataPath, sourceFileSet.getDataFileFullPath())); + co_await copyUpFile(endpoint, bucket, destinationDataPath, sourceFileSet.getDataFileFullPath()); } if (sourceFileSet.hasByteSampleFile()) { ASSERT(sourceFileSet.hasDataFile()); auto destinationByteSamplePath = joinPath(batch_dir, destinationFileSet.getByteSampleFileName()); - wait(copyUpFile(endpoint, bucket, destinationByteSamplePath, sourceFileSet.getBytesSampleFileFullPath())); + co_await copyUpFile(endpoint, bucket, destinationByteSamplePath, sourceFileSet.getBytesSampleFileFullPath()); } TraceEvent(s3VerboseEventSev(), "S3ClientCopyUpBulkDumpFileSetEnd") .detail("BatchDir", batch_dir) .detail("NumDeleted", pNumDeleted) .detail("BytesDeleted", pBytesDeleted); - return Void(); } -ACTOR static Future downloadPart(Reference endpoint, - std::string bucket, - std::string objectName, - Reference file, - PartState part, - PartConfig config) { - state PartState resultPart = part; - state int attempt = 0; - state int maxRetries = config.maxPartRetries; - state int delayMs = config.baseRetryDelayMs; +static Future downloadPart(Reference endpoint, + std::string bucket, + std::string objectName, + Reference file, + PartState part, + PartConfig config) { + PartState resultPart = part; + int attempt = 0; + int maxRetries = config.maxPartRetries; + int delayMs = config.baseRetryDelayMs; TraceEvent(SevDebug, "S3ClientDownloadPartStart") .detail("Bucket", bucket) @@ -717,11 +704,11 @@ ACTOR static Future downloadPart(Reference endpo .detail("Offset", resultPart.offset) .detail("Size", resultPart.size); - loop { + while (true) { + Error err; try { - state std::vector buffer; - state int64_t totalBytesRead = 0; - buffer.resize(resultPart.size); + std::shared_ptr> buffer = std::make_shared>(resultPart.size); + int64_t totalBytesRead = 0; // Add range validation if (resultPart.offset < 0 || resultPart.size <= 0) { @@ -732,11 +719,14 @@ ACTOR static Future downloadPart(Reference endpo } while (totalBytesRead < resultPart.size) { - int bytesRead = wait(endpoint->readObject(bucket, - objectName, - buffer.data() + totalBytesRead, - resultPart.size - totalBytesRead, - resultPart.offset + totalBytesRead)); + // Keep the shared buffer alive until the async read has fully settled, even if this actor is cancelled. + int bytesRead = + co_await uncancellable(holdWhile(buffer, + endpoint->readObject(bucket, + objectName, + buffer->data() + totalBytesRead, + resultPart.size - totalBytesRead, + resultPart.offset + totalBytesRead))); if (bytesRead == 0) { // Avoid infinite loop if server closes connection prematurely TraceEvent(SevError, "S3ClientDownloadPartUnexpectedEOF") @@ -758,7 +748,7 @@ ACTOR static Future downloadPart(Reference endpo // Verify checksum if provided (currently only MD5 is used for download verification) if (!resultPart.checksum.empty()) { - std::string calculatedMD5 = HTTP::computeMD5Sum(std::string((char*)buffer.data(), totalBytesRead)); + std::string calculatedMD5 = HTTP::computeMD5Sum(std::string((char*)buffer->data(), totalBytesRead)); if (resultPart.checksum != calculatedMD5) { TraceEvent(SevWarnAlways, "S3ClientDownloadPartChecksumMismatch") .detail("Expected", resultPart.checksum) @@ -767,7 +757,8 @@ ACTOR static Future downloadPart(Reference endpo } } - wait(file->write(buffer.data(), totalBytesRead, resultPart.offset)); + // Keep the shared buffer alive until the async write has fully settled, even if this actor is cancelled. + co_await uncancellable(holdWhile(buffer, file->write(buffer->data(), totalBytesRead, resultPart.offset))); resultPart.completed = true; TraceEvent(SevDebug, "S3ClientDownloadPartEnd") @@ -777,61 +768,64 @@ ACTOR static Future downloadPart(Reference endpo .detail("Offset", resultPart.offset) .detail("Size", resultPart.size) .detail("Attempts", attempt + 1); - return resultPart; + co_return resultPart; } catch (Error& e) { - attempt++; - if (attempt >= maxRetries || !isRetryableError(e.code())) { - TraceEvent(SevWarnAlways, "S3ClientDownloadPartFailed") - .detail("Bucket", bucket) - .detail("Object", objectName) - .detail("PartNumber", part.partNumber) - .detail("ErrorCode", e.code()) - .detail("Attempts", attempt) - .detail("FinalError", e.what()); - throw; - } + err = e; + } - TraceEvent(SevInfo, "S3ClientDownloadPartRetry") + attempt++; + if (attempt >= maxRetries || !isRetryableError(err.code())) { + TraceEvent(SevWarnAlways, "S3ClientDownloadPartFailed") .detail("Bucket", bucket) .detail("Object", objectName) .detail("PartNumber", part.partNumber) - .detail("Attempt", attempt) - .detail("Error", e.what()) - .detail("DelayMs", delayMs); - - wait(delay(delayMs / 1000.0)); - delayMs = std::min(delayMs * 2, config.maxRetryDelayMs); // Use configurable cap + .detail("ErrorCode", err.code()) + .detail("Attempts", attempt) + .detail("FinalError", err.what()); + throw err; } + + TraceEvent(SevInfo, "S3ClientDownloadPartRetry") + .detail("Bucket", bucket) + .detail("Object", objectName) + .detail("PartNumber", part.partNumber) + .detail("Attempt", attempt) + .detail("Error", err.what()) + .detail("DelayMs", delayMs); + + co_await delay(delayMs / 1000.0); + delayMs = std::min(delayMs * 2, config.maxRetryDelayMs); // Use configurable cap } } -ACTOR static Future> getExpectedChecksum(Reference endpoint, - std::string bucket, - std::string objectName) { +static AsyncResult> getExpectedChecksum(Reference endpoint, + std::string bucket, + std::string objectName) { PartConfig config; // Use default configuration - Optional result = wait(readChecksumWithFallback(endpoint, bucket, objectName, config)); - return result; + Optional result = co_await readChecksumWithFallback(endpoint, bucket, objectName, config); + co_return result; } // Copy down file from s3 to filepath. -ACTOR static Future copyDownFile(Reference endpoint, - std::string bucket, - std::string objectName, - std::string filepath, - PartConfig config = PartConfig()) { - state Reference file; - state std::vector parts; - state int64_t fileSize = 0; - state int64_t offset = 0; - state int partNumber = 1; - state int64_t partSize; - state std::string expectedChecksum; - state int retries = 0; - state int maxConcurrentDownloads; - state std::vector> activeDownloadFutures; - state std::vector activePartIndices; - - loop { +static Future copyDownFile(Reference endpoint, + std::string bucket, + std::string objectName, + std::string filepath, + PartConfig config = PartConfig()) { + Reference file; + std::vector parts; + int64_t fileSize = 0; + int64_t offset = 0; + int partNumber = 1; + int64_t partSize{ 0 }; + std::string expectedChecksum; + int retries = 0; + int maxConcurrentDownloads{ 0 }; + std::vector> activeDownloadFutures; + std::vector activePartIndices; + + while (true) { + Error err; try { TraceEvent(s3VerboseEventSev(), "S3ClientCopyDownFileStart") .detail("Bucket", bucket) @@ -842,7 +836,7 @@ ACTOR static Future copyDownFile(Reference endpoint, TraceEvent(SevDebug, "S3ClientCopyDownFileBeforeObjectSize") .detail("Bucket", bucket) .detail("Object", objectName); - int64_t s = wait(endpoint->objectSize(bucket, objectName)); + int64_t s = co_await endpoint->objectSize(bucket, objectName); TraceEvent(SevDebug, "S3ClientCopyDownFileAfterObjectSize") .detail("Bucket", bucket) .detail("Object", objectName) @@ -862,15 +856,15 @@ ACTOR static Future copyDownFile(Reference endpoint, int numParts = (fileSize + config.partSizeBytes - 1) / config.partSizeBytes; parts.reserve(numParts); - Reference f = wait(IAsyncFileSystem::filesystem()->open( + Reference f = co_await IAsyncFileSystem::filesystem()->open( filepath, IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_NO_AIO, - 0644)); + 0644); file = f; - wait(file->truncate(fileSize)); - wait(file->truncate(fileSize)); + co_await file->truncate(fileSize); + co_await file->truncate(fileSize); offset = 0; partNumber = 1; @@ -893,7 +887,7 @@ ACTOR static Future copyDownFile(Reference endpoint, // Wait for all active downloads to complete if (!activeDownloadFutures.empty()) { - std::vector completedParts = wait(getAll(activeDownloadFutures)); + std::vector completedParts = co_await getAll(activeDownloadFutures); // Update parts with completion status for (int i = 0; i < completedParts.size(); i++) { parts[activePartIndices[i]] = completedParts[i]; @@ -912,15 +906,15 @@ ACTOR static Future copyDownFile(Reference endpoint, } } - wait(file->truncate(fileSize)); - wait(file->sync()); + co_await file->truncate(fileSize); + co_await file->sync(); // Get and verify checksum using the helper - Optional cs = wait(getExpectedChecksum(endpoint, bucket, objectName)); + Optional cs = co_await getExpectedChecksum(endpoint, bucket, objectName); if (cs.present()) { expectedChecksum = cs.get(); - state std::string actualChecksum = wait(calculateFileChecksum(file, fileSize)); + std::string actualChecksum = co_await calculateFileChecksum(file, fileSize); if (actualChecksum != expectedChecksum) { TraceEvent(SevWarnAlways, "S3ClientCopyDownFileChecksumMismatch") .detail("Expected", expectedChecksum) @@ -940,65 +934,65 @@ ACTOR static Future copyDownFile(Reference endpoint, break; // Success } catch (Error& e) { - if ((e.code() == error_code_file_not_found || e.code() == error_code_http_request_failed || - e.code() == error_code_io_error) && - retries < config.maxFileRetries) { - TraceEvent(SevWarn, "S3ClientCopyDownFileRetry") - .errorUnsuppressed(e) - .detail("Bucket", bucket) - .detail("Object", objectName) - .detail("FilePath", filepath) - .detail("Retries", retries); - retries++; - - // Cleanup state for retry - parts.clear(); - activeDownloadFutures.clear(); - activePartIndices.clear(); - - if (file) { - try { - file = Reference(); - IAsyncFileSystem::filesystem()->deleteFile(filepath, true); - } catch (Error& cleanupError) { - TraceEvent(SevWarnAlways, "S3ClientCopyDownFileCleanupError") - .detail("FilePath", filepath) - .errorUnsuppressed(cleanupError); - } - } - if (g_network->isSimulated()) { - wait(delay(0)); - continue; + err = e; + } + + if ((err.code() == error_code_file_not_found || err.code() == error_code_http_request_failed || + err.code() == error_code_io_error) && + retries < config.maxFileRetries) { + TraceEvent(SevWarn, "S3ClientCopyDownFileRetry") + .errorUnsuppressed(err) + .detail("Bucket", bucket) + .detail("Object", objectName) + .detail("FilePath", filepath) + .detail("Retries", retries); + retries++; + + // Cleanup state for retry + parts.clear(); + activeDownloadFutures.clear(); + activePartIndices.clear(); + + if (file) { + try { + file = Reference(); + IAsyncFileSystem::filesystem()->deleteFile(filepath, true); + } catch (Error& cleanupError) { + TraceEvent(SevWarnAlways, "S3ClientCopyDownFileCleanupError") + .detail("FilePath", filepath) + .errorUnsuppressed(cleanupError); } - wait(delay(1.0 * retries)); - } else { - state Error err = e; - TraceEvent(SevWarnAlways, "S3ClientCopyDownFileError") - .detail("Bucket", bucket) - .detail("ObjectName", objectName) - .errorUnsuppressed(err) - .detail("FilePath", filepath) - .detail("FileSize", fileSize); - - if (file) { - try { - wait(file->sync()); - file = Reference(); - IAsyncFileSystem::filesystem()->deleteFile(filepath, false); - } catch (Error& e2) { - TraceEvent(SevWarnAlways, "S3ClientCopyDownFileCleanupError") - .detail("FilePath", filepath) - .errorUnsuppressed(e2); - } + } + if (g_network->isSimulated()) { + co_await delay(0); + continue; + } + co_await delay(1.0 * retries); + } else { + TraceEvent(SevWarnAlways, "S3ClientCopyDownFileError") + .detail("Bucket", bucket) + .detail("ObjectName", objectName) + .errorUnsuppressed(err) + .detail("FilePath", filepath) + .detail("FileSize", fileSize); + + if (file) { + try { + co_await file->sync(); + file = Reference(); + IAsyncFileSystem::filesystem()->deleteFile(filepath, false); + } catch (Error& e2) { + TraceEvent(SevWarnAlways, "S3ClientCopyDownFileCleanupError") + .detail("FilePath", filepath) + .errorUnsuppressed(e2); } - throw err; } + throw err; } } - return Void(); } -ACTOR Future copyDownFile(std::string s3url, std::string filepath) { +Future copyDownFile(std::string s3url, std::string filepath) { TraceEvent(SevDebug, "S3ClientCopyDownFileWrapperStart").detail("S3URL", s3url).detail("FilePath", filepath); std::string resource; S3BlobStoreEndpoint::ParametersT parameters; @@ -1008,18 +1002,17 @@ ACTOR Future copyDownFile(std::string s3url, std::string filepath) { .detail("S3URL", s3url) .detail("Resource", resource) .detail("Bucket", parameters["bucket"]); - wait(copyDownFile(endpoint, parameters["bucket"], resource, filepath)); + co_await copyDownFile(endpoint, parameters["bucket"], resource, filepath); TraceEvent(SevDebug, "S3ClientCopyDownFileWrapperEnd").detail("S3URL", s3url).detail("FilePath", filepath); - return Void(); } -ACTOR Future copyDownDirectory(std::string s3url, std::string dirpath) { - state std::string resource; +Future copyDownDirectory(std::string s3url, std::string dirpath) { + std::string resource; S3BlobStoreEndpoint::ParametersT parameters; - state Reference endpoint = getEndpoint(s3url, resource, parameters); - state std::string bucket = parameters["bucket"]; - S3BlobStoreEndpoint::ListResult items = wait(endpoint->listObjects(bucket, resource)); - state std::vector objects = items.objects; + Reference endpoint = getEndpoint(s3url, resource, parameters); + std::string bucket = parameters["bucket"]; + S3BlobStoreEndpoint::ListResult items = co_await endpoint->listObjects(bucket, resource); + std::vector objects = items.objects; TraceEvent(s3VerboseEventSev(), "S3ClientDownDirectoryStart") .detail("Filecount", objects.size()) .detail("Bucket", bucket) @@ -1027,27 +1020,25 @@ ACTOR Future copyDownDirectory(std::string s3url, std::string dirpath) { for (const auto& object : objects) { std::string filepath = dirpath + "/" + object.name.substr(resource.size()); std::string s3path = object.name; - wait(copyDownFile(endpoint, bucket, s3path, filepath)); + co_await copyDownFile(endpoint, bucket, s3path, filepath); } TraceEvent(s3VerboseEventSev(), "S3ClientDownDirectoryEnd").detail("Bucket", bucket).detail("Resource", resource); - return Void(); } -ACTOR Future deleteResource(std::string s3url) { - state std::string resource; +Future deleteResource(std::string s3url) { + std::string resource; S3BlobStoreEndpoint::ParametersT parameters; Reference endpoint = getEndpoint(s3url, resource, parameters); - state std::string bucket = parameters["bucket"]; - wait(endpoint->deleteRecursively(bucket, resource)); - return Void(); + std::string bucket = parameters["bucket"]; + co_await endpoint->deleteRecursively(bucket, resource); } -ACTOR Future listFiles(std::string s3url, int maxDepth) { +Future listFiles(std::string s3url, int maxDepth) { try { - state std::string resource; - state std::string error; - state S3BlobStoreEndpoint::ParametersT parameters; - state Reference bstore = getEndpoint(s3url, resource, parameters); + std::string resource; + std::string error; + S3BlobStoreEndpoint::ParametersT parameters; + Reference bstore = getEndpoint(s3url, resource, parameters); if (!bstore) { TraceEvent(SevError, "S3ClientListingFailed").detail("Error", error); @@ -1055,23 +1046,23 @@ ACTOR Future listFiles(std::string s3url, int maxDepth) { } // Get bucket directly from parameters - state std::string bucket = parameters["bucket"]; + std::string bucket = parameters["bucket"]; // Check if bucket exists first - bool exists = wait(bstore->bucketExists(bucket)); + bool exists = co_await bstore->bucketExists(bucket); if (!exists) { std::cerr << "ERROR: Bucket '" << bucket << "' does not exist" << std::endl; throw http_request_failed(); } // Let S3BlobStoreEndpoint handle the resource path construction - state Optional delimiter; + Optional delimiter; if (maxDepth <= 1) { delimiter = Optional('/'); } // Use listObjects with the resource path directly, letting S3BlobStoreEndpoint handle URL construction - state S3BlobStoreEndpoint::ListResult result = wait(bstore->listObjects(bucket, resource, delimiter, maxDepth)); + S3BlobStoreEndpoint::ListResult result = co_await bstore->listObjects(bucket, resource, delimiter, maxDepth); // Format and display the objects std::cout << "Contents of " << s3url << ":" << std::endl; @@ -1142,20 +1133,19 @@ ACTOR Future listFiles(std::string s3url, int maxDepth) { } throw; } - return Void(); } -ACTOR Future> listFiles_impl(Reference bstore, - std::string bucket, - std::string path) { - wait(bstore->requestRateRead->getAllowance(1)); +AsyncResult> listFiles_impl(Reference bstore, + std::string bucket, + std::string path) { + co_await bstore->requestRateRead->getAllowance(1); - state std::string resource = bstore->constructResourcePath(bucket, path); - state HTTP::Headers headers; - state std::string fullResource = resource + "?list-type=2&prefix=" + path; + std::string resource = bstore->constructResourcePath(bucket, path); + HTTP::Headers headers; + std::string fullResource = resource + "?list-type=2&prefix=" + path; Reference r = - wait(bstore->doRequest("GET", fullResource, headers, nullptr, 0, { 200, 404 })); + co_await bstore->doRequest("GET", fullResource, headers, nullptr, 0, { 200, 404 }); if (r->code == 404) { TraceEvent(SevWarn, "S3ClientListFilesNotFound").detail("Bucket", bucket).detail("Path", path); @@ -1193,7 +1183,7 @@ ACTOR Future> listFiles_impl(Referencenext_sibling(); } - return files; + co_return files; } catch (Error& e) { TraceEvent(SevWarn, "S3ClientListFilesError").error(e).detail("Bucket", bucket).detail("Path", path); throw; diff --git a/fdbclient/S3Client_cli.actor.cpp b/fdbclient/S3Client_cli.cpp similarity index 91% rename from fdbclient/S3Client_cli.actor.cpp rename to fdbclient/S3Client_cli.cpp index 1fab153ca8c..dedb0d0540f 100644 --- a/fdbclient/S3Client_cli.actor.cpp +++ b/fdbclient/S3Client_cli.cpp @@ -18,43 +18,32 @@ * limitations under the License. */ -#include "fdbclient/ClientKnobCollection.h" -#include -#include #include -#include #include #include #include #include #include -#include #include #ifdef _WIN32 #include #endif -#include #include "fdbclient/BuildFlags.h" -#include "fdbclient/BackupContainerFileSystem.h" #import "fdbclient/BackupTLSConfig.h" -#include "fdbclient/FDBTypes.h" -#include "fdbclient/IKnobCollection.h" #include "fdbclient/Knobs.h" +#include "fdbclient/NativeAPI.actor.h" #include "fdbclient/versions.h" -#include "fdbclient/S3Client.actor.h" -#include "fdbclient/BackupAgent.actor.h" +#include "fdbclient/S3Client.h" #include "flow/Platform.h" #include "flow/ArgParseUtil.h" #include "flow/FastRef.h" #include "flow/Trace.h" #include "flow/flow.h" -#include "flow/TLSConfig.actor.h" +#include "flow/TLSConfig.h" #include "SimpleOpt/SimpleOpt.h" -#include "flow/actorcompiler.h" // has to be last include - // CLI for S3Client. extern const char* getSourceVersion(); @@ -180,7 +169,7 @@ struct Params : public ReferenceCounted { void updateKnobs() { // Set default to 'true' for blobstore_enable_object_integrity_check if not explicitly set bool blobstore_enable_object_integrity_check_set = false; - for (const std::pair p : knobs) { + for (const auto& p : knobs) { if (p.first == blobstore_enable_object_integrity_check) { blobstore_enable_object_integrity_check_set = true; break; @@ -189,10 +178,10 @@ struct Params : public ReferenceCounted { if (!blobstore_enable_object_integrity_check_set) { knobs.push_back(std::pair(blobstore_enable_object_integrity_check, "true")); } - IKnobCollection::setupKnobs(knobs); + setupClientKnobs(knobs); // Reinitialize knobs in order to update knobs that are dependent on explicitly set knobs - IKnobCollection::getMutableGlobalKnobCollection().initialize(Randomize::False, IsSimulated::False); + initializeClientKnobs(Randomize::False, IsSimulated::False); } }; @@ -326,51 +315,48 @@ static int parseCommandLine(Reference param, CSimpleOpt* args) { } // Method called by main. Figures which of copy_up or copy_down to call. -ACTOR Future run(Reference params) { +Future run(Reference params) { if (params->command == "cp") { if (params->whichIsBlobstoreURL == 1) { if (std::filesystem::is_directory(params->src)) { - wait(copyUpDirectory(params->src, params->tgt)); + co_await copyUpDirectory(params->src, params->tgt); } else { - wait(copyUpFile(params->src, params->tgt)); + co_await copyUpFile(params->src, params->tgt); } } else { if (std::filesystem::is_directory(params->tgt)) { - wait(copyDownDirectory(params->src, params->tgt)); + co_await copyDownDirectory(params->src, params->tgt); } else { - wait(copyDownFile(params->src, params->tgt)); + co_await copyDownFile(params->src, params->tgt); } } } else if (params->command == "rm") { - wait(deleteResource(params->src)); + co_await deleteResource(params->src); } else if (params->command == "ls") { if (params->ls_recursive) { - wait(listFiles(params->src, std::numeric_limits::max())); + co_await listFiles(params->src, std::numeric_limits::max()); } else { - wait(listFiles(params->src, 1)); + co_await listFiles(params->src, 1); } } - return Void(); } -ACTOR Future deleteResource(std::string src) { +Future deleteResource(std::string src) { try { - wait(::deleteResource(src)); + co_await ::deleteResource(src); } catch (Error& e) { // Rethrow the error to ensure it's handled by the main error handler throw; } - return Void(); } -ACTOR Future listFiles(std::string src, int maxDepth) { +Future listFiles(std::string src, int maxDepth) { try { - wait(::listFiles(src, maxDepth)); + co_await ::listFiles(src, maxDepth); } catch (Error& e) { // Rethrow the error to ensure it's handled by the main error handler throw; } - return Void(); } } // namespace s3client_cli @@ -442,7 +428,7 @@ int main(int argc, char** argv) { fprintf(stderr, "ERROR: proxy format should be either IP:port or host:port\n"); flushAndExit(FDB_EXIT_ERROR); } - Optional* pProxy = (Optional*)g_network->global(INetwork::enProxy); + auto* pProxy = static_cast*>(g_network->global(INetwork::enProxy)); *pProxy = param->proxy.get(); } @@ -451,7 +437,7 @@ int main(int argc, char** argv) { .detail("SourceVersion", getSourceVersion()) .detail("Version", FDB_VT_VERSION) .detail("PackageName", FDB_VT_PACKAGE_NAME) - .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(NULL)) + .detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(nullptr)) .setMaxFieldLength(10000) .detail("CommandLine", commandLine) .setMaxFieldLength(0) diff --git a/fdbclient/Schemas.cpp b/fdbclient/Schemas.cpp index b0496352a95..d88ab83a70e 100644 --- a/fdbclient/Schemas.cpp +++ b/fdbclient/Schemas.cpp @@ -429,6 +429,7 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "trace_log_file_write_error", "trace_log_could_not_create_file", "trace_log_writer_thread_unresponsive", + "exclude_from_tlog_recruitment_low_disk", "process_error", "io_error", "io_timeout", @@ -534,9 +535,7 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "log_server_min_free_space", "log_server_min_free_space_ratio", "storage_server_durability_lag", - "storage_server_list_fetch_failed", - "blob_worker_lag", - "blob_worker_missing" + "storage_server_list_fetch_failed" ] }, "description":"The database is not being saturated by the workload." @@ -557,9 +556,7 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "log_server_min_free_space", "log_server_min_free_space_ratio", "storage_server_durability_lag", - "storage_server_list_fetch_failed", - "blob_worker_lag", - "blob_worker_missing" + "storage_server_list_fetch_failed" ] }, "description":"The database is not being saturated by the workload." @@ -682,7 +679,6 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "unreachable_cluster_controller_worker", "unreachable_dataDistributor_worker", "unreachable_ratekeeper_worker", - "unreachable_blobManager_worker", "unreachable_consistencyScan_worker", "unreadable_configuration", "full_replication_timeout", @@ -704,8 +700,7 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "primary_dc_missing", "fetch_primary_dc_timeout", "fetch_storage_wiggler_stats_timeout", - "fetch_consistency_scan_status_timeout", - "metacluster_metrics_missing" + "fetch_consistency_scan_status_timeout" ] }, "issues":[ @@ -715,7 +710,8 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "incorrect_cluster_file_contents", "trace_log_file_write_error", "trace_log_could_not_create_file", - "trace_log_writer_thread_unresponsive" + "trace_log_writer_thread_unresponsive", + "exclude_from_tlog_recruitment_low_disk" ] }, "description":"Cluster file contents do not match current cluster connection string. Verify cluster file is writable and has not been overwritten externally." @@ -969,6 +965,7 @@ const KeyRef JSONSchemas::statusSchema = R"statusSchema( "grv_proxies":1, "proxies":6, "backup_worker_enabled":1, + "range_backup_worker_enabled":1, "perpetual_storage_wiggle":0, "perpetual_storage_wiggle_locality":"0", "perpetual_storage_wiggle_engine":{ @@ -1379,9 +1376,7 @@ const KeyRef JSONSchemas::faultToleranceStatusSchema = R"statusSchema( "log_server_min_free_space", "log_server_min_free_space_ratio", "storage_server_durability_lag", - "storage_server_list_fetch_failed", - "blob_worker_lag", - "blob_worker_missing" + "storage_server_list_fetch_failed" ] }, "description":"The database is not being saturated by the workload." @@ -1402,9 +1397,7 @@ const KeyRef JSONSchemas::faultToleranceStatusSchema = R"statusSchema( "log_server_min_free_space", "log_server_min_free_space_ratio", "storage_server_durability_lag", - "storage_server_list_fetch_failed", - "blob_worker_lag", - "blob_worker_missing" + "storage_server_list_fetch_failed" ] }, "description":"The database is not being saturated by the workload." @@ -1461,7 +1454,6 @@ const KeyRef JSONSchemas::faultToleranceStatusSchema = R"statusSchema( "unreachable_cluster_controller_worker", "unreachable_dataDistributor_worker", "unreachable_ratekeeper_worker", - "unreachable_blobManager_worker", "unreachable_consistencyScan_worker", "unreadable_configuration", "full_replication_timeout", @@ -1483,8 +1475,7 @@ const KeyRef JSONSchemas::faultToleranceStatusSchema = R"statusSchema( "primary_dc_missing", "fetch_primary_dc_timeout", "fetch_storage_wiggler_stats_timeout", - "fetch_consistency_scan_status_timeout", - "metacluster_metrics_missing" + "fetch_consistency_scan_status_timeout" ] }, "issues":[ diff --git a/fdbclient/ServerKnobCollection.cpp b/fdbclient/ServerKnobCollection.cpp deleted file mode 100644 index ad0bc522b2f..00000000000 --- a/fdbclient/ServerKnobCollection.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * ServerKnobCollection.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. - */ - -#include "fdbclient/ServerKnobCollection.h" - -ServerKnobCollection::ServerKnobCollection(Randomize randomize, IsSimulated isSimulated) - : clientKnobCollection(randomize, isSimulated), - serverKnobs(randomize, &clientKnobCollection.getMutableClientKnobs(), isSimulated) {} - -void ServerKnobCollection::initialize(Randomize randomize, IsSimulated isSimulated) { - clientKnobCollection.initialize(randomize, isSimulated); - serverKnobs.initialize(randomize, &clientKnobCollection.getMutableClientKnobs(), isSimulated); -} - -void ServerKnobCollection::reset(Randomize randomize, IsSimulated isSimulated) { - clientKnobCollection.reset(randomize, isSimulated); - serverKnobs.reset(randomize, &clientKnobCollection.getMutableClientKnobs(), isSimulated); -} - -Optional ServerKnobCollection::tryParseKnobValue(std::string const& knobName, - std::string const& knobValue) const { - auto result = clientKnobCollection.tryParseKnobValue(knobName, knobValue); - if (result.present()) { - return result; - } - auto parsedKnobValue = serverKnobs.parseKnobValue(knobName, knobValue); - if (!std::holds_alternative(parsedKnobValue)) { - return KnobValueRef::create(parsedKnobValue); - } - return {}; -} - -bool ServerKnobCollection::trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) { - // Do not short circuit by directly returning: - // clientKnobCollection.trySetKnob(knobName, knobValue) || knobValue.visitSetKnob(knobName, serverKnobs) - // This is because some knobs have the same name in client and server e.g. MAX_WRITE_TRANSACTION_LIFE_VERSIONS - // When setting such knobs, we want both client and server knob to have their value updated - // Short circuiting would mean that server knob named FOO won't be updated if client knob FOO was updated - // Instead, we attempt setting client and server knobs in separate statements, and return true - // if at least one of the set attempts was succesful. - const bool setClientKnob = clientKnobCollection.trySetKnob(knobName, knobValue); - const bool setServerKnob = knobValue.visitSetKnob(knobName, serverKnobs); - return setClientKnob || setServerKnob; -} diff --git a/fdbclient/SpecialKeySpace.actor.cpp b/fdbclient/SpecialKeySpace.cpp similarity index 85% rename from fdbclient/SpecialKeySpace.actor.cpp rename to fdbclient/SpecialKeySpace.cpp index e7193504955..7858be1fc78 100644 --- a/fdbclient/SpecialKeySpace.actor.cpp +++ b/fdbclient/SpecialKeySpace.cpp @@ -1,5 +1,5 @@ /* - * SpecialKeySpace.actor.cpp + * SpecialKeySpace.cpp * * This source file is part of the FoundationDB open source project * @@ -33,12 +33,13 @@ #include "fdbclient/FDBOptions.g.h" #include "fdbclient/Knobs.h" #include "fdbclient/ProcessInterface.h" -#include "fdbclient/GlobalConfig.actor.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/GlobalConfig.h" +#include "fdbclient/SpecialKeySpace.h" #include "flow/Arena.h" #include "flow/UnitTest.h" -#include "fdbclient/ManagementAPI.actor.h" +#include "fdbclient/ManagementAPI.h" #include "fdbclient/StatusClient.h" +#include "flow/CoroUtils.h" #include "flow/actorcompiler.h" // This must be the last #include. namespace { @@ -47,7 +48,7 @@ const std::string kTracingTokenKey = "token"; static bool isAlphaNumeric(const std::string& key) { // [A-Za-z0-9_]+ - if (!key.size()) + if (key.empty()) return false; for (const char& c : key) { if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')) @@ -122,19 +123,19 @@ RangeResult rywGetRange(ReadYourWritesTransaction* ryw, const KeyRangeRef& kr, c // The cache object is used to cache the first read result from the rpc call during the key resolution, // then when we need to do key resolution or result filtering, // we, instead of rpc call, read from this cache object have consistent results -ACTOR Future moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl* skrImpl, - ReadYourWritesTransaction* ryw, - KeySelector* ks, - KeyRangeMap>* cache) { +Future moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl* skrImpl, + ReadYourWritesTransaction* ryw, + KeySelector* ks, + KeyRangeMap>* cache) { // should be removed before calling ASSERT(!ks->orEqual); // never being called if KeySelector is already normalized ASSERT(ks->offset != 1); - state Key startKey(skrImpl->getKeyRange().begin); - state Key endKey(skrImpl->getKeyRange().end); - state RangeResult result; + Key startKey(skrImpl->getKeyRange().begin); + Key endKey(skrImpl->getKeyRange().end); + RangeResult result; if (ks->offset < 1) { // less than the given key @@ -158,17 +159,17 @@ ACTOR Future moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl* GetRangeLimits limitsHint(ks->offset >= 1 ? ks->offset : 1 - ks->offset); if (skrImpl->isAsync()) { - const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast(skrImpl); - RangeResult result_ = wait(ptr->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint, cache)); + auto* ptr = dynamic_cast(skrImpl); + RangeResult result_ = co_await ptr->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint, cache); result = result_; } else { - RangeResult result_ = wait(skrImpl->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint)); + RangeResult result_ = co_await skrImpl->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint); result = result_; } - if (result.size() == 0) { + if (result.empty()) { TraceEvent(SevDebug, "ZeroElementsIntheRange").detail("Start", startKey).detail("End", endKey); - return Void(); + co_return; } // Note : KeySelector::setKey has byte limit according to the knobs, customize it if needed if (ks->offset < 1) { @@ -194,7 +195,6 @@ ACTOR Future moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl* .detail("NormalizedOffset", ks->offset) .detail("SpecialKeyRangeStart", skrImpl->getKeyRange().begin) .detail("SpecialKeyRangeEnd", skrImpl->getKeyRange().end); - return Void(); } // This function will normalize the given KeySelector to a standard KeySelector: @@ -205,24 +205,24 @@ ACTOR Future moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl* // to maintain; Thus, separate each part to make the code easy to understand and more compact // Boundary is the range of the legal key space, which, by default is the range of the module // And (\xff\xff, \xff\xff\xff) if SPECIAL_KEY_SPACE_RELAXED is turned on -ACTOR Future normalizeKeySelectorActor(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeySelector* ks, - KeyRangeRef boundary, - int* actualOffset, - RangeResult* result, - KeyRangeMap>* cache) { +Future normalizeKeySelectorActor(SpecialKeySpace* sks, + ReadYourWritesTransaction* ryw, + KeySelector* ks, + KeyRangeRef boundary, + int* actualOffset, + RangeResult* result, + KeyRangeMap>* cache) { // If offset < 1, where we need to move left, iter points to the range containing at least one smaller key // (It's a wasting of time to walk through the range whose begin key is same as ks->key) // (rangeContainingKeyBefore itself handles the case where ks->key == Key()) // Otherwise, we only need to move right if offset > 1, iter points to the range containing the key // Since boundary.end is always a key in the RangeMap, it is always safe to move right - state RangeMap::iterator iter = + RangeMap::iterator iter = ks->offset < 1 ? sks->getReadImpls().rangeContainingKeyBefore(ks->getKey()) : sks->getReadImpls().rangeContaining(ks->getKey()); while ((ks->offset < 1 && iter->begin() >= boundary.begin) || (ks->offset > 1 && iter->begin() < boundary.end)) { if (iter->value() != nullptr) { - wait(moveKeySelectorOverRangeActor(iter->value(), ryw, ks, cache)); + co_await moveKeySelectorOverRangeActor(iter->value(), ryw, ks, cache); } // Check if we can still move the iterator left if (ks->offset < 1) { @@ -253,7 +253,6 @@ ACTOR Future normalizeKeySelectorActor(SpecialKeySpace* sks, } ks->offset = 1; } - return Void(); } SpecialKeySpace::SpecialKeySpace(KeyRef spaceStartKey, KeyRef spaceEndKey, bool testOnly) @@ -270,55 +269,55 @@ SpecialKeySpace::SpecialKeySpace(KeyRef spaceStartKey, KeyRef spaceEndKey, bool } void SpecialKeySpace::modulesBoundaryInit() { - for (const auto& pair : moduleToBoundary) { - ASSERT(range.contains(pair.second)); + for (const auto& [module, moduleBoundary] : moduleToBoundary) { + ASSERT(range.contains(moduleBoundary)); // Make sure the module is not overlapping with any registered read modules // Note: same like ranges, one module's end cannot be another module's start, relax the condition if needed - ASSERT(modules.rangeContaining(pair.second.begin) == modules.rangeContaining(pair.second.end) && - modules[pair.second.begin] == SpecialKeySpace::MODULE::UNKNOWN); - modules.insert(pair.second, pair.first); + ASSERT(modules.rangeContaining(moduleBoundary.begin) == modules.rangeContaining(moduleBoundary.end) && + modules[moduleBoundary.begin] == SpecialKeySpace::MODULE::UNKNOWN); + modules.insert(moduleBoundary, module); // Note: Due to underlying implementation, the insertion here is important to make cross_module_read being // handled correctly - readImpls.insert(pair.second, nullptr); - writeImpls.insert(pair.second, nullptr); + readImpls.insert(moduleBoundary, nullptr); + writeImpls.insert(moduleBoundary, nullptr); } } -ACTOR Future SpecialKeySpace::checkRYWValid(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse) { +Future SpecialKeySpace::checkRYWValid(SpecialKeySpace* sks, + ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse) { ASSERT(ryw); - choose { - when(RangeResult result = - wait(SpecialKeySpace::getRangeAggregationActor(sks, ryw, begin, end, limits, reverse))) { - return result; - } - when(wait(ryw->resetFuture())) { - throw internal_error(); - } + auto choice = co_await race(SpecialKeySpace::getRangeAggregationActor(sks, ryw, begin, end, limits, reverse), + ryw->resetFuture()); + if (choice.index() == 0) { + co_return std::get<0>(std::move(choice)); + } + if (choice.index() == 1) { + throw internal_error(); } + UNREACHABLE(); } -ACTOR Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse) { +Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySpace* sks, + ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse) { // This function handles ranges which cover more than one keyrange and aggregates all results // KeySelector, GetRangeLimits and reverse are all handled here - state RangeResult result; - state RangeResult pairs; - state RangeMap::iterator iter; - state int actualBeginOffset; - state int actualEndOffset; - state KeyRangeRef moduleBoundary; + RangeResult result; + RangeResult pairs; + RangeMap::iterator iter; + int actualBeginOffset{ 0 }; + int actualEndOffset{ 0 }; + KeyRangeRef moduleBoundary; // used to cache results from potential first async read // the current implementation will read the whole range result to save in the cache - state KeyRangeMap> cache(Optional(), specialKeys.end); + KeyRangeMap> cache(Optional(), specialKeys.end); if (ryw->specialKeySpaceRelaxed()) { moduleBoundary = sks->range; @@ -339,20 +338,20 @@ ACTOR Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySp } } - wait(normalizeKeySelectorActor(sks, ryw, &begin, moduleBoundary, &actualBeginOffset, &result, &cache)); - wait(normalizeKeySelectorActor(sks, ryw, &end, moduleBoundary, &actualEndOffset, &result, &cache)); + co_await normalizeKeySelectorActor(sks, ryw, &begin, moduleBoundary, &actualBeginOffset, &result, &cache); + co_await normalizeKeySelectorActor(sks, ryw, &end, moduleBoundary, &actualEndOffset, &result, &cache); // Handle all corner cases like what RYW does // return if range inverted if (actualBeginOffset >= actualEndOffset && begin.getKey() >= end.getKey()) { CODE_PROBE(true, "inverted range"); - return RangeResultRef(false, false); + co_return RangeResultRef(false, false); } // If touches begin or end, return with readToBegin and readThroughEnd flags if (begin.getKey() == moduleBoundary.end || end.getKey() == moduleBoundary.begin) { CODE_PROBE(true, "query touches begin or end"); - return result; + co_return result; } - state RangeMap::Ranges ranges = + RangeMap::Ranges ranges = sks->getReadImpls().intersectingRanges(KeyRangeRef(begin.getKey(), end.getKey())); // TODO : workaround to write this two together to make the code compact @@ -367,11 +366,11 @@ ACTOR Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySp KeyRef keyStart = kr.contains(begin.getKey()) ? begin.getKey() : kr.begin; KeyRef keyEnd = kr.contains(end.getKey()) ? end.getKey() : kr.end; if (iter->value()->isAsync() && cache.rangeContaining(keyStart).value().present()) { - const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast(iter->value()); - RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache)); + auto* ptr = dynamic_cast(iter->value()); + RangeResult pairs_ = co_await ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache); pairs = pairs_; } else { - RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits)); + RangeResult pairs_ = co_await iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits); pairs = pairs_; } result.arena().dependsOn(pairs.arena()); @@ -386,7 +385,7 @@ ACTOR Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySp if (limits.isReached()) { result.more = true; result.readToBegin = false; - return result; + co_return result; }; } } @@ -398,31 +397,31 @@ ACTOR Future SpecialKeySpace::getRangeAggregationActor(SpecialKeySp KeyRef keyStart = kr.contains(begin.getKey()) ? begin.getKey() : kr.begin; KeyRef keyEnd = kr.contains(end.getKey()) ? end.getKey() : kr.end; if (iter->value()->isAsync() && cache.rangeContaining(keyStart).value().present()) { - const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast(iter->value()); - RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache)); + auto* ptr = dynamic_cast(iter->value()); + RangeResult pairs_ = co_await ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache); pairs = pairs_; } else { - RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits)); + RangeResult pairs_ = co_await iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits); pairs = pairs_; } result.arena().dependsOn(pairs.arena()); // limits handler - for (int i = 0; i < pairs.size(); ++i) { - ASSERT(iter->range().contains(pairs[i].key)); - result.push_back(result.arena(), pairs[i]); + for (const auto& keyValue : pairs) { + ASSERT(iter->range().contains(keyValue.key)); + result.push_back(result.arena(), keyValue); // Note : behavior here is even the last k-v pair makes total bytes larger than specified, it's still // returned. In other words, the total size of the returned value (less the last entry) will be less // than byteLimit - limits.decrement(pairs[i]); + limits.decrement(keyValue); if (limits.isReached()) { result.more = true; result.readThroughEnd = false; - return result; + co_return result; }; } } } - return result; + co_return result; } Future SpecialKeySpace::getRange(ReadYourWritesTransaction* ryw, @@ -449,20 +448,18 @@ Future SpecialKeySpace::getRange(ReadYourWritesTransaction* ryw, return checkRYWValid(this, ryw, begin, end, limits, reverse); } -ACTOR Future> SpecialKeySpace::getActor(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeyRef key) { +Future> SpecialKeySpace::getActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ryw, KeyRef key) { // use getRange to workaround this - RangeResult result = wait(sks->getRange(ryw, - KeySelector(firstGreaterOrEqual(key)), - KeySelector(firstGreaterOrEqual(keyAfter(key))), - GetRangeLimits(CLIENT_KNOBS->TOO_MANY), - Reverse::False)); + RangeResult result = co_await sks->getRange(ryw, + KeySelector(firstGreaterOrEqual(key)), + KeySelector(firstGreaterOrEqual(keyAfter(key))), + GetRangeLimits(CLIENT_KNOBS->TOO_MANY), + Reverse::False); ASSERT(result.size() <= 1); - if (result.size()) { - return Optional(result[0].value); + if (!result.empty()) { + co_return Optional(result[0].value); } else { - return Optional(); + co_return Optional(); } } @@ -575,11 +572,11 @@ KeyRange SpecialKeySpace::decode(const KeyRangeRef& kr) { return KeyRangeRef(begin->value()->decode(kr.begin), begin->value()->decode(kr.end)); } -ACTOR Future commitActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ryw) { - state RangeMap>, KeyRangeRef>::Ranges ranges = +Future commitActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ryw) { + RangeMap>, KeyRangeRef>::Ranges ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(specialKeys); - state RangeMap>, KeyRangeRef>::iterator iter = ranges.begin(); - state std::vector writeModulePtrs; + RangeMap>, KeyRangeRef>::iterator iter = ranges.begin(); + std::vector writeModulePtrs; std::unordered_set deduplicate; while (iter != ranges.end()) { std::pair> entry = iter->value(); @@ -593,9 +590,9 @@ ACTOR Future commitActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ++iter; } - state std::vector::const_iterator it; + std::vector::const_iterator it; for (it = writeModulePtrs.begin(); it != writeModulePtrs.end(); ++it) { - Optional msg = wait((*it)->commit(ryw)); + Optional msg = co_await (*it)->commit(ryw); if (msg.present()) { ryw->setSpecialKeySpaceErrorMsg(msg.get()); TraceEvent(SevDebug, "SpecialKeySpaceManagementAPIError") @@ -604,7 +601,6 @@ ACTOR Future commitActor(SpecialKeySpace* sks, ReadYourWritesTransaction* throw special_keys_api_failure(); } } - return Void(); } Future SpecialKeySpace::commit(ReadYourWritesTransaction* ryw) { @@ -647,9 +643,9 @@ Future SKSCTestAsyncReadImpl::getRange(ReadYourWritesTransaction* r ReadConflictRangeImpl::ReadConflictRangeImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {} -ACTOR static Future getReadConflictRangeImpl(ReadYourWritesTransaction* ryw, KeyRange kr) { - wait(ryw->pendingReads()); - return ryw->getReadConflictRangeIntersecting(kr); +static Future getReadConflictRangeImpl(ReadYourWritesTransaction* ryw, KeyRange kr) { + co_await ryw->pendingReads(); + co_return ryw->getReadConflictRangeIntersecting(kr); } Future ReadConflictRangeImpl::getRange(ReadYourWritesTransaction* ryw, @@ -688,12 +684,13 @@ Future ConflictingKeysImpl::getRange(ReadYourWritesTransaction* ryw return result; } -ACTOR Future ddMetricsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { - loop { +Future ddMetricsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { + while (true) { + Error err; try { auto keys = kr.removePrefix(ddStatsRange.begin); Standalone> resultWithoutPrefix = - wait(waitDataDistributionMetricsList(ryw->getDatabase(), keys, CLIENT_KNOBS->TOO_MANY)); + co_await waitDataDistributionMetricsList(ryw->getDatabase(), keys, CLIENT_KNOBS->TOO_MANY); RangeResult result; for (const auto& ddMetricsRef : resultWithoutPrefix) { // each begin key is the previous end key, thus we only encode the begin key in the result @@ -707,17 +704,17 @@ ACTOR Future ddMetricsGetRangeActor(ReadYourWritesTransaction* ryw, ValueRef bytes(result.arena(), statsString); result.push_back(result.arena(), KeyValueRef(beginKey, bytes)); } - return result; + co_return result; } catch (Error& e) { - state Error err(e); - if (e.code() == error_code_dd_not_found) { - TraceEvent(SevWarnAlways, "DataDistributorNotPresent") - .detail("Operation", "DDMetricsReqestThroughSpecialKeys"); - wait(delayJittered(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY)); - continue; - } - throw err; + err = e; + } + if (err.code() == error_code_dd_not_found) { + TraceEvent(SevWarnAlways, "DataDistributorNotPresent") + .detail("Operation", "DDMetricsReqestThroughSpecialKeys"); + co_await delayJittered(FLOW_KNOBS->PREVENT_FAST_SPIN_DELAY); + continue; } + throw err; } } @@ -833,16 +830,16 @@ RangeResult rywGetRange(ReadYourWritesTransaction* ryw, const KeyRangeRef& kr, c } // read from those readwrite modules in which special keys have one-to-one mapping with real persisted keys -ACTOR Future rwModuleWithMappingGetRangeActor(ReadYourWritesTransaction* ryw, - const SpecialKeyRangeRWImpl* impl, - KeyRangeRef kr) { - RangeResult resultWithoutPrefix = - wait(ryw->getTransaction().getRange(ryw->getDatabase()->specialKeySpace->decode(kr), CLIENT_KNOBS->TOO_MANY)); +Future rwModuleWithMappingGetRangeActor(ReadYourWritesTransaction* ryw, + const SpecialKeyRangeRWImpl* impl, + KeyRangeRef kr) { + RangeResult resultWithoutPrefix = co_await ryw->getTransaction().getRange( + ryw->getDatabase()->specialKeySpace->decode(kr), CLIENT_KNOBS->TOO_MANY); ASSERT(!resultWithoutPrefix.more && resultWithoutPrefix.size() < CLIENT_KNOBS->TOO_MANY); RangeResult result; for (const KeyValueRef& kv : resultWithoutPrefix) result.push_back_deep(result.arena(), KeyValueRef(impl->encode(kv.key), kv.value)); - return rywGetRange(ryw, kr, result); + co_return rywGetRange(ryw, kr, result); } ExcludeServersRangeImpl::ExcludeServersRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -904,14 +901,14 @@ bool parseNetWorkAddrFromKeys(ReadYourWritesTransaction* ryw, return true; } -ACTOR Future checkExclusion(Database db, - std::vector addresses, - std::set exclusions, - std::unordered_set localities, - bool markFailed, - Optional* msg) { - StatusObject status = wait(StatusClient::statusFetcher(db)); - state std::string errorString = +Future checkExclusion(Database db, + std::vector addresses, + std::set exclusions, + std::unordered_set localities, + bool markFailed, + Optional* msg) { + StatusObject status = co_await StatusClient::statusFetcher(db); + std::string errorString = "ERROR: Could not calculate the impact of this exclude on the total available space in the cluster.\n" "Please try the exclude again in 30 seconds.\n" "Call set(\"0xff0xff/management/options/exclude/force\", ...) first to exclude without checking available " @@ -923,31 +920,31 @@ ACTOR Future checkExclusion(Database db, if (!statusObj.get("cluster", statusObjCluster)) { *msg = ManagementAPIError::toJsonString( false, markFailed ? "exclude failed" : "exclude", errorString + "Field 'cluster' missing.\n"); - return false; + co_return false; } StatusObjectReader processesMap; if (!statusObjCluster.get("processes", processesMap)) { *msg = ManagementAPIError::toJsonString( false, markFailed ? "exclude failed" : "exclude", errorString + "Field 'processes' missing.\n"); - return false; + co_return false; } - state int ssTotalCount = 0; - state int ssExcludedCount = 0; + int ssTotalCount = 0; + int ssExcludedCount = 0; - state std::unordered_set diskLocalities; - state int64_t totalKvStoreFreeBytes = 0; - state int64_t totalKvStoreFreeBytesNotExcluded = 0; - state int64_t totalKvStoreUsedBytes = 0; - state int64_t totalKvStoreUsedBytesNotExcluded = 0; - state int64_t totalKvStoreAvailableBytes = 0; - state std::map> parsedLocalities; + std::unordered_set diskLocalities; + int64_t totalKvStoreFreeBytes = 0; + int64_t totalKvStoreFreeBytesNotExcluded = 0; + int64_t totalKvStoreUsedBytes = 0; + int64_t totalKvStoreUsedBytesNotExcluded = 0; + int64_t totalKvStoreAvailableBytes = 0; + std::map> parsedLocalities; // Convert the passed localities into a map of vectors to make it easier to check if a process // is excluded by locality. for (const auto& locality : localities) { std::pair locality_key_value = decodeLocality(locality); - if (locality_key_value.first == "") { + if (locality_key_value.first.empty()) { continue; } @@ -962,7 +959,7 @@ ACTOR Future checkExclusion(Database db, // be empty and if the addresses are not empty, we are not using the locality based exclusion. So this // step is a safeguard for future changes where the provided addresses are not empty and locality based // exclusions are used. - state std::vector excludedAddresses; + std::vector excludedAddresses; if (markFailed && !addresses.empty()) { excludedAddresses = addresses; } @@ -982,7 +979,7 @@ ACTOR Future checkExclusion(Database db, if (!process.get("address", addrStr)) { *msg = ManagementAPIError::toJsonString(false, markFailed ? "exclude failed" : "exclude", errorString); - return false; + co_return false; } NetworkAddress addr = NetworkAddress::parse(addrStr); excluded = addressExcluded(exclusions, addr); @@ -1037,7 +1034,7 @@ ACTOR Future checkExclusion(Database db, false, markFailed ? "exclude failed" : "exclude", errorString + "Field 'kvstore_used_bytes' missing for storage role.\n"); - return false; + co_return false; } int64_t free_bytes; @@ -1046,7 +1043,7 @@ ACTOR Future checkExclusion(Database db, false, markFailed ? "exclude failed" : "exclude", errorString + "Field 'kvstore_free_bytes' missing for storage role.\n"); - return false; + co_return false; } int64_t available_bytes; @@ -1055,7 +1052,7 @@ ACTOR Future checkExclusion(Database db, false, markFailed ? "exclude failed" : "exclude", errorString + "Field 'kvstore_available_bytes' missing for storage role.\n"); - return false; + co_return false; } totalKvStoreUsedBytes += used_bytes; @@ -1082,7 +1079,7 @@ ACTOR Future checkExclusion(Database db, catch (...) { *msg = ManagementAPIError::toJsonString( false, markFailed ? "exclude failed" : "exclude", errorString + "General exception raised.\n"); - return false; + co_return false; } // If the exclusion command only contains processes that serve a non storage role we can skip the free capacity @@ -1112,14 +1109,14 @@ ACTOR Future checkExclusion(Database db, "Call set(\"0xff0xff/management/options/exclude/force\", ...) first to exclude without " "checking available space.\n"; *msg = ManagementAPIError::toJsonString(false, markFailed ? "exclude failed" : "exclude", temp); - return false; + co_return false; } } if (markFailed) { - state bool safe; + bool safe{ false }; try { - bool _safe = wait(checkSafeExclusions(db, addresses)); + bool _safe = co_await checkSafeExclusions(db, addresses); safe = _safe; } catch (Error& e) { if (e.code() == error_code_actor_cancelled) @@ -1135,11 +1132,11 @@ ACTOR Future checkExclusion(Database db, "Call set(\"0xff0xff/management/failed/\", ...) to exclude without " "performing safety checks.\n"; *msg = ManagementAPIError::toJsonString(false, markFailed ? "exclude failed" : "exclude", temp); - return false; + co_return false; } } - return true; + co_return true; } void includeServers(ReadYourWritesTransaction* ryw) { @@ -1179,28 +1176,28 @@ void includeServers(ReadYourWritesTransaction* ryw) { } } -ACTOR Future> excludeCommitActor(ReadYourWritesTransaction* ryw, bool failed) { +Future> excludeCommitActor(ReadYourWritesTransaction* ryw, bool failed) { // parse network addresses - state Optional result; - state std::vector addresses; - state std::set exclusions; - state std::unordered_set localities; + Optional result; + std::vector addresses; + std::set exclusions; + std::unordered_set localities; if (!parseNetWorkAddrFromKeys(ryw, failed, addresses, exclusions, result)) - return result; + co_return result; // If force option is not set, we need to do safety check auto force = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandOptionSpecialKey( failed ? "failed" : "excluded", "force")]; // Only do safety check when we have servers to be excluded and the force option key is not set - if (addresses.size() && !(force.first && force.second.present())) { - bool safe = wait(checkExclusion(ryw->getDatabase(), addresses, exclusions, localities, failed, &result)); + if (!addresses.empty() && !(force.first && force.second.present())) { + bool safe = co_await checkExclusion(ryw->getDatabase(), addresses, exclusions, localities, failed, &result); if (!safe) - return result; + co_return result; } - wait(excludeServers(&(ryw->getTransaction()), addresses, failed)); + co_await excludeServers(&(ryw->getTransaction()), addresses, failed); includeServers(ryw); - return result; + co_return result; } Future> ExcludeServersRangeImpl::commit(ReadYourWritesTransaction* ryw) { @@ -1235,32 +1232,32 @@ Future> FailedServersRangeImpl::commit(ReadYourWritesTrans return excludeCommitActor(ryw, true); } -ACTOR Future ExclusionInProgressActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { - state RangeResult result; - state Transaction& tr = ryw->getTransaction(); +Future ExclusionInProgressActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { + RangeResult result; + Transaction& tr = ryw->getTransaction(); tr.setOption(FDBTransactionOptions::RAW_ACCESS); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); // necessary? tr.setOption(FDBTransactionOptions::LOCK_AWARE); - state Future> fExclusions = getAllExcludedServers(&tr); - state Future> fExcludedLocalities = getAllExcludedLocalities(&tr); - state Future fServerList = tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); - state Future>> fLogsKey = tr.get(logsKey); - wait(success(fExclusions) && success(fExcludedLocalities) && success(fServerList)); + Future> fExclusions = getAllExcludedServers(&tr); + Future> fExcludedLocalities = getAllExcludedLocalities(&tr); + Future fServerList = tr.getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); + Future>> fLogsKey = tr.get(logsKey); + co_await (success(fExclusions) && success(fExcludedLocalities) && success(fServerList)); - state std::vector excl = fExclusions.get(); - state std::set exclusions(excl.begin(), excl.end()); - state std::set inProgressExclusion; + std::vector excl = fExclusions.get(); + std::set exclusions(excl.begin(), excl.end()); + std::set inProgressExclusion; // Just getting a consistent read version proves that a set of tlogs satisfying the exclusions has completed // recovery Check that there aren't any storage servers with addresses violating the exclusions - state RangeResult serverList = fServerList.get(); + RangeResult serverList = fServerList.get(); ASSERT(!serverList.more && serverList.size() < CLIENT_KNOBS->TOO_MANY); // We have to make use of the localities here to verify if a server is still in the server list, // even if it might be missing in the workers as the server is not running anymore. - state std::vector excludedLocalities = fExcludedLocalities.get(); + std::vector excludedLocalities = fExcludedLocalities.get(); // Decode the excluded localities to check if any server is excluded by locality. - state std::vector> decodedExcludedLocalities; + std::vector> decodedExcludedLocalities; for (auto& excludedLocality : excludedLocalities) { decodedExcludedLocalities.push_back(decodeLocality(excludedLocality)); } @@ -1293,21 +1290,21 @@ ACTOR Future ExclusionInProgressActor(ReadYourWritesTransaction* ry } } - wait(success(fLogsKey)); + co_await fLogsKey; Optional> value = fLogsKey.get(); ASSERT(value.present()); // TODO(jscheuermann): The logs key range doesn't hold any information about localities. This is a limitation // for locality based exclusions. The problematic edge case here is a log server that still has mutation on it // but is currently not part of the worker list, e.g. because it was shutdown or is partitioned. auto logs = decodeLogsValue(value.get()); - for (auto const& log : logs.first) { - if (log.second == NetworkAddress() || addressExcluded(exclusions, log.second)) { - inProgressExclusion.insert(log.second); + for (const auto& [_logId, logAddress] : logs.first) { + if (logAddress == NetworkAddress() || addressExcluded(exclusions, logAddress)) { + inProgressExclusion.insert(logAddress); } } - for (auto const& log : logs.second) { - if (log.second == NetworkAddress() || addressExcluded(exclusions, log.second)) { - inProgressExclusion.insert(log.second); + for (const auto& [_logId, logAddress] : logs.second) { + if (logAddress == NetworkAddress() || addressExcluded(exclusions, logAddress)) { + inProgressExclusion.insert(logAddress); } } @@ -1325,7 +1322,7 @@ ACTOR Future ExclusionInProgressActor(ReadYourWritesTransaction* ry } } - return result; + co_return result; } ExclusionInProgressRangeImpl::ExclusionInProgressRangeImpl(KeyRangeRef kr) : SpecialKeyRangeAsyncImpl(kr) {} @@ -1336,9 +1333,9 @@ Future ExclusionInProgressRangeImpl::getRange(ReadYourWritesTransac return ExclusionInProgressActor(ryw, getKeyRange().begin, kr); } -ACTOR Future getProcessClassActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { +Future getProcessClassActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { ryw->setOption(FDBTransactionOptions::RAW_ACCESS); - std::vector _workers = wait(getWorkers(&ryw->getTransaction())); + std::vector _workers = co_await getWorkers(&ryw->getTransaction()); auto workers = _workers; // strip const // Note : the sort by string is anti intuition, ex. 1.1.1.1:11 < 1.1.1.1:5 std::sort(workers.begin(), workers.end(), [](const ProcessData& lhs, const ProcessData& rhs) { @@ -1359,17 +1356,17 @@ ACTOR Future getProcessClassActor(ReadYourWritesTransaction* ryw, K result.push_back(result.arena(), KeyValueRef(k, v)); } } - return rywGetRange(ryw, kr, result); + co_return rywGetRange(ryw, kr, result); } -ACTOR Future> processClassCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef range) { +Future> processClassCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef range) { // enable related options ryw->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); ryw->setOption(FDBTransactionOptions::LOCK_AWARE); ryw->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); ryw->setOption(FDBTransactionOptions::RAW_ACCESS); - std::vector workers = wait( - getWorkers(&ryw->getTransaction())); // make sure we use the Transaction object to avoid used_during_commit() + std::vector workers = co_await getWorkers( + &ryw->getTransaction()); // make sure we use the Transaction object to avoid used_during_commit() auto ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(range); auto iter = ranges.begin(); @@ -1400,7 +1397,7 @@ ACTOR Future> processClassCommitActor(ReadYourWritesTransa } ++iter; } - return Optional(); + co_return Optional(); } ProcessClassRangeImpl::ProcessClassRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -1459,9 +1456,9 @@ void ProcessClassRangeImpl::clear(ReadYourWritesTransaction* ryw, const KeyRef& ryw, "setclass", "Clear range operation is meaningless thus forbidden for setclass"); } -ACTOR Future getProcessClassSourceActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { +Future getProcessClassSourceActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { ryw->setOption(FDBTransactionOptions::RAW_ACCESS); - std::vector _workers = wait(getWorkers(&ryw->getTransaction())); + std::vector _workers = co_await getWorkers(&ryw->getTransaction()); auto workers = _workers; // strip const // Note : the sort by string is anti intuition, ex. 1.1.1.1:11 < 1.1.1.1:5 std::sort(workers.begin(), workers.end(), [](const ProcessData& lhs, const ProcessData& rhs) { @@ -1484,7 +1481,7 @@ ACTOR Future getProcessClassSourceActor(ReadYourWritesTransaction* result.arena().dependsOn(v.arena()); } } - return result; + co_return result; } ProcessClassSourceRangeImpl::ProcessClassSourceRangeImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {} @@ -1495,16 +1492,16 @@ Future ProcessClassSourceRangeImpl::getRange(ReadYourWritesTransact return getProcessClassSourceActor(ryw, getKeyRange().begin, kr); } -ACTOR Future getLockedKeyActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { +Future getLockedKeyActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(databaseLockedKey)); + Optional val = co_await ryw->getTransaction().get(databaseLockedKey); RangeResult result; if (val.present()) { UID uid = UID::fromString(BinaryReader::fromStringRef(val.get().substr(10), Unversioned()).toString()); result.push_back_deep(result.arena(), KeyValueRef(kr.begin, Value(uid.toString()))); } - return result; + co_return result; } LockDatabaseImpl::LockDatabaseImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -1527,11 +1524,11 @@ Future LockDatabaseImpl::getRange(ReadYourWritesTransaction* ryw, } } -ACTOR Future> lockDatabaseCommitActor(ReadYourWritesTransaction* ryw, UID uid) { - state Optional msg; +Future> lockDatabaseCommitActor(ReadYourWritesTransaction* ryw, UID uid) { + Optional msg; ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(databaseLockedKey)); + Optional val = co_await ryw->getTransaction().get(databaseLockedKey); if (val.present() && BinaryReader::fromStringRef(val.get().substr(10), Unversioned()) != uid) { // check database not locked @@ -1546,17 +1543,17 @@ ACTOR Future> lockDatabaseCommitActor(ReadYourWritesTransa ryw->getTransaction().addWriteConflictRange(normalKeys); } - return msg; + co_return msg; } -ACTOR Future> unlockDatabaseCommitActor(ReadYourWritesTransaction* ryw) { +Future> unlockDatabaseCommitActor(ReadYourWritesTransaction* ryw) { ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(databaseLockedKey)); + Optional val = co_await ryw->getTransaction().get(databaseLockedKey); if (val.present()) { ryw->getTransaction().clear(singleKeyRange(databaseLockedKey)); } - return Optional(); + co_return Optional(); } Future> LockDatabaseImpl::commit(ReadYourWritesTransaction* ryw) { @@ -1576,17 +1573,17 @@ Future> LockDatabaseImpl::commit(ReadYourWritesTransaction } } -ACTOR Future getConsistencyCheckKeyActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { +Future getConsistencyCheckKeyActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); ryw->getTransaction().setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional val = wait(ryw->getTransaction().get(fdbShouldConsistencyCheckBeSuspended)); + Optional val = co_await ryw->getTransaction().get(fdbShouldConsistencyCheckBeSuspended); bool ccSuspendSetting = val.present() ? BinaryReader::fromStringRef(val.get(), Unversioned()) : false; RangeResult result; if (ccSuspendSetting) { result.push_back_deep(result.arena(), KeyValueRef(kr.begin, ValueRef())); } - return result; + co_return result; } ConsistencyCheckImpl::ConsistencyCheckImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -1668,14 +1665,13 @@ void GlobalConfigImpl::set(ReadYourWritesTransaction* ryw, const KeyRef& key, co // Writes global configuration changes to durable memory. Also writes the // changes made in the transaction to a recent history set, and updates the // latest version which the global configuration was updated at. -ACTOR Future> globalConfigCommitActor(GlobalConfigImpl* globalConfig, - ReadYourWritesTransaction* ryw) { - state Transaction& tr = ryw->getTransaction(); +Future> globalConfigCommitActor(GlobalConfigImpl* globalConfig, ReadYourWritesTransaction* ryw) { + Transaction& tr = ryw->getTransaction(); ryw->setOption(FDBTransactionOptions::RAW_ACCESS); // History should only contain three most recent updates. If it currently // has three items, remove the oldest to make room for a new item. - RangeResult history = wait(tr.getRange(globalConfigHistoryKeys, CLIENT_KNOBS->TOO_MANY)); + RangeResult history = co_await tr.getRange(globalConfigHistoryKeys, CLIENT_KNOBS->TOO_MANY); constexpr int kGlobalConfigMaxHistorySize = 3; if (history.size() > kGlobalConfigMaxHistorySize - 1) { for (int i = 0; i < history.size() - (kGlobalConfigMaxHistorySize - 1); ++i) { @@ -1689,9 +1685,9 @@ ACTOR Future> globalConfigCommitActor(GlobalConfigImpl* gl // Transform writes from the special-key-space (\xff\xff/global_config/) to // the system key space (\xff/globalConfig/), and writes mutations to // latest version history. - state RangeMap>, KeyRangeRef>::Ranges ranges = + RangeMap>, KeyRangeRef>::Ranges ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(specialKeys); - state RangeMap>, KeyRangeRef>::iterator iter = ranges.begin(); + RangeMap>, KeyRangeRef>::iterator iter = ranges.begin(); while (iter != ranges.end()) { std::pair> entry = iter->value(); if (entry.first) { @@ -1709,7 +1705,7 @@ ACTOR Future> globalConfigCommitActor(GlobalConfigImpl* gl } GlobalConfig::applyChanges(tr, insertions, clears); - return Optional(); + co_return Optional(); } // Called when a transaction includes keys in the global configuration special-key-space range. @@ -1792,9 +1788,9 @@ void TracingOptionsImpl::clear(ReadYourWritesTransaction* ryw, const KeyRef& key CoordinatorsImpl::CoordinatorsImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} -ACTOR Future coordinatorsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { - state ClusterConnectionString cs = ryw->getDatabase()->getConnectionRecord()->getConnectionString(); - state std::vector coordinator_processes = wait(cs.tryResolveHostnames()); +Future coordinatorsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { + ClusterConnectionString cs = ryw->getDatabase()->getConnectionRecord()->getConnectionString(); + std::vector coordinator_processes = co_await cs.tryResolveHostnames(); RangeResult result; Key cluster_description_key = prefix.withSuffix("cluster_description"_sr); if (kr.contains(cluster_description_key)) { @@ -1807,7 +1803,7 @@ ACTOR Future coordinatorsGetRangeActor(ReadYourWritesTransaction* r [](const NetworkAddress& lhs, const NetworkAddress& rhs) { return lhs.toString() < rhs.toString(); }); std::string processes_str; for (const auto& w : coordinator_processes) { - if (processes_str.size()) + if (!processes_str.empty()) processes_str += ","; processes_str += w.toString(); } @@ -1815,7 +1811,7 @@ ACTOR Future coordinatorsGetRangeActor(ReadYourWritesTransaction* r if (kr.contains(processes_key)) { result.push_back_deep(result.arena(), KeyValueRef(processes_key, Value(processes_str))); } - return rywGetRange(ryw, kr, result); + co_return rywGetRange(ryw, kr, result); } Future CoordinatorsImpl::getRange(ReadYourWritesTransaction* ryw, @@ -1825,12 +1821,12 @@ Future CoordinatorsImpl::getRange(ReadYourWritesTransaction* ryw, return coordinatorsGetRangeActor(ryw, prefix, kr); } -ACTOR static Future> coordinatorsCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { - state ClusterConnectionString conn; // We don't care about the Key here. - state std::vector process_address_or_hostname_strs; - state Optional msg; - state int index; - state bool parse_error = false; +static Future> coordinatorsCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { + ClusterConnectionString conn; // We don't care about the Key here. + std::vector process_address_or_hostname_strs; + Optional msg; + int index{ 0 }; + bool parse_error = false; // check update for coordinators Key processes_key = "processes"_sr.withPrefix(kr.begin); @@ -1839,8 +1835,8 @@ ACTOR static Future> coordinatorsCommitActor(ReadYourWrite ASSERT(processes_entry.second.present()); // no clear should be seen here auto processesStr = processes_entry.second.get().toString(); boost::split(process_address_or_hostname_strs, processesStr, [](char c) { return c == ','; }); - if (!process_address_or_hostname_strs.size()) { - return ManagementAPIError::toJsonString( + if (process_address_or_hostname_strs.empty()) { + co_return ManagementAPIError::toJsonString( false, "coordinators", "New coordinators\' processes are empty, please specify new processes\' network addresses with format " @@ -1866,7 +1862,7 @@ ACTOR static Future> coordinatorsCommitActor(ReadYourWrite if (parse_error) { std::string error = "ERROR: \'" + process_address_or_hostname_strs[index] + "\' is not a valid network endpoint address\n"; - return ManagementAPIError::toJsonString(false, "coordinators", error); + co_return ManagementAPIError::toJsonString(false, "coordinators", error); } } } @@ -1882,7 +1878,7 @@ ACTOR static Future> coordinatorsCommitActor(ReadYourWrite newName = entry.second.get().toString(); } else { // throw the error - return ManagementAPIError::toJsonString( + co_return ManagementAPIError::toJsonString( false, "coordinators", "Cluster description must match [A-Za-z0-9_]+"); } } @@ -1891,7 +1887,7 @@ ACTOR static Future> coordinatorsCommitActor(ReadYourWrite .detail("NewConnectionString", conn.toString()) .detail("Description", entry.first ? entry.second.get().toString() : ""); - Optional r = wait(changeQuorumChecker(&ryw->getTransaction(), &conn, newName)); + Optional r = co_await changeQuorumChecker(&ryw->getTransaction(), &conn, newName); TraceEvent(SevDebug, "SKSChangeCoordinatorsFinish") .detail("Result", r.present() ? static_cast(r.get()) : -1); // -1 means success @@ -1906,7 +1902,7 @@ ACTOR static Future> coordinatorsCommitActor(ReadYourWrite } msg = ManagementAPIError::toJsonString(retriable, "coordinators", ManagementAPI::generateErrorMessage(res)); } - return msg; + co_return msg; } Future> CoordinatorsImpl::commit(ReadYourWritesTransaction* ryw) { @@ -1924,31 +1920,28 @@ void CoordinatorsImpl::clear(ReadYourWritesTransaction* ryw, const KeyRef& key) CoordinatorsAutoImpl::CoordinatorsAutoImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {} -ACTOR static Future CoordinatorsAutoImplActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { - state RangeResult res; - state std::string autoCoordinatorsKey; - state Transaction& tr = ryw->getTransaction(); +static Future CoordinatorsAutoImplActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { + RangeResult res; + std::string autoCoordinatorsKey; + Transaction& tr = ryw->getTransaction(); tr.setOption(FDBTransactionOptions::LOCK_AWARE); tr.setOption(FDBTransactionOptions::RAW_ACCESS); tr.setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional currentKey = wait(tr.get(coordinatorsKey)); + Optional currentKey = co_await tr.get(coordinatorsKey); if (!currentKey.present()) { ryw->setSpecialKeySpaceErrorMsg( ManagementAPIError::toJsonString(false, "auto_coordinators", "The coordinator key does not exist")); throw special_keys_api_failure(); } - state ClusterConnectionString old(currentKey.get().toString()); - state CoordinatorsResult result = CoordinatorsResult::SUCCESS; + ClusterConnectionString old(currentKey.get().toString()); + CoordinatorsResult result = CoordinatorsResult::SUCCESS; - std::vector oldCoordinators = wait(old.tryResolveHostnames()); - std::vector _desiredCoordinators = wait(autoQuorumChange()->getDesiredCoordinators( - &tr, - oldCoordinators, - Reference(new ClusterConnectionMemoryRecord(old)), - result)); + std::vector oldCoordinators = co_await old.tryResolveHostnames(); + std::vector _desiredCoordinators = co_await autoQuorumChange()->getDesiredCoordinators( + &tr, oldCoordinators, makeReference(old), result); if (result == CoordinatorsResult::NOT_ENOUGH_MACHINES) { // we could get not_enough_machines if we happen to see the database while the cluster controller is updating @@ -1962,21 +1955,21 @@ ACTOR static Future CoordinatorsAutoImplActor(ReadYourWritesTransac if (result == CoordinatorsResult::SAME_NETWORK_ADDRESSES) { for (const auto& host : old.hostnames) { - autoCoordinatorsKey += autoCoordinatorsKey.size() ? "," : ""; + autoCoordinatorsKey += autoCoordinatorsKey.empty() ? "" : ","; autoCoordinatorsKey += host.toString(); } for (const auto& coord : old.coords) { - autoCoordinatorsKey += autoCoordinatorsKey.size() ? "," : ""; + autoCoordinatorsKey += autoCoordinatorsKey.empty() ? "" : ","; autoCoordinatorsKey += coord.toString(); } } else { for (const auto& address : _desiredCoordinators) { - autoCoordinatorsKey += autoCoordinatorsKey.size() ? "," : ""; + autoCoordinatorsKey += autoCoordinatorsKey.empty() ? "" : ","; autoCoordinatorsKey += address.toString(); } } res.push_back_deep(res.arena(), KeyValueRef(kr.begin, Value(autoCoordinatorsKey))); - return res; + co_return res; } Future CoordinatorsAutoImpl::getRange(ReadYourWritesTransaction* ryw, @@ -1987,17 +1980,17 @@ Future CoordinatorsAutoImpl::getRange(ReadYourWritesTransaction* ry return CoordinatorsAutoImplActor(ryw, kr); } -ACTOR static Future getMinCommitVersionActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { +static Future getMinCommitVersionActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(minRequiredCommitVersionKey)); + Optional val = co_await ryw->getTransaction().get(minRequiredCommitVersionKey); RangeResult result; if (val.present()) { Version minRequiredCommitVersion = BinaryReader::fromStringRef(val.get(), Unversioned()); ValueRef version(result.arena(), boost::lexical_cast(minRequiredCommitVersion)); result.push_back_deep(result.arena(), KeyValueRef(kr.begin, version)); } - return result; + co_return result; } AdvanceVersionImpl::AdvanceVersionImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -2020,10 +2013,10 @@ Future AdvanceVersionImpl::getRange(ReadYourWritesTransaction* ryw, } } -ACTOR static Future> advanceVersionCommitActor(ReadYourWritesTransaction* ryw, Version v) { - Optional> versionEpochValue = wait(ryw->getTransaction().get(versionEpochKey)); +static Future> advanceVersionCommitActor(ReadYourWritesTransaction* ryw, Version v) { + Optional> versionEpochValue = co_await ryw->getTransaction().get(versionEpochKey); if (versionEpochValue.present()) { - return ManagementAPIError::toJsonString( + co_return ManagementAPIError::toJsonString( false, "advanceversion", "Illegal to modify the version while the version epoch is enabled"); } @@ -2036,19 +2029,19 @@ ACTOR static Future> advanceVersionCommitActor(ReadYourWri ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); TraceEvent(SevDebug, "AdvanceVersion").detail("MaxAllowedVersion", maxAllowedVerion); if (v > maxAllowedVerion) { - return ManagementAPIError::toJsonString( + co_return ManagementAPIError::toJsonString( false, "advanceversion", "The given version is larger than the maximum allowed value(2**63-1-version_per_second*3600*24*365*1000)"); } - Version rv = wait(ryw->getTransaction().getReadVersion()); + Version rv = co_await ryw->getTransaction().getReadVersion(); if (rv <= v) { ryw->getTransaction().set(minRequiredCommitVersionKey, BinaryWriter::toValue(v + 1, Unversioned())); } else { - return ManagementAPIError::toJsonString( + co_return ManagementAPIError::toJsonString( false, "advanceversion", "Current read version is larger than the given version"); } - return Optional(); + co_return Optional(); } Future> AdvanceVersionImpl::commit(ReadYourWritesTransaction* ryw) { @@ -2070,17 +2063,17 @@ Future> AdvanceVersionImpl::commit(ReadYourWritesTransacti return Optional(); } -ACTOR static Future getVersionEpochActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { +static Future getVersionEpochActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(versionEpochKey)); + Optional val = co_await ryw->getTransaction().get(versionEpochKey); RangeResult result; if (val.present()) { int64_t versionEpoch = BinaryReader::fromStringRef(val.get(), Unversioned()); ValueRef version(result.arena(), boost::lexical_cast(versionEpoch)); result.push_back_deep(result.arena(), KeyValueRef(kr.begin, version)); } - return result; + co_return result; } VersionEpochImpl::VersionEpochImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -2312,23 +2305,21 @@ void parse(std::vector::iterator it, std::vector::iterator } } -ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransaction* ryw, - KeyRef prefix, - KeyRangeRef kr) { - state RangeResult result; +static Future actorLineageGetRangeActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { + RangeResult result; // Set default values for all fields. The default will be used if the field // is missing in the key. - state NetworkAddress host; - state WaitState waitStateStart = WaitState{ 0 }; - state WaitState waitStateEnd = WaitState{ 2 }; - state time_t timeStart = 0; - state time_t timeEnd = std::numeric_limits::max(); - state int seqStart = 0; - state int seqEnd = std::numeric_limits::max(); - - state std::vector beginValues = kr.begin.removePrefix(prefix).splitAny("/"_sr); - state std::vector endValues = kr.end.removePrefix(prefix).splitAny("/"_sr); + NetworkAddress host; + WaitState waitStateStart = WaitState{ 0 }; + WaitState waitStateEnd = WaitState{ 2 }; + time_t timeStart = 0; + time_t timeEnd = std::numeric_limits::max(); + int seqStart = 0; + int seqEnd = std::numeric_limits::max(); + + std::vector beginValues = kr.begin.removePrefix(prefix).splitAny("/"_sr); + std::vector endValues = kr.end.removePrefix(prefix).splitAny("/"_sr); // Require index (either "state" or "time") and address:port. if (beginValues.size() < 2 || endValues.size() < 2) { ryw->setSpecialKeySpaceErrorMsg( @@ -2336,7 +2327,7 @@ ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransac throw special_keys_api_failure(); } - state NetworkAddress endRangeHost; + NetworkAddress endRangeHost; try { if (SpecialKeySpace::getActorLineageApiCommandRange("state").contains(kr)) { // For the range \xff\xff/actor_lineage/state/ip:port/wait-state/time/seq @@ -2375,9 +2366,9 @@ ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransac // Open endpoint to target process on each call. This can be optimized at // some point... - state ProcessInterface process; + ProcessInterface process; process.getInterface = RequestStream(Endpoint::wellKnown({ host }, WLTOKEN_PROCESS)); - ProcessInterface p = wait(retryBrokenPromise(process.getInterface, GetProcessInterfaceRequest{})); + ProcessInterface p = co_await retryBrokenPromise(process.getInterface, GetProcessInterfaceRequest{}); process = p; ActorLineageRequest actorLineageRequest; @@ -2385,7 +2376,7 @@ ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransac actorLineageRequest.waitStateEnd = waitStateEnd; actorLineageRequest.timeStart = timeStart; actorLineageRequest.timeEnd = timeEnd; - ActorLineageReply reply = wait(process.actorLineage.getReply(actorLineageRequest)); + ActorLineageReply reply = co_await process.actorLineage.getReply(actorLineageRequest); time_t dt = 0; int seq = -1; @@ -2428,7 +2419,7 @@ ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransac result.push_back_deep(result.arena(), KeyValueRef(streamKey.str(), stream.str())); } - if (sample.data.size() == 0) { + if (sample.data.empty()) { std::ostringstream streamKey; if (SpecialKeySpace::getActorLineageApiCommandRange("state").contains(kr)) { streamKey << SpecialKeySpace::getActorLineageApiCommandPrefix("state").toString() << host.toString() @@ -2444,7 +2435,7 @@ ACTOR static Future actorLineageGetRangeActor(ReadYourWritesTransac } } - return result; + co_return result; } Future ActorLineageImpl::getRange(ReadYourWritesTransaction* ryw, @@ -2527,14 +2518,12 @@ MaintenanceImpl::MaintenanceImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} // we will calculate the remaining time(truncated to integer, the same as fdbcli) and return back as the value // If the zoneId is the special one `ignoreSSFailuresZoneString`, // value will be 0 (same as fdbcli) -ACTOR static Future MaintenanceGetRangeActor(ReadYourWritesTransaction* ryw, - KeyRef prefix, - KeyRangeRef kr) { - state RangeResult result; +static Future MaintenanceGetRangeActor(ReadYourWritesTransaction* ryw, KeyRef prefix, KeyRangeRef kr) { + RangeResult result; // zoneId ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); - Optional val = wait(ryw->getTransaction().get(healthyZoneKey)); + Optional val = co_await ryw->getTransaction().get(healthyZoneKey); if (val.present()) { auto healthyZone = decodeHealthyZoneValue(val.get()); if ((healthyZone.first == ignoreSSFailuresZoneString) || @@ -2550,7 +2539,7 @@ ACTOR static Future MaintenanceGetRangeActor(ReadYourWritesTransact } } } - return rywGetRange(ryw, kr, result); + co_return rywGetRange(ryw, kr, result); } Future MaintenanceImpl::getRange(ReadYourWritesTransaction* ryw, @@ -2564,16 +2553,16 @@ Future MaintenanceImpl::getRange(ReadYourWritesTransaction* ryw, // In addition, if the zoneId now is 'ignoreSSFailuresZoneString', // which means the data distribution is disabled for storage failures. // Only clear this specific key is allowed, any other operations will throw error -ACTOR static Future> maintenanceCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { +static Future> maintenanceCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { // read ryw->getTransaction().setOption(FDBTransactionOptions::LOCK_AWARE); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); ryw->getTransaction().setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); - Optional val = wait(ryw->getTransaction().get(healthyZoneKey)); + Optional val = co_await ryw->getTransaction().get(healthyZoneKey); Optional> healthyZone = val.present() ? decodeHealthyZoneValue(val.get()) : Optional>(); - state RangeMap>, KeyRangeRef>::Ranges ranges = + RangeMap>, KeyRangeRef>::Ranges ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(kr); Key zoneId; double seconds; @@ -2586,7 +2575,7 @@ ACTOR static Future> maintenanceCommitActor(ReadYourWrites continue; if (iter->value().second.present()) { if (isSet) - return Optional(ManagementAPIError::toJsonString( + co_return Optional(ManagementAPIError::toJsonString( false, "maintenance", "Multiple zones given for maintenance, only one allowed at the same time")); isSet = true; zoneId = iter->begin().removePrefix(kr.begin); @@ -2602,11 +2591,11 @@ ACTOR static Future> maintenanceCommitActor(ReadYourWrites if (healthyZone.present() && healthyZone.get().first == ignoreSSFailuresZoneString) { std::string msg = "Maintenance mode cannot be used while data distribution is disabled for storage " "server failures."; - return Optional(ManagementAPIError::toJsonString(false, "maintenance", msg)); + co_return Optional(ManagementAPIError::toJsonString(false, "maintenance", msg)); } else if (seconds < 0) { std::string msg = "The specified maintenance time " + boost::lexical_cast(seconds) + " is a negative value"; - return Optional(ManagementAPIError::toJsonString(false, "maintenance", msg)); + co_return Optional(ManagementAPIError::toJsonString(false, "maintenance", msg)); } else { TraceEvent(SevDebug, "SKSMaintenanceSet").detail("ZoneId", zoneId.toString()); ryw->getTransaction().set(healthyZoneKey, @@ -2615,7 +2604,7 @@ ACTOR static Future> maintenanceCommitActor(ReadYourWrites (seconds * CLIENT_KNOBS->CORE_VERSIONSPERSECOND))); } } - return Optional(); + co_return Optional(); } Future> MaintenanceImpl::commit(ReadYourWritesTransaction* ryw) { @@ -2625,19 +2614,19 @@ Future> MaintenanceImpl::commit(ReadYourWritesTransaction* DataDistributionImpl::DataDistributionImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} // Read the system keys dataDistributionModeKey and rebalanceDDIgnoreKey -ACTOR static Future DataDistributionGetRangeActor(ReadYourWritesTransaction* ryw, - KeyRef prefix, - KeyRangeRef kr) { - state RangeResult result; +static Future DataDistributionGetRangeActor(ReadYourWritesTransaction* ryw, + KeyRef prefix, + KeyRangeRef kr) { + RangeResult result; // dataDistributionModeKey - state Key modeKey = "mode"_sr.withPrefix(prefix); + Key modeKey = "mode"_sr.withPrefix(prefix); ryw->getTransaction().setOption(FDBTransactionOptions::RAW_ACCESS); if (kr.contains(modeKey)) { auto entry = ryw->getSpecialKeySpaceWriteMap()[modeKey]; if (ryw->readYourWritesDisabled() || !entry.first) { - Optional f = wait(ryw->getTransaction().get(dataDistributionModeKey)); + Optional f = co_await ryw->getTransaction().get(dataDistributionModeKey); int mode = -1; if (f.present()) { mode = BinaryReader::fromStringRef(f.get(), Unversioned()); @@ -2646,17 +2635,17 @@ ACTOR static Future DataDistributionGetRangeActor(ReadYourWritesTra } } // rebalanceDDIgnoreKey - state Key rebalanceIgnoredKey = "rebalance_ignored"_sr.withPrefix(prefix); + Key rebalanceIgnoredKey = "rebalance_ignored"_sr.withPrefix(prefix); if (kr.contains(rebalanceIgnoredKey)) { auto entry = ryw->getSpecialKeySpaceWriteMap()[rebalanceIgnoredKey]; if (ryw->readYourWritesDisabled() || !entry.first) { - Optional f = wait(ryw->getTransaction().get(rebalanceDDIgnoreKey)); + Optional f = co_await ryw->getTransaction().get(rebalanceDDIgnoreKey); if (f.present()) { result.push_back_deep(result.arena(), KeyValueRef(rebalanceIgnoredKey, Value())); } } } - return rywGetRange(ryw, kr, result); + co_return rywGetRange(ryw, kr, result); } Future DataDistributionImpl::getRange(ReadYourWritesTransaction* ryw, @@ -2809,11 +2798,11 @@ bool parseLocalitiesFromKeys(ReadYourWritesTransaction* ryw, // On commit, parses the special exclusion keys and get the localities to be excluded, check for exclusions // and add them to the exclusion list. Also, clears the special management api keys with includeLocalities. -ACTOR Future> excludeLocalityCommitActor(ReadYourWritesTransaction* ryw, bool failed) { - state Optional result; - state std::unordered_set localities; - state std::vector addresses; - state std::set exclusions; +Future> excludeLocalityCommitActor(ReadYourWritesTransaction* ryw, bool failed) { + Optional result; + std::unordered_set localities; + std::vector addresses; + std::set exclusions; ryw->setOption(FDBTransactionOptions::RAW_ACCESS); ryw->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); @@ -2824,21 +2813,21 @@ ACTOR Future> excludeLocalityCommitActor(ReadYourWritesTra // If we fetch the workers here and in the status, we are bascially fetching the same information twice // which is fine under normal circumstances, but there is no need to do that. if (!parseLocalitiesFromKeys(ryw, failed, localities, result)) - return result; + co_return result; // If force option is not set, we need to do safety check auto force = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandOptionSpecialKey( failed ? "failed_locality" : "excluded_locality", "force")]; // only do safety check when we have localities to be excluded and the force option key is not set - if (localities.size() && !(force.first && force.second.present())) { - bool safe = wait(checkExclusion(ryw->getDatabase(), addresses, exclusions, localities, failed, &result)); + if (!localities.empty() && !(force.first && force.second.present())) { + bool safe = co_await checkExclusion(ryw->getDatabase(), addresses, exclusions, localities, failed, &result); if (!safe) - return result; + co_return result; } - wait(excludeLocalities(&ryw->getTransaction(), localities, failed)); + co_await excludeLocalities(&ryw->getTransaction(), localities, failed); includeLocalities(ryw); - return result; + co_return result; } ExcludedLocalitiesRangeImpl::ExcludedLocalitiesRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {} @@ -2899,37 +2888,34 @@ Future> FailedLocalitiesRangeImpl::commit(ReadYourWritesTr return excludeLocalityCommitActor(ryw, true); } -// Defined in ReadYourWrites.actor.cpp -ACTOR Future getWorkerInterfaces(Reference clusterRecord); // Defined in NativeAPI.actor.cpp -ACTOR Future verifyInterfaceActor(Reference connectLock, ClientWorkerInterface workerInterf); +Future verifyInterfaceActor(Reference const& connectLock, ClientWorkerInterface const& workerInterf); -ACTOR static Future workerInterfacesImplGetRangeActor(ReadYourWritesTransaction* ryw, - KeyRef prefix, - KeyRangeRef kr) { +static Future workerInterfacesImplGetRangeActor(ReadYourWritesTransaction* ryw, + KeyRef prefix, + KeyRangeRef kr) { if (!ryw->getDatabase().getPtr() || !ryw->getDatabase()->getConnectionRecord()) - return RangeResult(); + co_return RangeResult(); - state RangeResult interfs = wait(getWorkerInterfaces(ryw->getDatabase()->getConnectionRecord())); + RangeResult interfs = co_await getWorkerInterfaces(ryw->getDatabase()->getConnectionRecord()); // for options' special keys, the boolean flag indicates if it's a SET operation auto [verify, _] = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandOptionSpecialKey( "worker_interfaces", "verify")]; - state RangeResult result; + RangeResult result; if (verify) { // if verify option is set, we try to talk to every worker and only returns those we can talk to Reference connectLock(new FlowLock(CLIENT_KNOBS->CLI_CONNECT_PARALLELISM)); - state std::vector> verifyInterfs; + std::vector> verifyInterfs; for (const auto& [k_, value] : interfs) { auto k = k_.withPrefix(prefix); if (kr.contains(k)) { - ClientWorkerInterface workerInterf = - BinaryReader::fromStringRef(value, IncludeVersion()); + auto workerInterf = BinaryReader::fromStringRef(value, IncludeVersion()); verifyInterfs.push_back(verifyInterfaceActor(connectLock, workerInterf)); } else { verifyInterfs.push_back(false); } } - wait(waitForAll(verifyInterfs)); + co_await waitForAll(verifyInterfs); // state int index; for (int index = 0; index < interfs.size(); index++) { if (verifyInterfs[index].get()) { @@ -2946,7 +2932,7 @@ ACTOR static Future workerInterfacesImplGetRangeActor(ReadYourWrite } } std::sort(result.begin(), result.end(), KeyValueRef::OrderByKey{}); - return result; + co_return result; } WorkerInterfacesSpecialKeyImpl::WorkerInterfacesSpecialKeyImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {} @@ -2957,17 +2943,15 @@ Future WorkerInterfacesSpecialKeyImpl::getRange(ReadYourWritesTrans return workerInterfacesImplGetRangeActor(ryw, getKeyRange().begin, kr); } -ACTOR Future> getJSON(Database db, std::string jsonField = ""); - -ACTOR static Future FaultToleranceMetricsImplActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { - state RangeResult res; +static Future FaultToleranceMetricsImplActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) { + RangeResult res; if (ryw->getDatabase().getPtr() && ryw->getDatabase()->getConnectionRecord()) { - Optional val = wait(getJSON(ryw->getDatabase(), "fault_tolerance")); + Optional val = co_await getJSON(ryw->getDatabase(), "fault_tolerance"); if (val.present()) { res.push_back_deep(res.arena(), KeyValueRef(kr.begin, val.get())); } } - return res; + co_return res; } FaultToleranceMetricsImpl::FaultToleranceMetricsImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {} @@ -2980,16 +2964,16 @@ Future FaultToleranceMetricsImpl::getRange(ReadYourWritesTransactio return FaultToleranceMetricsImplActor(ryw, kr); } -ACTOR Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse, - RangeResult result) { - if (!result.size()) { - RangeResult testResult = wait(ryw->getRange(begin, end, limits, Snapshot::True, reverse)); +Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse, + RangeResult result) { + if (result.empty()) { + RangeResult testResult = co_await ryw->getRange(begin, end, limits, Snapshot::True, reverse); ASSERT(testResult == result); - return Void(); + co_return; } if (reverse) { @@ -3025,12 +3009,12 @@ ACTOR Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, std::vector keys; keys = { deterministicRandom()->randomChoice(candidateKeys), deterministicRandom()->randomChoice(candidateKeys) }; std::sort(keys.begin(), keys.end()); - state KeySelector testBegin = firstGreaterOrEqual(keys[0]); - state KeySelector testEnd = firstGreaterOrEqual(keys[1]); + KeySelector testBegin = firstGreaterOrEqual(keys[0]); + KeySelector testEnd = firstGreaterOrEqual(keys[1]); // Generate expected result. Linear time is ok here since we're in simulation, and there's a benefit to keeping this // simple (as we're using it as an test oracle) - state RangeResult expectedResult; + RangeResult expectedResult; // The reverse parameter should be the same as for the original read, so if // reverse is true then the results are _already_ in reverse order. for (const auto& kr : result) { @@ -3040,7 +3024,7 @@ ACTOR Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, } // Test - RangeResult testResult = wait(ryw->getRange(testBegin, testEnd, limits, Snapshot::True, reverse)); + RangeResult testResult = co_await ryw->getRange(testBegin, testEnd, limits, Snapshot::True, reverse); if (testResult != expectedResult) { fmt::print("Reverse: {}\n", static_cast(reverse)); fmt::print("Original range: [{}, {})\n", begin.toString(), end.toString()); @@ -3059,5 +3043,4 @@ ACTOR Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, } ASSERT(testResult == expectedResult); } - return Void(); } diff --git a/fdbclient/StatusClient.actor.cpp b/fdbclient/StatusClient.cpp similarity index 80% rename from fdbclient/StatusClient.actor.cpp rename to fdbclient/StatusClient.cpp index 74e4b269d2b..ce5df725ab6 100644 --- a/fdbclient/StatusClient.actor.cpp +++ b/fdbclient/StatusClient.cpp @@ -1,5 +1,5 @@ /* - * StatusClient.actor.cpp + * StatusClient.cpp * * This source file is part of the FoundationDB open source project * @@ -26,11 +26,10 @@ #include "fdbclient/Status.h" #include "fdbclient/json_spirit/json_spirit_writer_template.h" #include "fdbclient/json_spirit/json_spirit_reader_template.h" -#include "fdbrpc/genericactors.actor.h" +#include "fdbrpc/genericactors.h" +#include "flow/CoroUtils.h" #include -#include "flow/actorcompiler.h" // has to be last include - json_spirit::mValue readJSONStrictly(const std::string& s) { json_spirit::mValue val; std::string::const_iterator i = s.begin(); @@ -303,14 +302,14 @@ void JSONDoc::mergeValueInto(json_spirit::mValue& dst, const json_spirit::mValue // Check if a quorum of coordination servers is reachable // Will not throw, will just return non-present Optional if error -ACTOR Future> clientCoordinatorsStatusFetcher(Reference connRecord, - bool* quorum_reachable, - int* coordinatorsFaultTolerance) { +AsyncResult> clientCoordinatorsStatusFetcher(Reference connRecord, + bool* quorum_reachable, + int* coordinatorsFaultTolerance) { try { - state ClientCoordinators coord(connRecord); - state StatusObject statusObj; + ClientCoordinators coord(connRecord); + StatusObject statusObj; - state std::vector>> leaderServers; + std::vector>> leaderServers; leaderServers.reserve(coord.clientLeaderServers.size()); for (int i = 0; i < coord.clientLeaderServers.size(); i++) { if (coord.clientLeaderServers[i].hostname.present()) { @@ -325,7 +324,7 @@ ACTOR Future> clientCoordinatorsStatusFetcher(Reference> coordProtocols; + std::vector> coordProtocols; coordProtocols.reserve(coord.clientLeaderServers.size()); for (int i = 0; i < coord.clientLeaderServers.size(); i++) { if (coord.clientLeaderServers[i].hostname.present()) { @@ -338,9 +337,9 @@ ACTOR Future> clientCoordinatorsStatusFetcher(Reference> clientCoordinatorsStatusFetcher(Reference(); + co_return Optional(); } } // Client section of the json output // Will NOT throw, errors will be put into messages array -ACTOR Future clientStatusFetcher(Reference connRecord, - StatusArray* messages, - bool* quorum_reachable, - int* coordinatorsFaultTolerance) { - state StatusObject statusObj; +AsyncResult clientStatusFetcher(Reference connRecord, + StatusArray* messages, + bool* quorum_reachable, + int* coordinatorsFaultTolerance) { + StatusObject statusObj; - state Optional coordsStatusObj = - wait(clientCoordinatorsStatusFetcher(connRecord, quorum_reachable, coordinatorsFaultTolerance)); - state bool contentsUpToDate = wait(connRecord->upToDate()); + Optional coordsStatusObj = + co_await clientCoordinatorsStatusFetcher(connRecord, quorum_reachable, coordinatorsFaultTolerance); + bool contentsUpToDate = co_await connRecord->upToDate(); if (coordsStatusObj.present()) { statusObj["coordinators"] = coordsStatusObj.get(); @@ -402,7 +401,7 @@ ACTOR Future clientStatusFetcher(ReferencegetStoredConnectionString()); + ClusterConnectionString storedConnectionString = co_await connRecord->getStoredConnectionString(); std::string description = "Cluster file contents do not match current cluster connection string."; description += "\nThe file contains the connection string: "; description += storedConnectionString.toString().c_str(); @@ -416,50 +415,46 @@ ACTOR Future clientStatusFetcher(Referencepush_back(makeMessage(MessageType::INCORRECT_CLUSTER_FILE_CONTENTS, description.c_str())); } - return statusObj; + co_return statusObj; } // Cluster section of json output -ACTOR Future> clusterStatusFetcher(ClusterInterface cI, - StatusArray* messages, - std::string statusField) { - state StatusRequest req(statusField); - state Future clusterTimeout = delay(30.0); - state Optional oStatusObj; - - wait(delay(0.0)); // make sure the cluster controller is marked as not failed - - state Future> statusReply = cI.databaseStatus.tryGetReply(req); - loop { - choose { - when(ErrorOr result = wait(statusReply)) { - if (result.isError()) { - if (result.getError().code() == error_code_request_maybe_delivered) - messages->push_back(makeMessage(MessageType::UNREACHABLE_CLUSTER_CONTROLLER, - ("Unable to communicate with the cluster controller at " + - cI.address().toString() + " to get status.") - .c_str())); - else if (result.getError().code() == error_code_server_overloaded) - messages->push_back(makeMessage(MessageType::SERVER_OVERLOADED, - "The cluster controller is currently processing too many " - "status requests and is unable to respond")); - else - messages->push_back(makeMessage(MessageType::STATUS_INCOMPLETE_ERROR, - "Cluster encountered an error fetching status.")); - } else { - oStatusObj = result.get().statusObj; - } - break; - } - when(wait(clusterTimeout)) { +AsyncResult> clusterStatusFetcher(ClusterInterface cI, + StatusArray* messages, + std::string statusField) { + StatusRequest req(statusField); + Future clusterTimeout = delay(CLIENT_KNOBS->STATUS_TIMEOUT); + Optional oStatusObj; + + co_await delay(0.0); // make sure the cluster controller is marked as not failed + + Future> statusReply = cI.databaseStatus.tryGetReply(req); + auto res = co_await race(statusReply, clusterTimeout); + if (res.index() == 0) { + ErrorOr result = std::get<0>(std::move(res)); + if (result.isError()) { + if (result.getError().code() == error_code_request_maybe_delivered) + messages->push_back(makeMessage(MessageType::UNREACHABLE_CLUSTER_CONTROLLER, + ("Unable to communicate with the cluster controller at " + + cI.address().toString() + " to get status.") + .c_str())); + else if (result.getError().code() == error_code_server_overloaded) + messages->push_back(makeMessage(MessageType::SERVER_OVERLOADED, + "The cluster controller is currently processing too many " + "status requests and is unable to respond")); + else messages->push_back( - makeMessage(MessageType::STATUS_INCOMPLETE_TIMEOUT, "Timed out fetching cluster status.")); - break; - } + makeMessage(MessageType::STATUS_INCOMPLETE_ERROR, "Cluster encountered an error fetching status.")); + } else { + oStatusObj = result.get().statusObj; } + } else if (res.index() == 1) { + messages->push_back(makeMessage(MessageType::STATUS_INCOMPLETE_TIMEOUT, "Timed out fetching cluster status.")); + } else { + UNREACHABLE(); } - return oStatusObj; + co_return oStatusObj; } // Create and return a database_status section. @@ -510,26 +505,25 @@ StatusObject getClientDatabaseStatus(StatusObjectReader client, StatusObjectRead return databaseStatus; } -ACTOR Future statusFetcherImpl(Reference connRecord, - Reference>> clusterInterface, - std::string statusField) { +AsyncResult statusFetcherImpl(Reference connRecord, + Reference>> clusterInterface, + std::string statusField) { if (!g_network) throw network_not_setup(); - state StatusObject statusObj; - state StatusObject statusObjClient; - state StatusArray clientMessages; + StatusObject statusObj; + StatusObject statusObjClient; + StatusArray clientMessages; // This could be read from the JSON but doing so safely is ugly so using a real var. - state bool quorum_reachable = false; - state int coordinatorsFaultTolerance = 0; + bool quorum_reachable = false; + int coordinatorsFaultTolerance = 0; try { - state int64_t clientTime = g_network->timer(); + int64_t clientTime = g_network->timer(); - StatusObject _statusObjClient = - wait(clientStatusFetcher(connRecord, &clientMessages, &quorum_reachable, &coordinatorsFaultTolerance)); - statusObjClient = _statusObjClient; + statusObjClient = + co_await clientStatusFetcher(connRecord, &clientMessages, &quorum_reachable, &coordinatorsFaultTolerance); if (clientTime != -1) statusObjClient["timestamp"] = clientTime; @@ -544,19 +538,17 @@ ACTOR Future statusFetcherImpl(Reference // it. } - state StatusObject statusObjCluster; + StatusObject statusObjCluster; if (quorum_reachable) { try { - - state Future interfaceTimeout = delay(2.0); - - loop { + Future interfaceTimeout = delay(2.0); + while (true) { if (clusterInterface->get().present()) { Optional _statusObjCluster = - wait(clusterStatusFetcher(clusterInterface->get().get(), &clientMessages, statusField)); + co_await clusterStatusFetcher(clusterInterface->get().get(), &clientMessages, statusField); if (_statusObjCluster.present()) { - statusObjCluster = _statusObjCluster.get(); + statusObjCluster = std::move(_statusObjCluster).get(); // TODO: this is a temporary fix, getting the number of available coordinators should move to // the server side if (statusObjCluster.count("fault_tolerance")) { @@ -578,14 +570,12 @@ ACTOR Future statusFetcherImpl(Reference // else clusterStatusFetcher added a message break; } - choose { - when(wait(clusterInterface->onChange())) {} - when(wait(interfaceTimeout)) { - clientMessages.push_back(makeMessage(MessageType::NO_CLUSTER_CONTROLLER, - "Unable to locate a cluster controller within 2 seconds. " - "Check that there are server processes running.")); - break; - } + auto res = co_await race(clusterInterface->onChange(), interfaceTimeout); + if (res.index() == 1) { + clientMessages.push_back(makeMessage(MessageType::NO_CLUSTER_CONTROLLER, + "Unable to locate a cluster controller within 2 seconds. " + "Check that there are server processes running.")); + break; } } @@ -614,21 +604,21 @@ ACTOR Future statusFetcherImpl(Reference if (layers_valid.is_null()) layers_valid = false; - return statusObj; + co_return statusObj; } -ACTOR Future timeoutMonitorLeader(Database db) { - state Future leadMon = monitorLeader(db->getConnectionRecord(), db->statusClusterInterface); - loop { - wait(delay(CLIENT_KNOBS->STATUS_IDLE_TIMEOUT + 0.00001 + db->lastStatusFetch - now())); +Future timeoutMonitorLeader(Database db) { + Future leadMon = monitorLeader(db->getConnectionRecord(), db->statusClusterInterface); + while (true) { + co_await delay(CLIENT_KNOBS->STATUS_IDLE_TIMEOUT + 0.00001 + db->lastStatusFetch - now()); if (now() - db->lastStatusFetch > CLIENT_KNOBS->STATUS_IDLE_TIMEOUT) { db->statusClusterInterface = Reference>>(); - return Void(); + co_return; } } } -Future StatusClient::statusFetcher(Database db, std::string statusField) { +AsyncResult StatusClient::statusFetcher(Database db, std::string statusField) { db->lastStatusFetch = now(); if (!db->statusClusterInterface) { db->statusClusterInterface = makeReference>>(); diff --git a/fdbclient/StorageServerInterface.cpp b/fdbclient/StorageServerInterface.cpp index bc02abf772f..9fda4145ed8 100644 --- a/fdbclient/StorageServerInterface.cpp +++ b/fdbclient/StorageServerInterface.cpp @@ -29,6 +29,75 @@ // Includes template specializations for all tss operations on storage server types. // New StorageServerInterface reply types must be added here or it won't compile. +void StorageServerInterface::initEndpointsFromGetValue() { + getKey = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(1)); + getKeyValues = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(2)); + getShardState = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(3)); + waitMetrics = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(4)); + splitMetrics = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(5)); + getStorageMetrics = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(6)); + waitFailure = RequestStream>(getValue.getEndpoint().getAdjustedEndpoint(7)); + getQueuingMetrics = + RequestStream(getValue.getEndpoint().getAdjustedEndpoint(8)); + getKeyValueStoreType = + RequestStream>(getValue.getEndpoint().getAdjustedEndpoint(9)); + watchValue = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(10)); + getReadHotRanges = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(11)); + getRangeSplitPoints = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(12)); + getKeyValuesStream = + PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(13)); + getMappedKeyValues = + PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(14)); + changeFeedStream = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(15)); + overlappingChangeFeeds = + RequestStream(getValue.getEndpoint().getAdjustedEndpoint(16)); + changeFeedPop = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(17)); + changeFeedVersionUpdate = + RequestStream(getValue.getEndpoint().getAdjustedEndpoint(18)); + checkpoint = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(19)); + fetchCheckpoint = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(20)); + fetchCheckpointKeyValues = + RequestStream(getValue.getEndpoint().getAdjustedEndpoint(21)); + updateCommitCostRequest = + RequestStream(getValue.getEndpoint().getAdjustedEndpoint(22)); + auditStorage = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(23)); + getHotShards = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(24)); + getCheckSum = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(25)); + bulkdump = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(26)); +} + +void StorageServerInterface::initEndpoints() { + std::vector> streams; + streams.push_back(getValue.getReceiver(TaskPriority::LoadBalancedEndpoint)); + streams.push_back(getKey.getReceiver(TaskPriority::LoadBalancedEndpoint)); + streams.push_back(getKeyValues.getReceiver(TaskPriority::LoadBalancedEndpoint)); + streams.push_back(getShardState.getReceiver()); + streams.push_back(waitMetrics.getReceiver()); + streams.push_back(splitMetrics.getReceiver()); + streams.push_back(getStorageMetrics.getReceiver()); + streams.push_back(waitFailure.getReceiver()); + streams.push_back(getQueuingMetrics.getReceiver()); + streams.push_back(getKeyValueStoreType.getReceiver()); + streams.push_back(watchValue.getReceiver()); + streams.push_back(getReadHotRanges.getReceiver()); + streams.push_back(getRangeSplitPoints.getReceiver()); + streams.push_back(getKeyValuesStream.getReceiver(TaskPriority::LoadBalancedEndpoint)); + streams.push_back(getMappedKeyValues.getReceiver(TaskPriority::LoadBalancedEndpoint)); + streams.push_back(changeFeedStream.getReceiver()); + streams.push_back(overlappingChangeFeeds.getReceiver()); + streams.push_back(changeFeedPop.getReceiver()); + streams.push_back(changeFeedVersionUpdate.getReceiver()); + streams.push_back(checkpoint.getReceiver()); + streams.push_back(fetchCheckpoint.getReceiver()); + streams.push_back(fetchCheckpointKeyValues.getReceiver()); + streams.push_back(updateCommitCostRequest.getReceiver()); + streams.push_back(auditStorage.getReceiver()); + streams.push_back(getHotShards.getReceiver()); + streams.push_back(getCheckSum.getReceiver()); + streams.push_back(bulkdump.getReceiver()); + FlowTransport::transport().addEndpoints(streams); +} + // if size + hex of checksum is shorter than value, record that instead of actual value. break-even point is 12 // characters std::string traceChecksumValue(const ValueRef& s) { diff --git a/fdbclient/Subspace.cpp b/fdbclient/Subspace.cpp index dfdb213e1a1..3ec4da6cd9e 100644 --- a/fdbclient/Subspace.cpp +++ b/fdbclient/Subspace.cpp @@ -38,7 +38,7 @@ Subspace::Subspace(StringRef const& rawPrefix) { this->rawPrefix.append(this->rawPrefix.arena(), rawPrefix.begin(), rawPrefix.size()); } -Subspace::~Subspace() {} +Subspace::~Subspace() = default; Key Subspace::key() const { return Key(StringRef(rawPrefix.begin(), rawPrefix.size()), rawPrefix.arena()); @@ -87,4 +87,4 @@ Subspace Subspace::subspace(Tuple const& tuple) const { Subspace Subspace::get(Tuple const& tuple) const { return subspace(tuple); -} \ No newline at end of file +} diff --git a/fdbclient/SystemData.cpp b/fdbclient/SystemData.cpp index 8fbfce948f6..258209c8c3a 100644 --- a/fdbclient/SystemData.cpp +++ b/fdbclient/SystemData.cpp @@ -18,12 +18,12 @@ * limitations under the License. */ -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/SystemData.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/StorageServerInterface.h" #include "flow/Arena.h" -#include "flow/TDMetric.actor.h" +#include "flow/TDMetric.h" #include "flow/serialize.h" #include "flow/UnitTest.h" @@ -66,13 +66,13 @@ const KeyRef keyServersKeyServersKey = keyServersKeyServersKeys.begin; const UID anonymousShardId = UID(0x666666, 0x88888888); const uint64_t emptyShardId = 0x2222222; -const Key keyServersKey(const KeyRef& k) { +Key keyServersKey(const KeyRef& k) { return k.withPrefix(keyServersPrefix); } -const KeyRef keyServersKey(const KeyRef& k, Arena& arena) { +KeyRef keyServersKey(const KeyRef& k, Arena& arena) { return k.withPrefix(keyServersPrefix, arena); } -const Value keyServersValue(RangeResult result, const std::vector& src, const std::vector& dest) { +Value keyServersValue(RangeResult result, const std::vector& src, const std::vector& dest) { if (!CLIENT_KNOBS->TAG_ENCODE_KEY_SERVERS) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withKeyServerValue())); wr << src << dest; @@ -97,10 +97,7 @@ const Value keyServersValue(RangeResult result, const std::vector& src, con return keyServersValue(srcTag, destTag); } -const Value keyServersValue(const std::vector& src, - const std::vector& dest, - const UID& srcID, - const UID& destID) { +Value keyServersValue(const std::vector& src, const std::vector& dest, const UID& srcID, const UID& destID) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withShardEncodeLocationMetaData())); if (dest.empty()) { ASSERT(!destID.isValid()); @@ -111,7 +108,7 @@ const Value keyServersValue(const std::vector& src, return wr.toValue(); } -const Value keyServersValue(const std::vector& srcTag, const std::vector& destTag) { +Value keyServersValue(const std::vector& srcTag, const std::vector& destTag) { // src and dest are expected to be sorted BinaryWriter wr(IncludeVersion(ProtocolVersion::withKeyServerValueV2())); wr << srcTag << destTag; @@ -123,7 +120,7 @@ void decodeKeyServersValue(RangeResult result, std::vector& src, std::vector& dest, bool missingIsError) { - if (value.size() == 0) { + if (value.empty()) { src.clear(); dest.clear(); return; @@ -186,7 +183,7 @@ void decodeKeyServersValue(RangeResult result, srcID = UID(); destID = UID(); - if (value.size() == 0) { + if (value.empty()) { return; } @@ -217,7 +214,7 @@ void decodeKeyServersValue(std::map const& tag_uid, static std::vector srcTag, destTag; src.clear(); dest.clear(); - if (value.size() == 0) { + if (value.empty()) { return; } @@ -275,7 +272,7 @@ void decodeKeyServersValue(std::map const& tag_uid, } bool isSystemKey(KeyRef key) { - return key.size() && key[0] == systemKeys.begin[0]; + return !key.empty() && key[0] == systemKeys.begin[0]; } const KeyRangeRef conflictingKeysRange = @@ -291,7 +288,7 @@ const KeyRangeRef writeConflictRangeKeysRange = KeyRangeRef("\xff\xff/transactio const KeyRef accumulativeChecksumKey = "\xff\xff/accumulativeChecksum"_sr; -const Value accumulativeChecksumValue(const AccumulativeChecksumState& acsState) { +Value accumulativeChecksumValue(const AccumulativeChecksumState& acsState) { return ObjectWriter::toValue(acsState, IncludeVersion()); } @@ -309,7 +306,7 @@ const KeyRef auditRangePrefix = auditRanges.begin; const KeyRangeRef auditServers = KeyRangeRef("\xff/auditServers/"_sr, "\xff/auditServers0"_sr); const KeyRef auditServerPrefix = auditServers.begin; -const Key auditKey(const AuditType type, const UID& auditId) { +Key auditKey(const AuditType type, const UID& auditId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditPrefix); wr << static_cast(type); @@ -318,7 +315,7 @@ const Key auditKey(const AuditType type, const UID& auditId) { return wr.toValue(); } -const KeyRange auditKeyRange(const AuditType type) { +KeyRange auditKeyRange(const AuditType type) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditPrefix); wr << static_cast(type); @@ -326,7 +323,7 @@ const KeyRange auditKeyRange(const AuditType type) { return prefixRange(wr.toValue()); } -const Key auditRangeBasedProgressPrefixFor(const AuditType type, const UID& auditId) { +Key auditRangeBasedProgressPrefixFor(const AuditType type, const UID& auditId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditRangePrefix); wr << static_cast(type); @@ -336,7 +333,7 @@ const Key auditRangeBasedProgressPrefixFor(const AuditType type, const UID& audi return wr.toValue(); } -const KeyRange auditRangeBasedProgressRangeFor(const AuditType type, const UID& auditId) { +KeyRange auditRangeBasedProgressRangeFor(const AuditType type, const UID& auditId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditRangePrefix); wr << static_cast(type); @@ -346,7 +343,7 @@ const KeyRange auditRangeBasedProgressRangeFor(const AuditType type, const UID& return prefixRange(wr.toValue()); } -const KeyRange auditRangeBasedProgressRangeFor(const AuditType type) { +KeyRange auditRangeBasedProgressRangeFor(const AuditType type) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditRangePrefix); wr << static_cast(type); @@ -354,7 +351,7 @@ const KeyRange auditRangeBasedProgressRangeFor(const AuditType type) { return prefixRange(wr.toValue()); } -const Key auditServerBasedProgressPrefixFor(const AuditType type, const UID& auditId, const UID& serverId) { +Key auditServerBasedProgressPrefixFor(const AuditType type, const UID& auditId, const UID& serverId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditServerPrefix); wr << static_cast(type); @@ -366,7 +363,7 @@ const Key auditServerBasedProgressPrefixFor(const AuditType type, const UID& aud return wr.toValue(); } -const KeyRange auditServerBasedProgressRangeFor(const AuditType type, const UID& auditId) { +KeyRange auditServerBasedProgressRangeFor(const AuditType type, const UID& auditId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditServerPrefix); wr << static_cast(type); @@ -376,7 +373,7 @@ const KeyRange auditServerBasedProgressRangeFor(const AuditType type, const UID& return prefixRange(wr.toValue()); } -const KeyRange auditServerBasedProgressRangeFor(const AuditType type) { +KeyRange auditServerBasedProgressRangeFor(const AuditType type) { BinaryWriter wr(Unversioned()); wr.serializeBytes(auditServerPrefix); wr << static_cast(type); @@ -384,7 +381,7 @@ const KeyRange auditServerBasedProgressRangeFor(const AuditType type) { return prefixRange(wr.toValue()); } -const Value auditStorageStateValue(const AuditStorageState& auditStorageState) { +Value auditStorageStateValue(const AuditStorageState& auditStorageState) { return ObjectWriter::toValue(auditStorageState, IncludeVersion()); } @@ -397,14 +394,14 @@ AuditStorageState decodeAuditStorageState(const ValueRef& value) { const KeyRef checkpointPrefix = "\xff/checkpoint/"_sr; -const Key checkpointKeyFor(UID checkpointID) { +Key checkpointKeyFor(UID checkpointID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(checkpointPrefix); wr << checkpointID; return wr.toValue(); } -const Value checkpointValue(const CheckpointMetaData& checkpoint) { +Value checkpointValue(const CheckpointMetaData& checkpoint) { return ObjectWriter::toValue(checkpoint, IncludeVersion()); } @@ -424,14 +421,14 @@ CheckpointMetaData decodeCheckpointValue(const ValueRef& value) { // "\xff/dataMoves/[[UID]] := [[DataMoveMetaData]]" const KeyRangeRef dataMoveKeys("\xff/dataMoves/"_sr, "\xff/dataMoves0"_sr); -const Key dataMoveKeyFor(UID dataMoveId) { +Key dataMoveKeyFor(UID dataMoveId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(dataMoveKeys.begin); wr << dataMoveId; return wr.toValue(); } -const Value dataMoveValue(const DataMoveMetaData& dataMoveMetaData) { +Value dataMoveValue(const DataMoveMetaData& dataMoveMetaData) { return ObjectWriter::toValue(dataMoveMetaData, IncludeVersion()); } @@ -452,8 +449,8 @@ DataMoveMetaData decodeDataMoveValue(const ValueRef& value) { const KeyRangeRef ddStatsRange = KeyRangeRef("\xff\xff/metrics/data_distribution_stats/"_sr, "\xff\xff/metrics/data_distribution_stats/\xff\xff"_sr); -const Value logsValue(const std::vector>& logs, - const std::vector>& oldLogs) { +Value logsValue(const std::vector>& logs, + const std::vector>& oldLogs) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withLogsValue())); wr << logs; wr << oldLogs; @@ -475,11 +472,11 @@ const ValueRef serverKeysTrue = "1"_sr, // compatible with what was serverKeysTr serverKeysTrueEmptyRange = "3"_sr, // the server treats the range as empty. serverKeysFalse; -const UID newDataMoveId(const uint64_t physicalShardId, - AssignEmptyRange assignEmptyRange, - const DataMoveType type, - const DataMovementReason reason, - UnassignShard unassignShard) { +UID newDataMoveId(const uint64_t physicalShardId, + AssignEmptyRange assignEmptyRange, + const DataMoveType type, + const DataMovementReason reason, + UnassignShard unassignShard) { uint64_t split = 0; if (assignEmptyRange) { split = emptyShardId; @@ -499,7 +496,7 @@ const UID newDataMoveId(const uint64_t physicalShardId, return UID(physicalShardId, split); } -const Key serverKeysKey(UID serverID, const KeyRef& key) { +Key serverKeysKey(UID serverID, const KeyRef& key) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverKeysPrefix); wr << serverID; @@ -507,7 +504,7 @@ const Key serverKeysKey(UID serverID, const KeyRef& key) { wr.serializeBytes(key); return wr.toValue(); } -const Key serverKeysPrefixFor(UID serverID) { +Key serverKeysPrefixFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverKeysPrefix); wr << serverID; @@ -541,7 +538,7 @@ bool serverHasKey(ValueRef storedValue) { return assigned; } -const Value serverKeysValue(const UID& id) { +Value serverKeysValue(const UID& id) { if (!id.isValid()) { return serverKeysFalse; } @@ -607,7 +604,7 @@ void decodeServerKeysValue(const ValueRef& value, DataMovementReason& dataMoveReason) { dataMoveType = DataMoveType::LOGICAL; dataMoveReason = DataMovementReason::INVALID; - if (value.size() == 0) { + if (value.empty()) { assigned = false; emptyRange = false; id = UID(); @@ -631,54 +628,11 @@ void decodeServerKeysValue(const ValueRef& value, } } -const KeyRef cacheKeysPrefix = "\xff\x02/cacheKeys/"_sr; - -const Key cacheKeysKey(uint16_t idx, const KeyRef& key) { - BinaryWriter wr(Unversioned()); - wr.serializeBytes(cacheKeysPrefix); - wr << idx; - wr.serializeBytes("/"_sr); - wr.serializeBytes(key); - return wr.toValue(); -} -const Key cacheKeysPrefixFor(uint16_t idx) { - BinaryWriter wr(Unversioned()); - wr.serializeBytes(cacheKeysPrefix); - wr << idx; - wr.serializeBytes("/"_sr); - return wr.toValue(); -} -uint16_t cacheKeysDecodeIndex(const KeyRef& key) { - uint16_t idx; - BinaryReader rd(key.removePrefix(cacheKeysPrefix), Unversioned()); - rd >> idx; - return idx; -} -KeyRef cacheKeysDecodeKey(const KeyRef& key) { - return key.substr(cacheKeysPrefix.size() + sizeof(uint16_t) + 1); -} - -const KeyRef cacheChangeKey = "\xff\x02/cacheChangeKey"_sr; -const KeyRangeRef cacheChangeKeys("\xff\x02/cacheChangeKeys/"_sr, "\xff\x02/cacheChangeKeys0"_sr); -const KeyRef cacheChangePrefix = cacheChangeKeys.begin; -const Key cacheChangeKeyFor(uint16_t idx) { - BinaryWriter wr(Unversioned()); - wr.serializeBytes(cacheChangePrefix); - wr << idx; - return wr.toValue(); -} -uint16_t cacheChangeKeyDecodeIndex(const KeyRef& key) { - uint16_t idx; - BinaryReader rd(key.removePrefix(cacheChangePrefix), Unversioned()); - rd >> idx; - return idx; -} - const KeyRangeRef tssMappingKeys("\xff/tss/"_sr, "\xff/tss0"_sr); const KeyRangeRef tssQuarantineKeys("\xff/tssQ/"_sr, "\xff/tssQ0"_sr); -const Key tssQuarantineKeyFor(UID serverID) { +Key tssQuarantineKeyFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(tssQuarantineKeys.begin); wr << serverID; @@ -720,28 +674,28 @@ const KeyRef serverTagConflictPrefix = serverTagConflictKeys.begin; const KeyRangeRef serverTagHistoryKeys("\xff/serverTagHistory/"_sr, "\xff/serverTagHistory0"_sr); const KeyRef serverTagHistoryPrefix = serverTagHistoryKeys.begin; -const Key serverTagKeyFor(UID serverID) { +Key serverTagKeyFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverTagKeys.begin); wr << serverID; return wr.toValue(); } -const Key serverTagHistoryKeyFor(UID serverID) { +Key serverTagHistoryKeyFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverTagHistoryKeys.begin); wr << serverID; return addVersionStampAtEnd(wr.toValue()); } -const KeyRange serverTagHistoryRangeFor(UID serverID) { +KeyRange serverTagHistoryRangeFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverTagHistoryKeys.begin); wr << serverID; return prefixRange(wr.toValue()); } -const KeyRange serverTagHistoryRangeBefore(UID serverID, Version version) { +KeyRange serverTagHistoryRangeBefore(UID serverID, Version version) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverTagHistoryKeys.begin); wr << serverID; @@ -754,7 +708,7 @@ const KeyRange serverTagHistoryRangeBefore(UID serverID, Version version) { return KeyRangeRef(wr.toValue(), versionStr.withPrefix(wr.toValue())); } -const Value serverTagValue(Tag tag) { +Value serverTagValue(Tag tag) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withServerTagValue())); wr << tag; return wr.toValue(); @@ -782,7 +736,7 @@ Tag decodeServerTagValue(ValueRef const& value) { return s; } -const Key serverTagConflictKeyFor(Tag tag) { +Key serverTagConflictKeyFor(Tag tag) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverTagConflictKeys.begin); wr << tag; @@ -792,14 +746,14 @@ const Key serverTagConflictKeyFor(Tag tag) { const KeyRangeRef tagLocalityListKeys("\xff/tagLocalityList/"_sr, "\xff/tagLocalityList0"_sr); const KeyRef tagLocalityListPrefix = tagLocalityListKeys.begin; -const Key tagLocalityListKeyFor(Optional dcID) { +Key tagLocalityListKeyFor(Optional dcID) { BinaryWriter wr(AssumeVersion(currentProtocolVersion())); wr.serializeBytes(tagLocalityListKeys.begin); wr << dcID; return wr.toValue(); } -const Value tagLocalityListValue(int8_t const& tagLocality) { +Value tagLocalityListValue(int8_t const& tagLocality) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withTagLocalityListValue())); wr << tagLocality; return wr.toValue(); @@ -820,14 +774,14 @@ int8_t decodeTagLocalityListValue(ValueRef const& value) { const KeyRangeRef datacenterReplicasKeys("\xff\x02/datacenterReplicas/"_sr, "\xff\x02/datacenterReplicas0"_sr); const KeyRef datacenterReplicasPrefix = datacenterReplicasKeys.begin; -const Key datacenterReplicasKeyFor(Optional dcID) { +Key datacenterReplicasKeyFor(Optional dcID) { BinaryWriter wr(AssumeVersion(currentProtocolVersion())); wr.serializeBytes(datacenterReplicasKeys.begin); wr << dcID; return wr.toValue(); } -const Value datacenterReplicasValue(int const& replicas) { +Value datacenterReplicasValue(int const& replicas) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withDatacenterReplicasValue())); wr << replicas; return wr.toValue(); @@ -848,12 +802,12 @@ int decodeDatacenterReplicasValue(ValueRef const& value) { // "\xff\x02/tLogDatacenters/[[datacenterID]]" extern const KeyRangeRef tLogDatacentersKeys; extern const KeyRef tLogDatacentersPrefix; -const Key tLogDatacentersKeyFor(Optional dcID); +Key tLogDatacentersKeyFor(Optional dcID); const KeyRangeRef tLogDatacentersKeys("\xff\x02/tLogDatacenters/"_sr, "\xff\x02/tLogDatacenters0"_sr); const KeyRef tLogDatacentersPrefix = tLogDatacentersKeys.begin; -const Key tLogDatacentersKeyFor(Optional dcID) { +Key tLogDatacentersKeyFor(Optional dcID) { BinaryWriter wr(AssumeVersion(currentProtocolVersion())); wr.serializeBytes(tLogDatacentersKeys.begin); wr << dcID; @@ -872,14 +826,14 @@ const KeyRef primaryDatacenterKey = "\xff/primaryDatacenter"_sr; const KeyRangeRef serverListKeys("\xff/serverList/"_sr, "\xff/serverList0"_sr); const KeyRef serverListPrefix = serverListKeys.begin; -const Key serverListKeyFor(UID serverID) { +Key serverListKeyFor(UID serverID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(serverListKeys.begin); wr << serverID; return wr.toValue(); } -const Value serverListValue(StorageServerInterface const& server) { +Value serverListValue(StorageServerInterface const& server) { auto protocolVersion = currentProtocolVersion(); protocolVersion.addObjectSerializerFlag(); return ObjectWriter::toValue(server, IncludeVersion(protocolVersion)); @@ -928,14 +882,14 @@ const KeyRef processClassChangeKey = "\xff/processClassChanges"_sr; const KeyRef processClassVersionKey = "\xff/processClassChangesVersion"_sr; const ValueRef processClassVersionValue = "1"_sr; -const Key processClassKeyFor(StringRef processID) { +Key processClassKeyFor(StringRef processID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(processClassKeys.begin); wr << processID; return wr.toValue(); } -const Value processClassValue(ProcessClass const& processClass) { +Value processClassValue(ProcessClass const& processClass) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withProcessClassValue())); wr << processClass; return wr.toValue(); @@ -966,6 +920,7 @@ const KeyRangeRef configKeys("\xff/conf/"_sr, "\xff/conf0"_sr); const KeyRef configKeysPrefix = configKeys.begin; const KeyRef backupWorkerEnabledKey("\xff/conf/backup_worker_enabled"_sr); +const KeyRef rangeBackupWorkerEnabledKey("\xff/conf/range_backup_worker_enabled"_sr); const KeyRef perpetualStorageWiggleKey("\xff/conf/perpetual_storage_wiggle"_sr); const KeyRef perpetualStorageWiggleLocalityKey("\xff/conf/perpetual_storage_wiggle_locality"_sr); // The below two are there for compatible upgrade and downgrade. After 7.3, the perpetual wiggle related keys should use @@ -1046,14 +1001,14 @@ const KeyRef globalConfigVersionKey = "\xff/globalConfig/v"_sr; const KeyRangeRef workerListKeys("\xff/worker/"_sr, "\xff/worker0"_sr); const KeyRef workerListPrefix = workerListKeys.begin; -const Key workerListKeyFor(StringRef processID) { +Key workerListKeyFor(StringRef processID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(workerListKeys.begin); wr << processID; return wr.toValue(); } -const Value workerListValue(ProcessData const& processData) { +Value workerListValue(ProcessData const& processData) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withWorkerListValue())); wr << processData; return wr.toValue(); @@ -1077,16 +1032,15 @@ const KeyRangeRef backupProgressKeys("\xff\x02/backupProgress/"_sr, "\xff\x02/ba const KeyRef backupProgressPrefix = backupProgressKeys.begin; const KeyRef backupStartedKey = "\xff\x02/backupStarted"_sr; extern const KeyRef backupPausedKey = "\xff\x02/backupPaused"_sr; -extern const KeyRef backupWorkerMaxNoopVersionKey = "\xff\x02/backupWorkerMaxNoopVersion"_sr; -const Key backupProgressKeyFor(UID workerID) { +Key backupProgressKeyFor(UID workerID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(backupProgressPrefix); wr << workerID; return wr.toValue(); } -const Value backupProgressValue(const WorkerBackupStatus& status) { +Value backupProgressValue(const WorkerBackupStatus& status) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withBackupProgressValue())); wr << status; return wr.toValue(); @@ -1115,7 +1069,7 @@ Value encodeBackupStartedValue(const std::vector>& ids) std::vector> decodeBackupStartedValue(const ValueRef& value) { std::vector> ids; BinaryReader reader(value, IncludeVersion()); - if (value.size() > 0) + if (!value.empty()) reader >> ids; return ids; } @@ -1124,6 +1078,43 @@ bool mutationForKey(const MutationRef& m, const KeyRef& key) { return isSingleKeyMutation((MutationRef::Type)m.type) && m.param1 == key; } +// Backup keys related to Range Partitioned. +const KeyRef backupRangePartitionedMapUploadedPrefix = "\xff\x02/backupRangePartitionedMapUploaded/"_sr; +const KeyRangeRef backupRangePartitionedProgressKeys("\xff\x02/backupRangePartitionedProgress/"_sr, + "\xff\x02/backupRangePartitionedProgress0"_sr); +const KeyRef backupRangePartitionedProgressPrefix = backupRangePartitionedProgressKeys.begin; + +Key backupRangePartitionedMapUploadedKeyFor(Version v) { + return backupRangePartitionedMapUploadedPrefix.withSuffix(format("%lld", v)); +} + +Key backupRangePartitionedProgressKey(UID workerID) { + BinaryWriter wr(Unversioned()); + wr.serializeBytes(backupRangePartitionedProgressPrefix); + wr << workerID; + return wr.toValue(); +} + +Value backupRangePartitionedProgressValue(const WorkerBackupStatus& status) { + BinaryWriter wr(IncludeVersion(ProtocolVersion::withBackupProgressValue())); + wr << status; + return wr.toValue(); +} + +UID decodeBackupRangePartitionedProgressKey(const KeyRef& key) { + UID serverID; + BinaryReader rd(key.removePrefix(backupRangePartitionedProgressPrefix), Unversioned()); + rd >> serverID; + return serverID; +} + +WorkerBackupStatus decodeBackupRangePartitionedProgressValue(const ValueRef& value) { + WorkerBackupStatus status; + BinaryReader reader(value, IncludeVersion()); + reader >> status; + return status; +} + const KeyRef previousCoordinatorsKey = "\xff/previousCoordinators"_sr; const KeyRef coordinatorsKey = "\xff/coordinators"_sr; const KeyRef logsKey = "\xff/logs"_sr; @@ -1157,7 +1148,7 @@ const KeyRef bulkLoadModeKey = "\xff/bulkLoadMode"_sr; const KeyRangeRef bulkLoadTaskKeys = KeyRangeRef("\xff/bulkLoadTask/"_sr, "\xff/bulkLoadTask0"_sr); const KeyRef bulkLoadTaskPrefix = bulkLoadTaskKeys.begin; -const Value bulkLoadTaskStateValue(const BulkLoadTaskState& bulkLoadTaskState) { +Value bulkLoadTaskStateValue(const BulkLoadTaskState& bulkLoadTaskState) { return ObjectWriter::toValue(bulkLoadTaskState, IncludeVersion()); } @@ -1168,7 +1159,7 @@ BulkLoadTaskState decodeBulkLoadTaskState(const ValueRef& value) { return bulkLoadTaskState; } -const Value ssBulkLoadMetadataValue(const SSBulkLoadMetadata& ssBulkLoadMetadata) { +Value ssBulkLoadMetadataValue(const SSBulkLoadMetadata& ssBulkLoadMetadata) { return ObjectWriter::toValue(ssBulkLoadMetadata, IncludeVersion()); } @@ -1182,7 +1173,7 @@ SSBulkLoadMetadata decodeSSBulkLoadMetadata(const ValueRef& value) { const KeyRangeRef bulkLoadJobKeys = KeyRangeRef("\xff/bulkLoadJob/"_sr, "\xff/bulkLoadJob0"_sr); const KeyRef bulkLoadJobPrefix = bulkLoadJobKeys.begin; -const Value bulkLoadJobValue(const BulkLoadJobState& bulkLoadJobState) { +Value bulkLoadJobValue(const BulkLoadJobState& bulkLoadJobState) { return ObjectWriter::toValue(bulkLoadJobState, IncludeVersion()); } @@ -1198,7 +1189,7 @@ const KeyRef bulkLoadJobHistoryPrefix = bulkLoadJobHistoryKeys.begin; // BulkLoad job with the same jobId can run for multiple times, we only keep the latest one // in the history. -const Key bulkLoadJobHistoryKeyFor(const UID& jobId) { +Key bulkLoadJobHistoryKeyFor(const UID& jobId) { BinaryWriter wr(Unversioned()); wr.serializeBytes(bulkLoadJobHistoryPrefix); wr.serializeBytes(StringRef(jobId.toString())); @@ -1210,7 +1201,7 @@ const KeyRef bulkDumpModeKey = "\xff/bulkDumpMode"_sr; const KeyRangeRef bulkDumpKeys = KeyRangeRef("\xff/bulkDump/"_sr, "\xff/bulkDump0"_sr); const KeyRef bulkDumpPrefix = bulkDumpKeys.begin; -const Value bulkDumpStateValue(const BulkDumpState& bulkDumpState) { +Value bulkDumpStateValue(const BulkDumpState& bulkDumpState) { return ObjectWriter::toValue(bulkDumpState, IncludeVersion()); } @@ -1221,13 +1212,35 @@ BulkDumpState decodeBulkDumpState(const ValueRef& value) { return bulkDumpState; } +// BulkDump owner tracking - stored separately for backward compatibility +const KeyRangeRef bulkDumpOwnerKeys = KeyRangeRef("\xff/bulkDumpOwner/"_sr, "\xff/bulkDumpOwner0"_sr); +const KeyRef bulkDumpOwnerPrefix = bulkDumpOwnerKeys.begin; + +Key bulkDumpOwnerKeyFor(const UID& jobId) { + BinaryWriter wr(Unversioned()); + wr.serializeBytes(bulkDumpOwnerPrefix); + wr << jobId; + return wr.toValue(); +} + +// BulkLoad owner tracking - stored separately for backward compatibility +const KeyRangeRef bulkLoadOwnerKeys = KeyRangeRef("\xff/bulkLoadOwner/"_sr, "\xff/bulkLoadOwner0"_sr); +const KeyRef bulkLoadOwnerPrefix = bulkLoadOwnerKeys.begin; + +Key bulkLoadOwnerKeyFor(const UID& jobId) { + BinaryWriter wr(Unversioned()); + wr.serializeBytes(bulkLoadOwnerPrefix); + wr << jobId; + return wr.toValue(); +} + // Range Lock const std::string rangeLockNameForBulkLoad = "BulkLoad"; const KeyRangeRef rangeLockKeys = KeyRangeRef("\xff/rangeLock/"_sr, "\xff/rangeLock0"_sr); const KeyRef rangeLockPrefix = rangeLockKeys.begin; -const Value rangeLockStateSetValue(const RangeLockStateSet& rangeLockStateSet) { +Value rangeLockStateSetValue(const RangeLockStateSet& rangeLockStateSet) { return ObjectWriter::toValue(rangeLockStateSet, IncludeVersion()); } @@ -1241,14 +1254,14 @@ RangeLockStateSet decodeRangeLockStateSet(const ValueRef& value) { const KeyRangeRef rangeLockOwnerKeys = KeyRangeRef("\xff/rangeLockOwner/"_sr, "\xff/rangeLockOwner0"_sr); const KeyRef rangeLockOwnerPrefix = rangeLockOwnerKeys.begin; -const Key rangeLockOwnerKeyFor(const RangeLockOwnerName& ownerUniqueID) { +Key rangeLockOwnerKeyFor(const RangeLockOwnerName& ownerUniqueID) { BinaryWriter wr(Unversioned()); wr.serializeBytes(rangeLockOwnerPrefix); wr.serializeBytes(StringRef(ownerUniqueID)); return wr.toValue(); } -const Value rangeLockOwnerValue(const RangeLockOwner& rangeLockOwner) { +Value rangeLockOwnerValue(const RangeLockOwner& rangeLockOwner) { return ObjectWriter::toValue(rangeLockOwner, IncludeVersion()); } @@ -1270,7 +1283,7 @@ const KeyRef tagThrottleCountKey = "\xff\x02/throttledTags/manualThrottleCount"_ // Client status info prefix const KeyRangeRef fdbClientInfoPrefixRange("\xff\x02/fdbClientInfo/"_sr, "\xff\x02/fdbClientInfo0"_sr); -// See remaining fields in GlobalConfig.actor.h +// See remaining fields in GlobalConfig.h // ConsistencyCheck settings const KeyRef fdbShouldConsistencyCheckBeSuspended = "\xff\x02/ConsistencyCheck/Suspend"_sr; @@ -1477,13 +1490,11 @@ const KeyRef mustContainSystemMutationsKey = "\xff/mustContainSystemMutations"_s const KeyRangeRef monitorConfKeys("\xff\x02/monitorConf/"_sr, "\xff\x02/monitorConf0"_sr); -const KeyRef restoreRequestDoneKey = "\xff\x02/restoreRequestDone"_sr; - const KeyRef healthyZoneKey = "\xff\x02/healthyZone"_sr; const StringRef ignoreSSFailuresZoneString = "IgnoreSSFailures"_sr; const KeyRef rebalanceDDIgnoreKey = "\xff\x02/rebalanceDDIgnored"_sr; -const Value healthyZoneValue(StringRef const& zoneId, Version version) { +Value healthyZoneValue(StringRef const& zoneId, Version version) { BinaryWriter wr(IncludeVersion(ProtocolVersion::withHealthyZoneValue())); wr << zoneId; wr << version; @@ -1586,11 +1597,13 @@ TEST_CASE("noSim/SystemData/compat/KeyServers") { std::sort(src.begin(), src.end()); std::sort(dest.begin(), dest.end()); RangeResult idTag; - for (int i = 0; i < src.size(); ++i) { - idTag.push_back_deep(idTag.arena(), KeyValueRef(serverTagKeyFor(src[i]), serverTagValue(uid_tag[src[i]]))); + for (const auto& srcServerId : src) { + idTag.push_back_deep(idTag.arena(), + KeyValueRef(serverTagKeyFor(srcServerId), serverTagValue(uid_tag[srcServerId]))); } - for (int i = 0; i < dest.size(); ++i) { - idTag.push_back_deep(idTag.arena(), KeyValueRef(serverTagKeyFor(dest[i]), serverTagValue(uid_tag[dest[i]]))); + for (const auto& destServerId : dest) { + idTag.push_back_deep(idTag.arena(), + KeyValueRef(serverTagKeyFor(destServerId), serverTagValue(uid_tag[destServerId]))); } auto decodeAndVerify = diff --git a/fdbclient/TagThrottle.actor.cpp b/fdbclient/TagThrottle.cpp similarity index 73% rename from fdbclient/TagThrottle.actor.cpp rename to fdbclient/TagThrottle.cpp index 94dba48d66c..4fa35d53dce 100644 --- a/fdbclient/TagThrottle.actor.cpp +++ b/fdbclient/TagThrottle.cpp @@ -1,5 +1,5 @@ /* - * TagThrottle.actor.cpp + * TagThrottle.cpp * * This source file is part of the FoundationDB open source project * @@ -18,13 +18,8 @@ * limitations under the License. */ -#include "fdbclient/CommitProxyInterface.h" -#include "fdbclient/DatabaseContext.h" #include "fdbclient/SystemData.h" -#include "fdbclient/TagThrottle.actor.h" -#include "fdbclient/Tuple.h" - -#include "flow/actorcompiler.h" // has to be last include +#include "fdbclient/TagThrottle.h" double const ClientTagThrottleLimits::NO_EXPIRATION = std::numeric_limits::max(); @@ -97,7 +92,7 @@ Key TagThrottleKey::toKey() const { for (auto tag : tags) { *(str++) = (uint8_t)tag.size(); - if (tag.size() > 0) { + if (!tag.empty()) { memcpy(str, tag.begin(), tag.size()); str += tag.size(); } @@ -108,8 +103,8 @@ Key TagThrottleKey::toKey() const { TagThrottleKey TagThrottleKey::fromKey(const KeyRef& key) { const uint8_t* str = key.substr(tagThrottleKeysPrefix.size()).begin(); - TagThrottleType throttleType = TagThrottleType(*(str++)); - TransactionPriority priority = TransactionPriority(*(str++)); + auto throttleType = TagThrottleType(*(str++)); + auto priority = TransactionPriority(*(str++)); TagSet tags; while (str < key.end()) { @@ -128,43 +123,6 @@ TagThrottleValue TagThrottleValue::fromValue(const ValueRef& value) { return throttleValue; } -KeyRangeRef const tagQuotaKeys = KeyRangeRef("\xff/tagQuota/"_sr, "\xff/tagQuota0"_sr); -KeyRef const tagQuotaPrefix = tagQuotaKeys.begin; - -Key ThrottleApi::getTagQuotaKey(TransactionTagRef tag) { - return tag.withPrefix(tagQuotaPrefix); -} - -bool ThrottleApi::TagQuotaValue::isValid() const { - return reservedQuota <= totalQuota && reservedQuota >= 0; -} - -Value ThrottleApi::TagQuotaValue::toValue() const { - return Tuple::makeTuple(reservedQuota, totalQuota).pack(); -} - -ThrottleApi::TagQuotaValue ThrottleApi::TagQuotaValue::fromValue(ValueRef value) { - auto tuple = Tuple::unpack(value); - if (tuple.size() != 2) { - throw invalid_throttle_quota_value(); - } - TagQuotaValue result; - try { - result.reservedQuota = tuple.getInt(0); - result.totalQuota = tuple.getInt(1); - } catch (Error& e) { - TraceEvent(SevWarnAlways, "TagQuotaValueFailedToDeserialize").error(e); - throw invalid_throttle_quota_value(); - } - if (!result.isValid()) { - TraceEvent(SevWarnAlways, "TagQuotaValueInvalidQuotas") - .detail("ReservedQuota", result.reservedQuota) - .detail("TotalQuota", result.totalQuota); - throw invalid_throttle_quota_value(); - } - return result; -} - TEST_CASE("TagSet/toString") { { TagSet tagSet; diff --git a/fdbclient/TaskBucket.actor.cpp b/fdbclient/TaskBucket.cpp similarity index 68% rename from fdbclient/TaskBucket.actor.cpp rename to fdbclient/TaskBucket.cpp index ee30a45855c..bac6a8335b7 100644 --- a/fdbclient/TaskBucket.actor.cpp +++ b/fdbclient/TaskBucket.cpp @@ -1,5 +1,5 @@ /* - * TaskBucket.actor.cpp + * TaskBucket.cpp * * This source file is part of the FoundationDB open source project * @@ -21,7 +21,6 @@ #include "fdbclient/TaskBucket.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/actorcompiler.h" // has to be last include Reference Task::getDoneFuture(Reference fb) { return fb->unpack(params[reservedTaskParamKeyDone]); @@ -44,22 +43,20 @@ struct UnblockFutureTaskFunc : TaskFuncBase { return _finish(tr, tb, fb, task); }; - ACTOR static Future _finish(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference future = futureBucket->unpack(task->params[Task::reservedTaskParamKeyFuture]); + static Future _finish(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference future = futureBucket->unpack(task->params[Task::reservedTaskParamKeyFuture]); futureBucket->setOptions(tr); tr->clear(future->blocks.pack(task->params[Task::reservedTaskParamKeyBlockID])); - bool is_set = wait(future->isSet(tr)); + bool is_set = co_await future->isSet(tr); if (is_set) { - wait(future->performAllActions(tr, taskBucket)); + co_await future->performAllActions(tr, taskBucket); } - - return Void(); } }; StringRef UnblockFutureTaskFunc::name = "UnblockFuture"_sr; @@ -122,13 +119,13 @@ Key Task::reservedTaskParamValidValue = "_validvalue"_sr; // be set for non-default constructor arguments. To change this behavior look at all // Task() default constructions to see if they require params to be empty and call clear. Task::Task(Value type, uint32_t version, Value done, unsigned int priority) : extendMutex(1) { - if (type.size()) + if (!type.empty()) params[Task::reservedTaskParamKeyType] = type; if (version > 0) params[Task::reservedTaskParamKeyVersion] = BinaryWriter::toValue(version, Unversioned()); - if (done.size()) + if (!done.empty()) params[Task::reservedTaskParamKeyDone] = done; priority = std::min(priority, CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY); @@ -159,36 +156,35 @@ unsigned int Task::getPriority() const { class TaskBucketImpl { public: - ACTOR static Future> getTaskKey(Reference tr, - Reference taskBucket, - int priority = 0) { + static Future> getTaskKey(Reference tr, + Reference taskBucket, + int priority = 0) { Standalone uid = StringRef(deterministicRandom()->randomUniqueID().toString()); // Get keyspace for the specified priority level - state Subspace space = taskBucket->getAvailableSpace(priority); + Subspace space = taskBucket->getAvailableSpace(priority); { // Get a task key that is <= a random UID task key, if successful then return it RangeResult value = - wait(tr->getRange(KeyRangeRef(space.key(), space.pack(uid)), 1, Snapshot::True, Reverse::True)); + co_await tr->getRange(KeyRangeRef(space.key(), space.pack(uid)), 1, Snapshot::True, Reverse::True); if (!value.empty()) { - return Optional(value[0].key); + co_return Optional(value[0].key); } } { // Get a task key that is <= the maximum possible UID, if successful return it. - RangeResult value = - wait(tr->getRange(KeyRangeRef(space.key(), space.pack(maxUIDKey)), 1, Snapshot::True, Reverse::True)); + RangeResult value = co_await tr->getRange( + KeyRangeRef(space.key(), space.pack(maxUIDKey)), 1, Snapshot::True, Reverse::True); if (!value.empty()) { - return Optional(value[0].key); + co_return Optional(value[0].key); } } - return Optional(); + co_return Optional(); } - ACTOR static Future> getOne(Reference tr, - Reference taskBucket) { + static Future> getOne(Reference tr, Reference taskBucket) { if (taskBucket->priority_batch) tr->setOption(FDBTransactionOptions::PRIORITY_BATCH); @@ -197,29 +193,28 @@ class TaskBucketImpl { // give it some chances for the timed out tasks to get into the task loop in the case of // many other new tasks get added so that the timed out tasks never get chances to re-run if (deterministicRandom()->random01() < CLIENT_KNOBS->TASKBUCKET_CHECK_TIMEOUT_CHANCE) { - bool anyTimeouts = wait(requeueTimedOutTasks(tr, taskBucket)); + bool anyTimeouts = co_await requeueTimedOutTasks(tr, taskBucket); CODE_PROBE(anyTimeouts, "Found a task that timed out"); } - state std::vector>> taskKeyFutures(CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY + 1); + std::vector>> taskKeyFutures(CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY + 1); // Start looking for a task at each priority, highest first - state int pri; - for (pri = CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; pri >= 0; --pri) + for (int pri = CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; pri >= 0; --pri) taskKeyFutures[pri] = getTaskKey(tr, taskBucket, pri); // Task key and subspace it is located in. - state Optional taskKey; - state Subspace availableSpace; + Optional taskKey; + Subspace availableSpace; // In priority order from highest to lowest, wait for fetch to finish and if it found a task then cancel the // rest. - for (pri = CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; pri >= 0; --pri) { + for (int pri = CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; pri >= 0; --pri) { // If we already have a task key then cancel this fetch if (taskKey.present()) taskKeyFutures[pri].cancel(); else { - Optional key = wait(taskKeyFutures[pri]); + Optional key = co_await taskKeyFutures[pri]; if (key.present()) { taskKey = key; availableSpace = taskBucket->getAvailableSpace(pri); @@ -229,26 +224,26 @@ class TaskBucketImpl { // If we don't have a task key, requeue timed out tasks and try again by calling self. if (!taskKey.present()) { - bool anyTimeouts = wait(requeueTimedOutTasks(tr, taskBucket)); + bool anyTimeouts = co_await requeueTimedOutTasks(tr, taskBucket); // If there were timeouts, try to get a task since there should now be one in one of the available spaces. if (anyTimeouts) { CODE_PROBE(true, "Try to get one task from timeouts subspace"); - Reference task = wait(getOne(tr, taskBucket)); - return task; + Reference task = co_await getOne(tr, taskBucket); + co_return task; } - return Reference(); + co_return Reference(); } // Now we know the task key is present and we have the available space for the task's priority - state Tuple t = availableSpace.unpack(taskKey.get()); - state Key taskUID = t.getString(0); - state Subspace taskAvailableSpace = availableSpace.get(taskUID); + Tuple t = availableSpace.unpack(taskKey.get()); + Key taskUID = t.getString(0); + Subspace taskAvailableSpace = availableSpace.get(taskUID); - state Reference task(new Task()); + Reference task(new Task()); task->key = taskUID; - state RangeResult values = wait(tr->getRange(taskAvailableSpace.range(), CLIENT_KNOBS->TOO_MANY)); - Version version = wait(tr->getReadVersion()); + RangeResult values = co_await tr->getRange(taskAvailableSpace.range(), CLIENT_KNOBS->TOO_MANY); + Version version = co_await tr->getReadVersion(); task->timeoutVersion = version + (uint64_t)(taskBucket->timeout * (CLIENT_KNOBS->TASKBUCKET_TIMEOUT_JITTER_OFFSET + @@ -266,19 +261,19 @@ class TaskBucketImpl { tr->clear(taskAvailableSpace.range()); tr->set(taskBucket->active.key(), deterministicRandom()->randomUniqueID().toString()); - return task; + co_return task; } // Verify that the user configured task verification key still has the user specified value - ACTOR static Future taskVerify(Reference tb, - Reference tr, - Reference task) { + static Future taskVerify(Reference tb, + Reference tr, + Reference task) { if (task->params.find(Task::reservedTaskParamValidKey) == task->params.end()) { TraceEvent("TaskBucketTaskVerifyInvalidTask") .detail("Task", task->params[Task::reservedTaskParamKeyType]) .detail("ReservedTaskParamValidKey", "missing"); - return false; + co_return false; } if (task->params.find(Task::reservedTaskParamValidValue) == task->params.end()) { @@ -286,12 +281,12 @@ class TaskBucketImpl { .detail("Task", task->params[Task::reservedTaskParamKeyType]) .detail("ReservedTaskParamValidKey", task->params[Task::reservedTaskParamValidKey]) .detail("ReservedTaskParamValidValue", "missing"); - return false; + co_return false; } tb->setOptions(tr); - Optional keyValue = wait(tr->get(task->params[Task::reservedTaskParamValidKey])); + Optional keyValue = co_await tr->get(task->params[Task::reservedTaskParamValidKey]); if (!keyValue.present()) { TraceEvent("TaskBucketTaskVerifyInvalidTask") @@ -299,7 +294,7 @@ class TaskBucketImpl { .detail("ReservedTaskParamValidKey", task->params[Task::reservedTaskParamValidKey]) .detail("ReservedTaskParamValidValue", task->params[Task::reservedTaskParamValidValue]) .detail("KeyValue", "missing"); - return false; + co_return false; } if (keyValue.get().compare(StringRef(task->params[Task::reservedTaskParamValidValue]))) { @@ -308,74 +303,70 @@ class TaskBucketImpl { .detail("ReservedTaskParamValidKey", task->params[Task::reservedTaskParamValidKey]) .detail("ReservedTaskParamValidValue", task->params[Task::reservedTaskParamValidValue]) .detail("KeyValue", keyValue.get()); - return false; + co_return false; } - return true; + co_return true; } - ACTOR static Future taskVerify(Reference tb, Database cx, Reference task) { - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); - + static Future taskVerify(Reference tb, Database cx, Reference task) { + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); + Error err; try { - bool verified = wait(taskVerify(tb, tr, task)); - return verified; + bool verified = co_await taskVerify(tb, tr, task); + co_return verified; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - ACTOR static Future finishTaskRun(Reference tr, - Reference taskBucket, - Reference futureBucket, - Reference task, - Reference taskFunc, - VerifyTask verifyTask) { - bool isFinished = wait(taskBucket->isFinished(tr, task)); + static Future finishTaskRun(Reference tr, + Reference taskBucket, + Reference futureBucket, + Reference task, + Reference taskFunc, + VerifyTask verifyTask) { + bool isFinished = co_await taskBucket->isFinished(tr, task); if (isFinished) { - return Void(); + co_return; } - state bool validTask = true; + bool validTask = true; if (verifyTask) { - bool _validTask = wait(taskVerify(taskBucket, tr, task)); + bool _validTask = co_await taskVerify(taskBucket, tr, task); validTask = _validTask; } if (!validTask) { - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); } else { - wait(taskFunc->finish(tr, taskBucket, futureBucket, task)); + co_await taskFunc->finish(tr, taskBucket, futureBucket, task); } - - return Void(); } - ACTOR static Future doOne(Database cx, - Reference taskBucket, - Reference futureBucket) { - state Reference task = wait(taskBucket->getOne(cx)); - bool result = wait(taskBucket->doTask(cx, futureBucket, task)); - return result; + static Future doOne(Database cx, Reference taskBucket, Reference futureBucket) { + Reference task = co_await taskBucket->getOne(cx); + bool result = co_await taskBucket->doTask(cx, futureBucket, task); + co_return result; } - ACTOR static Future extendTimeoutRepeatedly(Database cx, - Reference taskBucket, - Reference task) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state double start = now(); - state Version versionNow = wait(runRYWTransaction(cx, [=](Reference tr) { + static Future extendTimeoutRepeatedly(Database cx, Reference taskBucket, Reference task) { + Reference tr(new ReadYourWritesTransaction(cx)); + double start = now(); + Version versionNow = co_await runRYWTransaction(cx, [=](Reference tr) { taskBucket->setOptions(tr); return map(tr->getReadVersion(), [=](Version v) { return v; }); - })); + }); - loop { - state FlowLock::Releaser releaser; + while (true) { + FlowLock::Releaser releaser; // Wait until we are half way to the timeout version of this task - wait(delay(0.8 * (BUGGIFY ? (2 * deterministicRandom()->random01()) : 1.0) * - (double)(task->timeoutVersion - (uint64_t)versionNow) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND)); + co_await delay(0.8 * (BUGGIFY ? (2 * deterministicRandom()->random01()) : 1.0) * + (double)(task->timeoutVersion - (uint64_t)versionNow) / + CLIENT_KNOBS->CORE_VERSIONSPERSECOND); if (now() - start > 300) { TraceEvent(SevWarnAlways, "TaskBucketLongExtend") @@ -385,122 +376,128 @@ class TaskBucketImpl { .detail("Priority", task->getPriority()); } // Take the extendMutex lock until we either succeed or stop trying to extend due to failure - wait(task->extendMutex.take()); + co_await task->extendMutex.take(); releaser = FlowLock::Releaser(task->extendMutex, 1); - loop { + while (true) { + Error err; try { tr->reset(); taskBucket->setOptions(tr); // Attempt to extend the task's timeout - state Version newTimeout = wait(taskBucket->extendTimeout(tr, task, UpdateParams::False)); - wait(tr->commit()); + Version newTimeout = co_await taskBucket->extendTimeout(tr, task, UpdateParams::False); + co_await tr->commit(); task->timeoutVersion = newTimeout; versionNow = tr->getCommittedVersion(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } } - ACTOR static Future doTask(Database cx, - Reference taskBucket, - Reference futureBucket, - Reference task) { - state Reference taskFunc; - state VerifyTask verifyTask(false); + static Future doTask(Database cx, + Reference taskBucket, + Reference futureBucket, + Reference task) { + Reference taskFunc; + VerifyTask verifyTask(false); if (!task || !TaskFuncBase::isValidTask(task)) - return false; + co_return false; + Error err; try { taskFunc = TaskFuncBase::create(task->params[Task::reservedTaskParamKeyType]); if (taskFunc) { verifyTask.set(task->params.find(Task::reservedTaskParamValidKey) != task->params.end()); if (verifyTask) { - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Reference tr(new ReadYourWritesTransaction(cx)); taskBucket->setOptions(tr); - + Error innerErr; try { - bool validTask = wait(taskVerify(taskBucket, tr, task)); + bool validTask = co_await taskVerify(taskBucket, tr, task); if (!validTask) { - bool isFinished = wait(taskBucket->isFinished(tr, task)); + bool isFinished = co_await taskBucket->isFinished(tr, task); if (!isFinished) { - wait(taskBucket->finish(tr, task)); + co_await taskBucket->finish(tr, task); } - wait(tr->commit()); - return true; + co_await tr->commit(); + co_return true; } break; } catch (Error& e) { - wait(tr->onError(e)); + innerErr = e; } + co_await tr->onError(innerErr); } } - wait(taskFunc->execute(cx, taskBucket, futureBucket, task) || - extendTimeoutRepeatedly(cx, taskBucket, task)); + co_await (taskFunc->execute(cx, taskBucket, futureBucket, task) || + extendTimeoutRepeatedly(cx, taskBucket, task)); if (BUGGIFY) - wait(delay(10.0)); - wait(runRYWTransaction(cx, [=](Reference tr) { + co_await delay(10.0); + co_await runRYWTransaction(cx, [=](Reference tr) { return finishTaskRun(tr, taskBucket, futureBucket, task, taskFunc, verifyTask); - })); + }); } + co_return true; } catch (Error& e) { - TraceEvent(SevWarn, "TaskBucketExecuteFailure") - .error(e) - .detail("TaskUID", task->key) + err = e; + } + TraceEvent(SevWarn, "TaskBucketExecuteFailure") + .error(err) + .detail("TaskUID", task->key) + .detail("TaskType", task->params[Task::reservedTaskParamKeyType].printable()) + .detail("Priority", task->getPriority()); + try { + co_await taskFunc->handleError(cx, task, err); + } catch (Error& handleErr) { + TraceEvent(SevWarn, "TaskBucketExecuteFailureLogErrorFailed") + .error(handleErr) // output handleError() error instead of original task error + .detail("TaskUID", task->key.printable()) .detail("TaskType", task->params[Task::reservedTaskParamKeyType].printable()) .detail("Priority", task->getPriority()); - try { - wait(taskFunc->handleError(cx, task, e)); - } catch (Error& e) { - TraceEvent(SevWarn, "TaskBucketExecuteFailureLogErrorFailed") - .error(e) // output handleError() error instead of original task error - .detail("TaskUID", task->key.printable()) - .detail("TaskType", task->params[Task::reservedTaskParamKeyType].printable()) - .detail("Priority", task->getPriority()); - } } // Return true to indicate that we did work. - return true; + co_return true; } - ACTOR static Future dispatch(Database cx, - Reference taskBucket, - Reference futureBucket, - std::shared_ptr pollDelay, - int maxConcurrentTasks) { - state std::vector> tasks(maxConcurrentTasks); + static Future dispatch(Database cx, + Reference taskBucket, + Reference futureBucket, + std::shared_ptr pollDelay, + int maxConcurrentTasks) { + std::vector> tasks(maxConcurrentTasks); for (auto& f : tasks) f = Never(); // Since the futures have to be kept in a vector to be compatible with waitForAny(), we'll keep a queue // of available slots in it. Initially, they're all available. - state std::vector availableSlots; + std::vector availableSlots; availableSlots.reserve(tasks.size()); for (int i = 0; i < tasks.size(); ++i) availableSlots.push_back(i); - state std::vector>> getTasks; - state unsigned int getBatchSize = 1; + std::vector>> getTasks; + unsigned int getBatchSize = 1; - loop { + while (true) { // Start running tasks while slots are available and we keep finding work to do ++taskBucket->dispatchSlotChecksStarted; while (!availableSlots.empty()) { getTasks.clear(); for (int i = 0, imax = std::min(getBatchSize, availableSlots.size()); i < imax; ++i) getTasks.push_back(taskBucket->getOne(cx)); - wait(waitForAllReady(getTasks)); + co_await waitForAllReady(getTasks); bool done = false; for (int i = 0; i < getTasks.size(); ++i) { @@ -540,7 +537,7 @@ class TaskBucketImpl { w = w || delay(*pollDelay * (0.9 + deterministicRandom()->random01() / 5)); // Jittered by 20 %, so +/- 10% } - wait(w); + co_await w; // Check all of the task slots, any that are finished should be replaced with Never() and their slots added // back to availableSlots @@ -553,40 +550,42 @@ class TaskBucketImpl { } } - ACTOR static Future watchPaused(Database cx, - Reference taskBucket, - Reference> paused) { - loop { - state Reference tr(new ReadYourWritesTransaction(cx)); + static Future watchPaused(Database cx, Reference taskBucket, Reference> paused) { + while (true) { + auto tr = makeReference(cx); + Error err; try { taskBucket->setOptions(tr); - Optional pausedVal = wait(tr->get(taskBucket->pauseKey)); + Optional pausedVal = co_await tr->get(taskBucket->pauseKey); paused->set(pausedVal.present()); - state Future watchPausedFuture = tr->watch(taskBucket->pauseKey); - wait(tr->commit()); - wait(watchPausedFuture); + Future watchPausedFuture = tr->watch(taskBucket->pauseKey); + co_await tr->commit(); + co_await watchPausedFuture; + continue; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } - ACTOR static Future run(Database cx, - Reference taskBucket, - Reference futureBucket, - std::shared_ptr pollDelay, - int maxConcurrentTasks) { - state Reference> paused = makeReference>(true); - state Future watchPausedFuture = watchPaused(cx, taskBucket, paused); + static Future run(Database cx, + Reference taskBucket, + Reference futureBucket, + std::shared_ptr pollDelay, + int maxConcurrentTasks) { + auto paused = makeReference>(true); + Future watchPausedFuture = watchPaused(cx, taskBucket, paused); taskBucket->metricLogger = taskBucket->cc.traceCounters( "TaskBucketMetrics", taskBucket->dbgid, CLIENT_KNOBS->TASKBUCKET_LOGGING_DELAY); - loop { + while (true) { while (paused->get()) { - wait(paused->onChange() || watchPausedFuture); + co_await (paused->onChange() || watchPausedFuture); } - wait(dispatch(cx, taskBucket, futureBucket, pollDelay, maxConcurrentTasks) || paused->onChange() || - watchPausedFuture); + co_await race(dispatch(cx, taskBucket, futureBucket, pollDelay, maxConcurrentTasks), + paused->onChange(), + watchPausedFuture); } } @@ -602,148 +601,149 @@ class TaskBucketImpl { return runRYWTransaction(cx, [=](Reference tr) { return addIdle(tr, taskBucket); }); } - ACTOR static Future isEmpty(Reference tr, Reference taskBucket) { + static Future isEmpty(Reference tr, Reference taskBucket) { taskBucket->setOptions(tr); // Check all available priorities for keys - state std::vector> resultFutures; + std::vector> resultFutures; for (int pri = 0; pri <= CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; ++pri) resultFutures.push_back(tr->getRange(taskBucket->getAvailableSpace(pri).range(), 1)); // If any priority levels have any keys then the taskbucket is not empty so return false - state int i; - for (i = 0; i < resultFutures.size(); ++i) { - RangeResult results = wait(resultFutures[i]); - if (results.size() > 0) - return false; + for (int i = 0; i < resultFutures.size(); ++i) { + RangeResult results = co_await resultFutures[i]; + if (!results.empty()) + co_return false; } - RangeResult values = wait(tr->getRange(taskBucket->timeouts.range(), 1)); - if (values.size() > 0) - return false; + RangeResult values = co_await tr->getRange(taskBucket->timeouts.range(), 1); + if (!values.empty()) + co_return false; - return true; + co_return true; } - ACTOR static Future isBusy(Reference tr, Reference taskBucket) { + static Future isBusy(Reference tr, Reference taskBucket) { taskBucket->setOptions(tr); // Check all available priorities for emptiness - state std::vector> resultFutures; + std::vector> resultFutures; for (int pri = 0; pri <= CLIENT_KNOBS->TASKBUCKET_MAX_PRIORITY; ++pri) resultFutures.push_back(tr->getRange(taskBucket->getAvailableSpace(pri).range(), 1)); // If any priority levels have any keys then return true as the level is 'busy' - state int i; - for (i = 0; i < resultFutures.size(); ++i) { - RangeResult results = wait(resultFutures[i]); - if (results.size() > 0) - return true; + for (int i = 0; i < resultFutures.size(); ++i) { + RangeResult results = co_await resultFutures[i]; + if (!results.empty()) + co_return true; } - return false; + co_return false; } // Verify that the task's keys are still in the timeout space at the expected timeout prefix - ACTOR static Future isFinished(Reference tr, - Reference taskBucket, - Reference task) { + static Future isFinished(Reference tr, + Reference taskBucket, + Reference task) { taskBucket->setOptions(tr); Tuple t = Tuple::makeTuple(task->timeoutVersion, task->key); - RangeResult values = wait(tr->getRange(taskBucket->timeouts.range(t), 1)); - if (values.size() > 0) - return false; + RangeResult values = co_await tr->getRange(taskBucket->timeouts.range(t), 1); + if (!values.empty()) + co_return false; - return true; + co_return true; } - ACTOR static Future getActiveKey(Reference tr, - Reference taskBucket, - Optional startingValue) { + static Future getActiveKey(Reference tr, + Reference taskBucket, + Optional startingValue) { taskBucket->setOptions(tr); - Optional new_value = wait(tr->get(taskBucket->active.key())); + Optional new_value = co_await tr->get(taskBucket->active.key()); if (new_value != startingValue) { - return true; + co_return true; } - return false; + co_return false; } - ACTOR static Future checkActive(Database cx, Reference taskBucket) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state Optional startingValue; + static Future checkActive(Database cx, Reference taskBucket) { + Reference tr(new ReadYourWritesTransaction(cx)); + Optional startingValue; - loop { + while (true) { + Error err; try { taskBucket->setOptions(tr); - bool is_busy = wait(isBusy(tr, taskBucket)); + bool is_busy = co_await isBusy(tr, taskBucket); if (!is_busy) { - wait(success(addIdle(tr, taskBucket))); + co_await addIdle(tr, taskBucket); } - Optional val = wait(tr->get(taskBucket->active.key())); + Optional val = co_await tr->get(taskBucket->active.key()); startingValue = val; - wait(tr->commit()); + co_await tr->commit(); break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } - state int idx = 0; + int idx = 0; for (; idx < CLIENT_KNOBS->TASKBUCKET_CHECK_ACTIVE_AMOUNT; ++idx) { - tr = Reference(new ReadYourWritesTransaction(cx)); - loop { + tr = makeReference(cx); + while (true) { + Error err; try { taskBucket->setOptions(tr); - wait(delay(CLIENT_KNOBS->TASKBUCKET_CHECK_ACTIVE_DELAY)); - bool isActiveKey = wait(getActiveKey(tr, taskBucket, startingValue)); + co_await delay(CLIENT_KNOBS->TASKBUCKET_CHECK_ACTIVE_DELAY); + bool isActiveKey = co_await getActiveKey(tr, taskBucket, startingValue); if (isActiveKey) { CODE_PROBE(true, "checkActive return true"); - return true; + co_return true; } break; } catch (Error& e) { - wait(tr->onError(e)); + err = e; } + co_await tr->onError(err); } } CODE_PROBE(true, "checkActive return false"); - return false; + co_return false; } - ACTOR static Future getTaskCount(Reference tr, - Reference taskBucket) { + static Future getTaskCount(Reference tr, Reference taskBucket) { taskBucket->setOptions(tr); - Optional val = wait(tr->get(taskBucket->prefix.pack("task_count"_sr))); + Optional val = co_await tr->get(taskBucket->prefix.pack("task_count"_sr)); if (!val.present()) - return 0; + co_return 0; ASSERT(val.get().size() == sizeof(int64_t)); int64_t intValue = 0; memcpy(&intValue, val.get().begin(), val.get().size()); - return intValue; + co_return intValue; } // Looks for tasks that have timed out and returns them to be available tasks. // Returns True if any tasks were affected. - ACTOR static Future requeueTimedOutTasks(Reference tr, - Reference taskBucket) { + static Future requeueTimedOutTasks(Reference tr, + Reference taskBucket) { CODE_PROBE(true, "Looks for tasks that have timed out and returns them to be available tasks."); - Version end = wait(tr->getReadVersion()); - state KeyRange range( + Version end = co_await tr->getReadVersion(); + KeyRange range( KeyRangeRef(taskBucket->timeouts.get(0).range().begin, taskBucket->timeouts.get(end).range().end)); - RangeResult values = wait(tr->getRange(range, CLIENT_KNOBS->TASKBUCKET_MAX_TASK_KEYS)); + RangeResult values = co_await tr->getRange(range, CLIENT_KNOBS->TASKBUCKET_MAX_TASK_KEYS); // Keys will be tuples of (taskUID, param) -> paramValue // Unfortunately we need to know the priority parameter for a taskUID before we can know which available-tasks @@ -779,22 +779,22 @@ class TaskBucketImpl { for (auto& p : task.params) tr->set(space.pack(p.key), p.value); - if (values.size() > 0) { + if (!values.empty()) { tr->clear(range); - return true; + co_return true; } - return false; + co_return false; } - ASSERT(lastKey != Key()); + ASSERT(!lastKey.empty()); tr->clear(KeyRangeRef(range.begin, lastKey)); - return true; + co_return true; } - ACTOR static Future debugPrintRange(Reference tr, Subspace subspace, Key msg) { + static Future debugPrintRange(Reference tr, Subspace subspace, Key msg) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - RangeResult values = wait(tr->getRange(subspace.range(), CLIENT_KNOBS->TOO_MANY)); + RangeResult values = co_await tr->getRange(subspace.range(), CLIENT_KNOBS->TOO_MANY); TraceEvent("TaskBucketDebugPrintRange") .detail("Key", subspace.key()) .detail("Count", values.size()) @@ -807,24 +807,22 @@ class TaskBucketImpl { .detail("Key", s.key) .detail("Value", s.value); }*/ - - return Void(); } - ACTOR static Future extendTimeout(Reference tr, - Reference taskBucket, - Reference task, - UpdateParams updateParams, - Version newTimeoutVersion) { + static Future extendTimeout(Reference tr, + Reference taskBucket, + Reference task, + UpdateParams updateParams, + Version newTimeoutVersion) { taskBucket->setOptions(tr); // First make sure it's safe to keep running - wait(taskBucket->keepRunning(tr, task)); + co_await taskBucket->keepRunning(tr, task); // This is where the task definition currently exists - state Subspace oldTimeoutSpace = taskBucket->timeouts.get(task->timeoutVersion).get(task->key); + Subspace oldTimeoutSpace = taskBucket->timeouts.get(task->timeoutVersion).get(task->key); // Update the task's timeout - Version version = wait(tr->getReadVersion()); + Version version = co_await tr->getReadVersion(); if (newTimeoutVersion == invalidVersion) newTimeoutVersion = version + taskBucket->timeout; @@ -838,7 +836,7 @@ class TaskBucketImpl { } // This is where the task definition is being moved to - state Subspace newTimeoutSpace = taskBucket->timeouts.get(newTimeoutVersion).get(task->key); + Subspace newTimeoutSpace = taskBucket->timeouts.get(newTimeoutVersion).get(task->key); tr->addReadConflictRange(oldTimeoutSpace.range()); tr->addWriteConflictRange(newTimeoutSpace.range()); @@ -852,7 +850,7 @@ class TaskBucketImpl { } else { CODE_PROBE(true, "Extended a task without updating parameters"); // Otherwise, read and transplant the params from the old to new timeout spaces - RangeResult params = wait(tr->getRange(oldTimeoutSpace.range(), CLIENT_KNOBS->TOO_MANY)); + RangeResult params = co_await tr->getRange(oldTimeoutSpace.range(), CLIENT_KNOBS->TOO_MANY); for (auto& kv : params) { Tuple paramKey = oldTimeoutSpace.unpack(kv.key); tr->set(newTimeoutSpace.pack(paramKey), kv.value); @@ -861,7 +859,7 @@ class TaskBucketImpl { tr->clear(oldTimeoutSpace.range()); - return newTimeoutVersion; + co_return newTimeoutVersion; } }; @@ -877,7 +875,7 @@ TaskBucket::TaskBucket(const Subspace& subspace, timeouts(prefix.get("to"_sr)), timeout(CLIENT_KNOBS->TASKBUCKET_TIMEOUT_VERSIONS), system_access(sysAccess), priority_batch(priorityBatch), lockAware(lockAware) {} -TaskBucket::~TaskBucket() {} +TaskBucket::~TaskBucket() = default; Future TaskBucket::clear(Reference tr) { setOptions(tr); @@ -928,13 +926,13 @@ void TaskBucket::setValidationCondition(Reference task, KeyRef vKey, KeyRe task->params[Task::reservedTaskParamValidValue] = vValue; } -ACTOR static Future actorAddTask(TaskBucket* tb, - Reference tr, - Reference task, - KeyRef validationKey) { +static Future actorAddTask(TaskBucket* tb, + Reference tr, + Reference task, + KeyRef validationKey) { tb->setOptions(tr); - Optional validationValue = wait(tr->get(validationKey)); + Optional validationValue = co_await tr->get(validationKey); if (!validationValue.present()) { TraceEvent(SevError, "TaskBucketAddTaskInvalidKey") @@ -945,7 +943,7 @@ ACTOR static Future actorAddTask(TaskBucket* tb, TaskBucket::setValidationCondition(task, validationKey, validationValue.get()); - return tb->addTask(tr, task); + co_return tb->addTask(tr, task); } Future TaskBucket::addTask(Reference tr, Reference task, KeyRef validationKey) { @@ -1032,18 +1030,18 @@ Future TaskBucket::debugPrintRange(Reference tr class FutureBucketImpl { public: - ACTOR static Future isEmpty(Reference tr, Reference futureBucket) { + static Future isEmpty(Reference tr, Reference futureBucket) { futureBucket->setOptions(tr); - Key lastKey = wait(tr->getKey(lastLessOrEqual(futureBucket->prefix.pack(maxUIDKey)))); - return !futureBucket->prefix.contains(lastKey); + Key lastKey = co_await tr->getKey(lastLessOrEqual(futureBucket->prefix.pack(maxUIDKey))); + co_return !futureBucket->prefix.contains(lastKey); } }; FutureBucket::FutureBucket(const Subspace& subspace, AccessSystemKeys sysAccess, LockAware lockAware) : prefix(subspace), system_access(sysAccess), lockAware(lockAware) {} -FutureBucket::~FutureBucket() {} +FutureBucket::~FutureBucket() = default; Future FutureBucket::clear(Reference tr) { setOptions(tr); @@ -1071,29 +1069,27 @@ Reference FutureBucket::unpack(Key key) { class TaskFutureImpl { public: - ACTOR static Future join(Reference tr, - Reference taskBucket, - Reference taskFuture, - std::vector> vectorFuture) { + static Future join(Reference tr, + Reference taskBucket, + Reference taskFuture, + std::vector> vectorFuture) { taskFuture->futureBucket->setOptions(tr); - bool is_set = wait(isSet(tr, taskFuture)); + bool is_set = co_await isSet(tr, taskFuture); // A taskFuture cannot be already set for it to be joined with others. if (is_set) { - return Void(); + co_return; } tr->clear(taskFuture->blocks.pack(StringRef())); - wait(_join(tr, taskBucket, taskFuture, vectorFuture)); - - return Void(); + co_await _join(tr, taskBucket, taskFuture, vectorFuture); } - ACTOR static Future _join(Reference tr, - Reference taskBucket, - Reference taskFuture, - std::vector> vectorFuture) { + static Future _join(Reference tr, + Reference taskBucket, + Reference taskFuture, + std::vector> vectorFuture) { std::vector> onSetFutures; for (int i = 0; i < vectorFuture.size(); ++i) { Key key = StringRef(deterministicRandom()->randomUniqueID().toString()); @@ -1105,32 +1101,30 @@ class TaskFutureImpl { onSetFutures.push_back(vectorFuture[i]->onSet(tr, taskBucket, task)); } - wait(waitForAll(onSetFutures)); - - return Void(); + co_await waitForAll(onSetFutures); } - ACTOR static Future isSet(Reference tr, Reference taskFuture) { + static Future isSet(Reference tr, Reference taskFuture) { taskFuture->futureBucket->setOptions(tr); - RangeResult values = wait(tr->getRange(taskFuture->blocks.range(), 1)); - if (values.size() > 0) - return false; + RangeResult values = co_await tr->getRange(taskFuture->blocks.range(), 1); + if (!values.empty()) + co_return false; - return true; + co_return true; } - ACTOR static Future onSet(Reference tr, - Reference taskBucket, - Reference taskFuture, - Reference task) { + static Future onSet(Reference tr, + Reference taskBucket, + Reference taskFuture, + Reference task) { taskFuture->futureBucket->setOptions(tr); - bool is_set = wait(isSet(tr, taskFuture)); + bool is_set = co_await isSet(tr, taskFuture); if (is_set) { CODE_PROBE(true, "is_set == true"); - wait(performAction(tr, taskBucket, taskFuture, task)); + co_await performAction(tr, taskBucket, taskFuture, task); } else { CODE_PROBE(true, "is_set == false"); Subspace callbackSpace = @@ -1139,50 +1133,44 @@ class TaskFutureImpl { tr->set(callbackSpace.pack(v.key), v.value); } } - - return Void(); } - ACTOR static Future set(Reference tr, - Reference taskBucket, - Reference taskFuture) { + static Future set(Reference tr, + Reference taskBucket, + Reference taskFuture) { taskFuture->futureBucket->setOptions(tr); tr->clear(taskFuture->blocks.range()); - wait(performAllActions(tr, taskBucket, taskFuture)); - - return Void(); + co_await performAllActions(tr, taskBucket, taskFuture); } - ACTOR static Future performAction(Reference tr, - Reference taskBucket, - Reference taskFuture, - Reference task) { + static Future performAction(Reference tr, + Reference taskBucket, + Reference taskFuture, + Reference task) { taskFuture->futureBucket->setOptions(tr); if (task && TaskFuncBase::isValidTask(task)) { Reference taskFunc = TaskFuncBase::create(task->params[Task::reservedTaskParamKeyType]); if (taskFunc.getPtr()) { - wait(taskFunc->finish(tr, taskBucket, taskFuture->futureBucket, task)); + co_await taskFunc->finish(tr, taskBucket, taskFuture->futureBucket, task); } } - - return Void(); } - ACTOR static Future performAllActions(Reference tr, - Reference taskBucket, - Reference taskFuture) { + static Future performAllActions(Reference tr, + Reference taskBucket, + Reference taskFuture) { taskFuture->futureBucket->setOptions(tr); - RangeResult values = wait(tr->getRange(taskFuture->callbacks.range(), CLIENT_KNOBS->TOO_MANY)); + RangeResult values = co_await tr->getRange(taskFuture->callbacks.range(), CLIENT_KNOBS->TOO_MANY); tr->clear(taskFuture->callbacks.range()); std::vector> actions; - if (values.size() != 0) { - state Reference task(new Task()); + if (!values.empty()) { + Reference task(new Task()); Key lastTaskID; for (auto& s : values) { Tuple t = taskFuture->callbacks.unpack(s.key); @@ -1190,7 +1178,7 @@ class TaskFutureImpl { Key key = t.getString(1); // If we see a new task ID and the old one isn't empty then process the task accumulated so far and make // a new task - if (taskID.size() != 0 && taskID != lastTaskID) { + if (!taskID.empty() && taskID != lastTaskID) { actions.push_back(performAction(tr, taskBucket, taskFuture, task)); task = makeReference(); } @@ -1201,32 +1189,28 @@ class TaskFutureImpl { actions.push_back(performAction(tr, taskBucket, taskFuture, task)); } - wait(waitForAll(actions)); - - return Void(); + co_await waitForAll(actions); } - ACTOR static Future onSetAddTask(Reference tr, - Reference taskBucket, - Reference taskFuture, - Reference task) { + static Future onSetAddTask(Reference tr, + Reference taskBucket, + Reference taskFuture, + Reference task) { taskFuture->futureBucket->setOptions(tr); task->params[Task::reservedTaskParamKeyAddTask] = task->params[Task::reservedTaskParamKeyType]; task->params[Task::reservedTaskParamKeyType] = "AddTask"_sr; - wait(onSet(tr, taskBucket, taskFuture, task)); - - return Void(); + co_await onSet(tr, taskBucket, taskFuture, task); } - ACTOR static Future onSetAddTask(Reference tr, - Reference taskBucket, - Reference taskFuture, - Reference task, - KeyRef validationKey) { + static Future onSetAddTask(Reference tr, + Reference taskBucket, + Reference taskFuture, + Reference task, + KeyRef validationKey) { taskFuture->futureBucket->setOptions(tr); - Optional validationValue = wait(tr->get(validationKey)); + Optional validationValue = co_await tr->get(validationKey); if (!validationValue.present()) { TraceEvent(SevError, "TaskBucketOnSetAddTaskInvalidKey") @@ -1238,9 +1222,7 @@ class TaskFutureImpl { task->params[Task::reservedTaskParamValidKey] = validationKey; task->params[Task::reservedTaskParamValidValue] = validationValue.get(); - wait(onSetAddTask(tr, taskBucket, taskFuture, task)); - - return Void(); + co_await onSetAddTask(tr, taskBucket, taskFuture, task); } static Future onSetAddTask(Reference tr, @@ -1257,25 +1239,25 @@ class TaskFutureImpl { return onSetAddTask(tr, taskBucket, taskFuture, task); } - ACTOR static Future> joinedFuture(Reference tr, - Reference taskBucket, - Reference taskFuture) { + static Future> joinedFuture(Reference tr, + Reference taskBucket, + Reference taskFuture) { taskFuture->futureBucket->setOptions(tr); std::vector> vectorFuture; // the next line means generate a new task future with different key, // but share the same prefix of futureBucket with the input taskFuture - state Reference future = taskFuture->futureBucket->future(tr); + Reference future = taskFuture->futureBucket->future(tr); vectorFuture.push_back(future); - wait(join(tr, taskBucket, taskFuture, vectorFuture)); - return future; + co_await join(tr, taskBucket, taskFuture, vectorFuture); + co_return future; } }; -TaskFuture::TaskFuture() {} +TaskFuture::TaskFuture() = default; TaskFuture::TaskFuture(const Reference bucket, Key k) : futureBucket(bucket), key(k) { - if (k.size() == 0) { + if (k.empty()) { key = deterministicRandom()->randomUniqueID().toString(); } @@ -1284,7 +1266,7 @@ TaskFuture::TaskFuture(const Reference bucket, Key k) : futureBuck callbacks = prefix.get("cb"_sr); } -TaskFuture::~TaskFuture() {} +TaskFuture::~TaskFuture() = default; void TaskFuture::addBlock(Reference tr, StringRef block_id) { tr->set(blocks.pack(block_id), ""_sr); @@ -1341,11 +1323,11 @@ Future> TaskFuture::joinedFuture(Reference::addRef(this)); } -ACTOR Future getCompletionKey(TaskCompletionKey* self, Future> f) { - Reference taskFuture = wait(f); +Future getCompletionKey(TaskCompletionKey* self, Future> f) { + Reference taskFuture = co_await f; self->joinFuture.clear(); self->key = taskFuture->key; - return self->key.get(); + co_return self->key.get(); } Future TaskCompletionKey::get(Reference tr, Reference taskBucket) { diff --git a/fdbclient/TestKnobCollection.cpp b/fdbclient/TestKnobCollection.cpp deleted file mode 100644 index 7e6f782fd0e..00000000000 --- a/fdbclient/TestKnobCollection.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * TestKnobCollection.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. - */ - -#include "fdbclient/TestKnobCollection.h" - -TestKnobCollection::TestKnobCollection(Randomize randomize, IsSimulated isSimulated) - : serverKnobCollection(randomize, isSimulated) { - initialize(randomize, isSimulated); -} - -void TestKnobCollection::initialize(Randomize randomize, IsSimulated isSimulated) { - serverKnobCollection.initialize(randomize, isSimulated); - testKnobs.initialize(); -} - -void TestKnobCollection::reset(Randomize randomize, IsSimulated isSimulated) { - serverKnobCollection.reset(randomize, isSimulated); - testKnobs.reset(); -} - -void TestKnobCollection::clearTestKnobs() { - testKnobs.reset(); -} - -Optional TestKnobCollection::tryParseKnobValue(std::string const& knobName, - std::string const& knobValue) const { - auto result = serverKnobCollection.tryParseKnobValue(knobName, knobValue); - if (result.present()) { - return result; - } - auto parsedKnobValue = testKnobs.parseKnobValue(knobName, knobValue); - if (!std::holds_alternative(parsedKnobValue)) { - return KnobValueRef::create(parsedKnobValue); - } - return {}; -} - -bool TestKnobCollection::trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) { - return serverKnobCollection.trySetKnob(knobName, knobValue) || knobValue.visitSetKnob(knobName, testKnobs); -} - -#define init(knob, value) initKnob(knob, value, #knob) - -TestKnobs::TestKnobs() { - initialize(); -} - -void TestKnobs::initialize() { - init(TEST_LONG, 0); - init(TEST_INT, 0); - init(TEST_DOUBLE, 0.0); - init(TEST_BOOL, false); - init(TEST_STRING, ""); -} - -bool TestKnobs::operator==(TestKnobs const& rhs) const { - return (TEST_LONG == rhs.TEST_LONG) && (TEST_INT == rhs.TEST_INT) && (TEST_DOUBLE == rhs.TEST_DOUBLE) && - (TEST_BOOL == rhs.TEST_BOOL) && (TEST_STRING == rhs.TEST_STRING); -} - -bool TestKnobs::operator!=(TestKnobs const& rhs) const { - return !(*this == rhs); -} diff --git a/fdbclient/ThreadSafeTransaction.cpp b/fdbclient/ThreadSafeTransaction.cpp index 4d79566f4c9..5e0a8810202 100644 --- a/fdbclient/ThreadSafeTransaction.cpp +++ b/fdbclient/ThreadSafeTransaction.cpp @@ -24,7 +24,7 @@ #include "fdbclient/ThreadSafeTransaction.h" #include "fdbclient/DatabaseContext.h" #include "fdbclient/versions.h" -#include "fdbclient/GenericManagementAPI.actor.h" +#include "fdbclient/GenericManagementAPI.h" #include "fdbclient/NativeAPI.actor.h" #include "flow/Arena.h" #include "flow/ProtocolVersion.h" @@ -46,12 +46,12 @@ ThreadFuture> ThreadSafeDatabase::createFromExistingDatabas db->checkDeferredError(); DatabaseContext* cx = db.getPtr(); cx->addref(); - return Future>(Reference(new ThreadSafeDatabase(cx))); + return Future>(makeReference(cx)); }); } Reference ThreadSafeDatabase::createTransaction() { - return Reference(new ThreadSafeTransaction(db)); + return makeReference(db); } void ThreadSafeDatabase::setOption(FDBDatabaseOptions::Option option, Optional value) { @@ -142,8 +142,8 @@ ThreadSafeDatabase::ThreadSafeDatabase(ConnectionRecordType connectionRecordType Reference connectionRecord = connectionRecordType == ConnectionRecordType::FILE ? Reference(ClusterConnectionFile::openOrDefault(connectionRecordString)) - : Reference( - new ClusterConnectionMemoryRecord(ClusterConnectionString(connectionRecordString))); + : Reference(makeReference( + ClusterConnectionString(connectionRecordString))); Database::createDatabase(connectionRecord, apiVersion, IsInternal::False, LocalityData(), db).extractPtr(); } catch (Error& e) { @@ -592,13 +592,13 @@ void ThreadSafeApi::stopNetwork() { } Reference ThreadSafeApi::createDatabase(const char* clusterFilePath) { - return Reference( - new ThreadSafeDatabase(ThreadSafeDatabase::ConnectionRecordType::FILE, clusterFilePath, apiVersion.version())); + return makeReference( + ThreadSafeDatabase::ConnectionRecordType::FILE, clusterFilePath, apiVersion.version()); } Reference ThreadSafeApi::createDatabaseFromConnectionString(const char* connectionString) { - return Reference(new ThreadSafeDatabase( - ThreadSafeDatabase::ConnectionRecordType::CONNECTION_STRING, connectionString, apiVersion.version())); + return makeReference( + ThreadSafeDatabase::ConnectionRecordType::CONNECTION_STRING, connectionString, apiVersion.version()); } void ThreadSafeApi::addNetworkThreadCompletionHook(void (*hook)(void*), void* hookParameter) { diff --git a/fdbclient/Tracing.actor.cpp b/fdbclient/Tracing.cpp similarity index 91% rename from fdbclient/Tracing.actor.cpp rename to fdbclient/Tracing.cpp index 4343e044fad..9ba14e6135d 100644 --- a/fdbclient/Tracing.actor.cpp +++ b/fdbclient/Tracing.cpp @@ -1,5 +1,5 @@ /* - * Tracing.actor.cpp + * Tracing.cpp * * This source file is part of the FoundationDB open source project * @@ -24,18 +24,15 @@ #include "flow/UnitTest.h" #include "flow/Knobs.h" #include "flow/IConnection.h" -#include "fdbclient/IKnobCollection.h" #include "flow/network.h" #include #include #include #include "flow/IUDPSocket.h" -#include "flow/actorcompiler.h" // has to be last include +#include "flow/CoroUtils.h" -#ifdef NO_INTELLISENSE namespace { -#endif // Initial size of buffer used to store serialized traces. Buffer will be // resized when necessary. @@ -83,23 +80,27 @@ struct LogfileTracer : ITracer { }; // A server listening for UDP trace messages, run only in simulation. -ACTOR Future simulationStartServer() { +Future simulationStartServer() { + // Preserve the actor scheduling boundary so UDP listener setup does not run synchronously + // from the first sampled Span destructor after coroutine conversion. + co_await delay(0); + // We're going to force the address to be loopback regardless of FLOW_KNOBS->TRACING_UDP_LISTENER_ADDR // because we're in simulation testing mode. TraceEvent(SevInfo, "UDPServerStarted") .detail("Address", "127.0.0.1") .detail("Port", FLOW_KNOBS->TRACING_UDP_LISTENER_PORT); - state NetworkAddress localAddress = + NetworkAddress localAddress = NetworkAddress::parse("127.0.0.1:" + std::to_string(FLOW_KNOBS->TRACING_UDP_LISTENER_PORT)); - state Reference serverSocket = wait(INetworkConnections::net()->createUDPSocket(localAddress)); + Reference serverSocket = co_await INetworkConnections::net()->createUDPSocket(localAddress); serverSocket->bind(localAddress); - state Standalone packetString = makeString(IUDPSocket::MAX_PACKET_SIZE); - state uint8_t* packet = mutateString(packetString); + auto packet = std::make_unique(IUDPSocket::MAX_PACKET_SIZE); - loop { - int size = wait(serverSocket->receive(packet, packet + IUDPSocket::MAX_PACKET_SIZE)); - auto message = packetString.substr(0, size); + while (true) { + uint8_t* packetBegin = packet.get(); + int size = co_await serverSocket->receive(packetBegin, packetBegin + IUDPSocket::MAX_PACKET_SIZE); + auto message = StringRef(packetBegin, size); // For now, just check the first byte in the message matches. Data is // currently written as an array, so first byte should match msgpack @@ -112,10 +113,10 @@ ACTOR Future simulationStartServer() { /* // Runs on an interval, printing debug information and performing other // connection tasks. -ACTOR Future traceLog(int* pendingMessages, bool* sendError) { - state bool sendErrorReset = false; +Future traceLog(int* pendingMessages, bool* sendError) { + bool sendErrorReset = false; - loop { + while (true) { TraceEvent("TracingSpanQueueSize").detail("PendingMessages", *pendingMessages); // Wait at least one full loop before attempting to send messages @@ -127,7 +128,7 @@ ACTOR Future traceLog(int* pendingMessages, bool* sendError) { sendErrorReset = true; } - wait(delay(kQueueSizeLogInterval)); + co_await delay(kQueueSizeLogInterval); } } */ @@ -163,7 +164,8 @@ struct UDPTracer : public ITracer { // Writes the given vector of linked SpanContext's to the request. If the vector is // empty, the request is not modified. inline void serialize_vector(const SmallVectorRef& vec, MsgpackBuffer& buf) { - int size = vec.size(); + // Each SpanContext produces 3 MsgPack elements: traceID.first, traceID.second, spanID + int size = vec.size() * 3; if (size <= 15) { buf.write_byte(static_cast(size) | 0b10010000); } else if (size <= 65535) { @@ -185,7 +187,8 @@ struct UDPTracer : public ITracer { // Writes the given vector of linked SpanEventRef's to the request. If the vector is // empty, the request is not modified. inline void serialize_vector(const SmallVectorRef& vec, MsgpackBuffer& buf) { - int size = vec.size(); + // Each SpanEventRef produces 3 MsgPack elements: name, time, attributes + int size = vec.size() * 3; if (size <= 15) { buf.write_byte(static_cast(size) | 0b10010000); } else if (size <= 65535) { @@ -223,10 +226,10 @@ struct UDPTracer : public ITracer { }; #ifndef WIN32 -ACTOR Future fastTraceLogger(int* unreadyMessages, int* failedMessages, int* totalMessages, bool* sendError) { - state bool sendErrorReset = false; +Future fastTraceLogger(int* unreadyMessages, int* failedMessages, int* totalMessages, bool* sendError) { + bool sendErrorReset = false; - loop { + while (true) { TraceEvent("TracingSpanStats") .detail("UnreadyMessages", *unreadyMessages) .detail("FailedMessages", *failedMessages) @@ -240,7 +243,7 @@ ACTOR Future fastTraceLogger(int* unreadyMessages, int* failedMessages, in sendErrorReset = true; } - wait(delay(kQueueSizeLogInterval)); + co_await delay(kQueueSizeLogInterval); } } @@ -264,10 +267,20 @@ struct FastUDPTracer : public UDPTracer { // Force loopback when in simulation mode destAddr = "127.0.0.1"; } - NetworkAddress destAddress = - NetworkAddress::parse(destAddr + ":" + std::to_string(FLOW_KNOBS->TRACING_UDP_LISTENER_PORT)); - - socket_ = INetworkConnections::net()->createUDPSocket(destAddress); + // NetworkAddress::parse() only accepts literal IP addresses, not hostnames. + // If the knob is set to a DNS name, parse() throws connection_string_invalid. + // Catch it here and disable tracing gracefully instead of crashing the process. + try { + NetworkAddress destAddress = + NetworkAddress::parse(destAddr + ":" + std::to_string(FLOW_KNOBS->TRACING_UDP_LISTENER_PORT)); + socket_ = INetworkConnections::net()->createUDPSocket(destAddress); + } catch (Error& e) { + TraceEvent(SevWarnAlways, "TracingInvalidListenerAddress") + .error(e) + .detail("Address", destAddr) + .detail("Hint", "TRACING_UDP_LISTENER_ADDR must be a literal IP address, not a hostname"); + send_error_ = true; // disable tracing for this process + } }); if (size == 0) { @@ -275,16 +288,15 @@ struct FastUDPTracer : public UDPTracer { } ++total_messages_; + if (send_error_) { + return; + } if (!socket_.isReady()) { ++unready_socket_messages_; return; } else if (socket_fd_ == -1) { socket_fd_ = socket_.get()->native_handle(); } - - if (send_error_) { - return; - } } void write() { @@ -322,9 +334,7 @@ struct FastUDPTracer : public UDPTracer { ITracer* g_tracer = new NoopTracer(); -#ifdef NO_INTELLISENSE } // namespace -#endif void openTracer(TracerType type) { if (g_tracer->type() == type) { @@ -349,7 +359,7 @@ void openTracer(TracerType type) { } } -ITracer::~ITracer() {} +ITracer::~ITracer() = default; Span& Span::operator=(Span&& o) { if (begin > 0.0 && context.isSampled()) { @@ -607,7 +617,7 @@ TEST_CASE("/flow/Tracing/FastUDPMessagePackEncoding") { ASSERT(data[70] == 0xcc); ASSERT(data[71] == static_cast(SpanStatus::OK)); // Linked SpanContext - ASSERT(data[72] == 0b10010001); + ASSERT(data[72] == 0b10010011); // 3 elements: traceID.first, traceID.second, spanID ASSERT(data[73] == 0xcf); ASSERT(swapUint64BE(&data[74]) == 200); ASSERT(data[82] == 0xcf); @@ -646,7 +656,7 @@ TEST_CASE("/flow/Tracing/FastUDPMessagePackEncoding") { ASSERT(data[72] == 0xcc); ASSERT(data[73] == static_cast(SpanStatus::OK)); // Linked SpanContext - ASSERT(data[74] == 0b10010001); + ASSERT(data[74] == 0b10010011); // 3 elements: traceID.first, traceID.second, spanID ASSERT(data[75] == 0xcf); ASSERT(swapUint64BE(&data[76]) == 300); ASSERT(data[84] == 0xcf); @@ -654,7 +664,7 @@ TEST_CASE("/flow/Tracing/FastUDPMessagePackEncoding") { ASSERT(data[93] == 0xcf); ASSERT(swapUint64BE(&data[94]) == 400); // Events - ASSERT(data[102] == 0b10010001); // empty + ASSERT(data[102] == 0b10010011); // 3 elements: name, time, attributes ASSERT(readMPString(&data[103]) == "event1"); ASSERT(data[110] == 0xcb); ASSERT(swapDoubleBE(&data[111]) == 100.101); diff --git a/fdbclient/VersionedMap.cpp b/fdbclient/VersionedMap.cpp deleted file mode 100644 index 7507ee30581..00000000000 --- a/fdbclient/VersionedMap.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * VersionedMap.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. - */ - -#include "fdbclient/VersionedMap.h" -#include "flow/TreeBenchmark.h" -#include "flow/UnitTest.h" - -template -struct VersionedMapHarness { - using map = VersionedMap; - using key_type = K; - - struct result { - typename map::iterator it; - - result(typename map::iterator it) : it(it) {} - - result& operator++() { - ++it; - return *this; - } - - const K& operator*() const { return it.key(); } - - const K& operator->() const { return it.key(); } - - bool operator==(result const& k) const { return it == k.it; } - bool operator!=(result const& k) const { return !(*this == k); } - }; - - map s; - - void insert(K const& k) { s.insert(k, 1); } - result find(K const& k) const { return result(s.atLatest().find(k)); } - result not_found() const { return result(s.atLatest().end()); } - result begin() const { return result(s.atLatest().begin()); } - result end() const { return result(s.atLatest().end()); } - result lower_bound(K const& k) const { return result(s.atLatest().lower_bound(k)); } - result upper_bound(K const& k) const { return result(s.atLatest().upper_bound(k)); } - void erase(K const& k) { s.erase(k); } -}; - -TEST_CASE("performance/map/int/VersionedMap") { - VersionedMapHarness tree; - - treeBenchmark(tree, *randomInt); - - return Void(); -} - -TEST_CASE("performance/map/StringRef/VersionedMap") { - Arena arena; - VersionedMapHarness tree; - - treeBenchmark(tree, [&arena]() { return randomStr(arena); }); - - return Void(); -} - -void forceLinkVersionedMapTests() {} diff --git a/fdbclient/WriteMap.cpp b/fdbclient/WriteMap.cpp index c36258cdc70..ca0ad365f2e 100644 --- a/fdbclient/WriteMap.cpp +++ b/fdbclient/WriteMap.cpp @@ -104,22 +104,26 @@ void WriteMap::mutate(KeyRef key, MutationRef::Type operation, ValueRef param, b it.tree.clear(); OperationStack op(RYWMutation(Optional(), MutationRef::SetValue)); coalesceOver(op, RYWMutation(param, operation), *arena); - PTreeImpl::insert( - writes, - ver, - WriteMapEntry( - key, std::move(op), true, following_conflict, is_conflict, following_unreadable, is_unreadable)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(key, + std::move(op), + FollowingKeysCleared::True, + FollowingKeysConflict(following_conflict), + IsConflict(is_conflict), + FollowingKeysUnreadable(following_unreadable), + IsUnreadable(is_unreadable))); } else { it.tree.clear(); PTreeImpl::insert(writes, ver, WriteMapEntry(key, OperationStack(RYWMutation(param, operation)), - is_cleared, - following_conflict, - is_conflict, - following_unreadable, - is_unreadable)); + FollowingKeysCleared(is_cleared), + FollowingKeysConflict(following_conflict), + IsConflict(is_conflict), + FollowingKeysUnreadable(following_unreadable), + IsUnreadable(is_unreadable))); } } else { if (!it.is_unreadable() && @@ -130,11 +134,11 @@ void WriteMap::mutate(KeyRef key, MutationRef::Type operation, ValueRef param, b ver, WriteMapEntry(key, OperationStack(RYWMutation(param, operation)), - is_cleared, - following_conflict, - is_conflict, - following_unreadable, - is_unreadable)); + FollowingKeysCleared(is_cleared), + FollowingKeysConflict(following_conflict), + IsConflict(is_conflict), + FollowingKeysUnreadable(following_unreadable), + IsUnreadable(is_unreadable))); } else { WriteMapEntry e(it.entry()); e.is_conflict = is_conflict; @@ -192,14 +196,26 @@ void WriteMap::clear(KeyRangeRef keys, bool addConflict) { ExtStringRef(keys.end, end_coalesce_clear ? 1 : 0)); if (insert_begin) - PTreeImpl::insert(writes, ver, WriteMapEntry(keys.begin, OperationStack(), true, true, true, false, false)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(keys.begin, + OperationStack(), + FollowingKeysCleared::True, + FollowingKeysConflict::True, + IsConflict::True, + FollowingKeysUnreadable::False, + IsUnreadable::False)); if (insert_end) - PTreeImpl::insert( - writes, - ver, - WriteMapEntry( - keys.end, OperationStack(), end_cleared, end_conflict, end_conflict, end_unreadable, end_unreadable)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(keys.end, + OperationStack(), + FollowingKeysCleared(end_cleared), + FollowingKeysConflict(end_conflict), + IsConflict(end_conflict), + FollowingKeysUnreadable(end_unreadable), + IsUnreadable(end_unreadable))); } void WriteMap::addUnmodifiedAndUnreadableRange(KeyRangeRef keys) { @@ -231,14 +247,26 @@ void WriteMap::addUnmodifiedAndUnreadableRange(KeyRangeRef keys) { ExtStringRef(keys.end, end_coalesce_unmodified ? 1 : 0)); if (insert_begin) - PTreeImpl::insert(writes, ver, WriteMapEntry(keys.begin, OperationStack(), false, false, false, true, true)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(keys.begin, + OperationStack(), + FollowingKeysCleared::False, + FollowingKeysConflict::False, + IsConflict::False, + FollowingKeysUnreadable::True, + IsUnreadable::True)); if (insert_end) - PTreeImpl::insert( - writes, - ver, - WriteMapEntry( - keys.end, OperationStack(), end_cleared, end_conflict, end_conflict, end_unreadable, end_unreadable)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(keys.end, + OperationStack(), + FollowingKeysCleared(end_cleared), + FollowingKeysConflict(end_conflict), + IsConflict(end_conflict), + FollowingKeysUnreadable(end_unreadable), + IsUnreadable(end_unreadable))); } void WriteMap::addConflictRange(KeyRangeRef keys) { @@ -256,11 +284,11 @@ void WriteMap::addConflictRange(KeyRangeRef keys) { } insertions.push_back(WriteMapEntry(keys.begin, it.is_operation() ? OperationStack(it.op()) : OperationStack(), - it.entry().following_keys_cleared, - true, - true, - it.entry().following_keys_unreadable, - it.entry().is_unreadable)); + FollowingKeysCleared(it.entry().following_keys_cleared), + FollowingKeysConflict::True, + IsConflict::True, + FollowingKeysUnreadable(it.entry().following_keys_unreadable), + IsUnreadable(it.entry().is_unreadable))); } while (it.endKey() < keys.end) { @@ -282,8 +310,13 @@ void WriteMap::addConflictRange(KeyRangeRef keys) { ++it; if (!it.keyAtBegin() || it.beginKey() != keys.end) { - insertions.push_back( - WriteMapEntry(keys.end, OperationStack(), isCleared, false, false, followingUnreadable, isUnreadable)); + insertions.push_back(WriteMapEntry(keys.end, + OperationStack(), + FollowingKeysCleared(isCleared), + FollowingKeysConflict::False, + IsConflict::False, + FollowingKeysUnreadable(followingUnreadable), + IsUnreadable(isUnreadable))); } } @@ -577,20 +610,28 @@ void WriteMap::clearNoConflict(KeyRangeRef keys) { ExtStringRef(keys.end, end_coalesce_clear ? 1 : 0)); for (int i = 0; i < conflict_ranges.size(); i++) { - PTreeImpl::insert( - writes, - ver, - WriteMapEntry( - conflict_ranges[i].toArenaOrRef(*arena), OperationStack(), true, conflicted, conflicted, false, false)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(conflict_ranges[i].toArenaOrRef(*arena), + OperationStack(), + FollowingKeysCleared::True, + FollowingKeysConflict(conflicted), + IsConflict(conflicted), + FollowingKeysUnreadable::False, + IsUnreadable::False)); conflicted = !conflicted; } ASSERT(conflicted != lastConflicted); if (insert_end) - PTreeImpl::insert( - writes, - ver, - WriteMapEntry( - keys.end, OperationStack(), end_cleared, end_conflict, end_conflict, end_unreadable, end_unreadable)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(keys.end, + OperationStack(), + FollowingKeysCleared(end_cleared), + FollowingKeysConflict(end_conflict), + IsConflict(end_conflict), + FollowingKeysUnreadable(end_unreadable), + IsUnreadable(end_unreadable))); } diff --git a/flowbench/BenchIdempotencyIds.cpp b/fdbclient/bench/BenchIdempotencyIds.cpp similarity index 100% rename from flowbench/BenchIdempotencyIds.cpp rename to fdbclient/bench/BenchIdempotencyIds.cpp diff --git a/flowbench/BenchIterate.cpp b/fdbclient/bench/BenchIterate.cpp similarity index 98% rename from flowbench/BenchIterate.cpp rename to fdbclient/bench/BenchIterate.cpp index 1f6103c7ab6..15a461e2bbf 100644 --- a/flowbench/BenchIterate.cpp +++ b/fdbclient/bench/BenchIterate.cpp @@ -23,9 +23,9 @@ #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/MutationList.h" +#include "GlobalData.h" #include "flow/Arena.h" #include "flow/FastAlloc.h" -#include "flowbench/GlobalData.h" void populate(Standalone>& mutations, size_t items, size_t size, KeyRef key, ValueRef value) { mutations = Standalone>{}; diff --git a/fdbserver/include/fdbserver/ServerDBInfo.h b/fdbclient/bench/BenchMain.cpp similarity index 82% rename from fdbserver/include/fdbserver/ServerDBInfo.h rename to fdbclient/bench/BenchMain.cpp index 4871485ff28..1502e9db3ed 100644 --- a/fdbserver/include/fdbserver/ServerDBInfo.h +++ b/fdbclient/bench/BenchMain.cpp @@ -1,5 +1,5 @@ /* - * ServerDBInfo.h + * BenchMain.cpp * * This source file is part of the FoundationDB open source project * @@ -18,10 +18,8 @@ * limitations under the License. */ -#ifndef FDBSERVER_SERVERDBINFO_H -#define FDBSERVER_SERVERDBINFO_H -#pragma once +#include "flow/BenchMain.h" -#include "fdbserver/ServerDBInfo.actor.h" - -#endif +int main(int argc, char** argv) { + return runBenchmarks(argc, argv); +} diff --git a/flowbench/BenchMetadataCheck.cpp b/fdbclient/bench/BenchMetadataCheck.cpp similarity index 100% rename from flowbench/BenchMetadataCheck.cpp rename to fdbclient/bench/BenchMetadataCheck.cpp diff --git a/flowbench/BenchPopulate.cpp b/fdbclient/bench/BenchPopulate.cpp similarity index 98% rename from flowbench/BenchPopulate.cpp rename to fdbclient/bench/BenchPopulate.cpp index 3b9b3c6eadf..3298359539c 100644 --- a/flowbench/BenchPopulate.cpp +++ b/fdbclient/bench/BenchPopulate.cpp @@ -22,9 +22,9 @@ #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" +#include "GlobalData.h" #include "flow/Arena.h" #include "flow/FastAlloc.h" -#include "flowbench/GlobalData.h" static constexpr bool EMPLACE_BACK = true; static constexpr bool PUSH_BACK = false; diff --git a/flowbench/BenchTempTagMessages.cpp b/fdbclient/bench/BenchTempTagMessages.cpp similarity index 100% rename from flowbench/BenchTempTagMessages.cpp rename to fdbclient/bench/BenchTempTagMessages.cpp diff --git a/flowbench/BenchVersionVector.cpp b/fdbclient/bench/BenchVersionVector.cpp similarity index 100% rename from flowbench/BenchVersionVector.cpp rename to fdbclient/bench/BenchVersionVector.cpp diff --git a/flowbench/BenchVersionVectorSerialization.cpp b/fdbclient/bench/BenchVersionVectorSerialization.cpp similarity index 100% rename from flowbench/BenchVersionVectorSerialization.cpp rename to fdbclient/bench/BenchVersionVectorSerialization.cpp diff --git a/fdbclient/bench/BenchVersionedMap.cpp b/fdbclient/bench/BenchVersionedMap.cpp new file mode 100644 index 00000000000..064ee56bc26 --- /dev/null +++ b/fdbclient/bench/BenchVersionedMap.cpp @@ -0,0 +1,564 @@ +/* + * BenchVersionedMap.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. + */ + +#include "benchmark/benchmark.h" + +#include "fdbclient/VersionedMap.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uint32_t versionedMapBenchmarkSeed = 12345; + +void resetVersionedMapBenchmarkRandom() { + // VersionedMap priorities come from deterministicRandom(); keep benchmark tree shape stable across iterations. + setThreadLocalDeterministicRandomSeed(versionedMapBenchmarkSeed); +} + +template +struct VersionedMapHarness { + using map = VersionedMap; + using key_type = K; + + struct result { + typename map::iterator it; + + explicit result(typename map::iterator it) : it(it) {} + + result& operator++() { + ++it; + return *this; + } + + const K& operator*() const { return it.key(); } + + const K& operator->() const { return it.key(); } + + bool operator==(result const& k) const { return it == k.it; } + bool operator!=(result const& k) const { return !(*this == k); } + }; + + map s; + + void insert(K const& k) { s.insert(k, 1); } + result find(K const& k) const { return result(s.latestFind(k)); } + result not_found() const { return result(s.latestEnd()); } + result begin() const { return result(s.latestBegin()); } + result end() const { return result(s.latestEnd()); } + result lower_bound(K const& k) const { return result(s.latestLowerBound(k)); } + result upper_bound(K const& k) const { return result(s.latestUpperBound(k)); } + result last_less_or_equal(K const& k) const { return result(s.latestLastLessOrEqual(k)); } + result last_less(K const& k) const { return result(s.latestLastLess(k)); } + void erase(K const& k) { s.erase(k); } +}; + +template +void sortUnique(std::vector& keys) { + std::sort(keys.begin(), keys.end()); + keys.erase(std::unique(keys.begin(), keys.end()), keys.end()); +} + +struct IntFixture { + using key_type = int; + + std::vector keys; + VersionedMapHarness tree; + + static std::unique_ptr create(int64_t keyCount, bool populateTree, bool uniqueSortedKeys = false) { + auto fixture = std::make_unique(); + resetVersionedMapBenchmarkRandom(); + fixture->keys.reserve(keyCount); + std::mt19937_64 rng(0x4d595df4d0f33173ULL); + std::uniform_int_distribution dist(0, INT_MAX); + for (int64_t i = 0; i < keyCount; ++i) { + fixture->keys.push_back(dist(rng)); + } + if (populateTree) { + for (const auto& key : fixture->keys) { + fixture->tree.insert(key); + } + } + if (uniqueSortedKeys) { + // Duplicate inserts update the existing tree node, so the tree and key list both contain the same unique + // keys after this. Sorting after population preserves the random insertion stream used to build the tree. + sortUnique(fixture->keys); + } + return fixture; + } +}; + +struct StringRefFixture { + using key_type = StringRef; + + Arena arena; + std::vector keys; + VersionedMapHarness tree; + + static std::unique_ptr create(int64_t keyCount, + bool populateTree, + bool uniqueSortedKeys = false) { + auto fixture = std::make_unique(); + resetVersionedMapBenchmarkRandom(); + fixture->keys.reserve(keyCount); + std::mt19937_64 rng(0x46a2f9b1d8c3e57bULL); + constexpr char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + constexpr int keySize = 100; + std::string key; + key.resize(keySize); + for (int64_t i = 0; i < keyCount; ++i) { + for (int j = 0; j < keySize; ++j) { + key[j] = alphabet[rng() % (sizeof(alphabet) - 1)]; + } + fixture->keys.push_back(StringRef(fixture->arena, key)); + } + if (populateTree) { + for (const auto& keyRef : fixture->keys) { + fixture->tree.insert(keyRef); + } + } + if (uniqueSortedKeys) { + // Duplicate inserts update the existing tree node, so the tree and key list both contain the same unique + // keys after this. Sorting after population preserves the random insertion stream used to build the tree. + sortUnique(fixture->keys); + } + return fixture; + } +}; + +template +static void bench_versioned_map_insert(benchmark::State& state) { + const int64_t keyCount = state.range(0); + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, false); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + fixture->tree.insert(key); + } + benchmark::DoNotOptimize(&fixture->tree); + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(keyCount * state.iterations()); +} + +template +static void bench_versioned_map_find(benchmark::State& state) { + const int64_t keyCount = state.range(0); + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + auto found = fixture->tree.find(key); + ASSERT(found != fixture->tree.not_found()); + benchmark::DoNotOptimize(found); + } + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(keyCount * state.iterations()); +} + +template +static void bench_versioned_map_lower_bound(benchmark::State& state) { + const int64_t keyCount = state.range(0); + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + auto found = fixture->tree.lower_bound(key); + ASSERT(found != fixture->tree.not_found()); + benchmark::DoNotOptimize(found); + } + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(keyCount * state.iterations()); +} + +template +static void bench_versioned_map_upper_bound(benchmark::State& state) { + const int64_t keyCount = state.range(0); + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + auto found = fixture->tree.upper_bound(key); + benchmark::DoNotOptimize(found); + } + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(keyCount * state.iterations()); +} + +template +static void bench_versioned_map_last_less_or_equal(benchmark::State& state) { + const int64_t keyCount = state.range(0); + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + auto found = fixture->tree.last_less_or_equal(key); + ASSERT(found != fixture->tree.not_found()); + ASSERT(key == *found); + benchmark::DoNotOptimize(found); + } + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(keyCount * state.iterations()); +} + +template +static void bench_versioned_map_last_less(benchmark::State& state) { + const int64_t keyCount = state.range(0); + int64_t foundCount = 0; + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true, true); + state.ResumeTiming(); + + for (int64_t i = 1; i < fixture->keys.size(); ++i) { + auto found = fixture->tree.last_less(fixture->keys[i]); + ASSERT(found != fixture->tree.not_found()); + ASSERT(fixture->keys[i - 1] == *found); + benchmark::DoNotOptimize(found); + } + foundCount += fixture->keys.size() - 1; + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(foundCount); +} + +template +static void bench_versioned_map_scan(benchmark::State& state) { + const int64_t keyCount = state.range(0); + int64_t scanned = 0; + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true, true); + state.ResumeTiming(); + + auto iter = fixture->tree.lower_bound(fixture->keys.front()); + for (const auto& key : fixture->keys) { + ASSERT(iter != fixture->tree.end()); + ASSERT(key == *iter); + ++iter; + } + scanned += fixture->keys.size(); + ASSERT(iter == fixture->tree.end()); + benchmark::DoNotOptimize(iter); + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(scanned); +} + +template +static void bench_versioned_map_find_sorted(benchmark::State& state) { + const int64_t keyCount = state.range(0); + int64_t foundCount = 0; + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true, true); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + auto found = fixture->tree.find(key); + ASSERT(found != fixture->tree.end()); + benchmark::DoNotOptimize(found); + } + foundCount += fixture->keys.size(); + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(foundCount); +} + +template +static void bench_versioned_map_erase(benchmark::State& state) { + const int64_t keyCount = state.range(0); + int64_t erased = 0; + for (auto _ : state) { + state.PauseTiming(); + auto fixture = Fixture::create(keyCount, true, true); + std::mt19937_64 rng(0x91e10da5c79e7b1dULL); + std::shuffle(fixture->keys.begin(), fixture->keys.end(), rng); + state.ResumeTiming(); + + for (const auto& key : fixture->keys) { + fixture->tree.erase(key); + } + erased += fixture->keys.size(); + ASSERT(fixture->tree.begin() == fixture->tree.end()); + benchmark::DoNotOptimize(&fixture->tree); + + state.PauseTiming(); + fixture.reset(); + state.ResumeTiming(); + } + state.SetItemsProcessed(erased); +} + +static void setKOpsCounter(benchmark::State& state, const char* name, int64_t opCount, double elapsed) { + state.counters[name] = elapsed == 0 ? 0 : (opCount / 1000.0) / elapsed; +} + +static void bench_versioned_map_multiversion_int(benchmark::State& state) { + constexpr int initialKeys = 20000; + constexpr int versions = 50000; + constexpr int retainedVersions = 5000; + constexpr int forgetEvery = 1000; + constexpr int scanWidth = 8; + + double populateElapsed = 0; + double createElapsed = 0; + double latestInsertElapsed = 0; + double latestEraseElapsed = 0; + double historicalFindElapsed = 0; + double historicalLowerBoundElapsed = 0; + double historicalScanElapsed = 0; + double compactForgetElapsed = 0; + + int64_t populateOps = 0; + int64_t createOps = 0; + int64_t latestInsertOps = 0; + int64_t latestEraseOps = 0; + int64_t historicalFindOps = 0; + int64_t historicalLowerBoundOps = 0; + int64_t historicalScanOps = 0; + int64_t compactForgetOps = 0; + + for (auto _ : state) { + resetVersionedMapBenchmarkRandom(); + double totalStart = timer(); + VersionedMap map; + map.createNewVersion(1); + + double start = timer(); + for (int key = 0; key < initialKeys; ++key) { + map.insert(key, key); + } + populateElapsed += timer() - start; + populateOps += initialKeys; + + for (int version = 2; version <= versions; ++version) { + start = timer(); + map.createNewVersion(version); + createElapsed += timer() - start; + ++createOps; + + start = timer(); + map.insert(version % initialKeys, version); + map.insert(initialKeys + version, version); + latestInsertElapsed += timer() - start; + latestInsertOps += 2; + + if (version > retainedVersions + 1) { + start = timer(); + map.erase(initialKeys + version - retainedVersions); + latestEraseElapsed += timer() - start; + ++latestEraseOps; + } + + Version readVersion = std::max(map.getOldestVersion(), version - retainedVersions / 2); + readVersion = std::max(readVersion, 1); + + start = timer(); + auto historicalFind = map.at(readVersion).find(int(readVersion % initialKeys)); + ASSERT(historicalFind); + historicalFindElapsed += timer() - start; + ++historicalFindOps; + + start = timer(); + auto historicalLower = map.at(readVersion).lower_bound(int((readVersion * 17) % initialKeys)); + ASSERT(historicalLower); + historicalLowerBoundElapsed += timer() - start; + ++historicalLowerBoundOps; + + start = timer(); + auto scan = map.at(readVersion).lower_bound(int((readVersion * 31) % (initialKeys - scanWidth))); + for (int i = 0; i < scanWidth; ++i) { + ASSERT(scan); + ++scan; + ++historicalScanOps; + } + historicalScanElapsed += timer() - start; + + if (version > retainedVersions && version % forgetEvery == 0) { + Version cutoff = version - retainedVersions; + start = timer(); + map.compact(cutoff); + map.forgetVersionsBefore(cutoff); + compactForgetElapsed += timer() - start; + compactForgetOps += 2; + } + } + + benchmark::DoNotOptimize(map.getLatestVersion()); + double iterationElapsed = timer() - totalStart; + state.SetIterationTime(iterationElapsed); + } + + state.SetItemsProcessed((populateOps + createOps + latestInsertOps + latestEraseOps + historicalFindOps + + historicalLowerBoundOps + historicalScanOps + compactForgetOps)); + setKOpsCounter(state, "populate_kops", populateOps, populateElapsed); + setKOpsCounter(state, "create_version_kops", createOps, createElapsed); + setKOpsCounter(state, "latest_insert_kops", latestInsertOps, latestInsertElapsed); + setKOpsCounter(state, "latest_erase_kops", latestEraseOps, latestEraseElapsed); + setKOpsCounter(state, "historical_find_kops", historicalFindOps, historicalFindElapsed); + setKOpsCounter(state, "historical_lower_bound_kops", historicalLowerBoundOps, historicalLowerBoundElapsed); + setKOpsCounter(state, "historical_scan_kops", historicalScanOps, historicalScanElapsed); + setKOpsCounter(state, "compact_forget_kops", compactForgetOps, compactForgetElapsed); +} + +} // namespace + +BENCHMARK_TEMPLATE(bench_versioned_map_insert, IntFixture) + ->Name("VersionedMap/int/insert") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_find, IntFixture) + ->Name("VersionedMap/int/find") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_lower_bound, IntFixture) + ->Name("VersionedMap/int/lower_bound") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_upper_bound, IntFixture) + ->Name("VersionedMap/int/upper_bound") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_last_less_or_equal, IntFixture) + ->Name("VersionedMap/int/last_less_or_equal") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_last_less, IntFixture) + ->Name("VersionedMap/int/last_less") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_scan, IntFixture) + ->Name("VersionedMap/int/scan") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_find_sorted, IntFixture) + ->Name("VersionedMap/int/find_sorted") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_erase, IntFixture) + ->Name("VersionedMap/int/erase") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_TEMPLATE(bench_versioned_map_insert, StringRefFixture) + ->Name("VersionedMap/StringRef/insert") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_find, StringRefFixture) + ->Name("VersionedMap/StringRef/find") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_lower_bound, StringRefFixture) + ->Name("VersionedMap/StringRef/lower_bound") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_upper_bound, StringRefFixture) + ->Name("VersionedMap/StringRef/upper_bound") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_last_less_or_equal, StringRefFixture) + ->Name("VersionedMap/StringRef/last_less_or_equal") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_last_less, StringRefFixture) + ->Name("VersionedMap/StringRef/last_less") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_scan, StringRefFixture) + ->Name("VersionedMap/StringRef/scan") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_find_sorted, StringRefFixture) + ->Name("VersionedMap/StringRef/find_sorted") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); +BENCHMARK_TEMPLATE(bench_versioned_map_erase, StringRefFixture) + ->Name("VersionedMap/StringRef/erase") + ->Arg(1000000) + ->Iterations(3) + ->Unit(benchmark::kMillisecond); + +BENCHMARK(bench_versioned_map_multiversion_int) + ->Name("VersionedMap/int/multiversion") + ->Iterations(3) + ->UseManualTime() + ->Unit(benchmark::kMillisecond); diff --git a/fdbclient/bench/CMakeLists.txt b/fdbclient/bench/CMakeLists.txt new file mode 100644 index 00000000000..63fef43158d --- /dev/null +++ b/fdbclient/bench/CMakeLists.txt @@ -0,0 +1,12 @@ +include(FDBBenchmark) + +fdb_find_sources(FDBCLIENT_BENCH_SRCS) +add_flow_target(EXECUTABLE NAME fdbclient_bench SRCS ${FDBCLIENT_BENCH_SRCS}) + +fdb_setup_googlebenchmark() + +target_include_directories( + fdbclient_bench + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") + +target_link_libraries(fdbclient_bench PRIVATE Threads::Threads fdb_google_benchmark fdbclient) diff --git a/flowbench/GlobalData.cpp b/fdbclient/bench/GlobalData.cpp similarity index 98% rename from flowbench/GlobalData.cpp rename to fdbclient/bench/GlobalData.cpp index 7585f310e73..69b811a4ed3 100644 --- a/flowbench/GlobalData.cpp +++ b/fdbclient/bench/GlobalData.cpp @@ -19,6 +19,7 @@ */ #include "fdbclient/FDBTypes.h" +#include "GlobalData.h" #include "flow/IRandom.h" static constexpr size_t globalDataSize = 1 << 20; diff --git a/flowbench/include/flowbench/GlobalData.h b/fdbclient/bench/GlobalData.h similarity index 88% rename from flowbench/include/flowbench/GlobalData.h rename to fdbclient/bench/GlobalData.h index 5bed599c814..8d9e9955136 100644 --- a/flowbench/include/flowbench/GlobalData.h +++ b/fdbclient/bench/GlobalData.h @@ -18,18 +18,17 @@ * limitations under the License. */ -#ifndef __FDBBENCH_GLOBALDATA_H__ -#define __FDBBENCH_GLOBALDATA_H__ +#ifndef FDB_FDBCLIENT_BENCH_GLOBALDATA_H +#define FDB_FDBCLIENT_BENCH_GLOBALDATA_H #pragma once #include "fdbclient/FDBTypes.h" +#include "flow/flow.h" KeyValueRef getKV(size_t keySize, size_t valueSize); KeyRef getKey(size_t keySize); -// Pre-generate a vector of T using a lambda then return them -// via next() one by one with wrap-around template struct InputGenerator { InputGenerator() {} diff --git a/fdbclient/include/fdbclient/AnnotateActor.h b/fdbclient/include/fdbclient/AnnotateActor.h index df753d974fa..902ae0d86c5 100644 --- a/fdbclient/include/fdbclient/AnnotateActor.h +++ b/fdbclient/include/fdbclient/AnnotateActor.h @@ -33,7 +33,7 @@ struct AnnotateActor { AnnotateActor() : set(false) {} - AnnotateActor(LineageReference* lineage) : set(false) { + explicit AnnotateActor(LineageReference* lineage) : set(false) { #ifdef ENABLE_SAMPLING if (lineage->getPtr() != 0) { index = g_network->getActorLineageSet().insert(*lineage); diff --git a/fdbclient/include/fdbclient/AsyncFileS3BlobStore.actor.h b/fdbclient/include/fdbclient/AsyncFileBlobStore.h similarity index 62% rename from fdbclient/include/fdbclient/AsyncFileS3BlobStore.actor.h rename to fdbclient/include/fdbclient/AsyncFileBlobStore.h index 5ecea287e7e..81930e529a0 100644 --- a/fdbclient/include/fdbclient/AsyncFileS3BlobStore.actor.h +++ b/fdbclient/include/fdbclient/AsyncFileBlobStore.h @@ -1,5 +1,5 @@ /* - * AsyncFileS3BlobStore.actor.h + * AsyncFileBlobStore.h * * This source file is part of the FoundationDB open source project * @@ -20,14 +20,6 @@ #pragma once -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBRPC_ASYNCFILEBLOBSTORE_ACTOR_G_H) -#define FDBRPC_ASYNCFILEBLOBSTORE_ACTOR_G_H -#include "fdbclient/AsyncFileS3BlobStore.actor.g.h" -#elif !defined(FDBRPC_ASYNCFILES3BLOBSTORE_ACTOR_H) -#define FDBRPC_ASYNCFILES3BLOBSTORE_ACTOR_H - #include #include @@ -35,33 +27,32 @@ #include "flow/serialize.h" #include "flow/Net2Packet.h" #include "flow/IRateControl.h" -#include "fdbclient/S3BlobStore.h" +#include "fdbclient/IBlobStore.h" #include "md5/md5.h" #include "libb64/encode.h" #include -#include "flow/actorcompiler.h" // This must be the last #include. -ACTOR template +template static Future joinErrorGroup(Future f, Promise p) { try { - wait(success(f) || p.getFuture()); - return f.get(); + co_await (success(f) || p.getFuture()); + co_return f.get(); } catch (Error& e) { if (p.canBeSet()) p.sendError(e); throw; } } -// This class represents a write-only file that lives in an S3-style blob store. It writes using the REST API, +// This class represents a write-only file that lives in a blob store. It writes using the REST API, // using multi-part upload and beginning to transfer each part as soon as it is large enough. // All write operations file operations must be sequential and contiguous. -// Limits on part sizes, upload speed, and concurrent uploads are taken from the S3BlobStoreEndpoint being used. -class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCounted { +// Limits on part sizes, upload speed, and concurrent uploads are taken from the IBlobStoreEndpoint being used. +class AsyncFileBlobStoreWrite final : public IAsyncFile, public ReferenceCounted { public: - void addref() override { ReferenceCounted::addref(); } - void delref() override { ReferenceCounted::delref(); } + void addref() override { ReferenceCounted::addref(); } + void delref() override { ReferenceCounted::delref(); } - virtual StringRef getClassName() override { return "AsyncFileS3BlobStoreWrite"_sr; } + StringRef getClassName() override { return "AsyncFileBlobStoreWrite"_sr; } struct Part : ReferenceCounted { Part(int n, int minSize, bool useSHA256) @@ -120,8 +111,8 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount Future read(void* data, int length, int64_t offset) override { throw file_not_readable(); } - ACTOR static Future write_impl(Reference f, const uint8_t* data, int length) { - state Part* p = f->m_parts.back().getPtr(); + static Future write_impl(Reference f, const uint8_t* data, int length) { + Part* p = f->m_parts.back().getPtr(); // If this write will cause the part to cross the min part size boundary then write to the boundary and start a // new part. while (p->length + length >= f->m_bstore->knobs.multipart_min_part_size) { @@ -134,12 +125,11 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount data = (const uint8_t*)data + finishlen; // End current part (and start new one) - wait(f->endCurrentPart(f.getPtr(), true)); + co_await f->endCurrentPart(f.getPtr(), true); p = f->m_parts.back().getPtr(); } p->write((const uint8_t*)data, length); - return Void(); } Future write(void const* data, int length, int64_t offset) override { @@ -148,7 +138,7 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount m_cursor += length; return m_error.getFuture() || - write_impl(Reference::addRef(this), (const uint8_t*)data, length); + write_impl(Reference::addRef(this), (const uint8_t*)data, length); } Future truncate(int64_t size) override { @@ -157,54 +147,52 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount return Void(); } - ACTOR static Future doPartUpload(AsyncFileS3BlobStoreWrite* f, Part* p) { + static Future doPartUpload(AsyncFileBlobStoreWrite* f, Part* p) { p->finalizeChecksum(); - std::string upload_id = wait(f->getUploadID()); - std::string etag = wait(f->m_bstore->uploadPart( - f->m_bucket, f->m_object, upload_id, p->number, &p->content, p->length, p->checksumString)); - return etag; + std::string upload_id = co_await f->getUploadID(); + std::string etag = co_await f->m_bstore->uploadPart( + f->m_bucket, f->m_object, upload_id, p->number, &p->content, p->length, p->checksumString); + co_return etag; } - ACTOR static Future doFinishUpload(AsyncFileS3BlobStoreWrite* f) { + static Future doFinishUpload(AsyncFileBlobStoreWrite* f) { // If there is only 1 part then it has not yet been uploaded so just write the whole file at once. if (f->m_parts.size() == 1) { Reference part = f->m_parts.back(); part->finalizeChecksum(); - wait(f->m_bstore->writeEntireFileFromBuffer( - f->m_bucket, f->m_object, &part->content, part->length, part->checksumString)); - return Void(); + co_await f->m_bstore->writeEntireFileFromBuffer( + f->m_bucket, f->m_object, &part->content, part->length, part->checksumString); + co_return; } // There are at least 2 parts. End the last part (which could be empty) - wait(f->endCurrentPart(f)); + co_await f->endCurrentPart(f); - state S3BlobStoreEndpoint::MultiPartSetT partSet; - state std::vector>::iterator p; + IBlobStoreEndpoint::MultiPartSetT partSet; + std::vector>::iterator p; // Wait for all the parts to be done to get their ETags, populate the partSet required to finish the object // upload. for (p = f->m_parts.begin(); p != f->m_parts.end(); ++p) { - std::string tag = wait((*p)->etag); + std::string tag = co_await (*p)->etag; if ((*p)->length > 0) { // The last part might be empty and has to be omitted. - partSet[(*p)->number] = S3BlobStoreEndpoint::PartInfo(tag, (*p)->checksumString); + partSet[(*p)->number] = IBlobStoreEndpoint::PartInfo(tag, (*p)->checksumString); } } // No need to wait for the upload ID here because the above loop waited for all the parts and each part required // the upload ID so it is ready - Optional checksumSHA256 = - wait(f->m_bstore->finishMultiPartUpload(f->m_bucket, f->m_object, f->m_upload_id.get(), partSet)); + Optional checksumSHA256 = co_await f->m_bstore->finishMultiPartUpload( + f->m_bucket, f->m_object, f->m_upload_id.get(), partSet, f->m_cursor); // Log the checksum if present - this is just a hash of the multipart structure, not the object content if (checksumSHA256.present()) { - TraceEvent(SevDebug, "AsyncFileS3BlobStoreMultipartUploadChecksum") + TraceEvent(SevDebug, "AsyncFileBlobStoreMultipartUploadChecksum") .detail("Bucket", f->m_bucket) .detail("Object", f->m_object) .detail("ChecksumSHA256", checksumSHA256.get()) .detail("Note", "This is a hash of the multipart structure, not object content"); } - - return Void(); } // Ready once all data has been sent AND acknowledged from the remote side @@ -237,7 +225,7 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount int64_t debugFD() const override { return -1; } - ~AsyncFileS3BlobStoreWrite() override { + ~AsyncFileBlobStoreWrite() override { m_upload_id.cancel(); m_finished.cancel(); m_parts.clear(); // Contains futures @@ -246,7 +234,7 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount std::string getFilename() const override { return m_object; } private: - Reference m_bstore; + Reference m_bstore; std::string m_bucket; std::string m_object; @@ -259,12 +247,12 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount FlowLock m_concurrentUploads; // End the current part and start uploading it, but also wait for a part to finish if too many are in transit. - ACTOR static Future endCurrentPart(AsyncFileS3BlobStoreWrite* f, bool startNew = false) { + static Future endCurrentPart(AsyncFileBlobStoreWrite* f, bool startNew = false) { if (f->m_parts.back()->length == 0) - return Void(); + co_return; // Wait for an upload slot to be available - wait(f->m_concurrentUploads.take()); + co_await f->m_concurrentUploads.take(); // Do the upload, and if it fails forward errors to m_error and also stop if anything else sends an error to // m_error Also, hold a releaser for the concurrent upload slot while all that is going on. @@ -274,11 +262,9 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount // Make a new part to write to if (startNew) - f->m_parts.push_back(Reference(new Part(f->m_parts.size() + 1, - f->m_bstore->knobs.multipart_min_part_size, - f->m_bstore->knobs.enable_object_integrity_check))); - - return Void(); + f->m_parts.push_back(makeReference(f->m_parts.size() + 1, + f->m_bstore->knobs.multipart_min_part_size, + f->m_bstore->knobs.enable_object_integrity_check)); } Future getUploadID() { @@ -288,7 +274,7 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount } public: - AsyncFileS3BlobStoreWrite(Reference bstore, std::string bucket, std::string object) + AsyncFileBlobStoreWrite(Reference bstore, std::string bucket, std::string object) : m_bstore(bstore), m_bucket(bucket), m_object(object), m_cursor(0), m_concurrentUploads(bstore->knobs.concurrent_writes_per_file) { @@ -298,46 +284,13 @@ class AsyncFileS3BlobStoreWrite final : public IAsyncFile, public ReferenceCount } }; -// Different Download Approaches: -// -// 1. AsyncFileS3BlobStoreRead::read -// - Always uses range requests (Range: bytes=0-24) -// - ❌ NO checksum verification - can't use x-amz-checksum-mode: ENABLED -// - Used by backup/restore operations through BackupContainerS3BlobStore::readFile() -// -// 2. S3BlobStoreEndpoint::readEntireFile (for small files): -// - ✅ HAS checksum verification using x-amz-checksum-mode: ENABLED -// - Used for small file downloads where full object retrieval is efficient -// -// 3. S3Client::copyDownFile (for large files): -// - Uses range-based downloads in parallel parts -// - ✅ Validates overall file checksum after download using XXH64 stored in tags/companion files -// -// Why Range Requests Can't Use S3 Checksums: -// - S3 limitation: x-amz-checksum-mode: ENABLED only works for full object downloads -// - Range requests (Range: bytes=X-Y) cannot be checksum-verified by S3 because: -// * The checksum is calculated for the entire object, not arbitrary byte ranges -// * S3 doesn't know how to verify partial content against a full-object checksum -// -// - Upload: SHA256 checksums stored with multipart uploads ✅ -// - Download: -// * Small files: S3 SHA256 verification ✅ -// * Large files: Custom XXH64 verification after complete download ✅ -// * Range requests: No S3 verification ❌ (but still get FoundationDB's own checksums) -// -// - Range requests are more efficient for large files and allow parallel downloads -// - S3's checksum verification wouldn't work with range requests anyway -// - You still get protection through FoundationDB's XXH64 checksums -// - The multipart upload checksums primarily protect against transmission errors during upload -// - The XXH64 checksums are used to verify the download. - -// This class represents a read-only file that lives in an S3-style blob store. It reads using the REST API. -class AsyncFileS3BlobStoreRead final : public IAsyncFile, public ReferenceCounted { +// This class represents a read-only file that lives in a blob store. It reads using the REST API. +class AsyncFileBlobStoreRead final : public IAsyncFile, public ReferenceCounted { public: - void addref() override { ReferenceCounted::addref(); } - void delref() override { ReferenceCounted::delref(); } + void addref() override { ReferenceCounted::addref(); } + void delref() override { ReferenceCounted::delref(); } - virtual StringRef getClassName() override { return "AsyncFileS3BlobStoreRead"_sr; } + StringRef getClassName() override { return "AsyncFileBlobStoreRead"_sr; } Future read(void* data, int length, int64_t offset) override; @@ -359,16 +312,13 @@ class AsyncFileS3BlobStoreRead final : public IAsyncFile, public ReferenceCounte std::string getFilename() const override { return m_object; } - ~AsyncFileS3BlobStoreRead() override {} + ~AsyncFileBlobStoreRead() override = default; - Reference m_bstore; + Reference m_bstore; std::string m_bucket; std::string m_object; mutable Future m_size; - AsyncFileS3BlobStoreRead(Reference bstore, std::string bucket, std::string object) + AsyncFileBlobStoreRead(Reference bstore, std::string bucket, std::string object) : m_bstore(bstore), m_bucket(bucket), m_object(object) {} }; - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/AsyncTaskThread.h b/fdbclient/include/fdbclient/AsyncTaskThread.h deleted file mode 100644 index 62101ad9470..00000000000 --- a/fdbclient/include/fdbclient/AsyncTaskThread.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * AsyncTaskThread.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __ASYNC_TASK_THREAD_H__ -#define __ASYNC_TASK_THREAD_H__ - -#include -#include -#include -#include - -#include "flow/network.h" -#include "flow/ThreadHelper.actor.h" -#include "flow/ThreadSafeQueue.h" - -class IAsyncTask { -public: - virtual void operator()() = 0; - virtual ~IAsyncTask() = default; - virtual bool isTerminate() const = 0; -}; - -template -class AsyncTask final : public IAsyncTask { - F func; - -public: - AsyncTask(const F& func) : func(func) {} - - void operator()() override { func(); } - bool isTerminate() const override { return false; } -}; - -class AsyncTaskThread { - ThreadSafeQueue> queue; - std::condition_variable cv; - std::mutex m; - std::thread thread; - - static void run(AsyncTaskThread* self); - - template - void addTask(const F& func) { - bool wakeUp = false; - { - std::lock_guard g(m); - wakeUp = queue.push(std::make_unique>(func)); - } - if (wakeUp) { - cv.notify_one(); - } - } - - static const double meanDelay; - -public: - AsyncTaskThread(); - - // Warning: This destructor can hang if a task hangs, so it is - // up to the caller to prevent tasks from hanging indefinitely - ~AsyncTaskThread(); - - template - auto execAsync(const F& func, TaskPriority priority = TaskPriority::DefaultOnMainThread) - -> Future { - if (g_network->isSimulated()) { - return map(delayJittered(meanDelay), [func](Void _) { return func(); }); - } - Promise promise; - addTask([promise, func, priority] { - try { - auto funcResult = func(); - onMainThreadVoid([promise, funcResult] { promise.send(funcResult); }, priority); - } catch (Error& e) { - TraceEvent("ErrorExecutingAsyncTask").error(e); - onMainThreadVoid([promise, e] { promise.sendError(e); }, priority); - } - }); - return promise.getFuture(); - } -}; - -#endif diff --git a/fdbclient/include/fdbclient/AuditUtils.actor.h b/fdbclient/include/fdbclient/AuditUtils.actor.h deleted file mode 100644 index 48311a80b9b..00000000000 --- a/fdbclient/include/fdbclient/AuditUtils.actor.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * AuditUtils.actor.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. - */ - -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_AUDITUTILS_ACTOR_G_H) -#define FDBCLIENT_AUDITUTILS_ACTOR_G_H -#include "fdbclient/AuditUtils.actor.g.h" -#elif !defined(FDBCLIENT_AUDITUTILS_ACTOR_H) -#define FDBCLIENT_AUDITUTILS_ACTOR_H -#pragma once - -#include "fdbclient/Audit.h" -#include "fdbclient/FDBTypes.h" -#include "fdbclient/NativeAPI.actor.h" -#include "fdbrpc/fdbrpc.h" - -#include "flow/actorcompiler.h" // has to be last include - -struct MoveKeyLockInfo { - UID prevOwner, myOwner, prevWrite; -}; - -ACTOR Future cancelAuditMetadata(Database cx, AuditType auditType, UID auditId); -ACTOR Future persistNewAuditState(Database cx, AuditStorageState auditState, MoveKeyLockInfo lock, bool ddEnabled); -ACTOR Future persistAuditState(Database cx, - AuditStorageState auditState, - std::string context, - MoveKeyLockInfo lock, - bool ddEnabled); -ACTOR Future getAuditState(Database cx, AuditType type, UID id); -ACTOR Future> getAuditStates(Database cx, - AuditType auditType, - bool newFirst, - Optional num = Optional(), - Optional phase = Optional()); - -ACTOR Future persistAuditStateByRange(Database cx, AuditStorageState auditState); -ACTOR Future> getAuditStateByRange(Database cx, - AuditType type, - UID auditId, - KeyRange range); -ACTOR Future persistAuditStateByServer(Database cx, AuditStorageState auditState); -ACTOR Future> getAuditStateByServer(Database cx, - AuditType type, - UID auditId, - UID auditServerId, - KeyRange range); -ACTOR Future clearAuditMetadataForType(Database cx, - AuditType auditType, - UID maxAuditIdToClear, - int numFinishAuditToKeep); -ACTOR Future checkStorageServerRemoved(Database cx, UID ssid); -AuditPhase stringToAuditPhase(std::string auditPhaseStr); -ACTOR Future checkAuditProgressCompleteByRange(Database cx, - AuditType auditType, - UID auditId, - KeyRange auditRange); -ACTOR Future checkAuditProgressCompleteByServer(Database cx, - AuditType auditType, - UID auditId, - KeyRange auditRange, - UID serverId, - std::shared_ptr> checkProgressBudget); -ACTOR Future> initAuditMetadata(Database cx, - MoveKeyLockInfo lock, - bool ddEnabled, - UID dataDistributorId, - int persistFinishAuditCount); - -struct AuditGetServerKeysRes { - KeyRange completeRange; - Version readAtVersion; - UID serverId; - std::vector ownRanges; - int64_t readBytes; - AuditGetServerKeysRes() = default; - AuditGetServerKeysRes(KeyRange completeRange, - Version readAtVersion, - UID serverId, - std::vector ownRanges, - int64_t readBytes) - : completeRange(completeRange), readAtVersion(readAtVersion), serverId(serverId), ownRanges(ownRanges), - readBytes(readBytes) {} -}; - -struct AuditGetKeyServersRes { - KeyRange completeRange; - Version readAtVersion; - int64_t readBytes; - std::unordered_map> rangeOwnershipMap; - AuditGetKeyServersRes() = default; - AuditGetKeyServersRes(KeyRange completeRange, - Version readAtVersion, - std::unordered_map> rangeOwnershipMap, - int64_t readBytes) - : completeRange(completeRange), readAtVersion(readAtVersion), rangeOwnershipMap(rangeOwnershipMap), - readBytes(readBytes) {} -}; - -std::vector coalesceRangeList(std::vector ranges); -Optional> rangesSame(std::vector rangesA, std::vector rangesB); -ACTOR Future getThisServerKeysFromServerKeys(UID serverID, Transaction* tr, KeyRange range); -ACTOR Future getShardMapFromKeyServers(UID auditServerId, Transaction* tr, KeyRange range); - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/AuditUtils.h b/fdbclient/include/fdbclient/AuditUtils.h new file mode 100644 index 00000000000..6421d94a79c --- /dev/null +++ b/fdbclient/include/fdbclient/AuditUtils.h @@ -0,0 +1,203 @@ +/* + * AuditUtils.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 + +#ifndef FDBCLIENT_AUDITUTILS_H +#define FDBCLIENT_AUDITUTILS_H + +#include "fdbclient/Audit.h" +#include "fdbclient/FDBTypes.h" +#include "fdbclient/NativeAPI.actor.h" +#include "fdbrpc/fdbrpc.h" + +struct MoveKeyLockInfo { + UID prevOwner, myOwner, prevWrite; +}; + +// Cancel an in-progress audit by setting its persisted state to Failed and clearing progress metadata. +Future cancelAuditMetadata(Database cx, AuditType auditType, UID auditId); + +// Persist a new audit's initial state to the database. Returns the assigned audit ID. +Future persistNewAuditState(Database cx, AuditStorageState auditState, MoveKeyLockInfo lock, bool ddEnabled); + +// Update an existing audit's persisted state (e.g. phase transitions). +Future persistAuditState(Database cx, + AuditStorageState auditState, + std::string context, + MoveKeyLockInfo lock, + bool ddEnabled); + +// Read a single audit's state by type and ID. +Future getAuditState(Database cx, AuditType type, UID id); + +// List audit states for a given type, optionally filtered by phase, ordered by ID. +AsyncResult> getAuditStates(Database cx, + AuditType auditType, + bool newFirst, + Optional num = Optional(), + Optional phase = Optional()); + +// Persist per-range audit progress (which sub-ranges have been validated). +Future persistAuditStateByRange(Database cx, AuditStorageState auditState); + +// Read per-range audit progress for the given audit within a key range. +Future> getAuditStateByRange(Database cx, AuditType type, UID auditId, KeyRange range); + +// Persist per-server audit progress. +Future persistAuditStateByServer(Database cx, AuditStorageState auditState); + +// Read per-server audit progress for a specific server within a key range. +Future> getAuditStateByServer(Database cx, + AuditType type, + UID auditId, + UID auditServerId, + KeyRange range); + +// Clear persisted audit metadata for completed/failed audits older than maxAuditIdToClear, +// keeping the most recent numFinishAuditToKeep finished audits. +Future clearAuditMetadataForType(Database cx, + AuditType auditType, + UID maxAuditIdToClear, + int numFinishAuditToKeep); + +// Check whether a storage server has been removed from the server list. +Future checkStorageServerRemoved(Database cx, UID ssid); + +// Parse a human-readable audit phase string (e.g. "running", "complete") into an AuditPhase enum. +AuditPhase stringToAuditPhase(std::string auditPhaseStr); + +// Check whether all sub-ranges of auditRange have completed range-based audit progress. +Future checkAuditProgressCompleteByRange(Database cx, AuditType auditType, UID auditId, KeyRange auditRange); + +// Check whether a specific server's audit progress is complete for the given range. +Future checkAuditProgressCompleteByServer(Database cx, + AuditType auditType, + UID auditId, + KeyRange auditRange, + UID serverId, + std::shared_ptr> checkProgressBudget); + +// Initialize audit metadata on DD startup: resume incomplete audits and clean up old ones. +Future> initAuditMetadata(Database cx, + MoveKeyLockInfo lock, + bool ddEnabled, + UID dataDistributorId, + int persistFinishAuditCount); + +// Result of reading a single server's owned ranges from the ServerKeys system key space. +struct AuditGetServerKeysRes { + KeyRange completeRange; // the sub-range that was fully read in this batch + Version readAtVersion; + UID serverId; + std::vector ownRanges; // ranges this server owns according to ServerKeys + int64_t readBytes; + AuditGetServerKeysRes() = default; + AuditGetServerKeysRes(KeyRange completeRange, + Version readAtVersion, + UID serverId, + std::vector ownRanges, + int64_t readBytes) + : completeRange(completeRange), readAtVersion(readAtVersion), serverId(serverId), ownRanges(ownRanges), + readBytes(readBytes) {} +}; + +// Result of reading the KeyServers system key space: maps each server to the ranges it owns. +struct AuditGetKeyServersRes { + KeyRange completeRange; // the sub-range that was fully read in this batch + Version readAtVersion; + int64_t readBytes; + std::unordered_map> rangeOwnershipMap; + AuditGetKeyServersRes() = default; + AuditGetKeyServersRes(KeyRange completeRange, + Version readAtVersion, + std::unordered_map> rangeOwnershipMap, + int64_t readBytes) + : completeRange(completeRange), readAtVersion(readAtVersion), rangeOwnershipMap(rangeOwnershipMap), + readBytes(readBytes) {} +}; + +// Merge overlapping or adjacent ranges in the input list into a minimal set of non-overlapping +// ranges covering the same key space. +std::vector coalesceRangeList(std::vector ranges); + +// Compare two sorted, non-overlapping range lists for equivalence. Handles different split +// points that cover the same total range. Returns the first mismatched pair, or empty if equal. +Optional> rangesSame(std::vector rangesA, std::vector rangesB); + +// A single consistency error found by checkLocationMetadataConsistency. +struct LocationMetadataError { + std::string message; + UID serverId; + Optional mismatchedRangeByKeyServer; + Optional mismatchedRangeByServerKey; +}; + +// Bidirectional consistency check between the KeyServers and ServerKeys views of shard +// ownership. Returns an error for each server that is missing from one side or has +// mismatched range assignments. An empty result means the two views are consistent. +std::vector checkLocationMetadataConsistency( + const std::unordered_map>& mapFromKeyServers, + const std::unordered_map>& mapFromServerKeys, + KeyRange claimRange); + +// Output of buildLocationMetadataMaps: the processed per-server range maps ready for +// consistency checking, plus counts of validated entries. +struct LocationMetadataMaps { + std::unordered_map> fromKeyServers; + std::unordered_map> fromServerKeys; + int64_t numValidatedKeyServers = 0; + int64_t numValidatedServerKeys = 0; +}; + +// Process raw per-server ownership data into maps suitable for checkLocationMetadataConsistency. +// For KeyServers data: coalesces overlapping ranges then intersects with claimRange. +// For ServerKeys data: intersects each range with claimRange. +// If traceId is provided, emits SevVerbose trace events for each range added. +LocationMetadataMaps buildLocationMetadataMaps( + const std::unordered_map>& mapFromKeyServersRaw, + const std::unordered_map>& serverOwnRangesMap, + KeyRange claimRange, + Optional traceId = Optional()); + +// Parse a ServerKeys KRM result into the list of ranges where serverHasKey() is true. +std::vector buildOwnRangesFromServerKeysResult(const RangeResult& krmResult); + +// Output of buildOwnershipMapFromKeyServersResult: per-server range ownership plus +// shard statistics from the KeyServers system key space. +struct KeyServersOwnershipMap { + std::unordered_map> rangeOwnershipMap; + int64_t totalShardsCount = 0; + int64_t shardsInAnonymousPhysicalShardCount = 0; +}; + +// Parse a KeyServers KRM result into a per-server ownership map. Decodes each entry's +// src and dest server lists via decodeKeyServersValue and merges them. Also counts total +// shards and shards using the anonymous physical shard ID. +KeyServersOwnershipMap buildOwnershipMapFromKeyServersResult(const RangeResult& krmResult, + const RangeResult& uidToTagMap); + +// Read a single server's owned ranges from the ServerKeys system key space via transaction. +Future getThisServerKeysFromServerKeys(UID serverID, Transaction* tr, KeyRange range); + +// Read the KeyServers system key space via transaction, producing a per-server ownership map. +Future getShardMapFromKeyServers(UID auditServerId, Transaction* tr, KeyRange range); + +#endif diff --git a/fdbclient/include/fdbclient/BackupAgent.actor.h b/fdbclient/include/fdbclient/BackupAgent.h similarity index 86% rename from fdbclient/include/fdbclient/BackupAgent.actor.h rename to fdbclient/include/fdbclient/BackupAgent.h index 0f62c647907..8955b096da7 100644 --- a/fdbclient/include/fdbclient/BackupAgent.actor.h +++ b/fdbclient/include/fdbclient/BackupAgent.h @@ -1,5 +1,5 @@ /* - * BackupAgent.actor.h + * BackupAgent.h * * This source file is part of the FoundationDB open source project * @@ -19,11 +19,6 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_BACKUP_AGENT_ACTOR_G_H) -#define FDBCLIENT_BACKUP_AGENT_ACTOR_G_H -#include "fdbclient/BackupAgent.actor.g.h" -#elif !defined(FDBCLIENT_BACKUP_AGENT_ACTOR_H) -#define FDBCLIENT_BACKUP_AGENT_ACTOR_H #include #include @@ -33,9 +28,8 @@ #include "fdbclient/TaskBucket.h" #include "fdbclient/Notified.h" #include "flow/IAsyncFile.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/KeyBackedTypes.h" #include "fdbclient/BackupContainer.h" -#include "flow/actorcompiler.h" // has to be last include FDB_BOOLEAN_PARAM(LockDB); FDB_BOOLEAN_PARAM(UnlockDB); @@ -45,8 +39,6 @@ FDB_BOOLEAN_PARAM(WaitForComplete); FDB_BOOLEAN_PARAM(ForceAction); FDB_BOOLEAN_PARAM(Terminator); FDB_BOOLEAN_PARAM(IncrementalBackupOnly); -FDB_BOOLEAN_PARAM(UsePartitionedLog); -FDB_BOOLEAN_PARAM(TransformPartitionedLog); FDB_BOOLEAN_PARAM(OnlyApplyMutationLogs); FDB_BOOLEAN_PARAM(InconsistentSnapshotOnly); FDB_BOOLEAN_PARAM(ShowErrors); @@ -61,6 +53,8 @@ FDB_BOOLEAN_PARAM(ReadLowPriority); extern Optional fileBackupAgentProxy; +constexpr int DEFAULT_ENCRYPTION_BLOCK_SIZE = 1048576; + class BackupAgentBase : NonCopyable { public: // Time formatter for anything backup or restore related @@ -150,7 +144,7 @@ class FileBackupAgent : public BackupAgentBase { public: FileBackupAgent(); - FileBackupAgent(FileBackupAgent&& r) noexcept + explicit(false) FileBackupAgent(FileBackupAgent&& r) noexcept : subspace(std::move(r.subspace)), config(std::move(r.config)), lastRestorable(std::move(r.lastRestorable)), taskBucket(std::move(r.taskBucket)), futureBucket(std::move(r.futureBucket)) {} @@ -177,24 +171,6 @@ class FileBackupAgent : public BackupAgentBase { static StringRef restoreStateText(ERestoreState id); static Key getPauseKey(); - // parallel restore - Future parallelRestoreFinish(Database cx, UID randomUID, UnlockDB = UnlockDB::True); - Future submitParallelRestore(Database cx, - Key backupTag, - Standalone> backupRanges, - Key bcUrl, - Optional proxy, - Version targetVersion, - LockDB lockDB, - UID randomUID, - Key addPrefix, - Key removePrefix); - Future atomicParallelRestore(Database cx, - Key tagName, - Standalone> ranges, - Key addPrefix, - Key removePrefix); - // restore() will // - make sure that url is readable and appears to be a complete backup // - make sure the requested TargetVersion is valid @@ -297,9 +273,10 @@ class FileBackupAgent : public BackupAgentBase { std::string const& tagName, Standalone> backupRanges, StopWhenDone = StopWhenDone::True, - UsePartitionedLog = UsePartitionedLog::False, + MutationLogType mutationLogType = MutationLogType::DEFAULT, IncrementalBackupOnly = IncrementalBackupOnly::False, Optional const& encryptionKeyFileName = {}, + int encryptionBlockSize = 0, int snapshotMode = 0); // snapshotMode: 0=RANGEFILE (default), 1=BULKDUMP, 2=BOTH Future submitBackup(Database cx, @@ -310,9 +287,10 @@ class FileBackupAgent : public BackupAgentBase { std::string const& tagName, Standalone> backupRanges, StopWhenDone stopWhenDone = StopWhenDone::True, - UsePartitionedLog partitionedLog = UsePartitionedLog::False, + MutationLogType mutationLogType = MutationLogType::DEFAULT, IncrementalBackupOnly incrementalBackupOnly = IncrementalBackupOnly::False, Optional const& encryptionKeyFileName = {}, + int encryptionBlockSize = 0, int snapshotMode = 0) { // Note: Do NOT call checkAndDisableBackupWorkers here. That function is for cleanup // when backups END (abort/discontinue), not when they START. Calling it here causes @@ -327,9 +305,10 @@ class FileBackupAgent : public BackupAgentBase { tagName, backupRanges, stopWhenDone, - partitionedLog, + mutationLogType, incrementalBackupOnly, encryptionKeyFileName, + encryptionBlockSize, snapshotMode); }); } @@ -338,7 +317,7 @@ class FileBackupAgent : public BackupAgentBase { Future discontinueBackup(Database cx, Key tagName) { return runRYWTransaction( cx, [=](Reference tr) { return discontinueBackup(tr, tagName); }) + - checkAndDisableBackupWorkers(cx); + checkAndDisableBackupWorkers(cx) + checkAndDisableRangeBackupWorkers(cx); } // Terminate an ongoing backup, without waiting for the backup to finish. @@ -353,12 +332,15 @@ class FileBackupAgent : public BackupAgentBase { // First abort the backup, then check and disable backup workers if needed. return runRYWTransaction(cx, [=](Reference tr) { return abortBackup(tr, tagName); }) + - checkAndDisableBackupWorkers(cx); + checkAndDisableBackupWorkers(cx) + checkAndDisableRangeBackupWorkers(cx); } // Disable backup workers if no active partitioned backup is running. Future checkAndDisableBackupWorkers(Database cx); + // Disable range backup workers if no active range-partitioned backup is running. + Future checkAndDisableRangeBackupWorkers(Database cx); + Future getStatus(Database cx, ShowErrors, std::string tagName); Future getStatusJSON(Database cx, std::string tagName); @@ -412,7 +394,7 @@ class DatabaseBackupAgent : public BackupAgentBase { DatabaseBackupAgent(); explicit DatabaseBackupAgent(Database src); - DatabaseBackupAgent(DatabaseBackupAgent&& r) noexcept + explicit(false) DatabaseBackupAgent(DatabaseBackupAgent&& r) noexcept : subspace(std::move(r.subspace)), states(std::move(r.states)), config(std::move(r.config)), errors(std::move(r.errors)), ranges(std::move(r.ranges)), tagNames(std::move(r.tagNames)), sourceStates(std::move(r.sourceStates)), sourceTagNames(std::move(r.sourceTagNames)), @@ -587,37 +569,37 @@ std::pair decodeBKMutationLogKey(Key key); Future logError(Database cx, Key keyErrors, const std::string& message); Future logError(Reference tr, Key keyErrors, const std::string& message); Future checkVersion(Reference const& tr); -ACTOR Future readCommitted(Database cx, - PromiseStream results, - Reference lock, - KeyRangeRef range, - Terminator terminator = Terminator::True, - AccessSystemKeys systemAccess = AccessSystemKeys::False, - LockAware lockAware = LockAware::False, - ReadLowPriority readLowPriority = ReadLowPriority::False); -ACTOR Future readCommitted(Database cx, - PromiseStream results, - Future active, - Reference lock, - KeyRangeRef range, - std::function(Key key)> groupBy, - Terminator terminator = Terminator::True, - AccessSystemKeys systemAccess = AccessSystemKeys::False, - LockAware lockAware = LockAware::False, - ReadLowPriority readLowPriority = ReadLowPriority::False); +Future readCommitted(Database cx, + PromiseStream results, + Reference lock, + KeyRangeRef range, + Terminator terminator = Terminator::True, + AccessSystemKeys systemAccess = AccessSystemKeys::False, + LockAware lockAware = LockAware::False, + ReadLowPriority readLowPriority = ReadLowPriority::False); +Future readCommitted(Database cx, + PromiseStream results, + Future active, + Reference lock, + KeyRangeRef range, + std::function(Key key)> groupBy, + Terminator terminator = Terminator::True, + AccessSystemKeys systemAccess = AccessSystemKeys::False, + LockAware lockAware = LockAware::False, + ReadLowPriority readLowPriority = ReadLowPriority::False); // Applies the mutations between the beginVersion and endVersion to the database during a restore. -ACTOR Future applyMutations(Database cx, - Key uid, - Key addPrefix, - Key removePrefix, - Version beginVersion, - Version* endVersion, - PublicRequestStream commit, - NotifiedVersion* committedVersion, - Reference> keyVersion, - bool provisionalProxy); -ACTOR Future cleanupBackup(Database cx, DeleteData deleteData); +Future applyMutations(Database cx, + Key uid, + Key addPrefix, + Key removePrefix, + Version beginVersion, + Version* endVersion, + PublicRequestStream commit, + NotifiedVersion* committedVersion, + Reference> keyVersion, + bool provisionalProxy); +Future cleanupBackup(Database cx, DeleteData deleteData); using EBackupState = BackupAgentBase::EnumState; template <> @@ -660,12 +642,12 @@ class KeyBackedTag : public KeyBackedProperty { typedef KeyBackedMap TagMap; // Map of tagName to {UID, aborted_flag} located in the fileRestorePrefixRange keyspace. class TagUidMap : public KeyBackedMap { - ACTOR static Future> getAll_impl(TagUidMap* tagsMap, - Reference tr, - Snapshot snapshot); + static Future> getAll_impl(TagUidMap* tagsMap, + Reference tr, + Snapshot snapshot); public: - TagUidMap(const StringRef& prefix) : TagMap("tag->uid/"_sr.withPrefix(prefix)), prefix(prefix) {} + explicit TagUidMap(const StringRef& prefix) : TagMap("tag->uid/"_sr.withPrefix(prefix)), prefix(prefix) {} Future> getAll(Reference tr, Snapshot snapshot = Snapshot::False) { @@ -685,7 +667,7 @@ class KeyBackedTaskConfig : public KeyBackedClass { static TaskParam uid() { return __FUNCTION__sr; } } TaskParams; - KeyBackedTaskConfig(StringRef prefix, UID uid = UID()) + explicit KeyBackedTaskConfig(StringRef prefix, UID uid = UID()) : KeyBackedClass(prefix), uid(uid), configSpace(uidPrefixKey("uid->config/"_sr.withPrefix(prefix), uid)) {} KeyBackedTaskConfig(StringRef prefix, Reference task) @@ -787,6 +769,7 @@ inline Standalone TupleCodec>::pack(Refer tuple.append(StringRef()); } + tuple.append((int64_t)bc->getEncryptionBlockSize()); return tuple.pack(); } template <> @@ -806,13 +789,17 @@ inline Reference TupleCodec>::unpa proxy = t.getString(2).toString(); } - return IBackupContainer::openContainer(url, proxy, encryptionKeyFileName); + int blockSize = 0; + if (t.size() > 3) { + blockSize = (int)t.getInt(3); + } + return IBackupContainer::openContainer(url, proxy, encryptionKeyFileName, blockSize); } class BackupConfig : public KeyBackedTaskConfig { public: - BackupConfig(UID uid = UID()) : KeyBackedTaskConfig(fileBackupPrefixRange.begin, uid) {} - BackupConfig(Reference task) : KeyBackedTaskConfig(fileBackupPrefixRange.begin, task) {} + explicit BackupConfig(UID uid = UID()) : KeyBackedTaskConfig(fileBackupPrefixRange.begin, uid) {} + explicit BackupConfig(Reference task) : KeyBackedTaskConfig(fileBackupPrefixRange.begin, task) {} // rangeFileMap maps a keyrange file's End to its Begin and Filename struct RangeSlice { @@ -918,8 +905,8 @@ class BackupConfig : public KeyBackedTaskConfig { return configSpace.pack(__FUNCTION__sr); } - // Set to true if partitioned log is enabled (only useful if backup worker is also enabled). - KeyBackedProperty partitionedLogEnabled() { return configSpace.pack(__FUNCTION__sr); } + // Mutation log type: 0 - DEFAULT (backup v1), 1 = PARTITIONED_LOG, 2 = RANGE_PARTITIONED_LOG. + KeyBackedProperty mutationLogType() { return configSpace.pack(__FUNCTION__sr); } // Set to true if only requesting incremental backup without base snapshot. KeyBackedProperty incrementalBackupOnly() { return configSpace.pack(__FUNCTION__sr); } @@ -945,6 +932,10 @@ class BackupConfig : public KeyBackedTaskConfig { // BulkDump total keys - stored when BulkDump snapshot completes KeyBackedProperty bulkDumpTotalKeys() { return configSpace.pack(__FUNCTION__sr); } + // BulkDump snapshot end version - set when BulkDump task completes + // Used for mode=BOTH to track that BulkDump data is available + KeyBackedProperty bulkDumpSnapshotEndVersion() { return configSpace.pack(__FUNCTION__sr); } + // Latest version for which all prior versions have had their log copy tasks completed KeyBackedProperty latestLogEndVersion() { return configSpace.pack(__FUNCTION__sr); } @@ -961,15 +952,55 @@ class BackupConfig : public KeyBackedTaskConfig { tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE); auto lastLog = latestLogEndVersion().get(tr); auto firstSnapshot = firstSnapshotEndVersion().get(tr); - auto plogEnabled = partitionedLogEnabled().get(tr); + auto mutLogType = mutationLogType().get(tr); auto workerVersion = latestBackupWorkerSavedVersion().get(tr); auto incrementalBackup = incrementalBackupOnly().get(tr); - return map(success(lastLog) && success(firstSnapshot) && success(plogEnabled) && success(workerVersion) && - success(incrementalBackup), + auto snapMode = snapshotMode().get(tr); + auto bulkDumpSnapshot = bulkDumpSnapshotEndVersion().get(tr); + return map(success(lastLog) && success(firstSnapshot) && success(mutLogType) && success(workerVersion) && + success(incrementalBackup) && success(snapMode) && success(bulkDumpSnapshot), [=](Void) -> Optional { + MutationLogType mutationLogType = + mutLogType.get().present() ? mutLogType.get().get() : MutationLogType::DEFAULT; // The latest log greater than the oldest snapshot is the restorable version Optional logVersion = - plogEnabled.get().present() && plogEnabled.get().get() ? workerVersion.get() : lastLog.get(); + mutationLogType == MutationLogType::PARTITIONED_LOG ? workerVersion.get() : lastLog.get(); + + // For mode=BOTH (2), require both rangefile and bulkdump snapshots to be complete + int mode = snapMode.get().present() ? snapMode.get().get() : 0; + if (mode == 2) { + // BOTH mode: need both firstSnapshotEndVersion (rangefile) and bulkDumpSnapshotEndVersion + if (!firstSnapshot.get().present() || !bulkDumpSnapshot.get().present()) { + // Incremental-only backup doesn't need snapshots + if (logVersion.present() && incrementalBackup.isReady() && + incrementalBackup.get().present() && incrementalBackup.get().get()) { + return logVersion.get() - 1; + } + return {}; // Not restorable until both complete + } + // Use the minimum of both as the effective first snapshot version + Version effectiveFirstSnapshot = + std::min(firstSnapshot.get().get(), bulkDumpSnapshot.get().get()); + if (logVersion.present() && logVersion.get() > effectiveFirstSnapshot) { + return std::max(logVersion.get() - 1, effectiveFirstSnapshot); + } + return {}; + } + + // For mode=BULKDUMP (1), use bulkDumpSnapshotEndVersion + if (mode == 1) { + if (logVersion.present() && bulkDumpSnapshot.get().present() && + logVersion.get() > bulkDumpSnapshot.get().get()) { + return std::max(logVersion.get() - 1, bulkDumpSnapshot.get().get()); + } + if (logVersion.present() && incrementalBackup.isReady() && + incrementalBackup.get().present() && incrementalBackup.get().get()) { + return logVersion.get() - 1; + } + return {}; + } + + // For mode=RANGEFILE (0, default), use existing logic with firstSnapshotEndVersion if (logVersion.present() && firstSnapshot.get().present() && logVersion.get() > firstSnapshot.get().get()) { return std::max(logVersion.get() - 1, firstSnapshot.get().get()); @@ -1012,7 +1043,8 @@ class BackupConfig : public KeyBackedTaskConfig { // Helper class for reading restore data from a buffer and throwing the right errors. struct StringRefReader { - StringRefReader(StringRef s = StringRef(), Error e = Error()) : rptr(s.begin()), end(s.end()), failure_error(e) {} + explicit StringRefReader(StringRef s = StringRef(), Error e = Error()) + : rptr(s.begin()), end(s.end()), failure_error(e) {} // Return remainder of data as a StringRef StringRef remainder() { return StringRef(rptr, end - rptr); } @@ -1052,31 +1084,22 @@ struct StringRefReader { namespace fileBackup { Standalone> decodeRangeFileBlock(const Standalone& buf); -ACTOR Future>> decodeRangeFileBlock(Reference file, - int64_t offset, - int len, - Database cx); +Future>> decodeRangeFileBlock(Reference file, + int64_t offset, + int len, + Database cx); Standalone> decodeMutationLogFileBlock(const Standalone& buf); // Reads a mutation log block from file and parses into batch mutation blocks for further parsing. -ACTOR Future>> decodeMutationLogFileBlock(Reference file, - int64_t offset, - int len); +Future>> decodeMutationLogFileBlock(Reference file, + int64_t offset, + int len); // Return a block of contiguous padding bytes "\0xff" for backup files, growing if needed. Value makePadding(int size); } // namespace fileBackup -// For fast restore simulation test -// For testing addPrefix feature in fast restore. -// Transform db content in restoreRanges by removePrefix and then addPrefix. -// Assume: DB is locked -ACTOR Future transformRestoredDatabase(Database cx, - Standalone> backupRanges, - Key addPrefix, - Key removePrefix); - void simulateBlobFailure(); // Add the set of ranges that are backed up in a default backup to the given vector. This consists of all normal keys @@ -1114,6 +1137,3 @@ bool isDefaultBackup(Container ranges) { // Returns a key-range used to denote that a shared mutation stream belongs to the default backup set. KeyRangeRef const& getDefaultBackupSharedRange(); - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/BackupContainer.h b/fdbclient/include/fdbclient/BackupContainer.h index dba89dff315..4a4c54b9195 100644 --- a/fdbclient/include/fdbclient/BackupContainer.h +++ b/fdbclient/include/fdbclient/BackupContainer.h @@ -33,8 +33,8 @@ FDB_BOOLEAN_PARAM(IncludeKeyRangeMap); class ReadYourWritesTransaction; -Future> timeKeeperEpochsFromVersion(Version const& v, Reference const& tr); -Future timeKeeperVersionFromDatetime(std::string const& datetime, Database const& db); +Future> timeKeeperEpochsFromVersion(Version v, Reference tr); +Future timeKeeperVersionFromDatetime(std::string datetime, Database db); // Helper function to check if a URL is a blobstore:// URL bool isBlobstoreUrl(const std::string& url); @@ -45,7 +45,7 @@ bool isBlobstoreUrl(const std::string& url); // TODO: Move the log file and range file format encoding/decoding stuff to this file and behind interfaces. class IBackupFile { public: - IBackupFile(const std::string& fileName) : m_fileName(fileName) {} + explicit IBackupFile(const std::string& fileName) : m_fileName(fileName) {} virtual ~IBackupFile() {} // Backup files are append-only and cannot have more than 1 append outstanding at once. virtual Future append(const void* data, int len) = 0; @@ -69,6 +69,9 @@ static const uint32_t BACKUP_AGENT_MLOG_VERSION = 2001; // Mutation log version written by BackupWorker static const uint32_t PARTITIONED_MLOG_VERSION = 4110; +// Mutation log version written by BackupWorker for range partitioned logs +static const uint32_t RANGE_PARTITIONED_MLOG_VERSION = 5001; + // Snapshot file version written by FileBackupAgent static const uint32_t BACKUP_AGENT_SNAPSHOT_FILE_VERSION = 1001; @@ -132,7 +135,9 @@ struct KeyspaceSnapshotFile { std::string fileName; int64_t totalSize; Optional restorable; // Whether or not the snapshot can be used in a restore, if known + std::string snapshotType; // "bulkdump" or "rangefile" (empty if not present in filename) bool isSingleVersion() const { return beginVersion == endVersion; } + bool isBulkDump() const { return snapshotType == "bulkdump"; } double expiredPct(Optional expiredEnd) const { double pctExpired = 0; if (expiredEnd.present() && expiredEnd.get() > beginVersion) { @@ -181,9 +186,25 @@ struct BackupFileList { void toStream(FILE* fout) const; }; +// Mutation log types for backup +enum class MutationLogType { DEFAULT = 0, PARTITIONED_LOG, RANGE_PARTITIONED_LOG }; + +inline std::string mutationLogTypeToString(MutationLogType type) { + switch (type) { + case MutationLogType::DEFAULT: + return "default"; + case MutationLogType::PARTITIONED_LOG: + return "partitioned-log-experimental"; + case MutationLogType::RANGE_PARTITIONED_LOG: + return "range-partitioned-log-experimental"; + default: + return "unknown"; + } +} + // The byte counts here only include usable log files and byte counts from kvrange manifests struct BackupDescription { - BackupDescription() : snapshotBytes(0) {} + BackupDescription() : snapshotBytes(0), mutationLogType(MutationLogType::DEFAULT) {} std::string url; Optional proxy; std::vector snapshots; @@ -203,8 +224,9 @@ struct BackupDescription { // The minimum version which this backup can be used to restore to Optional minRestorableVersion; std::string extendedDetail; // Freeform container-specific info. - bool partitioned; // If this backup contains partitioned mutation logs. + MutationLogType mutationLogType; bool fileLevelEncryption; // If this backup contains encrypted files. + int encryptionBlockSize; // Block size used for file encryption, 0 if not encrypted. // Resolves the versions above to timestamps using a given database's TimeKeeper data. // toString will use this information if present. @@ -269,6 +291,12 @@ class IBackupContainer { uint16_t tagId, int totalTags) = 0; + virtual Future> writeRangePartitionedLogFile(Version beginVersion, + Version endVersion, + Version baseVersion, + int32_t partitionId, + int blockSize) = 0; + // Write a KeyspaceSnapshotFile of range file names representing a full non overlapping // snapshot of the key ranges this backup is targeting. // For BulkDump snapshots, pass SnapshotMetadata with snapshotType="bulkdump" and the job ID. @@ -280,6 +308,9 @@ class IBackupContainer { IncludeKeyRangeMap includeKeyRangeMap, Optional metadata = Optional()) = 0; + // Write a partition list file which contains the partition info (e.g. key ranges and partition id). + virtual Future writePartitionListFile(Version v, std::string contents) = 0; + // Open a file for read by name virtual Future> readFile(const std::string& name) = 0; @@ -331,7 +362,8 @@ class IBackupContainer { // Get an IBackupContainer based on a container spec string static Reference openContainer(const std::string& url, const Optional& proxy, - const Optional& encryptionKeyFileName); + const Optional& encryptionKeyFileName, + int encryptionBlockSize); static std::vector getURLFormats(); static Future> listContainers(const std::string& baseURL, const Optional& proxy); @@ -340,12 +372,15 @@ class IBackupContainer { Optional const& getProxy() const { return proxy; } Optional const& getEncryptionKeyFileName() const { return encryptionKeyFileName; } - virtual Future writeEncryptionMetadata() = 0; + virtual Future writeEncryptionMetadata(int encryptionBlockSize) = 0; static std::string lastOpenError; virtual Future encryptionSetupComplete() const = 0; + virtual int getEncryptionBlockSize() const { return 0; } + virtual void setEncryptionBlockSize(int blockSize) {} + // TODO: change the following back to `private` once blob obj access is refactored protected: std::string URL; diff --git a/fdbclient/include/fdbclient/BackupContainerFileSystem.h b/fdbclient/include/fdbclient/BackupContainerFileSystem.h index 49450e2f99f..775f9068ba5 100644 --- a/fdbclient/include/fdbclient/BackupContainerFileSystem.h +++ b/fdbclient/include/fdbclient/BackupContainerFileSystem.h @@ -78,12 +78,6 @@ class BackupContainerFileSystem : public IBackupContainer { Future create() override = 0; Future exists() override = 0; - // TODO: refactor this to separate out the "deal with blob store" stuff from the backup business logic - static Reference openContainerFS(const std::string& url, - const Optional& proxy, - const Optional& encryptionKeyFileName, - bool isBackup = true); - // Get a list of fileNames and their sizes in the container under the given path // Although not required, an implementation can avoid traversing unwanted subfolders // by calling folderPathFilter(absoluteFolderPath) and checking for a false return value. @@ -115,6 +109,12 @@ class BackupContainerFileSystem : public IBackupContainer { uint16_t tagId, int totalTags) final; + Future> writeRangePartitionedLogFile(Version beginVersion, + Version endVersion, + Version baseVersion, + int32_t partitionId, + int blockSize) final; + Future> writeRangeFile(Version snapshotBeginVersion, int snapshotFileCount, Version fileVersion, @@ -129,9 +129,13 @@ class BackupContainerFileSystem : public IBackupContainer { IncludeKeyRangeMap IncludeKeyRangeMap, Optional metadata = Optional()) final; + Future writePartitionListFile(Version v, std::string contents) override; + // List log files, unsorted, which contain data at any version >= beginVersion and <= targetVersion. - // "partitioned" flag indicates if new partitioned mutation logs or old logs should be listed. - Future> listLogFiles(Version beginVersion, Version targetVersion, bool partitioned); + // "mutationLogType" value indicates which mutation log files should be listed. + Future> listLogFiles(Version beginVersion, + Version targetVersion, + MutationLogType mutationLogType); // List range files, unsorted, which contain data at or between beginVersion and endVersion // Note: The contents of each top level snapshot.N folder do not necessarily constitute a valid snapshot @@ -164,11 +168,14 @@ class BackupContainerFileSystem : public IBackupContainer { Version beginVersion) final; static Future createTestEncryptionKeyFile(std::string const& filename); - Future writeEncryptionMetadata() override; + Future writeEncryptionMetadata(int encryptionBlockSize) override; // Waits for encryption initialization to complete by reading encryption key file during container opening. Future encryptionSetupComplete() const override; + int getEncryptionBlockSize() const override { return encryptionBlockSize; } + void setEncryptionBlockSize(int blockSize) override { encryptionBlockSize = blockSize; } + protected: // Returns true if an encryption key file was provided. bool usesEncryption() const; @@ -177,6 +184,8 @@ class BackupContainerFileSystem : public IBackupContainer { Future writeEntireFileFallback(const std::string& fileName, const std::string& fileContents); + int encryptionBlockSize = 0; + private: struct VersionProperty { VersionProperty(Reference bc, const std::string& name) @@ -203,7 +212,8 @@ class BackupContainerFileSystem : public IBackupContainer { VersionProperty expiredEndVersion(); VersionProperty unreliableEndVersion(); VersionProperty logType(); - VersionProperty fileLevelEncryption(); + + static std::string encryptionMetadataFileName(); // List range files, unsorted, which contain data at or between beginVersion and endVersion // NOTE: This reads the range file folder schema from FDB 6.0.15 and earlier and is provided for backward diff --git a/fdbclient/include/fdbclient/BuildIdempotencyIdMutations.h b/fdbclient/include/fdbclient/BuildIdempotencyIdMutations.h index d6caade37cf..08fa4a8a24c 100644 --- a/fdbclient/include/fdbclient/BuildIdempotencyIdMutations.h +++ b/fdbclient/include/fdbclient/BuildIdempotencyIdMutations.h @@ -22,7 +22,7 @@ #define FDBCLIENT_BUILD_IDEMPOTENCY_ID_MUTATIONS_H #include "fdbclient/CommitProxyInterface.h" -#include "fdbclient/IdempotencyId.actor.h" +#include "fdbclient/IdempotencyId.h" #pragma once diff --git a/fdbclient/include/fdbclient/BulkDumping.h b/fdbclient/include/fdbclient/BulkDumping.h index 1aa56b4ff53..5751bf2b7ab 100644 --- a/fdbclient/include/fdbclient/BulkDumping.h +++ b/fdbclient/include/fdbclient/BulkDumping.h @@ -141,6 +141,8 @@ struct BulkDumpState { BulkLoadManifest getManifest() const { return manifest; } + double getSubmitTime() const { return now(); } + template void serialize(Ar& ar) { serializer(ar, jobId, jobRange, phase, taskId, manifest); diff --git a/fdbclient/include/fdbclient/BulkLoading.h b/fdbclient/include/fdbclient/BulkLoading.h index f1b2fa6b372..61a37ef9a13 100644 --- a/fdbclient/include/fdbclient/BulkLoading.h +++ b/fdbclient/include/fdbclient/BulkLoading.h @@ -23,8 +23,9 @@ #include "flow/Error.h" #include "flow/IRandom.h" #include "flow/Platform.h" -#include "flow/TDMetric.actor.h" +#include "flow/TDMetric.h" #include "flow/Trace.h" +#include "flow/Util.h" #include #include #pragma once @@ -188,7 +189,7 @@ struct BulkLoadFileSet { } } - BulkLoadFileSet(const std::string& rootPath) : rootPath(rootPath) { + explicit BulkLoadFileSet(const std::string& rootPath) : rootPath(rootPath) { if (rootPath.empty()) { TraceEvent(SevError, "BulkLoadFileSetProvideInvalidPath") .suppressFor(10.0) @@ -379,7 +380,7 @@ struct BulkLoadManifest { // [ByteSampleOverhead]: 100, [ByteSampleMinimalProbability]: 0.500000, [loadType]: 1, [TransportMethod]: 1" // To decode the string, we firstly split the string by ", ". Then, for each part, we remove "[*]: ". Finally, we // convert the remain string for each part to the fields defined in the BulkLoadManifest. - BulkLoadManifest(const std::string& rawString) { + explicit BulkLoadManifest(const std::string& rawString) { try { std::vector parts = splitString(rawString, ", "); formatVersion = std::stoi(stringRemovePrefix(parts[0], "[FormatVersion]: ")); @@ -565,7 +566,7 @@ struct BulkLoadManifestSet { BulkLoadManifestSet() = default; - BulkLoadManifestSet(int inputMaxCount) { maxCount = inputMaxCount; } + explicit BulkLoadManifestSet(int inputMaxCount) { maxCount = inputMaxCount; } bool isValid() const { if (maxCount == 0) { @@ -866,7 +867,7 @@ struct BulkLoadJobState { ", [JobRange]: " + jobRange.toString() + ", [Phase]: " + convertBulkLoadJobPhaseToString(phase) + ", [TransportMethod]: " + convertBulkLoadTransportMethodToString(transportMethod); - res = res + ", [SubmitTime]: " + std::to_string(submitTime); + res = res + ", [SubmitTime]: " + formatTimeISO8601(submitTime); res = res + ", [SinceSubmitMins]: " + std::to_string((now() - submitTime) / 60.0); if (taskCount.present()) { res = res + ", [TaskCount]: " + std::to_string(taskCount.get()); @@ -964,7 +965,7 @@ struct BulkLoadJobManifestFileHeader { // "[FormatVersion]: 1, [ManifestCount]: 2" // To decode the string, we firstly split the string by ", ". Then, for each part, we remove "[*]: ". Finally, // we convert the remain string for each part to the fields defined in the BulkLoadJobManifestFileHeader. - BulkLoadJobManifestFileHeader(const std::string& rawString) { + explicit BulkLoadJobManifestFileHeader(const std::string& rawString) { try { std::vector parts = splitString(rawString, ", "); formatVersion = std::stoi(stringRemovePrefix(parts[0], "[FormatVersion]: ")); @@ -1016,7 +1017,7 @@ struct BulkLoadJobFileManifestEntry { // 50705947, [Bytes]: 6889" To decode the string, we firstly split the string by ", ". Then, for each part, we // remove "[*]: ". Finally, we convert the remain string for each part to the fields defined in the // BulkLoadJobFileManifestEntry. - BulkLoadJobFileManifestEntry(const std::string& rawString) { + explicit BulkLoadJobFileManifestEntry(const std::string& rawString) { try { std::vector parts = splitString(rawString, ", "); if (parts.size() != 5) { @@ -1042,7 +1043,7 @@ struct BulkLoadJobFileManifestEntry { } // Used when dumping - BulkLoadJobFileManifestEntry(const BulkLoadManifest& manifest) + explicit BulkLoadJobFileManifestEntry(const BulkLoadManifest& manifest) : beginKey(manifest.getBeginKey()), endKey(manifest.getEndKey()), manifestRelativePath(manifest.getManifestRelativePath()), version(manifest.getVersion()), bytes(manifest.getTotalBytes()) { @@ -1093,7 +1094,7 @@ struct SSBulkLoadMetadata { SSBulkLoadMetadata() : dataMoveId(UID()), conductBulkLoad(false) {}; - SSBulkLoadMetadata(const UID& dataMoveId) : dataMoveId(dataMoveId) { + explicit SSBulkLoadMetadata(const UID& dataMoveId) : dataMoveId(dataMoveId) { conductBulkLoad = getConductBulkLoadFromDataMoveId(dataMoveId); return; } @@ -1159,4 +1160,4 @@ BulkLoadJobState createBulkLoadJob(const UID& dumpJobIdToLoad, const KeyRange& range, const std::string& jobRoot, const BulkLoadTransportMethod& transportMethod); -#endif \ No newline at end of file +#endif diff --git a/fdbclient/include/fdbclient/ClientKnobCollection.h b/fdbclient/include/fdbclient/ClientKnobCollection.h deleted file mode 100644 index 0002e181436..00000000000 --- a/fdbclient/include/fdbclient/ClientKnobCollection.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * ClientKnobCollection.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBCLIENT_CLIENTKNOBCOLLECTION_H -#define FDBCLIENT_CLIENTKNOBCOLLECTION_H - -#pragma once - -#include "fdbclient/ClientKnobs.h" -#include "fdbclient/IKnobCollection.h" -#include "flow/Knobs.h" - -/* - * Stores both flow knobs and client knobs, attempting to access server knobs or test knobs - * results in a run-time error - */ -class ClientKnobCollection : public IKnobCollection { - FlowKnobs flowKnobs; - ClientKnobs clientKnobs; - -public: - ClientKnobCollection(Randomize randomize, IsSimulated isSimulated); - void initialize(Randomize randomize, IsSimulated isSimulated) override; - void reset(Randomize randomize, IsSimulated isSimulated) override; - FlowKnobs const& getFlowKnobs() const override { return flowKnobs; } - ClientKnobs const& getClientKnobs() const override { return clientKnobs; } - ClientKnobs& getMutableClientKnobs() { return clientKnobs; } - ServerKnobs const& getServerKnobs() const override { throw internal_error(); } - TestKnobs const& getTestKnobs() const override { throw internal_error(); } - Optional tryParseKnobValue(std::string const& knobName, std::string const& knobValue) const override; - bool trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) override; -}; - -#endif // FDBCLIENT_CLIENTKNOBCCOLLECTION_H diff --git a/fdbclient/include/fdbclient/ClientKnobs.h b/fdbclient/include/fdbclient/ClientKnobs.h deleted file mode 100644 index 5bc1ea35301..00000000000 --- a/fdbclient/include/fdbclient/ClientKnobs.h +++ /dev/null @@ -1,352 +0,0 @@ -/* - * ClientKnobs.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBCLIENT_KNOBS_H -#define FDBCLIENT_KNOBS_H -#pragma once - -#include "flow/BooleanParam.h" -#include "flow/Knobs.h" -#include "flow/flow.h" - -FDB_BOOLEAN_PARAM(Randomize); -FDB_BOOLEAN_PARAM(IsSimulated); - -// Returns a deterministically random transaction timeout value for simulation testing. -// More weight is given to the famous 5s timeout, but [1, 10] range is returned with lower weight. -// This is only be used in simulation. -int getSimulatedTxnTimeoutSeconds(); - -class SWIFT_CXX_IMMORTAL_SINGLETON_TYPE ClientKnobs : public KnobsImpl { -public: - int TOO_MANY; // FIXME: this should really be split up so we can control these more specifically - - double SYSTEM_MONITOR_INTERVAL; - double NETWORK_BUSYNESS_MONITOR_INTERVAL; // The interval in which we should update the network busyness metric - double TSS_METRICS_LOGGING_INTERVAL; - - double FAILURE_MAX_DELAY; - double FAILURE_MIN_DELAY; - double RECOVERY_DELAY_START_GENERATION; - double RECOVERY_DELAY_SECONDS_PER_GENERATION; - double MAX_GENERATIONS; - double MAX_GENERATIONS_OVERRIDE; - double MAX_GENERATIONS_SIM; - - double COORDINATOR_RECONNECTION_DELAY; - int CLIENT_EXAMPLE_AMOUNT; - double MAX_CLIENT_STATUS_AGE; - int MAX_COMMIT_PROXY_CONNECTIONS; - int MAX_GRV_PROXY_CONNECTIONS; - double STATUS_IDLE_TIMEOUT; - double GRPC_CTL_SERVICE_DEFAULT_TIMEOUT; // Default timeout for gRPC FDBCTL service requests - bool SEND_ENTIRE_VERSION_VECTOR; - - // wrong_shard_server sometimes comes from the only nonfailed server, so we need to avoid a fast spin - double WRONG_SHARD_SERVER_DELAY; // SOMEDAY: This delay can limit performance of retrieving data when the cache is - // mostly wrong (e.g. dumping the database after a test) - double FUTURE_VERSION_RETRY_DELAY; - double GRV_ERROR_RETRY_DELAY; - int REPLY_BYTE_LIMIT; - double DEFAULT_BACKOFF; - double DEFAULT_MAX_BACKOFF; - double BACKOFF_GROWTH_RATE; - double RESOURCE_CONSTRAINED_MAX_BACKOFF; - int PROXY_COMMIT_OVERHEAD_BYTES; - double SHARD_STAT_SMOOTH_AMOUNT; - int INIT_MID_SHARD_BYTES; - - int TRANSACTION_SIZE_LIMIT; - int64_t KEY_SIZE_LIMIT; - int64_t SYSTEM_KEY_SIZE_LIMIT; - int64_t VALUE_SIZE_LIMIT; - int64_t SPLIT_KEY_SIZE_LIMIT; - int METADATA_VERSION_CACHE_SIZE; - int64_t CHANGE_FEED_LOCATION_LIMIT; - int64_t CHANGE_FEED_CACHE_SIZE; - double CHANGE_FEED_POP_TIMEOUT; - int64_t CHANGE_FEED_STREAM_MIN_BYTES; - double CHANGE_FEED_START_INTERVAL; - bool CHANGE_FEED_COALESCE_LOCATIONS; - int64_t CHANGE_FEED_CACHE_FLUSH_BYTES; - double CHANGE_FEED_CACHE_EXPIRE_TIME; - int64_t CHANGE_FEED_CACHE_LIMIT_BYTES; - - int MAX_BATCH_SIZE; - double GRV_BATCH_TIMEOUT; - int BROADCAST_BATCH_SIZE; - double TRANSACTION_TIMEOUT_DELAY_INTERVAL; - - // When locationCache in DatabaseContext gets to be this size, items will be evicted - int LOCATION_CACHE_EVICTION_SIZE; - int LOCATION_CACHE_EVICTION_SIZE_SIM; - double LOCATION_CACHE_ENDPOINT_FAILURE_GRACE_PERIOD; - double LOCATION_CACHE_FAILED_ENDPOINT_RETRY_INTERVAL; - - int GET_RANGE_SHARD_LIMIT; - int WARM_RANGE_SHARD_LIMIT; - int STORAGE_METRICS_SHARD_LIMIT; - int SHARD_COUNT_LIMIT; - double STORAGE_METRICS_UNFAIR_SPLIT_LIMIT; - double STORAGE_METRICS_TOO_MANY_SHARDS_DELAY; - double AGGREGATE_HEALTH_METRICS_MAX_STALENESS; - double DETAILED_HEALTH_METRICS_MAX_STALENESS; - double MID_SHARD_SIZE_MAX_STALENESS; - bool TAG_ENCODE_KEY_SERVERS; - int64_t RANGESTREAM_FRAGMENT_SIZE; - int RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT; - bool QUARANTINE_TSS_ON_MISMATCH; - double CHANGE_FEED_EMPTY_BATCH_TIME; - - // KeyRangeMap - int KRM_GET_RANGE_LIMIT; - int KRM_GET_RANGE_LIMIT_BYTES; // This must be sufficiently larger than KEY_SIZE_LIMIT to ensure that at least two - // entries will be returned from an attempt to read a key range map - - int DEFAULT_MAX_OUTSTANDING_WATCHES; - int ABSOLUTE_MAX_WATCHES; // The client cannot set the max outstanding watches higher than this - double WATCH_POLLING_TIME; - double NO_RECENT_UPDATES_DURATION; - double FAST_WATCH_TIMEOUT; - double WATCH_TIMEOUT; - - double IS_ACCEPTABLE_DELAY; - - // Versions -- knobs that control 5s timeout - int64_t VERSIONS_PER_SECOND; // Copy of SERVER_KNOBS, as we can't link with it. - int64_t MAX_WRITE_TRANSACTION_LIFE_VERSIONS; // Copy of SERVER_KNOBS, as we can't link with it. - - // Core - int64_t CORE_VERSIONSPERSECOND; // This is defined within the server but used for knobs based on server value - int LOG_RANGE_BLOCK_SIZE; - int MUTATION_BLOCK_SIZE; - double MAX_VERSION_CACHE_LAG; // The upper bound in seconds for OK amount of staleness when using a cached RV - double MAX_PROXY_CONTACT_LAG; // The upper bound in seconds for how often we want a response from the GRV proxies - double DEBUG_USE_GRV_CACHE_CHANCE; // Debug setting to change the chance for a regular GRV request to use the cache - bool FORCE_GRV_CACHE_OFF; // Panic button to turn off cache. Holds priority over other options. - double GRV_CACHE_RK_COOLDOWN; // Required number of seconds to pass after throttling to re-allow cache use - double GRV_SUSTAINED_THROTTLING_THRESHOLD; // If ALL GRV requests have been throttled in the last number of seconds - // specified here, ratekeeper is throttling and not a false positive - - // Taskbucket - double TASKBUCKET_LOGGING_DELAY; - int TASKBUCKET_MAX_PRIORITY; - double TASKBUCKET_CHECK_TIMEOUT_CHANCE; - double TASKBUCKET_TIMEOUT_JITTER_OFFSET; - double TASKBUCKET_TIMEOUT_JITTER_RANGE; - double TASKBUCKET_CHECK_ACTIVE_DELAY; - int TASKBUCKET_CHECK_ACTIVE_AMOUNT; - int TASKBUCKET_TIMEOUT_VERSIONS; - int TASKBUCKET_MAX_TASK_KEYS; - - // Backup - int BACKUP_LOCAL_FILE_WRITE_BLOCK; - int BACKUP_CONCURRENT_DELETES; - int BACKUP_SIMULATED_LIMIT_BYTES; - int BACKUP_GET_RANGE_LIMIT_BYTES; - int BACKUP_LOCK_BYTES; - double BACKUP_RANGE_TIMEOUT; - double BACKUP_RANGE_MINWAIT; - double BULKDUMP_JOB_TIMEOUT; - double BULKLOAD_JOB_TIMEOUT; - int BACKUP_SNAPSHOT_DISPATCH_INTERVAL_SEC; - int BACKUP_DEFAULT_SNAPSHOT_INTERVAL_SEC; - int BACKUP_SHARD_TASK_LIMIT; - double BACKUP_AGGREGATE_POLL_RATE; - double BACKUP_AGGREGATE_POLL_RATE_UPDATE_INTERVAL; - int BACKUP_LOG_WRITE_BATCH_MAX_SIZE; - int BACKUP_MAX_LOG_RANGES; - int BACKUP_SIM_COPY_LOG_RANGES; - int BACKUP_VERSION_DELAY; - int BACKUP_MAP_KEY_LOWER_LIMIT; - int BACKUP_MAP_KEY_UPPER_LIMIT; - int BACKUP_COPY_TASKS; - int BACKUP_BLOCK_SIZE; - int COPY_LOG_BLOCK_SIZE; - int COPY_LOG_BLOCKS_PER_TASK; - int COPY_LOG_PREFETCH_BLOCKS; - int COPY_LOG_READ_AHEAD_BYTES; - double COPY_LOG_TASK_DURATION_NANOS; - int BACKUP_TASKS_PER_AGENT; - int BACKUP_POLL_PROGRESS_SECONDS; - int SIM_BACKUP_TASKS_PER_AGENT; - int BACKUP_RANGEFILE_BLOCK_SIZE; - int BACKUP_LOGFILE_BLOCK_SIZE; - int BACKUP_DISPATCH_ADDTASK_SIZE; - bool BACKUP_ALLOW_DRYRUN; - int RESTORE_DISPATCH_ADDTASK_SIZE; - int RESTORE_DISPATCH_BATCH_SIZE; - int RESTORE_WRITE_TX_SIZE; - int RESTORE_PARTITIONED_BATCH_VERSION_SIZE; - int APPLY_MAX_LOCK_BYTES; - int APPLY_MIN_LOCK_BYTES; - int APPLY_BLOCK_SIZE; - double APPLY_MAX_DECAY_RATE; - double APPLY_MAX_INCREASE_FACTOR; - double BACKUP_ERROR_DELAY; - double BACKUP_STATUS_DELAY; - double BACKUP_STATUS_JITTER; - double MIN_CLEANUP_SECONDS; - int64_t FASTRESTORE_ATOMICOP_WEIGHT; // workload amplication factor for atomic op - int RESTORE_RANGES_READ_BATCH; - - bool BACKUP_CONTAINER_LOCAL_ALLOW_RELATIVE_PATH; - bool ENABLE_REPLICA_CONSISTENCY_CHECK_ON_BACKUP_READS; - int BACKUP_CONSISTENCY_CHECK_REQUIRED_REPLICAS; - bool BACKUP_READS_USE_LOW_PRIORITY; - int BULKLOAD_JOB_HISTORY_COUNT_MAX; // the max number of bulk load job history to keep. The oldest job history will - // be removed when the count exceeds this value. Set to 0 to disable history. - // Do not set the value to a large number, e.g. <= 10. - int BULKLOAD_VERBOSE_LEVEL; // Set to 1 to minimize the verbosity. Set to 5 to turn on all events for performance - // insights. Set to 10 to turn on all events. - int S3CLIENT_VERBOSE_LEVEL; // Similar to BULKLOAD_VERBOSE_LEVEL - - // Configuration - int32_t DEFAULT_AUTO_COMMIT_PROXIES; - int32_t DEFAULT_AUTO_GRV_PROXIES; - int32_t DEFAULT_COMMIT_GRV_PROXIES_RATIO; - int32_t DEFAULT_MAX_GRV_PROXIES; - int32_t DEFAULT_AUTO_RESOLVERS; - int32_t DEFAULT_AUTO_LOGS; - - double GLOBAL_CONFIG_REFRESH_BACKOFF; - double GLOBAL_CONFIG_REFRESH_MAX_BACKOFF; - double GLOBAL_CONFIG_REFRESH_TIMEOUT; - - // Dynamic Knobs - double COMMIT_QUORUM_TIMEOUT; - double GET_GENERATION_QUORUM_TIMEOUT; - double GET_KNOB_TIMEOUT; - double TIMEOUT_RETRY_UPPER_BOUND; - - // Client Status Info - double CSI_SAMPLING_PROBABILITY; - int64_t CSI_SIZE_LIMIT; - double CSI_STATUS_DELAY; - - bool HTTP_REQUEST_AWS_V4_HEADER; // setting this knob to true will enable AWS V4 style header. - std::string BLOBSTORE_ENCRYPTION_TYPE; - int BLOBSTORE_CONNECT_TRIES; - int BLOBSTORE_CONNECT_TIMEOUT; - int BLOBSTORE_MAX_CONNECTION_LIFE; - int BLOBSTORE_REQUEST_TRIES; - int BLOBSTORE_REQUEST_TIMEOUT_MIN; - int BLOBSTORE_REQUESTS_PER_SECOND; - int BLOBSTORE_LIST_REQUESTS_PER_SECOND; - int BLOBSTORE_WRITE_REQUESTS_PER_SECOND; - int BLOBSTORE_READ_REQUESTS_PER_SECOND; - int BLOBSTORE_DELETE_REQUESTS_PER_SECOND; - int BLOBSTORE_CONCURRENT_REQUESTS; - int BLOBSTORE_MULTIPART_MAX_PART_SIZE; - int BLOBSTORE_MULTIPART_MIN_PART_SIZE; - int BLOBSTORE_MULTIPART_RETRY_DELAY_MS; - int BLOBSTORE_CONCURRENT_UPLOADS; - int BLOBSTORE_CONCURRENT_LISTS; - int BLOBSTORE_CONCURRENT_WRITES_PER_FILE; - int BLOBSTORE_CONCURRENT_READS_PER_FILE; - int BLOBSTORE_ENABLE_READ_CACHE; - int BLOBSTORE_READ_BLOCK_SIZE; - int BLOBSTORE_READ_AHEAD_BLOCKS; - int BLOBSTORE_READ_CACHE_BLOCKS_PER_FILE; - int BLOBSTORE_MAX_SEND_BYTES_PER_SECOND; - int BLOBSTORE_MAX_RECV_BYTES_PER_SECOND; - int BLOBSTORE_LIST_MAX_KEYS_PER_PAGE; - bool BLOBSTORE_GLOBAL_CONNECTION_POOL; - bool BLOBSTORE_ENABLE_LOGGING; - double BLOBSTORE_STATS_LOGGING_INTERVAL; - double BLOBSTORE_LATENCY_LOGGING_INTERVAL; - double BLOBSTORE_LATENCY_LOGGING_ACCURACY; - int BLOBSTORE_MAX_DELAY_RETRYABLE_ERROR; - int BLOBSTORE_MAX_DELAY_CONNECTION_FAILED; - bool - BLOBSTORE_ENABLE_OBJECT_INTEGRITY_CHECK; // Enable integrity check of download. When not set, on upload, we - // we volunteer an md5 of the content and upload will fail if our - // md5 doesn't match that calculated by the server. When this flag - // is set, we hash the content using sha256 and have the server store - // the hash in the object metadata. On download, if this flag is set - // we will check the serverside proffered hash against that we - // calculate on the received content. If no match, throw an error. See - // https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html - // This download check only works for small files. For large files, - // we run a separate checksum. See design/s3-checksumming.md - // - int CONSISTENCY_CHECK_RATE_LIMIT_MAX; // Available in both normal and urgent mode - int CONSISTENCY_CHECK_ONE_ROUND_TARGET_COMPLETION_TIME; // Available in normal mode - int CONSISTENCY_CHECK_URGENT_NEXT_WAIT_TIME; // Available in urgent mode - int CONSISTENCY_CHECK_URGENT_BATCH_SHARD_COUNT; // Available in urgent mode - int CONSISTENCY_CHECK_URGENT_RETRY_DEPTH_MAX; // Available in urgent mode - std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_0; // Available in urgent mode - std::string CONSISTENCY_CHECK_URGENT_RANGE_END_0; // Available in urgent mode - std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_1; // Available in urgent mode - std::string CONSISTENCY_CHECK_URGENT_RANGE_END_1; // Available in urgent mode - std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_2; - std::string CONSISTENCY_CHECK_URGENT_RANGE_END_2; - std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_3; - std::string CONSISTENCY_CHECK_URGENT_RANGE_END_3; - - // fdbcli - int CLI_CONNECT_PARALLELISM; - double CLI_CONNECT_TIMEOUT; - bool CLI_PRINT_INVALID_CONFIGURATION; - - // trace - int TRACE_LOG_FILE_IDENTIFIER_MAX_LENGTH; - - // transaction tags - int MAX_TRANSACTION_TAG_LENGTH; - int MAX_TAGS_PER_TRANSACTION; - int COMMIT_SAMPLE_COST; // The expectation of sampling is every COMMIT_SAMPLE_COST sample once - int INCOMPLETE_SHARD_PLUS; // The size of (possible) incomplete shard when estimate clear range - double READ_TAG_SAMPLE_RATE; // Communicated to clients from cluster - double TAG_THROTTLE_SMOOTHING_WINDOW; - double TAG_THROTTLE_RECHECK_INTERVAL; - double TAG_THROTTLE_EXPIRATION_INTERVAL; - int64_t TAG_THROTTLING_PAGE_SIZE; // Used to round up the cost of operations - // Cost multiplier for writes (because write operations are more expensive than reads): - double GLOBAL_TAG_THROTTLING_RW_FUNGIBILITY_RATIO; - // Maximum duration that a transaction can be tag throttled by proxy before being rejected - double PROXY_MAX_TAG_THROTTLE_DURATION; - - // Enable to automatically retry transactions in the presence of transaction_lock_rejection error - // Set to false only for the rangeLocking simulation test - bool TRANSACTION_LOCK_REJECTION_RETRIABLE; - - // busyness reporting - double BUSYNESS_SPIKE_START_THRESHOLD; - double BUSYNESS_SPIKE_SATURATED_THRESHOLD; - - // The coordinator key/value in storage server might be inconsistent to the value stored in the cluster file. - // This might happen when a recovery is happening together with a cluster controller coordinator key change. - // During this process the database will be marked as "bad state" in changeQuorumChecker while later it will be - // available again. Using a backoffed retry when it happens. - int CHANGE_QUORUM_BAD_STATE_RETRY_TIMES; - double CHANGE_QUORUM_BAD_STATE_RETRY_DELAY; - - bool ENABLE_MUTATION_CHECKSUM; - // Enable to start accumulative checksum population and validation - bool ENABLE_ACCUMULATIVE_CHECKSUM; - // Enable to logging verbose trace events related to the accumulative checksum - bool ENABLE_ACCUMULATIVE_CHECKSUM_LOGGING; - - ClientKnobs(Randomize randomize, IsSimulated isSimulated); - void initialize(Randomize randomize, IsSimulated isSimulated); -}; - -#endif diff --git a/fdbclient/include/fdbclient/ClientVersion.h b/fdbclient/include/fdbclient/ClientVersion.h index 6de3f065913..5b0d2535923 100644 --- a/fdbclient/include/fdbclient/ClientVersion.h +++ b/fdbclient/include/fdbclient/ClientVersion.h @@ -36,7 +36,7 @@ struct ClientVersionRef { protocolVersion(arena, cv.protocolVersion) {} ClientVersionRef(StringRef clientVersion, StringRef sourceVersion, StringRef protocolVersion) : clientVersion(clientVersion), sourceVersion(sourceVersion), protocolVersion(protocolVersion) {} - ClientVersionRef(StringRef versionString) { + explicit ClientVersionRef(StringRef versionString) { std::vector parts = versionString.splitAny(","_sr); if (parts.size() != 3) { initUnknown(); diff --git a/fdbclient/include/fdbclient/CommitProxyInterface.h b/fdbclient/include/fdbclient/CommitProxyInterface.h index 983c6de7a5b..05fe9d81919 100644 --- a/fdbclient/include/fdbclient/CommitProxyInterface.h +++ b/fdbclient/include/fdbclient/CommitProxyInterface.h @@ -27,15 +27,10 @@ #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/EncryptKeyProxyInterface.h" #include "fdbclient/GlobalConfig.h" #include "fdbclient/GrvProxyInterface.h" -#include "fdbclient/IdempotencyId.actor.h" +#include "fdbclient/IdempotencyId.h" #include "fdbclient/StorageServerInterface.h" -#include "fdbclient/TagThrottle.actor.h" -#include "fdbclient/VersionVector.h" -#include "fdbrpc/Stats.h" -#include "fdbrpc/TimedRequest.h" struct CommitProxyInterface { constexpr static FileIdentifier file_identifier = 8954922; @@ -45,11 +40,10 @@ struct CommitProxyInterface { Optional processId; bool provisional; PublicRequestStream commit; - PublicRequestStream - getConsistentReadVersion; // Returns a version which (1) is committed, and (2) is >= the latest version reported - // committed (by a commit response) when this request was sent - // (at some point between when this request is sent and when its response is - // received, the latest version reported committed) + // Reserved to preserve the historical adjusted-endpoint numbering for the + // commit proxy interface. Commit proxies do not serve GRV traffic; clients + // use GrvProxyInterface::getConsistentReadVersion for that path. + PublicRequestStream legacyGetConsistentReadVersion; PublicRequestStream getKeyServersLocations; RequestStream getStorageServerRejoinInfo; @@ -74,7 +68,7 @@ struct CommitProxyInterface { void serialize(Archive& ar) { serializer(ar, processId, provisional, commit); if (Archive::isDeserializing) { - getConsistentReadVersion = + legacyGetConsistentReadVersion = PublicRequestStream(commit.getEndpoint().getAdjustedEndpoint(1)); getKeyServersLocations = PublicRequestStream(commit.getEndpoint().getAdjustedEndpoint(2)); @@ -98,7 +92,7 @@ struct CommitProxyInterface { void initEndpoints() { std::vector> streams; streams.push_back(commit.getReceiver(TaskPriority::ReadSocket)); - streams.push_back(getConsistentReadVersion.getReceiver(TaskPriority::ReadSocket)); + streams.push_back(legacyGetConsistentReadVersion.getReceiver(TaskPriority::ReadSocket)); streams.push_back(getKeyServersLocations.getReceiver( TaskPriority::ReadSocket)); // priority lowered to TaskPriority::DefaultEndpoint on the proxy streams.push_back(getStorageServerRejoinInfo.getReceiver(TaskPriority::ProxyStorageRejoin)); @@ -126,7 +120,6 @@ struct ClientDBInfo { Optional forward; std::vector history; UID clusterId; - Optional encryptKeyProxy; ClusterType clusterType = ClusterType::STANDALONE; @@ -140,7 +133,7 @@ struct ClientDBInfo { if constexpr (!is_fb_function) { ASSERT(ar.protocolVersion().isValid()); } - serializer(ar, grvProxies, commitProxies, id, forward, history, encryptKeyProxy, clusterId, clusterType); + serializer(ar, grvProxies, commitProxies, id, forward, history, clusterId, clusterType); } }; @@ -208,7 +201,7 @@ struct CommitTransactionRequest : TimedRequest { bool verify() const { return true; } CommitTransactionRequest() : CommitTransactionRequest(SpanContext()) {} - CommitTransactionRequest(SpanContext const& context) : spanContext(context), flags(0) {} + explicit CommitTransactionRequest(SpanContext const& context) : spanContext(context), flags(0) {} template void serialize(Ar& ar) { @@ -230,121 +223,6 @@ static inline int getBytes(CommitTransactionRequest const& r) { return total; } -struct GetReadVersionReply : public BasicLoadBalancedReply { - constexpr static FileIdentifier file_identifier = 15709388; - Version version; - bool locked; - Optional metadataVersion; - int64_t midShardSize = 0; - bool rkDefaultThrottled = false; - bool rkBatchThrottled = false; - - TransactionTagMap tagThrottleInfo; - double proxyTagThrottledDuration{ 0.0 }; - - VersionVector ssVersionVectorDelta; - UID proxyId; // GRV proxy ID to detect old GRV proxies at client side - - GetReadVersionReply() : version(invalidVersion), locked(false) {} - - template - void serialize(Ar& ar) { - serializer(ar, - BasicLoadBalancedReply::processBusyTime, - version, - locked, - metadataVersion, - tagThrottleInfo, - midShardSize, - rkDefaultThrottled, - rkBatchThrottled, - ssVersionVectorDelta, - proxyId, - proxyTagThrottledDuration); - } -}; - -struct GetReadVersionRequest : TimedRequest { - constexpr static FileIdentifier file_identifier = 838566; - enum { - PRIORITY_SYSTEM_IMMEDIATE = - 15 << 24, // Highest possible priority, always executed even if writes are otherwise blocked - PRIORITY_DEFAULT = 8 << 24, - PRIORITY_BATCH = 1 << 24 - }; - enum { - FLAG_USE_MIN_KNOWN_COMMITTED_VERSION = 4, - FLAG_USE_PROVISIONAL_PROXIES = 2, - FLAG_CAUSAL_READ_RISKY = 1, - FLAG_PRIORITY_MASK = PRIORITY_SYSTEM_IMMEDIATE, - }; - - SpanContext spanContext; - uint32_t transactionCount; - uint32_t flags; - TransactionPriority priority; - - TransactionTagMap tags; - // Not serialized, because this field does not need to be sent to master. - // It is used for reporting to clients the amount of time spent delayed by - // the TagQueue - double proxyTagThrottledDuration{ 0.0 }; - - Optional debugID; - ReplyPromise reply; - - Version maxVersion; // max version in the client's version vector cache - - GetReadVersionRequest() : transactionCount(1), flags(0), maxVersion(invalidVersion) {} - GetReadVersionRequest(SpanContext spanContext, - uint32_t transactionCount, - TransactionPriority priority, - Version maxVersion, - uint32_t flags = 0, - TransactionTagMap tags = TransactionTagMap(), - Optional debugID = Optional()) - : spanContext(spanContext), transactionCount(transactionCount), flags(flags), priority(priority), tags(tags), - debugID(debugID), maxVersion(maxVersion) { - flags = flags & ~FLAG_PRIORITY_MASK; - switch (priority) { - case TransactionPriority::BATCH: - flags |= PRIORITY_BATCH; - break; - case TransactionPriority::DEFAULT: - flags |= PRIORITY_DEFAULT; - break; - case TransactionPriority::IMMEDIATE: - flags |= PRIORITY_SYSTEM_IMMEDIATE; - break; - default: - ASSERT(false); - } - } - - bool verify() const { return true; } - - bool operator<(GetReadVersionRequest const& rhs) const { return priority < rhs.priority; } - - bool isTagged() const { return !tags.empty(); } - - template - void serialize(Ar& ar) { - serializer(ar, transactionCount, flags, tags, debugID, reply, spanContext, maxVersion); - - if (ar.isDeserializing) { - if ((flags & PRIORITY_SYSTEM_IMMEDIATE) == PRIORITY_SYSTEM_IMMEDIATE) { - priority = TransactionPriority::IMMEDIATE; - } else if ((flags & PRIORITY_DEFAULT) == PRIORITY_DEFAULT) { - priority = TransactionPriority::DEFAULT; - } else if ((flags & PRIORITY_BATCH) == PRIORITY_BATCH) { - priority = TransactionPriority::BATCH; - } else { - priority = TransactionPriority::DEFAULT; - } - } - } -}; - struct GetKeyServerLocationsReply { constexpr static FileIdentifier file_identifier = 10636023; Arena arena; @@ -597,38 +475,6 @@ struct ExclusionSafetyCheckRequest { } }; -struct GlobalConfigRefreshReply { - constexpr static FileIdentifier file_identifier = 12680327; - Arena arena; - Version version; - RangeResultRef result; - - GlobalConfigRefreshReply() {} - GlobalConfigRefreshReply(Arena const& arena, Version version, RangeResultRef result) - : arena(arena), version(version), result(result) {} - - template - void serialize(Ar& ar) { - serializer(ar, result, version, arena); - } -}; - -struct GlobalConfigRefreshRequest { - constexpr static FileIdentifier file_identifier = 2828131; - Version lastKnown; - ReplyPromise reply; - - GlobalConfigRefreshRequest() {} - explicit GlobalConfigRefreshRequest(Version lastKnown) : lastKnown(lastKnown) {} - - bool verify() const noexcept { return true; } - - template - void serialize(Ar& ar) { - serializer(ar, lastKnown, reply); - } -}; - struct SetThrottledShardReply { constexpr static FileIdentifier file_identifier = 2828140; diff --git a/fdbclient/include/fdbclient/CommitTransaction.h b/fdbclient/include/fdbclient/CommitTransaction.h index 26922fde2cd..968766b3cae 100644 --- a/fdbclient/include/fdbclient/CommitTransaction.h +++ b/fdbclient/include/fdbclient/CommitTransaction.h @@ -113,16 +113,6 @@ struct MutationRef { (accumulativeChecksumIndex.present() ? sizeof(uint16_t) + 1 : 1); } int expectedSize() const { return param1.size() + param2.size(); } - int weightedTotalSize() const { - // AtomicOp can cause more workload to FDB cluster than the same-size set mutation; - // Amplify atomicOp size to consider such extra workload. - // A good value for FASTRESTORE_ATOMICOP_WEIGHT needs experimental evaluations. - if (isAtomicOp()) { - return totalSize() * CLIENT_KNOBS->FASTRESTORE_ATOMICOP_WEIGHT; - } else { - return totalSize(); - } - } std::string toString() const { std::string checksumStr; diff --git a/fdbclient/include/fdbclient/ConsistencyScanInterface.actor.h b/fdbclient/include/fdbclient/ConsistencyScanInterface.h similarity index 76% rename from fdbclient/include/fdbclient/ConsistencyScanInterface.actor.h rename to fdbclient/include/fdbclient/ConsistencyScanInterface.h index e7b6045a692..fb277767b32 100644 --- a/fdbclient/include/fdbclient/ConsistencyScanInterface.actor.h +++ b/fdbclient/include/fdbclient/ConsistencyScanInterface.h @@ -18,26 +18,21 @@ * limitations under the License. */ +#pragma once + #include "fdbclient/SystemData.h" #include "fdbclient/json_spirit/json_spirit_value.h" #include "flow/serialize.h" #include "fmt/core.h" -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_CONSISTENCYSCANINTERFACE_ACTOR_G_H) -#define FDBCLIENT_CONSISTENCYSCANINTERFACE_ACTOR_G_H -#include "fdbclient/ConsistencyScanInterface.actor.g.h" -#elif !defined(FDBCLIENT_CONSISTENCYSCANINTERFACE_ACTOR_H) -#define FDBCLIENT_CONSISTENCYSCANINTERFACE_ACTOR_H #include "fdbclient/CommitProxyInterface.h" #include "fdbclient/DatabaseConfiguration.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/RunRYWTransaction.actor.h" +#include "fdbclient/RunRYWTransaction.h" #include "fdbrpc/fdbrpc.h" #include "fdbrpc/Locality.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "fdbclient/KeyBackedRangeMap.actor.h" - -#include "flow/actorcompiler.h" // must be last include +#include "fdbclient/KeyBackedTypes.h" +#include "fdbclient/KeyBackedRangeMap.h" struct ConsistencyScanInterface { constexpr static FileIdentifier file_identifier = 4983265; @@ -87,7 +82,7 @@ struct HaltConsistencyScanRequest { // // The class trigger will only be fired by changes to Config or RangeConfig struct ConsistencyScanState : public KeyBackedClass { - ConsistencyScanState(Key prefix = SystemKey("\xff/consistencyScanState"_sr)) : KeyBackedClass(prefix) {} + explicit ConsistencyScanState(Key prefix = SystemKey("\xff/consistencyScanState"_sr)) : KeyBackedClass(prefix) {} struct Config { constexpr static FileIdentifier file_identifier = 23123; @@ -312,60 +307,16 @@ struct ConsistencyScanState : public KeyBackedClass { // History of scan round stats stored by their start version StatsHistoryMap roundStatsHistory() { return { subspace.pack(__FUNCTION__sr), IncludeVersion() }; } - ACTOR static Future clearStatsActor(ConsistencyScanState* self, Reference tr) { + Future clearStats(Reference tr) { // read the keyspaces so the transaction conflicts on write (key-backed properties don't expose conflict ranges, // and the extra work here is negligible because this is a rare manual command so performance is not a huge // concern) - wait(success(self->currentRoundStats().getD(tr)) && success(self->lifetimeStats().getD(tr)) && - success(self->roundStatsHistory().getRange(tr, {}, {}, 1, Snapshot::False, Reverse::False))); + co_await (success(currentRoundStats().getD(tr)) && success(lifetimeStats().getD(tr)) && + success(roundStatsHistory().getRange(tr, {}, {}, 1, Snapshot::False, Reverse::False))); // update each of the stats keyspaces to empty - self->currentRoundStats().set(tr, ConsistencyScanState::RoundStats()); - self->lifetimeStats().set(tr, ConsistencyScanState::LifetimeStats()); - self->roundStatsHistory().erase(tr, 0, MAX_VERSION); - return Void(); + currentRoundStats().set(tr, ConsistencyScanState::RoundStats()); + lifetimeStats().set(tr, ConsistencyScanState::LifetimeStats()); + roundStatsHistory().erase(tr, 0, MAX_VERSION); } - - Future clearStats(Reference tr) { return clearStatsActor(this, tr); } }; - -///////////////////// -// Code below this line is not used by the Consistency Scan Role, only the ConsistencyCheck Workload. - -ACTOR Future getVersion(Database cx); -ACTOR Future getKeyServers( - Database cx, - Promise>>> keyServersPromise, - KeyRangeRef kr, - bool performQuiescentChecks, - bool failureIsError, - bool* success); -ACTOR Future getKeyLocations(Database cx, - std::vector>> shards, - Promise>> keyLocationPromise, - bool performQuiescentChecks, - bool* success); -ACTOR Future checkDataConsistency(Database cx, - VectorRef keyLocations, - DatabaseConfiguration configuration, - std::map tssMapping, - bool performQuiescentChecks, - bool performTSSCheck, - bool firstClient, - bool failureIsError, - int clientId, - int clientCount, - bool distributed, - bool shuffleShards, - int shardSampleFactor, - int64_t sharedRandomNumber, - int64_t repetitions, - int64_t* bytesReadInPreviousRound, - int restart, - int64_t maxRate, - int64_t targetInterval, - bool* success); - -#include "flow/unactorcompiler.h" - -#endif // FDBCLIENT_CONSISTENCYSCANINTERFACE_H \ No newline at end of file diff --git a/fdbclient/include/fdbclient/CoordinationInterface.h b/fdbclient/include/fdbclient/CoordinationInterface.h index fd73ff6ac6f..25a90807c20 100644 --- a/fdbclient/include/fdbclient/CoordinationInterface.h +++ b/fdbclient/include/fdbclient/CoordinationInterface.h @@ -39,9 +39,9 @@ struct ClientLeaderRegInterface { Optional hostname; ClientLeaderRegInterface() {} - ClientLeaderRegInterface(NetworkAddress remote); - ClientLeaderRegInterface(INetwork* local); - ClientLeaderRegInterface(Hostname hostname) : hostname(hostname) {} + explicit ClientLeaderRegInterface(NetworkAddress remote); + explicit ClientLeaderRegInterface(INetwork* local); + explicit ClientLeaderRegInterface(Hostname hostname) : hostname(hostname) {} bool operator==(const ClientLeaderRegInterface& rhs) const { return getLeader == rhs.getLeader && openDatabase == rhs.openDatabase; @@ -66,7 +66,7 @@ class ClusterConnectionString { constexpr static FileIdentifier file_identifier = 13602011; ClusterConnectionString() {} - ClusterConnectionString(const std::string& connectionString); + explicit ClusterConnectionString(const std::string& connectionString); ClusterConnectionString(const std::vector& coordinators, Key key); ClusterConnectionString(const std::vector& hosts, Key key); @@ -119,7 +119,7 @@ FDB_BOOLEAN_PARAM(ConnectionStringNeedsPersisted); // one that is only stored in memory. class IClusterConnectionRecord { public: - IClusterConnectionRecord(ConnectionStringNeedsPersisted connectionStringNeedsPersisted) + explicit IClusterConnectionRecord(ConnectionStringNeedsPersisted connectionStringNeedsPersisted) : connectionStringNeedsPersisted(connectionStringNeedsPersisted) {} virtual ~IClusterConnectionRecord() {} @@ -194,7 +194,7 @@ struct LeaderInfo { bool forward; LeaderInfo() : forward(false) {} - LeaderInfo(UID changeID) : changeID(changeID), forward(false) {} + explicit LeaderInfo(UID changeID) : changeID(changeID), forward(false) {} bool operator<(LeaderInfo const& r) const { return changeID < r.changeID; } bool operator>(LeaderInfo const& r) const { return r < *this; } diff --git a/fdbclient/include/fdbclient/DataDistributionConfig.actor.h b/fdbclient/include/fdbclient/DataDistributionConfig.h similarity index 81% rename from fdbclient/include/fdbclient/DataDistributionConfig.actor.h rename to fdbclient/include/fdbclient/DataDistributionConfig.h index b53c7613a17..420348f5c82 100644 --- a/fdbclient/include/fdbclient/DataDistributionConfig.actor.h +++ b/fdbclient/include/fdbclient/DataDistributionConfig.h @@ -1,5 +1,5 @@ /* - * DataDistributionConfig.actor.h + * DataDistributionConfig.h * * This source file is part of the FoundationDB open source project * @@ -18,23 +18,18 @@ * limitations under the License. */ -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_DATA_DISTRIBUTION_CONFIG_ACTOR_G_H) -#define FDBCLIENT_DATA_DISTRIBUTION_CONFIG_ACTOR_G_H -#include "fdbclient/DataDistributionConfig.actor.g.h" -#elif !defined(FDBCLIENT_DATA_DISTRIBUTION_CONFIG_ACTOR_H) -#define FDBCLIENT_DATA_DISTRIBUTION_CONFIG_ACTOR_H +#pragma once #include "flow/serialize.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/SystemData.h" #include "fdbclient/FDBTypes.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "fdbclient/KeyBackedRangeMap.actor.h" +#include "fdbclient/KeyBackedTypes.h" +#include "fdbclient/KeyBackedRangeMap.h" #include "fdbclient/ReadYourWrites.h" -#include "fdbclient/RunTransaction.actor.h" +#include "fdbclient/RunTransaction.h" #include "fdbclient/DatabaseContext.h" #include "fdbclient/json_spirit/json_spirit_value.h" -#include "flow/actorcompiler.h" // This must be the last #include. // DD Configuration for a key range range. // There are many properties here, all Optional, because for a given key range range a property @@ -42,7 +37,7 @@ struct DDRangeConfig { constexpr static FileIdentifier file_identifier = 9193856; - DDRangeConfig(Optional replicationFactor = {}, Optional teamID = {}) + explicit(false) DDRangeConfig(Optional replicationFactor = {}, Optional teamID = {}) : replicationFactor(replicationFactor), teamID(teamID) {} Optional replicationFactor; @@ -93,7 +88,7 @@ template <> struct fmt::formatter : FormatUsingTraceable {}; struct DDConfiguration : public KeyBackedClass { - DDConfiguration(KeyRef prefix = SystemKey("\xff\x02/ddconfig/"_sr)) : KeyBackedClass(prefix) {} + explicit DDConfiguration(KeyRef prefix = SystemKey("\xff\x02/ddconfig/"_sr)) : KeyBackedClass(prefix) {} // RangeConfigMap is a KeyBackedRangeMap of DDRangeConfig values describing various option overrides for key ranges typedef KeyBackedRangeMap::max() : 0; - int regionsWithNonNegativePriority = 0; - for (auto& r : regions) { - if (r.priority >= 0) { - regionsWithNonNegativePriority++; - } - worstSatelliteTLogReplicationFactor = std::min( - worstSatelliteTLogReplicationFactor, r.satelliteTLogReplicationFactor - r.satelliteTLogWriteAntiQuorum); - if (r.satelliteTLogUsableDcsFallback > 0) { - worstSatelliteTLogReplicationFactor = - std::min(worstSatelliteTLogReplicationFactor, - r.satelliteTLogReplicationFactorFallback - r.satelliteTLogWriteAntiQuorumFallback); - } - } - - if (worstSatelliteTLogReplicationFactor <= 0) { - // HA is not enabled in this database. Return single cluster zone failures to tolerate. - return std::min(tLogReplicationFactor - 1 - tLogWriteAntiQuorum, storageTeamSize - 1); - } - - // Compute HA enabled database zone failure tolerance. - auto isGeoReplicatedData = [this, &fullyReplicatedRegions]() { - return usableRegions > 1 && fullyReplicatedRegions > 1; - }; - - if (isGeoReplicatedData() && (!forAvailability || regionsWithNonNegativePriority > 1)) { - return 1 + std::min(std::max(tLogReplicationFactor - 1 - tLogWriteAntiQuorum, - worstSatelliteTLogReplicationFactor - 1), - storageTeamSize - 1); - } - // Primary and Satellite tLogs are synchronously replicated, hence we can lose all but 1. - return std::min(tLogReplicationFactor + worstSatelliteTLogReplicationFactor - 1 - tLogWriteAntiQuorum, - storageTeamSize - 1); - } + int32_t maxZoneFailuresTolerated(int fullyReplicatedRegions, bool forAvailability) const; // CommitProxy Servers int32_t commitProxyCount; @@ -243,6 +209,7 @@ struct DatabaseConfiguration { // Backup Workers bool backupWorkerEnabled; + bool rangeBackupWorkerEnabled; // Data centers int32_t usableRegions; // Number of regions which have a replica of the database. diff --git a/fdbclient/include/fdbclient/DatabaseContext.h b/fdbclient/include/fdbclient/DatabaseContext.h index 0104dcb30f3..b79688508fe 100644 --- a/fdbclient/include/fdbclient/DatabaseContext.h +++ b/fdbclient/include/fdbclient/DatabaseContext.h @@ -24,10 +24,11 @@ #include "flow/ApiVersion.h" #include "flow/FastAlloc.h" #include "flow/FastRef.h" -#include "fdbclient/GlobalConfig.actor.h" +#include "fdbclient/GlobalConfig.h" #include "fdbclient/StorageServerInterface.h" #include "flow/IRandom.h" #include "flow/genericactors.actor.h" +#include #include #include #pragma once @@ -36,13 +37,12 @@ #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/KeyRangeMap.h" #include "fdbclient/CommitProxyInterface.h" -#include "fdbclient/SpecialKeySpace.actor.h" +#include "fdbclient/SpecialKeySpace.h" #include "fdbclient/VersionVector.h" -#include "fdbclient/IKeyValueStore.actor.h" #include "fdbrpc/QueueModel.h" #include "fdbrpc/MultiInterface.h" -#include "flow/TDMetric.actor.h" -#include "fdbclient/EventTypes.actor.h" +#include "flow/TDMetric.h" +#include "fdbclient/EventTypes.h" #include "fdbrpc/Smoother.h" #include "fdbrpc/DDSketch.h" @@ -89,7 +89,7 @@ class ClientTagThrottleData : NonCopyable { Smoother smoothReleased; public: - ClientTagThrottleData(ClientTagThrottleLimits const& limits) + explicit ClientTagThrottleData(ClientTagThrottleLimits const& limits) : tpsRate(limits.tpsRate), expiration(limits.expiration), lastCheck(now()), smoothRate(CLIENT_KNOBS->TAG_THROTTLE_SMOOTHING_WINDOW), smoothReleased(CLIENT_KNOBS->TAG_THROTTLE_SMOOTHING_WINDOW) { @@ -152,7 +152,7 @@ class WatchMetadata : public ReferenceCounted { Reference parameters; - WatchMetadata(Reference parameters) : parameters(parameters) {} + explicit WatchMetadata(Reference parameters) : parameters(parameters) {} }; struct MutationAndVersionStream { @@ -237,7 +237,7 @@ class DatabaseContext : public ReferenceCounted, public FastAll Future getHealthMetrics(bool detailed); // Get storage stats of a storage server from the cached healthy metrics if now() - lastUpdate < maxStaleness. // Otherwise, ask GRVProxy for the up-to-date health metrics. - Future> getStorageStats(const UID& id, double maxStaleness); + Future> getStorageStats(UID id, double maxStaleness); // Pass a negative value for `shardLimit` to indicate no limit on the shard number. Future getStorageMetrics( KeyRange const& keys, @@ -379,7 +379,9 @@ class DatabaseContext : public ReferenceCounted, public FastAll TagSet tags; Optional debugID; - VersionRequest(SpanContext spanContext, TagSet tags = TagSet(), Optional debugID = Optional()) + explicit VersionRequest(SpanContext spanContext, + TagSet tags = TagSet(), + Optional debugID = Optional()) : spanContext(spanContext), tags(tags), debugID(debugID) {} }; @@ -388,7 +390,16 @@ class DatabaseContext : public ReferenceCounted, public FastAll PromiseStream stream; Future actor; }; - std::map versionBatcher; + struct VersionBatcherKey { + uint32_t flags; + Optional maxGrvQueueDelayMS; + + VersionBatcherKey(uint32_t flags, Optional maxGrvQueueDelayMS) + : flags(flags), maxGrvQueueDelayMS(maxGrvQueueDelayMS) {} + + std::strong_ordering operator<=>(VersionBatcherKey const& rhs) const = default; + }; + std::map versionBatcher; AsyncTrigger connectionFileChangedTrigger; @@ -418,10 +429,6 @@ class DatabaseContext : public ReferenceCounted, public FastAll // map from tssid -> metrics for that tss pair std::unordered_map> tssMetrics; - IKeyValueStore* storage = nullptr; - - void setStorage(IKeyValueStore* storage); - // map from ssid -> ss tag // @note this map allows the client to identify the latest commit versions // of storage servers (note that "ssVersionVectorCache" identifies storage @@ -515,7 +522,7 @@ class DatabaseContext : public ReferenceCounted, public FastAll TaskPriority taskID; Int64MetricHandle getValueSubmitted; - EventMetricHandle getValueCompleted; + EventMetricHandle getValueCompleted; Reference> clientInfo; Future clientInfoMonitor; @@ -670,7 +677,8 @@ class DatabaseContext : public ReferenceCounted, public FastAll // Similar to tr.onError(), but doesn't require a DatabaseContext. struct Backoff { - Backoff(double backoff = CLIENT_KNOBS->DEFAULT_BACKOFF, double maxBackoff = CLIENT_KNOBS->DEFAULT_MAX_BACKOFF) + explicit Backoff(double backoff = CLIENT_KNOBS->DEFAULT_BACKOFF, + double maxBackoff = CLIENT_KNOBS->DEFAULT_MAX_BACKOFF) : backoff(backoff), maxBackoff(maxBackoff) {} Future onError() { diff --git a/fdbclient/include/fdbclient/EncryptKeyProxyInterface.h b/fdbclient/include/fdbclient/EncryptKeyProxyInterface.h deleted file mode 100644 index 505aa117b21..00000000000 --- a/fdbclient/include/fdbclient/EncryptKeyProxyInterface.h +++ /dev/null @@ -1,317 +0,0 @@ -/* - * EncryptKeyProxyInterface.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBSERVER_ENCRYPTKEYPROXYINTERFACE_H -#define FDBSERVER_ENCRYPTKEYPROXYINTERFACE_H -#pragma once - -#include "fdbclient/FDBTypes.h" -#include "fdbrpc/fdbrpc.h" -#include "fdbrpc/Locality.h" -#include "flow/Arena.h" -#include "flow/EncryptUtils.h" -#include "flow/FileIdentifier.h" -#include "flow/IRandom.h" -#include "flow/network.h" - -#include - -#define DEBUG_ENCRYPT_KEY_PROXY false - -struct KMSHealthStatus { - constexpr static FileIdentifier file_identifier = 2378149; - bool canConnectToKms; - bool canConnectToEKP; - double lastUpdatedTS; - std::string kmsConnectorType; - std::vector restKMSUrls; - bool kmsStable = true; - - KMSHealthStatus() : canConnectToKms(false), canConnectToEKP(false), lastUpdatedTS(-1), kmsStable(true) {} - - bool operator==(const KMSHealthStatus& other) { - return canConnectToKms == other.canConnectToKms && canConnectToEKP == other.canConnectToEKP; - } - - bool healthnessChanged(const KMSHealthStatus& other) { - return canConnectToKms != other.canConnectToKms || canConnectToEKP != other.canConnectToEKP; - } - - std::string toString() const { - std::stringstream ss; - ss << "CanConnectToKms(" << canConnectToKms << ")" - << ", CanConnectToEKP(" << canConnectToEKP << ")" - << ", LastUpdatedTS(" << lastUpdatedTS << ")" - << ", KMSConnectorType(" << kmsConnectorType << ")" - << ", RESTKmsUrls("; - bool firstUrl = true; - for (const auto& url : restKMSUrls) { - if (!firstUrl) { - ss << ", "; - } - ss << url; - firstUrl = false; - } - - ss << "), KmsStable(" << kmsStable << ")"; - return ss.str(); - } - - template - void serialize(Ar& ar) { - serializer(ar, canConnectToKms, canConnectToEKP, lastUpdatedTS, kmsConnectorType, restKMSUrls, kmsStable); - } -}; - -struct EncryptKeyProxyInterface { - constexpr static FileIdentifier file_identifier = 1303419; - struct LocalityData locality; - UID myId; - RequestStream> waitFailure; - RequestStream haltEncryptKeyProxy; - RequestStream getBaseCipherKeysByIds; - RequestStream getLatestBaseCipherKeys; - RequestStream getLatestBlobMetadata; - RequestStream getHealthStatus; - - EncryptKeyProxyInterface() {} - explicit EncryptKeyProxyInterface(const struct LocalityData& loc, UID id) : locality(loc), myId(id) {} - - NetworkAddress address() const { return haltEncryptKeyProxy.getEndpoint().getPrimaryAddress(); } - NetworkAddressList addresses() const { return haltEncryptKeyProxy.getEndpoint().addresses; } - - UID id() const { return myId; } - - bool operator==(const EncryptKeyProxyInterface& toCompare) const { return myId == toCompare.myId; } - bool operator!=(const EncryptKeyProxyInterface& toCompare) const { return !(*this == toCompare); } - - template - void serialize(Archive& ar) { - if constexpr (!is_fb_function) { - ASSERT(ar.protocolVersion().isValid()); - } - serializer(ar, locality, myId, waitFailure); - if (Archive::isDeserializing) { - haltEncryptKeyProxy = - RequestStream(waitFailure.getEndpoint().getAdjustedEndpoint(1)); - getBaseCipherKeysByIds = RequestStream( - waitFailure.getEndpoint().getAdjustedEndpoint(2)); - getLatestBaseCipherKeys = RequestStream( - waitFailure.getEndpoint().getAdjustedEndpoint(3)); - getLatestBlobMetadata = - RequestStream(waitFailure.getEndpoint().getAdjustedEndpoint(4)); - getHealthStatus = RequestStream( - waitFailure.getEndpoint().getAdjustedEndpoint(5)); - } - } - - void initEndpoints() { - std::vector> streams; - streams.push_back(waitFailure.getReceiver()); - streams.push_back(haltEncryptKeyProxy.getReceiver(TaskPriority::DefaultPromiseEndpoint)); - streams.push_back(getBaseCipherKeysByIds.getReceiver(TaskPriority::Worker)); - streams.push_back(getLatestBaseCipherKeys.getReceiver(TaskPriority::Worker)); - streams.push_back(getLatestBlobMetadata.getReceiver(TaskPriority::Worker)); - streams.push_back(getHealthStatus.getReceiver(TaskPriority::Worker)); - FlowTransport::transport().addEndpoints(streams); - } -}; - -struct HaltEncryptKeyProxyRequest { - constexpr static FileIdentifier file_identifier = 2378138; - UID requesterID; - ReplyPromise reply; - - HaltEncryptKeyProxyRequest() : requesterID(deterministicRandom()->randomUniqueID()) {} - explicit HaltEncryptKeyProxyRequest(UID uid) : requesterID(uid) {} - - template - void serialize(Ar& ar) { - serializer(ar, requesterID, reply); - } -}; - -struct EncryptKeyProxyHealthStatusRequest { - constexpr static FileIdentifier file_identifier = 2378139; - ReplyPromise reply; - - EncryptKeyProxyHealthStatusRequest() {} - - template - void serialize(Ar& ar) { - serializer(ar, reply); - } -}; - -struct EKPBaseCipherDetails { - constexpr static FileIdentifier file_identifier = 2149615; - int64_t encryptDomainId; - uint64_t baseCipherId; - Standalone baseCipherKey; - EncryptCipherKeyCheckValue baseCipherKCV; - int64_t refreshAt; - int64_t expireAt; - - EKPBaseCipherDetails() - : encryptDomainId(0), baseCipherId(0), baseCipherKey(Standalone()), baseCipherKCV(0), refreshAt(0), - expireAt(-1) {} - explicit EKPBaseCipherDetails(int64_t dId, - uint64_t id, - Standalone key, - EncryptCipherKeyCheckValue cipherKCV) - : encryptDomainId(dId), baseCipherId(id), baseCipherKey(key), baseCipherKCV(cipherKCV), - refreshAt(std::numeric_limits::max()), expireAt(std::numeric_limits::max()) {} - explicit EKPBaseCipherDetails(int64_t dId, - uint64_t id, - Standalone key, - EncryptCipherKeyCheckValue cipherKCV, - int64_t refAt, - int64_t expAt) - : encryptDomainId(dId), baseCipherId(id), baseCipherKey(key), baseCipherKCV(cipherKCV), refreshAt(refAt), - expireAt(expAt) {} - - bool operator==(const EKPBaseCipherDetails& r) const { - return encryptDomainId == r.encryptDomainId && baseCipherId == r.baseCipherId && refreshAt == r.refreshAt && - expireAt == r.expireAt && baseCipherKey.toString() == r.baseCipherKey.toString(); - } - - template - void serialize(Ar& ar) { - serializer(ar, encryptDomainId, baseCipherId, baseCipherKey, baseCipherKCV, refreshAt, expireAt); - } -}; - -struct EKPGetBaseCipherKeysByIdsReply { - constexpr static FileIdentifier file_identifier = 9485259; - std::vector baseCipherDetails; - int numHits; - Optional error; - - EKPGetBaseCipherKeysByIdsReply() : numHits(0) {} - - template - void serialize(Ar& ar) { - serializer(ar, baseCipherDetails, numHits, error); - } -}; - -struct EKPGetBaseCipherKeysRequestInfo { - constexpr static FileIdentifier file_identifier = 2180516; - // Encryption cipher domain identifier - EncryptCipherDomainId domainId; - // Encryption cipher KMS assigned identifier - EncryptCipherBaseKeyId baseCipherId; - - EKPGetBaseCipherKeysRequestInfo() - : domainId(INVALID_ENCRYPT_DOMAIN_ID), baseCipherId(INVALID_ENCRYPT_CIPHER_KEY_ID) {} - EKPGetBaseCipherKeysRequestInfo(const EncryptCipherDomainId dId, const EncryptCipherBaseKeyId bCId) - : domainId(dId), baseCipherId(bCId) {} - - bool operator==(const EKPGetBaseCipherKeysRequestInfo& info) const { - return domainId == info.domainId && baseCipherId == info.baseCipherId; - } - - template - void serialize(Ar& ar) { - serializer(ar, domainId, baseCipherId); - } -}; - -struct EKPGetBaseCipherKeysByIdsRequest { - constexpr static FileIdentifier file_identifier = 4930263; - std::vector baseCipherInfos; - Optional debugId; - ReplyPromise reply; - - EKPGetBaseCipherKeysByIdsRequest() {} - - template - void serialize(Ar& ar) { - serializer(ar, baseCipherInfos, debugId, reply); - } -}; - -struct EKPGetLatestBaseCipherKeysReply { - constexpr static FileIdentifier file_identifier = 4831583; - std::vector baseCipherDetails; - int numHits; - Optional error; - - EKPGetLatestBaseCipherKeysReply() : numHits(0) {} - explicit EKPGetLatestBaseCipherKeysReply(const std::vector& cipherDetails) - : baseCipherDetails(cipherDetails), numHits(0) {} - - template - void serialize(Ar& ar) { - serializer(ar, baseCipherDetails, numHits, error); - } -}; - -struct EKPGetBaseCipherKeysRequestInfo_Hash { - std::size_t operator()(const EKPGetBaseCipherKeysRequestInfo& info) const { - boost::hash> hasher; - return hasher(std::make_pair(info.domainId, info.baseCipherId)); - } -}; - -struct EKPGetLatestBaseCipherKeysRequest { - constexpr static FileIdentifier file_identifier = 1910123; - std::vector encryptDomainIds; - Optional debugId; - ReplyPromise reply; - - EKPGetLatestBaseCipherKeysRequest() {} - explicit EKPGetLatestBaseCipherKeysRequest(const std::vector& ids) : encryptDomainIds(ids) {} - - template - void serialize(Ar& ar) { - serializer(ar, encryptDomainIds, debugId, reply); - } -}; - -// partition and credentials information for a given blob domain - -struct EKPGetLatestBlobMetadataReply { - constexpr static FileIdentifier file_identifier = 5761581; - // stuff deleted. This RPC should not be happening. - - EKPGetLatestBlobMetadataReply() {} - - template - void serialize(Ar& ar) { - serializer(ar); - } -}; - -struct EKPGetLatestBlobMetadataRequest { - constexpr static FileIdentifier file_identifier = 3821549; - std::vector domainIds; - Optional debugId; - ReplyPromise reply; - - EKPGetLatestBlobMetadataRequest() {} - - template - void serialize(Ar& ar) { - serializer(ar, domainIds, debugId, reply); - } -}; - -#endif diff --git a/fdbclient/include/fdbclient/EventTypes.h b/fdbclient/include/fdbclient/EventTypes.h new file mode 100644 index 00000000000..69b248aada3 --- /dev/null +++ b/fdbclient/include/fdbclient/EventTypes.h @@ -0,0 +1,34 @@ +/* + * EventTypes.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 "flow/flow.h" +#include "flow/TDMetric.h" + +struct GetValueCompleteDescriptor { + int64_t latency; +}; + +template <> +struct Descriptor + : DescribeType> {}; diff --git a/fdbclient/include/fdbclient/FDBTypes.h b/fdbclient/include/fdbclient/FDBTypes.h index 5eced645fe7..e775e10d31d 100644 --- a/fdbclient/include/fdbclient/FDBTypes.h +++ b/fdbclient/include/fdbclient/FDBTypes.h @@ -981,7 +981,7 @@ struct KeyValueStoreType { }; KeyValueStoreType() : type(END) {} - KeyValueStoreType(StoreType type) : type(type) { + explicit(false) KeyValueStoreType(StoreType type) : type(type) { if ((uint32_t)type > END) this->type = END; } @@ -1038,7 +1038,7 @@ struct TLogVersion { } version; TLogVersion() : version(UNSET) {} - TLogVersion(Version v) : version(v) {} + explicit(false) TLogVersion(Version v) : version(v) {} operator Version() const { return version; } @@ -1083,7 +1083,7 @@ struct TLogSpillType { }; TLogSpillType() : type(DEFAULT) {} - TLogSpillType(SpillType type) : type(type) { + explicit(false) TLogSpillType(SpillType type) : type(type) { if ((uint32_t)type >= END) { this->type = UNSET; } @@ -1163,7 +1163,7 @@ struct StorageBytes { }; struct LogMessageVersion { // Each message pushed into the log system has a unique, totally ordered LogMessageVersion - // See ILogSystem::push() for how these are assigned + // See LogSystem::push() for how these are assigned Version version; uint32_t sub; @@ -1392,7 +1392,7 @@ struct StorageMigrationType { enum MigrationType { DEFAULT = 1, UNSET = 0, DISABLED = 1, AGGRESSIVE = 2, GRADUAL = 3, END = 4 }; StorageMigrationType() : type(UNSET) {} - StorageMigrationType(MigrationType type) : type(type) { + explicit(false) StorageMigrationType(MigrationType type) : type(type) { if ((uint32_t)type >= END) { this->type = UNSET; } @@ -1436,7 +1436,7 @@ struct EncryptionAtRestModeDeprecated { }; EncryptionAtRestModeDeprecated() : mode(DISABLED) {} - EncryptionAtRestModeDeprecated(Mode mode) : mode(mode) { + explicit(false) EncryptionAtRestModeDeprecated(Mode mode) : mode(mode) { if ((uint32_t)mode >= END) { this->mode = DISABLED; } @@ -1517,10 +1517,6 @@ struct Traceable : std::true_type { static std::string toString(const EncryptionAtRestModeDeprecated& mode) { return mode.toString(); } }; -typedef StringRef ClusterNameRef; -typedef Standalone ClusterName; - -// TODO(gglass): delete metacluster code and tenant code and reassess the need for this enum enum class ClusterType { STANDALONE, LEGACY_UNUSED_METACLUSTER_MANAGEMENT, LEGACY_UNUSED_METACLUSTER_DATA }; struct GRVCacheSpace { @@ -1579,9 +1575,9 @@ struct StorageMetadataType { bool wrongConfiguredForWiggle = false; StorageMetadataType() : createdTime(0) {} - StorageMetadataType(double t, - KeyValueStoreType storeType = KeyValueStoreType::END, - bool wrongConfiguredForWiggle = false) + explicit StorageMetadataType(double t, + KeyValueStoreType storeType = KeyValueStoreType::END, + bool wrongConfiguredForWiggle = false) : createdTime(t), storeType(storeType), wrongConfiguredForWiggle(wrongConfiguredForWiggle) {} static double currentTime() { return g_network->timer(); } @@ -1622,7 +1618,7 @@ struct StorageWiggleValue { constexpr static FileIdentifier file_identifier = 732124; UID id; // storage id - StorageWiggleValue(UID id = UID(0, 0)) : id(id) {} + explicit StorageWiggleValue(UID id = UID(0, 0)) : id(id) {} // To change this serialization, ProtocolVersion::PerpetualWiggleMetadata must be updated, and downgrades need // to be considered @@ -1649,13 +1645,13 @@ struct ReadOptions { Optional debugID; Optional consistencyCheckStartVersion; - ReadOptions(Optional debugID = Optional(), - ReadType type = ReadType::NORMAL, - CacheResult cache = CacheResult::True, - Optional version = Optional()) + explicit ReadOptions(Optional debugID = Optional(), + ReadType type = ReadType::NORMAL, + CacheResult cache = CacheResult::True, + Optional version = Optional()) : type(type), cacheResult(cache), debugID(debugID), consistencyCheckStartVersion(version) {} - ReadOptions(ReadType type, CacheResult cache = CacheResult::True) : ReadOptions({}, type, cache) {} + explicit ReadOptions(ReadType type, CacheResult cache = CacheResult::True) : ReadOptions({}, type, cache) {} template void serialize(Ar& ar) { @@ -1665,11 +1661,12 @@ struct ReadOptions { // Can be used to identify types (e.g. IDatabase) that can be used to create transactions with a `createTransaction` // function -template +template struct transaction_creator_traits : std::false_type {}; template -struct transaction_creator_traits> : std::true_type {}; + requires requires { typename T::TransactionT; } +struct transaction_creator_traits : std::true_type {}; template struct transaction_creator_traits> : transaction_creator_traits {}; @@ -1692,7 +1689,7 @@ struct Versionstamp { Versionstamp() {} Versionstamp(Version version, uint16_t batchNumber) : version(version), batchNumber(batchNumber) {} - Versionstamp(Standalone str) { + explicit Versionstamp(Standalone str) { ASSERT(str.size() == sizeof(Version) + sizeof(batchNumber)); version = bigEndian64(*reinterpret_cast(str.begin())); batchNumber = bigEndian16(*reinterpret_cast(str.begin() + sizeof(Version))); diff --git a/fdbclient/include/fdbclient/GenericManagementAPI.actor.h b/fdbclient/include/fdbclient/GenericManagementAPI.h similarity index 75% rename from fdbclient/include/fdbclient/GenericManagementAPI.actor.h rename to fdbclient/include/fdbclient/GenericManagementAPI.h index 7e9bd7f80d5..762e6e3fadc 100644 --- a/fdbclient/include/fdbclient/GenericManagementAPI.actor.h +++ b/fdbclient/include/fdbclient/GenericManagementAPI.h @@ -1,5 +1,5 @@ /* - * GenericManagementAPI.actor.h + * GenericManagementAPI.h * * This source file is part of the FoundationDB open source project * @@ -19,11 +19,6 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_GENERIC_MANAGEMENT_API_ACTOR_G_H) -#define FDBCLIENT_GENERIC_MANAGEMENT_API_ACTOR_G_H -#include "fdbclient/GenericManagementAPI.actor.g.h" -#elif !defined(FDBCLIENT_GENERIC_MANAGEMENT_API_ACTOR_H) -#define FDBCLIENT_GENERIC_MANAGEMENT_API_ACTOR_H /* This file defines "management" interfaces that have been templated to support both IClientAPI and Native version of databases, transactions, etc., and includes functions for performing cluster @@ -41,8 +36,7 @@ the contents of the system key space. #include "fdbclient/DatabaseConfiguration.h" #include "fdbclient/Status.h" #include "fdbclient/SystemData.h" -#include "fdbclient/StorageWiggleMetrics.actor.h" -#include "flow/actorcompiler.h" // has to be last include +#include "fdbclient/StorageWiggleMetrics.h" // ConfigurationResult enumerates normal outcomes of changeConfig() and various error // conditions specific to it. changeConfig may also throw an Error to report other problems. @@ -70,7 +64,8 @@ enum class ConfigurationResult { DATABASE_CREATED_WARN_SHARDED_ROCKSDB_EXPERIMENTAL, DATABASE_IS_REGISTERED, INVALID_STORAGE_TYPE, - BACKUP_WORKER_ENABLED_RESTRICTED + BACKUP_WORKER_ENABLED_RESTRICTED, + RANGE_BACKUP_WORKER_ENABLED_RESTRICTED }; enum class CoordinatorsResult { @@ -134,17 +129,17 @@ ConfigureAutoResult parseConfig(StatusObject const& status); // Management API written in template code to support both IClientAPI and NativeAPI namespace ManagementAPI { -ACTOR template +template Future> getWorkers(Reference tr, typename Tr::template FutureT processClassesF, typename Tr::template FutureT processDataF) { // processClassesF and processDataF are used to hold standalone memory processClassesF = tr->getRange(processClassKeys, CLIENT_KNOBS->TOO_MANY); processDataF = tr->getRange(workerListKeys, CLIENT_KNOBS->TOO_MANY); - state Future processClasses = safeThreadFutureToFuture(processClassesF); - state Future processData = safeThreadFutureToFuture(processDataF); + Future processClasses = safeThreadFutureToFuture(processClassesF); + Future processData = safeThreadFutureToFuture(processDataF); - wait(success(processClasses) && success(processData)); + co_await (success(processClasses) && success(processData)); ASSERT(!processClasses.get().more && processClasses.get().size() < CLIENT_KNOBS->TOO_MANY); ASSERT(!processData.get().more && processData.get().size() < CLIENT_KNOBS->TOO_MANY); @@ -168,31 +163,31 @@ Future> getWorkers(Reference tr, results.push_back(data); } - return results; + co_return results; } // All versions of changeConfig apply the given set of configuration tokens to the database, and return a // ConfigurationResult (or error). // Accepts a full configuration in key/value format (from buildConfiguration) -ACTOR template +template Future changeConfig(Reference db, std::map m, bool force) { - state StringRef initIdKey = "\xff/init_id"_sr; - state Reference tr = db->createTransaction(); + StringRef initIdKey = "\xff/init_id"_sr; + Reference tr = db->createTransaction(); if (!m.size()) { - return ConfigurationResult::NO_OPTIONS_PROVIDED; + co_return ConfigurationResult::NO_OPTIONS_PROVIDED; } // make sure we have essential configuration options std::string initKey = configKeysPrefix.toString() + "initialized"; - state bool creating = m.count(initKey) != 0; - state Optional locked; + bool creating = m.count(initKey) != 0; + Optional locked; { auto iter = m.find(databaseLockedKey.toString()); if (iter != m.end()) { if (!creating) { - return ConfigurationResult::LOCKED_NOT_NEW; + co_return ConfigurationResult::LOCKED_NOT_NEW; } locked = UID::fromString(iter->second); m.erase(iter); @@ -201,20 +196,22 @@ Future changeConfig(Reference db, std::maprandomUniqueID().toString(); if (!isCompleteConfiguration(m)) { - return ConfigurationResult::INCOMPLETE_CONFIGURATION; + co_return ConfigurationResult::INCOMPLETE_CONFIGURATION; } } - state Future tooLong = delay(60); - state Key versionKey = BinaryWriter::toValue(deterministicRandom()->randomUniqueID(), Unversioned()); - state bool oldReplicationUsesDcId = false; + Future tooLong = delay(60); + Key versionKey = BinaryWriter::toValue(deterministicRandom()->randomUniqueID(), Unversioned()); + bool oldReplicationUsesDcId = false; // the caller need to reset the perpetual wiggle stats if pw=0 in case the reset txn on DD side is cancelled // due to DD can die at the same time - state bool resetPPWStats = false; - state bool warnPPWGradual = false; - state bool warnShardedRocksDBIsExperimental = false; + bool resetPPWStats = false; + bool warnPPWGradual = false; + bool warnShardedRocksDBIsExperimental = false; - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); @@ -222,32 +219,32 @@ Future changeConfig(Reference db, std::mapsetOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); if (!creating && !force) { - state typename DB::TransactionT::template FutureT fConfigF = + typename DB::TransactionT::template FutureT fConfigF = tr->getRange(configKeys, CLIENT_KNOBS->TOO_MANY); - state Future fConfig = safeThreadFutureToFuture(fConfigF); - state typename DB::TransactionT::template FutureT processClassesF; - state typename DB::TransactionT::template FutureT processDataF; - state Future> fWorkers = getWorkers(tr, processClassesF, processDataF); - wait(success(fConfig) || tooLong); + Future fConfig = safeThreadFutureToFuture(fConfigF); + typename DB::TransactionT::template FutureT processClassesF; + typename DB::TransactionT::template FutureT processDataF; + Future> fWorkers = getWorkers(tr, processClassesF, processDataF); + co_await (success(fConfig) || tooLong); if (!fConfig.isReady()) { - return ConfigurationResult::DATABASE_UNAVAILABLE; + co_return ConfigurationResult::DATABASE_UNAVAILABLE; } if (fConfig.isReady()) { ASSERT(fConfig.get().size() < CLIENT_KNOBS->TOO_MANY); - state DatabaseConfiguration oldConfig; + DatabaseConfiguration oldConfig; oldConfig.fromKeyValues((VectorRef)fConfig.get()); - state DatabaseConfiguration newConfig = oldConfig; + DatabaseConfiguration newConfig = oldConfig; for (auto kv : m) { newConfig.set(kv.first, kv.second); } if (!newConfig.isValid()) { - return ConfigurationResult::INVALID_CONFIGURATION; + co_return ConfigurationResult::INVALID_CONFIGURATION; } if (newConfig.tLogPolicy->attributeKeys().count("dcid") && newConfig.regions.size() > 0) { - return ConfigurationResult::REGION_REPLICATION_MISMATCH; + co_return ConfigurationResult::REGION_REPLICATION_MISMATCH; } oldReplicationUsesDcId = @@ -261,7 +258,7 @@ Future changeConfig(Reference db, std::map changeConfig(Reference db, std::map 1) { - return ConfigurationResult::MULTIPLE_ACTIVE_REGIONS; + co_return ConfigurationResult::MULTIPLE_ACTIVE_REGIONS; } } - state typename DB::TransactionT::template FutureT fServerListF = + typename DB::TransactionT::template FutureT fServerListF = tr->getRange(serverListKeys, CLIENT_KNOBS->TOO_MANY); - state Future fServerList = + Future fServerList = (newConfig.regions.size()) ? safeThreadFutureToFuture(fServerListF) : Future(); if (newConfig.usableRegions == 2) { if (oldReplicationUsesDcId) { - state typename DB::TransactionT::template FutureT fLocalityListF = + typename DB::TransactionT::template FutureT fLocalityListF = tr->getRange(tagLocalityListKeys, CLIENT_KNOBS->TOO_MANY); - state Future fLocalityList = safeThreadFutureToFuture(fLocalityListF); - wait(success(fLocalityList) || tooLong); + Future fLocalityList = safeThreadFutureToFuture(fLocalityListF); + co_await (success(fLocalityList) || tooLong); if (!fLocalityList.isReady()) { - return ConfigurationResult::DATABASE_UNAVAILABLE; + co_return ConfigurationResult::DATABASE_UNAVAILABLE; } RangeResult localityList = fLocalityList.get(); ASSERT(!localityList.more && localityList.size() < CLIENT_KNOBS->TOO_MANY); @@ -304,28 +301,27 @@ Future changeConfig(Reference db, std::map= 0 must be fully replicated - state std::vector>> - replicasFuturesF; - state std::vector>> replicasFutures; + std::vector>> replicasFuturesF; + std::vector>> replicasFutures; for (auto& it : newConfig.regions) { if (it.priority >= 0) { replicasFuturesF.push_back(tr->get(datacenterReplicasKeyFor(it.dcId))); replicasFutures.push_back(safeThreadFutureToFuture(replicasFuturesF.back())); } } - wait(waitForAll(replicasFutures) || tooLong); + co_await (waitForAll(replicasFutures) || tooLong); for (auto& it : replicasFutures) { if (!it.isReady()) { - return ConfigurationResult::DATABASE_UNAVAILABLE; + co_return ConfigurationResult::DATABASE_UNAVAILABLE; } if (!it.get().present()) { - return ConfigurationResult::REGION_NOT_FULLY_REPLICATED; + co_return ConfigurationResult::REGION_NOT_FULLY_REPLICATED; } } } @@ -333,9 +329,9 @@ Future changeConfig(Reference db, std::mapTOO_MANY); @@ -352,13 +348,13 @@ Future changeConfig(Reference db, std::map (oldReplicationUsesDcId ? 1 : 0)) { - return ConfigurationResult::STORAGE_IN_UNKNOWN_DCID; + co_return ConfigurationResult::STORAGE_IN_UNKNOWN_DCID; } } - wait(success(fWorkers) || tooLong); + co_await (success(fWorkers) || tooLong); if (!fWorkers.isReady()) { - return ConfigurationResult::DATABASE_UNAVAILABLE; + co_return ConfigurationResult::DATABASE_UNAVAILABLE; } if (newConfig.regions.size()) { @@ -371,7 +367,7 @@ Future changeConfig(Reference db, std::map 0 && region.priority >= 0) { int totalSatelliteProcesses = 0; @@ -379,7 +375,7 @@ Future changeConfig(Reference db, std::map changeConfig(Reference db, std::map changeConfig(Reference db, std::mapfirst == backupWorkerEnabledKey && i->second == "0") { tr->clear(backupProgressKeys); TraceEvent("BackupWorkerProgressCleared"); } + + // Clear range partitioned backup progress when range partitioned backup workers are disabled + if (i->first == rangeBackupWorkerEnabledKey && i->second == "0") { + tr->clear(backupProgressKeys); + TraceEvent("RangePartitionedBackupWorkerProgressCleared"); + } } if (!creating && resetPPWStats) { - state StorageWiggleData wiggleData; - wait(wiggleData.resetStorageWiggleMetrics(tr, PrimaryRegion(true))); - wait(wiggleData.resetStorageWiggleMetrics(tr, PrimaryRegion(false))); + StorageWiggleData wiggleData; + co_await wiggleData.resetStorageWiggleMetrics(tr, PrimaryRegion(true)); + co_await wiggleData.resetStorageWiggleMetrics(tr, PrimaryRegion(false)); } tr->addReadConflictRange(singleKeyRange(moveKeysLockOwnerKey)); tr->set(moveKeysLockOwnerKey, versionKey); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); break; } catch (Error& e) { - state Error e1(e); - if ((e.code() == error_code_not_committed || e.code() == error_code_transaction_too_old) && creating) { + err = e; + hasErr = true; + } + if (hasErr) { + Error e1(err); + if ((err.code() == error_code_not_committed || err.code() == error_code_transaction_too_old) && creating) { // The database now exists. Determine whether we created it or it was already existing/created by // someone else. The latter is an error. tr->reset(); - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - state typename DB::TransactionT::template FutureT> vF = tr->get(initIdKey); - Optional v = wait(safeThreadFutureToFuture(vF)); + typename DB::TransactionT::template FutureT> vF = tr->get(initIdKey); + Optional v = co_await safeThreadFutureToFuture(vF); if (v != m[initIdKey.toString()]) - return ConfigurationResult::DATABASE_ALREADY_CREATED; + co_return ConfigurationResult::DATABASE_ALREADY_CREATED; else if (m[configKeysPrefix.toString() + "storage_engine"] == std::to_string(KeyValueStoreType::SSD_SHARDED_ROCKSDB)) - return ConfigurationResult::DATABASE_CREATED_WARN_SHARDED_ROCKSDB_EXPERIMENTAL; + co_return ConfigurationResult::DATABASE_CREATED_WARN_SHARDED_ROCKSDB_EXPERIMENTAL; else - return ConfigurationResult::DATABASE_CREATED; + co_return ConfigurationResult::DATABASE_CREATED; } catch (Error& e2) { - wait(safeThreadFutureToFuture(tr->onError(e2))); + err = e2; + hasErr = true; + } + if (hasErr) { + co_await safeThreadFutureToFuture(tr->onError(err)); } } } - wait(safeThreadFutureToFuture(tr->onError(e1))); + co_await safeThreadFutureToFuture(tr->onError(e1)); } } if (warnPPWGradual) { - return ConfigurationResult::SUCCESS_WARN_PPW_GRADUAL; + co_return ConfigurationResult::SUCCESS_WARN_PPW_GRADUAL; } else if (warnShardedRocksDBIsExperimental) { - return ConfigurationResult::SUCCESS_WARN_SHARDED_ROCKSDB_EXPERIMENTAL; + co_return ConfigurationResult::SUCCESS_WARN_SHARDED_ROCKSDB_EXPERIMENTAL; } else { - return ConfigurationResult::SUCCESS; + co_return ConfigurationResult::SUCCESS; } } -ACTOR template +template Future autoConfig(Reference db, ConfigureAutoResult conf) { - state Reference tr = db->createTransaction(); - state Key versionKey = BinaryWriter::toValue(deterministicRandom()->randomUniqueID(), Unversioned()); + Reference tr = db->createTransaction(); + Key versionKey = BinaryWriter::toValue(deterministicRandom()->randomUniqueID(), Unversioned()); if (!conf.address_class.size()) - return ConfigurationResult::INCOMPLETE_CONFIGURATION; // FIXME: correct return type + co_return ConfigurationResult::INCOMPLETE_CONFIGURATION; // FIXME: correct return type - loop { + while (true) { + Error err; + bool hasErr = false; try { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE); tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES); - state typename DB::TransactionT::template FutureT processClassesF; - state typename DB::TransactionT::template FutureT processDataF; - std::vector workers = wait(getWorkers(tr, processClassesF, processDataF)); + typename DB::TransactionT::template FutureT processClassesF; + typename DB::TransactionT::template FutureT processDataF; + std::vector workers = co_await getWorkers(tr, processClassesF, processDataF); std::map>> address_processId; for (auto& w : workers) { address_processId[w.address] = w.locality.processId(); @@ -550,7 +564,7 @@ Future autoConfig(Reference db, ConfigureAutoResult con std::map m; auto r = buildConfiguration(modes, m); if (r != ConfigurationResult::SUCCESS) - return r; + co_return r; for (auto& kv : m) tr->set(kv.first, kv.second); @@ -559,10 +573,14 @@ Future autoConfig(Reference db, ConfigureAutoResult con tr->addReadConflictRange(singleKeyRange(moveKeysLockOwnerKey)); tr->set(moveKeysLockOwnerKey, versionKey); - wait(safeThreadFutureToFuture(tr->commit())); - return ConfigurationResult::SUCCESS; + co_await safeThreadFutureToFuture(tr->commit()); + co_return ConfigurationResult::SUCCESS; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; + hasErr = true; + } + if (hasErr) { + co_await safeThreadFutureToFuture(tr->onError(err)); } } } @@ -600,6 +618,3 @@ Future changeConfig(Reference db, std::string generateErrorMessage(const CoordinatorsResult& res); } // namespace ManagementAPI - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/GlobalConfig.actor.h b/fdbclient/include/fdbclient/GlobalConfig.actor.h deleted file mode 100644 index ce199053bbe..00000000000 --- a/fdbclient/include/fdbclient/GlobalConfig.actor.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * GlobalConfig.actor.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 - -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_GLOBALCONFIG_ACTOR_G_H) -#define FDBCLIENT_GLOBALCONFIG_ACTOR_G_H -#include "fdbclient/GlobalConfig.actor.g.h" -#elif !defined(FDBCLIENT_GLOBALCONFIG_ACTOR_H) -#define FDBCLIENT_GLOBALCONFIG_ACTOR_H - -#include -#include -#include -#include -#include - -#include "fdbclient/CommitProxyInterface.h" -#include "fdbclient/GlobalConfig.h" -#include "fdbclient/ReadYourWrites.h" - -#include "flow/actorcompiler.h" // has to be last include - -// The global configuration is a series of typed key-value pairs synced to all -// nodes (server and client) in an FDB cluster in an eventually consistent -// manner. Only small key-value pairs should be stored in global configuration; -// an excessive amount of data can cause synchronization slowness. - -// Keys -extern const KeyRef fdbClientInfoTxnSampleRate; -extern const KeyRef fdbClientInfoTxnSizeLimit; - -extern const KeyRef transactionTagSampleRate; -extern const KeyRef transactionTagSampleCost; - -extern const KeyRef samplingFrequency; -extern const KeyRef samplingWindow; - -// Structure used to hold the values stored by global configuration. The arena -// is used as memory to store both the key and the value (the value is only -// stored in the arena if it is an object; primitives are just copied). -struct ConfigValue : ReferenceCounted { - Arena arena; - std::any value; - - ConfigValue() {} - ConfigValue(Arena&& a, std::any&& v) : arena(a), value(v) {} -}; - -class GlobalConfig : NonCopyable { -public: - // Requires a database pointer to allow global configuration to run - // transactions on the database. - explicit GlobalConfig(DatabaseContext* cx); - - // Requires an AsyncVar object to watch for changes on. The ClientDBInfo pointer - // should point to a ClientDBInfo object which will contain the updated - // global configuration history when the given AsyncVar changes. This - // function should be called whenever the database object changes, in order - // to allow global configuration to run transactions on the latest - // database. - template - void init(Reference const> db, const ClientDBInfo* dbInfo) { - _updater = updater(this, dbInfo); - // Bind changes in `db` to the `dbInfoChanged` AsyncTrigger. - // TODO: Change AsyncTrigger to a Reference - _forward = forward(db, std::addressof(dbInfoChanged)); - } - - // Given a list of insertions and clears, applies the necessary changes to - // the given transaction to update the global configuration keyspace. Keys - // in the list of mutations should not include the global configuration - // prefix (`\xff\xff/global_config/`). The caller must still commit the - // given transaction in order to persist the changes. - static void applyChanges(Transaction& tr, - const VectorRef& insertions, - const VectorRef& clears); - - // Use this function to turn a global configuration key defined above into - // the full path needed to set the value in the database. - // - // For example, given "config/a", returns "\xff\xff/global_config/config/a". - static Key prefixedKey(KeyRef key); - - // Get a value from the framework. Values are returned as a ConfigValue - // reference which also contains the arena holding the object. As long as - // the caller keeps the ConfigValue reference, the value is guaranteed to - // be readable. An empty reference is returned if the value does not exist. - const Reference get(KeyRef name); - const std::map> get(KeyRangeRef range); - - // For arithmetic value types, returns a copy of the value for the given - // key, or the supplied default value if the framework does not know about - // the key. - template {}, bool>::type = true> - const T get(KeyRef name, T defaultVal) { - try { - auto configValue = get(name); - if (configValue.isValid()) { - if (configValue->value.has_value()) { - return std::any_cast(configValue->value); - } - } - - return defaultVal; - } catch (Error& e) { - throw; - } - } - - // Trying to write into the global configuration keyspace? To write data, - // submit a transaction to \xff\xff/global_config/ with - // encoded using the FDB tuple typecodes. Use the helper - // function `prefixedKey` to correctly prefix your global configuration - // key. - - // Triggers the returned future when the global configuration singleton has - // been created and is ready. - Future onInitialized(); - - // Triggers the returned future when any key-value pair in the global - // configuration changes. - Future onChange(); - - // Calls \ref fn when the value associated with \ref key is changed. \ref - // key should be one of the string literals defined at the top of - // GlobalConfig.actor.cpp, to ensure memory validity. \ref fn is passed the - // updated value for the key, or an empty optional if the key has been - // cleared. If the value is an allocated object, its memory remains in the - // control of global configuration. - void trigger(KeyRef key, std::function)> fn); - -private: - // The functions below only affect the local copy of the global - // configuration keyspace! To insert or remove values across all nodes you - // must use a transaction (see the note above). - - // Inserts the given key-value pair into the local copy of the global - // configuration keyspace, overwriting the old key-value pair if it exists. - // `value` must be encoded using the FDB tuple typecodes. - void insert(KeyRef key, ValueRef value); - // Removes the given key (and associated value) from the local copy of the - // global configuration keyspace. - void erase(Key key); - // Removes the given key range (and associated values) from the local copy - // of the global configuration keyspace. - void erase(KeyRangeRef range); - - // Updates local copy of global configuration by reading the entire key-range - // from storage (proxied through the GrvProxies). Returns the version of the - // refreshed data. - ACTOR static Future refresh(GlobalConfig* self, Version lastKnown, Version largestSeen); - ACTOR static Future updater(GlobalConfig* self, const ClientDBInfo* dbInfo); - - DatabaseContext* cx; - AsyncTrigger dbInfoChanged; - Future _forward; - Future _updater; - Promise initialized; - AsyncTrigger configChanged; - std::unordered_map> data; - Version lastUpdate; - // The key should be a global config string literal key (see the top of this class). - std::unordered_map)>> callbacks; -}; - -#include "flow/unactorcompiler.h" - -#endif diff --git a/fdbclient/include/fdbclient/GlobalConfig.h b/fdbclient/include/fdbclient/GlobalConfig.h index ac7f680f18e..eaad72b186a 100644 --- a/fdbclient/include/fdbclient/GlobalConfig.h +++ b/fdbclient/include/fdbclient/GlobalConfig.h @@ -20,8 +20,21 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include + #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBTypes.h" +#include "flow/genericactors.actor.h" + +class DatabaseContext; +struct ClientDBInfo; +class Transaction; // Used to store a list of mutations made to the global configuration at a // specific version. @@ -29,7 +42,7 @@ struct VersionHistory { constexpr static FileIdentifier file_identifier = 5863456; VersionHistory() {} - VersionHistory(Version v) : version(v) {} + explicit VersionHistory(Version v) : version(v) {} Version version; Standalone> mutations; @@ -43,3 +56,147 @@ struct VersionHistory { serializer(ar, mutations, version); } }; + +// The global configuration is a series of typed key-value pairs synced to all +// nodes (server and client) in an FDB cluster in an eventually consistent +// manner. Only small key-value pairs should be stored in global configuration; +// an excessive amount of data can cause synchronization slowness. + +// Keys +extern const KeyRef fdbClientInfoTxnSampleRate; +extern const KeyRef fdbClientInfoTxnSizeLimit; + +extern const KeyRef transactionTagSampleRate; +extern const KeyRef transactionTagSampleCost; + +extern const KeyRef samplingFrequency; +extern const KeyRef samplingWindow; + +// Structure used to hold the values stored by global configuration. The arena +// is used as memory to store both the key and the value (the value is only +// stored in the arena if it is an object; primitives are just copied). +struct ConfigValue : ReferenceCounted { + Arena arena; + std::any value; + + ConfigValue() {} + ConfigValue(Arena&& a, std::any&& v) : arena(a), value(v) {} +}; + +class GlobalConfig : NonCopyable { +public: + // Requires a database pointer to allow global configuration to run + // transactions on the database. + explicit GlobalConfig(DatabaseContext* cx); + + // Requires an AsyncVar object to watch for changes on. The ClientDBInfo pointer + // should point to a ClientDBInfo object which will contain the updated + // global configuration history when the given AsyncVar changes. This + // function should be called whenever the database object changes, in order + // to allow global configuration to run transactions on the latest + // database. + template + void init(Reference const> db, const ClientDBInfo* dbInfo) { + _updater = updater(dbInfo); + // Bind changes in `db` to the `dbInfoChanged` AsyncTrigger. + // TODO: Change AsyncTrigger to a Reference + _forward = forward(db, std::addressof(dbInfoChanged)); + } + + // Given a list of insertions and clears, applies the necessary changes to + // the given transaction to update the global configuration keyspace. Keys + // in the list of mutations should not include the global configuration + // prefix (`\xff\xff/global_config/`). The caller must still commit the + // given transaction in order to persist the changes. + static void applyChanges(Transaction& tr, + const VectorRef& insertions, + const VectorRef& clears); + + // Use this function to turn a global configuration key defined above into + // the full path needed to set the value in the database. + // + // For example, given "config/a", returns "\xff\xff/global_config/config/a". + static Key prefixedKey(KeyRef key); + + // Get a value from the framework. Values are returned as a ConfigValue + // reference which also contains the arena holding the object. As long as + // the caller keeps the ConfigValue reference, the value is guaranteed to + // be readable. An empty reference is returned if the value does not exist. + Reference get(KeyRef name); + std::map> get(KeyRangeRef range); + + // For arithmetic value types, returns a copy of the value for the given + // key, or the supplied default value if the framework does not know about + // the key. + template + requires(std::is_arithmetic_v) + const T get(KeyRef name, T defaultVal) { + try { + auto configValue = get(name); + if (configValue.isValid()) { + if (configValue->value.has_value()) { + return std::any_cast(configValue->value); + } + } + + return defaultVal; + } catch (Error& e) { + throw; + } + } + + // Trying to write into the global configuration keyspace? To write data, + // submit a transaction to \xff\xff/global_config/ with + // encoded using the FDB tuple typecodes. Use the helper + // function `prefixedKey` to correctly prefix your global configuration + // key. + + // Triggers the returned future when the global configuration singleton has + // been created and is ready. + Future onInitialized(); + + // Triggers the returned future when any key-value pair in the global + // configuration changes. + Future onChange(); + + // Calls \ref fn when the value associated with \ref key is changed. \ref + // key should be one of the string literals defined at the top of + // GlobalConfig.cpp, to ensure memory validity. \ref fn is passed the + // updated value for the key, or an empty optional if the key has been + // cleared. If the value is an allocated object, its memory remains in the + // control of global configuration. + void trigger(KeyRef key, std::function)> fn); + +private: + // The functions below only affect the local copy of the global + // configuration keyspace. To insert or remove values across all nodes you + // must use a transaction (see the note above). + + // Inserts the given key-value pair into the local copy of the global + // configuration keyspace, overwriting the old key-value pair if it exists. + // `value` must be encoded using the FDB tuple typecodes. + void insert(KeyRef key, ValueRef value); + // Removes the given key (and associated value) from the local copy of the + // global configuration keyspace. + void erase(Key key); + // Removes the given key range (and associated values) from the local copy + // of the global configuration keyspace. + void erase(KeyRangeRef range); + + // Updates local copy of global configuration by reading the entire key-range + // from storage (proxied through the GrvProxies). Returns the version of the + // refreshed data. + Future refresh(Version lastKnown, Version largestSeen); + Future updater(const ClientDBInfo* dbInfo); + + DatabaseContext* cx; + AsyncTrigger dbInfoChanged; + Future _forward; + Future _updater; + Promise initialized; + AsyncTrigger configChanged; + std::unordered_map> data; + Version lastUpdate; + // The key should be a global config string literal key (see the top of this file). + std::unordered_map)>> callbacks; +}; diff --git a/fdbclient/include/fdbclient/GrvProxyInterface.h b/fdbclient/include/fdbclient/GrvProxyInterface.h index 347e5279a56..8c3872db015 100644 --- a/fdbclient/include/fdbclient/GrvProxyInterface.h +++ b/fdbclient/include/fdbclient/GrvProxyInterface.h @@ -21,10 +21,158 @@ #ifndef FDBCLIENT_GRVPROXYINTERFACE_H #define FDBCLIENT_GRVPROXYINTERFACE_H #pragma once +#include "fdbclient/TagThrottle.h" +#include "fdbclient/VersionVector.h" #include "flow/FileIdentifier.h" #include "fdbrpc/fdbrpc.h" +#include "fdbrpc/LoadBalance.actor.h" +#include "fdbrpc/Stats.h" +#include "fdbrpc/TimedRequest.h" #include "fdbclient/FDBTypes.h" +struct GetReadVersionReply : public BasicLoadBalancedReply { + constexpr static FileIdentifier file_identifier = 15709388; + Version version; + bool locked; + Optional metadataVersion; + int64_t midShardSize = 0; + bool rkDefaultThrottled = false; + bool rkBatchThrottled = false; + + TransactionTagMap tagThrottleInfo; + + VersionVector ssVersionVectorDelta; + UID proxyId; // GRV proxy ID to detect old GRV proxies at client side + + GetReadVersionReply() : version(invalidVersion), locked(false) {} + + template + void serialize(Ar& ar) { + serializer(ar, + BasicLoadBalancedReply::processBusyTime, + version, + locked, + metadataVersion, + tagThrottleInfo, + midShardSize, + rkDefaultThrottled, + rkBatchThrottled, + ssVersionVectorDelta, + proxyId); + } +}; + +struct GetReadVersionRequest : TimedRequest { + constexpr static FileIdentifier file_identifier = 838566; + enum { + PRIORITY_SYSTEM_IMMEDIATE = + 15 << 24, // Highest possible priority, always executed even if writes are otherwise blocked + PRIORITY_DEFAULT = 8 << 24, + PRIORITY_BATCH = 1 << 24 + }; + enum { + FLAG_USE_MIN_KNOWN_COMMITTED_VERSION = 4, + FLAG_USE_PROVISIONAL_PROXIES = 2, + FLAG_CAUSAL_READ_RISKY = 1, + FLAG_PRIORITY_MASK = PRIORITY_SYSTEM_IMMEDIATE, + }; + + SpanContext spanContext; + uint32_t transactionCount; + uint32_t flags; + TransactionPriority priority; + + TransactionTagMap tags; + + Optional debugID; + ReplyPromise reply; + + Version maxVersion; // max version in the client's version vector cache + Optional maxGrvQueueDelayMS; // max tolerated GRV proxy queue delay due to ratekeeper throttling + + GetReadVersionRequest() : transactionCount(1), flags(0), maxVersion(invalidVersion) {} + GetReadVersionRequest(SpanContext spanContext, + uint32_t transactionCount, + TransactionPriority priority, + Version maxVersion, + uint32_t flags = 0, + TransactionTagMap tags = TransactionTagMap(), + Optional debugID = Optional(), + Optional maxGrvQueueDelayMS = Optional()) + : spanContext(spanContext), transactionCount(transactionCount), flags(flags), priority(priority), tags(tags), + debugID(debugID), maxVersion(maxVersion), maxGrvQueueDelayMS(maxGrvQueueDelayMS) { + this->flags &= ~FLAG_PRIORITY_MASK; + switch (priority) { + case TransactionPriority::BATCH: + this->flags |= PRIORITY_BATCH; + break; + case TransactionPriority::DEFAULT: + this->flags |= PRIORITY_DEFAULT; + break; + case TransactionPriority::IMMEDIATE: + this->flags |= PRIORITY_SYSTEM_IMMEDIATE; + break; + default: + ASSERT(false); + } + } + + bool verify() const { return true; } + + bool operator<(GetReadVersionRequest const& rhs) const { return priority < rhs.priority; } + + bool isTagged() const { return !tags.empty(); } + + template + void serialize(Ar& ar) { + serializer(ar, transactionCount, flags, tags, debugID, reply, spanContext, maxVersion, maxGrvQueueDelayMS); + + if (ar.isDeserializing) { + if ((flags & PRIORITY_SYSTEM_IMMEDIATE) == PRIORITY_SYSTEM_IMMEDIATE) { + priority = TransactionPriority::IMMEDIATE; + } else if ((flags & PRIORITY_DEFAULT) == PRIORITY_DEFAULT) { + priority = TransactionPriority::DEFAULT; + } else if ((flags & PRIORITY_BATCH) == PRIORITY_BATCH) { + priority = TransactionPriority::BATCH; + } else { + priority = TransactionPriority::DEFAULT; + } + } + } +}; + +struct GlobalConfigRefreshReply { + constexpr static FileIdentifier file_identifier = 12680327; + Arena arena; + Version version; + RangeResultRef result; + + GlobalConfigRefreshReply() {} + GlobalConfigRefreshReply(Arena const& arena, Version version, RangeResultRef result) + : arena(arena), version(version), result(result) {} + + template + void serialize(Ar& ar) { + serializer(ar, result, version, arena); + } +}; + +struct GlobalConfigRefreshRequest { + constexpr static FileIdentifier file_identifier = 2828131; + Version lastKnown; + ReplyPromise reply; + + GlobalConfigRefreshRequest() {} + explicit GlobalConfigRefreshRequest(Version lastKnown) : lastKnown(lastKnown) {} + + bool verify() const noexcept { return true; } + + template + void serialize(Ar& ar) { + serializer(ar, lastKnown, reply); + } +}; + // GrvProxy is proxy primarily specializing on serving GetReadVersion. It also // serves health metrics since it communicates with RateKeeper to gather health // information of the cluster, and handles proxied GlobalConfig requests. diff --git a/fdbclient/include/fdbclient/HighContentionPrefixAllocator.actor.h b/fdbclient/include/fdbclient/HighContentionPrefixAllocator.h similarity index 66% rename from fdbclient/include/fdbclient/HighContentionPrefixAllocator.actor.h rename to fdbclient/include/fdbclient/HighContentionPrefixAllocator.h index d685efdc2b0..88e41857078 100644 --- a/fdbclient/include/fdbclient/HighContentionPrefixAllocator.actor.h +++ b/fdbclient/include/fdbclient/HighContentionPrefixAllocator.h @@ -1,5 +1,5 @@ /* - * HighContentionPrefixAllocator.actor.h + * HighContentionPrefixAllocator.h * * This source file is part of the FoundationDB open source project * @@ -20,24 +20,15 @@ #pragma once -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_HIGHCONTENTIONPREFIXALLOCATOR_ACTOR_G_H) -#define FDBCLIENT_HIGHCONTENTIONPREFIXALLOCATOR_ACTOR_G_H -#include "fdbclient/HighContentionPrefixAllocator.actor.g.h" -#elif !defined(FDBCLIENT_HIGHCONTENTIONPREFIXALLOCATOR_ACTOR_H) -#define FDBCLIENT_HIGHCONTENTIONPREFIXALLOCATOR_ACTOR_H - #include "fdbclient/ClientBooleanParams.h" #include "fdbclient/CommitTransaction.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/Subspace.h" #include "flow/UnitTest.h" -#include "flow/actorcompiler.h" // This must be the last #include. class HighContentionPrefixAllocator { public: - HighContentionPrefixAllocator(Subspace subspace) : counters(subspace.get(0)), recent(subspace.get(1)) {} + explicit HighContentionPrefixAllocator(Subspace subspace) : counters(subspace.get(0)), recent(subspace.get(1)) {} template Future> allocate(Reference tr) { @@ -59,22 +50,22 @@ class HighContentionPrefixAllocator { Subspace counters; Subspace recent; - ACTOR template + template Future> allocate(HighContentionPrefixAllocator* self, Reference tr) { - state int64_t start = 0; - state int64_t window = 0; + int64_t start = 0; + int64_t window = 0; - loop { - state typename TransactionT::template FutureT rangeFuture = + while (true) { + typename TransactionT::template FutureT rangeFuture = tr->getRange(self->counters.range(), 1, Snapshot::True, Reverse::True); - RangeResult range = wait(safeThreadFutureToFuture(rangeFuture)); + RangeResult range = co_await safeThreadFutureToFuture(rangeFuture); if (range.size() > 0) { start = self->counters.unpack(range[0].key).getInt(0); } - state bool windowAdvanced = false; - loop { + bool windowAdvanced = false; + while (true) { // if thread safety is needed, this should be locked { if (windowAdvanced) { tr->clear(KeyRangeRef(self->counters.key(), self->counters.get(start).key())); @@ -85,11 +76,11 @@ class HighContentionPrefixAllocator { int64_t inc = 1; tr->atomicOp(self->counters.get(start).key(), StringRef((uint8_t*)&inc, 8), MutationRef::AddValue); - state typename TransactionT::template FutureT> countFuture = + typename TransactionT::template FutureT> countFuture = tr->get(self->counters.get(start).key(), Snapshot::True); // } - Optional countValue = wait(safeThreadFutureToFuture(countFuture)); + Optional countValue = co_await safeThreadFutureToFuture(countFuture); int64_t count = 0; if (countValue.present()) { @@ -108,20 +99,20 @@ class HighContentionPrefixAllocator { windowAdvanced = true; } - loop { - state int64_t candidate = deterministicRandom()->randomInt(start, start + window); + while (true) { + int64_t candidate = deterministicRandom()->randomInt(start, start + window); // if thread safety is needed, this should be locked { - state typename TransactionT::template FutureT latestCounterFuture = + typename TransactionT::template FutureT latestCounterFuture = tr->getRange(self->counters.range(), 1, Snapshot::True, Reverse::True); - state typename TransactionT::template FutureT> candidateValueFuture = + typename TransactionT::template FutureT> candidateValueFuture = tr->get(self->recent.get(candidate).key()); tr->setOption(FDBTransactionOptions::NEXT_WRITE_NO_WRITE_CONFLICT_RANGE); tr->set(self->recent.get(candidate).key(), ValueRef()); // } - wait(success(safeThreadFutureToFuture(latestCounterFuture)) && - success(safeThreadFutureToFuture(candidateValueFuture))); + co_await (success(safeThreadFutureToFuture(latestCounterFuture)) && + success(safeThreadFutureToFuture(candidateValueFuture))); int64_t currentWindowStart = 0; if (latestCounterFuture.get().size() > 0) { @@ -134,12 +125,9 @@ class HighContentionPrefixAllocator { if (!candidateValueFuture.get().present()) { tr->addWriteConflictRange(singleKeyRange(self->recent.get(candidate).key())); - return Tuple::makeTuple(candidate).pack(); + co_return Tuple::makeTuple(candidate).pack(); } } } } }; - -#include "flow/unactorcompiler.h" -#endif \ No newline at end of file diff --git a/fdbclient/include/fdbclient/IBlobStore.h b/fdbclient/include/fdbclient/IBlobStore.h new file mode 100644 index 00000000000..3c5f88629b0 --- /dev/null +++ b/fdbclient/include/fdbclient/IBlobStore.h @@ -0,0 +1,522 @@ +/* + * IBlobStore.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 +#include +#include +#include +#include +#include +#include + +#include "fdbclient/Knobs.h" +#include "fdbclient/JSONDoc.h" +#include "fdbrpc/HTTP.h" +#include "fdbrpc/Stats.h" +#include "flow/IRandom.h" +#include "flow/IRateControl.h" +#include "flow/IConnection.h" +#include "flow/Net2Packet.h" +#include "flow/flow.h" + +#include + +// Blob store endpoint configuration knobs, shared across all implementations. +struct BlobKnobs { + BlobKnobs(); + int secure_connection, connect_tries, connect_timeout, max_connection_life, request_tries, request_timeout_min, + requests_per_second, list_requests_per_second, write_requests_per_second, read_requests_per_second, + delete_requests_per_second, multipart_max_part_size, multipart_min_part_size, concurrent_requests, + concurrent_uploads, concurrent_lists, concurrent_reads_per_file, concurrent_writes_per_file, enable_read_cache, + read_block_size, read_ahead_blocks, read_cache_blocks_per_file, max_send_bytes_per_second, + max_recv_bytes_per_second, sdk_auth, enable_object_integrity_check, global_connection_pool, + max_delay_retryable_error, max_delay_connection_failed, multipart_retry_delay_ms; + + bool set(StringRef name, int value); + std::string getURLParameters() const; + static std::vector getKnobDescriptions() { + return { + "secure_connection (or sc) Set 1 for secure connection and 0 for insecure connection.", + "connect_tries (or ct) Number of times to try to connect for each request.", + "connect_timeout (or cto) Number of seconds to wait for a connect request to succeed.", + "max_connection_life (or mcl) Maximum number of seconds to use a single TCP connection.", + "request_tries (or rt) Number of times to try each request until a parsable HTTP " + "response other than 429 is received.", + "request_timeout_min (or rtom) Number of seconds to wait for a request to succeed after a " + "connection is established.", + "requests_per_second (or rps) Max number of requests to start per second.", + "list_requests_per_second (or lrps) Max number of list requests to start per second.", + "write_requests_per_second (or wrps) Max number of write requests to start per second.", + "read_requests_per_second (or rrps) Max number of read requests to start per second.", + "delete_requests_per_second (or drps) Max number of delete requests to start per second.", + "multipart_max_part_size (or maxps) Max part size for multipart uploads.", + "multipart_min_part_size (or minps) Min part size for multipart uploads.", + "multipart_retry_delay_ms (or mrd) Delay in milliseconds between retries for multipart uploads.", + "concurrent_requests (or cr) Max number of total requests in progress at once, regardless of " + "operation-specific concurrency limits.", + "concurrent_uploads (or cu) Max concurrent uploads (part or whole) that can be in progress " + "at once.", + "concurrent_lists (or cl) Max concurrent list operations that can be in progress at once.", + "concurrent_reads_per_file (or crps) Max concurrent reads in progress for any one file.", + "concurrent_writes_per_file (or cwps) Max concurrent uploads in progress for any one file.", + "enable_read_cache (or erc) Whether read block caching is enabled.", + "read_block_size (or rbs) Block size in bytes to be used for reads.", + "read_ahead_blocks (or rab) Number of blocks to read ahead of requested offset.", + "read_cache_blocks_per_file (or rcb) Size of the read cache for a file in blocks.", + "max_send_bytes_per_second (or sbps) Max send bytes per second for all requests combined.", + "max_recv_bytes_per_second (or rbps) Max receive bytes per second for all requests combined (NOT YET " + "USED).", + "max_delay_retryable_error (or dre) Max seconds to delay before retry when see a retryable error.", + "max_delay_connection_failed (or dcf) Max seconds to delay before retry when see a connection " + "failure.", + "sdk_auth (or sa) Use AWS SDK to resolve credentials. Only valid if " + "BUILD_AWS_BACKUP is enabled.", + "enable_object_integrity_check (or eoic) Enable integrity check on GET requests (Default: false).", + "global_connection_pool (or gcp) Enable shared connection pool between all blobstore instances.", + "provider (or p) Blob store provider: s3 (default) or gcs." + }; + } + + bool isTLS() const { return secure_connection == 1; } +}; + +// Unique key that identifies interchangeable connections for the same settings and destination. +// FIXME: can we define std::hash as a struct member of IBlobStoreEndpoint? +struct BlobStoreConnectionPoolKey { + std::string host; + std::string service; + std::string region; + bool isTLS; + + BlobStoreConnectionPoolKey(const std::string& host, + const std::string& service, + const std::string& region, + bool isTLS) + : host(host), service(service), region(region), isTLS(isTLS) {} + + bool operator==(const BlobStoreConnectionPoolKey& other) const { + return isTLS == other.isTLS && host == other.host && service == other.service && region == other.region; + } +}; + +namespace std { +template <> +struct hash { + std::size_t operator()(const BlobStoreConnectionPoolKey& key) const { + std::size_t seed = 0; + boost::hash_combine(seed, std::hash{}(key.host)); + boost::hash_combine(seed, std::hash{}(key.service)); + boost::hash_combine(seed, std::hash{}(key.region)); + boost::hash_combine(seed, std::hash{}(key.isTLS)); + return seed; + } +}; +} // namespace std + +// Representation of all the things you need to connect to a blob store instance. +// Reference counted because a very large number of them could be needed. +class IBlobStoreEndpoint { +public: + struct Stats { + Stats() : requests_successful(0), requests_failed(0), bytes_sent(0) {} + Stats operator-(const Stats& rhs); + void clear() { memset(this, 0, sizeof(*this)); } + json_spirit::mObject getJSON(); + + int64_t requests_successful; + int64_t requests_failed; + int64_t bytes_sent; + }; + + static Stats s_stats; + + struct BlobStats { + UID id; + CounterCollection cc; + Counter requestsSuccessful; + Counter requestsFailed; + Counter newConnections; + Counter expiredConnections; + Counter reusedConnections; + Counter fastRetries; + + LatencySample requestLatency; + + // init not in static codepath, to avoid initialization race issues and so no blob connections means no + // unnecessary blob stats traces + BlobStats() + : id(deterministicRandom()->randomUniqueID()), cc("BlobStoreStats", id.toString()), + requestsSuccessful("RequestsSuccessful", cc), requestsFailed("RequestsFailed", cc), + newConnections("NewConnections", cc), expiredConnections("ExpiredConnections", cc), + reusedConnections("ReusedConnections", cc), fastRetries("FastRetries", cc), + requestLatency("BlobStoreRequestLatency", + id, + CLIENT_KNOBS->BLOBSTORE_LATENCY_LOGGING_INTERVAL, + CLIENT_KNOBS->BLOBSTORE_LATENCY_LOGGING_ACCURACY) {} + }; + + struct ReusableConnection { + Reference conn; + double expirationTime; + // CROSS_PROCESS_FIX: Track which process created this connection + NetworkAddress creatingProcess; + ReusableConnection() : expirationTime(0) { + if (g_network && g_network->isSimulated()) { + creatingProcess = g_network->getLocalAddress(); + } + } + ReusableConnection(Reference c, double exp) : conn(c), expirationTime(exp) { + if (g_network && g_network->isSimulated()) { + creatingProcess = g_network->getLocalAddress(); + } + } + + // CROSS_PROCESS_FIX: Copy constructor with cross-process detection + explicit(false) ReusableConnection(const ReusableConnection& other) + : conn(other.conn), expirationTime(other.expirationTime), creatingProcess(other.creatingProcess) { + if (g_network && g_network->isSimulated() && creatingProcess.isValid() && + creatingProcess != g_network->getLocalAddress()) { + // Cross-process copy detected - invalidate the connection to prevent sharing + conn = Reference(); + expirationTime = 0; // Mark as expired + } + } + + // CROSS_PROCESS_FIX: Assignment operator with cross-process detection + ReusableConnection& operator=(const ReusableConnection& other) { + if (this != &other) { + conn = other.conn; + expirationTime = other.expirationTime; + creatingProcess = other.creatingProcess; + if (g_network && g_network->isSimulated() && creatingProcess.isValid() && + creatingProcess != g_network->getLocalAddress()) { + // Cross-process assignment detected - invalidate the connection to prevent sharing + conn = Reference(); + expirationTime = 0; // Mark as expired + } + } + return *this; + } + }; + + // Reference counted connection pool queue + struct ConnectionPoolData : NonCopyable, ReferenceCounted { + std::queue pool; + + ConnectionPoolData() = default; + + // Destructor implementation in BlobStoreCommon.cpp + // In simulation, explicitly closes all pooled connections before destruction + ~ConnectionPoolData(); + }; + + struct ObjectInfo { + std::string name; + int64_t size; + }; + + struct ListResult { + std::vector commonPrefixes; + std::vector objects; + }; + + struct PartInfo { + std::string etag; + std::string checksum; // MD5 or SHA256 depending on integrity check setting + PartInfo() = default; + explicit PartInfo(std::string e, std::string c = "") : etag(e), checksum(c) {} + }; + + using ParametersT = std::map; + using MultiPartSetT = std::map; + + // Global connection pool for multiple blobstore endpoints with same connection settings and request destination. + // NOTE: This is disabled by default (BLOBSTORE_GLOBAL_CONNECTION_POOL=false), so each endpoint gets its own pool + static std::unordered_map>& getGlobalConnectionPool() { + // Use process address as key to separate connection pools per simulated process + static std::map>> + processConnectionPools; + + NetworkAddress currentProcess; + if (g_network && g_network->isSimulated()) { + currentProcess = g_network->getLocalAddress(); + } + return processConnectionPools[currentProcess]; + } + +protected: + IBlobStoreEndpoint(std::string const& host, + std::string const& service, + std::string const& region, + Optional const& proxyHost, + Optional const& proxyPort, + BlobKnobs const& knobs = BlobKnobs(), + HTTP::Headers extraHeaders = HTTP::Headers()) + : host(host), service(service), region(region), proxyHost(proxyHost), proxyPort(proxyPort), + useProxy(proxyHost.present() && proxyPort.present()), knobs(knobs), extraHeaders(extraHeaders), + requestRate(new SpeedLimit(knobs.requests_per_second, 1)), + requestRateList(new SpeedLimit(knobs.list_requests_per_second, 1)), + requestRateWrite(new SpeedLimit(knobs.write_requests_per_second, 1)), + requestRateRead(new SpeedLimit(knobs.read_requests_per_second, 1)), + requestRateDelete(new SpeedLimit(knobs.delete_requests_per_second, 1)), + sendRate(new SpeedLimit(knobs.max_send_bytes_per_second, 1)), + recvRate(new SpeedLimit(knobs.max_recv_bytes_per_second, 1)), concurrentRequests(knobs.concurrent_requests), + concurrentUploads(knobs.concurrent_uploads), concurrentLists(knobs.concurrent_lists) { + + if (host.empty() || (proxyHost.present() != proxyPort.present())) + throw connection_string_invalid(); + + // set connection pool instance + if (useProxy || !knobs.global_connection_pool) { + // don't use global connection pool if there's a proxy, as it complicates the logic + // FIXME: handle proxies? + connectionPool = makeReference(); + } else { + BlobStoreConnectionPoolKey key(host, service, region, knobs.isTLS()); + auto it = getGlobalConnectionPool().find(key); + if (it != getGlobalConnectionPool().end()) { + connectionPool = it->second; + } else { + connectionPool = makeReference(); + getGlobalConnectionPool().insert({ key, connectionPool }); + } + } + ASSERT(connectionPool.isValid()); + + maybeStartStatsLogger(); + } + +public: + virtual ~IBlobStoreEndpoint() = default; + virtual void addref() = 0; + virtual void delref() = 0; + + static std::string getURLFormat(bool withResource = false) { + const char* resource = ""; + if (withResource) + resource = ""; + return format("blobstore://@[:]/" + "%s[?=[&=]...]", + resource); + } + + // Parse url and return an IBlobStoreEndpoint. + // If the url has parameters that cannot be consumed then an error will be thrown unless + // ignored_parameters is given in which case the unconsumed parameters will be added to it. + static Reference fromString(const std::string& url, + const Optional& proxy, + std::string* resourceFromURL, + std::string* error, + ParametersT* ignored_parameters); + + std::string getHost() const { return host; } + std::string getRegion() const { return region; } + + // Get a normalized version of this URL with the given resource and any non-default BlobKnob values as URL + // parameters in addition to the passed params string + virtual std::string getResourceURL(std::string resource, std::string params) const; + + // Check if an object exists in a bucket + virtual Future objectExists(std::string const& bucket, std::string const& object) = 0; + + // Get the size of an object in a bucket + virtual Future objectSize(std::string const& bucket, std::string const& object) = 0; + + // Read an arbitrary segment of an object + virtual Future readObject(std::string const& bucket, + std::string const& object, + void* data, + int length, + int64_t offset) = 0; + + // Delete an object in a bucket + virtual Future deleteObject(std::string const& bucket, std::string const& object) = 0; + + // Delete all objects in a bucket under a prefix. Note this is not atomic as blob store does not + // support this operation directly. This method is just a convenience method that lists and deletes + // all of the objects in the bucket under the given prefix. + // Since it can take a while, if a pNumDeleted and/or pBytesDeleted are provided they will be incremented every time + // a deletion of an object completes. + Future deleteRecursively(std::string const& bucket, + std::string prefix = "", + int* pNumDeleted = nullptr, + int64_t* pBytesDeleted = nullptr); + + Future writeEntireFile(std::string const& bucket, std::string const& object, std::string const& content); + virtual Future writeEntireFileFromBuffer(std::string const& bucket, + std::string const& object, + UnsentPacketQueue* pContent, + int contentLen, + std::string const& contentHash) = 0; + + // MultiPart upload methods + // Returns UploadID + virtual Future beginMultiPartUpload(std::string const& bucket, std::string const& object) = 0; + // Returns eTag + virtual Future uploadPart(std::string const& bucket, + std::string const& object, + std::string const& uploadID, + unsigned int partNumber, + UnsentPacketQueue* pContent, + int contentLen, + std::string const& contentHash) = 0; + virtual Future> finishMultiPartUpload(std::string const& bucket, + std::string const& object, + std::string const& uploadID, + MultiPartSetT const& parts, + int64_t totalSize = 0) = 0; + + // Get bucket contents via a stream, since listing large buckets will take many serial blob requests. + // If a delimiter is passed then common prefixes will be read in parallel, recursively, depending on recurseFilter. + // recurseFilter must be a function that takes a string and returns true if it passes. The default behavior is + // to assume true. + virtual Future listObjectsStream(std::string const& bucket, + PromiseStream results, + Optional prefix = {}, + Optional delimiter = {}, + int maxDepth = 0, + std::function recurseFilter = nullptr) = 0; + + // Get a list of the files in a bucket, see listObjectsStream for more argument detail. + AsyncResult listObjects(std::string const& bucket, + Optional prefix = {}, + Optional delimiter = {}, + int maxDepth = 0, + std::function recurseFilter = nullptr); + + // Create a bucket if it does not already exist. + virtual Future createBucket(std::string const& bucket) = 0; + + // Check if a bucket exists + virtual Future bucketExists(std::string const& bucket) = 0; + + // Get a list of all buckets + virtual AsyncResult> listBuckets() = 0; + + // Read an entire small file and return its contents + virtual AsyncResult readEntireFile(std::string const& bucket, std::string const& object) = 0; + + // Provider-specific credential refresh from files or SDK. + // Default implementation reads credential files and calls extractCredentialFields. + virtual Future updateSecret(); + + // Extract credential fields from a JSON account object. + // Returns true if credentials were successfully extracted, false to continue searching. + virtual bool extractCredentialFields(JSONDoc& account) = 0; + + virtual std::string credentialFileKey() const { return "@" + host; } + + virtual bool lookupSecretOnEachRequest() { return true; } + + virtual void setRequestHeaders(std::string const& verb, std::string const& resource, HTTP::Headers& headers) = 0; + + // Transform resource URI for the actual HTTP request (e.g., URI encoding, proxy absolute-form). + virtual std::string normalizeResourceForRequest(std::string const& resource) { return resource; } + + // In simulation, optionally mutate a successful response to inject provider-specific errors. + // Called after receiving the response, before success/failure handling. + virtual void simulateRequestFailure(std::string const& verb, + std::string const& resource, + Reference& r) {} + + // Called on non-success responses during doRequest retry loop. + // Set retryExtended=true to extend retries beyond maxTries. + virtual void processRequestFailure(Reference const& r, + TraceEvent& event, + bool& retryExtended) {} + + // Called before sending the main request when retryExtended is true. + // Returns true to proceed with the request, false to skip this iteration. + virtual Future preRetryCheck(std::string const& verb, + std::string const& resource, + ReusableConnection& rconn, + int requestTimeout, + bool& retryExtended) { + co_return true; + } + + // Do an HTTP request to the blob store, read the response. Handles connection, retry, and authentication. + Future> doRequest(std::string const& verb, + std::string const& resource, + const HTTP::Headers& headers, + UnsentPacketQueue* pContent, + int contentLen, + std::set successCodes); + + // Shared connection management + Future connect(bool* reusingConn); + void returnConnection(ReusableConnection& rconn); + + // Shared state + BlobKnobs knobs; + HTTP::Headers extraHeaders; + std::string host; + std::string service; + std::string region; + Optional proxyHost; + Optional proxyPort; + bool useProxy; + + // FIXME: add periodic connection reaper to pool + Reference connectionPool; + + // Speed and concurrency limits + Reference requestRate; + Reference requestRateList; + Reference requestRateWrite; + Reference requestRateRead; + Reference requestRateDelete; + Reference sendRate; + Reference recvRate; + FlowLock concurrentRequests; + FlowLock concurrentUploads; + FlowLock concurrentLists; + + // null when initialized, so no blob stats until a blob connection is used + std::unique_ptr blobStats; + Future statsLogger; + + void maybeStartStatsLogger() { + if (!blobStats && CLIENT_KNOBS->BLOBSTORE_ENABLE_LOGGING) { + blobStats = std::make_unique(); + specialCounter(blobStats->cc, "GlobalConnectionPoolCount", [this]() { + return this->getGlobalConnectionPool().size(); + }); + specialCounter(blobStats->cc, "GlobalConnectionPoolSize", [this]() { + // FIXME: could track this explicitly via an int variable with extra logic, but this should be small and + // infrequent + int totalConnections = 0; + for (auto& it : this->getGlobalConnectionPool()) { + totalConnections += it.second->pool.size(); + } + return totalConnections; + }); + + statsLogger = blobStats->cc.traceCounters( + "BlobStoreMetrics", blobStats->id, CLIENT_KNOBS->BLOBSTORE_STATS_LOGGING_INTERVAL, "BlobStoreMetrics"); + } + } + // Try to read a file, parse it as JSON, and return the resulting document. + // It will NOT throw if any errors are encountered, it will just return an empty + // JSON object and will log trace events for the errors encountered. + static Future> tryReadJSONFile(std::string path); +}; \ No newline at end of file diff --git a/fdbclient/include/fdbclient/IKnobCollection.h b/fdbclient/include/fdbclient/IKnobCollection.h deleted file mode 100644 index b8fdc4b099d..00000000000 --- a/fdbclient/include/fdbclient/IKnobCollection.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * IKnobCollection.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBCLIENT_IKNOBCOLLECTION_H -#define FDBCLIENT_IKNOBCOLLECTION_H - -#pragma once - -#include - -#include "fdbclient/ClientKnobs.h" -#include "fdbclient/KnobValue.h" -#include "fdbclient/ServerKnobs.h" -#include "flow/Knobs.h" - -/* - * Each IKnobCollection instantiation stores several Knobs objects, and when parsing or setting a knob, - * these Knobs objects will be traversed in order, until the requested knob name is found. The order of traversal is: - * - FlowKnobs - * - ClientKnobs - * - ServerKnobs - * - TestKnobs - */ -class IKnobCollection { - static std::unique_ptr& globalKnobCollection(); - -public: - virtual ~IKnobCollection() = default; - - enum class Type { - CLIENT, - SERVER, - TEST, - }; - - static std::unique_ptr create(Type, Randomize, IsSimulated); - virtual void initialize(Randomize randomize, IsSimulated isSimulated) = 0; - virtual void reset(Randomize randomize, IsSimulated isSimulated) = 0; - virtual FlowKnobs const& getFlowKnobs() const = 0; - virtual ClientKnobs const& getClientKnobs() const = 0; - virtual ServerKnobs const& getServerKnobs() const = 0; - virtual class TestKnobs const& getTestKnobs() const = 0; - virtual void clearTestKnobs() {} - virtual Optional tryParseKnobValue(std::string const& knobName, std::string const& knobValue) const = 0; - KnobValue parseKnobValue(std::string const& knobName, std::string const& knobValue) const; - static KnobValue parseKnobValue(std::string const& knobName, std::string const& knobValue, Type); - // Result indicates whether or not knob was successfully set: - virtual bool trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) = 0; - void setKnob(std::string const& knobName, KnobValueRef const& knobValue); - - static void setGlobalKnobCollection(Type, Randomize, IsSimulated); - static IKnobCollection const& getGlobalKnobCollection(); - static IKnobCollection& getMutableGlobalKnobCollection(); - - // Sets up a list of pairs. If encounter a failure, - // immediately throws the error. - static void setupKnobs(const std::vector>& knobs); - - // KnobValue mutations are no longer emitted via config DB. -}; - -#endif // FDBCLIENT_IKNOBCOLLECTION_H diff --git a/fdbclient/include/fdbclient/IdempotencyId.actor.h b/fdbclient/include/fdbclient/IdempotencyId.h similarity index 89% rename from fdbclient/include/fdbclient/IdempotencyId.actor.h rename to fdbclient/include/fdbclient/IdempotencyId.h index 30ef23c90f8..5162dea9894 100644 --- a/fdbclient/include/fdbclient/IdempotencyId.actor.h +++ b/fdbclient/include/fdbclient/IdempotencyId.h @@ -1,5 +1,5 @@ /* - * IdempotencyId.actor.h + * IdempotencyId.h * * This source file is part of the FoundationDB open source project * @@ -18,14 +18,6 @@ * limitations under the License. */ -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_IDEMPOTENCY_ID_ACTOR_G_H) -#define FDBCLIENT_IDEMPOTENCY_ID_ACTOR_G_H -#include "fdbclient/IdempotencyId.actor.g.h" -#elif !defined(FDBCLIENT_IDEMPOTENCY_ID_ACTOR_H) -#define FDBCLIENT_IDEMPOTENCY_ID_ACTOR_H - #pragma once #include "fdbclient/FDBTypes.h" @@ -34,7 +26,6 @@ #include "flow/Arena.h" #include "flow/IRandom.h" #include "flow/serialize.h" -#include "flow/actorcompiler.h" // this has to be the last include struct CommitResult { Version commitVersion; @@ -94,9 +85,9 @@ struct IdempotencyIdRef { bool operator==(const IdempotencyIdRef& other) const { return asStringRefUnsafe() == other.asStringRefUnsafe(); } - IdempotencyIdRef(IdempotencyIdRef&& other) = default; + explicit(false) IdempotencyIdRef(IdempotencyIdRef&& other) = default; IdempotencyIdRef& operator=(IdempotencyIdRef&& other) = default; - IdempotencyIdRef(const IdempotencyIdRef& other) = default; + explicit(false) IdempotencyIdRef(const IdempotencyIdRef& other) = default; IdempotencyIdRef& operator=(const IdempotencyIdRef& other) = default; template @@ -189,14 +180,11 @@ KeyRangeRef makeIdempotencySingleKeyRange(Arena& arena, Version version, uint8_t void decodeIdempotencyKey(KeyRef key, Version& commitVersion, uint8_t& highOrderBatchIndex); -ACTOR Future getIdmpKeyStatus(Database db); +AsyncResult getIdmpKeyStatus(Database db); // Delete zero or more idempotency ids older than minAgeSeconds // // Normally idempotency ids are deleted as part of the normal commit process, so this only needs to clean ids that // leaked during a failure scenario. The rate of leaked idempotency ids should be low. The rate is zero during normal // operation, and proportional to the number of in-flight transactions during a failure scenario. -ACTOR Future cleanIdempotencyIds(Database db, double minAgeSeconds); - -#include "flow/unactorcompiler.h" -#endif +Future cleanIdempotencyIds(Database db, double minAgeSeconds); diff --git a/fdbclient/include/fdbclient/JSONDoc.h b/fdbclient/include/fdbclient/JSONDoc.h index ce43c7ce81f..4f0975413d2 100644 --- a/fdbclient/include/fdbclient/JSONDoc.h +++ b/fdbclient/include/fdbclient/JSONDoc.h @@ -71,19 +71,19 @@ struct JSONDoc { // Construction from const json_spirit::mObject, trivial and will never throw. // Resulting JSONDoc will not allow modifications. - JSONDoc(const json_spirit::mObject& o) : pObj(&o), wpObj(nullptr) {} + explicit(false) JSONDoc(const json_spirit::mObject& o) : pObj(&o), wpObj(nullptr) {} // Construction from json_spirit::mObject. Allows modifications. - JSONDoc(json_spirit::mObject& o) : pObj(&o), wpObj(&o) {} + explicit(false) JSONDoc(json_spirit::mObject& o) : pObj(&o), wpObj(&o) {} // Construction from const json_spirit::mValue (which is a Variant type) which will try to // convert it to an mObject. This will throw if that fails, just as it would // if the caller called get_obj() itself and used the previous constructor instead. - JSONDoc(const json_spirit::mValue& v) : pObj(&v.get_obj()), wpObj(nullptr) {} + explicit(false) JSONDoc(const json_spirit::mValue& v) : pObj(&v.get_obj()), wpObj(nullptr) {} // Construction from non-const json_spirit::mValue - will convert the mValue to // an object if it isn't already and then attach to it. - JSONDoc(json_spirit::mValue& v) { + explicit(false) JSONDoc(json_spirit::mValue& v) { if (v.type() != json_spirit::obj_type) v = json_spirit::mObject(); wpObj = &v.get_obj(); diff --git a/fdbclient/include/fdbclient/KeyBackedRangeMap.actor.h b/fdbclient/include/fdbclient/KeyBackedRangeMap.h similarity index 86% rename from fdbclient/include/fdbclient/KeyBackedRangeMap.actor.h rename to fdbclient/include/fdbclient/KeyBackedRangeMap.h index ade92d0a438..7740d93a9b5 100644 --- a/fdbclient/include/fdbclient/KeyBackedRangeMap.actor.h +++ b/fdbclient/include/fdbclient/KeyBackedRangeMap.h @@ -1,5 +1,5 @@ /* - * KeyBackedRangeMap.actor.h + * KeyBackedRangeMap.h * * This source file is part of the FoundationDB open source project * @@ -19,15 +19,9 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_KEYBACKEDRANGEMAP_ACTOR_G_H) -#define FDBCLIENT_KEYBACKEDRANGEMAP_ACTOR_G_H -#include "fdbclient/KeyBackedRangeMap.actor.g.h" -#elif !defined(FDBCLIENT_KEYBACKEDRANGEMAP_ACTOR_H) -#define FDBCLIENT_KEYBACKEDRANGEMAP_ACTOR_H - #include "flow/FastRef.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. +#include "fdbclient/KeyBackedTypes.h" +#include "flow/CoroUtils.h" // A local in-memory representation of a KeyBackedRangeMap snapshot // It is invalid to look up a range in this map which not within the range of @@ -132,19 +126,19 @@ class KeyBackedRangeMap { : kvMap(prefix, trigger, valueCodec) {} // Get the RangeValue for the range that contains key, if there is a begin and end in the map which contain key - ACTOR template + template static Future> getRangeForKey(KeyBackedRangeMap self, Transaction tr, KeyType key, Snapshot snapshot = Snapshot::False) { - state Future> begin = self.kvMap.seekLessOrEqual(tr, key, snapshot); - state Future> end = self.kvMap.seekGreaterThan(tr, key, snapshot); - wait(success(begin) && success(end)); + Future> begin = self.kvMap.seekLessOrEqual(tr, key, snapshot); + Future> end = self.kvMap.seekGreaterThan(tr, key, snapshot); + co_await (success(begin) && success(end)); if (begin.get().present() && end.get().present()) { - return RangeValue{ { begin.get()->key, end.get()->key }, begin.get()->value }; + co_return RangeValue{ { begin.get()->key, end.get()->key }, begin.get()->value }; } - return Optional(); + co_return Optional(); } template @@ -158,7 +152,7 @@ class KeyBackedRangeMap { // the the range with the given value. // Adjacent ranges that are identical will be coalesced in the update transaction. // Since the transaction type may not be RYW, this method must take care to not rely on reading its own updates. - ACTOR template + template static Future updateRangeActor(KeyBackedRangeMap self, Transaction tr, KeyType begin, @@ -176,23 +170,23 @@ class KeyBackedRangeMap { // can be removed, which is the case if its value matches the previous boundary's value // - compare the boundary after the last modified boundary to see if it can be removed because // its value matches the last modified boundary's value - Optional beginKV = wait(self.kvMap.seekLessThan(tr, begin)); - state KeySelector rangeBegin = KeySelector::firstGreaterOrEqual(beginKV.present() ? beginKV->key : begin); + Optional beginKV = co_await self.kvMap.seekLessThan(tr, begin); + KeySelector rangeBegin = KeySelector::firstGreaterOrEqual(beginKV.present() ? beginKV->key : begin); // rangeEnd is past end so the result will include end if it exists - state KeySelector rangeEnd = KeySelector::firstGreaterThan(end); + KeySelector rangeEnd = KeySelector::firstGreaterThan(end); - state int readSize = BUGGIFY ? 1 : 100000; - state Future boundariesFuture = + int readSize = BUGGIFY ? 1 : 100000; + Future boundariesFuture = self.kvMap.getRange(tr, rangeBegin, rangeEnd, GetRangeLimits(readSize)); // As we walk through the range boundaries, keep two values about the last visited boundary - state ValueType original; // value prior to modification - state Optional previous; // value after modification + ValueType original{}; // value prior to modification + Optional previous; // value after modification // Indicates that begin has been either seen/updated or created. - state bool beginDone = false; + bool beginDone = false; // Indicates that end was found - state bool endFound = false; + bool endFound = false; // The results will contain // A) 0 or 1 key < begin Use this to initialize previous. @@ -200,9 +194,9 @@ class KeyBackedRangeMap { // Possibly delete it if update matches previous range. // C) >=0 keys > begin and < end Update these, delete if they match previous // D) 0 or 1 key == end. Delete this key if it matches previous. Set to default if it doesn't exist. - loop { + while (true) { kbt_debug("RANGEMAP updateRange loop\n"); - RangeResultType boundaries = wait(boundariesFuture); + RangeResultType boundaries = co_await boundariesFuture; for (auto const& bv : boundaries.results) { kbt_debug("RANGEMAP updateRange result key={} value={}\n", bv.first, bv.second); // Should never see a result past the end key @@ -293,8 +287,6 @@ class KeyBackedRangeMap { kbt_debug("RANGEMAP updateRange set end\n"); self.kvMap.set(tr, end, original); } - - return Void(); } template @@ -312,7 +304,7 @@ class KeyBackedRangeMap { } } - ACTOR template + template static Future> getSnapshotActor(KeyBackedRangeMap self, Transaction tr, KeyType begin, @@ -322,20 +314,20 @@ class KeyBackedRangeMap { // The range read should start at KeySelector::lastLessOrEqual(begin) to get the range which covers begin. // However this could touch a key outside of the map subspace which can lead to various errors. // Use seekLessOrEqual() to find a begin key safely. - Optional beginKV = wait(self.kvMap.seekLessOrEqual(tr, begin)); - state KeySelector rangeBegin = KeySelector::firstGreaterOrEqual(beginKV.present() ? beginKV->key : begin); + Optional beginKV = co_await self.kvMap.seekLessOrEqual(tr, begin); + KeySelector rangeBegin = KeySelector::firstGreaterOrEqual(beginKV.present() ? beginKV->key : begin); // rangeEnd is past end so the result will include end if it exists - state KeySelector rangeEnd = KeySelector::firstGreaterThan(end); + KeySelector rangeEnd = KeySelector::firstGreaterThan(end); - state int readSize = BUGGIFY ? 1 : 100000; - state Future boundariesFuture = + int readSize = BUGGIFY ? 1 : 100000; + Future boundariesFuture = self.kvMap.getRange(tr, rangeBegin, rangeEnd, GetRangeLimits(readSize)); - state Reference result = makeReference(); - loop { + Reference result = makeReference(); + while (true) { kbt_debug("RANGEMAP snapshot loop\n"); - RangeResultType boundaries = wait(boundariesFuture); + RangeResultType boundaries = co_await boundariesFuture; for (auto const& bv : boundaries.results) { result->map[bv.first] = bv.second; } @@ -360,7 +352,7 @@ class KeyBackedRangeMap { } kbt_debug("RANGEMAP snapshot end\n"); - return result; + co_return result; } // Return a LocalSnapshot of all ranges from the map which cover the range of begin through end. @@ -380,7 +372,3 @@ class KeyBackedRangeMap { private: Map kvMap; }; - -#include "flow/unactorcompiler.h" - -#endif diff --git a/fdbclient/include/fdbclient/KeyBackedTypes.actor.h b/fdbclient/include/fdbclient/KeyBackedTypes.h similarity index 91% rename from fdbclient/include/fdbclient/KeyBackedTypes.actor.h rename to fdbclient/include/fdbclient/KeyBackedTypes.h index e4994492aae..ef240c61d58 100644 --- a/fdbclient/include/fdbclient/KeyBackedTypes.actor.h +++ b/fdbclient/include/fdbclient/KeyBackedTypes.h @@ -1,5 +1,5 @@ /* - * KeyBackedTypes.actor.h + * KeyBackedTypes.h * * This source file is part of the FoundationDB open source project * @@ -20,19 +20,13 @@ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_KEYBACKEDTYPES_ACTOR_G_H) -#define FDBCLIENT_KEYBACKEDTYPES_ACTOR_G_H -#include "fdbclient/KeyBackedTypes.actor.g.h" -#elif !defined(FDBCLIENT_KEYBACKEDTYPES_ACTOR_H) -#define FDBCLIENT_KEYBACKEDTYPES_ACTOR_H - #include #include #include #include "fdbclient/ClientBooleanParams.h" #include "fdbclient/CommitTransaction.h" -#include "fdbclient/RunTransaction.actor.h" +#include "fdbclient/RunTransaction.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/GenericTransactionHelper.h" @@ -40,12 +34,11 @@ #include "fdbclient/TupleVersionstamp.h" #include "flow/ObjectSerializer.h" #include "flow/Platform.h" +#include "flow/CoroUtils.h" #include "flow/genericactors.actor.h" #include "flow/serialize.h" #include "flow/ThreadHelper.actor.h" -#include "flow/actorcompiler.h" // This must be the last #include. - #define KEYBACKEDTYPES_DEBUG 0 #if KEYBACKEDTYPES_DEBUG || !defined(NO_INTELLISENSE) #define kbt_debug fmt::print @@ -203,7 +196,8 @@ inline KeyRange TupleCodec::unpack(Standalone const& val) { } template -struct TupleCodec>> { + requires(std::is_enum_v) +struct TupleCodec { static inline Standalone pack(Enum const& val) { return Tuple::makeTuple(static_cast(val)).pack(); } @@ -226,7 +220,7 @@ struct BinaryCodec { // Codec for using Flatbuffer compatible types via ObjectWriter/ObjectReader template struct ObjectCodec { - ObjectCodec(VersionOptions vo) : vo(vo) {} + explicit(false) ObjectCodec(VersionOptions vo) : vo(vo) {} VersionOptions vo; inline Standalone pack(T const& val) const { return ObjectWriter::toValue(val, vo); } @@ -247,7 +241,7 @@ class WatchableTrigger { Key key; public: - WatchableTrigger(Key k) : key(k) {} + explicit WatchableTrigger(Key k) : key(k) {} template void update(Transaction tr, AddConflictRange conflict = AddConflictRange::False) { @@ -279,21 +273,11 @@ class WatchableTrigger { return holdWhile(watchFuture, safeThreadFutureToFuture(watchFuture)); } -// Forward declaration of this static template actor does not work correctly, so this actor is forward declared -// in a way that works for both compiling and in IDEs. -#if defined(NO_INTELLISENSE) template - static Future onChangeActor(WatchableTrigger const& self, - Reference const& db, - Optional const& initialVersion, - Promise const& watching); -#else - ACTOR template static Future onChangeActor(WatchableTrigger self, Reference db, Optional initialVersion, Promise watching); -#endif // Watch the trigger until it changes. The result will be ready when it is observed that the trigger value's // version is greater than initialVersion, and the observed trigger value version will be returned. @@ -313,42 +297,45 @@ class WatchableTrigger { } }; -ACTOR template +template Future WatchableTrigger::onChangeActor(WatchableTrigger self, Reference db, Optional initialVersion, Promise watching) { - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); - loop { + while (true) { if constexpr (can_set_transaction_options) { db->setOptions(tr); } + Error err; try { // If the initialVersion is not set yet, then initialize it with the read version if (!initialVersion.present()) { - wait(store(initialVersion, safeThreadFutureToFuture(tr->getReadVersion()))); + co_await store(initialVersion, safeThreadFutureToFuture(tr->getReadVersion())); } if (watching.canBeSet()) { watching.send(*initialVersion); } // Get the trigger's latest value. - Optional currentVal = wait(self.get(tr)); + Optional currentVal = co_await self.get(tr); // If the trigger has a value and its version is > initialVersion then the trigger has fired so break if (currentVal.present() && currentVal->version > *initialVersion) { - return currentVal->version; + co_return currentVal->version; } // Otherwise, watch the key and repeat the loop once the watch fires - state Future watch = self.watch(tr); - wait(safeThreadFutureToFuture(tr->commit())); - wait(watch); + Future watch = self.watch(tr); + co_await safeThreadFutureToFuture(tr->commit()); + co_await watch; tr->reset(); + continue; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -357,7 +344,8 @@ Future WatchableTrigger::onChangeActor(WatchableTrigger self, template , bool SystemAccess = true> class KeyBackedProperty { public: - KeyBackedProperty(KeyRef key = invalidKey, Optional trigger = {}, Codec codec = {}) + explicit(false) + KeyBackedProperty(KeyRef key = invalidKey, Optional trigger = {}, Codec codec = {}) : key(key), trigger(trigger), codec(codec) {} template @@ -424,8 +412,8 @@ class KeyBackedProperty { } template - std::enable_if_t, Future> set(TransactionContext tcx, - T const& val) { + requires(is_transaction_creator) + Future set(TransactionContext tcx, T const& val) { return runTransaction(tcx, [this, val](decltype(tcx->createTransaction()) tr) { if constexpr (SystemAccess) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); @@ -437,7 +425,8 @@ class KeyBackedProperty { } template - std::enable_if_t, void> set(Transaction tr, T const& val) { + requires(!is_transaction_creator) + void set(Transaction tr, T const& val) { tr->set(key, packValue(val)); if (trigger.present()) { trigger->update(tr); @@ -445,7 +434,8 @@ class KeyBackedProperty { } template - std::enable_if_t, Future> clear(TransactionContext tcx) { + requires(is_transaction_creator) + Future clear(TransactionContext tcx) { return runTransaction(tcx, [this](decltype(tcx->createTransaction()) tr) { if constexpr (SystemAccess) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); @@ -457,7 +447,8 @@ class KeyBackedProperty { } template - std::enable_if_t, void> clear(Transaction tr) { + requires(!is_transaction_creator) + void clear(Transaction tr) { tr->clear(key); if (trigger.present()) { trigger->update(tr); @@ -496,7 +487,8 @@ class KeyBackedBinaryValue : public KeyBackedProperty> { typedef KeyBackedProperty> Base; public: - KeyBackedBinaryValue(KeyRef key = invalidKey, Optional trigger = {}) : Base(key, trigger) {} + explicit(false) KeyBackedBinaryValue(KeyRef key = invalidKey, Optional trigger = {}) + : Base(key, trigger) {} template void atomicOp(Transaction tr, T const& val, MutationRef::Type type) { @@ -562,7 +554,8 @@ template > class KeyBackedMap { public: - KeyBackedMap(KeyRef prefix = invalidKey, Optional trigger = {}, ValueCodec valueCodec = {}) + explicit(false) + KeyBackedMap(KeyRef prefix = invalidKey, Optional trigger = {}, ValueCodec valueCodec = {}) : subspace(prefixRange(prefix)), trigger(trigger), valueCodec(valueCodec) {} typedef _KeyType KeyType; @@ -600,9 +593,9 @@ class KeyBackedMap { map(safeThreadFutureToFuture(getRangeFuture), [prefix = subspace.begin, valueCodec = valueCodec](RangeResult const& kvs) -> RangeResultType { RangeResultType rangeResult; - for (int i = 0; i < kvs.size(); ++i) { - KeyType key = KeyCodec::unpack(kvs[i].key.removePrefix(prefix)); - ValueType val = valueCodec.unpack(kvs[i].value); + for (const auto& keyValue : kvs) { + KeyType key = KeyCodec::unpack(keyValue.key.removePrefix(prefix)); + ValueType val = valueCodec.unpack(keyValue.value); rangeResult.results.push_back(PairType(key, val)); } rangeResult.more = kvs.more; @@ -611,7 +604,7 @@ class KeyBackedMap { } } - ACTOR template + template static Future getRangeActor(KeyBackedMap self, Transaction tr, KeySelector begin, @@ -623,18 +616,18 @@ class KeyBackedMap { begin.pack(self.subspace.begin).toString(), end.pack(self.subspace.begin).toString()); - state ::KeySelector ksBegin = begin.pack(self.subspace.begin); - state ::KeySelector ksEnd = end.pack(self.subspace.begin); - state typename transaction_future_type::type getRangeFuture = + ::KeySelector ksBegin = begin.pack(self.subspace.begin); + ::KeySelector ksEnd = end.pack(self.subspace.begin); + typename transaction_future_type::type getRangeFuture = tr->getRange(ksBegin, ksEnd, limits, snapshot, reverse); // Since the getRange result must be filtered to keys within the map subspace, it is possible that the given // TypedKeySelectors and GetRangeLimits yields a result in which no KV pairs are within the map subspace. If // this happens, we can't return any map KV pairs for the caller to base the next request on, so we will loop // and continue with the next request here. - state RangeResultType rangeResult; - loop { - RangeResult kvs = wait(safeThreadFutureToFuture(getRangeFuture)); + RangeResultType rangeResult{}; + while (true) { + RangeResult kvs = co_await safeThreadFutureToFuture(getRangeFuture); kbt_debug("MAP GETRANGE KeySelectors {} - {} results={} more={}\n", begin.pack(self.subspace.begin).toString(), end.pack(self.subspace.begin).toString(), @@ -680,7 +673,7 @@ class KeyBackedMap { getRangeFuture = tr->getRange(ksBegin, ksEnd, limits, snapshot, reverse); } - return rangeResult; + co_return rangeResult; } // GetRange with typed KeySelectors @@ -698,7 +691,7 @@ class KeyBackedMap { // These operation can be accomplished using KeySelectors however they run the risk of touching keys outside of // map subspace, which can cause problems if this touches an offline range or a key which is unreadable by range // read operations due to having been modified with a version stamp operation in the current transaction. - ACTOR template + template static Future> seek(KeyBackedMap self, Transaction tr, KeyType query, @@ -727,15 +720,15 @@ class KeyBackedMap { end = self.subspace.end; } - state typename transaction_future_type::type getRangeFuture = + typename transaction_future_type::type getRangeFuture = tr->getRange(KeyRangeRef(begin, end), 1, snapshot, Reverse{ lessThan }); - RangeResult kvs = wait(safeThreadFutureToFuture(getRangeFuture)); + RangeResult kvs = co_await safeThreadFutureToFuture(getRangeFuture); if (kvs.empty()) { - return Optional(); + co_return Optional(); } - return self.unpackKV(kvs.front()); + co_return self.unpackKV(kvs.front()); } template @@ -896,7 +889,7 @@ class KeyBackedObjectMap template > class KeyBackedSet { public: - KeyBackedSet(KeyRef key = invalidKey, Optional trigger = {}) + explicit(false) KeyBackedSet(KeyRef key = invalidKey, Optional trigger = {}) : subspace(prefixRange(key)), trigger(trigger) {} typedef _ValueType ValueType; @@ -928,7 +921,7 @@ class KeyBackedSet { })); } - ACTOR template + template static Future getRangeActor(KeyBackedSet self, Transaction tr, KeySelector begin, @@ -940,18 +933,18 @@ class KeyBackedSet { begin.pack(self.subspace.begin).toString(), end.pack(self.subspace.begin).toString()); - state ::KeySelector ksBegin = begin.pack(self.subspace.begin); - state ::KeySelector ksEnd = end.pack(self.subspace.begin); - state typename transaction_future_type::type getRangeFuture = + ::KeySelector ksBegin = begin.pack(self.subspace.begin); + ::KeySelector ksEnd = end.pack(self.subspace.begin); + typename transaction_future_type::type getRangeFuture = tr->getRange(ksBegin, ksEnd, limits, snapshot, reverse); // Since the getRange result must be filtered to keys within the map subspace, it is possible that the given // TypedKeySelectors and GetRangeLimits yields a result in which no KV pairs are within the map subspace. If // this happens, we can't return any map KV pairs for the caller to base the next request on, so we will loop // and continue with the next request here. - state RangeResultType rangeResult; - loop { - RangeResult kvs = wait(safeThreadFutureToFuture(getRangeFuture)); + RangeResultType rangeResult{}; + while (true) { + RangeResult kvs = co_await safeThreadFutureToFuture(getRangeFuture); kbt_debug("MAP GETRANGE KeySelectors {} - {} results={} more={}\n", begin.pack(self.subspace.begin).toString(), end.pack(self.subspace.begin).toString(), @@ -995,7 +988,7 @@ class KeyBackedSet { getRangeFuture = tr->getRange(ksBegin, ksEnd, limits, snapshot, reverse); } - return rangeResult; + co_return rangeResult; } // GetRange with typed KeySelectors @@ -1013,7 +1006,7 @@ class KeyBackedSet { // These operation can be accomplished using KeySelectors however they run the risk of touching keys outside of // map subspace, which can cause problems if this touches an offline range or a key which is unreadable by range // read operations due to having been modified with a version stamp operation in the current transaction. - ACTOR template + template static Future> seek(KeyBackedSet self, Transaction tr, ValueType query, @@ -1042,15 +1035,15 @@ class KeyBackedSet { end = self.subspace.end; } - state typename transaction_future_type::type getRangeFuture = + typename transaction_future_type::type getRangeFuture = tr->getRange(KeyRangeRef(begin, end), 1, snapshot, Reverse{ lessThan }); - RangeResult kvs = wait(safeThreadFutureToFuture(getRangeFuture)); + RangeResult kvs = co_await safeThreadFutureToFuture(getRangeFuture); if (kvs.empty()) { - return Optional(); + co_return Optional(); } - return self.unpackKey(kvs.front().key); + co_return self.unpackKey(kvs.front().key); } template @@ -1143,13 +1136,9 @@ class KeyBackedSet { class KeyBackedClass { public: - KeyBackedClass(StringRef prefix, Optional triggerOverride = {}) + explicit KeyBackedClass(StringRef prefix, Optional triggerOverride = {}) : subspace(prefix), trigger(triggerOverride.orDefault(subspace.pack("_changeTrigger"_sr))) {} Subspace subspace; WatchableTrigger trigger; }; - -#include "flow/unactorcompiler.h" - -#endif diff --git a/fdbclient/include/fdbclient/KeyRangeMap.h b/fdbclient/include/fdbclient/KeyRangeMap.h index 7a90b7f2923..49b44970000 100644 --- a/fdbclient/include/fdbclient/KeyRangeMap.h +++ b/fdbclient/include/fdbclient/KeyRangeMap.h @@ -107,7 +107,7 @@ class CoalescedKeyRangeMap : public RangeMap::operator=(std::move(r)); } - CoalescedKeyRangeMap(CoalescedKeyRangeMap&& source) = default; + explicit(false) CoalescedKeyRangeMap(CoalescedKeyRangeMap&& source) = default; void insert(const KeyRangeRef& keys, const Val& value); void insert(const KeyRef& key, const Val& value); @@ -131,26 +131,26 @@ class KeyRangeActorMap { // krm*(): KeyRangeMap-like abstraction stored in the database, accessed through Transactions class Transaction; class ReadYourWritesTransaction; -Future krmGetRanges(Transaction* const& tr, - Key const& mapPrefix, - KeyRange const& keys, - int const& limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - int const& limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); -Future krmGetRanges(Reference const& tr, - Key const& mapPrefix, - KeyRange const& keys, - int const& limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - int const& limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); -Future krmGetRangesUnaligned(Transaction* const& tr, - Key const& mapPrefix, - KeyRange const& keys, - int const& limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - int const& limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); -Future krmGetRangesUnaligned(Reference const& tr, - Key const& mapPrefix, - KeyRange const& keys, - int const& limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, - int const& limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); +Future krmGetRanges(Transaction* tr, + Key mapPrefix, + KeyRange keys, + int limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + int limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); +Future krmGetRanges(Reference tr, + Key mapPrefix, + KeyRange keys, + int limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + int limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); +Future krmGetRangesUnaligned(Transaction* tr, + Key mapPrefix, + KeyRange keys, + int limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + int limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); +Future krmGetRangesUnaligned(Reference tr, + Key mapPrefix, + KeyRange keys, + int limit = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT, + int limitBytes = CLIENT_KNOBS->KRM_GET_RANGE_LIMIT_BYTES); void krmSetPreviouslyEmptyRange(Transaction* tr, const KeyRef& mapPrefix, const KeyRangeRef& keys, @@ -162,11 +162,8 @@ void krmSetPreviouslyEmptyRange(struct CommitTransactionRef& tr, const KeyRangeRef& keys, const ValueRef& newValue, const ValueRef& oldEndValue); -Future krmSetRange(Transaction* const& tr, Key const& mapPrefix, KeyRange const& range, Value const& value); -Future krmSetRange(Reference const& tr, - Key const& mapPrefix, - KeyRange const& range, - Value const& value); +Future krmSetRange(Transaction* tr, Key mapPrefix, KeyRange range, Value value); +Future krmSetRange(Reference tr, Key mapPrefix, KeyRange range, Value value); Future krmSetRangeCoalescing(Transaction* const& tr, Key const& mapPrefix, KeyRange const& range, diff --git a/fdbclient/include/fdbclient/Knobs.h b/fdbclient/include/fdbclient/Knobs.h index c2e32c523ec..493506f69d5 100644 --- a/fdbclient/include/fdbclient/Knobs.h +++ b/fdbclient/include/fdbclient/Knobs.h @@ -20,10 +20,344 @@ #pragma once -#include "fdbclient/IKnobCollection.h" +#include -#define CLIENT_KNOBS (&IKnobCollection::getGlobalKnobCollection().getClientKnobs()) +#include "fdbclient/KnobValue.h" +#include "flow/BooleanParam.h" +#include "flow/Knobs.h" +#include "flow/flow.h" + +FDB_BOOLEAN_PARAM(Randomize); +FDB_BOOLEAN_PARAM(IsSimulated); + +// Returns a deterministically random transaction timeout value for simulation testing. +// More weight is given to the famous 5s timeout, but [1, 10] range is returned with lower weight. +// This is only be used in simulation. +int getSimulatedTxnTimeoutSeconds(); + +class SWIFT_CXX_IMMORTAL_SINGLETON_TYPE ClientKnobs : public KnobsImpl { +public: + int TOO_MANY; // FIXME: this should really be split up so we can control these more specifically + + double SYSTEM_MONITOR_INTERVAL; + double NETWORK_BUSYNESS_MONITOR_INTERVAL; // The interval in which we should update the network busyness metric + double TSS_METRICS_LOGGING_INTERVAL; + + double FAILURE_MAX_DELAY; + double FAILURE_MIN_DELAY; + double RECOVERY_DELAY_START_GENERATION; + double RECOVERY_DELAY_SECONDS_PER_GENERATION; + double MAX_GENERATIONS; + double MAX_GENERATIONS_OVERRIDE; + double MAX_GENERATIONS_SIM; + + double COORDINATOR_RECONNECTION_DELAY; + int CLIENT_EXAMPLE_AMOUNT; + double MAX_CLIENT_STATUS_AGE; + int MAX_COMMIT_PROXY_CONNECTIONS; + int MAX_GRV_PROXY_CONNECTIONS; + double STATUS_IDLE_TIMEOUT; + double STATUS_TIMEOUT; + double GRPC_CTL_SERVICE_DEFAULT_TIMEOUT; // Default timeout for gRPC FDBCTL service requests + bool SEND_ENTIRE_VERSION_VECTOR; + + // wrong_shard_server sometimes comes from the only nonfailed server, so we need to avoid a fast spin + double WRONG_SHARD_SERVER_DELAY; // SOMEDAY: This delay can limit performance of retrieving data when the cache is + // mostly wrong (e.g. dumping the database after a test) + double FUTURE_VERSION_RETRY_DELAY; + double GRV_ERROR_RETRY_DELAY; + int REPLY_BYTE_LIMIT; + double DEFAULT_BACKOFF; + double DEFAULT_MAX_BACKOFF; + double BACKOFF_GROWTH_RATE; + double RESOURCE_CONSTRAINED_MAX_BACKOFF; + int PROXY_COMMIT_OVERHEAD_BYTES; + double SHARD_STAT_SMOOTH_AMOUNT; + int INIT_MID_SHARD_BYTES; + + int TRANSACTION_SIZE_LIMIT; + int64_t KEY_SIZE_LIMIT; + int64_t SYSTEM_KEY_SIZE_LIMIT; + int64_t VALUE_SIZE_LIMIT; + int64_t SPLIT_KEY_SIZE_LIMIT; + int METADATA_VERSION_CACHE_SIZE; + int64_t CHANGE_FEED_LOCATION_LIMIT; + int64_t CHANGE_FEED_CACHE_SIZE; + double CHANGE_FEED_POP_TIMEOUT; + int64_t CHANGE_FEED_STREAM_MIN_BYTES; + double CHANGE_FEED_START_INTERVAL; + bool CHANGE_FEED_COALESCE_LOCATIONS; + int64_t CHANGE_FEED_CACHE_FLUSH_BYTES; + double CHANGE_FEED_CACHE_EXPIRE_TIME; + int64_t CHANGE_FEED_CACHE_LIMIT_BYTES; + + int MAX_BATCH_SIZE; + double GRV_BATCH_TIMEOUT; + int BROADCAST_BATCH_SIZE; + double TRANSACTION_TIMEOUT_DELAY_INTERVAL; + + // When locationCache in DatabaseContext gets to be this size, items will be evicted + int LOCATION_CACHE_EVICTION_SIZE; + int LOCATION_CACHE_EVICTION_SIZE_SIM; + double LOCATION_CACHE_ENDPOINT_FAILURE_GRACE_PERIOD; + double LOCATION_CACHE_FAILED_ENDPOINT_RETRY_INTERVAL; + + int GET_RANGE_SHARD_LIMIT; + int WARM_RANGE_SHARD_LIMIT; + int STORAGE_METRICS_SHARD_LIMIT; + int SHARD_COUNT_LIMIT; + double STORAGE_METRICS_UNFAIR_SPLIT_LIMIT; + double STORAGE_METRICS_TOO_MANY_SHARDS_DELAY; + double AGGREGATE_HEALTH_METRICS_MAX_STALENESS; + double DETAILED_HEALTH_METRICS_MAX_STALENESS; + double MID_SHARD_SIZE_MAX_STALENESS; + bool TAG_ENCODE_KEY_SERVERS; + bool QUARANTINE_TSS_ON_MISMATCH; + double CHANGE_FEED_EMPTY_BATCH_TIME; + + // KeyRangeMap + int KRM_GET_RANGE_LIMIT; + int KRM_GET_RANGE_LIMIT_BYTES; // This must be sufficiently larger than KEY_SIZE_LIMIT to ensure that at least two + // entries will be returned from an attempt to read a key range map + + int DEFAULT_MAX_OUTSTANDING_WATCHES; + int ABSOLUTE_MAX_WATCHES; // The client cannot set the max outstanding watches higher than this + double WATCH_POLLING_TIME; + double NO_RECENT_UPDATES_DURATION; + double FAST_WATCH_TIMEOUT; + double WATCH_TIMEOUT; + + double IS_ACCEPTABLE_DELAY; + + // Versions -- knobs that control 5s timeout + int64_t VERSIONS_PER_SECOND; // Copy of SERVER_KNOBS, as we can't link with it. + int64_t MAX_WRITE_TRANSACTION_LIFE_VERSIONS; // Copy of SERVER_KNOBS, as we can't link with it. + + // Core + int64_t CORE_VERSIONSPERSECOND; // This is defined within the server but used for knobs based on server value + int LOG_RANGE_BLOCK_SIZE; + int MUTATION_BLOCK_SIZE; + double MAX_VERSION_CACHE_LAG; // The upper bound in seconds for OK amount of staleness when using a cached RV + double MAX_PROXY_CONTACT_LAG; // The upper bound in seconds for how often we want a response from the GRV proxies + double DEBUG_USE_GRV_CACHE_CHANCE; // Debug setting to change the chance for a regular GRV request to use the cache + bool FORCE_GRV_CACHE_OFF; // Panic button to turn off cache. Holds priority over other options. + double GRV_CACHE_RK_COOLDOWN; // Required number of seconds to pass after throttling to re-allow cache use + double GRV_SUSTAINED_THROTTLING_THRESHOLD; // If ALL GRV requests have been throttled in the last number of seconds + // specified here, ratekeeper is throttling and not a false positive + + // Taskbucket + double TASKBUCKET_LOGGING_DELAY; + int TASKBUCKET_MAX_PRIORITY; + double TASKBUCKET_CHECK_TIMEOUT_CHANCE; + double TASKBUCKET_TIMEOUT_JITTER_OFFSET; + double TASKBUCKET_TIMEOUT_JITTER_RANGE; + double TASKBUCKET_CHECK_ACTIVE_DELAY; + int TASKBUCKET_CHECK_ACTIVE_AMOUNT; + int TASKBUCKET_TIMEOUT_VERSIONS; + int TASKBUCKET_MAX_TASK_KEYS; + + // Backup + int BACKUP_LOCAL_FILE_WRITE_BLOCK; + int BACKUP_CONCURRENT_DELETES; + int BACKUP_SIMULATED_LIMIT_BYTES; + int BACKUP_GET_RANGE_LIMIT_BYTES; + int BACKUP_LOCK_BYTES; + double BACKUP_RANGE_TIMEOUT; + double BACKUP_RANGE_MINWAIT; + double BULKDUMP_JOB_TIMEOUT; + double BULKLOAD_JOB_TIMEOUT; + int BACKUP_SNAPSHOT_DISPATCH_INTERVAL_SEC; + int BACKUP_DEFAULT_SNAPSHOT_INTERVAL_SEC; + int BACKUP_SHARD_TASK_LIMIT; + double BACKUP_AGGREGATE_POLL_RATE; + double BACKUP_AGGREGATE_POLL_RATE_UPDATE_INTERVAL; + int BACKUP_LOG_WRITE_BATCH_MAX_SIZE; + int BACKUP_MAX_LOG_RANGES; + int BACKUP_SIM_COPY_LOG_RANGES; + int BACKUP_VERSION_DELAY; + int BACKUP_MAP_KEY_LOWER_LIMIT; + int BACKUP_MAP_KEY_UPPER_LIMIT; + int BACKUP_COPY_TASKS; + int BACKUP_BLOCK_SIZE; + int COPY_LOG_BLOCK_SIZE; + int COPY_LOG_BLOCKS_PER_TASK; + int COPY_LOG_PREFETCH_BLOCKS; + int COPY_LOG_READ_AHEAD_BYTES; + double COPY_LOG_TASK_DURATION_SECONDS; + int BACKUP_TASKS_PER_AGENT; + int BACKUP_POLL_PROGRESS_SECONDS; + int SIM_BACKUP_TASKS_PER_AGENT; + int BACKUP_RANGEFILE_BLOCK_SIZE; + int BACKUP_LOGFILE_BLOCK_SIZE; + int BACKUP_DISPATCH_ADDTASK_SIZE; + bool BACKUP_ALLOW_DRYRUN; + int RESTORE_DISPATCH_ADDTASK_SIZE; + int RESTORE_DISPATCH_BATCH_SIZE; + int RESTORE_WRITE_TX_SIZE; + int RESTORE_PARTITIONED_BATCH_VERSION_SIZE; + int APPLY_MAX_LOCK_BYTES; + int APPLY_MIN_LOCK_BYTES; + int APPLY_BLOCK_SIZE; + double APPLY_MAX_DECAY_RATE; + double APPLY_MAX_INCREASE_FACTOR; + double BACKUP_ERROR_DELAY; + double BACKUP_STATUS_DELAY; + double BACKUP_STATUS_JITTER; + double MIN_CLEANUP_SECONDS; + int RESTORE_RANGES_READ_BATCH; + + // interval for version directory bucketing in range-partitioned backup. + int64_t BACKUP_RANGE_PARTITIONED_VDIR_INTERVAL; + bool BACKUP_CONTAINER_LOCAL_ALLOW_RELATIVE_PATH; + bool ENABLE_REPLICA_CONSISTENCY_CHECK_ON_BACKUP_READS; + int BACKUP_CONSISTENCY_CHECK_REQUIRED_REPLICAS; + bool BACKUP_READS_USE_LOW_PRIORITY; + int BULKLOAD_JOB_HISTORY_COUNT_MAX; // the max number of bulk load job history to keep. The oldest job history will + // be removed when the count exceeds this value. Set to 0 to disable history. + // Do not set the value to a large number, e.g. <= 10. + int BULKLOAD_VERBOSE_LEVEL; // Set to 1 to minimize the verbosity. Set to 5 to turn on all events for performance + // insights. Set to 10 to turn on all events. + int S3CLIENT_VERBOSE_LEVEL; // Similar to BULKLOAD_VERBOSE_LEVEL + + // Configuration + int32_t DEFAULT_AUTO_COMMIT_PROXIES; + int32_t DEFAULT_AUTO_GRV_PROXIES; + int32_t DEFAULT_COMMIT_GRV_PROXIES_RATIO; + int32_t DEFAULT_MAX_GRV_PROXIES; + int32_t DEFAULT_AUTO_RESOLVERS; + int32_t DEFAULT_AUTO_LOGS; + + double GLOBAL_CONFIG_REFRESH_BACKOFF; + double GLOBAL_CONFIG_REFRESH_MAX_BACKOFF; + double GLOBAL_CONFIG_REFRESH_TIMEOUT; + + // Dynamic Knobs + double COMMIT_QUORUM_TIMEOUT; + double GET_GENERATION_QUORUM_TIMEOUT; + double GET_KNOB_TIMEOUT; + double TIMEOUT_RETRY_UPPER_BOUND; + + // Client Status Info + double CSI_SAMPLING_PROBABILITY; + int64_t CSI_SIZE_LIMIT; + double CSI_STATUS_DELAY; + + bool HTTP_REQUEST_AWS_V4_HEADER; // setting this knob to true will enable AWS V4 style header. + std::string BLOBSTORE_ENCRYPTION_TYPE; + int BLOBSTORE_CONNECT_TRIES; + int BLOBSTORE_CONNECT_TIMEOUT; + int BLOBSTORE_MAX_CONNECTION_LIFE; + int BLOBSTORE_REQUEST_TRIES; + int BLOBSTORE_REQUEST_TIMEOUT_MIN; + int BLOBSTORE_REQUESTS_PER_SECOND; + int BLOBSTORE_LIST_REQUESTS_PER_SECOND; + int BLOBSTORE_WRITE_REQUESTS_PER_SECOND; + int BLOBSTORE_READ_REQUESTS_PER_SECOND; + int BLOBSTORE_DELETE_REQUESTS_PER_SECOND; + int BLOBSTORE_CONCURRENT_REQUESTS; + int BLOBSTORE_MULTIPART_MAX_PART_SIZE; + int BLOBSTORE_MULTIPART_MIN_PART_SIZE; + int BLOBSTORE_MULTIPART_RETRY_DELAY_MS; + int BLOBSTORE_CONCURRENT_UPLOADS; + int BLOBSTORE_CONCURRENT_LISTS; + int BLOBSTORE_CONCURRENT_WRITES_PER_FILE; + int BLOBSTORE_CONCURRENT_READS_PER_FILE; + int BLOBSTORE_ENABLE_READ_CACHE; + int BLOBSTORE_READ_BLOCK_SIZE; + int BLOBSTORE_READ_AHEAD_BLOCKS; + int BLOBSTORE_READ_CACHE_BLOCKS_PER_FILE; + int BLOBSTORE_MAX_SEND_BYTES_PER_SECOND; + int BLOBSTORE_MAX_RECV_BYTES_PER_SECOND; + int BLOBSTORE_LIST_MAX_KEYS_PER_PAGE; + bool BLOBSTORE_GLOBAL_CONNECTION_POOL; + bool BLOBSTORE_ENABLE_LOGGING; + double BLOBSTORE_STATS_LOGGING_INTERVAL; + double BLOBSTORE_LATENCY_LOGGING_INTERVAL; + double BLOBSTORE_LATENCY_LOGGING_ACCURACY; + int BLOBSTORE_MAX_DELAY_RETRYABLE_ERROR; + int BLOBSTORE_MAX_DELAY_CONNECTION_FAILED; + bool + BLOBSTORE_ENABLE_OBJECT_INTEGRITY_CHECK; // Enable integrity check of download. When not set, on upload, we + // we volunteer an md5 of the content and upload will fail if our + // md5 doesn't match that calculated by the server. When this flag + // is set, we hash the content using sha256 and have the server store + // the hash in the object metadata. On download, if this flag is set + // we will check the serverside proffered hash against that we + // calculate on the received content. If no match, throw an error. See + // https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html + // This download check only works for small files. For large files, + // we run a separate checksum. See design/s3-checksumming.md + // + int CONSISTENCY_CHECK_RATE_LIMIT_MAX; // Available in both normal and urgent mode + int CONSISTENCY_CHECK_ONE_ROUND_TARGET_COMPLETION_TIME; // Available in normal mode + int CONSISTENCY_CHECK_URGENT_NEXT_WAIT_TIME; // Available in urgent mode + int CONSISTENCY_CHECK_URGENT_BATCH_SHARD_COUNT; // Available in urgent mode + int CONSISTENCY_CHECK_URGENT_RETRY_DEPTH_MAX; // Available in urgent mode + std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_0; // Available in urgent mode + std::string CONSISTENCY_CHECK_URGENT_RANGE_END_0; // Available in urgent mode + std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_1; // Available in urgent mode + std::string CONSISTENCY_CHECK_URGENT_RANGE_END_1; // Available in urgent mode + std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_2; + std::string CONSISTENCY_CHECK_URGENT_RANGE_END_2; + std::string CONSISTENCY_CHECK_URGENT_RANGE_BEGIN_3; + std::string CONSISTENCY_CHECK_URGENT_RANGE_END_3; + + // fdbcli + int CLI_CONNECT_PARALLELISM; + double CLI_CONNECT_TIMEOUT; + bool CLI_PRINT_INVALID_CONFIGURATION; + + // trace + int TRACE_LOG_FILE_IDENTIFIER_MAX_LENGTH; + + // transaction tags + int MAX_TRANSACTION_TAG_LENGTH; + int MAX_TAGS_PER_TRANSACTION; + int COMMIT_SAMPLE_COST; // The expectation of sampling is every COMMIT_SAMPLE_COST sample once + int INCOMPLETE_SHARD_PLUS; // The size of (possible) incomplete shard when estimate clear range + double READ_TAG_SAMPLE_RATE; // Communicated to clients from cluster + double TAG_THROTTLE_SMOOTHING_WINDOW; + double TAG_THROTTLE_RECHECK_INTERVAL; + double TAG_THROTTLE_EXPIRATION_INTERVAL; + int64_t TAG_THROTTLING_PAGE_SIZE; // Used to round up the cost of operations + // Cost multiplier for writes (because write operations are more expensive than reads): + double TAG_THROTTLING_RW_FUNGIBILITY_RATIO; + + // Enable to automatically retry transactions in the presence of transaction_lock_rejection error + // Set to false only for the rangeLocking simulation test + bool TRANSACTION_LOCK_REJECTION_RETRIABLE; + + // busyness reporting + double BUSYNESS_SPIKE_START_THRESHOLD; + double BUSYNESS_SPIKE_SATURATED_THRESHOLD; + + // The coordinator key/value in storage server might be inconsistent to the value stored in the cluster file. + // This might happen when a recovery is happening together with a cluster controller coordinator key change. + // During this process the database will be marked as "bad state" in changeQuorumChecker while later it will be + // available again. Using a backoffed retry when it happens. + int CHANGE_QUORUM_BAD_STATE_RETRY_TIMES; + double CHANGE_QUORUM_BAD_STATE_RETRY_DELAY; + + bool ENABLE_MUTATION_CHECKSUM; + // Enable to start accumulative checksum population and validation + bool ENABLE_ACCUMULATIVE_CHECKSUM; + // Enable to logging verbose trace events related to the accumulative checksum + bool ENABLE_ACCUMULATIVE_CHECKSUM_LOGGING; + + ClientKnobs(Randomize randomize, IsSimulated isSimulated); + void initialize(Randomize randomize, IsSimulated isSimulated); +}; + +extern ClientKnobs const* CLIENT_KNOBS; inline const ClientKnobs& getClientKnobs() { return *CLIENT_KNOBS; } + +void resetClientKnobs(Randomize randomize, IsSimulated isSimulated); +void initializeClientKnobs(Randomize randomize, IsSimulated isSimulated); +Optional tryParseClientKnobValue(std::string const& knobName, std::string const& knobValue); +KnobValue parseClientKnobValue(std::string const& knobName, std::string const& knobValue); +bool trySetClientKnob(std::string const& knobName, KnobValueRef const& knobValue); +void setClientKnob(std::string const& knobName, KnobValueRef const& knobValue); +void setupClientKnobs(std::vector> const& knobs); diff --git a/fdbclient/include/fdbclient/ManagementAPI.actor.h b/fdbclient/include/fdbclient/ManagementAPI.actor.h deleted file mode 100644 index fbf1ed08bf1..00000000000 --- a/fdbclient/include/fdbclient/ManagementAPI.actor.h +++ /dev/null @@ -1,296 +0,0 @@ -/* - * ManagementAPI.actor.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 -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_MANAGEMENT_API_ACTOR_G_H) -#define FDBCLIENT_MANAGEMENT_API_ACTOR_G_H -#include "fdbclient/ManagementAPI.actor.g.h" -#elif !defined(FDBCLIENT_MANAGEMENT_API_ACTOR_H) -#define FDBCLIENT_MANAGEMENT_API_ACTOR_H - -/* This file defines "management" interfaces for configuration, coordination changes, and -the inclusion and exclusion of servers. It is used to implement fdbcli management commands -and by test workloads that simulate such. It isn't exposed to C clients or anywhere outside -our code base and doesn't need to be versioned. It doesn't do anything you can't do with the -standard API and some knowledge of the contents of the system key space. -*/ - -#include -#include -#include "fdbclient/GenericManagementAPI.actor.h" -#include "fdbclient/NativeAPI.actor.h" -#include "fdbclient/RangeLock.h" -#include "fdbclient/ReadYourWrites.h" -#include "fdbclient/DatabaseConfiguration.h" -#include "fdbclient/MonitorLeader.h" -#include "flow/actorcompiler.h" // has to be last include - -ACTOR Future getDatabaseConfiguration(Transaction* tr, bool useSystemPriority = false); -ACTOR Future getDatabaseConfiguration(Database cx, bool useSystemPriority = false); -ACTOR Future waitForFullReplication(Database cx); - -struct IQuorumChange : ReferenceCounted { - virtual ~IQuorumChange() {} - virtual Future> getDesiredCoordinators(Transaction* tr, - std::vector oldCoordinators, - Reference, - CoordinatorsResult&) = 0; - virtual std::string getDesiredClusterKeyName() const { return std::string(); } -}; - -// Change to use the given set of coordination servers -ACTOR Future> changeQuorumChecker(Transaction* tr, - ClusterConnectionString* conn, - std::string newName); -ACTOR Future changeQuorum(Database cx, Reference change); -Reference autoQuorumChange(int desired = -1); -Reference nameQuorumChange(std::string const& name, Reference const& other); - -// Exclude the given set of servers from use as state servers. Returns as soon as the change is durable, without -// necessarily waiting for the servers to be evacuated. A NetworkAddress with a port of 0 means all servers on the -// given IP. -ACTOR Future excludeServers(Database cx, std::vector servers, bool failed = false); -ACTOR Future excludeServers(Transaction* tr, std::vector servers, bool failed = false); - -// Exclude the servers matching the given set of localities from use as state servers. Returns as soon as the change -// is durable, without necessarily waiting for the servers to be evacuated. -ACTOR Future excludeLocalities(Database cx, std::unordered_set localities, bool failed = false); -ACTOR Future excludeLocalities(Transaction* tr, std::unordered_set localities, bool failed = false); - -// Remove the given servers from the exclusion list. A NetworkAddress with a port of 0 means all servers on the given -// IP. A NetworkAddress() means all servers (don't exclude anything) -ACTOR Future includeServers(Database cx, std::vector servers, bool failed = false); - -// Remove the given localities from the exclusion list. -ACTOR Future includeLocalities(Database cx, - std::vector localities, - bool failed = false, - bool includeAll = false); - -// Set the process class of processes with the given address. A NetworkAddress with a port of 0 means all servers on -// the given IP. -ACTOR Future setClass(Database cx, AddressExclusion server, ProcessClass processClass); - -// Get the current list of excluded servers including both "exclude" and "failed". -ACTOR Future> getAllExcludedServers(Database cx); -ACTOR Future> getAllExcludedServers(Transaction* tr); - -// Get the current list of excluded servers. -ACTOR Future> getExcludedServerList(Transaction* tr); - -// Get the current list of failed servers. -ACTOR Future> getExcludedFailedServerList(Transaction* tr); - -// Get the current list of excluded localities -ACTOR Future> getAllExcludedLocalities(Database cx); -ACTOR Future> getAllExcludedLocalities(Transaction* tr); - -// Get the current list of excluded localities. -ACTOR Future> getExcludedLocalityList(Transaction* tr); - -// Get the current list of failed localities. -ACTOR Future> getExcludedFailedLocalityList(Transaction* tr); - -// Decodes the locality string to a pair of locality prefix and its value. -// The prefix could be dcid, processid, machineid, processid. -std::pair decodeLocality(const std::string& locality); -std::set getServerAddressesByLocality( - const std::map server_interfaces, - const std::string& locality); -std::set getAddressesByLocality(const std::vector& workers, const std::string& locality); - -// Check for the given, previously excluded servers to be evacuated (no longer used for state). If waitForExclusion -// is true, this actor returns once it is safe to shut down all such machines without impacting fault tolerance, -// until and unless any of them are explicitly included with includeServers() -ACTOR Future> checkForExcludingServers(Database cx, - std::vector servers, - bool waitForAllExcluded); -ACTOR Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr, - std::set* exclusions, - std::set* inProgressExclusion); - -// Gets a list of all workers in the cluster (excluding testers) -ACTOR Future> getWorkers(Database cx); -ACTOR Future> getWorkers(Transaction* tr); - -ACTOR Future timeKeeperSetDisable(Database cx); - -ACTOR Future lockDatabase(Transaction* tr, UID id); -ACTOR Future lockDatabase(Reference tr, UID id); -ACTOR Future lockDatabase(Database cx, UID id); - -ACTOR Future unlockDatabase(Transaction* tr, UID id); -ACTOR Future unlockDatabase(Reference tr, UID id); -ACTOR Future unlockDatabase(Database cx, UID id); - -ACTOR Future checkDatabaseLock(Transaction* tr, UID id); -ACTOR Future checkDatabaseLock(Reference tr, UID id); - -ACTOR Future advanceVersion(Database cx, Version v); - -ACTOR Future setDDMode(Database cx, int mode); - -ACTOR Future forceRecovery(Reference clusterFile, Standalone dcId); - -// Start an audit on range of the specific type. -ACTOR Future auditStorage(Reference clusterFile, - KeyRange range, - AuditType type, - KeyValueStoreType engineType, - double timeoutSeconds); -// Cancel an audit given type and id -ACTOR Future cancelAuditStorage(Reference clusterFile, - AuditType type, - UID auditId, - double timeoutSeconds); - -// Set bulk load mode -// When the mode is on, DD will periodically check if there is any bulkload task to do by scaning the metadata. -ACTOR Future setBulkLoadMode(Database cx, int mode); - -// Get bulk load mode value. -ACTOR Future getBulkLoadMode(Database cx); - -// Create a bulkload task submission transaction without commit -// Used by ManagementAPI and bulkdumpRestore at DD -ACTOR Future setBulkLoadSubmissionTransaction(Transaction* tr, BulkLoadTaskState bulkLoadTask); - -// Create an bulkload task acknowledge transaction without commit -// Used by ManagementAPI and bulkdumpRestore at DD -ACTOR Future setBulkLoadFinalizeTransaction(Transaction* tr, KeyRange range, UID taskId); - -// Get bulk load task for the input range and taskId -ACTOR Future getBulkLoadTask(Transaction* tr, - KeyRange range, - UID taskId, - std::vector phases); - -// Add bulkLoad job to history map -ACTOR Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState jobState); - -// Get all past bulkLoad jobs from history map -ACTOR Future> getBulkLoadJobFromHistory(Database cx); - -// Erase all bulkLoad job history metadata if jobId is not provided. Otherwise, erase the job with the given jobId. -ACTOR Future clearBulkLoadJobHistory(Database cx, Optional jobId = Optional()); - -// Get the current running bulk load job -// Set lockAware=true when checking during database restore (when database is locked). -ACTOR Future> getRunningBulkLoadJob(Database cx, bool lockAware = false); - -// Cancel bulkLoad job for the given jobId -ACTOR Future cancelBulkLoadJob(Database cx, UID jobId); - -// Acknowledge all bulk load tasks that are in the Error phase. -// After acknowledge, the write traffic to the task's range is turned on and the task's metadata is cleared by the bulk -// load engine. -ACTOR Future acknowledgeAllErrorBulkLoadTasks(Database cx, UID jobId, KeyRange jobRange); - -// Submit a BulkLoad job: loading data from a remote folder using bulkloading mechanism. -// There is at most one BulkLoad or one BulkDump job at a time. -// If there is any existing BulkLoad or BulkDump job, reject the new job. -// Set lockAware=true when submitting during database restore (when database is locked). -ACTOR Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, bool lockAware = false); - -// Return the existing BulkLoad job metadata (if any) -ACTOR Future> getSubmittedBulkLoadJob(Transaction* tr); - -// Set bulk dump mode. When the mode is on, DD will periodically check if there is any bulkdump task to do by scaning -// the metadata. -ACTOR Future setBulkDumpMode(Database cx, int mode); - -// Get bulk dump mode value. -ACTOR Future getBulkDumpMode(Database cx); - -// TODO(BulkDump): Cancel or clear the BulkDump job -ACTOR Future cancelBulkDumpJob(Database cx, UID jobId); - -// Submit a bulkdump job: dumping data to a remote folder by storage servers. -// There is at most one BulkLoad or one BulkDump job at a time. -// If there is any existing BulkLoad or BulkDump job, reject the new job. -ACTOR Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob); - -// Return the existing job metadata -ACTOR Future> getSubmittedBulkDumpJob(Transaction* tr); - -// Get total number of completed tasks within the input range -ACTOR Future getBulkDumpCompleteTaskCount(Database cx, KeyRange rangeToRead); - -// Persist a rangeLock owner to database metadata -// A range can only be locked by a registered owner -ACTOR Future registerRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID, std::string description); - -// Remove an owner form the database metadata -ACTOR Future removeRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID); - -// Get all registered rangeLock owner -ACTOR Future> getAllRangeLockOwners(Database cx); - -// Get a rangeLock owner by ownerUniqueID -ACTOR Future> getRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID); - -// Block write traffic to a user range (the input range must be within normalKeys). -// One transaction can call releaseExclusiveReadLockOnRange at most for one time. -ACTOR Future takeExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID); - -ACTOR Future takeExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID); - -// Unblock a user range (the input range must be within normalKeys). -// One transaction can call releaseExclusiveReadLockOnRange at most for one time. -ACTOR Future releaseExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID); - -ACTOR Future releaseExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID); - -// Get locked ranges within the input range (the input range must be within normalKeys) -ACTOR Future>> findExclusiveReadLockOnRange( - Database cx, - KeyRange range, - Optional ownerName = Optional()); - -// Clear all exclusive read lock by the input user. Not transactional. -ACTOR Future releaseExclusiveReadLockByUser(Database cx, RangeLockOwnerName ownerUniqueID); - -ACTOR Future printHealthyZone(Database cx); -ACTOR Future clearHealthyZone(Database cx, bool printWarning = false, bool clearSSFailureZoneString = false); -ACTOR Future setHealthyZone(Database cx, StringRef zoneId, double seconds, bool printWarning = false); - -ACTOR Future waitForPrimaryDC(Database cx, StringRef dcId); - -// Gets the cluster connection string -ACTOR Future> getConnectionString(Database cx); - -void schemaCoverage(std::string const& spath, bool covered = true); -bool schemaMatch(json_spirit::mValue const& schema, - json_spirit::mValue const& result, - std::string& errorStr, - Severity sev = SevError, - bool checkCoverage = false, - std::string path = std::string(), - std::string schema_path = std::string()); - -// execute payload in 'snapCmd' on all the coordinators, TLogs and -// storage nodes -ACTOR Future mgmtSnapCreate(Database cx, Standalone snapCmd, UID snapUID); - -ACTOR Future disableBackupWorker(Database cx); -ACTOR Future enableBackupWorker(Database cx); - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/ManagementAPI.h b/fdbclient/include/fdbclient/ManagementAPI.h new file mode 100644 index 00000000000..35496efda33 --- /dev/null +++ b/fdbclient/include/fdbclient/ManagementAPI.h @@ -0,0 +1,419 @@ +/* + * ManagementAPI.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 + +/* This file defines "management" interfaces for configuration, coordination changes, and +the inclusion and exclusion of servers. It is used to implement fdbcli management commands +and by test workloads that simulate such. It isn't exposed to C clients or anywhere outside +our code base and doesn't need to be versioned. It doesn't do anything you can't do with the +standard API and some knowledge of the contents of the system key space. +*/ + +#include +#include +#include "fdbclient/GenericManagementAPI.h" +#include "fdbclient/NativeAPI.actor.h" +#include "fdbclient/RangeLock.h" +#include "fdbclient/ReadYourWrites.h" +#include "fdbclient/DatabaseConfiguration.h" +#include "fdbclient/MonitorLeader.h" + +Future getDatabaseConfiguration(Transaction* tr, bool useSystemPriority = false); +Future getDatabaseConfiguration(Database cx, bool useSystemPriority = false); +Future waitForFullReplication(Database cx); + +struct IQuorumChange : ReferenceCounted { + virtual ~IQuorumChange() {} + virtual Future> getDesiredCoordinators(Transaction* tr, + std::vector oldCoordinators, + Reference, + CoordinatorsResult&) = 0; + virtual std::string getDesiredClusterKeyName() const { return std::string(); } +}; + +// Change to use the given set of coordination servers +Future> changeQuorumChecker(Transaction* tr, + ClusterConnectionString* conn, + std::string newName); +Future changeQuorum(Database cx, Reference change); +Reference autoQuorumChange(int desired = -1); +Reference nameQuorumChange(std::string const& name, Reference const& other); + +// Exclude the given set of servers from use as state servers. Returns as soon as the change is durable, without +// necessarily waiting for the servers to be evacuated. A NetworkAddress with a port of 0 means all servers on the +// given IP. +Future excludeServers(Database cx, std::vector servers, bool failed = false); +Future excludeServers(Transaction* tr, std::vector servers, bool failed = false); + +// Exclude the servers matching the given set of localities from use as state servers. Returns as soon as the change +// is durable, without necessarily waiting for the servers to be evacuated. +Future excludeLocalities(Database cx, std::unordered_set localities, bool failed = false); +Future excludeLocalities(Transaction* tr, std::unordered_set localities, bool failed = false); + +// Remove the given servers from the exclusion list. A NetworkAddress with a port of 0 means all servers on the given +// IP. A NetworkAddress() means all servers (don't exclude anything) +Future includeServers(Database cx, std::vector servers, bool failed = false); + +// Remove the given localities from the exclusion list. +Future includeLocalities(Database cx, + std::vector localities, + bool failed = false, + bool includeAll = false); + +// Set the process class of processes with the given address. A NetworkAddress with a port of 0 means all servers on +// the given IP. +Future setClass(Database cx, AddressExclusion server, ProcessClass processClass); + +// Get the current list of excluded servers including both "exclude" and "failed". +Future> getAllExcludedServers(Database cx); +Future> getAllExcludedServers(Transaction* tr); + +// Get the current list of excluded servers. +Future> getExcludedServerList(Transaction* tr); + +// Get the current list of failed servers. +Future> getExcludedFailedServerList(Transaction* tr); + +// Get the current list of excluded localities +Future> getAllExcludedLocalities(Database cx); +Future> getAllExcludedLocalities(Transaction* tr); + +// Get the current list of excluded localities. +Future> getExcludedLocalityList(Transaction* tr); + +// Get the current list of failed localities. +Future> getExcludedFailedLocalityList(Transaction* tr); + +// Decodes the locality string to a pair of locality prefix and its value. +// The prefix could be dcid, processid, machineid, processid. +std::pair decodeLocality(const std::string& locality); +std::set getServerAddressesByLocality( + const std::map server_interfaces, + const std::string& locality); +std::set getAddressesByLocality(const std::vector& workers, const std::string& locality); + +// Check for the given, previously excluded servers to be evacuated (no longer used for state). If waitForExclusion +// is true, this actor returns once it is safe to shut down all such machines without impacting fault tolerance, +// until and unless any of them are explicitly included with includeServers() +Future> checkForExcludingServers(Database cx, + std::vector servers, + bool waitForAllExcluded); +Future checkForExcludingServersTxActor(ReadYourWritesTransaction* tr, + std::set* exclusions, + std::set* inProgressExclusion); + +// Gets a list of all workers in the cluster (excluding testers) +Future> getWorkers(Database cx); +Future> getWorkers(Transaction* tr); + +Future timeKeeperSetDisable(Database cx); + +Future lockDatabase(Transaction* tr, UID id); +Future lockDatabase(Reference tr, UID id); +Future lockDatabase(Database cx, UID id); + +Future unlockDatabase(Transaction* tr, UID id); +Future unlockDatabase(Reference tr, UID id); +Future unlockDatabase(Database cx, UID id); + +Future checkDatabaseLock(Transaction* tr, UID id); +Future checkDatabaseLock(Reference tr, UID id); + +Future advanceVersion(Database cx, Version v); + +Future setDDMode(Database cx, int mode); + +Future forceRecovery(Reference clusterFile, Standalone dcId); + +// Start an audit on range of the specific type. +Future auditStorage(Reference clusterFile, + KeyRange range, + AuditType type, + KeyValueStoreType engineType, + double timeoutSeconds); +// Cancel an audit given type and id +Future cancelAuditStorage(Reference clusterFile, + AuditType type, + UID auditId, + double timeoutSeconds); + +// Set bulk load mode +// When the mode is on, DD will periodically check if there is any bulkload task to do by scaning the metadata. +Future setBulkLoadMode(Database cx, int mode); + +// Get bulk load mode value. +Future getBulkLoadMode(Database cx); + +// Create a bulkload task submission transaction without commit +// Used by ManagementAPI and bulkdumpRestore at DD +Future setBulkLoadSubmissionTransaction(Transaction* tr, BulkLoadTaskState bulkLoadTask); + +// Create an bulkload task acknowledge transaction without commit +// Used by ManagementAPI and bulkdumpRestore at DD +Future setBulkLoadFinalizeTransaction(Transaction* tr, KeyRange range, UID taskId); + +// Get bulk load task for the input range and taskId +Future getBulkLoadTask(Transaction* tr, + KeyRange range, + UID taskId, + std::vector phases); + +// Add bulkLoad job to history map +Future addBulkLoadJobToHistory(Transaction* tr, BulkLoadJobState jobState); + +// Get all past bulkLoad jobs from history map +AsyncResult> getBulkLoadJobFromHistory(Database cx); + +// Erase all bulkLoad job history metadata if jobId is not provided. Otherwise, erase the job with the given jobId. +Future clearBulkLoadJobHistory(Database cx, Optional jobId = Optional()); + +// Get the current running bulk load job +// Set lockAware=true when checking during database restore (when database is locked). +Future> getRunningBulkLoadJob(Database cx, bool lockAware = false); + +// Cancel bulkLoad job for the given jobId +Future cancelBulkLoadJob(Database cx, UID jobId); + +// Acknowledge all bulk load tasks that are in the Error phase. +// After acknowledge, the write traffic to the task's range is turned on and the task's metadata is cleared by the bulk +// load engine. +Future acknowledgeAllErrorBulkLoadTasks(Database cx, UID jobId, KeyRange jobRange); + +// Submit a BulkLoad job: loading data from a remote folder using bulkloading mechanism. +// There is at most one BulkLoad or one BulkDump job at a time. +// If there is any existing BulkLoad or BulkDump job, reject the new job. +// Set lockAware=true when submitting during database restore (when database is locked). +Future submitBulkLoadJob(Database cx, BulkLoadJobState jobState, bool lockAware = false); + +// Return the existing BulkLoad job metadata (if any) +Future> getSubmittedBulkLoadJob(Transaction* tr); + +// Set bulk dump mode. When the mode is on, DD will periodically check if there is any bulkdump task to do by scaning +// the metadata. +Future setBulkDumpMode(Database cx, int mode); + +// Get bulk dump mode value. +Future getBulkDumpMode(Database cx); + +// TODO(BulkDump): Cancel or clear the BulkDump job +Future cancelBulkDumpJob(Database cx, UID jobId); + +// Submit a bulkdump job: dumping data to a remote folder by storage servers. +// There is at most one BulkLoad or one BulkDump job at a time. +// If there is any existing BulkLoad or BulkDump job, reject the new job. +Future submitBulkDumpJob(Database cx, BulkDumpState bulkDumpJob); + +// Return the existing job metadata +Future> getSubmittedBulkDumpJob(Transaction* tr); + +// Get total number of completed tasks within the input range +Future getBulkDumpCompleteTaskCount(Database cx, KeyRange rangeToRead); + +// ==================== BulkDump/BulkLoad Progress Tracking ==================== + +// Represents a stalled task for observability +struct BulkLoadStalledTask { + UID taskId; + KeyRange range; + UID storageServerId; + double stalledSeconds; + int restartCount; + std::string lastError; + + BulkLoadStalledTask() : stalledSeconds(0), restartCount(0) {} + BulkLoadStalledTask(UID taskId, + KeyRange range, + UID storageServerId, + double stalledSeconds, + int restartCount, + std::string lastError) + : taskId(taskId), range(range), storageServerId(storageServerId), stalledSeconds(stalledSeconds), + restartCount(restartCount), lastError(lastError) {} +}; + +// Aggregated progress for a BulkDump job +struct BulkDumpProgress { + UID jobId; + KeyRange jobRange; + int totalTasks; + int completeTasks; + int runningTasks; + int errorTasks; + int64_t totalBytes; + int64_t completedBytes; + double elapsedSeconds; + double startTime; // For computing current throughput + std::vector stalledTasks; + + BulkDumpProgress() + : totalTasks(0), completeTasks(0), runningTasks(0), errorTasks(0), totalBytes(0), completedBytes(0), + elapsedSeconds(0), startTime(0) {} + + double progressPercent() const { return totalTasks > 0 ? 100.0 * completeTasks / totalTasks : 0; } + + double avgBytesPerSecond() const { return elapsedSeconds > 0 ? completedBytes / elapsedSeconds : 0; } + + Optional etaSeconds() const { + double rate = avgBytesPerSecond(); + if (rate > 0 && completedBytes < totalBytes) { + return (totalBytes - completedBytes) / rate; + } + return {}; + } +}; + +// Aggregated progress for a BulkLoad job +struct BulkLoadProgress { + UID jobId; + KeyRange jobRange; + int totalTasks; + int submittedTasks; + int triggeredTasks; + int runningTasks; + int completeTasks; + int errorTasks; + int64_t totalBytes; + int64_t completedBytes; + double elapsedSeconds; + double startTime; + std::vector stalledTasks; + + BulkLoadProgress() + : totalTasks(0), submittedTasks(0), triggeredTasks(0), runningTasks(0), completeTasks(0), errorTasks(0), + totalBytes(0), completedBytes(0), elapsedSeconds(0), startTime(0) {} + + double progressPercent() const { return totalTasks > 0 ? 100.0 * completeTasks / totalTasks : 0; } + + double avgBytesPerSecond() const { return elapsedSeconds > 0 ? completedBytes / elapsedSeconds : 0; } + + Optional etaSeconds() const { + double rate = avgBytesPerSecond(); + if (rate > 0 && completedBytes < totalBytes) { + return (totalBytes - completedBytes) / rate; + } + return {}; + } +}; + +// Get aggregated progress for the current BulkDump job (if any) +// Reads task metadata from system keys and computes aggregate metrics +Future> getBulkDumpProgress(Database cx); + +// Get aggregated progress for the current BulkLoad job (if any) +// Reads task metadata from system keys and computes aggregate metrics +Future> getBulkLoadProgress(Database cx); + +// Threshold in seconds for considering a task "stalled". +// SST file downloads from blobstore can take 5-10 minutes per task, +// so this needs to be well above normal download times. +constexpr double BULK_TASK_STALL_THRESHOLD_SECONDS = 600.0; + +// BulkDump owner tracking - stored in separate system keys for backward compatibility +struct BulkDumpOwnerInfo { + UID ownerUID; + std::string ownerType; // e.g., "backup", "dr", "migration" + std::string ownerName; // e.g., backup tag + double submitTime; + + constexpr static FileIdentifier file_identifier = 1384510; + + BulkDumpOwnerInfo() : submitTime(0) {} + BulkDumpOwnerInfo(UID uid, std::string type, std::string name, double time) + : ownerUID(uid), ownerType(type), ownerName(name), submitTime(time) {} + + template + void serialize(Ar& ar) { + serializer(ar, ownerUID, ownerType, ownerName, submitTime); + } +}; + +// Generic bulk owner tracking (internal implementation) +Future setBulkOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo, bool isBulkDump); +Future> getBulkOwner(Database cx, UID jobId, bool isBulkDump); + +// Public API wrappers (for backward compatibility) +Future setBulkDumpOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo); +Future> getBulkDumpOwner(Database cx, UID jobId); +Future setBulkLoadOwner(Database cx, UID jobId, BulkDumpOwnerInfo ownerInfo); +Future> getBulkLoadOwner(Database cx, UID jobId); + +// ==================== End Progress Tracking ==================== + +// Persist a rangeLock owner to database metadata +// A range can only be locked by a registered owner +Future registerRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID, std::string description); + +// Remove an owner form the database metadata +Future removeRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID); + +// Get all registered rangeLock owner +AsyncResult> getAllRangeLockOwners(Database cx); + +// Get a rangeLock owner by ownerUniqueID +Future> getRangeLockOwner(Database cx, RangeLockOwnerName ownerUniqueID); + +// Block write traffic to a user range (the input range must be within normalKeys). +// One transaction can call releaseExclusiveReadLockOnRange at most for one time. +Future takeExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID); + +Future takeExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID); + +// Unblock a user range (the input range must be within normalKeys). +// One transaction can call releaseExclusiveReadLockOnRange at most for one time. +Future releaseExclusiveReadLockOnRange(Transaction* tr, KeyRange range, RangeLockOwnerName ownerUniqueID); + +Future releaseExclusiveReadLockOnRange(Database cx, KeyRange range, RangeLockOwnerName ownerUniqueID); + +// Get locked ranges within the input range (the input range must be within normalKeys) +Future>> findExclusiveReadLockOnRange( + Database cx, + KeyRange range, + Optional ownerName = Optional()); + +// Clear all exclusive read lock by the input user. Not transactional. +Future releaseExclusiveReadLockByUser(Database cx, RangeLockOwnerName ownerUniqueID); + +Future printHealthyZone(Database cx); +Future clearHealthyZone(Database cx, bool printWarning = false, bool clearSSFailureZoneString = false); +Future setHealthyZone(Database cx, StringRef zoneId, double seconds, bool printWarning = false); + +Future waitForPrimaryDC(Database cx, StringRef dcId); + +// Gets the cluster connection string +Future> getConnectionString(Database cx); + +void schemaCoverage(std::string const& spath, bool covered = true); +bool schemaMatch(json_spirit::mValue const& schema, + json_spirit::mValue const& result, + std::string& errorStr, + Severity sev = SevError, + bool checkCoverage = false, + std::string path = std::string(), + std::string schema_path = std::string()); + +// execute payload in 'snapCmd' on all the coordinators, TLogs and +// storage nodes +Future mgmtSnapCreate(Database cx, Standalone snapCmd, UID snapUID); + +Future disableBackupWorker(Database cx); +Future enableBackupWorker(Database cx); +Future disableRangeBackupWorker(Database cx); +Future enableRangeBackupWorker(Database cx); diff --git a/fdbclient/include/fdbclient/MonitorLeader.h b/fdbclient/include/fdbclient/MonitorLeader.h index 9aa2dc837a1..607452ba971 100644 --- a/fdbclient/include/fdbclient/MonitorLeader.h +++ b/fdbclient/include/fdbclient/MonitorLeader.h @@ -75,19 +75,18 @@ Future monitorLeader(Reference const& connFile, // This is one place where the leader election algorithm is run. The coodinator contacts all coodinators to collect // nominees, the nominee with the most nomination is the leader, and collects client data from the leader. This function // also monitors the change of the leader. -Future monitorLeaderAndGetClientInfo(Key const& clusterKey, - std::vector const& hostnames, - std::vector const& coordinators, - ClientData* const& clientData, - Reference>> const& leaderInfo); - -Future monitorProxies( - Reference>> const& connRecord, - Reference> const& clientInfo, - Reference>> const& coordinator, - Reference>>> const& supportedVersions, - Key const& traceLogGroup, - IsInternal const& internal); +Future monitorLeaderAndGetClientInfo(Key clusterKey, + std::vector hostnames, + std::vector coordinators, + ClientData* clientData, + Reference>> leaderInfo); + +Future monitorProxies(Reference>> connRecord, + Reference> clientInfo, + Reference>> coordinator, + Reference>>> supportedVersions, + Key traceLogGroup, + IsInternal internal); void shrinkProxyList(ClientDBInfo& ni, std::vector& lastCommitProxyUIDs, @@ -99,8 +98,8 @@ void shrinkProxyList(ClientDBInfo& ni, #pragma region Implementation #endif -Future monitorLeaderInternal(Reference const& connRecord, - Reference> const& outSerializedLeaderInfo); +Future monitorLeaderInternal(Reference connRecord, + Reference> outSerializedLeaderInfo); template struct LeaderDeserializer { @@ -110,8 +109,8 @@ struct LeaderDeserializer { } }; -Future asyncDeserializeClusterInterface(const Reference>& serializedInfo, - const Reference>>& outKnownLeader); +Future asyncDeserializeClusterInterface(Reference> serializedInfo, + Reference>> outKnownLeader); template <> struct LeaderDeserializer { diff --git a/fdbclient/include/fdbclient/MultiVersionTransaction.h b/fdbclient/include/fdbclient/MultiVersionTransaction.h index f4c351b3ce0..ffbf8a008d2 100644 --- a/fdbclient/include/fdbclient/MultiVersionTransaction.h +++ b/fdbclient/include/fdbclient/MultiVersionTransaction.h @@ -393,7 +393,7 @@ class DLDatabase : public IDatabase, ThreadSafeReferenceCounted { // The DL prefix stands for "dynamic library". class DLApi : public IClientApi { public: - DLApi(std::string fdbCPath, bool unlinkOnLoad = false); + explicit DLApi(std::string fdbCPath, bool unlinkOnLoad = false); void selectApiVersion(int apiVersion) override; const char* getClientVersion() override; @@ -585,7 +585,7 @@ struct ClientInfo : ClientDesc, ThreadSafeReferenceCounted { ClientInfo() : ClientDesc(std::string(), false, false), protocolVersion(0), api(nullptr), failed(true), initialized(false), threadIndex(0) {} - ClientInfo(IClientApi* api) + explicit ClientInfo(IClientApi* api) : ClientDesc("internal", false, false), protocolVersion(0), api(api), failed(false), initialized(false), threadIndex(0) {} ClientInfo(IClientApi* api, std::string libPath, bool useFutureVersion, int threadIndex) diff --git a/fdbclient/include/fdbclient/MutationLogReader.actor.h b/fdbclient/include/fdbclient/MutationLogReader.h similarity index 78% rename from fdbclient/include/fdbclient/MutationLogReader.actor.h rename to fdbclient/include/fdbclient/MutationLogReader.h index ea56b81775c..cf27f7934e9 100644 --- a/fdbclient/include/fdbclient/MutationLogReader.actor.h +++ b/fdbclient/include/fdbclient/MutationLogReader.h @@ -1,5 +1,5 @@ /* - * MutationLogReader.actor.h + * MutationLogReader.h * * This source file is part of the FoundationDB open source project * @@ -20,18 +20,11 @@ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_MUTATION_LOG_READER_ACTOR_G_H) -#define FDBCLIENT_MUTATION_LOG_READER_ACTOR_G_H -#include "fdbclient/MutationLogReader.actor.g.h" -#elif !defined(FDBCLIENT_MUTATION_LOG_READER_ACTOR_H) -#define FDBCLIENT_MUTATION_LOG_READER_ACTOR_H - #include #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" #include "flow/flow.h" #include "flow/ActorCollection.h" -#include "flow/actorcompiler.h" // has to be last include namespace mutation_log_reader { @@ -68,7 +61,7 @@ class PipelinedReader { void startReading(Database cx); Future getNext(Database cx); - ACTOR static Future getNext_impl(PipelinedReader* self, Database cx); + static Future getNext_impl(PipelinedReader* self, Database cx); void release() { readerLimit.release(); } @@ -109,22 +102,22 @@ class MutationLogReader : public ReferenceCounted { } } - ACTOR static Future> Create(Database cx, - Version bv, - Version ev, - Key uid, - Key beginKey, - unsigned pd) { - state Reference self(new MutationLogReader(cx, bv, ev, uid, beginKey, pd)); - wait(self->initializePQ(self.getPtr())); - return self; + static Future> Create(Database cx, + Version bv, + Version ev, + Key uid, + Key beginKey, + unsigned pd) { + Reference self(new MutationLogReader(cx, bv, ev, uid, beginKey, pd)); + co_await self->initializePQ(self.getPtr()); + co_return self; } Future> getNext(); private: - ACTOR static Future initializePQ(MutationLogReader* self); - ACTOR static Future> getNext_impl(MutationLogReader* self); + static Future initializePQ(MutationLogReader* self); + static Future> getNext_impl(MutationLogReader* self); std::vector> pipelinedReaders; std::priority_queue priorityQueue; @@ -133,6 +126,3 @@ class MutationLogReader : public ReferenceCounted { unsigned pipelineDepth; unsigned finished; }; - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/NativeAPI.actor.h b/fdbclient/include/fdbclient/NativeAPI.actor.h index 9921b7e0616..82317ea2c43 100644 --- a/fdbclient/include/fdbclient/NativeAPI.actor.h +++ b/fdbclient/include/fdbclient/NativeAPI.actor.h @@ -28,7 +28,7 @@ #include "flow/BooleanParam.h" #include "flow/flow.h" #include "flow/WipedString.h" -#include "flow/TDMetric.actor.h" +#include "flow/TDMetric.h" #include "flow/IRandom.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/CommitProxyInterface.h" @@ -110,8 +110,8 @@ class Database { Database() {} // an uninitialized database can be destructed or reassigned safely; that's it void operator=(Database const& rhs) { db = rhs.db; } - Database(Database const& rhs) : db(rhs.db) {} - Database(Database&& r) noexcept : db(std::move(r.db)) {} + explicit(false) Database(Database const& rhs) : db(rhs.db) {} + explicit(false) Database(Database&& r) noexcept : db(std::move(r.db)) {} void operator=(Database&& r) noexcept { db = std::move(r.db); } // For internal use by the native client: @@ -175,6 +175,7 @@ struct TransactionOptions { bool rawAccess : 1; bool bypassStorageQuota : 1; bool enableReplicaConsistencyCheck : 1; + Optional maxGrvQueueDelayMS; int requiredReplicas; TransactionPriority priority; @@ -184,7 +185,7 @@ struct TransactionOptions { // update clear function if you add a new field - TransactionOptions(Database const& cx); + explicit TransactionOptions(Database const& cx); TransactionOptions(); void reset(Database const& cx); @@ -199,7 +200,7 @@ struct TransactionLogInfo : public ReferenceCounted, NonCopy enum LoggingLocation { DONT_LOG = 0, TRACE_LOG = 1, DATABASE = 2 }; TransactionLogInfo() : logLocation(DONT_LOG), maxFieldLength(0) {} - TransactionLogInfo(LoggingLocation location) : logLocation(location), maxFieldLength(0) {} + explicit TransactionLogInfo(LoggingLocation location) : logLocation(location), maxFieldLength(0) {} TransactionLogInfo(std::string id, LoggingLocation location) : logLocation(location), maxFieldLength(0), identifier(id) {} @@ -245,7 +246,7 @@ struct Watch : public ReferenceCounted, NonCopyable { Optional readOptions; Watch() : valuePresent(false), setPresent(false), watchFuture(Never()) {} - Watch(Key key) : key(key), valuePresent(false), setPresent(false), watchFuture(Never()) {} + explicit Watch(Key key) : key(key), valuePresent(false), setPresent(false), watchFuture(Never()) {} Watch(Key key, Optional val) : key(key), value(val), valuePresent(true), setPresent(false), watchFuture(Never()) {} @@ -269,8 +270,6 @@ struct TransactionState : ReferenceCounted { // after rounding up to the nearest page size and applying a write penalty int64_t totalCost = 0; - double proxyTagThrottledDuration = 0.0; - int numErrors = 0; double startTime = 0; Promise> versionstampPromise; @@ -543,9 +542,7 @@ Future Database::run(Fun fun) { } ACTOR Future waitForCommittedVersion(Database cx, Version version, SpanContext spanContext); -ACTOR Future>> waitDataDistributionMetricsList(Database cx, - KeyRange keys, - int shardLimit); +Future>> waitDataDistributionMetricsList(Database cx, KeyRange keys, int shardLimit); int64_t extractIntOption(Optional value, int64_t minValue = std::numeric_limits::min(), @@ -553,7 +550,7 @@ int64_t extractIntOption(Optional value, // Takes a snapshot of the cluster, specifically the following persistent // states: coordinator, TLog and storage state -ACTOR Future snapCreate(Database cx, Standalone snapCmd, UID snapUID); +Future snapCreate(Database cx, Standalone snapCmd, UID snapUID); // Adds necessary mutation(s) to the transaction, so that *one* checkpoint will be created for // each and every shards overlapping with `ranges`. @@ -572,7 +569,7 @@ Future createCheckpoint(Reference tr, // Gets checkpoint metadata for `ranges` at the specific version, with the particular format. // Returns a list of [range, checkpoint], where the `checkpoint` has data over `range`. // checkpoint_not_found() error will be returned if the specific checkpoint cannot be found. -ACTOR Future>> getCheckpointMetaData( +Future>> getCheckpointMetaData( Database cx, std::vector ranges, Version version, @@ -581,15 +578,15 @@ ACTOR Future>> getCheckpoint double timeout = 5.0); // Checks with Data Distributor that it is safe to mark all servers in exclusions as failed -ACTOR Future checkSafeExclusions(Database cx, std::vector exclusions); +Future checkSafeExclusions(Database cx, std::vector exclusions); // Measured in bytes, rounded up to the nearest page size. Multiply by fungibility ratio // because writes are more expensive than reads. inline uint64_t getWriteOperationCost(uint64_t bytes) { if (bytes == 0) { - return CLIENT_KNOBS->GLOBAL_TAG_THROTTLING_RW_FUNGIBILITY_RATIO * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE; + return CLIENT_KNOBS->TAG_THROTTLING_RW_FUNGIBILITY_RATIO * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE; } else { - return CLIENT_KNOBS->GLOBAL_TAG_THROTTLING_RW_FUNGIBILITY_RATIO * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE * + return CLIENT_KNOBS->TAG_THROTTLING_RW_FUNGIBILITY_RATIO * CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE * ((bytes - 1) / CLIENT_KNOBS->TAG_THROTTLING_PAGE_SIZE + 1); } } @@ -606,11 +603,11 @@ inline uint64_t getReadOperationCost(uint64_t bytes) { // Create a transaction to set the value of system key \xff/conf/perpetual_storage_wiggle. If enable == true, the value // will be 1. Otherwise, the value will be 0. The caller should take care of the reset of StorageWiggleMetrics if // necessary. Returns the FDB version at which the transaction was committed. -ACTOR Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware = LockAware::False); +Future setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware = LockAware::False); -ACTOR Future>> readStorageWiggleValues(Database cx, - bool primary, - bool use_system_priority); +Future>> readStorageWiggleValues(Database cx, + bool primary, + bool use_system_priority); // Returns the maximum legal size of a key. This size will be determined by the prefix of the passed in key // (system keys have a larger maximum size). This should be used for generic max key size requests. @@ -629,12 +626,12 @@ int64_t getMaxClearKeySize(KeyRef const& key); struct KeyRangeLocationInfo; // Return the aggregated StorageMetrics of range keys to the caller. The locations tell which interface should // serve the request. The final result is within (min-permittedError/2, max + permittedError/2) if valid. -ACTOR Future> waitStorageMetricsWithLocation(Version version, - KeyRange keys, - std::vector locations, - StorageMetrics min, - StorageMetrics max, - StorageMetrics permittedError); +Future> waitStorageMetricsWithLocation(Version version, + KeyRange keys, + std::vector locations, + StorageMetrics min, + StorageMetrics max, + StorageMetrics permittedError); // Return the suggested split points from storage server.The locations tell which interface should // serve the request. `limit` is the current estimated storage metrics of `keys`.The returned points, if present, @@ -646,19 +643,20 @@ ACTOR Future>>> splitStorageMetricsWithLoc StorageMetrics estimated, Optional minSplitBytes); +Future getWorkerInterfaces(Reference clusterRecord); + namespace NativeAPI { -ACTOR Future>> getServerListAndProcessClasses( - Transaction* tr); +Future>> getServerListAndProcessClasses(Transaction* tr); } -ACTOR Future getKeyLocation_internal(Database cx, - Key key, - SpanContext spanContext, - Optional debugID, - UseProvisionalProxies useProvisionalProxies, - Reverse isBackward, - Version version); - -ACTOR Future refreshTransaction(DatabaseContext* self, Transaction* tr); +Future getKeyLocation_internal(Database cx, + Key key, + SpanContext spanContext, + Optional debugID, + UseProvisionalProxies useProvisionalProxies, + Reverse isBackward, + Version version); + +Future refreshTransaction(DatabaseContext* self, Transaction* tr); #include "flow/unactorcompiler.h" #endif diff --git a/fdbclient/include/fdbclient/Notified.h b/fdbclient/include/fdbclient/Notified.h index b055a5ce7d1..edcbd521dde 100644 --- a/fdbclient/include/fdbclient/Notified.h +++ b/fdbclient/include/fdbclient/Notified.h @@ -23,7 +23,7 @@ #pragma once #include "fdbclient/FDBTypes.h" -#include "flow/TDMetric.actor.h" +#include "flow/TDMetric.h" #include "flow/swift_support.h" #include @@ -76,7 +76,7 @@ struct Notified { void operator=(const ValueType& v) { set(v); } - Notified(Notified&& r) noexcept : waiting(std::move(r.waiting)), val(std::move(r.val)) {} + explicit(false) Notified(Notified&& r) noexcept : waiting(std::move(r.waiting)), val(std::move(r.val)) {} void operator=(Notified&& r) noexcept { waiting = std::move(r.waiting); val = std::move(r.val); diff --git a/fdbclient/include/fdbclient/ParallelStream.actor.h b/fdbclient/include/fdbclient/ParallelStream.actor.h deleted file mode 100644 index a4c6ea02e98..00000000000 --- a/fdbclient/include/fdbclient/ParallelStream.actor.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * ParallelStream.actor.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 - -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_PARALLEL_STREAM_ACTOR_G_H) -#define FDBCLIENT_PARALLEL_STREAM_ACTOR_G_H -#include "fdbclient/ParallelStream.actor.g.h" -#elif !defined(FDBCLIENT_PARALLEL_STREAM_ACTOR_H) -#define FDBCLIENT_PARALLEL_STREAM_ACTOR_H - -#include "flow/genericactors.actor.h" -#include "flow/actorcompiler.h" // must be last include - -// ParallelStream is used to fetch data from multiple streams in parallel and then merge them back into a single stream -// in order. -template -class ParallelStream { - Reference semaphore; - struct FragmentConstructorTag { - explicit FragmentConstructorTag() = default; - }; - -public: - // A Fragment is a single stream that will get results to be merged back into the main output stream - class Fragment : public ReferenceCounted { - Reference semaphore; - PromiseStream stream; - BoundedFlowLock::Releaser releaser; - friend class ParallelStream; - - public: - Fragment(Reference semaphore, int64_t permitNumber, FragmentConstructorTag) - : semaphore(semaphore), releaser(semaphore.getPtr(), permitNumber) {} - template - void send(U&& value) { - stream.send(std::forward(value)); - } - void sendError(Error e) { stream.sendError(e); } - void finish() { - releaser.release(); // Release before destruction to free up pending fragments - stream.sendError(end_of_stream()); - } - Future onEmpty() { return stream.onEmpty(); } - }; - -private: - PromiseStream> fragments; - size_t fragmentsProcessed{ 0 }; - PromiseStream results; - Future flusher; - -public: - // A background actor which take results from the oldest fragment and sends them to the main output stream - ACTOR static Future flushToClient(ParallelStream* self) { - state const int messagesBetweenYields = 1000; - state int messagesSinceYield = 0; - try { - loop { - state Reference fragment = waitNext(self->fragments.getFuture()); - loop { - try { - wait(self->results.onEmpty()); - T value = waitNext(fragment->stream.getFuture()); - self->results.send(value); - if (++messagesSinceYield == messagesBetweenYields) { - wait(yield()); - messagesSinceYield = 0; - } - } catch (Error& e) { - if (e.code() == error_code_end_of_stream) { - fragment.clear(); - break; - } else { - throw e; - } - } - } - } - } catch (Error& e) { - if (e.code() == error_code_actor_cancelled) { - throw; - } - self->results.sendError(e); - return Void(); - } - } - - ParallelStream(PromiseStream results, size_t bufferLimit) : results(results) { - semaphore = makeReference(1, bufferLimit); - flusher = flushToClient(this); - } - - // Creates a fragment to get merged into the main output stream - ACTOR static Future createFragmentImpl(ParallelStream* self) { - int64_t permitNumber = wait(self->semaphore->take()); - auto fragment = makeReference(self->semaphore, permitNumber, FragmentConstructorTag()); - self->fragments.send(fragment); - return fragment.getPtr(); - } - - Future createFragment() { return createFragmentImpl(this); } - - Future finish() { - fragments.sendError(end_of_stream()); - return flusher; - } -}; - -#include "flow/unactorcompiler.h" - -#endif diff --git a/fdbclient/include/fdbclient/RandomKeyValueUtils.h b/fdbclient/include/fdbclient/RandomKeyValueUtils.h index 5a0ed3097e5..8b75b145c64 100644 --- a/fdbclient/include/fdbclient/RandomKeyValueUtils.h +++ b/fdbclient/include/fdbclient/RandomKeyValueUtils.h @@ -68,7 +68,7 @@ struct RandomIntGenerator : IGenerator { } } - RandomIntGenerator(unsigned int only = 0) : min(only), max(only) {} + explicit RandomIntGenerator(unsigned int only = 0) : min(only), max(only) {} RandomIntGenerator(unsigned int first, unsigned int last, bool skewTowardFirst = false) : min(first), max(last) { if (first != last && skewTowardFirst) { skew = (first < last) ? SMALL : LARGE; @@ -77,9 +77,9 @@ struct RandomIntGenerator : IGenerator { std::swap(min, max); } } - RandomIntGenerator(const char* cstr) : RandomIntGenerator(std::string(cstr)) {} - RandomIntGenerator(std::string str) : RandomIntGenerator(StringRef(str)) {} - RandomIntGenerator(StringRef str) { + explicit(false) RandomIntGenerator(const char* cstr) : RandomIntGenerator(std::string(cstr)) {} + explicit(false) RandomIntGenerator(std::string str) : RandomIntGenerator(StringRef(str)) {} + explicit(false) RandomIntGenerator(StringRef str) { bool skewTowardFirst = false; if (!str.empty() && str[0] == '^') { skewTowardFirst = true; @@ -143,9 +143,9 @@ struct RandomIntGenerator : IGenerator { struct RandomStringGenerator : IKeyGenerator { RandomStringGenerator() {} RandomStringGenerator(RandomIntGenerator size, RandomIntGenerator byteset) : size(size), bytes(byteset) {} - RandomStringGenerator(const char* cstr) : RandomStringGenerator(std::string(cstr)) {} - RandomStringGenerator(std::string str) : RandomStringGenerator(StringRef(str)) {} - RandomStringGenerator(StringRef str) { + explicit(false) RandomStringGenerator(const char* cstr) : RandomStringGenerator(std::string(cstr)) {} + explicit(false) RandomStringGenerator(std::string str) : RandomStringGenerator(StringRef(str)) {} + explicit(false) RandomStringGenerator(StringRef str) { StringRef sSize = str.eat("/"); StringRef sBytes = str; if (sBytes.size() == 0) { @@ -179,7 +179,7 @@ struct RandomStringGenerator : IKeyGenerator { // It uses a large pre-generated string and generates random substrings from it. struct RandomValueGenerator { template - RandomValueGenerator(Args&&... args) : strings(std::forward(args)...) { + explicit RandomValueGenerator(Args&&... args) : strings(std::forward(args)...) { // Make a similar RandomStringGenerator to generate the noise block from noise = RandomStringGenerator(RandomIntGenerator(std::max(2e6, strings.size.max)), strings.bytes).next(); } @@ -282,11 +282,11 @@ struct RandomStringSetGenerator : public RandomStringSetGeneratorBase { : indexGen(indexGen), stringGen(stringGen) { init(indexGen, stringGen); } - RandomStringSetGenerator(const char* cstr) : RandomStringSetGenerator(std::string(cstr)) {} - RandomStringSetGenerator(std::string str) : RandomStringSetGenerator(StringRef(str)) {} - RandomStringSetGenerator(StringRef str) { + explicit(false) RandomStringSetGenerator(const char* cstr) : RandomStringSetGenerator(std::string(cstr)) {} + explicit(false) RandomStringSetGenerator(std::string str) : RandomStringSetGenerator(StringRef(str)) {} + explicit(false) RandomStringSetGenerator(StringRef str) { indexGen = str.eat("::"); - stringGen = str; + stringGen = StringGenT(str); init(indexGen, stringGen); } @@ -302,9 +302,9 @@ typedef RandomStringSetGenerator RandomKeySetGenerator; // String Definition Format: RandomKeySet[,RandomKeySet]... struct RandomKeyTupleGenerator : public IKeyGenerator { RandomKeyTupleGenerator() {}; - RandomKeyTupleGenerator(std::vector tupleParts) : tuples(tupleParts) {} - RandomKeyTupleGenerator(std::string s) : RandomKeyTupleGenerator(StringRef(s)) {} - RandomKeyTupleGenerator(StringRef s) { + explicit RandomKeyTupleGenerator(std::vector tupleParts) : tuples(tupleParts) {} + explicit RandomKeyTupleGenerator(std::string s) : RandomKeyTupleGenerator(StringRef(s)) {} + explicit RandomKeyTupleGenerator(StringRef s) { while (!s.empty()) { tuples.push_back(s.eat(",")); } diff --git a/fdbclient/include/fdbclient/ReadYourWrites.h b/fdbclient/include/fdbclient/ReadYourWrites.h index e17a7d2aa82..ea55ce94cce 100644 --- a/fdbclient/include/fdbclient/ReadYourWrites.h +++ b/fdbclient/include/fdbclient/ReadYourWrites.h @@ -147,7 +147,7 @@ class ReadYourWritesTransaction final : NonCopyable, // These are to permit use as state variables in actors: ReadYourWritesTransaction() : cache(&arena), writes(&arena) {} void operator=(ReadYourWritesTransaction&& r) noexcept; - ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept; + explicit(false) ReadYourWritesTransaction(ReadYourWritesTransaction&& r) noexcept; void cancel(); void reset(); @@ -205,7 +205,8 @@ class ReadYourWritesTransaction final : NonCopyable, template using FutureT = Future; - virtual void debugTrace(BaseTraceEvent&& event); + // not virtual because final class + void debugTrace(BaseTraceEvent&& event); void debugPrint(std::string const& message); // Used by ThreadSafeTransaction for exceptions thrown in void methods. @@ -267,4 +268,6 @@ class ReadYourWritesTransaction final : NonCopyable, ReadYourWritesTransactionOptions options; }; +Future> getJSON(Database db, std::string jsonField = ""); + #endif diff --git a/fdbclient/include/fdbclient/RunRYWTransaction.actor.h b/fdbclient/include/fdbclient/RunRYWTransaction.actor.h deleted file mode 100644 index bd05b8e5248..00000000000 --- a/fdbclient/include/fdbclient/RunRYWTransaction.actor.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * RunRYWTransaction.actor.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 - -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_RUNRYWTRANSACTION_ACTOR_G_H) -#define FDBCLIENT_RUNRYWTRANSACTION_ACTOR_G_H -#include "fdbclient/RunRYWTransaction.actor.g.h" -#elif !defined(FDBCLIENT_RUNRYWTRANSACTION_ACTOR_H) -#define FDBCLIENT_RUNRYWTRANSACTION_ACTOR_H - -#include - -#include "flow/flow.h" -#include "fdbclient/RunTransaction.actor.h" -#include "fdbclient/ReadYourWrites.h" -#include "flow/actorcompiler.h" // This must be the last #include. - -// Runs a RYW transaction in a retry loop on the given Database. -// -// Takes a function func that accepts a Reference as a parameter and returns a non-Void -// Future. This function is run inside the transaction, and when the transaction is successfully committed the result of -// the function is returned. -// -// The supplied function should be idempotent. Otherwise, outcome of this function will depend on how many times the -// transaction is retried. -ACTOR template -Future()(Reference()).getValue())> runRYWTransaction( - Database cx, - Function func) { - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { - try { - state decltype(std::declval()(Reference()).getValue()) result = - wait(func(tr)); - wait(tr->commit()); - return result; - } catch (Error& e) { - wait(tr->onError(e)); - } - } -} - -// Debug version of runRYWTransaction. It logs the function name and the committed version of the transaction. -// Note the function name is required, e.g., taskFunc->getName() for TaskFuncBase. -ACTOR template -Future()(Reference()).getValue())> -runRYWTransactionDebug(Database cx, StringRef name, Function func) { - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { - try { - // func should be idempodent; otherwise, retry will get undefined result - state decltype(std::declval()(Reference()).getValue()) result = - wait(func(tr)); - wait(tr->commit()); - TraceEvent("DebugRunRYWTransaction") - .detail("Function", name) - .detail("CommitVersion", tr->getCommittedVersion()); - return result; - } catch (Error& e) { - wait(tr->onError(e)); - } - } -} - -// Runs a RYW transaction in a retry loop on the given Database. -// -// Takes a function func that accepts a Reference as a parameter and returns a Void -// Future. This function is run inside the transaction. -// -// The supplied function should be idempotent. Otherwise, outcome of this function will depend on how many times the -// transaction is retried. -ACTOR template -Future runRYWTransactionVoid(Database cx, Function func) { - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { - try { - wait(func(tr)); - wait(tr->commit()); - return Void(); - } catch (Error& e) { - wait(tr->onError(e)); - } - } -} - -ACTOR template -Future()(Reference()).getValue())> -runRYWTransactionFailIfLocked(Database cx, Function func) { - state Reference tr(new ReadYourWritesTransaction(cx)); - loop { - try { - state decltype(std::declval()(Reference()).getValue()) result = - wait(func(tr)); - wait(tr->commit()); - return result; - } catch (Error& e) { - if (e.code() == error_code_database_locked) - throw; - wait(tr->onError(e)); - } - } -} - -ACTOR template -Future()(Reference()).getValue())> runRYWTransactionNoRetry( - Database cx, - Function func) { - state Reference tr(new ReadYourWritesTransaction(cx)); - state decltype(std::declval()(Reference()).getValue()) result = wait(func(tr)); - wait(tr->commit()); - return result; -} - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/RunRYWTransaction.h b/fdbclient/include/fdbclient/RunRYWTransaction.h new file mode 100644 index 00000000000..9b9bca3a78a --- /dev/null +++ b/fdbclient/include/fdbclient/RunRYWTransaction.h @@ -0,0 +1,137 @@ +/* + * RunRYWTransaction.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 + +#include "flow/flow.h" +#include "fdbclient/RunTransaction.h" +#include "fdbclient/ReadYourWrites.h" + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif + +template +using RunRYWTransactionResult = decltype(std::declval()(Reference()).getValue()); + +// Runs a RYW transaction in a retry loop on the given Database. +// +// Takes a function func that accepts a Reference as a parameter and returns a non-Void +// Future. This function is run inside the transaction, and when the transaction is successfully committed the result of +// the function is returned. +// +// The supplied function should be idempotent. Otherwise, outcome of this function will depend on how many times the +// transaction is retried. +template +Future> runRYWTransaction(Database cx, Function func, ExplicitVoid = {}) { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; + try { + auto const result = co_await func(tr); + co_await tr->commit(); + co_return result; + } catch (Error& e) { + err = e; + } + co_await tr->onError(err); + } +} + +// Debug version of runRYWTransaction. It logs the function name and the committed version of the transaction. +// Note the function name is required, e.g., taskFunc->getName() for TaskFuncBase. +template +Future> runRYWTransactionDebug(Database cx, + StringRef name, + Function func, + ExplicitVoid = {}) { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; + try { + // func should be idempodent; otherwise, retry will get undefined result + auto const result = co_await func(tr); + co_await tr->commit(); + TraceEvent("DebugRunRYWTransaction") + .detail("Function", name) + .detail("CommitVersion", tr->getCommittedVersion()); + co_return result; + } catch (Error& e) { + err = e; + } + co_await tr->onError(err); + } +} + +// Runs a RYW transaction in a retry loop on the given Database. +// +// Takes a function func that accepts a Reference as a parameter and returns a Void +// Future. This function is run inside the transaction. +// +// The supplied function should be idempotent. Otherwise, outcome of this function will depend on how many times the +// transaction is retried. +template +Future runRYWTransactionVoid(Database cx, Function func) { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; + try { + co_await func(tr); + co_await tr->commit(); + co_return; + } catch (Error& e) { + err = e; + } + co_await tr->onError(err); + } +} + +template +Future> runRYWTransactionFailIfLocked(Database cx, Function func, ExplicitVoid = {}) { + Reference tr(new ReadYourWritesTransaction(cx)); + while (true) { + Error err; + try { + auto const result = co_await func(tr); + co_await tr->commit(); + co_return result; + } catch (Error& e) { + err = e; + } + if (err.code() == error_code_database_locked) + throw err; + co_await tr->onError(err); + } +} + +template +Future> runRYWTransactionNoRetry(Database cx, Function func, ExplicitVoid = {}) { + Reference tr(new ReadYourWritesTransaction(cx)); + auto const result = co_await func(tr); + co_await tr->commit(); + co_return result; +} + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif diff --git a/fdbclient/include/fdbclient/RunTransaction.actor.h b/fdbclient/include/fdbclient/RunTransaction.h similarity index 69% rename from fdbclient/include/fdbclient/RunTransaction.actor.h rename to fdbclient/include/fdbclient/RunTransaction.h index d4d81cc9164..a9b99fe7897 100644 --- a/fdbclient/include/fdbclient/RunTransaction.actor.h +++ b/fdbclient/include/fdbclient/RunTransaction.h @@ -1,5 +1,5 @@ /* - * RunTransaction.actor.h + * RunTransaction.h * * This source file is part of the FoundationDB open source project * @@ -20,19 +20,15 @@ #pragma once -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_RUNTRANSACTION_ACTOR_G_H) -#define FDBCLIENT_RUNTRANSACTION_ACTOR_G_H -#include "fdbclient/RunTransaction.actor.g.h" -#elif !defined(FDBCLIENT_RUNTRANSACTION_ACTOR_H) -#define FDBCLIENT_RUNTRANSACTION_ACTOR_H - #include #include "flow/flow.h" #include "fdbclient/FDBOptions.g.h" -#include "flow/actorcompiler.h" // This must be the last #include. + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif template struct transaction_option_setter : std::false_type {}; @@ -43,43 +39,47 @@ struct transaction_option_setter> : transaction_option_setter {} template constexpr bool can_set_transaction_options = transaction_option_setter::value; -ACTOR template -Future()(Reference()).getValue())> runTransaction( - Reference db, - Function func) { - state Reference tr = db->createTransaction(); - loop { +template +using RunTransactionResult = decltype(std::declval()(Reference()).getValue()); + +template +Future> runTransaction(Reference db, Function func, ExplicitVoid = {}) { + Reference tr = db->createTransaction(); + while (true) { if constexpr (can_set_transaction_options) { db->setOptions(tr); } + Error err; try { // func should be idempotent; otherwise, retry will get undefined result - state decltype(std::declval()(Reference()).getValue()) result = - wait(func(tr)); - wait(safeThreadFutureToFuture(tr->commit())); - return result; + auto const result = co_await func(tr); + co_await safeThreadFutureToFuture(tr->commit()); + co_return result; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR template +template Future runTransactionVoid(Reference db, Function func) { - state Reference tr = db->createTransaction(); - loop { + Reference tr = db->createTransaction(); + while (true) { if constexpr (can_set_transaction_options) { db->setOptions(tr); } + Error err; try { // func should be idempotent; otherwise, retry will get undefined result - wait(func(tr)); - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await func(tr); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -129,8 +129,12 @@ auto SystemDB(Reference db, bool write = false, bool lockAware = false, bool // SystemDB with all options true template auto SystemDBWriteLockedNow(Reference db) { - return makeReference>(db, true, true, true); + constexpr bool write = true; + constexpr bool lockAware = true; + constexpr bool immediate = true; + return makeReference>(db, write, lockAware, immediate); } -#include "flow/unactorcompiler.h" +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop #endif diff --git a/fdbclient/include/fdbclient/S3BlobStore.h b/fdbclient/include/fdbclient/S3BlobStore.h index 3faa0c18967..abe52746202 100644 --- a/fdbclient/include/fdbclient/S3BlobStore.h +++ b/fdbclient/include/fdbclient/S3BlobStore.h @@ -21,119 +21,15 @@ #pragma once #include -#include #include -#include -#include "flow/IRandom.h" -#include "flow/flow.h" #include "flow/Net2Packet.h" -#include "fdbclient/Knobs.h" -#include "flow/IRateControl.h" +#include "fdbclient/IBlobStore.h" #include "fdbrpc/HTTP.h" -#include "fdbrpc/Stats.h" -#include "fdbclient/JSONDoc.h" -#include "flow/IConnection.h" -#include - -// unique key that identifies interchangeable connections for the same settings and destination -// FIXME: can we define std::hash as a struct member of a S3BlobStoreEndpoint? -struct BlobStoreConnectionPoolKey { - std::string host; - std::string service; - std::string region; - bool isTLS; - - BlobStoreConnectionPoolKey(const std::string& host, - const std::string& service, - const std::string& region, - bool isTLS) - : host(host), service(service), region(region), isTLS(isTLS) {} - - bool operator==(const BlobStoreConnectionPoolKey& other) const { - return isTLS == other.isTLS && host == other.host && service == other.service && region == other.region; - } -}; - -namespace std { -template <> -struct hash { - std::size_t operator()(const BlobStoreConnectionPoolKey& key) const { - std::size_t seed = 0; - boost::hash_combine(seed, std::hash{}(key.host)); - boost::hash_combine(seed, std::hash{}(key.service)); - boost::hash_combine(seed, std::hash{}(key.region)); - boost::hash_combine(seed, std::hash{}(key.isTLS)); - return seed; - } -}; -} // namespace std - -// Representation of all the things you need to connect to a blob store instance with some credentials. -// Reference counted because a very large number of them could be needed. -class S3BlobStoreEndpoint : public ReferenceCounted { +class S3BlobStoreEndpoint : public IBlobStoreEndpoint, ReferenceCounted { public: - struct Stats { - Stats() : requests_successful(0), requests_failed(0), bytes_sent(0) {} - Stats operator-(const Stats& rhs); - void clear() { memset(this, 0, sizeof(*this)); } - json_spirit::mObject getJSON(); - - int64_t requests_successful; - int64_t requests_failed; - int64_t bytes_sent; - }; - - static Stats s_stats; - - struct BlobStats { - UID id; - CounterCollection cc; - Counter requestsSuccessful; - Counter requestsFailed; - Counter newConnections; - Counter expiredConnections; - Counter reusedConnections; - Counter fastRetries; - - LatencySample requestLatency; - - // init not in static codepath, to avoid initialization race issues and so no blob connections means no - // unnecessary blob stats traces - BlobStats() - : id(deterministicRandom()->randomUniqueID()), cc("BlobStoreStats", id.toString()), - requestsSuccessful("RequestsSuccessful", cc), requestsFailed("RequestsFailed", cc), - newConnections("NewConnections", cc), expiredConnections("ExpiredConnections", cc), - reusedConnections("ReusedConnections", cc), fastRetries("FastRetries", cc), - requestLatency("BlobStoreRequestLatency", - id, - CLIENT_KNOBS->BLOBSTORE_LATENCY_LOGGING_INTERVAL, - CLIENT_KNOBS->BLOBSTORE_LATENCY_LOGGING_ACCURACY) {} - }; - // null when initialized, so no blob stats until a blob connection is used - std::unique_ptr blobStats; - Future statsLogger; - - void maybeStartStatsLogger() { - if (!blobStats && CLIENT_KNOBS->BLOBSTORE_ENABLE_LOGGING) { - blobStats = std::make_unique(); - specialCounter(blobStats->cc, "GlobalConnectionPoolCount", [this]() { - return this->getGlobalConnectionPool().size(); - }); - specialCounter(blobStats->cc, "GlobalConnectionPoolSize", [this]() { - // FIXME: could track this explicitly via an int variable with extra logic, but this should be small and - // infrequent - int totalConnections = 0; - for (auto& it : this->getGlobalConnectionPool()) { - totalConnections += it.second->pool.size(); - } - return totalConnections; - }); - - statsLogger = blobStats->cc.traceCounters( - "BlobStoreMetrics", blobStats->id, CLIENT_KNOBS->BLOBSTORE_STATS_LOGGING_INTERVAL, "BlobStoreMetrics"); - } - } + void addref() override { ReferenceCounted::addref(); } + void delref() override { ReferenceCounted::delref(); } struct Credentials { std::string key; @@ -141,235 +37,50 @@ class S3BlobStoreEndpoint : public ReferenceCounted { std::string securityToken; }; - struct BlobKnobs { - BlobKnobs(); - int secure_connection, connect_tries, connect_timeout, max_connection_life, request_tries, request_timeout_min, - requests_per_second, list_requests_per_second, write_requests_per_second, read_requests_per_second, - delete_requests_per_second, multipart_max_part_size, multipart_min_part_size, concurrent_requests, - concurrent_uploads, concurrent_lists, concurrent_reads_per_file, concurrent_writes_per_file, - enable_read_cache, read_block_size, read_ahead_blocks, read_cache_blocks_per_file, - max_send_bytes_per_second, max_recv_bytes_per_second, sdk_auth, enable_object_integrity_check, - global_connection_pool, max_delay_retryable_error, max_delay_connection_failed, multipart_retry_delay_ms; - - bool set(StringRef name, int value); - std::string getURLParameters() const; - static std::vector getKnobDescriptions() { - return { - "secure_connection (or sc) Set 1 for secure connection and 0 for insecure connection.", - "connect_tries (or ct) Number of times to try to connect for each request.", - "connect_timeout (or cto) Number of seconds to wait for a connect request to succeed.", - "max_connection_life (or mcl) Maximum number of seconds to use a single TCP connection.", - "request_tries (or rt) Number of times to try each request until a parsable HTTP " - "response other than 429 is received.", - "request_timeout_min (or rtom) Number of seconds to wait for a request to succeed after a " - "connection is established.", - "requests_per_second (or rps) Max number of requests to start per second.", - "list_requests_per_second (or lrps) Max number of list requests to start per second.", - "write_requests_per_second (or wrps) Max number of write requests to start per second.", - "read_requests_per_second (or rrps) Max number of read requests to start per second.", - "delete_requests_per_second (or drps) Max number of delete requests to start per second.", - "multipart_max_part_size (or maxps) Max part size for multipart uploads.", - "multipart_min_part_size (or minps) Min part size for multipart uploads.", - "multipart_retry_delay_ms (or mrd) Delay in milliseconds between retries for multipart uploads.", - "concurrent_requests (or cr) Max number of total requests in progress at once, regardless of " - "operation-specific concurrency limits.", - "concurrent_uploads (or cu) Max concurrent uploads (part or whole) that can be in progress " - "at once.", - "concurrent_lists (or cl) Max concurrent list operations that can be in progress at once.", - "concurrent_reads_per_file (or crps) Max concurrent reads in progress for any one file.", - "concurrent_writes_per_file (or cwps) Max concurrent uploads in progress for any one file.", - "enable_read_cache (or erc) Whether read block caching is enabled.", - "read_block_size (or rbs) Block size in bytes to be used for reads.", - "read_ahead_blocks (or rab) Number of blocks to read ahead of requested offset.", - "read_cache_blocks_per_file (or rcb) Size of the read cache for a file in blocks.", - "max_send_bytes_per_second (or sbps) Max send bytes per second for all requests combined.", - "max_recv_bytes_per_second (or rbps) Max receive bytes per second for all requests combined (NOT YET " - "USED).", - "max_delay_retryable_error (or dre) Max seconds to delay before retry when see a retryable error.", - "max_delay_connection_failed (or dcf) Max seconds to delay before retry when see a connection " - "failure.", - "sdk_auth (or sa) Use AWS SDK to resolve credentials. Only valid if " - "BUILD_AWS_BACKUP is enabled.", - "enable_object_integrity_check (or eoic) Enable integrity check on GET requests (Default: false).", - "global_connection_pool (or gcp) Enable shared connection pool between all blobstore instances." - }; - } - - bool isTLS() const { return secure_connection == 1; } - }; - - struct ReusableConnection { - Reference conn; - double expirationTime; - // CROSS_PROCESS_FIX: Track which process created this connection - NetworkAddress creatingProcess; - ReusableConnection() : expirationTime(0) { - if (g_network && g_network->isSimulated()) { - creatingProcess = g_network->getLocalAddress(); - } - } - ReusableConnection(Reference c, double exp) : conn(c), expirationTime(exp) { - if (g_network && g_network->isSimulated()) { - creatingProcess = g_network->getLocalAddress(); - } - } - - // CROSS_PROCESS_FIX: Copy constructor with cross-process detection - ReusableConnection(const ReusableConnection& other) - : conn(other.conn), expirationTime(other.expirationTime), creatingProcess(other.creatingProcess) { - if (g_network && g_network->isSimulated() && creatingProcess.isValid() && - creatingProcess != g_network->getLocalAddress()) { - // Cross-process copy detected - invalidate the connection to prevent sharing - conn = Reference(); - expirationTime = 0; // Mark as expired - } - } - - // CROSS_PROCESS_FIX: Assignment operator with cross-process detection - ReusableConnection& operator=(const ReusableConnection& other) { - if (this != &other) { - conn = other.conn; - expirationTime = other.expirationTime; - creatingProcess = other.creatingProcess; - if (g_network && g_network->isSimulated() && creatingProcess.isValid() && - creatingProcess != g_network->getLocalAddress()) { - // Cross-process assignment detected - invalidate the connection to prevent sharing - conn = Reference(); - expirationTime = 0; // Mark as expired - } - } - return *this; - } - }; - - // basically, reference counted queue with option to add other fields - struct ConnectionPoolData : NonCopyable, ReferenceCounted { - std::queue pool; - - ConnectionPoolData() {} - - // Destructor implementation in S3BlobStore.actor.cpp - // In simulation, explicitly closes all pooled connections before destruction - ~ConnectionPoolData(); - }; - - // global connection pool for multiple blobstore endpoints with same connection settings and request destination - // NOTE: This is disabled by default (BLOBSTORE_GLOBAL_CONNECTION_POOL=false), so each endpoint gets its own pool - static std::unordered_map>& getGlobalConnectionPool() { - // Use process address as key to separate connection pools per simulated process - static std::map>> - processConnectionPools; - - NetworkAddress currentProcess; - if (g_network && g_network->isSimulated()) { - currentProcess = g_network->getLocalAddress(); - } - return processConnectionPools[currentProcess]; - } - S3BlobStoreEndpoint(std::string const& host, std::string const& service, std::string region, Optional const& proxyHost, Optional const& proxyPort, - Optional const& creds, + Optional const& creds, BlobKnobs const& knobs = BlobKnobs(), - HTTP::Headers extraHeaders = HTTP::Headers()) - : host(host), service(service), region(region), proxyHost(proxyHost), proxyPort(proxyPort), - useProxy(proxyHost.present() && proxyPort.present()), credentials(creds), - lookupKey(creds.present() && creds.get().key.empty()), - lookupSecret(creds.present() && creds.get().secret.empty()), knobs(knobs), extraHeaders(extraHeaders), - requestRate(new SpeedLimit(knobs.requests_per_second, 1)), - requestRateList(new SpeedLimit(knobs.list_requests_per_second, 1)), - requestRateWrite(new SpeedLimit(knobs.write_requests_per_second, 1)), - requestRateRead(new SpeedLimit(knobs.read_requests_per_second, 1)), - requestRateDelete(new SpeedLimit(knobs.delete_requests_per_second, 1)), - sendRate(new SpeedLimit(knobs.max_send_bytes_per_second, 1)), - recvRate(new SpeedLimit(knobs.max_recv_bytes_per_second, 1)), concurrentRequests(knobs.concurrent_requests), - concurrentUploads(knobs.concurrent_uploads), concurrentLists(knobs.concurrent_lists) { - - if (host.empty() || (proxyHost.present() != proxyPort.present())) - throw connection_string_invalid(); + HTTP::Headers extraHeaders = HTTP::Headers()); - // set connection pool instance - if (useProxy || !knobs.global_connection_pool) { - // don't use global connection pool if there's a proxy, as it complicates the logic - // FIXME: handle proxies? - connectionPool = makeReference(); - } else { - BlobStoreConnectionPoolKey key(host, service, region, knobs.isTLS()); - auto it = getGlobalConnectionPool().find(key); - if (it != getGlobalConnectionPool().end()) { - connectionPool = it->second; - } else { - connectionPool = makeReference(); - getGlobalConnectionPool().insert({ key, connectionPool }); - } - } - ASSERT(connectionPool.isValid()); - - maybeStartStatsLogger(); - } - - static std::string getURLFormat(bool withResource = false) { - const char* resource = ""; - if (withResource) - resource = ""; - return format("blobstore://::@[:]/" - "%s[?=[&=]...]", - resource); - } + // Infer the cloud region from a hostname by matching known service prefixes + // (e.g. "s3.us-west-2.amazonaws.com" -> "us-west-2", "cos.ap-beijing.myqcloud.com" -> "ap-beijing"). + // Returns "" if no pattern matches. + static std::string guessRegionFromDomain(std::string domain); - typedef std::map ParametersT; - - // Parse url and return a S3BlobStoreEndpoint - // If the url has parameters that S3BlobStoreEndpoint can't consume then an error will be thrown unless - // ignored_parameters is given in which case the unconsumed parameters will be added to it. + // Convenience: parse URL and return as S3BlobStoreEndpoint (downcasts from IBlobStoreEndpoint::fromString). + // Callers that only need the IBlobStoreEndpoint interface should use IBlobStoreEndpoint::fromString directly. static Reference fromString(const std::string& url, const Optional& proxy, std::string* resourceFromURL, std::string* error, - ParametersT* ignored_parameters); - - // Get a normalized version of this URL with the given resource and any non-default BlobKnob values as URL - // parameters in addition to the passed params string - std::string getResourceURL(std::string resource, std::string params) const; - - // Construct a resource path for S3 operations - std::string constructResourcePath(const std::string& bucket, const std::string& object) const; - - // FIXME: add periodic connection reaper to pool - // local connection pool for this blobstore - Reference connectionPool; - Future connect(bool* reusingConn); - void returnConnection(ReusableConnection& conn); + ParametersT* ignored_parameters) { + Reference base = + IBlobStoreEndpoint::fromString(url, proxy, resourceFromURL, error, ignored_parameters); + auto* endpoint = dynamic_cast(base.getPtr()); + if (base && !endpoint) { + if (error) { + *error = "Parsed blob store endpoint is not an S3BlobStoreEndpoint"; + } + return Reference(); + } + return Reference::addRef(endpoint); + } - std::string host; - std::string service; - std::string region; - Optional proxyHost; - Optional proxyPort; - bool useProxy; + Future updateSecret() override; + bool extractCredentialFields(JSONDoc& account) override; + std::string credentialFileKey() const override; + bool lookupSecretOnEachRequest() override; + void setRequestHeaders(std::string const& verb, std::string const& resource, HTTP::Headers& headers) override; + std::string normalizeResourceForRequest(std::string const& resource) override; Optional credentials; bool lookupKey; bool lookupSecret; - BlobKnobs knobs; - HTTP::Headers extraHeaders; - - // Speed and concurrency limits - Reference requestRate; - Reference requestRateList; - Reference requestRateWrite; - Reference requestRateRead; - Reference requestRateDelete; - Reference sendRate; - Reference recvRate; - FlowLock concurrentRequests; - FlowLock concurrentUploads; - FlowLock concurrentLists; - - Future updateSecret(); + bool simulatedTokenError = false; // Set by simulateRequestFailure for BUGGIFY testing // Calculates the authentication string from the secret key static std::string hmac_sha1(Credentials const& creds, std::string const& msg); @@ -384,95 +95,73 @@ class S3BlobStoreEndpoint : public ReferenceCounted { std::string date = "", std::string datestamp = ""); - std::string getHost() const { return host; } - - std::string getRegion() const { return region; } - - // Do an HTTP request to the Blob Store, read the response. Handles authentication. - // Every blob store interaction should ultimately go through this function + // doRequest hooks for S3 token error recovery + void simulateRequestFailure(std::string const& verb, + std::string const& resource, + Reference& r) override; + void processRequestFailure(Reference const& r, + TraceEvent& event, + bool& retryExtended) override; + Future preRetryCheck(std::string const& verb, + std::string const& resource, + ReusableConnection& rconn, + int requestTimeout, + bool& retryExtended) override; - Future> doRequest(std::string const& verb, - std::string const& resource, - const HTTP::Headers& headers, - UnsentPacketQueue* pContent, - int contentLen, - std::set successCodes); - - struct ObjectInfo { - std::string name; - int64_t size; - }; + // Get a normalized version of this URL with the given resource and any non-default BlobKnob values as URL + // parameters in addition to the passed params string + std::string getResourceURL(std::string resource, std::string params) const override; - struct ListResult { - std::vector commonPrefixes; - std::vector objects; - }; + // Construct a resource path for S3 operations + std::string constructResourcePath(const std::string& bucket, const std::string& object) const; - // Get bucket contents via a stream, since listing large buckets will take many serial blob requests + // Get bucket contents via a stream, since listing large buckets will take many serial blob requests. // If a delimiter is passed then common prefixes will be read in parallel, recursively, depending on recurseFilter. - // Recursefilter is a must be a function that takes a string and returns true if it passes. The default behavior is + // recurseFilter must be a function that takes a string and returns true if it passes. The default behavior is // to assume true. Future listObjectsStream(std::string const& bucket, PromiseStream results, Optional prefix = {}, Optional delimiter = {}, int maxDepth = 0, - std::function recurseFilter = nullptr); - - // Get a list of the files in a bucket, see listObjectsStream for more argument detail. - Future listObjects(std::string const& bucket, - Optional prefix = {}, - Optional delimiter = {}, - int maxDepth = 0, - std::function recurseFilter = nullptr); + std::function recurseFilter = nullptr) override; // Get a list of all buckets - Future> listBuckets(); + AsyncResult> listBuckets() override; // Check if a bucket exists - Future bucketExists(std::string const& bucket); + Future bucketExists(std::string const& bucket) override; // Check if an object exists in a bucket - Future objectExists(std::string const& bucket, std::string const& object); + Future objectExists(std::string const& bucket, std::string const& object) override; // Get the size of an object in a bucket - Future objectSize(std::string const& bucket, std::string const& object); + Future objectSize(std::string const& bucket, std::string const& object) override; // Read an arbitrary segment of an object Future readObject(std::string const& bucket, std::string const& object, void* data, int length, - int64_t offset); + int64_t offset) override; // Delete an object in a bucket - Future deleteObject(std::string const& bucket, std::string const& object); - - // Delete all objects in a bucket under a prefix. Note this is not atomic as blob store does not - // support this operation directly. This method is just a convenience method that lists and deletes - // all of the objects in the bucket under the given prefix. - // Since it can take a while, if a pNumDeleted and/or pBytesDeleted are provided they will be incremented every time - // a deletion of an object completes. - Future deleteRecursively(std::string const& bucket, - std::string prefix = "", - int* pNumDeleted = nullptr, - int64_t* pBytesDeleted = nullptr); + Future deleteObject(std::string const& bucket, std::string const& object) override; - // Create a bucket if it does not already exists. - Future createBucket(std::string const& bucket); + // Create a bucket if it does not already exist. + Future createBucket(std::string const& bucket) override; // Useful methods for working with tiny files - Future readEntireFile(std::string const& bucket, std::string const& object); - Future writeEntireFile(std::string const& bucket, std::string const& object, std::string const& content); + AsyncResult readEntireFile(std::string const& bucket, std::string const& object) override; Future writeEntireFileFromBuffer(std::string const& bucket, std::string const& object, UnsentPacketQueue* pContent, int contentLen, - std::string const& contentHash); + std::string const& contentHash) override; // MultiPart upload methods // Returns UploadID - Future beginMultiPartUpload(std::string const& bucket, std::string const& object); + Future beginMultiPartUpload(std::string const& bucket, std::string const& object) override; // Returns eTag Future uploadPart(std::string const& bucket, std::string const& object, @@ -480,19 +169,13 @@ class S3BlobStoreEndpoint : public ReferenceCounted { unsigned int partNumber, UnsentPacketQueue* pContent, int contentLen, - std::string const& contentHash); - - struct PartInfo { - std::string etag; - std::string checksum; // MD5 or SHA256 depending on integrity check setting - PartInfo() = default; - PartInfo(std::string e, std::string c = "") : etag(e), checksum(c) {} - }; - typedef std::map MultiPartSetT; + std::string const& contentHash) override; Future> finishMultiPartUpload(std::string const& bucket, std::string const& object, std::string const& uploadID, - MultiPartSetT const& parts); + MultiPartSetT const& parts, + int64_t totalSize = 0) override; + Future abortMultiPartUpload(std::string const& bucket, std::string const& object, std::string const& uploadID); diff --git a/fdbclient/include/fdbclient/S3Client.actor.h b/fdbclient/include/fdbclient/S3Client.h similarity index 81% rename from fdbclient/include/fdbclient/S3Client.actor.h rename to fdbclient/include/fdbclient/S3Client.h index d24a62f24e2..076e1314a0e 100644 --- a/fdbclient/include/fdbclient/S3Client.actor.h +++ b/fdbclient/include/fdbclient/S3Client.h @@ -1,5 +1,5 @@ /* - * S3Client.actor.h + * S3Client.h * * This source file is part of the FoundationDB open source project * @@ -20,17 +20,10 @@ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_S3CLIENT_ACTOR_G_H) -#define FDBCLIENT_S3CLIENT_ACTOR_G_H -#include "fdbclient/S3Client.actor.g.h" -#elif !defined(FDBCLIENT_S3CLIENT_ACTOR_H) -#define FDBCLIENT_S3CLIENT_ACTOR_H - #include #include "fdbclient/S3BlobStore.h" #include "fdbclient/BulkDumping.h" #include "flow/Error.h" -#include "flow/actorcompiler.h" // This must be the last #include. // FDB S3 Client. Includes copying files and directories to and from s3. // Uses the S3BlobStoreEndpoint to interact with s3. The s3url is of the form @@ -81,28 +74,28 @@ const std::string BLOBSTORE_PREFIX = "blobstore://"; // dirpath: Local directory path to copy from // s3url: S3 URL to copy to (must include bucket parameter) // Returns a Future that completes when the operation is done -ACTOR Future copyUpDirectory(std::string dirpath, std::string s3url); +Future copyUpDirectory(std::string dirpath, std::string s3url); // Copy filepath to bucket at resource in s3. // Uses multipart upload for large files. // filepath: Local file path to copy from // s3url: S3 URL to copy to (must include bucket parameter) // Returns a Future that completes when the operation is done -ACTOR Future copyUpFile(std::string filepath, std::string s3url); +Future copyUpFile(std::string filepath, std::string s3url); // Copy the file from s3 down to the local filesystem. // Overwrites existing file. Uses multipart download for large files. // s3url: S3 URL to copy from (must include bucket parameter) // filepath: Local file path to copy to // Returns a Future that completes when the operation is done -ACTOR Future copyDownFile(std::string s3url, std::string filepath); +Future copyDownFile(std::string s3url, std::string filepath); // Copy down the directory content from s3 to the local filesystem. // Recursively copies all files and subdirectories. // s3url: S3 URL to copy from (must include bucket parameter) // dirpath: Local directory path to copy to // Returns a Future that completes when the operation is done -ACTOR Future copyDownDirectory(std::string s3url, std::string dirpath); +Future copyDownDirectory(std::string s3url, std::string dirpath); // Upload the source file set after clearing any existing files at the destination. // Used for bulk operations like backup and restore. @@ -110,27 +103,27 @@ ACTOR Future copyDownDirectory(std::string s3url, std::string dirpath); // sourceFileSet: Source file set to copy // destinationFileSet: Destination file set to create // Returns a Future that completes when the operation is done -ACTOR Future copyUpBulkDumpFileSet(std::string s3url, - BulkLoadFileSet sourceFileSet, - BulkLoadFileSet destinationFileSet); +Future copyUpBulkDumpFileSet(std::string s3url, + BulkLoadFileSet sourceFileSet, + BulkLoadFileSet destinationFileSet); // Delete the file or directory at s3url -- recursively. // s3url: S3 URL to delete (must include bucket parameter) // Returns a Future that completes when the operation is done -ACTOR Future deleteResource(std::string s3url); +Future deleteResource(std::string s3url); // Calculate the xxhash64 checksum of a file. // Used for verifying data integrity during uploads and downloads. // file: File handle to calculate checksum for // size: Size of the file in bytes, or -1 to determine automatically -// Returns a Future that completes with the hex string representation of the checksum -ACTOR Future calculateFileChecksum(Reference file, int64_t size = -1); +// Returns an AsyncResult that completes with the hex string representation of the checksum +AsyncResult calculateFileChecksum(Reference file, int64_t size = -1); // List files and directories at the given S3 URL // s3url: S3 URL to list (must include bucket parameter) // maxDepth: Maximum depth to recurse (default: 1) // Returns a Future that completes when the operation is done -ACTOR Future listFiles(std::string s3url, int maxDepth = 1); +Future listFiles(std::string s3url, int maxDepth = 1); // Get the endpoint for the given s3url. // Populates parameters and resource with parse of s3url. @@ -141,6 +134,3 @@ ACTOR Future listFiles(std::string s3url, int maxDepth = 1); Reference getEndpoint(const std::string& s3url, std::string& resource, S3BlobStoreEndpoint::ParametersT& parameters); - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/ServerKnobCollection.h b/fdbclient/include/fdbclient/ServerKnobCollection.h deleted file mode 100644 index 5177f4b312b..00000000000 --- a/fdbclient/include/fdbclient/ServerKnobCollection.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ServerKnobCollection.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBCLIENT_SERVERKNOBCOLLECTION_H -#define FDBCLIENT_SERVERKNOBCOLLECTION_H - -#pragma once - -#include "fdbclient/ClientKnobCollection.h" -#include "fdbclient/IKnobCollection.h" - -/* - * Stores both flow knobs, client knobs, and server knobs. Attempting to access test knobs - * results in a run-time error - */ -class ServerKnobCollection : public IKnobCollection { - ClientKnobCollection clientKnobCollection; - ServerKnobs serverKnobs; - -public: - ServerKnobCollection(Randomize randomize, IsSimulated isSimulated); - void initialize(Randomize randomize, IsSimulated isSimulated) override; - void reset(Randomize randomize, IsSimulated isSimulated) override; - FlowKnobs const& getFlowKnobs() const override { return clientKnobCollection.getFlowKnobs(); } - ClientKnobs const& getClientKnobs() const override { return clientKnobCollection.getClientKnobs(); } - ServerKnobs const& getServerKnobs() const override { return serverKnobs; } - TestKnobs const& getTestKnobs() const override { throw internal_error(); } - Optional tryParseKnobValue(std::string const& knobName, std::string const& knobValue) const override; - bool trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) override; -}; - -#endif // FDBCLIENT_SERVERKNOBCOLLECTION_H diff --git a/fdbclient/include/fdbclient/SimpleIni.h b/fdbclient/include/fdbclient/SimpleIni.h index b0f75fe7591..9ba1caf1ff7 100644 --- a/fdbclient/include/fdbclient/SimpleIni.h +++ b/fdbclient/include/fdbclient/SimpleIni.h @@ -297,10 +297,11 @@ class CSimpleIniTempl { const SI_CHAR* pComment; int nOrder; - Entry(const SI_CHAR* a_pszItem = NULL, int a_nOrder = 0) : pItem(a_pszItem), pComment(NULL), nOrder(a_nOrder) {} + explicit(false) Entry(const SI_CHAR* a_pszItem = NULL, int a_nOrder = 0) + : pItem(a_pszItem), pComment(NULL), nOrder(a_nOrder) {} Entry(const SI_CHAR* a_pszItem, const SI_CHAR* a_pszComment, int a_nOrder) : pItem(a_pszItem), pComment(a_pszComment), nOrder(a_nOrder) {} - Entry(const Entry& rhs) { operator=(rhs); } + explicit(false) Entry(const Entry& rhs) { operator=(rhs); } Entry& operator=(const Entry& rhs) { pItem = rhs.pItem; pComment = rhs.pComment; @@ -354,7 +355,7 @@ class CSimpleIniTempl { virtual void Write(const char* a_pBuf) = 0; private: - OutputWriter(const OutputWriter&); // disable + explicit(false) OutputWriter(const OutputWriter&); // disable OutputWriter& operator=(const OutputWriter&); // disable }; @@ -363,11 +364,11 @@ class CSimpleIniTempl { FILE* m_file; public: - FileWriter(FILE* a_file) : m_file(a_file) {} + explicit FileWriter(FILE* a_file) : m_file(a_file) {} void Write(const char* a_pBuf) { fputs(a_pBuf, m_file); } private: - FileWriter(const FileWriter&); // disable + explicit(false) FileWriter(const FileWriter&); // disable FileWriter& operator=(const FileWriter&); // disable }; @@ -376,11 +377,11 @@ class CSimpleIniTempl { std::string& m_string; public: - StringWriter(std::string& a_string) : m_string(a_string) {} + explicit StringWriter(std::string& a_string) : m_string(a_string) {} void Write(const char* a_pBuf) { m_string.append(a_pBuf); } private: - StringWriter(const StringWriter&); // disable + explicit(false) StringWriter(const StringWriter&); // disable StringWriter& operator=(const StringWriter&); // disable }; @@ -390,11 +391,11 @@ class CSimpleIniTempl { std::ostream& m_ostream; public: - StreamWriter(std::ostream& a_ostream) : m_ostream(a_ostream) {} + explicit StreamWriter(std::ostream& a_ostream) : m_ostream(a_ostream) {} void Write(const char* a_pBuf) { m_ostream << a_pBuf; } private: - StreamWriter(const StreamWriter&); // disable + explicit(false) StreamWriter(const StreamWriter&); // disable StreamWriter& operator=(const StreamWriter&); // disable }; #endif // SI_SUPPORT_IOSTREAMS @@ -405,8 +406,8 @@ class CSimpleIniTempl { class Converter : private SI_CONVERTER { public: using SI_CONVERTER::SizeToStore; - Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { m_scratch.resize(1024); } - Converter(const Converter& rhs) { operator=(rhs); } + explicit Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { m_scratch.resize(1024); } + explicit(false) Converter(const Converter& rhs) { operator=(rhs); } Converter& operator=(const Converter& rhs) { m_scratch = rhs.m_scratch; return *this; @@ -436,7 +437,7 @@ class CSimpleIniTempl { @param a_bMultiKey See the method SetMultiKey() for details. @param a_bMultiLine See the method SetMultiLine() for details. */ - CSimpleIniTempl(bool a_bIsUtf8 = false, bool a_bMultiKey = false, bool a_bMultiLine = false); + explicit CSimpleIniTempl(bool a_bIsUtf8 = false, bool a_bMultiKey = false, bool a_bMultiLine = false); /** Destructor */ ~CSimpleIniTempl(); @@ -1011,7 +1012,7 @@ class CSimpleIniTempl { private: // copying is not permitted - CSimpleIniTempl(const CSimpleIniTempl&); // disabled + explicit(false) CSimpleIniTempl(const CSimpleIniTempl&); // disabled CSimpleIniTempl& operator=(const CSimpleIniTempl&); // disabled /** Parse the data looking for a file comment and store it if found. @@ -1862,9 +1863,9 @@ SI_Error CSimpleIniTempl::SetLongValue(const // convert to an ASCII string char szInput[64]; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); + snprintf_s(szInput, sizeof(szInput), a_bUseHex ? "0x%lx" : "%ld", a_nValue); #else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); + snprintf(szInput, sizeof(szInput), a_bUseHex ? "0x%lx" : "%ld", a_nValue); #endif // __STDC_WANT_SECURE_LIB__ // convert to output text @@ -1914,13 +1915,8 @@ SI_Error CSimpleIniTempl::SetDoubleValue(cons if (!a_pSection || !a_pKey) return SI_FAIL; - // convert to an ASCII string char szInput[64]; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, "%f", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, "%f", a_nValue); -#endif // __STDC_WANT_SECURE_LIB__ + snprintf(szInput, sizeof(szInput), "%f", a_nValue); // convert to output text SI_CHAR szOutput[64]; @@ -2436,10 +2432,10 @@ class SI_ConvertA { SI_ConvertA() {} public: - SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} + explicit SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} /* copy and assignment */ - SI_ConvertA(const SI_ConvertA& rhs) { operator=(rhs); } + explicit(false) SI_ConvertA(const SI_ConvertA& rhs) { operator=(rhs); } SI_ConvertA& operator=(const SI_ConvertA& rhs) { m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; return *this; @@ -2555,10 +2551,10 @@ class SI_ConvertW { SI_ConvertW() {} public: - SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} + explicit SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} /* copy and assignment */ - SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } + explicit(false) SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } SI_ConvertW& operator=(const SI_ConvertW& rhs) { m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; return *this; @@ -2736,10 +2732,10 @@ class SI_ConvertW { SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) {} public: - SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; } + explicit SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; } /* copy and assignment */ - SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } + explicit(false) SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } SI_ConvertW& operator=(const SI_ConvertW& rhs) { m_pEncoding = rhs.m_pEncoding; m_pConverter = NULL; @@ -2943,10 +2939,10 @@ class SI_ConvertW { SI_ConvertW() {} public: - SI_ConvertW(bool a_bStoreIsUtf8) { m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; } + explicit SI_ConvertW(bool a_bStoreIsUtf8) { m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; } /* copy and assignment */ - SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } + explicit(false) SI_ConvertW(const SI_ConvertW& rhs) { operator=(rhs); } SI_ConvertW& operator=(const SI_ConvertW& rhs) { m_uCodePage = rhs.m_uCodePage; return *this; diff --git a/fdbclient/include/fdbclient/SnapshotCache.h b/fdbclient/include/fdbclient/SnapshotCache.h index d6b9b90c642..e64ed5a23e0 100644 --- a/fdbclient/include/fdbclient/SnapshotCache.h +++ b/fdbclient/include/fdbclient/SnapshotCache.h @@ -29,7 +29,8 @@ struct ExtStringRef { ExtStringRef() : extra_zero_bytes(0) {} - ExtStringRef(StringRef const& s, int extra_zero_bytes = 0) : base(s), extra_zero_bytes(extra_zero_bytes) {} + explicit(false) ExtStringRef(StringRef const& s, int extra_zero_bytes = 0) + : base(s), extra_zero_bytes(extra_zero_bytes) {} Standalone toStandaloneStringRef() const { auto s = makeString(size()); @@ -180,7 +181,7 @@ class SnapshotCache { // Note that an uncached range stops at the next individual key that is known, even though we might want to read // through that key. In RYWIterator, it might also stop at a dependent write which is not known at all! - iterator(SnapshotCache* cache) : parent(cache), it(cache->entries.begin()), offset(0) { + explicit iterator(SnapshotCache* cache) : parent(cache), it(cache->entries.begin()), offset(0) { //++*this; // gives begin } @@ -311,7 +312,7 @@ class SnapshotCache { entries.insert(Entry(allKeys.end, afterAllKeys, VectorRef()), NoMetric(), true); } // Visual Studio refuses to generate these, apparently despite the standard - SnapshotCache(SnapshotCache&& r) noexcept : arena(r.arena), entries(std::move(r.entries)) {} + explicit(false) SnapshotCache(SnapshotCache&& r) noexcept : arena(r.arena), entries(std::move(r.entries)) {} SnapshotCache& operator=(SnapshotCache&& r) noexcept { entries = std::move(r.entries); arena = r.arena; diff --git a/fdbclient/include/fdbclient/SpecialKeySpace.actor.h b/fdbclient/include/fdbclient/SpecialKeySpace.h similarity index 90% rename from fdbclient/include/fdbclient/SpecialKeySpace.actor.h rename to fdbclient/include/fdbclient/SpecialKeySpace.h index 340f22dbb13..7dd0715b776 100644 --- a/fdbclient/include/fdbclient/SpecialKeySpace.actor.h +++ b/fdbclient/include/fdbclient/SpecialKeySpace.h @@ -1,5 +1,5 @@ /* - * SpecialKeySpace.actor.h + * SpecialKeySpace.h * * This source file is part of the FoundationDB open source project * @@ -20,18 +20,11 @@ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_SPECIALKEYSPACE_ACTOR_G_H) -#define FDBCLIENT_SPECIALKEYSPACE_ACTOR_G_H -#include "fdbclient/SpecialKeySpace.actor.g.h" -#elif !defined(FDBCLIENT_SPECIALKEYSPACE_ACTOR_H) -#define FDBCLIENT_SPECIALKEYSPACE_ACTOR_H - #include "flow/flow.h" #include "flow/Arena.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/KeyRangeMap.h" #include "fdbclient/ReadYourWrites.h" -#include "flow/actorcompiler.h" // This must be the last #include. class SpecialKeyRangeReadImpl { public: @@ -131,11 +124,11 @@ class SpecialKeyRangeAsyncImpl : public SpecialKeyRangeReadImpl { bool isAsync() const override { return true; } - ACTOR static Future getRangeAsyncActor(const SpecialKeyRangeReadImpl* skrAyncImpl, - ReadYourWritesTransaction* ryw, - KeyRangeRef kr, - GetRangeLimits limits, - KeyRangeMap>* cache) { + static Future getRangeAsyncActor(const SpecialKeyRangeReadImpl* skrAyncImpl, + ReadYourWritesTransaction* ryw, + KeyRangeRef kr, + GetRangeLimits limits, + KeyRangeMap>* cache) { ASSERT(skrAyncImpl->getKeyRange().contains(kr)); ASSERT(cache != nullptr); ASSERT(cache->rangeContaining(kr.begin) == cache->rangeContainingKeyBefore(kr.end)); @@ -143,7 +136,7 @@ class SpecialKeyRangeAsyncImpl : public SpecialKeyRangeReadImpl { // For simplicity, every time we need to cache, we read the whole range // Although sometimes the range can be narrowed, // there is not a general way to do it in complicated scenarios - RangeResult result_ = wait(skrAyncImpl->getRange(ryw, skrAyncImpl->getKeyRange(), limits)); + RangeResult result_ = co_await skrAyncImpl->getRange(ryw, skrAyncImpl->getKeyRange(), limits); cache->insert(skrAyncImpl->getKeyRange(), result_); } const auto& allResults = (*cache)[kr.begin].get(); @@ -155,9 +148,9 @@ class SpecialKeyRangeAsyncImpl : public SpecialKeyRangeReadImpl { if (start < end) { RangeResult result = RangeResultRef(allResults.slice(start, end), false); result.arena().dependsOn(allResults.arena()); - return result; - } else - return RangeResult(); + co_return result; + } + co_return RangeResult(); } }; @@ -187,7 +180,7 @@ class SpecialKeySpace { READWRITE // The underlying special key range can be called with get, getRange, set, clear }; - SpecialKeySpace(KeyRef spaceStartKey = Key(), KeyRef spaceEndKey = normalKeys.end, bool testOnly = true); + explicit SpecialKeySpace(KeyRef spaceStartKey = Key(), KeyRef spaceEndKey = normalKeys.end, bool testOnly = true); Future> get(ReadYourWritesTransaction* ryw, const Key& key); @@ -235,20 +228,20 @@ class SpecialKeySpace { static const std::set& getTracingOptions() { return tracingOptions; } private: - ACTOR static Future> getActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ryw, KeyRef key); - - ACTOR static Future checkRYWValid(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse); - ACTOR static Future getRangeAggregationActor(SpecialKeySpace* sks, - ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse); + static Future> getActor(SpecialKeySpace* sks, ReadYourWritesTransaction* ryw, KeyRef key); + + static Future checkRYWValid(SpecialKeySpace* sks, + ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse); + static Future getRangeAggregationActor(SpecialKeySpace* sks, + ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse); KeyRangeMap readImpls; KeyRangeMap modules; @@ -568,12 +561,9 @@ class FaultToleranceMetricsImpl : public SpecialKeyRangeReadImpl { // If the underlying set of key-value pairs of a key space is not changing, then we expect repeating a read to give the // same result. Additionally, we can generate the expected result of any read if that read is reading a subrange. This // actor performs a read of an arbitrary subrange of [begin, end) and validates the results. -ACTOR Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, - KeySelector begin, - KeySelector end, - GetRangeLimits limits, - Reverse reverse, - RangeResult result); - -#include "flow/unactorcompiler.h" -#endif +Future validateSpecialSubrangeRead(ReadYourWritesTransaction* ryw, + KeySelector begin, + KeySelector end, + GetRangeLimits limits, + Reverse reverse, + RangeResult result); diff --git a/fdbclient/include/fdbclient/Status.h b/fdbclient/include/fdbclient/Status.h index 405dd9a8407..c998d7d61bb 100644 --- a/fdbclient/include/fdbclient/Status.h +++ b/fdbclient/include/fdbclient/Status.h @@ -34,7 +34,7 @@ struct StatusObject : json_spirit::mObject { typedef json_spirit::mArray Array; StatusObject() {} - StatusObject(json_spirit::mObject const& o) : json_spirit::mObject(o) {} + explicit(false) StatusObject(json_spirit::mObject const& o) : json_spirit::mObject(o) {} }; template @@ -61,12 +61,12 @@ void save(Ar& ar, StatusObject const& statusObj) { struct StatusArray : json_spirit::mArray { StatusArray() {} - StatusArray(json_spirit::mArray const& o) : json_spirit::mArray(o.begin(), o.end()) {} + explicit(false) StatusArray(json_spirit::mArray const& o) : json_spirit::mArray(o.begin(), o.end()) {} }; struct StatusValue : json_spirit::mValue { StatusValue() {} - StatusValue(json_spirit::mValue const& o) : json_spirit::mValue(o) {} + explicit(false) StatusValue(json_spirit::mValue const& o) : json_spirit::mValue(o) {} }; enum class MessageType { diff --git a/fdbclient/include/fdbclient/StatusClient.h b/fdbclient/include/fdbclient/StatusClient.h index b9176d55a53..ccd64b911e1 100644 --- a/fdbclient/include/fdbclient/StatusClient.h +++ b/fdbclient/include/fdbclient/StatusClient.h @@ -36,7 +36,7 @@ class StatusClient { // only. // @out status json // @note we don't have support to fetch arbitrary status json fields at this point. - static Future statusFetcher(Database db, std::string statusField = ""); + static AsyncResult statusFetcher(Database db, std::string statusField = ""); }; #endif diff --git a/fdbclient/include/fdbclient/StorageCheckpoint.h b/fdbclient/include/fdbclient/StorageCheckpoint.h index 8ed71aab797..28ba9d37bed 100644 --- a/fdbclient/include/fdbclient/StorageCheckpoint.h +++ b/fdbclient/include/fdbclient/StorageCheckpoint.h @@ -187,7 +187,7 @@ struct DataMoveMetaData { DataMoveMetaData(UID id, KeyRange range) : id(id), version(invalidVersion), priority(0), mode(0) { this->ranges.push_back(range); } - DataMoveMetaData(UID id) : id(id), version(invalidVersion), priority(0), mode(0) {} + explicit DataMoveMetaData(UID id) : id(id), version(invalidVersion), priority(0), mode(0) {} Phase getPhase() const { return static_cast(phase); } diff --git a/fdbclient/include/fdbclient/StorageServerInterface.h b/fdbclient/include/fdbclient/StorageServerInterface.h index 230a2d63b42..9be4826d51a 100644 --- a/fdbclient/include/fdbclient/StorageServerInterface.h +++ b/fdbclient/include/fdbclient/StorageServerInterface.h @@ -36,7 +36,7 @@ #include "fdbrpc/TimedRequest.h" #include "fdbrpc/TSSComparison.h" #include "fdbclient/CommitTransaction.h" -#include "fdbclient/TagThrottle.actor.h" +#include "fdbclient/TagThrottle.h" #include "fdbclient/Tracing.h" #include "flow/UnitTest.h" #include "fdbclient/VersionVector.h" @@ -129,6 +129,7 @@ struct StorageServerInterface { RequestStream bulkdump; private: + void initEndpointsFromGetValue(); bool acceptingRequests; public: @@ -156,82 +157,12 @@ struct StorageServerInterface { serializer(ar, uniqueID, locality, getValue, tssPairID, acceptingRequests); if (Ar::isDeserializing) { - getKey = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(1)); - getKeyValues = - PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(2)); - getShardState = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(3)); - waitMetrics = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(4)); - splitMetrics = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(5)); - getStorageMetrics = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(6)); - waitFailure = RequestStream>(getValue.getEndpoint().getAdjustedEndpoint(7)); - getQueuingMetrics = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(8)); - getKeyValueStoreType = - RequestStream>(getValue.getEndpoint().getAdjustedEndpoint(9)); - watchValue = PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(10)); - getReadHotRanges = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(11)); - getRangeSplitPoints = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(12)); - getKeyValuesStream = - PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(13)); - getMappedKeyValues = - PublicRequestStream(getValue.getEndpoint().getAdjustedEndpoint(14)); - changeFeedStream = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(15)); - overlappingChangeFeeds = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(16)); - changeFeedPop = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(17)); - changeFeedVersionUpdate = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(18)); - checkpoint = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(19)); - fetchCheckpoint = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(20)); - fetchCheckpointKeyValues = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(21)); - updateCommitCostRequest = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(22)); - auditStorage = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(23)); - getHotShards = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(24)); - getCheckSum = - RequestStream(getValue.getEndpoint().getAdjustedEndpoint(25)); - bulkdump = RequestStream(getValue.getEndpoint().getAdjustedEndpoint(26)); + initEndpointsFromGetValue(); } } bool operator==(StorageServerInterface const& s) const { return uniqueID == s.uniqueID; } bool operator<(StorageServerInterface const& s) const { return uniqueID < s.uniqueID; } - void initEndpoints() { - std::vector> streams; - streams.push_back(getValue.getReceiver(TaskPriority::LoadBalancedEndpoint)); - streams.push_back(getKey.getReceiver(TaskPriority::LoadBalancedEndpoint)); - streams.push_back(getKeyValues.getReceiver(TaskPriority::LoadBalancedEndpoint)); - streams.push_back(getShardState.getReceiver()); - streams.push_back(waitMetrics.getReceiver()); - streams.push_back(splitMetrics.getReceiver()); - streams.push_back(getStorageMetrics.getReceiver()); - streams.push_back(waitFailure.getReceiver()); - streams.push_back(getQueuingMetrics.getReceiver()); - streams.push_back(getKeyValueStoreType.getReceiver()); - streams.push_back(watchValue.getReceiver()); - streams.push_back(getReadHotRanges.getReceiver()); - streams.push_back(getRangeSplitPoints.getReceiver()); - streams.push_back(getKeyValuesStream.getReceiver(TaskPriority::LoadBalancedEndpoint)); - streams.push_back(getMappedKeyValues.getReceiver(TaskPriority::LoadBalancedEndpoint)); - streams.push_back(changeFeedStream.getReceiver()); - streams.push_back(overlappingChangeFeeds.getReceiver()); - streams.push_back(changeFeedPop.getReceiver()); - streams.push_back(changeFeedVersionUpdate.getReceiver()); - streams.push_back(checkpoint.getReceiver()); - streams.push_back(fetchCheckpoint.getReceiver()); - streams.push_back(fetchCheckpointKeyValues.getReceiver()); - streams.push_back(updateCommitCostRequest.getReceiver()); - streams.push_back(auditStorage.getReceiver()); - streams.push_back(getHotShards.getReceiver()); - streams.push_back(getCheckSum.getReceiver()); - streams.push_back(bulkdump.getReceiver()); - FlowTransport::transport().addEndpoints(streams); - } + void initEndpoints(); }; struct StorageInfo : NonCopyable, public ReferenceCounted { @@ -243,8 +174,8 @@ struct StorageInfo : NonCopyable, public ReferenceCounted { struct StorageServerMetaInfo : public StorageServerInterface { Optional metadata; - StorageServerMetaInfo(const StorageServerInterface& interface, - Optional metadata = Optional()) + explicit StorageServerMetaInfo(const StorageServerInterface& interface, + Optional metadata = Optional()) : StorageServerInterface(interface), metadata(metadata) {} }; @@ -475,7 +406,7 @@ struct GetKeyValuesStreamReply : public ReplyPromiseStreamReply { bool cached = false; GetKeyValuesStreamReply() : version(invalidVersion), more(false), cached(false) {} - GetKeyValuesStreamReply(GetKeyValuesReply r) + explicit GetKeyValuesStreamReply(GetKeyValuesReply r) : arena(r.arena), data(r.data), version(r.version), more(r.more), cached(r.cached) {} int expectedSize() const { return sizeof(GetKeyValuesStreamReply) + data.expectedSize(); } @@ -821,7 +752,7 @@ struct ReadHotSubRangeRequest { int chunkCount = 1; ReadHotSubRangeRequest() {} - ReadHotSubRangeRequest(KeyRangeRef const& keys, SplitType type = SplitType::BYTES, int chunkCount = 1) + explicit ReadHotSubRangeRequest(KeyRangeRef const& keys, SplitType type = SplitType::BYTES, int chunkCount = 1) : keys(arena, keys), type(type), chunkCount(chunkCount) {} template @@ -971,7 +902,7 @@ struct FetchCheckpointReply : public ReplyPromiseStreamReply { Standalone data; FetchCheckpointReply() {} - FetchCheckpointReply(StringRef token) : token(token) {} + explicit FetchCheckpointReply(StringRef token) : token(token) {} int expectedSize() const { return data.expectedSize(); } diff --git a/fdbclient/include/fdbclient/StorageWiggleMetrics.actor.h b/fdbclient/include/fdbclient/StorageWiggleMetrics.h similarity index 90% rename from fdbclient/include/fdbclient/StorageWiggleMetrics.actor.h rename to fdbclient/include/fdbclient/StorageWiggleMetrics.h index fdc919be6c0..c1f15763b2d 100644 --- a/fdbclient/include/fdbclient/StorageWiggleMetrics.actor.h +++ b/fdbclient/include/fdbclient/StorageWiggleMetrics.h @@ -1,5 +1,5 @@ /* - * StorageWiggleMetrics.actor.h + * StorageWiggleMetrics.h * * This source file is part of the FoundationDB open source project * @@ -19,19 +19,13 @@ */ #pragma once -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_STORAGEWIGGLEMETRICS_ACTOR_G_H) -#define FDBCLIENT_STORAGEWIGGLEMETRICS_ACTOR_G_H -#include "fdbclient/StorageWiggleMetrics.actor.g.h" -#elif !defined(FDBCLIENT_STORAGEWIGGLEMETRICS_ACTOR_H) -#define FDBCLIENT_STORAGEWIGGLEMETRICS_ACTOR_H #include "fdbrpc/Smoother.h" #include "flow/ObjectSerializer.h" #include "flow/serialize.h" #include "fdbclient/SystemData.h" -#include "fdbclient/KeyBackedTypes.actor.h" -#include "fdbclient/RunTransaction.actor.h" -#include "flow/actorcompiler.h" +#include "fdbclient/KeyBackedTypes.h" +#include "fdbclient/RunTransaction.h" FDB_BOOLEAN_PARAM(PrimaryRegion); @@ -118,22 +112,22 @@ struct StorageWiggleDelay { namespace { // Persistent the total delay time to the database, and return accumulated delay time. -ACTOR template +template Future addPerpetualWiggleDelay_impl( TrType tr, KeyBackedObjectProperty delayProperty, double secDelta) { - state StorageWiggleDelay delayObj = wait(delayProperty.getD(tr, Snapshot::False)); + StorageWiggleDelay delayObj = co_await delayProperty.getD(tr, Snapshot::False); delayObj.delaySeconds += secDelta; delayProperty.set(tr, delayObj); - return delayObj.delaySeconds; + co_return delayObj.delaySeconds; } // set all fields except for smoothed durations to default values. If the metrics is not given, load from system key // space -ACTOR template +template Future resetStorageWiggleMetrics_impl( TrType tr, KeyBackedObjectProperty metricsProperty, @@ -141,14 +135,13 @@ Future resetStorageWiggleMetrics_impl( tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); if (!metrics.present()) { - wait(store(metrics, metricsProperty.get(tr))); + metrics = co_await metricsProperty.get(tr); } if (metrics.present()) { metrics.get().reset(); metricsProperty.set(tr, metrics.get()); } - return Void(); } } // namespace @@ -159,7 +152,7 @@ class StorageWiggleData { public: struct DataForDc : public KeyBackedClass { - DataForDc(StringRef prefix) : KeyBackedClass(prefix) {} + explicit DataForDc(StringRef prefix) : KeyBackedClass(prefix) {} auto storageWiggleDelay() const { auto key = subspace.pack("storageWiggleDelay"_sr); @@ -216,7 +209,7 @@ class StorageWiggleData { return resetStorageWiggleMetrics_impl(tr, storageWiggleMetrics(primary), metrics); } - ACTOR template + template static Future updateStorageWiggleMetrics_impl( KeyBackedProperty wiggleSpeed, KeyBackedObjectProperty storageMetrics, @@ -225,13 +218,12 @@ class StorageWiggleData { PrimaryRegion primary) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); tr->setOption(FDBTransactionOptions::LOCK_AWARE); - Optional v = wait(wiggleSpeed.get(tr)); + Optional v = co_await wiggleSpeed.get(tr); if (v.present() && v == "1"_sr) { storageMetrics.set(tr, metrics); } else { CODE_PROBE(true, "Intend to update StorageWiggleMetrics after PW disabled"); } - return Void(); } // update the serialized metrics when the perpetual wiggle is enabled @@ -241,6 +233,3 @@ class StorageWiggleData { perpetualWiggleSpeed(), storageWiggleMetrics(primary), tr, metrics, primary); } }; - -#include "flow/unactorcompiler.h" -#endif \ No newline at end of file diff --git a/fdbclient/include/fdbclient/Subspace.h b/fdbclient/include/fdbclient/Subspace.h index 2d68e0476c5..da62e2ee470 100644 --- a/fdbclient/include/fdbclient/Subspace.h +++ b/fdbclient/include/fdbclient/Subspace.h @@ -28,8 +28,8 @@ class Subspace { public: - Subspace(Tuple const& tuple = Tuple(), StringRef const& rawPrefix = StringRef()); - Subspace(StringRef const& rawPrefix); + explicit Subspace(Tuple const& tuple = Tuple(), StringRef const& rawPrefix = StringRef()); + explicit(false) Subspace(StringRef const& rawPrefix); virtual ~Subspace(); @@ -70,4 +70,4 @@ class Subspace { Standalone> rawPrefix; }; -#endif \ No newline at end of file +#endif diff --git a/fdbclient/include/fdbclient/SystemData.h b/fdbclient/include/fdbclient/SystemData.h index 19d1029595b..e9e13f6d4e4 100644 --- a/fdbclient/include/fdbclient/SystemData.h +++ b/fdbclient/include/fdbclient/SystemData.h @@ -79,13 +79,9 @@ enum class DataMovementReason : uint8_t { // SystemKey is just a Key but with a special type so that instances of it can be found easily throughput the code base // and in simulation constructions will verify that no SystemKey is a direct prefix of any other. struct SystemKey : Key { - SystemKey(Key const& k); + explicit SystemKey(Key const& k); }; -struct RestoreLoaderInterface; -struct RestoreApplierInterface; -struct RestoreMasterInterface; - extern const KeyRangeRef normalKeys; // '' to systemKeys.begin extern const KeyRangeRef systemKeys; // [FF] to [FF][FF] extern const KeyRangeRef nonMetadataSystemKeys; // [FF][00] to [FF][01] @@ -109,16 +105,13 @@ extern const KeyRef keyServersPrefix, keyServersEnd, keyServersKeyServersKey; // temporary ID, until a permanent ID is assigned to it. extern const UID anonymousShardId; extern const uint64_t assignedEmptyShardId; -const Key keyServersKey(const KeyRef& k); -const KeyRef keyServersKey(const KeyRef& k, Arena& arena); -const Value keyServersValue(RangeResult result, - const std::vector& src, - const std::vector& dest = std::vector()); -const Value keyServersValue(const std::vector& srcTag, const std::vector& destTag = std::vector()); -const Value keyServersValue(const std::vector& src, - const std::vector& dest, - const UID& srcID, - const UID& destID); +Key keyServersKey(const KeyRef& k); +KeyRef keyServersKey(const KeyRef& k, Arena& arena); +Value keyServersValue(RangeResult result, + const std::vector& src, + const std::vector& dest = std::vector()); +Value keyServersValue(const std::vector& srcTag, const std::vector& destTag = std::vector()); +Value keyServersValue(const std::vector& src, const std::vector& dest, const UID& srcID, const UID& destID); // `result` must be the full result of getting serverTagKeys void decodeKeyServersValue(RangeResult result, const ValueRef& value, @@ -139,7 +132,7 @@ void decodeKeyServersValue(RangeResult result, bool isSystemKey(KeyRef key); extern const KeyRef accumulativeChecksumKey; -const Value accumulativeChecksumValue(const AccumulativeChecksumState& acsState); +Value accumulativeChecksumValue(const AccumulativeChecksumState& acsState); AccumulativeChecksumState decodeAccumulativeChecksum(const ValueRef& value); extern const KeyRangeRef auditKeys; @@ -148,34 +141,34 @@ extern const KeyRangeRef auditRanges; extern const KeyRef auditRangePrefix; // Key for a particular audit -const Key auditKey(const AuditType type, const UID& auditId); +Key auditKey(const AuditType type, const UID& auditId); // KeyRange for whole audit -const KeyRange auditKeyRange(const AuditType type); +KeyRange auditKeyRange(const AuditType type); // Prefix for audit work progress by range -const Key auditRangeBasedProgressPrefixFor(const AuditType type, const UID& auditId); +Key auditRangeBasedProgressPrefixFor(const AuditType type, const UID& auditId); // Range for audit work progress by range -const KeyRange auditRangeBasedProgressRangeFor(const AuditType type, const UID& auditId); -const KeyRange auditRangeBasedProgressRangeFor(const AuditType type); +KeyRange auditRangeBasedProgressRangeFor(const AuditType type, const UID& auditId); +KeyRange auditRangeBasedProgressRangeFor(const AuditType type); // Prefix for audit work progress by server -const Key auditServerBasedProgressPrefixFor(const AuditType type, const UID& auditId, const UID& serverId); +Key auditServerBasedProgressPrefixFor(const AuditType type, const UID& auditId, const UID& serverId); // Range for audit work progress by server -const KeyRange auditServerBasedProgressRangeFor(const AuditType type, const UID& auditId); -const KeyRange auditServerBasedProgressRangeFor(const AuditType type); +KeyRange auditServerBasedProgressRangeFor(const AuditType type, const UID& auditId); +KeyRange auditServerBasedProgressRangeFor(const AuditType type); -const Value auditStorageStateValue(const AuditStorageState& auditStorageState); +Value auditStorageStateValue(const AuditStorageState& auditStorageState); AuditStorageState decodeAuditStorageState(const ValueRef& value); // "\xff/checkpoint/[[UID]] := [[CheckpointMetaData]]" extern const KeyRef checkpointPrefix; -const Key checkpointKeyFor(UID checkpointID); -const Value checkpointValue(const CheckpointMetaData& checkpoint); +Key checkpointKeyFor(UID checkpointID); +Value checkpointValue(const CheckpointMetaData& checkpoint); UID decodeCheckpointKey(const KeyRef& key); CheckpointMetaData decodeCheckpointValue(const ValueRef& value); // "\xff/dataMoves/[[UID]] := [[DataMoveMetaData]]" extern const KeyRangeRef dataMoveKeys; -const Key dataMoveKeyFor(UID dataMoveId); -const Value dataMoveValue(const DataMoveMetaData& dataMove); +Key dataMoveKeyFor(UID dataMoveId); +Value dataMoveValue(const DataMoveMetaData& dataMove); UID decodeDataMoveKey(const KeyRef& key); DataMoveMetaData decodeDataMoveValue(const ValueRef& value); @@ -187,17 +180,17 @@ DataMoveMetaData decodeDataMoveValue(const ValueRef& value); extern const KeyRangeRef serverKeysRange; extern const KeyRef serverKeysPrefix; extern const ValueRef serverKeysTrue, serverKeysTrueEmptyRange, serverKeysFalse; -const UID newDataMoveId(const uint64_t physicalShardId, - AssignEmptyRange assignEmptyRange, - const DataMoveType type, - const DataMovementReason reason, - UnassignShard unassignShard = UnassignShard::False); -const Key serverKeysKey(UID serverID, const KeyRef& keys); -const Key serverKeysPrefixFor(UID serverID); +UID newDataMoveId(const uint64_t physicalShardId, + AssignEmptyRange assignEmptyRange, + const DataMoveType type, + const DataMovementReason reason, + UnassignShard unassignShard = UnassignShard::False); +Key serverKeysKey(UID serverID, const KeyRef& keys); +Key serverKeysPrefixFor(UID serverID); UID serverKeysDecodeServer(const KeyRef& key); std::pair serverKeysDecodeServerBegin(const KeyRef& key); bool serverHasKey(ValueRef storedValue); -const Value serverKeysValue(const UID& id); +Value serverKeysValue(const UID& id); void decodeDataMoveId(const UID& id, bool& assigned, bool& emptyRange, @@ -223,7 +216,7 @@ extern const KeyRangeRef tssMappingKeys; // For quarantining a misbehaving TSS. extern const KeyRangeRef tssQuarantineKeys; -const Key tssQuarantineKeyFor(UID serverID); +Key tssQuarantineKeyFor(UID serverID); UID decodeTssQuarantineKey(KeyRef const&); // \xff/tssMismatch/[[Tuple]] := [[TraceEventString]] @@ -251,23 +244,23 @@ extern const KeyRef serverTagConflictPrefix; extern const KeyRangeRef serverTagHistoryKeys; extern const KeyRef serverTagHistoryPrefix; -const Key serverTagKeyFor(UID serverID); -const Key serverTagHistoryKeyFor(UID serverID); -const KeyRange serverTagHistoryRangeFor(UID serverID); -const KeyRange serverTagHistoryRangeBefore(UID serverID, Version version); -const Value serverTagValue(Tag); +Key serverTagKeyFor(UID serverID); +Key serverTagHistoryKeyFor(UID serverID); +KeyRange serverTagHistoryRangeFor(UID serverID); +KeyRange serverTagHistoryRangeBefore(UID serverID, Version version); +Value serverTagValue(Tag); UID decodeServerTagKey(KeyRef const&); Version decodeServerTagHistoryKey(KeyRef const&); Tag decodeServerTagValue(ValueRef const&); -const Key serverTagConflictKeyFor(Tag); +Key serverTagConflictKeyFor(Tag); // "\xff/tagLocalityList/[[datacenterID]]" := "[[tagLocality]]" // Provides the tagLocality for the given datacenterID // See "FDBTypes.h" struct Tag for more details on tagLocality extern const KeyRangeRef tagLocalityListKeys; extern const KeyRef tagLocalityListPrefix; -const Key tagLocalityListKeyFor(Optional dcID); -const Value tagLocalityListValue(int8_t const&); +Key tagLocalityListKeyFor(Optional dcID); +Value tagLocalityListValue(int8_t const&); Optional decodeTagLocalityListKey(KeyRef const&); int8_t decodeTagLocalityListValue(ValueRef const&); @@ -276,8 +269,8 @@ int8_t decodeTagLocalityListValue(ValueRef const&); // Used in the initialization of the Data Distributor. extern const KeyRangeRef datacenterReplicasKeys; extern const KeyRef datacenterReplicasPrefix; -const Key datacenterReplicasKeyFor(Optional dcID); -const Value datacenterReplicasValue(int const&); +Key datacenterReplicasKeyFor(Optional dcID); +Value datacenterReplicasValue(int const&); Optional decodeDatacenterReplicasKey(KeyRef const&); int decodeDatacenterReplicasValue(ValueRef const&); @@ -286,7 +279,7 @@ int decodeDatacenterReplicasValue(ValueRef const&); // (as opposed to having no value at all) extern const KeyRangeRef tLogDatacentersKeys; extern const KeyRef tLogDatacentersPrefix; -const Key tLogDatacentersKeyFor(Optional dcID); +Key tLogDatacentersKeyFor(Optional dcID); Optional decodeTLogDatacentersKey(KeyRef const&); extern const KeyRef primaryDatacenterKey; @@ -297,8 +290,8 @@ extern const KeyRef primaryDatacenterKey; // have a new ID. When removed from here, a storage server may release all resources and destroy itself. extern const KeyRangeRef serverListKeys; extern const KeyRef serverListPrefix; -const Key serverListKeyFor(UID serverID); -const Value serverListValue(StorageServerInterface const&); +Key serverListKeyFor(UID serverID); +Value serverListValue(StorageServerInterface const&); UID decodeServerListKey(KeyRef const&); StorageServerInterface decodeServerListValue(ValueRef const&); @@ -312,8 +305,8 @@ extern const KeyRef processClassPrefix; extern const KeyRef processClassChangeKey; extern const KeyRef processClassVersionKey; extern const ValueRef processClassVersionValue; -const Key processClassKeyFor(StringRef processID); -const Value processClassValue(ProcessClass const&); +Key processClassKeyFor(StringRef processID); +Value processClassValue(ProcessClass const&); Key decodeProcessClassKey(KeyRef const&); ProcessClass decodeProcessClassValue(ValueRef const&); UID decodeProcessClassKeyOld(KeyRef const& key); @@ -325,6 +318,7 @@ extern const KeyRangeRef configKeys; extern const KeyRef configKeysPrefix; extern const KeyRef backupWorkerEnabledKey; +extern const KeyRef rangeBackupWorkerEnabledKey; extern const KeyRef perpetualStorageWiggleKey; extern const KeyRef perpetualStorageWiggleLocalityKey; extern const KeyRef perpetualStorageWiggleIDPrefix; @@ -411,8 +405,8 @@ extern const KeyRef globalConfigVersionKey; // and are currently (recently) available extern const KeyRangeRef workerListKeys; extern const KeyRef workerListPrefix; -const Key workerListKeyFor(StringRef processID); -const Value workerListValue(ProcessData const&); +Key workerListKeyFor(StringRef processID); +Value workerListValue(ProcessData const&); Key decodeWorkerListKey(KeyRef const&); ProcessData decodeWorkerListValue(ValueRef const&); @@ -421,11 +415,24 @@ ProcessData decodeWorkerListValue(ValueRef const&); // See "FDBTypes.h" struct WorkerBackupStatus for more details on the return type value. extern const KeyRangeRef backupProgressKeys; extern const KeyRef backupProgressPrefix; -const Key backupProgressKeyFor(UID workerID); -const Value backupProgressValue(const WorkerBackupStatus& status); +Key backupProgressKeyFor(UID workerID); +Value backupProgressValue(const WorkerBackupStatus& status); UID decodeBackupProgressKey(const KeyRef& key); WorkerBackupStatus decodeBackupProgressValue(const ValueRef& value); +// "\xff\x02/backupRangePartitionedProgress/[[workerID]]" := "[[WorkerBackupStatus]]" +extern const KeyRangeRef backupRangePartitionedProgressKeys; +extern const KeyRef backupRangePartitionedProgressPrefix; +Key backupRangePartitionedProgressKey(UID workerID); +Value backupRangePartitionedProgressValue(const WorkerBackupStatus& status); +UID decodeBackupRangePartitionedProgressKey(const KeyRef& key); +WorkerBackupStatus decodeBackupRangePartitionedProgressValue(const ValueRef& value); + +// The key to signal when partition map has been uploaded for a given version. +// "\xff\x02/backupRangePartitionedMapUploaded/" := "1" +extern const KeyRef backupRangePartitionedMapUploadedPrefix; +Key backupRangePartitionedMapUploadedKeyFor(Version v); + // The key to signal backup workers a new backup job is submitted. // "\xff\x02/backupStarted" := "[[vector]]" extern const KeyRef backupStartedKey; @@ -438,9 +445,6 @@ std::vector> decodeBackupStartedValue(const ValueRef& va // 1 = Send a signal to pause/already paused. extern const KeyRef backupPausedKey; -// The key to store the maximum version that backup workers popped in NOOP mode. -extern const KeyRef backupWorkerMaxNoopVersionKey; - // "\xff/previousCoordinators" = "[[ClusterConnectionString]]" // Set to the encoded structure of the cluster's previous set of coordinators. // Changed when performing quorumChange. @@ -467,8 +471,8 @@ extern const KeyRef minRequiredCommitVersionKey; // number of microseconds since the Unix epoch. extern const KeyRef versionEpochKey; -const Value logsValue(const std::vector>& logs, - const std::vector>& oldLogs); +Value logsValue(const std::vector>& logs, + const std::vector>& oldLogs); std::pair>, std::vector>> decodeLogsValue( const ValueRef& value); @@ -495,37 +499,48 @@ extern const UID dataDistributionModeLock; extern const KeyRef bulkLoadModeKey; extern const KeyRangeRef bulkLoadTaskKeys; extern const KeyRef bulkLoadTaskPrefix; -const Value bulkLoadTaskStateValue(const BulkLoadTaskState& bulkLoadTaskState); +Value bulkLoadTaskStateValue(const BulkLoadTaskState& bulkLoadTaskState); BulkLoadTaskState decodeBulkLoadTaskState(const ValueRef& value); -const Value ssBulkLoadMetadataValue(const SSBulkLoadMetadata& ssBulkLoadMetadata); +Value ssBulkLoadMetadataValue(const SSBulkLoadMetadata& ssBulkLoadMetadata); SSBulkLoadMetadata decodeSSBulkLoadMetadata(const ValueRef& value); extern const KeyRangeRef bulkLoadJobKeys; extern const KeyRef bulkLoadJobPrefix; -const Value bulkLoadJobValue(const BulkLoadJobState& bulkLoadJobState); +Value bulkLoadJobValue(const BulkLoadJobState& bulkLoadJobState); BulkLoadJobState decodeBulkLoadJobState(const ValueRef& value); extern const KeyRangeRef bulkLoadJobHistoryKeys; extern const KeyRef bulkLoadJobHistoryPrefix; -const Key bulkLoadJobHistoryKeyFor(const UID& jobId); +Key bulkLoadJobHistoryKeyFor(const UID& jobId); extern const KeyRef bulkDumpModeKey; extern const KeyRangeRef bulkDumpKeys; extern const KeyRef bulkDumpPrefix; -const Value bulkDumpStateValue(const BulkDumpState& bulkDumpState); +Value bulkDumpStateValue(const BulkDumpState& bulkDumpState); BulkDumpState decodeBulkDumpState(const ValueRef& value); +// BulkDump owner tracking - stored separately for backward compatibility +// "\xff/bulkDumpOwner/[[jobId]]" := "[[BulkDumpOwnerInfo]]" +extern const KeyRangeRef bulkDumpOwnerKeys; +extern const KeyRef bulkDumpOwnerPrefix; + +// "\xff/bulkLoadOwner/[[jobId]]" := "[[BulkDumpOwnerInfo]]" (reuses same struct) +extern const KeyRangeRef bulkLoadOwnerKeys; +extern const KeyRef bulkLoadOwnerPrefix; +Key bulkDumpOwnerKeyFor(const UID& jobId); +Key bulkLoadOwnerKeyFor(const UID& jobId); + extern const std::string rangeLockNameForBulkLoad; extern const KeyRangeRef rangeLockKeys; extern const KeyRef rangeLockPrefix; -const Value rangeLockStateSetValue(const RangeLockStateSet& rangeLockStateSet); +Value rangeLockStateSetValue(const RangeLockStateSet& rangeLockStateSet); RangeLockStateSet decodeRangeLockStateSet(const ValueRef& value); extern const KeyRangeRef rangeLockOwnerKeys; extern const KeyRef rangeLockOwnerPrefix; -const Key rangeLockOwnerKeyFor(const RangeLockOwnerName& ownerUniqueID); -const Value rangeLockOwnerValue(const RangeLockOwner& rangeLockOwner); +Key rangeLockOwnerKeyFor(const RangeLockOwnerName& ownerUniqueID); +Value rangeLockOwnerValue(const RangeLockOwner& rangeLockOwner); RangeLockOwner decodeRangeLockOwner(const ValueRef& value); // Keys to view and control tag throttling @@ -536,8 +551,6 @@ extern const KeyRef tagThrottleSignalKey; extern const KeyRef tagThrottleAutoEnabledKey; extern const KeyRef tagThrottleLimitKey; extern const KeyRef tagThrottleCountKey; -extern const KeyRangeRef tagQuotaKeys; -extern const KeyRef tagQuotaPrefix; // Log Range constant variables // Used in the backup pipeline to track mutations @@ -668,7 +681,7 @@ namespace DDIgnore { enum IgnoreType : uint8_t { NONE = 0, REBALANCE_DISK = 1, REBALANCE_READ = 2, ALL = 3 }; } -const Value healthyZoneValue(StringRef const& zoneId, Version version); +Value healthyZoneValue(StringRef const& zoneId, Version version); std::pair decodeHealthyZoneValue(ValueRef const&); // All mutations done to this range are blindly copied into txnStateStore. diff --git a/fdbclient/include/fdbclient/TagThrottle.actor.h b/fdbclient/include/fdbclient/TagThrottle.h similarity index 77% rename from fdbclient/include/fdbclient/TagThrottle.actor.h rename to fdbclient/include/fdbclient/TagThrottle.h index 47ccb818687..6a924f4a34a 100644 --- a/fdbclient/include/fdbclient/TagThrottle.actor.h +++ b/fdbclient/include/fdbclient/TagThrottle.h @@ -1,5 +1,5 @@ /* - * TagThrottle.actor.h + * TagThrottle.h * * This source file is part of the FoundationDB open source project * @@ -19,11 +19,6 @@ */ #include "flow/Arena.h" -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_TAG_THROTTLE_ACTOR_G_H) -#define FDBCLIENT_TAG_THROTTLE_ACTOR_G_H -#include "fdbclient/TagThrottle.actor.g.h" -#elif !defined(FDBCLIENT_TAG_THROTTLE_ACTOR_H) -#define FDBCLIENT_TAG_THROTTLE_ACTOR_H #pragma once @@ -35,7 +30,6 @@ #include "fdbclient/FDBOptions.g.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/CommitTransaction.h" -#include "flow/actorcompiler.h" // This must be the last #include. typedef StringRef TransactionTagRef; typedef Standalone TransactionTag; @@ -247,79 +241,83 @@ namespace ThrottleApi { // The template functions can be called with Native API like DatabaseContext, Transaction/ReadYourWritesTransaction // or using IClientAPI like IDatabase, ITransaction -ACTOR template +template Future> getValidAutoEnabled(Reference tr) { // hold the returned standalone object's memory - state typename Tr::template FutureT> valueF = tr->get(tagThrottleAutoEnabledKey); - Optional value = wait(safeThreadFutureToFuture(valueF)); + typename Tr::template FutureT> valueF = tr->get(tagThrottleAutoEnabledKey); + Optional value = co_await safeThreadFutureToFuture(valueF); if (!value.present()) { - return {}; + co_return Optional(); } else if (value.get() == "1"_sr) { - return true; + co_return true; } else if (value.get() == "0"_sr) { - return false; + co_return false; } else { TraceEvent(SevWarnAlways, "InvalidAutoTagThrottlingValue").detail("Value", value.get()); - return {}; + co_return Optional(); } } -ACTOR template +template Future> getRecommendedTags(Reference db, int limit) { - state Reference tr = db->createTransaction(); - loop { + Reference tr = db->createTransaction(); + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - Optional enableAuto = wait(getValidAutoEnabled(tr)); + Optional enableAuto = co_await getValidAutoEnabled(tr); if (!enableAuto.present()) { tr->reset(); - wait(delay(CLIENT_KNOBS->DEFAULT_BACKOFF)); + co_await delay(CLIENT_KNOBS->DEFAULT_BACKOFF); continue; } else if (enableAuto.get()) { - return std::vector(); + co_return std::vector(); } - state typename DB::TransactionT::template FutureT f = + typename DB::TransactionT::template FutureT f = tr->getRange(KeyRangeRef(tagThrottleAutoKeysPrefix, tagThrottleKeys.end), limit); - RangeResult throttles = wait(safeThreadFutureToFuture(f)); + RangeResult throttles = co_await safeThreadFutureToFuture(f); std::vector results; for (auto throttle : throttles) { results.push_back(TagThrottleInfo(TagThrottleKey::fromKey(throttle.key), TagThrottleValue::fromValue(throttle.value))); } - return results; + co_return results; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR template +template Future> getThrottledTags(Reference db, int limit, ContainsRecommended containsRecommended = ContainsRecommended::False) { - state Reference tr = db->createTransaction(); - state Optional reportAuto; - loop { + Reference tr = db->createTransaction(); + Optional reportAuto; + while (true) { + Error err; try { tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS); - wait(store(reportAuto, getValidAutoEnabled(tr))); + reportAuto = co_await getValidAutoEnabled(tr); if (!reportAuto.present()) { tr->reset(); - wait(delay(CLIENT_KNOBS->DEFAULT_BACKOFF)); + co_await delay(CLIENT_KNOBS->DEFAULT_BACKOFF); continue; } - state typename DB::TransactionT::template FutureT f = tr->getRange( + typename DB::TransactionT::template FutureT f = tr->getRange( reportAuto.get() ? tagThrottleKeys : KeyRangeRef(tagThrottleKeysPrefix, tagThrottleAutoKeysPrefix), limit); - RangeResult throttles = wait(safeThreadFutureToFuture(f)); + RangeResult throttles = co_await safeThreadFutureToFuture(f); std::vector results; for (auto throttle : throttles) { results.push_back(TagThrottleInfo(TagThrottleKey::fromKey(throttle.key), TagThrottleValue::fromValue(throttle.value))); } - return results; + co_return results; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -328,12 +326,12 @@ void signalThrottleChange(Reference tr) { tr->atomicOp(tagThrottleSignalKey, "XXXXXXXXXX\x00\x00\x00\x00"_sr, MutationRef::SetVersionstampedValue); } -ACTOR template +template Future updateThrottleCount(Reference tr, int64_t delta) { - state typename Tr::template FutureT> countVal = tr->get(tagThrottleCountKey); - state typename Tr::template FutureT> limitVal = tr->get(tagThrottleLimitKey); + typename Tr::template FutureT> countVal = tr->get(tagThrottleCountKey); + typename Tr::template FutureT> limitVal = tr->get(tagThrottleLimitKey); - wait(success(safeThreadFutureToFuture(countVal)) && success(safeThreadFutureToFuture(limitVal))); + co_await (success(safeThreadFutureToFuture(countVal)) && success(safeThreadFutureToFuture(limitVal))); int64_t count = 0; int64_t limit = 0; @@ -358,29 +356,29 @@ Future updateThrottleCount(Reference tr, int64_t delta) { writer << count; tr->set(tagThrottleCountKey, writer.toValue()); - return Void(); } -ACTOR template +template Future unthrottleMatchingThrottles(Reference db, KeyRef beginKey, KeyRef endKey, Optional priority, bool onlyExpiredThrottles) { - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); - state KeySelector begin = firstGreaterOrEqual(beginKey); - state KeySelector end = firstGreaterOrEqual(endKey); + KeySelector begin = firstGreaterOrEqual(beginKey); + KeySelector end = firstGreaterOrEqual(endKey); - state bool removed = false; + bool removed = false; - loop { + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + Error err; try { // holds memory of the RangeResult - state typename DB::TransactionT::template FutureT f = tr->getRange(begin, end, 1000); - state RangeResult tags = wait(safeThreadFutureToFuture(f)); - state uint64_t unthrottledTags = 0; + typename DB::TransactionT::template FutureT f = tr->getRange(begin, end, 1000); + RangeResult tags = co_await safeThreadFutureToFuture(f); + uint64_t unthrottledTags = 0; uint64_t manualUnthrottledTags = 0; for (auto tag : tags) { if (onlyExpiredThrottles) { @@ -405,24 +403,25 @@ Future unthrottleMatchingThrottles(Reference db, } if (manualUnthrottledTags > 0) { - wait(updateThrottleCount(tr, -manualUnthrottledTags)); + co_await updateThrottleCount(tr, -manualUnthrottledTags); } if (unthrottledTags > 0) { signalThrottleChange(tr); } - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); if (!tags.more) { - return removed; + co_return removed; } ASSERT(tags.size() > 0); begin = KeySelector(firstGreaterThan(tags[tags.size() - 1].key), tags.arena()); } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } @@ -448,14 +447,14 @@ Future unthrottleAll(Reference db, return unthrottleMatchingThrottles(db, begin, end, priority, false); } -ACTOR template +template Future unthrottleTags(Reference db, TagSet tags, Optional throttleType, Optional priority) { - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); - state std::vector keys; + std::vector keys; for (auto p : allTransactionPriorities) { if (!priority.present() || priority.get() == p) { if (!throttleType.present() || throttleType.get() == TagThrottleType::AUTO) { @@ -467,20 +466,21 @@ Future unthrottleTags(Reference db, } } - state bool removed = false; + bool removed = false; - loop { + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + Error err; try { - state std::vector>> valueFutures; - state std::vector>> values; + std::vector>> valueFutures; + std::vector>> values; values.reserve(keys.size()); for (auto key : keys) { valueFutures.push_back(tr->get(key)); values.push_back(safeThreadFutureToFuture(valueFutures.back())); } - wait(waitForAll(values)); + co_await waitForAll(values); int delta = 0; for (int i = 0; i < values.size(); ++i) { @@ -500,21 +500,22 @@ Future unthrottleTags(Reference db, } if (delta != 0) { - wait(updateThrottleCount(tr, delta)); + co_await updateThrottleCount(tr, delta); } if (removed) { signalThrottleChange(tr); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); } - return removed; + co_return removed; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR template +template Future throttleTags(Reference db, TagSet tags, double tpsRate, @@ -523,8 +524,8 @@ Future throttleTags(Reference db, TransactionPriority priority, Optional expirationTime = Optional(), Optional reason = Optional()) { - state Reference tr = db->createTransaction(); - state Key key = TagThrottleKey(tags, throttleType, priority).toKey(); + Reference tr = db->createTransaction(); + Key key = TagThrottleKey(tags, throttleType, priority).toKey(); ASSERT(initialDuration > 0); @@ -537,17 +538,18 @@ Future throttleTags(Reference db, reason.present() ? reason.get() : TagThrottledReason::UNSET); BinaryWriter wr(IncludeVersion(ProtocolVersion::withTagThrottleValueReason())); wr << throttle; - state Value value = wr.toValue(); + Value value = wr.toValue(); - loop { + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + Error err; try { if (throttleType == TagThrottleType::MANUAL) { // hold the returned standalone object's memory - state typename DB::TransactionT::template FutureT> oldThrottleF = tr->get(key); - Optional oldThrottle = wait(safeThreadFutureToFuture(oldThrottleF)); + typename DB::TransactionT::template FutureT> oldThrottleF = tr->get(key); + Optional oldThrottle = co_await safeThreadFutureToFuture(oldThrottleF); if (!oldThrottle.present()) { - wait(updateThrottleCount(tr, 1)); + co_await updateThrottleCount(tr, 1); } } @@ -557,61 +559,40 @@ Future throttleTags(Reference db, signalThrottleChange(tr); } - wait(safeThreadFutureToFuture(tr->commit())); - return Void(); + co_await safeThreadFutureToFuture(tr->commit()); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -ACTOR template +template Future enableAuto(Reference db, bool enabled) { - state Reference tr = db->createTransaction(); + Reference tr = db->createTransaction(); - loop { + while (true) { tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS); + Error err; try { // hold the returned standalone object's memory - state typename DB::TransactionT::template FutureT> valueF = - tr->get(tagThrottleAutoEnabledKey); - Optional value = wait(safeThreadFutureToFuture(valueF)); + typename DB::TransactionT::template FutureT> valueF = tr->get(tagThrottleAutoEnabledKey); + Optional value = co_await safeThreadFutureToFuture(valueF); if (!value.present() || (enabled && value.get() != "1"_sr) || (!enabled && value.get() != "0"_sr)) { tr->set(tagThrottleAutoEnabledKey, enabled ? "1"_sr : "0"_sr); signalThrottleChange(tr); - wait(safeThreadFutureToFuture(tr->commit())); + co_await safeThreadFutureToFuture(tr->commit()); } - return Void(); + co_return; } catch (Error& e) { - wait(safeThreadFutureToFuture(tr->onError(e))); + err = e; } + co_await safeThreadFutureToFuture(tr->onError(err)); } } -class TagQuotaValue { -public: - int64_t reservedQuota{ 0 }; - int64_t totalQuota{ 0 }; - bool isValid() const; - Value toValue() const; - static TagQuotaValue fromValue(ValueRef); -}; - -Key getTagQuotaKey(TransactionTagRef); - -template -void setTagQuota(Reference tr, TransactionTagRef tag, int64_t reservedQuota, int64_t totalQuota) { - TagQuotaValue tagQuotaValue; - tagQuotaValue.reservedQuota = reservedQuota; - tagQuotaValue.totalQuota = totalQuota; - if (!tagQuotaValue.isValid()) { - throw invalid_throttle_quota_value(); - } - tr->set(getTagQuotaKey(tag), tagQuotaValue.toValue()); - signalThrottleChange(tr); -} - }; // namespace ThrottleApi template @@ -622,6 +603,3 @@ using PrioritizedTransactionTagMap = std::map using UIDTransactionTagMap = std::unordered_map>; - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/TaskBucket.h b/fdbclient/include/fdbclient/TaskBucket.h index d3f67ff9169..b470068eed2 100644 --- a/fdbclient/include/fdbclient/TaskBucket.h +++ b/fdbclient/include/fdbclient/TaskBucket.h @@ -28,9 +28,9 @@ #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" -#include "fdbclient/RunRYWTransaction.actor.h" +#include "fdbclient/RunRYWTransaction.h" #include "fdbclient/Subspace.h" -#include "fdbclient/KeyBackedTypes.actor.h" +#include "fdbclient/KeyBackedTypes.h" class FutureBucket; class TaskFuture; @@ -56,7 +56,7 @@ FDB_BOOLEAN_PARAM(UpdateParams); // same Task class Task : public ReferenceCounted { public: - Task(Value type = StringRef(), uint32_t version = 0, Value done = StringRef(), unsigned int priority = 0); + explicit Task(Value type = StringRef(), uint32_t version = 0, Value done = StringRef(), unsigned int priority = 0); ~Task() {}; // Methods that safely read values from task's params @@ -102,7 +102,7 @@ class Task : public ReferenceCounted { template class TaskParam { public: - TaskParam(StringRef key) : key(key) {} + explicit(false) TaskParam(StringRef key) : key(key) {} T get(Reference task) const { return TupleCodec::unpack(task->params[key]); } void set(Reference task, T const& val) const { task->params[key] = TupleCodec::pack(val); } bool exists(Reference task) const { return task->params.find(key) != task->params.end(); } @@ -139,10 +139,10 @@ class FutureBucket; // instance may declare the Task a failure and move it back to the available subspace. class TaskBucket : public ReferenceCounted { public: - TaskBucket(const Subspace& subspace, - AccessSystemKeys = AccessSystemKeys::False, - PriorityBatch = PriorityBatch::False, - LockAware = LockAware::False); + explicit TaskBucket(const Subspace& subspace, + AccessSystemKeys = AccessSystemKeys::False, + PriorityBatch = PriorityBatch::False, + LockAware = LockAware::False); virtual ~TaskBucket(); void setOptions(Reference tr) { @@ -311,7 +311,9 @@ class TaskFuture; class FutureBucket : public ReferenceCounted { public: - FutureBucket(const Subspace& subspace, AccessSystemKeys = AccessSystemKeys::False, LockAware = LockAware::False); + explicit FutureBucket(const Subspace& subspace, + AccessSystemKeys = AccessSystemKeys::False, + LockAware = LockAware::False); virtual ~FutureBucket(); void setOptions(Reference tr) { @@ -350,7 +352,7 @@ class FutureBucket : public ReferenceCounted { class TaskFuture : public ReferenceCounted { public: TaskFuture(); - TaskFuture(const Reference bucket, Standalone key = Standalone()); + explicit TaskFuture(const Reference bucket, Standalone key = Standalone()); virtual ~TaskFuture(); Future isSet(Reference tr); @@ -499,8 +501,8 @@ struct TaskCompletionKey { TaskCompletionKey() {} private: - TaskCompletionKey(Reference f) : joinFuture(f) {} - TaskCompletionKey(Key k) : key(k) {} + explicit TaskCompletionKey(Reference f) : joinFuture(f) {} + explicit TaskCompletionKey(Key k) : key(k) {} }; #endif diff --git a/fdbclient/include/fdbclient/TestKnobCollection.h b/fdbclient/include/fdbclient/TestKnobCollection.h deleted file mode 100644 index 2a88616fa63..00000000000 --- a/fdbclient/include/fdbclient/TestKnobCollection.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * TestKnobCollection.h - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2013-2026 Apple Inc. and the FoundationDB project authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FDBCLIENT_TESTKNOBCOLLECTION_H -#define FDBCLIENT_TESTKNOBCOLLECTION_H - -#pragma once - -#include "fdbclient/IKnobCollection.h" -#include "fdbclient/ServerKnobCollection.h" - -class TestKnobs : public KnobsImpl { -public: - TestKnobs(); - - // Nonatomic test knobs - int64_t TEST_LONG; - int TEST_INT; - double TEST_DOUBLE; - bool TEST_BOOL; - std::string TEST_STRING; - - bool operator==(TestKnobs const&) const; - bool operator!=(TestKnobs const&) const; - void initialize(); -}; - -/* - * Stores both flow knobs, client knobs, server knobs, and test knobs. As the - * name implies, this class is only meant to be used for testing. - */ -class TestKnobCollection : public IKnobCollection { - ServerKnobCollection serverKnobCollection; - TestKnobs testKnobs; - -public: - TestKnobCollection(Randomize randomize, IsSimulated isSimulated); - void initialize(Randomize randomize, IsSimulated isSimulated) override; - void reset(Randomize randomize, IsSimulated isSimulated) override; - FlowKnobs const& getFlowKnobs() const override { return serverKnobCollection.getFlowKnobs(); } - ClientKnobs const& getClientKnobs() const override { return serverKnobCollection.getClientKnobs(); } - ServerKnobs const& getServerKnobs() const override { return serverKnobCollection.getServerKnobs(); } - TestKnobs const& getTestKnobs() const override { return testKnobs; } - void clearTestKnobs() override; - Optional tryParseKnobValue(std::string const& knobName, std::string const& knobValue) const override; - bool trySetKnob(std::string const& knobName, KnobValueRef const& knobValue) override; -}; - -#endif // FDBCLIENT_TESTKNOBCOLLECTION_H diff --git a/fdbclient/include/fdbclient/ThreadSafeTransaction.h b/fdbclient/include/fdbclient/ThreadSafeTransaction.h index 23b65fa7ab4..94699beefb7 100644 --- a/fdbclient/include/fdbclient/ThreadSafeTransaction.h +++ b/fdbclient/include/fdbclient/ThreadSafeTransaction.h @@ -71,7 +71,7 @@ class ThreadSafeDatabase : public IDatabase, public ThreadSafeReferenceCounted(false)) {} void operator=(ThreadSafeTransaction&& r) noexcept; - ThreadSafeTransaction(ThreadSafeTransaction&& r) noexcept; + explicit(false) ThreadSafeTransaction(ThreadSafeTransaction&& r) noexcept; void reset() override; diff --git a/fdbclient/include/fdbclient/Tracing.h b/fdbclient/include/fdbclient/Tracing.h index cc8321a7d5e..ba4a274a719 100644 --- a/fdbclient/include/fdbclient/Tracing.h +++ b/fdbclient/include/fdbclient/Tracing.h @@ -50,7 +50,7 @@ struct SpanContext { SpanContext() : traceID(UID()), spanID(0), m_Flags(TraceFlags::unsampled) {} SpanContext(UID traceID, uint64_t spanID, TraceFlags flags) : traceID(traceID), spanID(spanID), m_Flags(flags) {} SpanContext(UID traceID, uint64_t spanID) : traceID(traceID), spanID(spanID), m_Flags(TraceFlags::unsampled) {} - SpanContext(const SpanContext& span) = default; + explicit(false) SpanContext(const SpanContext& span) = default; bool isSampled() const { return (m_Flags & TraceFlags::sampled) == TraceFlags::sampled; } std::string toString() const { return format("%016llx%016llx%016llx", traceID.first(), traceID.second(), spanID); }; bool isValid() const { return traceID.first() != 0 && traceID.second() != 0 && spanID != 0; } @@ -160,7 +160,7 @@ class Span { explicit Span(const Location& location) : Span(location, SpanContext()) {} Span(const Span&) = delete; - Span(Span&& o) { + explicit(false) Span(Span&& o) { arena = std::move(o.arena); context = o.context; location = o.location; diff --git a/fdbclient/include/fdbclient/TransactionLineage.h b/fdbclient/include/fdbclient/TransactionLineage.h index ec0d439755c..e1ba1a040d7 100644 --- a/fdbclient/include/fdbclient/TransactionLineage.h +++ b/fdbclient/include/fdbclient/TransactionLineage.h @@ -108,7 +108,8 @@ class ScopedLineage { } getCurrentLineage()->modify(member) = before; } - ScopedLineage(ScopedLineage&& o) : before(std::move(o.before)), member(o.member), valid(o.valid) { + explicit(false) ScopedLineage(ScopedLineage&& o) + : before(std::move(o.before)), member(o.member), valid(o.valid) { o.release(); } ScopedLineage& operator=(ScopedLineage&& o) { diff --git a/fdbclient/include/fdbclient/Tuple.h b/fdbclient/include/fdbclient/Tuple.h index 76e102a48b3..52ea0bf417b 100644 --- a/fdbclient/include/fdbclient/Tuple.h +++ b/fdbclient/include/fdbclient/Tuple.h @@ -121,7 +121,7 @@ struct Tuple { } private: - Tuple(const StringRef& data, bool exclude_incomplete = false, bool exclude_user_type = false); + explicit Tuple(const StringRef& data, bool exclude_incomplete = false, bool exclude_user_type = false); Standalone> data; std::vector offsets; }; diff --git a/fdbclient/include/fdbclient/TupleVersionstamp.h b/fdbclient/include/fdbclient/TupleVersionstamp.h index bfcb6a97f43..4c6e7f16ef7 100644 --- a/fdbclient/include/fdbclient/TupleVersionstamp.h +++ b/fdbclient/include/fdbclient/TupleVersionstamp.h @@ -33,7 +33,7 @@ struct TupleVersionstamp { "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00"_sr; TupleVersionstamp() : data(DEFAULT_VERSIONSTAMP) {} - TupleVersionstamp(StringRef); + explicit TupleVersionstamp(StringRef); TupleVersionstamp(int64_t version, uint16_t batchNumber, uint16_t userVersion = 0); int64_t getVersion() const; diff --git a/fdbclient/include/fdbclient/VersionVector.h b/fdbclient/include/fdbclient/VersionVector.h index 112cfb4284b..57160149a0e 100644 --- a/fdbclient/include/fdbclient/VersionVector.h +++ b/fdbclient/include/fdbclient/VersionVector.h @@ -43,7 +43,7 @@ struct VersionVector { // version in the "versions" map.) VersionVector() : maxVersion(invalidVersion), cachedEncodedSize(InvalidEncodedSize) {} - VersionVector(Version version) : maxVersion(version), cachedEncodedSize(InvalidEncodedSize) {} + explicit VersionVector(Version version) : maxVersion(version), cachedEncodedSize(InvalidEncodedSize) {} private: // Only invoked by getDelta() and applyDelta(), where tag has been validated diff --git a/fdbclient/include/fdbclient/VersionedMap.actor.h b/fdbclient/include/fdbclient/VersionedMap.actor.h deleted file mode 100644 index 6be5db37718..00000000000 --- a/fdbclient/include/fdbclient/VersionedMap.actor.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * VersionedMap.actor.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 - -// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source -// version. -#if defined(NO_INTELLISENSE) && !defined(FDBCLIENT_VERSIONEDMAP_ACTOR_G_H) -#define FDBCLIENT_VERSIONEDMAP_ACTOR_G_H -#include "fdbclient/VersionedMap.actor.g.h" -#elif !defined(FDBCLIENT_VERSIONEDMAP_ACTOR_H) -#define FDBCLIENT_VERSIONEDMAP_ACTOR_H - -#include "flow/flow.h" -#include "flow/actorcompiler.h" // This must be the last #include. - -ACTOR template -Future deferredCleanupActor(std::vector toFree, TaskPriority taskID = TaskPriority::DefaultYield) { - state int freeCount = 0; - while (!toFree.empty()) { - Tree a = std::move(toFree.back()); - toFree.pop_back(); - - for (int c = 0; c < 3; c++) { - if (a->pointer[c] && a->pointer[c]->isSoleOwner()) - toFree.push_back(std::move(a->pointer[c])); - } - - if (++freeCount % 100 == 0) - wait(yield(taskID)); - } - - return Void(); -} - -#include "flow/unactorcompiler.h" -#endif diff --git a/fdbclient/include/fdbclient/VersionedMap.h b/fdbclient/include/fdbclient/VersionedMap.h index 2eb1db58fd7..02f036bba37 100644 --- a/fdbclient/include/fdbclient/VersionedMap.h +++ b/fdbclient/include/fdbclient/VersionedMap.h @@ -26,7 +26,55 @@ #include "flow/IndexedSet.h" #include "fdbclient/FDBTypes.h" #include "flow/IRandom.h" -#include "fdbclient/VersionedMap.actor.h" +#include + +// Drains old PTree roots without recursive Reference destruction, including if +// the cleanup coroutine is cancelled while suspended at yield(). +template +class DeferredCleanupWorklist { +public: + explicit DeferredCleanupWorklist(std::vector&& toFree) : toFree(std::move(toFree)) {} + ~DeferredCleanupWorklist() { drain(); } + + bool empty() const { return toFree.empty(); } + void cleanupOne() { + Tree a = std::move(toFree.back()); + toFree.pop_back(); + + auto* node = a.extractPtr(); + if (node == nullptr || !node->delref_no_destroy()) { + return; + } + + for (auto& child : node->pointer) { + if (child) { + toFree.push_back(std::move(child)); + } + } + delete node; + } + +private: + void drain() { + while (!toFree.empty()) { + cleanupOne(); + } + } + + std::vector toFree; +}; + +template +Future deferredCleanupActor(std::vector toFree, TaskPriority taskID = TaskPriority::DefaultYield) { + DeferredCleanupWorklist cleanup(std::move(toFree)); + int freeCount = 0; + while (!cleanup.empty()) { + cleanup.cleanupOne(); + + if (++freeCount % 100 == 0) + co_await yield(taskID); + } +} // PTree is a persistent balanced binary tree implementation. It is based on a treap as a way to guarantee O(1) space // for node insertion (rotating is asymptotically cheap), but the constant factors are very large. @@ -63,6 +111,9 @@ struct PTree : public ReferenceCounted>, FastAllocated>, NonCo PTree(const T& data, Version ver) : lastUpdateVersion(ver), updated(false), data(data) { priority = deterministicRandom()->randomUInt32(); } + PTree(const T& data, Version ver, IRandom& random) : lastUpdateVersion(ver), updated(false), data(data) { + priority = random.randomUInt32(); + } PTree(uint32_t pri, T const& data, Reference const& left, Reference const& right, Version ver) : priority(pri), lastUpdateVersion(ver), updated(false), data(data) { pointer[0] = left; @@ -70,7 +121,7 @@ struct PTree : public ReferenceCounted>, FastAllocated>, NonCo } private: - PTree(PTree const&); + explicit(false) PTree(PTree const&); }; template @@ -79,8 +130,8 @@ class PTreeFinger { // This finger size supports trees with up to exp(96/4.3) ~= 4,964,514,749 entries. // The number 4.3 comes from here: https://en.wikipedia.org/wiki/Random_binary_tree#The_longest_path // see also: check(). - static constexpr size_t N = 96; - PTreeFingerEntry entries_[N]; + static constexpr size_t pathCapacity = 96; + PTreeFingerEntry entries_[pathCapacity]; size_t size_ = 0; size_t bound_sz_ = 0; @@ -88,8 +139,8 @@ class PTreeFinger { PTreeFinger() {} // Explicit copy constructors ensure we copy the live values in entries_. - PTreeFinger(PTreeFinger const& f) { *this = f; } - PTreeFinger(PTreeFinger&& f) { *this = f; } + explicit(false) PTreeFinger(PTreeFinger const& f) { *this = f; } + explicit(false) PTreeFinger(PTreeFinger&& f) { *this = f; } PTreeFinger& operator=(PTreeFinger const& f) { size_ = f.size_; @@ -113,12 +164,12 @@ class PTreeFinger { void resize(size_t sz) { size_ = sz; - ASSERT(size_ < N); + ASSERT(size_ < pathCapacity); } void push_back(PTree const* node) { entries_[size_++] = { node }; - ASSERT(size_ < N); + ASSERT(size_ < pathCapacity); } void push_for_bound(PTree const* node, bool less) { @@ -279,19 +330,19 @@ T get(PTreeFinger& f) { return f.back()->data; } -// Modifies p to point to a PTree with x inserted +// Modifies p to point to a PTree with x inserted, using a caller-owned priority generator. template -void insert(Reference>& p, Version at, const T& x) { +void insert(Reference>& p, Version at, const T& x, IRandom& priorityRandom) { if (!p) { - p = makeReference>(x, at); + p = makeReference>(x, at, priorityRandom); } else { int c = ::compare(x, p->data); if (c == 0) { p = makeReference>(p->priority, x, p->left(at), p->right(at), at); } else { - const bool direction = !(c < 0); + const bool direction = c >= 0; Reference> child = p->child(direction, at); - insert(child, at, x); + insert(child, at, x, priorityRandom); p = update(p, direction, child, at); if (p->child(direction, at)->priority > p->priority) rotate(p, at, !direction); @@ -299,6 +350,13 @@ void insert(Reference>& p, Version at, const T& x) { } } +// Modifies p to point to a PTree with x inserted. +template +void insert(Reference>& p, Version at, const T& x) { + auto priorityRandom = deterministicRandom(); + insert(p, at, x, *priorityRandom); +} + template Reference> firstNode(const Reference>& p, Version at) { if (!p) @@ -338,12 +396,16 @@ void last(const Reference>& p, Version at, PTreeFinger& f) { // modifies p to point to a PTree with the root of p removed template void removeRoot(Reference>& p, Version at) { - if (!p->right(at)) + const auto& right = p->right(at); + if (!right) p = p->left(at); - else if (!p->left(at)) - p = p->right(at); else { - bool direction = p->right(at)->priority < p->left(at)->priority; + const auto& left = p->left(at); + if (!left) { + p = right; + return; + } + bool direction = right->priority < left->priority; rotate(p, at, direction); Reference> child = p->child(direction, at); removeRoot(child, at); @@ -473,11 +535,13 @@ void demoteRoot(Reference>& p, Version at) { ASSERT(false); uint32_t priority[2]; - for (int i = 0; i < 2; i++) - if (p->child(i, at)) - priority[i] = p->child(i, at)->priority; + for (int i = 0; i < 2; i++) { + const auto& child = p->child(i, at); + if (child) + priority[i] = child->priority; else priority[i] = 0; + } bool higherDirection = priority[1] > priority[0]; @@ -498,7 +562,7 @@ Reference> append(const Reference>& left, const Reference> r = makeReference>(lastNode(left, at)->data, at); + auto r = makeReference>(lastNode(left, at)->data, at); if (EXPENSIVE_VALIDATION) { ASSERT(r->data < firstNode(right, at)->data); } @@ -613,10 +677,13 @@ void check(const Reference>& p) { // This essentially gets rid of node versions that will never be read (beyond 5s worth of versions) // TODO look into making this per-version compaction. (We could keep track of updated nodes at each version for example) template -void compact(Reference>& p, Version newOldestVersion) { +void compact(Reference>& p, Version newOldestVersion, std::unordered_set*>& visited) { if (!p) { return; } + if (!visited.insert(p.getPtr()).second) { + return; + } if (p->updated && p->lastUpdateVersion <= newOldestVersion) { /* If the node has been updated, figure out which pointer was replaced. And replace that pointer with the updated pointer. Then we can get rid of the updated child pointer and then make room in the node for future @@ -629,8 +696,14 @@ void compact(Reference>& p, Version newOldestVersion) { } Reference> left = p->left(newOldestVersion); Reference> right = p->right(newOldestVersion); - compact(left, newOldestVersion); - compact(right, newOldestVersion); + compact(left, newOldestVersion, visited); + compact(right, newOldestVersion, visited); +} + +template +void compact(Reference>& p, Version newOldestVersion) { + std::unordered_set*> visited; + compact(p, newOldestVersion, visited); } } // namespace PTreeImpl @@ -670,6 +743,7 @@ class VersionedMap : NonCopyable { typedef Reference Tree; Version oldestVersion, latestVersion; + Reference priorityRandom; // This deque keeps track of PTree root nodes at various versions. Since the // versions increase monotonically, the deque is implicitly sorted and hence @@ -691,12 +765,16 @@ class VersionedMap : NonCopyable { static const int overheadPerItem = nextFastAllocatedSize(sizeof(PTreeT)) * 4; struct iterator; - VersionedMap() : oldestVersion(0), latestVersion(0) { roots.emplace_back(0, Tree()); } - VersionedMap(VersionedMap&& v) noexcept - : oldestVersion(v.oldestVersion), latestVersion(v.latestVersion), roots(std::move(v.roots)) {} + VersionedMap() : oldestVersion(0), latestVersion(0), priorityRandom(deterministicRandom()) { + roots.emplace_back(0, Tree()); + } + explicit(false) VersionedMap(VersionedMap&& v) noexcept + : oldestVersion(v.oldestVersion), latestVersion(v.latestVersion), priorityRandom(std::move(v.priorityRandom)), + roots(std::move(v.roots)) {} void operator=(VersionedMap&& v) noexcept { oldestVersion = v.oldestVersion; latestVersion = v.latestVersion; + priorityRandom = std::move(v.priorityRandom); roots = std::move(v.roots); } @@ -770,8 +848,10 @@ class VersionedMap : NonCopyable { // insert() and erase() invalidate atLatest() and all iterators into it void insert(const K& k, const T& t) { insert(k, t, latestVersion); } void insert(const K& k, const T& t, Version insertAt) { - PTreeImpl::insert( - roots.back().second, latestVersion, MapPair>(k, std::make_pair(t, insertAt))); + PTreeImpl::insert(roots.back().second, + latestVersion, + MapPair>(k, std::make_pair(t, insertAt)), + *priorityRandom); } void erase(const K& begin, const K& end) { PTreeImpl::remove(roots.back().second, latestVersion, begin, end); } void erase(const K& key) { // key must be present @@ -790,9 +870,10 @@ class VersionedMap : NonCopyable { ASSERT(newOldestVersion <= latestVersion); // auto newBegin = roots.lower_bound(newOldestVersion); auto newBegin = lower_bound(roots.begin(), roots.end(), newOldestVersion, rootsComparator()); + std::unordered_set visited; for (auto root = roots.begin(); root != newBegin; ++root) { if (root->second) - PTreeImpl::compact(root->second, newOldestVersion); + PTreeImpl::compact(root->second, newOldestVersion, visited); } // printf("\nPrinting the tree at latest version after compaction.\n"); // PTreeImpl::printTreeDetails(roots.back().second(), 0); @@ -843,60 +924,127 @@ class VersionedMap : NonCopyable { PTreeFingerT finger; }; + static iterator beginAt(Tree const& root, Version at) { + iterator i(root, at); + PTreeImpl::first(root, at, i.finger); + return i; + } + + static iterator endAt(Tree const& root, Version at) { return iterator(root, at); } + + // Returns x such that key==*x, or end() + template + static iterator findAt(Tree const& root, Version at, const X& key) { + iterator i(root, at); + PTreeImpl::lower_bound(root, at, key, i.finger); + if (i && i.key() == key) + return i; + else + return endAt(root, at); + } + + // Returns the smallest x such that *x>=key, or end() + template + static iterator lowerBoundAt(Tree const& root, Version at, const X& key) { + iterator i(root, at); + PTreeImpl::lower_bound(root, at, key, i.finger); + return i; + } + + // Returns the smallest x such that *x>key, or end() + template + static iterator upperBoundAt(Tree const& root, Version at, const X& key) { + iterator i(root, at); + PTreeImpl::upper_bound(root, at, key, i.finger); + return i; + } + + // Returns the largest x such that *x<=key, or end() + template + static iterator lastLessOrEqualAt(Tree const& root, Version at, const X& key) { + iterator i(root, at); + PTreeImpl::upper_bound(root, at, key, i.finger); + --i; + return i; + } + + // Returns the largest x such that *x + static iterator lastLessAt(Tree const& root, Version at, const X& key) { + iterator i(root, at); + PTreeImpl::lower_bound(root, at, key, i.finger); + --i; + return i; + } + + iterator latestBegin() const { return beginAt(roots.back().second, latestVersion); } + iterator latestEnd() const { return endAt(roots.back().second, latestVersion); } + + // Returns x such that key==*x, or latestEnd() + template + iterator latestFind(const X& key) const { + return findAt(roots.back().second, latestVersion, key); + } + + // Returns the smallest x such that *x>=key, or latestEnd() + template + iterator latestLowerBound(const X& key) const { + return lowerBoundAt(roots.back().second, latestVersion, key); + } + + // Returns the smallest x such that *x>key, or latestEnd() + template + iterator latestUpperBound(const X& key) const { + return upperBoundAt(roots.back().second, latestVersion, key); + } + + // Returns the largest x such that *x<=key, or latestEnd() + template + iterator latestLastLessOrEqual(const X& key) const { + return lastLessOrEqualAt(roots.back().second, latestVersion, key); + } + + // Returns the largest x such that *x + iterator latestLastLess(const X& key) const { + return lastLessAt(roots.back().second, latestVersion, key); + } + class ViewAtVersion { public: ViewAtVersion(Tree const& root, Version at) : root(root), at(at) {} - iterator begin() const { - iterator i(root, at); - PTreeImpl::first(root, at, i.finger); - return i; - } - iterator end() const { return iterator(root, at); } + iterator begin() const { return VersionedMap::beginAt(root, at); } + iterator end() const { return VersionedMap::endAt(root, at); } // Returns x such that key==*x, or end() template iterator find(const X& key) const { - iterator i(root, at); - PTreeImpl::lower_bound(root, at, key, i.finger); - if (i && i.key() == key) - return i; - else - return end(); + return VersionedMap::findAt(root, at, key); } // Returns the smallest x such that *x>=key, or end() template iterator lower_bound(const X& key) const { - iterator i(root, at); - PTreeImpl::lower_bound(root, at, key, i.finger); - return i; + return VersionedMap::lowerBoundAt(root, at, key); } // Returns the smallest x such that *x>key, or end() template iterator upper_bound(const X& key) const { - iterator i(root, at); - PTreeImpl::upper_bound(root, at, key, i.finger); - return i; + return VersionedMap::upperBoundAt(root, at, key); } // Returns the largest x such that *x<=key, or end() template iterator lastLessOrEqual(const X& key) const { - iterator i(root, at); - PTreeImpl::upper_bound(root, at, key, i.finger); - --i; - return i; + return VersionedMap::lastLessOrEqualAt(root, at, key); } // Returns the largest x such that *x iterator lastLess(const X& key) const { - iterator i(root, at); - PTreeImpl::lower_bound(root, at, key, i.finger); - --i; - return i; + return VersionedMap::lastLessAt(root, at, key); } void validate() { diff --git a/fdbclient/include/fdbclient/WriteMap.h b/fdbclient/include/fdbclient/WriteMap.h index 8521fd64de6..2e4efe2d766 100644 --- a/fdbclient/include/fdbclient/WriteMap.h +++ b/fdbclient/include/fdbclient/WriteMap.h @@ -66,6 +66,12 @@ class OperationStack { bool operator==(const OperationStack& r) const; }; +FDB_BOOLEAN_PARAM(FollowingKeysCleared); +FDB_BOOLEAN_PARAM(FollowingKeysConflict); +FDB_BOOLEAN_PARAM(IsConflict); +FDB_BOOLEAN_PARAM(FollowingKeysUnreadable); +FDB_BOOLEAN_PARAM(IsUnreadable); + struct WriteMapEntry { KeyRef key; OperationStack stack; @@ -77,11 +83,11 @@ struct WriteMapEntry { WriteMapEntry(KeyRef const& key, OperationStack&& stack, - bool following_keys_cleared, - bool following_keys_conflict, - bool is_conflict, - bool following_keys_unreadable, - bool is_unreadable) + FollowingKeysCleared following_keys_cleared, + FollowingKeysConflict following_keys_conflict, + IsConflict is_conflict, + FollowingKeysUnreadable following_keys_unreadable, + IsUnreadable is_unreadable) : key(key), stack(std::move(stack)), following_keys_cleared(following_keys_cleared), following_keys_conflict(following_keys_conflict), is_conflict(is_conflict), following_keys_unreadable(following_keys_unreadable), is_unreadable(is_unreadable) {} @@ -127,14 +133,36 @@ class WriteMap { public: explicit WriteMap(Arena* arena) : arena(arena), writeMapEmpty(true), ver(-1), scratch_iterator(this) { - PTreeImpl::insert( - writes, ver, WriteMapEntry(allKeys.begin, OperationStack(), false, false, false, false, false)); - PTreeImpl::insert(writes, ver, WriteMapEntry(allKeys.end, OperationStack(), false, false, false, false, false)); - PTreeImpl::insert( - writes, ver, WriteMapEntry(afterAllKeys, OperationStack(), false, false, false, false, false)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(allKeys.begin, + OperationStack(), + FollowingKeysCleared::False, + FollowingKeysConflict::False, + IsConflict::False, + FollowingKeysUnreadable::False, + IsUnreadable::False)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(allKeys.end, + OperationStack(), + FollowingKeysCleared::False, + FollowingKeysConflict::False, + IsConflict::False, + FollowingKeysUnreadable::False, + IsUnreadable::False)); + PTreeImpl::insert(writes, + ver, + WriteMapEntry(afterAllKeys, + OperationStack(), + FollowingKeysCleared::False, + FollowingKeysConflict::False, + IsConflict::False, + FollowingKeysUnreadable::False, + IsUnreadable::False)); } - WriteMap(WriteMap&& r) noexcept + explicit(false) WriteMap(WriteMap&& r) noexcept : arena(r.arena), writeMapEmpty(r.writeMapEmpty), writes(std::move(r.writes)), ver(r.ver), scratch_iterator(std::move(r.scratch_iterator)) {} diff --git a/fdbclient/include/fdbclient/json_spirit/json_spirit_reader_template.h b/fdbclient/include/fdbclient/json_spirit/json_spirit_reader_template.h index 9cd03ce9d31..203d6ecbf06 100644 --- a/fdbclient/include/fdbclient/json_spirit/json_spirit_reader_template.h +++ b/fdbclient/include/fdbclient/json_spirit/json_spirit_reader_template.h @@ -205,7 +205,7 @@ class Semantic_actions { typedef typename Config_type::Array_type Array_type; typedef typename String_type::value_type Char_type; - Semantic_actions(Value_type& value) : value_(value), current_p_(0) {} + explicit Semantic_actions(Value_type& value) : value_(value), current_p_(0) {} void begin_obj(Char_type c) { assert(c == '{'); @@ -335,7 +335,7 @@ class Json_grammar : public spirit_namespace::grammar Semantic_actions_t; - Json_grammar(Semantic_actions_t& semantic_actions) : actions_(semantic_actions) {} + explicit Json_grammar(Semantic_actions_t& semantic_actions) : actions_(semantic_actions) {} static void throw_not_value(Iter_type begin, Iter_type end) { throw_error(begin, "not a value"); } @@ -352,7 +352,7 @@ class Json_grammar : public spirit_namespace::grammar class definition { public: - definition(const Json_grammar& self) { + explicit definition(const Json_grammar& self) { using namespace spirit_namespace; typedef typename Value_type::String_type::value_type Char_type; @@ -437,7 +437,7 @@ struct Multi_pass_iters { typedef std::istream_iterator istream_iter; typedef spirit_namespace::multi_pass Mp_iter; - Multi_pass_iters(Istream_type& is) { + explicit Multi_pass_iters(Istream_type& is) { is.unsetf(std::ios::skipws); begin_ = spirit_namespace::make_multi_pass(istream_iter(is)); diff --git a/fdbclient/include/fdbclient/json_spirit/json_spirit_value.h b/fdbclient/include/fdbclient/json_spirit/json_spirit_value.h index a37fc000bd0..b82ac9c32c0 100644 --- a/fdbclient/include/fdbclient/json_spirit/json_spirit_value.h +++ b/fdbclient/include/fdbclient/json_spirit/json_spirit_value.h @@ -45,23 +45,24 @@ class Value_impl { typedef typename String_type::const_pointer Const_str_ptr; // eg const char* Value_impl(); // creates null value - Value_impl(Const_str_ptr value); - Value_impl(const String_type& value); - Value_impl(const Object& value); - Value_impl(const Array& value); - Value_impl(bool value); - Value_impl(int value); - Value_impl(int64_t value); - Value_impl(uint64_t value); - Value_impl(double value); + explicit(false) Value_impl(Const_str_ptr value); + explicit(false) Value_impl(const String_type& value); + explicit(false) Value_impl(const Object& value); + explicit(false) Value_impl(const Array& value); + explicit(false) Value_impl(bool value); + explicit(false) Value_impl(int value); + explicit(false) Value_impl(int64_t value); + explicit(false) Value_impl(uint64_t value); + explicit(false) Value_impl(double value); template Value_impl(Iter first, Iter last); // constructor from containers, e.g. std::vector or std::list template - Value_impl(const boost::variant& variant); // constructor for compatible variant types + explicit(false) Value_impl( + const boost::variant& variant); // constructor for compatible variant types - Value_impl(const Value_impl& other); + explicit(false) Value_impl(const Value_impl& other); bool operator==(const Value_impl& lhs) const; diff --git a/fdbclient/sha1/SHA1.cpp b/fdbclient/sha1/SHA1.cpp index af31c019186..5ce7380d09d 100644 --- a/fdbclient/sha1/SHA1.cpp +++ b/fdbclient/sha1/SHA1.cpp @@ -13,10 +13,8 @@ -- Volker Grabsch */ -#include "fdbclient/sha1/SHA1.h" +#include "SHA1.h" #include -#include -#include /* Help macros */ #define SHA1_ROL(value, bits) (((value) << (bits)) | (((value) & 0xffffffff) >> (32 - (bits)))) diff --git a/fdbclient/include/fdbclient/sha1/SHA1.h b/fdbclient/sha1/SHA1.h similarity index 100% rename from fdbclient/include/fdbclient/sha1/SHA1.h rename to fdbclient/sha1/SHA1.h diff --git a/fdbclient/tests/gcp_fixture.sh b/fdbclient/tests/gcp_fixture.sh new file mode 100644 index 00000000000..af6b56247fd --- /dev/null +++ b/fdbclient/tests/gcp_fixture.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Functions for dealing w/ gcp/GCS +# +# Here is how to use this fixture: +# +# - set GCS_FDB_BUCKET and GCS_APPLICATION_TOKEN +# +# # First source this fixture. +# if ! source "${cwd}/gcp_fixture.sh"; then +# err "Failed to source gcp_fixture.sh" +# exit 1 +# fi +# # Save off the return from create_gcp_dir. You'll need it for shutdown. +# if ! TEST_SCRATCH_DIR=$( create_gcp_dir "${scratch_dir}" ); then +# err "Failed creating local gcp_dir" +# exit 1 +# fi +# readonly TEST_SCRATCH_DIR +# if ! readarray -t configs < <(gcp_setup "${build_dir}" "${TEST_SCRATCH_DIR}"); then +# err "Failed gcp_setup" +# return 1 +# fi +# readonly host="${configs[0]}" +# ...etc. +# # When done, call shutdown_gcp +# shutdown_gcp "${TEST_SCRATCH_DIR}" +# + +# Cleanup any mess we've made. For calling from signal trap on exit. +# $1 The gcp scratch directory to clean up on exit. +function shutdown_gcp { + local local_scratch_dir="${1}" + if [[ -d "${local_scratch_dir}" ]]; then + rm -rf "${local_scratch_dir}" + fi +} + +# Create directory for gcp test to use writing temporary data. +# Call shutdown_gcp to clean up the directory created here for test. +# $1 Directory where we want to write data and logs. +# check $? error code on return. +function create_gcp_dir { + local dir="${1}" + local gcp_dir + gcp_dir=$(mktemp -d "${dir}/gcp.$$.XXXX") + # Exit if the temp directory wasn't created successfully. + if [[ ! -d "${gcp_dir}" ]]; then + echo "ERROR: Failed create of gcp directory ${gcp_dir}" >&2 + return 1 + fi + echo "${gcp_dir}" +} + +# Write out blob_credentials +# $1 The token to use querying credentials +# $2 Hostname going to gcp +# $3 The scratch dir to write the blob credentials file into +# Echos the blob_credentials file path. +function write_gcp_blob_credentials { + local token="${1}" + local host="${2}" + local dir="${3}" + blob_credentials_str="{\"accounts\": { \"@${host}\": {\"token\": \"${token}\"}}}" + readonly blob_credentials_file="${dir}/blob_credentials.json" + echo "${blob_credentials_str}" > "${blob_credentials_file}" + echo "${blob_credentials_file}" +} + +# Set up GCS access. +# $1 build_dir +# $2 gcp_dir (This is what is returned when you call create_gcp_dir +# so call it first). +# Returns array of configurations to use contacting GCS. +function gcp_setup { + if [ -z "$GCS_APPLICATION_TOKEN" ]; then + echo "Error: GCS_APPLICATION_TOKEN is not set." + exit 1 + fi + if [ -z "$GCS_FDB_BUCKET" ]; then + echo "Error: GCS_FDB_BUCKET is not set." + exit 1 + fi + local local_build_dir="${1}" + local local_gcp_dir="${2}" + + readonly host="storage.googleapis.com" + if ! blob_credentials_file=$(write_gcp_blob_credentials "${GCS_APPLICATION_TOKEN}" "${host}" "${local_gcp_dir}"); then + err "Failed to write credentials file" + exit 1 + fi + results_array=("@${host}" "${GCS_FDB_BUCKET}" "${blob_credentials_file}") + printf "%s\n" "${results_array[@]}" +} diff --git a/fdbclient/tests/s3client_test.sh b/fdbclient/tests/s3client_test.sh index 0c001c53b6f..496e77734c3 100755 --- a/fdbclient/tests/s3client_test.sh +++ b/fdbclient/tests/s3client_test.sh @@ -35,6 +35,7 @@ function filter_http_debug { echo "${output}" | grep -v "Contents of" | grep -v "^$" | grep -v "^[[:space:]]*$" | grep -v "HTTP" | grep -v "^\[.*\]" | grep -v "^Request Header:" | grep -v "^Response Header:" | grep -v "^Response Code:" | grep -v "^Response ContentLen:" | grep -v "^-- RESPONSE CONTENT--" | grep -v "^--------" | grep -v "^" | grep -v "^'$" | grep -v "^++" } + # Run s3client with proper TLS CA file handling # This function handles the case where TLS_CA_FILE might be empty function run_s3client { @@ -250,7 +251,7 @@ function test_nonexistent_bucket { # 1. The command succeeded (status 0) # 2. The output contains the URL header # 3. There are no objects listed (no lines after the header) - if echo "${output}" | grep -q "Contents of" && + if output_contains "${output}" "Contents of" && [[ $(filter_http_debug "${output}" | wc -l) -eq 0 ]]; then # Success - we have the header and no other non-empty lines (excluding HTTP debug lines) return 0 @@ -293,7 +294,7 @@ function test_nonexistent_resource { local filtered_output filtered_output=$(filter_http_debug "${output}") - if ! (echo "${output}" | grep -q "Contents of" && + if ! (output_contains "${output}" "Contents of" && [[ -z "$(echo "${filtered_output}" | tr -d '[:space:]')" ]]); then err "Failed to detect non-existent resource in S3" return 1 @@ -307,8 +308,8 @@ function test_nonexistent_resource { # 1. The command succeeded (status 0) # 2. The output contains the URL header # 3. There are no actual object listings (no lines starting with FILE or DIR) - if echo "${output}" | grep -q "Contents of" && - ! echo "${output}" | grep -q "^FILE\|^DIR"; then + if output_contains "${output}" "Contents of" && + ! output_matches "${output}" "^FILE\|^DIR"; then # Success - we have the header and no object listings return 0 else @@ -367,8 +368,8 @@ function test_empty_bucket { local filtered_output filtered_output=$(filter_http_debug "${output}") - if echo "${output}" | grep -q "No objects found" || - (echo "${output}" | grep -q "Contents of" && + if output_contains "${output}" "No objects found" || + (output_contains "${output}" "Contents of" && [[ -z "$(echo "${filtered_output}" | tr -d '[:space:]')" ]]); then log "Successfully handled empty bucket listing" return 0 @@ -466,7 +467,7 @@ function test_list_with_files { local missing=0 for i in $(seq 1 "${file_count}"); do - if ! echo "${output}" | grep -q "ls_test/file${i}"; then + if ! output_contains "${output}" "ls_test/file${i}"; then err "Missing file${i} in ls output" missing=1 fi @@ -538,7 +539,7 @@ function test_list_with_files { local output local status output=$(run_s3client "${s3client}" "${credentials}" "${logsdir}" "false" \ - --knob_blobstore_list_max_keys_per_page=100 ls --recursive "${url}" 2>&1) + --knob_blobstore_list_max_keys_per_page=100 ls --recursive "${url}" 2>/dev/null) status=$? local missing=0 @@ -549,10 +550,10 @@ function test_list_with_files { for i in $(seq 1 "${files_per_level}"); do local expected="${current_path}/file${current_depth}_${i}" - if ! echo "${output}" | grep -q "${expected}"; then + if ! output_contains "${output}" "${expected}"; then err "Missing ${expected} in ls output" log "=== DEBUG: Recursive ls output ===" - echo "${output}" | grep -v "HTTP" | head -30 + awk '!/HTTP/ { print; if (++count == 30) exit }' <<<"${output}" log "=== END DEBUG ===" missing=1 fi @@ -580,7 +581,7 @@ function test_list_with_files { while [[ $non_recursive_attempt -lt $non_recursive_attempts ]]; do output=$(run_s3client "${s3client}" "${credentials}" "${logsdir}" "false" \ - --knob_blobstore_list_max_keys_per_page=10 ls "${url}" 2>&1) + --knob_blobstore_list_max_keys_per_page=10 ls "${url}" 2>/dev/null) status=$? # Reset missing flag for this attempt @@ -799,6 +800,32 @@ if [[ "${USE_S3}" == "true" ]]; then query_str='bucket='"${bucket}"'®ion='"${region}"'&secure_connection=1' fi path_prefix="bulkload/test/s3client" +elif [[ "${USE_MOCK_GCS:-false}" == "true" ]]; then + log "Testing GCS against MockS3Server" + # shellcheck source=/dev/null + if ! source "${cwd}/mocks3_fixture.sh"; then + err "Failed to source mocks3_fixture.sh" + exit 1 + fi + if ! TEST_SCRATCH_DIR=$(mktemp -d "${scratch_dir}/mockgcs_test.XXXXXX"); then + err "Failed create of the mockgcs test dir." >&2 + exit 1 + fi + readonly TEST_SCRATCH_DIR + if ! start_mocks3 "${build_dir}" "${TEST_SCRATCH_DIR}/mocks3_data"; then + err "Failed to start MockS3Server" + exit 1 + fi + readonly host="${MOCKS3_HOST}:${MOCKS3_PORT}" + readonly bucket="test-bucket" + readonly region="" + # GCS-format credential file with Bearer token. + # Key by hostname only (not host:port) — GCSBlobStoreEndpoint::credentialFileKey() is "@" + parsed host. + readonly blob_credentials_file="${TEST_SCRATCH_DIR}/blob_credentials.json" + echo "{\"accounts\": {\"@${MOCKS3_HOST}\": {\"token\": \"mock-gcs-token\"}}}" > "${blob_credentials_file}" + query_str='bucket='"${bucket}"'&p=gcs&secure_connection=0' + path_prefix="gcsclient" + else log "Testing against MockS3Server" # Now source in the mocks3 fixture so we can use its methods in the below. diff --git a/fdbclient/tests/tests_common.sh b/fdbclient/tests/tests_common.sh index 821059d515d..e41332a07b8 100644 --- a/fdbclient/tests/tests_common.sh +++ b/fdbclient/tests/tests_common.sh @@ -96,6 +96,44 @@ function err { echo "$(date -Iseconds) ERROR: ${*}" >&2 } +# Check whether captured output contains a literal string. +# Avoids echo|grep -q because pipefail can turn grep's early-exit into +# a false negative via SIGPIPE on the upstream echo. +# $1 output to search +# $2 literal string to find +function output_contains { + local output="$1" + local needle="$2" + grep -Fq -- "${needle}" <<<"${output}" +} + +# Check whether captured output matches a basic regex. +# $1 output to search +# $2 regex pattern +function output_matches { + local output="$1" + local pattern="$2" + grep -q -- "${pattern}" <<<"${output}" +} + +# Case-insensitive regex match. +# $1 output to search +# $2 regex pattern +function output_matches_i { + local output="$1" + local pattern="$2" + grep -qi -- "${pattern}" <<<"${output}" +} + +# Extended regex match. +# $1 output to search +# $2 extended regex pattern +function output_matches_E { + local output="$1" + local pattern="$2" + grep -qE -- "${pattern}" <<<"${output}" +} + # Check if test data should be preserved (PRESERVE_TEST_DATA=1) # If yes, prints preservation message and returns 0 (should skip cleanup) # If no, returns 1 (should continue with normal cleanup) @@ -288,7 +326,7 @@ function log_test_result { # $1 Dir to search under. function grep_for_severity40 { local dir="${1}" - if grep -r -C 3 -e "Severity=\"40\"" "${dir}"; then + if grep -r --exclude-dir=encryption_mismatch_logs -C 3 -e "Severity=\"40\"" "${dir}"; then err "Found 'Severity=40' errors" return 1 fi @@ -324,18 +362,50 @@ function get_use_s3_default { fi } -# Common S3/MockS3 environment setup - shared across all S3 tests -# Prerequisites: USE_S3 and TLS_CA_FILE must be set before calling this function +# Detect which blob store provider to use based on environment variables. +# Sets USE_GCS global (USE_S3 must already be set). +function detect_blobstore_provider { + USE_GCS="$( if [[ -n "${GCS_FDB_BUCKET:-}" && -n "${GCS_APPLICATION_TOKEN:-}" ]]; then echo "true"; else echo "false"; fi )" + readonly USE_GCS +} + +# Common blobstore environment setup - shared across all blob store tests +# Supports S3, GCS, and MockS3Server. +# Prerequisites: USE_S3, USE_GCS, and TLS_CA_FILE must be set before calling this function # (use get_use_s3_default and setup_tls_ca_file) # $1 build directory, $2 scratch directory, $3 path prefix (used for temp dir naming) # Sets global variables: TEST_SCRATCH_DIR, host, bucket, region, blob_credentials_file, query_str -# Exports: FDB_BLOB_CREDENTIALS, FDB_TLS_CA_FILE (if using real S3) +# Exports: FDB_BLOB_CREDENTIALS, FDB_TLS_CA_FILE (if using a real cloud provider) function setup_s3_environment { local local_build_dir="${1}" local local_scratch_dir="${2}" local local_path_prefix="${3}" - if [[ "${USE_S3}" == "true" ]]; then + if [[ "${USE_GCS:-false}" == "true" ]]; then + log "Testing against GCS" + if ! source "${TESTS_COMMON_DIR}/gcp_fixture.sh"; then + err "Failed to source gcp_fixture.sh" + exit 1 + fi + if ! TEST_SCRATCH_DIR=$( create_gcp_dir "${local_scratch_dir}" ); then + err "Failed creating local gcp_dir" + exit 1 + fi + if ! readarray -t configs < <(gcp_setup "${local_build_dir}" "${TEST_SCRATCH_DIR}"); then + err "Failed gcp_setup" + return 1 + fi + host="${configs[0]}" + bucket="${configs[1]}" + blob_credentials_file="${configs[2]}" + region="" + query_str="bucket=${bucket}&p=gcs&secure_connection=1" + export FDB_BLOB_CREDENTIALS="${blob_credentials_file}" + if [[ -n "${TLS_CA_FILE:-}" ]]; then + export FDB_TLS_CA_FILE="${TLS_CA_FILE}" + fi + + elif [[ "${USE_S3}" == "true" ]]; then log "Testing against s3" # Source AWS fixture (use TESTS_COMMON_DIR for reliable path resolution) if ! source "${TESTS_COMMON_DIR}/aws_fixture.sh"; then @@ -359,6 +429,7 @@ function setup_s3_environment { if [[ -n "${TLS_CA_FILE:-}" ]]; then export FDB_TLS_CA_FILE="${TLS_CA_FILE}" fi + else log "Testing against MockS3Server" # Source MockS3 fixture (use TESTS_COMMON_DIR for reliable path resolution) @@ -391,9 +462,9 @@ function setup_s3_environment { readonly query_str } -# Setup TLS CA file for S3 connections +# Setup TLS CA file for cloud provider connections (S3, GCS) function setup_tls_ca_file { - if [[ "${USE_S3}" == "true" ]]; then + if [[ "${USE_S3}" == "true" || "${USE_GCS:-false}" == "true" ]]; then # Try to find a valid TLS CA file if not explicitly set if [[ -z "${TLS_CA_FILE:-}" ]]; then # Common locations for TLS CA files on different systems diff --git a/fdbclient/vexillographer/fdb.options b/fdbclient/vexillographer/fdb.options index ca2c966ee7c..013681b942f 100644 --- a/fdbclient/vexillographer/fdb.options +++ b/fdbclient/vexillographer/fdb.options @@ -321,7 +321,7 @@ description is not currently required but encouraged.