From 8d12269eecf5ca45c3602de811f75626d4b7b751 Mon Sep 17 00:00:00 2001 From: Matthew Russotto Date: Fri, 29 Aug 2025 17:49:01 -0400 Subject: [PATCH] SERVER-109844 Basic support for disaggregated storage clusters (#40566) Co-authored-by: Benety Goh Co-authored-by: Mathias Stearn Co-authored-by: Kaitlin Mahar Co-authored-by: Brandon Stoll Co-authored-by: Vanessa Noia <54818020+nessnoia@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Vishnu K Co-authored-by: Sunil Narasimhamurthy Co-authored-by: Jiawei Yang Co-authored-by: Will Korteland Co-authored-by: Saman Memaripour Co-authored-by: huayu-ouyang Co-authored-by: Suganthi Mani <38441312+smani87@users.noreply.github.com> Co-authored-by: Thomas Goyne Co-authored-by: Haley Connelly Co-authored-by: Billy Donahue Co-authored-by: Kirollos Morkos Co-authored-by: Lingzhi Deng Co-authored-by: Hartek Sabharwal Co-authored-by: Aaron Himelman Co-authored-by: Moustafa Maher Co-authored-by: prathmesh-kallurkar Co-authored-by: Dan Larkin-York <13419935+dhly-etc@users.noreply.github.com> Co-authored-by: Shreyas Kalyan <35750327+shreyaskalyan@users.noreply.github.com> Co-authored-by: Shreyas Kalyan Co-authored-by: Jonathan Reams Co-authored-by: adriangzz Co-authored-by: Eric Milkie Co-authored-by: Aaron B Co-authored-by: Ali Mir Co-authored-by: Alex Blekhman Co-authored-by: mpobrien Co-authored-by: Mark Benvenuto Co-authored-by: Ruby Chen Co-authored-by: Jagadish Nallapaneni <146780625+jagadishmdb@users.noreply.github.com> Co-authored-by: Jonas Bergler Co-authored-by: Peter Macko Co-authored-by: Nic Co-authored-by: Jiawei Yang Co-authored-by: Jordi Serra Torrens Co-authored-by: Sunil Narasimhamurthy GitOrigin-RevId: a1c6609c820052137e2aa759711e86c337ae6f9f --- .github/CODEOWNERS | 21 +- README.third_party.md | 2 + .../resmokeconfig/resmoke_modules.yml | 18 +- .../resmokelib/testing/fixtures/_builder.py | 28 + .../resmokelib/testing/fixtures/replicaset.py | 13 +- .../resmokelib/testing/fixtures/standalone.py | 6 + .../resmokelib/testing/testcases/fixture.py | 4 + .../resmokelib/testing/testcases/jstest.py | 5 + .../tests/burn_in_tests_end2end/__init__.py | 1 - .../test_burn_in_tests_end2end.py | 51 - etc/evergreen.yml | 2 - etc/evergreen_lint.yml | 2 + etc/evergreen_yml_components/definitions.yml | 25 + .../tasks/compile_tasks_shared.yml | 2 + .../tasks/misc_tasks.yml | 38 +- .../clusters_and_integrations/tasks.yml | 5 +- .../tasks.yml | 37 +- .../variants/amazon/test_dev.yml | 7 +- .../amazon/test_dev_master_branch_only.yml | 26 +- .../variants/rhel/test_dev.yml | 4 +- .../rhel/test_dev_master_branch_only.yml | 4 +- .../sanitizer/test_dev_master_branch_only.yml | 16 +- evergreen/build_and_push_module_images.sh | 7 + evergreen/fetch_module_images.sh | 7 + jstests/libs/OWNERS.yml | 6 + .../replicated_record_ids_utils.js | 2 +- jstests/libs/replicated_ident_utils.js | 86 + jstests/libs/replsettest.js | 24 +- modules_poc/modules.yaml | 11 +- sbom.json | 43 + src/mongo/BUILD.bazel | 5 + src/mongo/config.h.in | 3 + src/mongo/db/BUILD.bazel | 16 +- src/mongo/db/admission/flow_control.cpp | 175 +- src/mongo/db/admission/flow_control.h | 91 +- src/mongo/db/admission/flow_control_test.cpp | 13 +- src/mongo/db/auth/auth_op_observer.cpp | 5 +- src/mongo/db/commands/BUILD.bazel | 1 + src/mongo/db/commands/shutdown_d.cpp | 6 +- src/mongo/db/disagg_storage/BUILD.bazel | 31 - src/mongo/db/disagg_storage/OWNERS.yml | 5 - .../db/disagg_storage/server_parameters.idl | 45 - src/mongo/db/exec/scoped_timer.h | 2 +- src/mongo/db/ftdc/ftdc_system_stats_linux.cpp | 228 + src/mongo/db/local_catalog/BUILD.bazel | 4 +- .../db/local_catalog/collection_options.h | 2 + src/mongo/db/local_catalog/database_impl.cpp | 28 +- .../db/local_catalog/durable_catalog.cpp | 13 +- .../db/local_catalog/local_oplog_info.cpp | 5 + src/mongo/db/mongod_main.cpp | 83 +- .../db/op_msg_fuzzer_router_fixture_test.cpp | 1 - src/mongo/db/op_observer/BUILD.bazel | 2 +- src/mongo/db/op_observer/op_observer_impl.cpp | 14 +- src/mongo/db/op_observer/op_observer_util.cpp | 9 +- src/mongo/db/op_observer/op_observer_util.h | 4 +- src/mongo/db/repl/BUILD.bazel | 1 + src/mongo/db/repl/oplog.cpp | 23 +- src/mongo/db/repl/oplog_applier_impl.cpp | 2 +- src/mongo/db/repl/oplog_applier_impl_test.cpp | 55 - .../repl/oplog_applier_impl_test_fixture.cpp | 25 +- .../db/repl/oplog_applier_impl_test_fixture.h | 10 +- src/mongo/db/repl/optime.h | 6 + src/mongo/db/repl/rollback_impl_test.cpp | 5 +- src/mongo/db/repl/storage_timestamp_test.cpp | 23 +- src/mongo/db/rss/BUILD.bazel | 77 + src/mongo/db/rss/OWNERS.yml | 6 + src/mongo/db/rss/attached_storage/BUILD.bazel | 43 + .../attached_persistence_provider.cpp | 101 + .../attached_persistence_provider.h | 105 + .../attached_service_lifecycle.cpp | 126 + .../attached_service_lifecycle.h | 77 + src/mongo/db/rss/persistence_provider.h | 131 + .../db/rss/replicated_storage_service.cpp | 63 + src/mongo/db/rss/replicated_storage_service.h | 55 + src/mongo/db/rss/service_lifecycle.h | 97 + .../db/service_context_d_test_fixture.cpp | 2 +- src/mongo/db/startup_recovery.cpp | 9 + src/mongo/db/storage/BUILD.bazel | 4 + src/mongo/db/storage/devnull/BUILD.bazel | 1 + .../db/storage/devnull/devnull_kv_engine.h | 4 +- .../kv/kv_drop_pending_ident_reaper_test.cpp | 4 +- src/mongo/db/storage/kv/kv_engine.h | 26 +- .../db/storage/kv/kv_engine_test_harness.cpp | 18 +- .../storage/kv/kv_engine_timestamps_test.cpp | 5 +- src/mongo/db/storage/mdb_catalog.cpp | 5 +- src/mongo/db/storage/storage_engine.h | 20 + src/mongo/db/storage/storage_engine_impl.cpp | 30 +- src/mongo/db/storage/storage_engine_impl.h | 6 + src/mongo/db/storage/storage_engine_mock.h | 9 +- .../db/storage/storage_engine_test_fixture.h | 4 +- src/mongo/db/storage/storage_options.cpp | 10 +- src/mongo/db/storage/storage_options.h | 12 +- src/mongo/db/storage/wiredtiger/BUILD.bazel | 3 + .../wiredtiger/spill_wiredtiger_kv_engine.cpp | 2 +- .../wiredtiger/spill_wiredtiger_kv_engine.h | 4 +- .../spill_wiredtiger_kv_engine_test.cpp | 2 + .../wiredtiger_customization_hooks.cpp | 26 + .../wiredtiger_customization_hooks.h | 24 + .../wiredtiger/wiredtiger_global_options.h | 1 + .../wiredtiger/wiredtiger_global_options.idl | 12 + .../storage/wiredtiger/wiredtiger_index.cpp | 2 + .../db/storage/wiredtiger/wiredtiger_init.cpp | 6 +- .../wiredtiger/wiredtiger_kv_engine.cpp | 497 +- .../storage/wiredtiger/wiredtiger_kv_engine.h | 36 +- .../wiredtiger/wiredtiger_kv_engine_test.cpp | 34 +- .../wiredtiger_prepare_conflict_test.cpp | 6 +- .../wiredtiger/wiredtiger_record_store.cpp | 12 +- .../wiredtiger/wiredtiger_record_store.h | 1 + .../wiredtiger_record_store_test.cpp | 49 +- .../wiredtiger_record_store_test_harness.cpp | 16 +- .../wiredtiger_recovery_unit_test.cpp | 10 +- .../db/storage/wiredtiger/wiredtiger_util.cpp | 11 +- .../db/storage/wiredtiger/wiredtiger_util.h | 4 +- src/mongo/db/transaction/BUILD.bazel | 1 + .../transaction/transaction_participant.cpp | 8 + src/mongo/dbtests/BUILD.bazel | 1 + .../executor/mock_network_fixture_test.cpp | 2 +- src/mongo/mongo_config_header.py | 1 + src/third_party/OWNERS.yml | 3 + .../BUILD.bazel | 48 + .../dist/LICENSE | 28 + .../dist/README.md | 133 + .../googlemock/include/gmock/gmock-actions.h | 2404 ++++++ .../include/gmock/gmock-cardinalities.h | 159 + .../include/gmock/gmock-function-mocker.h | 519 ++ .../googlemock/include/gmock/gmock-matchers.h | 5891 ++++++++++++++ .../include/gmock/gmock-more-actions.h | 659 ++ .../include/gmock/gmock-more-matchers.h | 120 + .../include/gmock/gmock-nice-strict.h | 277 + .../include/gmock/gmock-spec-builders.h | 2146 +++++ .../dist/googlemock/include/gmock/gmock.h | 97 + .../include/gmock/internal/custom/README.md | 18 + .../internal/custom/gmock-generated-actions.h | 7 + .../gmock/internal/custom/gmock-matchers.h | 37 + .../gmock/internal/custom/gmock-port.h | 40 + .../gmock/internal/gmock-internal-utils.h | 484 ++ .../include/gmock/internal/gmock-port.h | 140 + .../include/gmock/internal/gmock-pp.h | 279 + .../dist/googlemock/src/gmock-all.cc | 46 + .../googlemock/src/gmock-cardinalities.cc | 155 + .../googlemock/src/gmock-internal-utils.cc | 258 + .../dist/googlemock/src/gmock-matchers.cc | 478 ++ .../googlemock/src/gmock-spec-builders.cc | 792 ++ .../dist/googlemock/src/gmock.cc | 225 + .../dist/googlemock/src/gmock_main.cc | 73 + .../include/gtest/gtest-assertion-result.h | 244 + .../include/gtest/gtest-death-test.h | 345 + .../googletest/include/gtest/gtest-matchers.h | 923 +++ .../googletest/include/gtest/gtest-message.h | 251 + .../include/gtest/gtest-param-test.h | 602 ++ .../googletest/include/gtest/gtest-printers.h | 1236 +++ .../dist/googletest/include/gtest/gtest-spi.h | 250 + .../include/gtest/gtest-test-part.h | 192 + .../include/gtest/gtest-typed-test.h | 331 + .../dist/googletest/include/gtest/gtest.h | 2338 ++++++ .../include/gtest/gtest_pred_impl.h | 279 + .../googletest/include/gtest/gtest_prod.h | 60 + .../include/gtest/internal/custom/README.md | 44 + .../gtest/internal/custom/gtest-port.h | 37 + .../gtest/internal/custom/gtest-printers.h | 42 + .../include/gtest/internal/custom/gtest.h | 37 + .../internal/gtest-death-test-internal.h | 306 + .../include/gtest/internal/gtest-filepath.h | 233 + .../include/gtest/internal/gtest-internal.h | 1517 ++++ .../include/gtest/internal/gtest-param-util.h | 1064 +++ .../include/gtest/internal/gtest-port-arch.h | 124 + .../include/gtest/internal/gtest-port.h | 2486 ++++++ .../include/gtest/internal/gtest-string.h | 178 + .../include/gtest/internal/gtest-type-util.h | 220 + .../dist/googletest/src/gtest-all.cc | 49 + .../googletest/src/gtest-assertion-result.cc | 77 + .../dist/googletest/src/gtest-death-test.cc | 1587 ++++ .../dist/googletest/src/gtest-filepath.cc | 414 + .../dist/googletest/src/gtest-internal-inl.h | 1234 +++ .../dist/googletest/src/gtest-matchers.cc | 98 + .../dist/googletest/src/gtest-port.cc | 1434 ++++ .../dist/googletest/src/gtest-printers.cc | 555 ++ .../dist/googletest/src/gtest-test-part.cc | 106 + .../dist/googletest/src/gtest-typed-test.cc | 108 + .../dist/googletest/src/gtest.cc | 7083 +++++++++++++++++ .../dist/googletest/src/gtest_main.cc | 66 + .../scripts/import.sh | 31 + 182 files changed, 44273 insertions(+), 689 deletions(-) delete mode 100644 buildscripts/tests/burn_in_tests_end2end/__init__.py delete mode 100644 buildscripts/tests/burn_in_tests_end2end/test_burn_in_tests_end2end.py create mode 100644 evergreen/build_and_push_module_images.sh create mode 100644 evergreen/fetch_module_images.sh create mode 100644 jstests/libs/replicated_ident_utils.js delete mode 100644 src/mongo/db/disagg_storage/BUILD.bazel delete mode 100644 src/mongo/db/disagg_storage/OWNERS.yml delete mode 100644 src/mongo/db/disagg_storage/server_parameters.idl create mode 100644 src/mongo/db/rss/BUILD.bazel create mode 100644 src/mongo/db/rss/OWNERS.yml create mode 100644 src/mongo/db/rss/attached_storage/BUILD.bazel create mode 100644 src/mongo/db/rss/attached_storage/attached_persistence_provider.cpp create mode 100644 src/mongo/db/rss/attached_storage/attached_persistence_provider.h create mode 100644 src/mongo/db/rss/attached_storage/attached_service_lifecycle.cpp create mode 100644 src/mongo/db/rss/attached_storage/attached_service_lifecycle.h create mode 100644 src/mongo/db/rss/persistence_provider.h create mode 100644 src/mongo/db/rss/replicated_storage_service.cpp create mode 100644 src/mongo/db/rss/replicated_storage_service.h create mode 100644 src/mongo/db/rss/service_lifecycle.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/BUILD.bazel create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/LICENSE create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/README.md create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-actions.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-cardinalities.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-function-mocker.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-matchers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-more-actions.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-more-matchers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-nice-strict.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-spec-builders.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/custom/README.md create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/custom/gmock-generated-actions.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/custom/gmock-matchers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/custom/gmock-port.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/gmock-internal-utils.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/gmock-port.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/internal/gmock-pp.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock-all.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock-cardinalities.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock-internal-utils.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock-matchers.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock-spec-builders.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/src/gmock_main.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-assertion-result.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-death-test.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-matchers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-message.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-param-test.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-printers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-spi.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-test-part.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest-typed-test.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest_pred_impl.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/gtest_prod.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/custom/README.md create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/custom/gtest-port.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/custom/gtest-printers.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/custom/gtest.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-death-test-internal.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-filepath.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-internal.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-param-util.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-port-arch.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-port.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-string.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/include/gtest/internal/gtest-type-util.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-all.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-assertion-result.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-death-test.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-filepath.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-internal-inl.h create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-matchers.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-port.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-printers.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-test-part.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest-typed-test.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest.cc create mode 100644 src/third_party/googletest_restricted_for_disagg_only/dist/googletest/src/gtest_main.cc create mode 100755 src/third_party/googletest_restricted_for_disagg_only/scripts/import.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0da876b6255..2ff38acbdf4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -81,6 +81,9 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot /buildscripts/idl/**/idl_compatibility_errors.py @10gen/query-optimization @svc-auto-approve-bot /buildscripts/idl/**/test_compatibility.py @10gen/query-optimization @svc-auto-approve-bot +# The following patterns are parsed from ./buildscripts/modules/atlas/OWNERS.yml +/buildscripts/modules/atlas/ @10gen/server-disagg-storage @svc-auto-approve-bot + # The following patterns are parsed from ./buildscripts/monitor_build_status/OWNERS.yml /buildscripts/monitor_build_status/ @10gen/devprod-correctness @svc-auto-approve-bot @@ -888,6 +891,8 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot /jstests/libs/**/catalog_list_operations_consistency_validator.js @10gen/server-catalog-and-routing @svc-auto-approve-bot /jstests/libs/**/raw_operation_utils.js @10gen/server-collection-write-path @svc-auto-approve-bot /jstests/libs/**/json_utils.js @10gen/query-integration-extensions @svc-auto-approve-bot +/jstests/libs/**/replicated_ident_utils.js @10gen/server-storage-engine-integration @svc-auto-approve-bot +/jstests/libs/**/replicated_record_ids_utils.js @10gen/server-storage-engine-integration @svc-auto-approve-bot # The following patterns are parsed from ./jstests/libs/clustered_collections/OWNERS.yml /jstests/libs/clustered_collections/**/* @10gen/server-collection-write-path @svc-auto-approve-bot @@ -1831,9 +1836,6 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot /src/mongo/db/commands/query_cmd/**/release_memory_cmd.* @10gen/query-execution @svc-auto-approve-bot /src/mongo/db/commands/query_cmd/**/update_metrics.* @10gen/query-execution @svc-auto-approve-bot -# The following patterns are parsed from ./src/mongo/db/disagg_storage/OWNERS.yml -/src/mongo/db/disagg_storage/**/* @10gen/server-disagg-storage @svc-auto-approve-bot - # The following patterns are parsed from ./src/mongo/db/exec/OWNERS.yml /src/mongo/db/exec/**/* @10gen/query-execution-classic @svc-auto-approve-bot /src/mongo/db/exec/**/OWNERS.yml @10gen/query-execution-staff-leads @10gen/query-integration-staff-leads @10gen/query-optimization-staff-leads @svc-auto-approve-bot @@ -2043,6 +2045,15 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot # The following patterns are parsed from ./src/mongo/db/modules/atlas/OWNERS.yml /src/mongo/db/modules/atlas/**/* @10gen/server-disagg-storage @svc-auto-approve-bot +# The following patterns are parsed from ./src/mongo/db/modules/atlas/jstests/disagg_storage/OWNERS.yml +/src/mongo/db/modules/atlas/jstests/disagg_storage/**/* @10gen/server-disagg-storage @svc-auto-approve-bot + +# The following patterns are parsed from ./src/mongo/db/modules/atlas/src/disagg_storage/OWNERS.yml +/src/mongo/db/modules/atlas/src/disagg_storage/**/* @10gen/server-disagg-storage @svc-auto-approve-bot + +# The following patterns are parsed from ./src/mongo/db/modules/atlas/src/disagg_storage/encryption/OWNERS.yml +/src/mongo/db/modules/atlas/src/disagg_storage/encryption/**/* @10gen/server-security @svc-auto-approve-bot + # The following patterns are parsed from ./src/mongo/db/modules/enterprise/OWNERS.yml /src/mongo/db/modules/enterprise/BUILD.bazel @10gen/devprod-build @svc-auto-approve-bot /src/mongo/db/modules/enterprise/README.md @10gen/server-release @svc-auto-approve-bot @@ -2589,6 +2600,9 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot # The following patterns are parsed from ./src/mongo/db/repl/split_horizon/OWNERS.yml /src/mongo/db/repl/split_horizon/**/* @10gen/server-split-horizon @svc-auto-approve-bot +# The following patterns are parsed from ./src/mongo/db/rss/OWNERS.yml +/src/mongo/db/rss/**/* @10gen/server-replication @10gen/server-storage-execution @svc-auto-approve-bot + # The following patterns are parsed from ./src/mongo/db/s/OWNERS.yml /src/mongo/db/s/**/* @10gen/server-cluster-scalability @svc-auto-approve-bot /src/mongo/db/s/**/*transaction* @10gen/server-transactions @svc-auto-approve-bot @@ -3068,6 +3082,7 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot /src/third_party/**/croaring @10gen/query-execution @svc-auto-approve-bot /src/third_party/**/fmt @10gen/server-programmability @svc-auto-approve-bot /src/third_party/**/folly @10gen/server-workload-scheduling @svc-auto-approve-bot +/src/third_party/**/googletest_restricted_for_disagg_only @10gen/server-disagg-storage @svc-auto-approve-bot /src/third_party/**/gperftools @10gen/server-workload-scheduling @svc-auto-approve-bot /src/third_party/**/grpc @10gen/server-networking-and-observability @svc-auto-approve-bot /src/third_party/**/icu4c* @10gen/query-execution @svc-auto-approve-bot diff --git a/README.third_party.md b/README.third_party.md index 60ab410e09d..4141a53b0f7 100644 --- a/README.third_party.md +++ b/README.third_party.md @@ -66,6 +66,7 @@ a notice will be included in | [pyiso8601] | MIT | 2.1.0 | unknown | | | [RoaringBitmap/CRoaring] | Unknown License | v3.0.1 | | ✗ | | [SchemaStore/schemastore] | Apache-2.0 | Unknown | | | +| [sls-proto] | Unknown License | 1.0 | unknown | ✗ | | [smhasher] | Unknown License | Unknown | unknown | ✗ | | [Snowball Stemming Algorithms] | BSD-3-Clause | 7b264ffa0f767c579d052fd8142558dc8264d795 | ✗ | ✗ | | [subunit] | BSD-3-Clause, Apache-2.0 | 1.4.4 | unknown | | @@ -122,6 +123,7 @@ a notice will be included in [opentelemetry-cpp]: https://github.com/open-telemetry/opentelemetry-cpp/ [opentelemetry-proto]: https://github.com/open-telemetry/opentelemetry-proto [pyiso8601]: https://pypi.org/project/iso8601/ +[sls-proto]: https://github.com/10gen/sls [smhasher]: https://github.com/aappleby/smhasher/blob/a6bd3ce/ [subunit]: https://github.com/testing-cabal/subunit [tcmalloc]: https://github.com/google/tcmalloc diff --git a/buildscripts/resmokeconfig/resmoke_modules.yml b/buildscripts/resmokeconfig/resmoke_modules.yml index 1980ef145ac..752d3ec0a2f 100644 --- a/buildscripts/resmokeconfig/resmoke_modules.yml +++ b/buildscripts/resmokeconfig/resmoke_modules.yml @@ -1,12 +1,12 @@ enterprise: jstest_dirs: - src/mongo/db/modules/enterprise/jstests -# atlas: -# fixture_dirs: -# - buildscripts/modules/atlas/fixtures -# hook_dirs: -# - buildscripts/modules/atlas/hooks -# suite_dirs: -# - buildscripts/modules/atlas/suites -# jstest_dirs: -# - buildscripts/modules/atlas/jstests +atlas: + fixture_dirs: + - buildscripts/modules/atlas/fixtures + hook_dirs: + - buildscripts/modules/atlas/hooks + suite_dirs: + - buildscripts/modules/atlas/suites + jstest_dirs: + - src/mongo/db/modules/atlas/jstests diff --git a/buildscripts/resmokelib/testing/fixtures/_builder.py b/buildscripts/resmokelib/testing/fixtures/_builder.py index 617c0f452e3..ae32b3bb0b4 100644 --- a/buildscripts/resmokelib/testing/fixtures/_builder.py +++ b/buildscripts/resmokelib/testing/fixtures/_builder.py @@ -1,5 +1,6 @@ """Utilities for constructing fixtures that may span multiple versions.""" +import json import logging import threading from abc import ABC, abstractmethod @@ -223,6 +224,33 @@ class ReplSetBuilder(FixtureBuilder): ) replset.install_mongod(node) + if replset.disagg_base_config: + members = [] + for idx, node in enumerate(replset.nodes): + member = { + "_id": idx, + "host": node.get_internal_connection_string(), + "priority": 1 + } + members.append(member) + disagg_base_config = { + **replset.disagg_base_config, + "replSetConfig": { + "_id": replset.replset_name, + "version": 1, + "term": 1, + "members": members, + } + } + for node in replset.nodes: + opts = node.get_mongod_options() + opts["set_parameters"]["disaggregatedStorageConfig"] = json.dumps( + disagg_base_config) + opts["set_parameters"]["disaggregatedStorageEnabled"] = True + opts["set_parameters"]["logComponentVerbosity"] = json.dumps( + {"disaggregatedStorage": 5}) + node.set_mongod_options(opts) + if replset.start_initial_sync_node: if not replset.initial_sync_node: replset.initial_sync_node_idx = replset.num_nodes diff --git a/buildscripts/resmokelib/testing/fixtures/replicaset.py b/buildscripts/resmokelib/testing/fixtures/replicaset.py index 72eb2c969bf..1fce2723b88 100644 --- a/buildscripts/resmokelib/testing/fixtures/replicaset.py +++ b/buildscripts/resmokelib/testing/fixtures/replicaset.py @@ -74,6 +74,7 @@ class ReplicaSetFixture(interface.ReplFixture, interface._DockerComposeInterface launch_mongot=False, load_all_extensions=False, router_endpoint_for_mongot: Optional[int] = None, + disagg_base_config=None, ): """Initialize ReplicaSetFixture.""" @@ -139,6 +140,8 @@ class ReplicaSetFixture(interface.ReplFixture, interface._DockerComposeInterface # Set the default oplogSize to 511MB. self.mongod_options.setdefault("oplogSize", 511) + self.disagg_base_config = disagg_base_config + # The dbpath in mongod_options is used as the dbpath prefix for replica set members and # takes precedence over other settings. The ShardedClusterFixture uses this parameter to # create replica sets and assign their dbpath structure explicitly. @@ -462,12 +465,14 @@ class ReplicaSetFixture(interface.ReplFixture, interface._DockerComposeInterface primary = self.nodes[0] client = primary.mongo_client() while True: - self.logger.info("Waiting for primary on port %d to be elected.", primary.port) - is_master = client.admin.command("isMaster")["ismaster"] - if is_master: + self.logger.info( + "Waiting for primary on port %d to be elected.", primary.port) + cmd_result = client.admin.command("isMaster") + if cmd_result["ismaster"]: break time.sleep(0.1) # Wait a little bit before trying again. - self.logger.info("Primary on port %d successfully elected.", primary.port) + self.logger.info( + "Primary on port %d successfully elected.", primary.port) def _await_secondaries(self): # Wait for the secondaries to become available. diff --git a/buildscripts/resmokelib/testing/fixtures/standalone.py b/buildscripts/resmokelib/testing/fixtures/standalone.py index cc6277297e5..beef209a2f4 100644 --- a/buildscripts/resmokelib/testing/fixtures/standalone.py +++ b/buildscripts/resmokelib/testing/fixtures/standalone.py @@ -188,6 +188,12 @@ class MongoDFixture(interface.Fixture, interface._DockerComposeInterface): self.logger.debug("Mongod not running when gathering standalone fixture pid.") return out + def get_mongod_options(self): + return self.mongod_options + + def set_mongod_options(self, options): + self.mongod_options = options + def _handle_await_ready_retry(self, deadline): remaining = deadline - time.time() if remaining <= 0.0: diff --git a/buildscripts/resmokelib/testing/testcases/fixture.py b/buildscripts/resmokelib/testing/testcases/fixture.py index 13825cd91ed..f3c2b86e5ed 100644 --- a/buildscripts/resmokelib/testing/testcases/fixture.py +++ b/buildscripts/resmokelib/testing/testcases/fixture.py @@ -50,6 +50,10 @@ class FixtureSetupTestCase(FixtureTestCase): self.fixture.await_ready() if ( not isinstance(self.fixture, (fixture_interface.NoOpFixture, ExternalFixture)) + # TODO(SERVER-109851): Remove this. + # disagg mongod does not yet support "refreshLogicalSessionCacheNow" because it requires + # wtimeout support. + and self.fixture.__class__.__name__ != "DisaggFixture" # Replica set with --configsvr cannot run refresh unless it is part of a sharded cluster. and not ( isinstance(self.fixture, ReplicaSetFixture) diff --git a/buildscripts/resmokelib/testing/testcases/jstest.py b/buildscripts/resmokelib/testing/testcases/jstest.py index bc3d33520b0..8abb20a69d2 100644 --- a/buildscripts/resmokelib/testing/testcases/jstest.py +++ b/buildscripts/resmokelib/testing/testcases/jstest.py @@ -3,7 +3,9 @@ import copy import os import os.path +import random import shutil +import string import sys import threading import uuid @@ -67,6 +69,9 @@ class _SingleJSTestCase(interface.ProcessTestCase): global_vars["MongoRunner.dataPath"] = data_path test_data = global_vars.get("TestData", {}).copy() + test_run_id = "".join(random.choices(string.ascii_letters + string.digits, k=10)) + self.fixture.test_run_id = test_run_id + test_data["test_run_id"] = test_run_id test_data["minPort"] = core.network.PortAllocator.min_test_port(self.fixture.job_num) test_data["maxPort"] = core.network.PortAllocator.max_test_port(self.fixture.job_num) test_data["peerPids"] = self.fixture.pids() diff --git a/buildscripts/tests/burn_in_tests_end2end/__init__.py b/buildscripts/tests/burn_in_tests_end2end/__init__.py deleted file mode 100644 index 4b7a2bb941b..00000000000 --- a/buildscripts/tests/burn_in_tests_end2end/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty.""" diff --git a/buildscripts/tests/burn_in_tests_end2end/test_burn_in_tests_end2end.py b/buildscripts/tests/burn_in_tests_end2end/test_burn_in_tests_end2end.py deleted file mode 100644 index 37a7213b59b..00000000000 --- a/buildscripts/tests/burn_in_tests_end2end/test_burn_in_tests_end2end.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import subprocess -import sys -import unittest - -import yaml - -import buildscripts.burn_in_tests as under_test - - -class TestBurnInTestsEnd2End(unittest.TestCase): - @unittest.skip( - "Disabled since this test has behavior dependent on currently modified jstests. Re-enable with SERVER-108783." - ) - @classmethod - def setUpClass(cls): - subprocess.run( - [ - sys.executable, - "buildscripts/burn_in_tests.py", - "generate-test-membership-map-file-for-ci", - ] - ) - - @classmethod - def tearDownClass(cls): - if os.path.exists(under_test.BURN_IN_TEST_MEMBERSHIP_FILE): - os.remove(under_test.BURN_IN_TEST_MEMBERSHIP_FILE) - - def test_valid_yaml_output(self): - process = subprocess.run( - [ - sys.executable, - "buildscripts/burn_in_tests.py", - "run", - "--yaml", - ], - text=True, - capture_output=True, - ) - self.assertEqual( - 0, - process.returncode, - process.stderr, - ) - - output = process.stdout - try: - yaml.safe_load(output) - except Exception: - self.fail(msg="burn_in_tests.py does not output valid yaml.") diff --git a/etc/evergreen.yml b/etc/evergreen.yml index c3d655c576b..cc48f45e1e7 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -93,8 +93,6 @@ include: - filename: etc/evergreen_yml_components/variants/codecoverage/test_dev.yml - - filename: src/mongo/db/modules/atlas/atlas_dev.yml - parameters: - key: evergreen_config_file_path value: "etc/evergreen.yml" diff --git a/etc/evergreen_lint.yml b/etc/evergreen_lint.yml index 47a2cc705eb..faa0745067a 100644 --- a/etc/evergreen_lint.yml +++ b/etc/evergreen_lint.yml @@ -79,6 +79,7 @@ rules: - assigned_to_jira_team_server_workload_scheduling - assigned_to_jira_team_server_networking_and_observability - assigned_to_jira_team_server_integration + - assigned_to_jira_team_server_disagg # https://github.com/10gen/mothra/blob/main/mothra/teams/rnd_dev_prod.yaml - assigned_to_jira_team_devprod_build @@ -130,6 +131,7 @@ rules: - incompatible_inmemory - incompatible_all_feature_flags - incompatible_development_variant + - incompatible_disaggregated_storage - requires_compile_variant - requires_large_host - requires_large_host_tsan diff --git a/etc/evergreen_yml_components/definitions.yml b/etc/evergreen_yml_components/definitions.yml index 1a42ede787d..354409e811d 100644 --- a/etc/evergreen_yml_components/definitions.yml +++ b/etc/evergreen_yml_components/definitions.yml @@ -1236,6 +1236,19 @@ functions: args: - "./src/evergreen/resmoke_tests_execute_bazel.sh" + "assume ECR role": &assume_ecr_role + command: ec2.assume_role + params: + role_arn: "${disagg_storage_ecr_arn}" + + "fetch module images": &fetch_module_images + command: subprocess.exec + params: + binary: bash + add_expansions_to_env: true # needed to get the AWS secrets from ec2.assume_role + args: + - "./src/evergreen/fetch_module_images.sh" + "retrieve generated test configuration": &retrieve_generated_test_configuration command: s3.get @@ -1472,6 +1485,8 @@ functions: - *f_expansions_write - *sign_macos_dev_binaries - *multiversion_exclude_tags_generate + - *assume_ecr_role + - *fetch_module_images - *execute_resmoke_tests # The existence of the "run_tests_infrastructure_failure" file indicates this failure isn't # directly actionable. We use type=setup rather than type=system or type=test for this command @@ -1521,6 +1536,8 @@ functions: - *configure_evergreen_api_credentials - *sign_macos_dev_binaries - *multiversion_exclude_tags_generate + - *assume_ecr_role + - *fetch_module_images - *execute_resmoke_tests # The existence of the "run_tests_infrastructure_failure" file indicates this failure isn't # directly actionable. We use type=setup rather than type=system or type=test for this command @@ -3294,3 +3311,11 @@ functions: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] args: - "./src/evergreen/container_registry_login.sh" + + "build and push module images": &build_and_push_module_images + command: subprocess.exec + params: + binary: bash + add_expansions_to_env: true # needed to get the AWS secrets from ec2.assume_role + args: + - "./src/evergreen/build_and_push_module_images.sh" diff --git a/etc/evergreen_yml_components/tasks/compile_tasks_shared.yml b/etc/evergreen_yml_components/tasks/compile_tasks_shared.yml index 74628a2ed93..ac89472f53b 100644 --- a/etc/evergreen_yml_components/tasks/compile_tasks_shared.yml +++ b/etc/evergreen_yml_components/tasks/compile_tasks_shared.yml @@ -212,6 +212,8 @@ tasks: - "src/src/**.yml" - "src/src/mongo/client/sdam/json_tests/sdam_tests/**" - "src/src/mongo/client/sdam/json_tests/server_selection_tests/**" + - "src/src/mongo/db/modules/atlas/evergreen/**" + - "src/src/mongo/db/modules/atlas/jstests/**" - "src/src/mongo/db/modules/enterprise/docs/**" - "src/src/mongo/db/modules/enterprise/jstests/**" - "src/src/mongo/db/modules/subscription/jstests/**" diff --git a/etc/evergreen_yml_components/tasks/misc_tasks.yml b/etc/evergreen_yml_components/tasks/misc_tasks.yml index ec435b6eb54..52a134d2c0a 100644 --- a/etc/evergreen_yml_components/tasks/misc_tasks.yml +++ b/etc/evergreen_yml_components/tasks/misc_tasks.yml @@ -431,7 +431,13 @@ tasks: - <<: *run_jepsen_template name: jepsen_config_fuzzer_list-append - tags: ["assigned_to_jira_team_server_repl", "experimental", "jepsen_docker"] + tags: + [ + "assigned_to_jira_team_server_repl", + "experimental", + "jepsen_docker", + "uses_docker", + ] commands: - func: "do setup" - func: "do jepsen docker setup" @@ -514,7 +520,13 @@ tasks: - <<: *run_jepsen_template name: jepsen_list-append - tags: ["assigned_to_jira_team_server_repl", "experimental", "jepsen_docker"] + tags: + [ + "assigned_to_jira_team_server_repl", + "experimental", + "jepsen_docker", + "uses_docker", + ] commands: - func: "do setup" - func: "do jepsen docker setup" @@ -689,11 +701,7 @@ tasks: # Check that the mutational fuzzer can parse JS files modified in a patch build. - name: lint_fuzzer_sanity_patch - tags: - [ - "assigned_to_jira_team_devprod_correctness", - "development_critical_single_variant", - ] + tags: ["assigned_to_jira_team_devprod_correctness", "experimental"] patch_only: true commands: - command: manifest.load @@ -1517,6 +1525,22 @@ tasks: commands: - func: "generate smoke test tasks" + - name: push_mongod_to_ecr + tags: ["assigned_to_jira_team_disag_mongod"] + depends_on: + - name: package + commands: + - command: manifest.load + - func: "git get project and add git tag" + - func: "f_expansions_write" + - func: "set up venv" + - func: "fetch dist tarball" + - func: "extract binaries" + - command: ec2.assume_role + params: + role_arn: "${disagg_storage_ecr_arn}" + - func: "build and push module images" + - name: selinux_rhel8_enterprise tags: ["assigned_to_jira_team_server_security", "experimental"] depends_on: diff --git a/etc/evergreen_yml_components/tasks/resmoke/server_divisions/clusters_and_integrations/tasks.yml b/etc/evergreen_yml_components/tasks/resmoke/server_divisions/clusters_and_integrations/tasks.yml index 179992f1a00..10395b88e04 100644 --- a/etc/evergreen_yml_components/tasks/resmoke/server_divisions/clusters_and_integrations/tasks.yml +++ b/etc/evergreen_yml_components/tasks/resmoke/server_divisions/clusters_and_integrations/tasks.yml @@ -1962,14 +1962,15 @@ tasks: - <<: *jstestfuzz_template name: resharding_timeseries_fuzzer_gen - tags: - [ + tags: [ "assigned_to_jira_team_server_cluster_scalability", "default", "feature_flag_guarded", "random_name", "require_npm", "requires_all_feature_flags", + # TODO SERVER-109849: Remove this tag. + "incompatible_disaggregated_storage", ] commands: - func: "generate resmoke tasks" diff --git a/etc/evergreen_yml_components/tasks/resmoke/server_divisions/durable_transactions_and_availability/tasks.yml b/etc/evergreen_yml_components/tasks/resmoke/server_divisions/durable_transactions_and_availability/tasks.yml index 7228341092d..01effd20b25 100644 --- a/etc/evergreen_yml_components/tasks/resmoke/server_divisions/durable_transactions_and_availability/tasks.yml +++ b/etc/evergreen_yml_components/tasks/resmoke/server_divisions/durable_transactions_and_availability/tasks.yml @@ -667,12 +667,13 @@ tasks: - <<: *jstestfuzz_template name: initial_sync_fuzzer_sanity_patch_gen patch_only: true - tags: - [ + tags: [ "assigned_to_jira_team_server_repl", "default", "require_npm", "random_name", + # TODO SERVER-109849: Remove this tag. + "incompatible_disaggregated_storage", ] commands: - func: "generate resmoke tasks" @@ -1551,13 +1552,14 @@ tasks: - <<: *jstestfuzz_template name: rollback_fuzzer_sanity_patch_gen patch_only: true - tags: - [ + tags: [ "assigned_to_jira_team_server_repl", "default", "rollbackfuzzer", "require_npm", "random_name", + # TODO SERVER-109849: Remove this tag. + "incompatible_disaggregated_storage", ] commands: - func: "generate resmoke tasks" @@ -2341,3 +2343,30 @@ tasks: - func: "generate resmoke tasks" vars: suite: v1index_jscore_passthrough + + ################################################ + # Disagg Storage tasks # + ################################################ + + - <<: *gen_task_template + name: disagg_storage_gen + tags: + [ + "assigned_to_jira_team_server_disagg", + "default", + "large", + "clustered_collections", + "uses_docker", + ] + commands: + - func: "generate resmoke tasks" + vars: + suite: disagg_storage + use_large_distro: "true" + + - <<: *task_template + name: disagg_repl_jscore_passthrough + tags: ["assigned_to_jira_team_server_disagg", "default", "uses_docker"] + commands: + - func: "do setup" + - func: "run tests" diff --git a/etc/evergreen_yml_components/variants/amazon/test_dev.yml b/etc/evergreen_yml_components/variants/amazon/test_dev.yml index a5f7714cf61..7cbb5ddfbfe 100644 --- a/etc/evergreen_yml_components/variants/amazon/test_dev.yml +++ b/etc/evergreen_yml_components/variants/amazon/test_dev.yml @@ -85,7 +85,7 @@ buildvariants: - <<: *linux-arm64-dynamic-compile-params name: &amazon-linux2023-arm64-static-compile amazon-linux2023-arm64-static-compile - display_name: "! Amazon Linux 2023 arm64 Enterprise Compile" + display_name: "! Amazon Linux 2023 arm64 Atlas Compile" tags: ["required", "bazel_check", "forbid_tasks_tagged_with_experimental"] expansions: <<: *linux-arm64-static-enterprise-compile-expansions @@ -104,7 +104,6 @@ buildvariants: # since it's running on a c6g.16xlarge bazel_compile_flags: >- --define=MONGO_DISTMOD=amazon2023 - --//bazel/config:build_otel=True --remote_execution_priority=3 --jobs=1600 --build_atlas=True @@ -169,8 +168,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags distros: - amazon2023-arm64-atlas-latest-large - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags distros: - amazon2023-arm64-atlas-latest-large - name: .fuzzer_deterministic diff --git a/etc/evergreen_yml_components/variants/amazon/test_dev_master_branch_only.yml b/etc/evergreen_yml_components/variants/amazon/test_dev_master_branch_only.yml index 86ef5898439..d4609a55f26 100644 --- a/etc/evergreen_yml_components/variants/amazon/test_dev_master_branch_only.yml +++ b/etc/evergreen_yml_components/variants/amazon/test_dev_master_branch_only.yml @@ -168,10 +168,16 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only distros: - amazon2023-arm64-atlas-latest-large - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only distros: - amazon2023-arm64-atlas-latest-large + - name: .default !.requires_large_host .uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only + distros: + - amazon2023-arm64-latest-small + - name: .default .requires_large_host .uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only + distros: + - amazon2023-arm64-latest-large - name: .fuzzer_deterministic - <<: *enterprise-amazon-linux2023-arm64-all-feature-flags-template @@ -193,8 +199,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.requires_all_feature_flags distros: - amazon2023-arm64-latest-large - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.requires_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.requires_all_feature_flags + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.requires_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.requires_all_feature_flags distros: - amazon2023-arm64-latest-large - name: .fuzzer_deterministic @@ -219,8 +225,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags !.multiversion !.suggested_excluding_required__for_devprod_mitigation_only distros: - amazon2023-arm64-atlas-latest-large - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags !.multiversion !.suggested_excluding_required__for_devprod_mitigation_only - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags !.multiversion !.suggested_excluding_required__for_devprod_mitigation_only + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags !.multiversion !.suggested_excluding_required__for_devprod_mitigation_only + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags !.multiversion !.suggested_excluding_required__for_devprod_mitigation_only distros: - amazon2023-arm64-atlas-latest-large - name: .fuzzer_deterministic @@ -524,8 +530,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.multiversion distros: - amazon2023-arm64-latest-large - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.multiversion - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.multiversion + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.multiversion + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.suggested_excluding_required__for_devprod_mitigation_only !.multiversion distros: - amazon2023-arm64-latest-large - name: .fuzzer_deterministic !.multiversion @@ -548,8 +554,8 @@ buildvariants: # - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.multiversion !.serverless !.exclude_when_record_ids_replicated # distros: # - amazon2023-arm64-atlas-latest-large - # - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.multiversion !.serverless !.exclude_when_record_ids_replicated - # - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.multiversion !.serverless !.exclude_when_record_ids_replicated + # - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.multiversion !.serverless !.exclude_when_record_ids_replicated + # - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags !.multiversion !.serverless !.exclude_when_record_ids_replicated # distros: # - amazon2023-arm64-atlas-latest-large # expansions: diff --git a/etc/evergreen_yml_components/variants/rhel/test_dev.yml b/etc/evergreen_yml_components/variants/rhel/test_dev.yml index fa333c788f8..4cfe4c08685 100644 --- a/etc/evergreen_yml_components/variants/rhel/test_dev.yml +++ b/etc/evergreen_yml_components/variants/rhel/test_dev.yml @@ -129,8 +129,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags distros: - rhel8.8-medium - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.requires_all_feature_flags distros: - rhel8.8-medium diff --git a/etc/evergreen_yml_components/variants/rhel/test_dev_master_branch_only.yml b/etc/evergreen_yml_components/variants/rhel/test_dev_master_branch_only.yml index 1d3ea65f7a5..3f98bcf77ee 100644 --- a/etc/evergreen_yml_components/variants/rhel/test_dev_master_branch_only.yml +++ b/etc/evergreen_yml_components/variants/rhel/test_dev_master_branch_only.yml @@ -303,8 +303,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags distros: - rhel8.8-medium - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_all_feature_flags distros: - rhel8.8-medium diff --git a/etc/evergreen_yml_components/variants/sanitizer/test_dev_master_branch_only.yml b/etc/evergreen_yml_components/variants/sanitizer/test_dev_master_branch_only.yml index b6197f3e94c..3a724527e32 100644 --- a/etc/evergreen_yml_components/variants/sanitizer/test_dev_master_branch_only.yml +++ b/etc/evergreen_yml_components/variants/sanitizer/test_dev_master_branch_only.yml @@ -226,11 +226,11 @@ buildvariants: - name: .release_critical .requires_large_host_debug_mode !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags distros: - *enterprise-rhel-8-64-bit-dynamic-debug-mode-large-distro-name - - name: .default !.requires_large_host !.requires_large_host_debug_mode !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags - - name: .default .requires_large_host !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags + - name: .default !.requires_large_host !.requires_large_host_debug_mode !.uses_docker !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags distros: - *enterprise-rhel-8-64-bit-dynamic-debug-mode-large-distro-name - - name: .default .requires_large_host_debug_mode !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags + - name: .default .requires_large_host_debug_mode !.uses_docker !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags distros: - *enterprise-rhel-8-64-bit-dynamic-debug-mode-large-distro-name - name: .non_deterministic !.requires_large_host !.requires_large_host_debug_mode !.incompatible_development_variant !.incompatible_debug_mode !.incompatible_system_allocator !.requires_all_feature_flags @@ -480,8 +480,8 @@ buildvariants: - name: .release_critical .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags distros: - rhel8.8-xlarge - - name: .default !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags + - name: .default !.requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags distros: - rhel8.8-xlarge - name: .non_deterministic !.requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_aubsan !.incompatible_system_allocator !.incompatible_all_feature_flags @@ -583,11 +583,11 @@ buildvariants: - name: .release_critical .requires_large_host_tsan !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags distros: - *enterprise-rhel8-debug-tsan-large-distro-name - - name: .default !.requires_large_host !.requires_large_host_tsan !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags - - name: .default .requires_large_host !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags + - name: .default !.requires_large_host !.requires_large_host_tsan !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags + - name: .default .requires_large_host !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags distros: - *enterprise-rhel8-debug-tsan-large-distro-name - - name: .default .requires_large_host_tsan !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags + - name: .default .requires_large_host_tsan !.uses_docker !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags distros: - *enterprise-rhel8-debug-tsan-large-distro-name - name: .non_deterministic !.requires_large_host !.requires_large_host_tsan !.requires_compile_variant !.incompatible_development_variant !.incompatible_tsan !.incompatible_system_allocator !.incompatible_all_feature_flags diff --git a/evergreen/build_and_push_module_images.sh b/evergreen/build_and_push_module_images.sh new file mode 100644 index 00000000000..299d7ec8c36 --- /dev/null +++ b/evergreen/build_and_push_module_images.sh @@ -0,0 +1,7 @@ +set -e + +for dir in ./src/src/mongo/db/modules/*; do + if test -f $dir/evergreen/build_and_push_images.sh; then + bash $dir/evergreen/build_and_push_images.sh + fi +done diff --git a/evergreen/fetch_module_images.sh b/evergreen/fetch_module_images.sh new file mode 100644 index 00000000000..11781249223 --- /dev/null +++ b/evergreen/fetch_module_images.sh @@ -0,0 +1,7 @@ +set -e + +for dir in ./src/src/mongo/db/modules/*; do + if test -f $dir/evergreen/fetch_images.sh; then + bash $dir/evergreen/fetch_images.sh + fi +done diff --git a/jstests/libs/OWNERS.yml b/jstests/libs/OWNERS.yml index 32c66ea7fc8..9fa000a2c9e 100644 --- a/jstests/libs/OWNERS.yml +++ b/jstests/libs/OWNERS.yml @@ -87,3 +87,9 @@ filters: - "json_utils.js": approvers: - 10gen/query-integration-extensions + - "replicated_ident_utils.js": + approvers: + - 10gen/server-storage-engine-integration + - "replicated_record_ids_utils.js": + approvers: + - 10gen/server-storage-engine-integration diff --git a/jstests/libs/collection_write_path/replicated_record_ids_utils.js b/jstests/libs/collection_write_path/replicated_record_ids_utils.js index 209978cc7a2..6f657e46154 100644 --- a/jstests/libs/collection_write_path/replicated_record_ids_utils.js +++ b/jstests/libs/collection_write_path/replicated_record_ids_utils.js @@ -2,7 +2,7 @@ // documents, otherwise. import {ReplSetTest} from "jstests/libs/replsettest.js"; -function getShowRecordIdsCursor(node, dbName, replicatedCollName) { +export function getShowRecordIdsCursor(node, dbName, replicatedCollName) { return node .getDB(dbName) [replicatedCollName].aggregate([{"$project": {"recordId": {"$meta": "recordId"}, "document": "$$ROOT"}}]); diff --git a/jstests/libs/replicated_ident_utils.js b/jstests/libs/replicated_ident_utils.js new file mode 100644 index 00000000000..830e97f3e66 --- /dev/null +++ b/jstests/libs/replicated_ident_utils.js @@ -0,0 +1,86 @@ +/* + * Helpers for basic testing of replicated idents. + */ +function getOplog(node) { + return node.getDB("local").oplog.rs; +} + +export function getSortedCatalogEntries(node, sortField = "ident") { + const adminDB = node.getDB("admin"); + const isSystemProfile = {"name": "system.profile"}; + const isLocal = {"db": "local"}; + const match = {$nor: [isSystemProfile, isLocal]}; + return adminDB.aggregate([{$listCatalog: {}}, {$match: match}, {$sort: {sortField: 1}}]).toArray(); +} +/** + * Given catalog entries for 2 nodes, where catalog entries for both nodes must be sorted by the + * same field, validates that each entry has a matching 'ident'. + */ +export function assertMatchingCatalogIdents(node0CatalogIdents, node1CatalogIdents) { + jsTest.log( + `Asserting catalog entries for node0 ${tojson(node0CatalogIdents)} with node1 ${tojson(node1CatalogIdents)}`, + ); + assert.eq( + node0CatalogIdents.length, + node1CatalogIdents.length, + `Expected nodes to have same number of entries. Entries for node0 ${tojson( + node0CatalogIdents, + )}, entries for node1 ${node1CatalogIdents}`, + ); + + const numCatalogEntries = node0CatalogIdents.length; + const entriesThatDontMatch = []; + for (let i = 0; i < numCatalogEntries; i++) { + const entryNode0 = node0CatalogIdents[i]; + const entryNode1 = node1CatalogIdents[i]; + + if (bsonWoCompare(entryNode0, entryNode1) !== 0) { + // For visibility, collect all mismatched entries before failing. + entriesThatDontMatch.push([entryNode0, entryNode1]); + jsTest.log( + `Expected both nodes to have same entries. Node0 has ${tojson( + entryNode0, + )}, Node1 has ${tojson(entryNode1)}`, + ); + } + } + + assert.eq( + 0, + entriesThatDontMatch.length, + `Catalog entries for were expected to match, but don't. Entries that don't match ${tojson( + entriesThatDontMatch, + )}`, + ); +} + +// Validates that all 'create' collection oplog entries contain collection idents. +export function assertCreateOplogEntriesContainIdents(node) { + const createOps = getOplog(node) + .find({"op": "c", "o.create": {$exists: true}}) + .toArray(); + jsTest.log("Create oplog entries on node " + node.port + " " + tojson(createOps)); + assert.lt(0, createOps.length); + for (let op of createOps) { + assert( + op.hasOwnProperty("o2"), + `Expected to have 'o2' field present in ${tojson( + op, + )}. Dumping all create oplog entries ${tojson(createOps)}`, + ); + + const o2 = op["o2"]; + assert( + o2.hasOwnProperty("ident"), + `Expected to find 'ident' property in 'o2' field of ${tojson( + op, + )}. Dumping all create oplog entries ${tojson(createOps)}`, + ); + assert( + o2.hasOwnProperty("idIndexIdent"), + `Expected to find 'iddIndexIdent' property in 'o2' field of ${tojson( + op, + )}. Dumping all create oplog entries ${tojson(createOps)}`, + ); + } +} diff --git a/jstests/libs/replsettest.js b/jstests/libs/replsettest.js index f81be63881d..e2f1b0fdc76 100644 --- a/jstests/libs/replsettest.js +++ b/jstests/libs/replsettest.js @@ -1768,6 +1768,28 @@ export class ReplSetTest { }); } + /** + * Runs replSetInitiate on the first node of the replica set. + * + * TODO (SERVER-109841): Replsetinitiate is currently a no-op command for disagg. Determine the + * next steps for this function if additional functionality is to be incorporated. + */ + initiateForDisagg(cfg, initCmd) { + const startTime = new Date(); // Measure the execution time of this function. + + // Blocks until there is a primary. We use a faster retry interval here since we expect the + // primary to be ready very soon. We also turn the failpoint off once we have a primary. + this.getPrimary(this.kDefaultTimeoutMS, 25 /* retryIntervalMS */); + + jsTest.log( + "ReplSetTest initiateForDisagg took " + + (new Date() - startTime) + + "ms for " + + this.nodes.length + + " nodes.", + ); + } + /** * Steps up 'node' as primary and by default it waits for the stepped up node to become a * writable primary and waits for all nodes to reach the same optime before sending the @@ -3589,7 +3611,7 @@ function _constructStartNewInstances(rst, opts) { rst._unbridgedPorts = Array.from({length: numNodes}, rst._allocatePortForNode); rst._unbridgedNodes = []; } else { - rst.ports = Array.from({length: numNodes}, rst._allocatePortForNode); + rst.ports = opts.ports || Array.from({length: numNodes}, rst._allocatePortForNode); } for (let i = 0; i < numNodes; i++) { diff --git a/modules_poc/modules.yaml b/modules_poc/modules.yaml index ea4cf4d2863..f7dbc2cd62d 100644 --- a/modules_poc/modules.yaml +++ b/modules_poc/modules.yaml @@ -530,6 +530,7 @@ encrypted_storage_engine: slack: server-security jira: Server Security files: + - src/mongo/db/modules/atlas/src/disagg_storage/encryption - src/mongo/db/modules/enterprise/src/encryptdb security: @@ -854,8 +855,7 @@ disagg_storage: slack: disaggregated-storage-mongod jira: RSSD files: - - src/mongo/db/modules/atlas - - src/mongo/db/disagg_storage + - src/mongo/db/modules/atlas/src/disagg_storage storage_engine_api: meta: @@ -992,6 +992,13 @@ installer: files: - src/mongo/installer/ +replicated_storage_service: + meta: + slack: server-replication + jira: Server Replication + files: + - src/mongo/db/rss + replication: meta: slack: server-replication diff --git a/sbom.json b/sbom.json index 82a43bad8df..a125ec9895e 100644 --- a/sbom.json +++ b/sbom.json @@ -914,6 +914,49 @@ }, "scope": "required" }, + { + "supplier": { + "name": "Organization: github" + }, + "name": "googletest", + "version": "1.17.0", + "licenses": [ + { + "license": { + "id": "BSD-3-Clause" + } + } + ], + "purl": "pkg:github/googletest/googletest@v1.17.0", + "properties": [ + { + "name": "internal:team_responsible", + "value": "Disaggregated Storage" + }, + { + "name": "emits_persisted_data", + "value": "false" + }, + { + "name": "info_link", + "value": "https://github.com/google/googletest" + }, + { + "name": "import_script_path", + "value": "src/third_party/googletest_restricted_for_disagg_only/scripts/import.sh" + } + ], + "type": "library", + "bom-ref": "e57f94bd-b0b1-4e47-912e-c690a01e4f95", + "evidence": { + "occurrences": [ + { + "location": "src/third_party/googletest_restricted_for_disagg_only" + } + ] + }, + "scope": "required" + }, { "type": "library", "bom-ref": "pkg:github/gperftools/gperftools@gperftools-2.9.1", diff --git a/src/mongo/BUILD.bazel b/src/mongo/BUILD.bazel index 8b87ae59ed8..6d61ec0a9f6 100644 --- a/src/mongo/BUILD.bazel +++ b/src/mongo/BUILD.bazel @@ -21,6 +21,11 @@ generate_config_header( "MONGO_CONFIG_OTEL": "1", }, "//conditions:default": {}, + }) | select({ + "//bazel/config:build_atlas_enabled": { + "MONGO_CONFIG_DISAGG_STORAGE": "1", + }, + "//conditions:default": {}, }) | select({ "//bazel/config:mutex_observation_enabled": { "MONGO_CONFIG_MUTEX_OBSERVATION": "1", diff --git a/src/mongo/config.h.in b/src/mongo/config.h.in index 66ee67d8e4c..c0f864e4d42 100644 --- a/src/mongo/config.h.in +++ b/src/mongo/config.h.in @@ -125,5 +125,8 @@ // Defined if the build includes OpenTelemetry @mongo_config_otel@ +// Defined if the build includes disaggregated storage +@mongo_config_disagg_storage@ + // Defined if the build includes mutex observation @mongo_config_mutex_observation@ diff --git a/src/mongo/db/BUILD.bazel b/src/mongo/db/BUILD.bazel index f8bb96c1cc4..7ab09f1eb68 100644 --- a/src/mongo/db/BUILD.bazel +++ b/src/mongo/db/BUILD.bazel @@ -3320,6 +3320,8 @@ mongo_cc_library( "//src/mongo/db/repl:serveronly_repl", "//src/mongo/db/repl:storage_interface_impl", "//src/mongo/db/repl:topology_coordinator", + "//src/mongo/db/rss:persistence_provider_impl", + "//src/mongo/db/rss:service_lifecycle_impl", "rw_concern_d", "//src/mongo/db/session:kill_sessions_local", "//src/mongo/db/session:service_liaison_mongod", @@ -3433,6 +3435,11 @@ mongo_cc_library( "//src/mongo/db/modules/enterprise/src/kmip:kmip_configuration", ], "//conditions:default": [], + }) + select({ + "//bazel/config:build_atlas_enabled": [ + "//src/mongo/db/modules/atlas/src/disagg_storage/encryption:sls_log_encryption_manager", + ], + "//conditions:default": [], }), ) @@ -3530,6 +3537,7 @@ mongo_cc_library( "//src/mongo/db/repl:storage_interface_impl", "//src/mongo/db/repl:topology_coordinator", "//src/mongo/db/repl:wait_for_majority_service", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/s:query_analysis_writer", "//src/mongo/db/s:sessions_collection_config_server", "//src/mongo/db/s:sharding_commands_d", @@ -3563,11 +3571,6 @@ mongo_cc_library( "//src/mongo/util/tracing_profiler", ], "//conditions:default": [], - }) + select({ - "//bazel/config:build_atlas_required_settings": [ - "//src/mongo/db/modules/atlas:atlas_only", - ], - "//conditions:default": [], }), ) @@ -3697,6 +3700,7 @@ mongo_cc_library( "//src/mongo/db/local_catalog:catalog_impl", "//src/mongo/db/op_observer", "//src/mongo/db/repl:replmocks", + "//src/mongo/db/rss:persistence_provider_impl", "//src/mongo/db/s:sharding_runtime_d", "//src/mongo/db/storage:storage_control", "//src/mongo/db/storage:storage_options", @@ -4138,6 +4142,7 @@ mongo_cc_library( "//src/mongo/db/local_catalog:database_holder", "//src/mongo/db/op_observer", "//src/mongo/db/repl:replmocks", + "//src/mongo/db/rss:persistence_provider_impl", "//src/mongo/db/s:sharding_runtime_d", "//src/mongo/db/stats:top", "//src/mongo/db/storage:storage_control", @@ -4369,6 +4374,7 @@ mongo_cc_benchmark( "//src/mongo/db/repl:repl_coordinator_impl", "//src/mongo/db/repl:serveronly_repl", "//src/mongo/db/repl:storage_interface_impl", + "//src/mongo/db/rss:persistence_provider_impl", "//src/mongo/db/s:sharding_runtime_d", "//src/mongo/db/storage:storage_control", "//src/mongo/db/storage/wiredtiger:storage_wiredtiger", diff --git a/src/mongo/db/admission/flow_control.cpp b/src/mongo/db/admission/flow_control.cpp index 93310d5a8c3..2a0080b2bd3 100644 --- a/src/mongo/db/admission/flow_control.cpp +++ b/src/mongo/db/admission/flow_control.cpp @@ -127,24 +127,62 @@ Timestamp getMedianAppliedTimestamp(const std::vector& sortedM const int sustainerIdx = sortedMemberData.size() / 2; return sortedMemberData[sustainerIdx].getLastAppliedOpTime().getTimestamp(); } +} // namespace + +namespace flow_control_details { +ReplicationTimestampProvider::ReplicationTimestampProvider(repl::ReplicationCoordinator* replCoord) + : _replCoord(replCoord) {} + +Timestamp ReplicationTimestampProvider::getCurrSustainerTimestamp() const { + return getMedianAppliedTimestamp(_currMemberData); +} + +Timestamp ReplicationTimestampProvider::getPrevSustainerTimestamp() const { + return getMedianAppliedTimestamp(_prevMemberData); +} + +repl::TimestampAndWallTime ReplicationTimestampProvider::getTargetTimestampAndWallTime() const { + auto time = _replCoord->getLastCommittedOpTimeAndWallTime(); + return {.timestamp = time.opTime.getTimestamp(), .wallTime = time.wallTime}; +} + +repl::TimestampAndWallTime ReplicationTimestampProvider::getLastWriteTimestampAndWallTime() const { + auto time = _replCoord->getMyLastAppliedOpTimeAndWallTime(); + return {.timestamp = time.opTime.getTimestamp(), .wallTime = time.wallTime}; +} + +void ReplicationTimestampProvider::update() { + _prevMemberData = _currMemberData; + _currMemberData = _replCoord->getMemberData(); + + // Sort MemberData with the 0th index being the node with the lowest applied optime. + std::sort(_currMemberData.begin(), + _currMemberData.end(), + [](const repl::MemberData& left, const repl::MemberData& right) -> bool { + return left.getLastAppliedOpTime() < right.getLastAppliedOpTime(); + }); +} + +bool ReplicationTimestampProvider::flowControlUsable() const { + return _replCoord->canAcceptNonLocalWrites(); +} /** * Sanity checks whether the successive queries of topology data are comparable for doing a flow * control calculation. In particular, the number of members must be the same and the median * applier's timestamp must not go backwards. */ -bool sustainerAdvanced(const std::vector& prevMemberData, - const std::vector& currMemberData) { - if (currMemberData.size() == 0 || currMemberData.size() != prevMemberData.size()) { +bool ReplicationTimestampProvider::sustainerAdvanced() const { + if (_currMemberData.size() == 0 || _currMemberData.size() != _prevMemberData.size()) { LOGV2_WARNING(22223, "Flow control detected a change in topology", - "prevSize"_attr = prevMemberData.size(), - "currSize"_attr = currMemberData.size()); + "prevSize"_attr = _prevMemberData.size(), + "currSize"_attr = _currMemberData.size()); return false; } - auto currSustainerAppliedTs = getMedianAppliedTimestamp(currMemberData); - auto prevSustainerAppliedTs = getMedianAppliedTimestamp(prevMemberData); + auto currSustainerAppliedTs = getMedianAppliedTimestamp(_currMemberData); + auto prevSustainerAppliedTs = getMedianAppliedTimestamp(_prevMemberData); if (currSustainerAppliedTs < prevSustainerAppliedTs) { LOGV2_WARNING(22224, @@ -156,13 +194,42 @@ bool sustainerAdvanced(const std::vector& prevMemberData, return true; } -} // namespace + +void ReplicationTimestampProvider::setCurrMemberData_forTest( + const std::vector& memberData) { + _currMemberData = memberData; + std::sort(_currMemberData.begin(), + _currMemberData.end(), + [](const repl::MemberData& left, const repl::MemberData& right) -> bool { + return left.getLastAppliedOpTime() < right.getLastAppliedOpTime(); + }); +} + +void ReplicationTimestampProvider::setPrevMemberData_forTest( + const std::vector& memberData) { + _prevMemberData = memberData; + std::sort(_prevMemberData.begin(), + _prevMemberData.end(), + [](const repl::MemberData& left, const repl::MemberData& right) -> bool { + return left.getLastAppliedOpTime() < right.getLastAppliedOpTime(); + }); +} + +} // namespace flow_control_details FlowControl::FlowControl(repl::ReplicationCoordinator* replCoord) - : _replCoord(replCoord), _lastTimeSustainerAdvanced(Date_t::now()) {} + : _timestampProvider( + std::make_unique(replCoord)), + _lastTimeSustainerAdvanced(Date_t::now()) {} FlowControl::FlowControl(ServiceContext* service, repl::ReplicationCoordinator* replCoord) - : _replCoord(replCoord), _lastTimeSustainerAdvanced(Date_t::now()) { + : FlowControl(service, + std::make_unique(replCoord)) { +} + +FlowControl::FlowControl(ServiceContext* service, + std::unique_ptr timestampProvider) + : _timestampProvider(std::move(timestampProvider)), _lastTimeSustainerAdvanced(Date_t::now()) { // Initialize _lastTargetTicketsPermitted to maximum tickets to make sure flow control doesn't // cause a slow start on start up. FlowControlTicketholder::set(service, std::make_unique(kMaxTickets)); @@ -254,44 +321,26 @@ void FlowControl::disableUntil(Date_t deadline) { _disableUntil.store(deadline); } -/** - * Advance the `_*MemberData` fields and sort the new data by the element's last applied optime. - */ -void FlowControl::_updateTopologyData() { - _prevMemberData = _currMemberData; - _currMemberData = _replCoord->getMemberData(); - - // Sort MemberData with the 0th index being the node with the lowest applied optime. - std::sort(_currMemberData.begin(), - _currMemberData.end(), - [](const repl::MemberData& left, const repl::MemberData& right) -> bool { - return left.getLastAppliedOpTime() < right.getLastAppliedOpTime(); - }); -} - -int FlowControl::_calculateNewTicketsForLag(const std::vector& prevMemberData, - const std::vector& currMemberData, +int FlowControl::_calculateNewTicketsForLag(const Timestamp& prevSustainerTimestamp, + const Timestamp& currSustainerTimestamp, std::int64_t locksUsedLastPeriod, double locksPerOp, std::uint64_t lagMillis, std::uint64_t thresholdLagMillis) { + invariant(prevSustainerTimestamp <= currSustainerTimestamp, + fmt::format("PrevSustainer: {} CurrSustainer: {}", + prevSustainerTimestamp.toString(), + currSustainerTimestamp.toString())); invariant(lagMillis >= thresholdLagMillis); - const auto currSustainerAppliedTs = getMedianAppliedTimestamp(currMemberData); - const auto prevSustainerAppliedTs = getMedianAppliedTimestamp(prevMemberData); - invariant(prevSustainerAppliedTs <= currSustainerAppliedTs, - fmt::format("PrevSustainer: {} CurrSustainer: {}", - prevSustainerAppliedTs.toString(), - currSustainerAppliedTs.toString())); - const std::int64_t sustainerAppliedCount = - _approximateOpsBetween(prevSustainerAppliedTs, currSustainerAppliedTs); + _approximateOpsBetween(prevSustainerTimestamp, currSustainerTimestamp); LOGV2_DEBUG(22218, DEBUG_LOG_LEVEL, - " PrevApplied: {prevSustainerAppliedTs} CurrApplied: {currSustainerAppliedTs} " + " PrevApplied: {prevSustainerTimestamp} CurrApplied: {currSustainerTimestamp} " "NumSustainerApplied: {sustainerAppliedCount}", - "prevSustainerAppliedTs"_attr = prevSustainerAppliedTs, - "currSustainerAppliedTs"_attr = currSustainerAppliedTs, + "prevSustainerTimestamp"_attr = prevSustainerTimestamp, + "currSustainerTimestamp"_attr = currSustainerTimestamp, "sustainerAppliedCount"_attr = sustainerAppliedCount); if (sustainerAppliedCount > 0) { _lastTimeSustainerAdvanced = Date_t::now(); @@ -359,35 +408,35 @@ int FlowControl::getNumTickets(Date_t now) { } // Flow Control is only enabled on nodes that can accept writes. - const bool canAcceptWrites = _replCoord->canAcceptNonLocalWrites(); + const bool flowControlUsable = _timestampProvider->flowControlUsable(); if (auto sfp = flowControlTicketOverride.scoped(); MONGO_unlikely(sfp.isActive())) { int numTickets = sfp.getData().getIntField("numTickets"); - if (numTickets > 0 && canAcceptWrites) { + if (numTickets > 0 && flowControlUsable) { return numTickets; } } // It's important to update the topology on each iteration. - _updateTopologyData(); - const repl::OpTimeAndWallTime myLastApplied = _replCoord->getMyLastAppliedOpTimeAndWallTime(); - const repl::OpTimeAndWallTime lastCommitted = _replCoord->getLastCommittedOpTimeAndWallTime(); + _timestampProvider->update(); + const auto lastWriteTime = _timestampProvider->getLastWriteTimestampAndWallTime(); + const auto lastTargetTime = _timestampProvider->getTargetTimestampAndWallTime(); const double locksPerOp = _getLocksPerOp(); const std::int64_t locksUsedLastPeriod = _getLocksUsedLastPeriod(); - if (gFlowControlEnabled.load() == false || canAcceptWrites == false || locksPerOp < 0.0) { - _trimSamples(std::min(lastCommitted.opTime.getTimestamp(), - getMedianAppliedTimestamp(_prevMemberData))); + if (gFlowControlEnabled.load() == false || flowControlUsable == false || locksPerOp < 0.0) { + _trimSamples( + std::min(lastTargetTime.timestamp, _timestampProvider->getPrevSustainerTimestamp())); return kMaxTickets; } int ret = 0; const auto thresholdLagMillis = getThresholdLagMillis(); - // Successive lastCommitted and lastApplied wall clock time recordings are not guaranteed to be + // Successive lastTargetTime and lastApplied wall clock time recordings are not guaranteed to be // monotonically increasing. Recordings that satisfy the following check result in a negative // value for lag, so ignore them. - const bool ignoreWallTimes = lastCommitted.wallTime > myLastApplied.wallTime; + const bool ignoreWallTimes = lastTargetTime.wallTime > lastWriteTime.wallTime; // _approximateOpsBetween will return -1 if the input timestamps are in the same "bucket". // This is an indication that there are very few ops between the two timestamps. @@ -395,9 +444,8 @@ int FlowControl::getNumTickets(Date_t now) { // Don't let the no-op writer on idle systems fool the sophisticated "is the replica set // lagged" classifier. const bool isHealthy = !ignoreWallTimes && - (getLagMillis(myLastApplied.wallTime, lastCommitted.wallTime) < thresholdLagMillis || - _approximateOpsBetween(lastCommitted.opTime.getTimestamp(), - myLastApplied.opTime.getTimestamp()) == -1); + (getLagMillis(lastWriteTime.wallTime, lastTargetTime.wallTime) < thresholdLagMillis || + _approximateOpsBetween(lastTargetTime.timestamp, lastWriteTime.timestamp) == -1); if (isHealthy) { // The add/multiply technique is used to ensure ticket allocation can ramp up quickly, @@ -412,16 +460,16 @@ int FlowControl::getNumTickets(Date_t now) { auto waitTime = curTimeMicros64() - _startWaitTime; _isLaggedTimeMicros.fetchAndAddRelaxed(waitTime); } - } else if (!ignoreWallTimes && sustainerAdvanced(_prevMemberData, _currMemberData)) { + } else if (!ignoreWallTimes && _timestampProvider->sustainerAdvanced()) { // Expected case where flow control has meaningful data from the last period to make a new // calculation. - ret = - _calculateNewTicketsForLag(_prevMemberData, - _currMemberData, - locksUsedLastPeriod, - locksPerOp, - getLagMillis(myLastApplied.wallTime, lastCommitted.wallTime), - thresholdLagMillis); + ret = _calculateNewTicketsForLag( + _timestampProvider->getPrevSustainerTimestamp(), + _timestampProvider->getCurrSustainerTimestamp(), + locksUsedLastPeriod, + locksPerOp, + getLagMillis(lastWriteTime.wallTime, lastTargetTime.wallTime), + thresholdLagMillis); if (!_isLagged.load()) { _isLagged.store(true); _isLaggedCount.fetchAndAddRelaxed(1); @@ -443,9 +491,10 @@ int FlowControl::getNumTickets(Date_t now) { DEBUG_LOG_LEVEL, "FlowControl debug.", "isLagged"_attr = (_isLagged.load() ? "true" : "false"), - "currlagMillis"_attr = getLagMillis(myLastApplied.wallTime, lastCommitted.wallTime), - "opsLagged"_attr = _approximateOpsBetween(lastCommitted.opTime.getTimestamp(), - myLastApplied.opTime.getTimestamp()), + "currlagMillis"_attr = + getLagMillis(lastWriteTime.wallTime, lastTargetTime.wallTime), + "opsLagged"_attr = + _approximateOpsBetween(lastTargetTime.timestamp, lastWriteTime.timestamp), "granting"_attr = ret, "lastGranted"_attr = _lastTargetTicketsPermitted.load(), "lastSustainerApplied"_attr = _lastSustainerAppliedCount.load(), @@ -457,7 +506,7 @@ int FlowControl::getNumTickets(Date_t now) { _lastTargetTicketsPermitted.store(ret); _trimSamples( - std::min(lastCommitted.opTime.getTimestamp(), getMedianAppliedTimestamp(_prevMemberData))); + std::min(lastTargetTime.timestamp, _timestampProvider->getPrevSustainerTimestamp())); return ret; } diff --git a/src/mongo/db/admission/flow_control.h b/src/mongo/db/admission/flow_control.h index 0c1f23226ab..8ac4ef06ab9 100644 --- a/src/mongo/db/admission/flow_control.h +++ b/src/mongo/db/admission/flow_control.h @@ -34,6 +34,7 @@ #include "mongo/bson/timestamp.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/member_data.h" +#include "mongo/db/repl/optime.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/replication_coordinator_fwd.h" #include "mongo/db/service_context.h" @@ -62,12 +63,66 @@ namespace mongo { */ class FlowControl { public: + class TimestampProvider { + public: + virtual ~TimestampProvider() = default; + /** + * The sustainer timestamp is the timestamp which, if moved forward, will cause an + * advance in the target timestamp. For replication, it is the median applied timestamp + * on all the relevant nodes. We need to know this timestamp both for the current iteration + * and the previous iteration. + */ + virtual Timestamp getCurrSustainerTimestamp() const = 0; + virtual Timestamp getPrevSustainerTimestamp() const = 0; + + /** + * The target time is the time we are trying to throttle to. For replication, it is the + * last committed time (majority snapshot time). + */ + virtual repl::TimestampAndWallTime getTargetTimestampAndWallTime() const = 0; + + /** + * The last write time is what we are trying to control. For replication, it is + * the last applied time. + */ + virtual repl::TimestampAndWallTime getLastWriteTimestampAndWallTime() const = 0; + + /** + * Is flow control possible with this timestamp provider? For replication, + * true if this is a primary and majority read concern is enabled. + */ + virtual bool flowControlUsable() const = 0; + + /** + * Are the previous and current updates compatible? For replication, + * makes sure number of nodes is the same and the median node timestamp (the sustainer) + * has not gone backwards. + */ + virtual bool sustainerAdvanced() const = 0; + + /** + * Advance the `_*MemberData` fields and sort the new data by the element's last applied + * optime. + */ + virtual void update() = 0; + }; + static constexpr int kMaxTickets = 1000 * 1000 * 1000; + /** + * Construct a flow control object based on a custom timestamp provider. + * Takes ownership of the timestamp provider. + */ + FlowControl(ServiceContext* service, std::unique_ptr timestampProvider); + + /** + * Construct a replication-based flow control object. + */ FlowControl(ServiceContext* service, repl::ReplicationCoordinator* replCoord); /** - * Construct a flow control object without adding a periodic job runner for testing. + * Construct a replication-based flow control object without adding a periodic job runner for + * testing. */ FlowControl(repl::ReplicationCoordinator* replCoord); @@ -122,13 +177,13 @@ public: std::int64_t _approximateOpsBetween(Timestamp prevTs, Timestamp currTs); - void _updateTopologyData(); - int _calculateNewTicketsForLag(const std::vector& prevMemberData, - const std::vector& currMemberData, + int _calculateNewTicketsForLag(const Timestamp& prevSustainerTimestamp, + const Timestamp& currSustainerTimestamp, std::int64_t locksUsedLastPeriod, double locksPerOp, std::uint64_t lagMillis, std::uint64_t thresholdLagMillis); + void _trimSamples(Timestamp trimSamplesTo); // Sample of (timestamp, ops, lock acquisitions) where ops and lock acquisitions are @@ -139,7 +194,7 @@ public: } private: - repl::ReplicationCoordinator* _replCoord; + std::unique_ptr _timestampProvider; // These values are updated with each flow control computation and are also surfaced in server // status. @@ -161,9 +216,6 @@ private: std::int64_t _lastPollLockAcquisitions = 0; - std::vector _currMemberData; - std::vector _prevMemberData; - Date_t _lastTimeSustainerAdvanced; // This value is used for calculating server status metrics. @@ -172,4 +224,27 @@ private: PeriodicJobAnchor _jobAnchor; }; +namespace flow_control_details { +class ReplicationTimestampProvider final : public FlowControl::TimestampProvider { +public: + explicit ReplicationTimestampProvider(repl::ReplicationCoordinator* replCoord); + Timestamp getCurrSustainerTimestamp() const final; + Timestamp getPrevSustainerTimestamp() const final; + repl::TimestampAndWallTime getTargetTimestampAndWallTime() const final; + repl::TimestampAndWallTime getLastWriteTimestampAndWallTime() const final; + bool flowControlUsable() const final; + bool sustainerAdvanced() const final; + void update() final; + + void setCurrMemberData_forTest(const std::vector& memberData); + void setPrevMemberData_forTest(const std::vector& memberData); + +private: + repl::ReplicationCoordinator* _replCoord; + std::vector _currMemberData; + std::vector _prevMemberData; +}; + +} // namespace flow_control_details + } // namespace mongo diff --git a/src/mongo/db/admission/flow_control_test.cpp b/src/mongo/db/admission/flow_control_test.cpp index 44fd2d43d9a..2aba8d6f877 100644 --- a/src/mongo/db/admission/flow_control_test.cpp +++ b/src/mongo/db/admission/flow_control_test.cpp @@ -241,6 +241,15 @@ TEST_F(FlowControlTest, CalculatingTickets) { currMemberData.emplace_back(constructMemberData(Timestamp(2000))); currMemberData.emplace_back(constructMemberData(Timestamp(3000))); + flow_control_details::ReplicationTimestampProvider timestampProvider(replCoordMock); + timestampProvider.setPrevMemberData_forTest(prevMemberData); + timestampProvider.setCurrMemberData_forTest(currMemberData); + auto prevSustainerTimestamp = timestampProvider.getPrevSustainerTimestamp(); + auto currSustainerTimestamp = timestampProvider.getCurrSustainerTimestamp(); + + ASSERT_EQ(Timestamp(1000), prevSustainerTimestamp); + ASSERT_EQ(Timestamp(2000), currSustainerTimestamp); + // Construct samples where Timestamp X maps to operation number X. for (int ts = 1; ts <= 3000; ++ts) { flowControl->sample(Timestamp(ts), 1); @@ -251,8 +260,8 @@ TEST_F(FlowControlTest, CalculatingTickets) { const std::uint64_t thresholdLag = 1; const std::uint64_t currLag = thresholdLag; ASSERT_EQ(1900, - flowControl->_calculateNewTicketsForLag(prevMemberData, - currMemberData, + flowControl->_calculateNewTicketsForLag(prevSustainerTimestamp, + currSustainerTimestamp, locksUsedLastPeriod, locksPerOp, currLag, diff --git a/src/mongo/db/auth/auth_op_observer.cpp b/src/mongo/db/auth/auth_op_observer.cpp index f5dbc5fc964..1500c37a639 100644 --- a/src/mongo/db/auth/auth_op_observer.cpp +++ b/src/mongo/db/auth/auth_op_observer.cpp @@ -39,6 +39,7 @@ #include "mongo/db/op_observer/op_observer_util.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/oplog_entry.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/util/assert_util.h" #include "mongo/util/decorable.h" #include "mongo/util/namespace_string_util.h" @@ -123,7 +124,9 @@ void AuthOpObserver::onCreateCollection( BSONObj o2; if (createCollCatalogIdentifier.has_value() && - shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))) { + shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))) { o2 = repl::MutableOplogEntry::makeCreateCollObject2( createCollCatalogIdentifier->catalogId, createCollCatalogIdentifier->ident, diff --git a/src/mongo/db/commands/BUILD.bazel b/src/mongo/db/commands/BUILD.bazel index 00019fc635d..e8595496d42 100644 --- a/src/mongo/db/commands/BUILD.bazel +++ b/src/mongo/db/commands/BUILD.bazel @@ -1471,6 +1471,7 @@ mongo_cc_library( "//src/mongo/db/repl:repl_server_parameters", "//src/mongo/db/repl:replica_set_messages", "//src/mongo/db/repl/dbcheck", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/s:sharding_catalog_manager", "//src/mongo/db/s:sharding_commands_d", "//src/mongo/db/s:transaction_coordinator", diff --git a/src/mongo/db/commands/shutdown_d.cpp b/src/mongo/db/commands/shutdown_d.cpp index 4f842ab644d..81533fa856b 100644 --- a/src/mongo/db/commands/shutdown_d.cpp +++ b/src/mongo/db/commands/shutdown_d.cpp @@ -35,6 +35,7 @@ #include "mongo/db/index_builds/index_builds_coordinator.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/s/transaction_coordinator_service.h" #include "mongo/logv2/log.h" #include "mongo/platform/compiler.h" @@ -71,7 +72,10 @@ Status stepDownForShutdown(OperationContext* opCtx, // Specify a high freeze time, so that if there is a stall during shut down, the node // does not run for election. - replCoord->stepDown(opCtx, false /* force */, waitTime, Days(1)); + auto& rss = rss::ReplicatedStorageService::get(opCtx); + if (rss.getPersistenceProvider().shouldStepDownForShutdown()) { + replCoord->stepDown(opCtx, false /* force */, waitTime, Days(1)); + } if (MONGO_unlikely(hangInShutdownAfterStepdown.shouldFail())) { LOGV2(4695100, "hangInShutdownAfterStepdown failpoint enabled"); diff --git a/src/mongo/db/disagg_storage/BUILD.bazel b/src/mongo/db/disagg_storage/BUILD.bazel deleted file mode 100644 index 4b97ce5876d..00000000000 --- a/src/mongo/db/disagg_storage/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("//bazel:mongo_src_rules.bzl", "idl_generator", "mongo_cc_benchmark", "mongo_cc_library", "mongo_cc_unit_test") - -package(default_visibility = ["//visibility:public"]) - -exports_files( - glob([ - "*.h", - "*.cpp", - ]), -) - -idl_generator( - name = "server_parameters_gen", - src = "server_parameters.idl", - deps = [ - "//src/mongo/db:basic_types_gen", - ], -) - -mongo_cc_library( - name = "server_parameters", - srcs = [ - "server_parameters_gen", - ], - hdrs = [ - ], - deps = [ - "//src/mongo/db:server_base", - "//src/mongo/idl:idl_parser", - ], -) diff --git a/src/mongo/db/disagg_storage/OWNERS.yml b/src/mongo/db/disagg_storage/OWNERS.yml deleted file mode 100644 index b8993a85562..00000000000 --- a/src/mongo/db/disagg_storage/OWNERS.yml +++ /dev/null @@ -1,5 +0,0 @@ -version: 1.0.0 -filters: - - "*": - approvers: - - 10gen/server-disagg-storage diff --git a/src/mongo/db/disagg_storage/server_parameters.idl b/src/mongo/db/disagg_storage/server_parameters.idl deleted file mode 100644 index 8f1e4031924..00000000000 --- a/src/mongo/db/disagg_storage/server_parameters.idl +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2025-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# . -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. -# - -# server setParameters for disaggregated storage - -global: - cpp_namespace: "mongo::disagg" - -imports: - - "mongo/db/basic_types.idl" - -server_parameters: - disaggregatedStorageEnabled: - description: >- - Set this to run the server as a compute node in a disaggregated storage cluster. - set_at: startup - cpp_vartype: bool - cpp_varname: gDisaggregatedStorageEnabled - default: false - redact: false diff --git a/src/mongo/db/exec/scoped_timer.h b/src/mongo/db/exec/scoped_timer.h index c40165a065e..c6f349b4f6a 100644 --- a/src/mongo/db/exec/scoped_timer.h +++ b/src/mongo/db/exec/scoped_timer.h @@ -158,7 +158,7 @@ private: X(abortAllTransactions) \ X(joinLogicalSessionCache) \ X(shutDownCursorManager) \ - X(shutDownSLSStateMachine) \ + X(shutDownStateRequiredForStorageAccess) \ /* For magic restore: */ \ X(magicRestoreToolTotal) \ X(readMagicRestoreConfig) \ diff --git a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp index c309d454378..27a903414e7 100644 --- a/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp +++ b/src/mongo/db/ftdc/ftdc_system_stats_linux.cpp @@ -33,20 +33,31 @@ #include "mongo/db/ftdc/collector.h" #include "mongo/db/ftdc/controller.h" #include "mongo/db/ftdc/ftdc_system_stats.h" +#include "mongo/logv2/log.h" #include "mongo/util/errno_util.h" #include "mongo/util/functional.h" #include "mongo/util/processinfo.h" #include "mongo/util/procparser.h" #include +#include #include +#include #include #include #include +#include + +#include +#include +#include +#include #include #include +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kFTDC + namespace mongo { namespace { @@ -102,6 +113,188 @@ static const std::map> kSockstatKeys{ {"TCP"_sd, {"inuse"_sd, "orphan"_sd, "tw"_sd, "alloc"_sd}}, }; +/** + * Class to gather NIC stats by emulating ethtool -S functionality by using the ioctl SIOCETHTOOL. + */ +class EthTool { +public: + static std::unique_ptr create(StringData interface) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + auto ec = lastPosixError(); + LOGV2_WARNING( + 10985539, "Ethtool socket allocation failed", "error"_attr = errorMessage(ec)); + return nullptr; + } + + auto ethtool = std::unique_ptr(new EthTool(interface, fd)); + auto drvinfo = ethtool->get_info(); + + // Some Linux interfaces cannot be found by ethtool IOCTL. + // Some Linux interfaces have no stats (i.e. the "bridge" driver used by containers). + if (!drvinfo.has_value() || drvinfo->n_stats == 0) { + LOGV2_WARNING(10985540, + "Skipping Ethtool stats collection for interface", + "interface"_attr = interface); + return nullptr; + } + + return ethtool; + } + + ~EthTool() { + free(_gstrings); + + close(_fd); + } + + // Get a list of all non-loopback interfaces for the machine + static std::vector interface_names() { + struct ifaddrs* ifaddr; + + if (getifaddrs(&ifaddr) == -1) { + auto ec = lastPosixError(); + uasserted(10985538, fmt::format("getifaddrs failed: {}", errorMessage(ec))); + } + ON_BLOCK_EXIT([&] { freeifaddrs(ifaddr); }); + + std::set names; + for (ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) { + continue; + } + + if ((ifa->ifa_flags & IFF_LOOPBACK) == IFF_LOOPBACK) { + continue; + } + + names.insert(ifa->ifa_name); + } + + std::vector vec; + std::copy(names.begin(), names.end(), std::back_inserter(vec)); + return vec; + } + + // Get a list of stats names for a given interface + std::vector& get_strings() { + if (!_names.has_value()) { + auto drvinfo = get_info(); + _get_strings(drvinfo->n_stats); + } + + return _names.get(); + } + + // Get a list of stats for a given interface + std::vector get_stats() { + if (!_names.has_value()) { + return std::vector(); + } + + return _get_stats(_names->size()); + } + + // Get a some basic information about the interface + boost::optional get_info() { + ethtool_drvinfo drvinfo; + memset(&drvinfo, 0, sizeof(drvinfo)); + drvinfo.cmd = ETHTOOL_GDRVINFO; + + if (_ioctlNoThrow("drvinfo", &drvinfo)) { + return boost::none; + } + + return boost::optional(drvinfo); + } + + // Name of the interface this class monitors + StringData name() const { + return _interface; + } + +private: + explicit EthTool(StringData interface, int fd) : _fd(fd), _interface(std::string(interface)) {} + + void _get_strings(size_t count) { + _gstrings = static_cast( + calloc(1, sizeof(ethtool_gstrings) + count * ETH_GSTRING_LEN)); + + _gstrings->cmd = ETHTOOL_GSTRINGS; + _gstrings->string_set = ETH_SS_STATS; + _gstrings->len = count; + + _names.emplace(std::vector()); + + if (_ioctlNoThrow("get_strings", _gstrings)) { + return; + } + + char* ptr = reinterpret_cast(_gstrings) + sizeof(ethtool_gstrings); + for (size_t i = 0; i < count; i++) { + auto s = StringData(ptr); + + _names->push_back(s); + + ptr += ETH_GSTRING_LEN; + } + } + + std::vector _get_stats(size_t count) { + std::vector stats_buf(sizeof(ethtool_stats) + count * 8, + 0); /* 8 is the number specfied in ethtool.h */ + + ethtool_stats* stats = reinterpret_cast(stats_buf.data()); + stats->cmd = ETHTOOL_GSTATS; + stats->n_stats = count; + + if (_ioctlNoThrow("get_stats", stats)) { + return std::vector(); + } + + char* ptr = reinterpret_cast(stats) + sizeof(ethtool_stats); + + std::vector stats_vec(ptr, ptr + count * 8); + + return stats_vec; + } + + // Returns non-zero on error + int _ioctlNoThrow(StringData name, void* cmd) { + ifreq ifr; + + strcpy(ifr.ifr_name, _interface.c_str()); + ifr.ifr_data = cmd; + + auto ret = ioctl(_fd, SIOCETHTOOL, &ifr); + + if (MONGO_unlikely(ret) && !_warningLogged) { + auto ec = lastPosixError(); + _warningLogged = true; + + LOGV2_WARNING(10985553, + "Failed to get strings for ethtool", + "interface"_attr = _interface, + "name"_attr = name, + "error"_attr = errorMessage(ec)); + } + + return ret; + } + +private: + int _fd; + + ethtool_gstrings* _gstrings{nullptr}; + + boost::optional> _names; + + std::string _interface; + + bool _warningLogged{false}; +}; + + /** * Collect metrics from the Linux /proc file system. */ @@ -111,6 +304,16 @@ public: for (const auto& disk : _disks) { _disksStringData.emplace_back(disk); } + + auto interfaces = EthTool::interface_names(); + + _ethtools.reserve(interfaces.size()); + for (const auto& ifn : interfaces) { + auto nic = EthTool::create(ifn); + if (nic) { + _ethtools.push_back(std::move(nic)); + } + } } void collect(OperationContext* opCtx, BSONObjBuilder& builder) override { @@ -219,6 +422,29 @@ public: &subObjBuilder); subObjBuilder.doneFast(); } + + { + BSONObjBuilder subObjBuilder(builder.subobjStart("ethtool"_sd)); + + for (auto& tool : _ethtools) { + BSONObjBuilder subNICBuilder(subObjBuilder.subobjStart(tool->name())); + + auto names = tool->get_strings(); + if (names.empty()) { + continue; + } + + auto stats = tool->get_stats(); + if (stats.empty()) { + continue; + } + invariant(stats.size() >= names.size()); + + for (size_t i = 0; i < names.size(); i++) { + subNICBuilder.append(names[i], static_cast(stats[i])); + } + } + } } private: @@ -227,6 +453,8 @@ private: // List of physical disks to collect stats from as StringData to pass to parseProcDiskStatsFile. std::vector _disksStringData; + + std::vector> _ethtools; }; class SimpleFunctionCollector final : public FTDCCollectorInterface { diff --git a/src/mongo/db/local_catalog/BUILD.bazel b/src/mongo/db/local_catalog/BUILD.bazel index 91db8e300dc..0ce8d21dae9 100644 --- a/src/mongo/db/local_catalog/BUILD.bazel +++ b/src/mongo/db/local_catalog/BUILD.bazel @@ -106,6 +106,7 @@ mongo_cc_library( "//src/mongo/db/repl:oplog_visibility_manager", "//src/mongo/db/repl:optime", "//src/mongo/db/repl:repl_coordinator_interface", # TODO(SERVER-93876): Remove. + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/storage:oplog_truncate_markers", "//src/mongo/db/storage:record_store_base", ], @@ -124,6 +125,7 @@ mongo_cc_library( ":durable_catalog_entry_metadata", "//src/mongo/db:server_base", "//src/mongo/db/op_observer:op_observer_util", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/storage:feature_document_util", "//src/mongo/db/storage:ident", "//src/mongo/db/storage:mdb_catalog", @@ -646,7 +648,6 @@ mongo_cc_library( "//src/mongo/db:vector_clock", "//src/mongo/db/collection_crud", "//src/mongo/db/commands:server_status_core", - "//src/mongo/db/disagg_storage:server_parameters", "//src/mongo/db/index:index_access_method", "//src/mongo/db/index:preallocated_container_pool", "//src/mongo/db/matcher/doc_validation", @@ -656,6 +657,7 @@ mongo_cc_library( "//src/mongo/db/repl:oplog", "//src/mongo/db/repl:repl_server_parameters", "//src/mongo/db/repl:repl_settings", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/stats:top", "//src/mongo/db/storage:mdb_catalog", "//src/mongo/db/storage:record_store_base", diff --git a/src/mongo/db/local_catalog/collection_options.h b/src/mongo/db/local_catalog/collection_options.h index ca331b8f100..1719b110971 100644 --- a/src/mongo/db/local_catalog/collection_options.h +++ b/src/mongo/db/local_catalog/collection_options.h @@ -176,6 +176,8 @@ struct CollectionOptions { boost::optional encryptedFieldConfig; // When 'true', will use the same recordIds across all nodes in the replica set. + // When using disaggregated storage, will be enabled implicitly when the collection + // is created. bool recordIdsReplicated = false; }; diff --git a/src/mongo/db/local_catalog/database_impl.cpp b/src/mongo/db/local_catalog/database_impl.cpp index 4867c29a3e4..cc2ea5c2c1a 100644 --- a/src/mongo/db/local_catalog/database_impl.cpp +++ b/src/mongo/db/local_catalog/database_impl.cpp @@ -35,7 +35,6 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/audit.h" #include "mongo/db/basic_types_gen.h" -#include "mongo/db/disagg_storage/server_parameters_gen.h" #include "mongo/db/index_builds/index_build_block.h" #include "mongo/db/index_builds/index_builds_common.h" #include "mongo/db/local_catalog/catalog_raii.h" @@ -65,6 +64,7 @@ #include "mongo/db/record_id.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/db/server_options.h" #include "mongo/db/server_parameter.h" @@ -158,10 +158,9 @@ RecordId acquireCatalogId( OperationContext* opCtx, const boost::optional& createCollCatalogIdentifier, MDBCatalog* mdbCatalog) { - if (disagg::gDisaggregatedStorageEnabled && createCollCatalogIdentifier.has_value()) { - // Replicated catalogIds aren't compatible with standard architecture, as a node may create - // local collection whose catalogId collides with that of a replicated collection created on - // another node. + auto& rss = rss::ReplicatedStorageService::get(opCtx); + if (rss.getPersistenceProvider().shouldUseReplicatedCatalogIdentifiers() && + createCollCatalogIdentifier.has_value()) { return createCollCatalogIdentifier->catalogId; } return mdbCatalog->reserveCatalogId(opCtx); @@ -770,8 +769,10 @@ Collection* DatabaseImpl::_createCollection( // Additionally, we do not set the recordIdsReplicated:true option on timeseries and // clustered collections because in those cases the recordId is the _id, or on capped // collections which utilizes a separate mechanism for ensuring uniform recordIds. - if (generatedUUID && !nss.isOnInternalDb() && !optionsWithUUID.timeseries && - !optionsWithUUID.clusteredIndex && !optionsWithUUID.capped && + const bool collectionTypeSupportsReplicatedRecordIds = + !optionsWithUUID.timeseries && !optionsWithUUID.clusteredIndex && !optionsWithUUID.capped; + const auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + if (generatedUUID && !nss.isOnInternalDb() && collectionTypeSupportsReplicatedRecordIds && gFeatureFlagRecordIdsReplicated.isEnabledUseLastLTSFCVWhenUninitialized( VersionContext::getDecoration(opCtx), serverGlobalParams.featureCompatibility.acquireFCVSnapshot()) && @@ -781,6 +782,19 @@ Collection* DatabaseImpl::_createCollection( "Collection will use recordIdsReplicated:true.", "oldValue"_attr = optionsWithUUID.recordIdsReplicated); optionsWithUUID.recordIdsReplicated = true; + } else if (provider.shouldUseReplicatedRecordIds() && nss.isReplicated() && + !nss.isImplicitlyReplicated() && collectionTypeSupportsReplicatedRecordIds) { + tassert(10985561, + str::stream() << "Replicated record IDs must be enabled with " << provider.name(), + gFeatureFlagRecordIdsReplicated.isEnabledUseLatestFCVWhenUninitialized( + VersionContext::getDecoration(opCtx), + serverGlobalParams.featureCompatibility.acquireFCVSnapshot())); + LOGV2_DEBUG(10985560, + 2, + "Collection will use recordIdsReplicated:true", + "provider"_attr = provider.name(), + "oldValue"_attr = optionsWithUUID.recordIdsReplicated); + optionsWithUUID.recordIdsReplicated = true; } uassert(ErrorCodes::CommandNotSupported, diff --git a/src/mongo/db/local_catalog/durable_catalog.cpp b/src/mongo/db/local_catalog/durable_catalog.cpp index 837c1cde9fc..965d639ba24 100644 --- a/src/mongo/db/local_catalog/durable_catalog.cpp +++ b/src/mongo/db/local_catalog/durable_catalog.cpp @@ -39,6 +39,7 @@ #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/op_observer/op_observer_util.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/storage/feature_document_util.h" #include "mongo/db/storage/kv/kv_engine.h" #include "mongo/db/storage/mdb_catalog.h" @@ -331,10 +332,13 @@ Status createIndex(OperationContext* opCtx, auto& ru = *shard_role_details::getRecoveryUnit(opCtx); auto storageEngine = opCtx->getServiceContext()->getStorageEngine(); auto kvEngine = storageEngine->getEngine(); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + invariant(collectionOptions.uuid); - bool replicateLocalCatalogIdentifiers = - shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx)); + bool replicateLocalCatalogIdentifiers = shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx)); if (replicateLocalCatalogIdentifiers) { // If a previous attempt at creating this index was rolled back, the ident may still be drop // pending. Complete that drop before creating the index if so. @@ -349,6 +353,7 @@ Status createIndex(OperationContext* opCtx, } Status status = kvEngine->createSortedDataInterface( + provider, ru, nss, *collectionOptions.uuid, @@ -466,8 +471,10 @@ Status dropAndRecreateIndexIdentForResume(OperationContext* opCtx, return status; invariant(collectionOptions.uuid); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); status = - engine->createSortedDataInterface(*shard_role_details::getRecoveryUnit(opCtx), + engine->createSortedDataInterface(provider, + *shard_role_details::getRecoveryUnit(opCtx), nss, *collectionOptions.uuid, ident, diff --git a/src/mongo/db/local_catalog/local_oplog_info.cpp b/src/mongo/db/local_catalog/local_oplog_info.cpp index 41f972a484a..b631882ce0f 100644 --- a/src/mongo/db/local_catalog/local_oplog_info.cpp +++ b/src/mongo/db/local_catalog/local_oplog_info.cpp @@ -39,6 +39,7 @@ #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/optime.h" #include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/storage/record_store.h" #include "mongo/db/storage/recovery_unit.h" #include "mongo/db/storage/storage_options.h" @@ -146,6 +147,10 @@ std::vector LocalOplogInfo::getNextOpTimes(OperationContext* opCtx, s Timestamp ts; // Provide a sample to FlowControl after the `oplogInfo.newOpMutex` is released. ON_BLOCK_EXIT([opCtx, &ts, count] { + auto& rss = rss::ReplicatedStorageService::get(opCtx); + if (!rss.getPersistenceProvider().shouldUseOplogWritesForFlowControlSampling()) + return; + auto flowControl = FlowControl::get(opCtx); if (flowControl) { flowControl->sample(ts, count); diff --git a/src/mongo/db/mongod_main.cpp b/src/mongo/db/mongod_main.cpp index 203996c31e8..b1034b5dca0 100644 --- a/src/mongo/db/mongod_main.cpp +++ b/src/mongo/db/mongod_main.cpp @@ -154,17 +154,14 @@ #include "mongo/db/repl/repl_settings.h" #include "mongo/db/repl/replication_consistency_markers_impl.h" #include "mongo/db/repl/replication_coordinator.h" -#include "mongo/db/repl/replication_coordinator_external_state_impl.h" -#include "mongo/db/repl/replication_coordinator_impl.h" #include "mongo/db/repl/replication_coordinator_impl_gen.h" #include "mongo/db/repl/replication_process.h" #include "mongo/db/repl/replication_recovery.h" -#include "mongo/db/repl/storage_interface.h" #include "mongo/db/repl/storage_interface_impl.h" -#include "mongo/db/repl/topology_coordinator.h" #include "mongo/db/repl/wait_for_majority_service.h" #include "mongo/db/replication_state_transition_lock_guard.h" #include "mongo/db/request_execution_context.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/s/migration_blocking_operation/multi_update_coordinator.h" #include "mongo/db/s/migration_chunk_cloner_source_op_observer.h" #include "mongo/db/s/query_analysis_op_observer_configsvr.h" @@ -606,9 +603,9 @@ ExitCode _initAndListen(ServiceContext* serviceContext) { ec != ExitCode::clean) return ec; - FlowControl::set(serviceContext, - std::make_unique( - serviceContext, repl::ReplicationCoordinator::get(serviceContext))); + auto& rss = rss::ReplicatedStorageService::get(serviceContext); + auto& serviceLifecycle = rss.getServiceLifecycle(); + serviceLifecycle.initializeFlowControl(serviceContext); // If a crash occurred during file-copy based initial sync, we may need to finish or clean up. { @@ -620,8 +617,20 @@ ExitCode _initAndListen(ServiceContext* serviceContext) { admission::initializeExecutionControl(serviceContext); - auto lastShutdownState = catalog::startUpStorageEngineAndCollectionCatalog( - serviceContext, &cc(), StorageEngineInitFlags{}, &startupTimeElapsedBuilder); + serviceLifecycle.initializeStorageEngineExtensions(serviceContext); + + auto lastShutdownState = [&]() { + if (rss.getPersistenceProvider().shouldDelayDataAccessDuringStartup()) { + // If data isn't ready yet, we shouldn't try to read it. + auto initializeStorageEngineOpCtx = serviceContext->makeOperationContext(&cc()); + return catalog::startUpStorageEngine(initializeStorageEngineOpCtx.get(), + StorageEngineInitFlags{}, + &startupTimeElapsedBuilder); + } else { + return catalog::startUpStorageEngineAndCollectionCatalog( + serviceContext, &cc(), StorageEngineInitFlags{}, &startupTimeElapsedBuilder); + } + }(); StorageControl::startStorageControls(serviceContext); auto logStartupStats = std::make_unique>>([&] { @@ -898,7 +907,8 @@ ExitCode _initAndListen(ServiceContext* serviceContext) { &startupTimeElapsedBuilder); replCoord->startup(startupOpCtx.get(), lastShutdownState); } else { - if (storageEngine->supportsCappedCollections()) { + if (rss.getPersistenceProvider().supportsLocalCollections() && + storageEngine->supportsCappedCollections()) { logStartup(startupOpCtx.get()); } @@ -1368,30 +1378,16 @@ auto makeReplicaSetNodeExecutor(ServiceContext* serviceContext) { "ReplNodeDbWorkerNetwork", nullptr, makeShardingEgressHooksList(serviceContext))); } -auto makeReplicationExecutor(ServiceContext* serviceContext) { - ThreadPool::Options tpOptions; - tpOptions.threadNamePrefix = "ReplCoord-"; - tpOptions.poolName = "ReplCoordThreadPool"; - tpOptions.maxThreads = 50; - tpOptions.onCreateThread = [serviceContext](const std::string& threadName) { - Client::initThread(threadName, - serviceContext->getService(ClusterRole::ShardServer), - Client::noSession(), - ClientOperationKillableByStepdown{false}); - }; - auto hookList = std::make_unique(); - hookList->addHook(std::make_unique(serviceContext)); - return executor::ThreadPoolTaskExecutor::create( - std::make_unique(tpOptions), - executor::makeNetworkInterface("ReplNetwork", nullptr, std::move(hookList))); -} - void setUpReplicaSetDDLHooks(ServiceContext* serviceContext) { ReplicaSetDDLTracker::create(serviceContext); DirectConnectionDDLHook::create(serviceContext); } void setUpReplication(ServiceContext* serviceContext) { + auto& serviceLifecycle = + rss::ReplicatedStorageService::get(serviceContext).getServiceLifecycle(); + serviceLifecycle.initializeStateRequiredForStorageAccess(serviceContext); + repl::StorageInterface::set(serviceContext, std::make_unique()); auto storageInterface = repl::StorageInterface::get(serviceContext); @@ -1403,22 +1399,10 @@ void setUpReplication(ServiceContext* serviceContext) { serviceContext, std::make_unique( storageInterface, std::move(consistencyMarkers), std::move(recovery))); - auto replicationProcess = repl::ReplicationProcess::get(serviceContext); - repl::TopologyCoordinator::Options topoCoordOptions; - topoCoordOptions.maxSyncSourceLagSecs = Seconds(repl::maxSyncSourceLagSecs); - topoCoordOptions.clusterRole = serverGlobalParams.clusterRole; + std::unique_ptr replCoord = + serviceLifecycle.initializeReplicationCoordinator(serviceContext); - auto replCoord = std::make_unique( - serviceContext, - getGlobalReplSettings(), - std::make_unique( - serviceContext, storageInterface, replicationProcess), - makeReplicationExecutor(serviceContext), - std::make_unique(topoCoordOptions), - replicationProcess, - storageInterface, - SecureRandom().nextInt64()); // Only create a ReplicaSetNodeExecutor if sharding is disabled and replication is enabled. // Note that sharding sets up its own executors for scheduling work to remote nodes. if (serverGlobalParams.clusterRole.has(ClusterRole::None) && @@ -1840,8 +1824,12 @@ void shutdownTask(const ShutdownTaskArgs& shutdownArgs) { SectionScopedTimer scopedTimer(serviceContext->getFastClockSource(), TimedSectionId::killAllOperations, &shutdownTimeElapsedBuilder); - serviceContext->setKillAllOperations( - [](const StringData t) { return t == kFTDCThreadName; }); + auto& serviceLifecycle = + rss::ReplicatedStorageService::get(serviceContext).getServiceLifecycle(); + serviceContext->setKillAllOperations([&serviceLifecycle](const StringData t) { + return t == kFTDCThreadName || + serviceLifecycle.shouldKeepThreadAliveUntilStorageEngineHasShutDown(t); + }); if (MONGO_unlikely(pauseWhileKillingOperationsAtShutdown.shouldFail())) { LOGV2_OPTIONS(4701700, @@ -1987,6 +1975,13 @@ void shutdownTask(const ShutdownTaskArgs& shutdownArgs) { true /* memLeakAllowed */); } + // Depending on the underlying implementation, there may be some state that needs to be shut + // down after the replication subsystem and the storage engine. + auto& serviceLifecycle = + rss::ReplicatedStorageService::get(serviceContext).getServiceLifecycle(); + serviceLifecycle.shutdownStateRequiredForStorageAccess(serviceContext, + &shutdownTimeElapsedBuilder); + // We drop the scope cache because leak sanitizer can't see across the // thread we use for proxying MozJS requests. Dropping the cache cleans up // the memory and makes leak sanitizer happy. diff --git a/src/mongo/db/op_msg_fuzzer_router_fixture_test.cpp b/src/mongo/db/op_msg_fuzzer_router_fixture_test.cpp index 908f3280e6a..bd235e5709e 100644 --- a/src/mongo/db/op_msg_fuzzer_router_fixture_test.cpp +++ b/src/mongo/db/op_msg_fuzzer_router_fixture_test.cpp @@ -27,7 +27,6 @@ * it in the license file. */ - #include "mongo/db/op_msg_fuzzer_router_fixture.h" #include "mongo/base/string_data.h" diff --git a/src/mongo/db/op_observer/BUILD.bazel b/src/mongo/db/op_observer/BUILD.bazel index 1cc4588a94f..0fa7f109d2d 100644 --- a/src/mongo/db/op_observer/BUILD.bazel +++ b/src/mongo/db/op_observer/BUILD.bazel @@ -141,8 +141,8 @@ mongo_cc_library( "//src/mongo:base", "//src/mongo/bson/dotted_path:dotted_path_support", # TODO(SERVER-93876): Remove. "//src/mongo/db:shard_role_api", - "//src/mongo/db/disagg_storage:server_parameters", "//src/mongo/db/local_catalog:collection_options", + "//src/mongo/db/rss:replicated_storage_service", ], ) diff --git a/src/mongo/db/op_observer/op_observer_impl.cpp b/src/mongo/db/op_observer/op_observer_impl.cpp index f7bdd15a3da..9e404d8d7b1 100644 --- a/src/mongo/db/op_observer/op_observer_impl.cpp +++ b/src/mongo/db/op_observer/op_observer_impl.cpp @@ -66,6 +66,7 @@ #include "mongo/db/repl/oplog_entry_gen.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_options.h" #include "mongo/db/session/logical_session_id_helpers.h" #include "mongo/db/session/session_txn_record_gen.h" @@ -330,8 +331,9 @@ void OpObserverImpl::onCreateIndex(OperationContext* opCtx, return; } - bool replicateLocalCatalogIdentifiers = - shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx)); + bool replicateLocalCatalogIdentifiers = shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx)); BSONObjBuilder builder; // Note that despite using this constant, we are not building a CreateIndexCommand here @@ -417,7 +419,9 @@ void OpObserverImpl::onStartIndexBuild(OperationContext* opCtx, oplogEntry.setNss(nss.getCommandNS()); oplogEntry.setUuid(collUUID); oplogEntry.setObject(oplogEntryBuilder.done()); - if (shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))) { + if (shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))) { // TODO (SERVER-109824): Move 'directoryPerDB' and 'directoryForIndexes' to the function // parameters. oplogEntry.setObject2(BSON("indexes" << o2IndexesArr.arr() << "directoryPerDB" @@ -1190,7 +1194,9 @@ void OpObserverImpl::onCreateCollection( oplogEntry.setNss(collectionName.getCommandNS()); oplogEntry.setUuid(options.uuid); oplogEntry.setObject(MutableOplogEntry::makeCreateCollObject(collectionName, options, idIndex)); - if (shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))) { + if (shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))) { invariant(createCollCatalogIdentifier.has_value(), "Missing catalog identifier required to log replicated " "collection"); diff --git a/src/mongo/db/op_observer/op_observer_util.cpp b/src/mongo/db/op_observer/op_observer_util.cpp index 5110f1e5d6f..6893ebac92d 100644 --- a/src/mongo/db/op_observer/op_observer_util.cpp +++ b/src/mongo/db/op_observer/op_observer_util.cpp @@ -33,8 +33,8 @@ #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/dotted_path/dotted_path_support.h" -#include "mongo/db/disagg_storage/server_parameters_gen.h" #include "mongo/db/global_catalog/shard_key_pattern.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/storage/storage_parameters_gen.h" #include "mongo/util/duration.h" #include "mongo/util/fail_point.h" @@ -53,10 +53,9 @@ const OpStateAccumulator::Decoration> MONGO_FAIL_POINT_DEFINE(addDestinedRecipient); MONGO_FAIL_POINT_DEFINE(sleepBetweenInsertOpTimeGenerationAndLogOp); -bool shouldReplicateLocalCatalogIdentifers(const VersionContext& vCtx) { - if (disagg::gDisaggregatedStorageEnabled) { - // Disaggregated storage relies on consistent catalog storage. Safe-guard if FCV is not yet - // initialized despite the feature being enabled. +bool shouldReplicateLocalCatalogIdentifers(const rss::PersistenceProvider& provider, + const VersionContext& vCtx) { + if (provider.shouldUseReplicatedCatalogIdentifiers()) { return true; } const auto fcvSnapshot = serverGlobalParams.featureCompatibility.acquireFCVSnapshot(); diff --git a/src/mongo/db/op_observer/op_observer_util.h b/src/mongo/db/op_observer/op_observer_util.h index d0ab51f37ec..2b0a0615870 100644 --- a/src/mongo/db/op_observer/op_observer_util.h +++ b/src/mongo/db/op_observer/op_observer_util.h @@ -35,6 +35,7 @@ #include "mongo/db/local_catalog/collection_options.h" #include "mongo/db/namespace_string.h" #include "mongo/db/op_observer/op_observer.h" +#include "mongo/db/rss/persistence_provider.h" #include "mongo/util/assert_util.h" #include "mongo/util/decorable.h" #include "mongo/util/fail_point.h" @@ -54,7 +55,8 @@ extern FailPoint sleepBetweenInsertOpTimeGenerationAndLogOp; /** * Returns true when local catalog identifiers should be replicated through the oplog. */ -bool shouldReplicateLocalCatalogIdentifers(const VersionContext& vCtx); +bool shouldReplicateLocalCatalogIdentifers(const rss::PersistenceProvider&, + const VersionContext& vCtx); /** * Returns true if gFeatureFlagPrimaryDrivenIndexBuilds is enabled. diff --git a/src/mongo/db/repl/BUILD.bazel b/src/mongo/db/repl/BUILD.bazel index 16ff7d50260..e4dfbb93a53 100644 --- a/src/mongo/db/repl/BUILD.bazel +++ b/src/mongo/db/repl/BUILD.bazel @@ -972,6 +972,7 @@ mongo_cc_library( "//src/mongo/db/pipeline:change_stream_preimage", "//src/mongo/db/query/write_ops", "//src/mongo/db/repl/dbcheck", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/session:session_catalog_mongod", "//src/mongo/db/stats:counters", "//src/mongo/db/stats:server_read_concern_write_concern_metrics", diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index e366f04f504..fca9efe7c25 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -105,6 +105,7 @@ #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/timestamp_block.h" #include "mongo/db/repl/transaction_oplog_application.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/session/logical_session_id_gen.h" #include "mongo/db/sharding_environment/shard_id.h" @@ -211,7 +212,10 @@ StringData getInvalidatingReason(const OplogApplication::Mode mode, const bool i boost::optional extractReplicatedCatalogIdentifier( OperationContext* opCtx, const OplogEntry& oplogEntry) { auto& o2 = oplogEntry.getObject2(); - if (!o2 || !shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))) { + if (!o2 || + !shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))) { // Either no catalog identifier information was provided, or replicated local catalog // identifiers are not supported. return boost::none; @@ -365,7 +369,9 @@ void createIndexForApplyOps(OperationContext* opCtx, IndexBuildInfo indexBuildInfo = [&] { auto storageEngine = opCtx->getServiceContext()->getStorageEngine(); if (!indexMetadata || - !shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))) { + !shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))) { return IndexBuildInfo(indexSpec, *storageEngine, indexCollection->ns().dbName()); } @@ -733,8 +739,13 @@ void createOplog(OperationContext* opCtx, uow.commit(); }); - /* sync here so we don't get any surprising lag later when we try to sync */ - service->getStorageEngine()->flushAllFiles(opCtx, /*callerHoldsReadLock*/ false); + // We cannot guarantee that we have a stable timestamp at this point, but if the persistence + // provider supports unstable checkpoints, we can take a checkpoint now to avoid any surprising + // lag later when we try to sync. + auto& rss = rss::ReplicatedStorageService::get(service); + if (rss.getPersistenceProvider().supportsUnstableCheckpoints()) { + service->getStorageEngine()->flushAllFiles(opCtx, /*callerHoldsReadLock*/ false); + } } void createOplog(OperationContext* opCtx) { @@ -961,7 +972,9 @@ const StringMap kOpsMap = { auto swOplogEntry = IndexBuildOplogEntry::parse( opCtx, entry, - shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(opCtx))); + shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(), + VersionContext::getDecoration(opCtx))); if (!swOplogEntry.isOK()) { return swOplogEntry.getStatus().withContext( "Error parsing 'startIndexBuild' oplog entry"); diff --git a/src/mongo/db/repl/oplog_applier_impl.cpp b/src/mongo/db/repl/oplog_applier_impl.cpp index be1e80d65d9..af108a2d49b 100644 --- a/src/mongo/db/repl/oplog_applier_impl.cpp +++ b/src/mongo/db/repl/oplog_applier_impl.cpp @@ -291,7 +291,7 @@ public: } // Should be called after all oplog entries have been processed to handle the deletes that - // were not superceded by a later write. + // were not superseded by a later write. void handleLatestDeletes(std::function handler) { std::for_each(_retryImageWrites.begin(), _retryImageWrites.end(), diff --git a/src/mongo/db/repl/oplog_applier_impl_test.cpp b/src/mongo/db/repl/oplog_applier_impl_test.cpp index d894bfa0ee5..d43c77927a3 100644 --- a/src/mongo/db/repl/oplog_applier_impl_test.cpp +++ b/src/mongo/db/repl/oplog_applier_impl_test.cpp @@ -103,7 +103,6 @@ #include "mongo/db/session/session_txn_record_gen.h" #include "mongo/db/sharding_environment/shard_id.h" #include "mongo/db/stats/counters.h" -#include "mongo/db/storage/mdb_catalog.h" #include "mongo/db/storage/write_unit_of_work.h" #include "mongo/db/tenant_id.h" #include "mongo/db/timeseries/timeseries_gen.h" @@ -141,21 +140,6 @@ namespace mongo { namespace repl { namespace { -CreateCollCatalogIdentifier newCatalogIdentifier(OperationContext* opCtx, - const DatabaseName& dbName, - bool includeIdIndexIdent) { - auto storageEngine = opCtx->getServiceContext()->getStorageEngine(); - auto mdbCatalog = storageEngine->getMDBCatalog(); - invariant(mdbCatalog); - - CreateCollCatalogIdentifier catalogIdentifier; - catalogIdentifier.catalogId = mdbCatalog->reserveCatalogId(opCtx); - catalogIdentifier.ident = storageEngine->generateNewCollectionIdent(dbName); - if (includeIdIndexIdent) { - catalogIdentifier.idIndexIdent = storageEngine->generateNewIndexIdent(dbName); - } - return catalogIdentifier; -} auto parseFromOplogEntryArray(const BSONObj& obj, int elem) { BSONElement tsArray; @@ -632,45 +616,6 @@ TEST_F(OplogApplierImplTest, CreateCollectionCommand) { ASSERT_TRUE(collectionExists(_opCtx.get(), nss)); } -TEST_F(OplogApplierImplTest, CreateCollectionCommandDisaggBasic) { - RAIIServerParameterControllerForTest disaggServer("disaggregatedStorageEnabled", true); - RAIIServerParameterControllerForTest replicateLocalCatalogInfoController( - "featureFlagReplicateLocalCatalogIdentifiers", true); - - NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.t"); - auto catalogIdentifier = - newCatalogIdentifier(_opCtx.get(), nss.dbName(), true /* includeIdIndexIdent*/); - - auto entry = - makeCreateCollectionOplogEntry(nextOpTime(), - nss, - CollectionOptions{.uuid = UUID::gen()}, - BSON("v" << 2 << "key" << BSON("_id_" << 1) << "name" - << "_id_") /* idIndex */, - catalogIdentifier); - - bool applyCmdCalled = false; - _opObserver->onCreateCollectionFn = - [&](OperationContext* opCtx, - const NamespaceString& collNss, - const CollectionOptions&, - const BSONObj&, - const boost::optional& collCatalogIdentifier) { - applyCmdCalled = true; - ASSERT_TRUE(opCtx); - ASSERT_TRUE( - shard_role_details::getLocker(opCtx)->isDbLockedForMode(nss.dbName(), MODE_IX)); - ASSERT_EQUALS(nss, collNss); - ASSERT(collCatalogIdentifier); - ASSERT_EQUALS(catalogIdentifier.catalogId, collCatalogIdentifier->catalogId); - ASSERT_EQUALS(catalogIdentifier.ident, collCatalogIdentifier->ident); - }; - ASSERT_OK(_applyOplogEntryOrGroupedInsertsWrapper( - _opCtx.get(), ApplierOperation{&entry}, OplogApplication::Mode::kInitialSync)); - ASSERT_TRUE(applyCmdCalled); - ASSERT_TRUE(collectionExists(_opCtx.get(), nss)); -} - TEST_F(OplogApplierImplTest, CreateCollectionCommandMultitenant) { setServerParameter("multitenancySupport", true); setServerParameter("featureFlagRequireTenantID", true); diff --git a/src/mongo/db/repl/oplog_applier_impl_test_fixture.cpp b/src/mongo/db/repl/oplog_applier_impl_test_fixture.cpp index 7c760b73aea..bf455331a78 100644 --- a/src/mongo/db/repl/oplog_applier_impl_test_fixture.cpp +++ b/src/mongo/db/repl/oplog_applier_impl_test_fixture.cpp @@ -62,6 +62,7 @@ #include "mongo/db/repl/storage_interface_impl.h" #include "mongo/db/session/session_catalog_mongod.h" #include "mongo/db/sharding_environment/shard_id.h" +#include "mongo/db/storage/mdb_catalog.h" #include "mongo/db/storage/write_unit_of_work.h" #include "mongo/db/tenant_id.h" #include "mongo/db/transaction/session_catalog_mongod_transaction_interface_impl.h" @@ -198,14 +199,18 @@ void OplogApplierImplOpObserver::onCollMod(OperationContext* opCtx, onCollModFn(opCtx, nss, uuid, collModCmd, oldCollOptions, indexInfo); } +std::unique_ptr OplogApplierImplTest::makeReplCoord( + ServiceContext* serviceContext) { + return std::make_unique(serviceContext); +} + void OplogApplierImplTest::setUp() { ServiceContextMongoDTest::setUp(); serviceContext = getServiceContext(); _opCtx = cc().makeOperationContext(); - ReplicationCoordinator::set(serviceContext, - std::make_unique(serviceContext)); + ReplicationCoordinator::set(serviceContext, makeReplCoord(serviceContext)); ASSERT_OK(ReplicationCoordinator::get(_opCtx.get())->setFollowerMode(MemberState::RS_PRIMARY)); StorageInterface::set(serviceContext, std::make_unique()); @@ -625,5 +630,21 @@ void createIndex(OperationContext* opCtx, opCtx, collUUID, spec, IndexBuildsManager::IndexConstraints::kEnforce, false); } +CreateCollCatalogIdentifier newCatalogIdentifier(OperationContext* opCtx, + const DatabaseName& dbName, + bool includeIdIndexIdent) { + auto storageEngine = opCtx->getServiceContext()->getStorageEngine(); + auto mdbCatalog = storageEngine->getMDBCatalog(); + invariant(mdbCatalog); + + CreateCollCatalogIdentifier catalogIdentifier; + catalogIdentifier.catalogId = mdbCatalog->reserveCatalogId(opCtx); + catalogIdentifier.ident = storageEngine->generateNewCollectionIdent(dbName); + if (includeIdIndexIdent) { + catalogIdentifier.idIndexIdent = storageEngine->generateNewIndexIdent(dbName); + } + return catalogIdentifier; +} + } // namespace repl } // namespace mongo diff --git a/src/mongo/db/repl/oplog_applier_impl_test_fixture.h b/src/mongo/db/repl/oplog_applier_impl_test_fixture.h index c34d6ec38fe..eedd25cde71 100644 --- a/src/mongo/db/repl/oplog_applier_impl_test_fixture.h +++ b/src/mongo/db/repl/oplog_applier_impl_test_fixture.h @@ -277,6 +277,7 @@ protected: return OpTime(Timestamp(Seconds(lastSecond++), 0), 1LL); } + virtual std::unique_ptr makeReplCoord(ServiceContext*); void setUp() override; void tearDown() override; @@ -415,6 +416,13 @@ void createIndex(OperationContext* opCtx, UUID collUUID, const BSONObj& spec); -} // namespace MONGO_MOD_PUB repl +/** + * Generate a new catalog identifier. + */ +CreateCollCatalogIdentifier newCatalogIdentifier(OperationContext* opCtx, + const DatabaseName& dbName, + bool includeIdIndexIdent); + +} // namespace MONGO_MOD_PUB repl } // namespace mongo diff --git a/src/mongo/db/repl/optime.h b/src/mongo/db/repl/optime.h index e4a76dc0871..f9598447017 100644 --- a/src/mongo/db/repl/optime.h +++ b/src/mongo/db/repl/optime.h @@ -212,4 +212,10 @@ public: }; std::ostream& operator<<(std::ostream& out, const OpTimeAndWallTime& opTime); + +// A convenience class for holding both a Timestamp and a Date_t. +struct TimestampAndWallTime { + Timestamp timestamp; + Date_t wallTime; +}; } // namespace mongo::repl diff --git a/src/mongo/db/repl/rollback_impl_test.cpp b/src/mongo/db/repl/rollback_impl_test.cpp index 991500a01af..18e31c4fd56 100644 --- a/src/mongo/db/repl/rollback_impl_test.cpp +++ b/src/mongo/db/repl/rollback_impl_test.cpp @@ -59,6 +59,7 @@ #include "mongo/db/repl/replication_consistency_markers.h" #include "mongo/db/repl/replication_process.h" #include "mongo/db/repl/rollback_test_fixture.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context.h" #include "mongo/db/session/logical_session_id.h" @@ -2250,7 +2251,9 @@ TEST_F(RollbackImplObserverInfoTest, auto uuid = UUID::gen(); BSONObj indexObj; - if (shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(_opCtx.get()))) { + if (shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(_opCtx.get()).getPersistenceProvider(), + VersionContext::getDecoration(_opCtx.get()))) { indexObj = BSON("createIndexes" << nss.coll() << "spec" << BSON("v" << 2 << "key" << "x" diff --git a/src/mongo/db/repl/storage_timestamp_test.cpp b/src/mongo/db/repl/storage_timestamp_test.cpp index 93e0582e741..c1368f162cd 100644 --- a/src/mongo/db/repl/storage_timestamp_test.cpp +++ b/src/mongo/db/repl/storage_timestamp_test.cpp @@ -106,6 +106,7 @@ #include "mongo/db/repl/storage_interface.h" #include "mongo/db/repl/storage_interface_impl.h" #include "mongo/db/repl/timestamp_block.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_d_test_fixture.h" #include "mongo/db/session/logical_session_id.h" @@ -916,7 +917,9 @@ public: } StringData indexNameOplogField() const { - return shouldReplicateLocalCatalogIdentifers(VersionContext::getDecoration(_opCtx)) + return shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(_opCtx).getPersistenceProvider(), + VersionContext::getDecoration(_opCtx)) ? "o.spec.name" : "o.name"; } @@ -3088,14 +3091,16 @@ TEST_F(StorageTimestampTest, CreateCollectionWithSystemIndex) { // supports 2 phase index build. indexStartTs = op.getTimestamp(); indexCreateTs = - repl::OplogEntry(queryOplog(BSON("op" << "c" - << "ns" << nss.getCommandNS().ns_forTest() - << "o.createIndexes" << nss.coll() - << (shouldReplicateLocalCatalogIdentifers( - VersionContext::getDecoration(_opCtx)) - ? "o.spec.name" - : "o.name") - << "user_1_db_1"))) + repl::OplogEntry( + queryOplog(BSON( + "op" << "c" + << "ns" << nss.getCommandNS().ns_forTest() << "o.createIndexes" << nss.coll() + << (shouldReplicateLocalCatalogIdentifers( + rss::ReplicatedStorageService::get(_opCtx).getPersistenceProvider(), + VersionContext::getDecoration(_opCtx)) + ? "o.spec.name" + : "o.name") + << "user_1_db_1"))) .getTimestamp(); indexCompleteTs = indexCreateTs; diff --git a/src/mongo/db/rss/BUILD.bazel b/src/mongo/db/rss/BUILD.bazel new file mode 100644 index 00000000000..045053ba155 --- /dev/null +++ b/src/mongo/db/rss/BUILD.bazel @@ -0,0 +1,77 @@ +load("//bazel:mongo_src_rules.bzl", "mongo_cc_benchmark", "mongo_cc_library", "mongo_cc_unit_test") + +package(default_visibility = ["//visibility:public"]) + +exports_files( + glob([ + "*.h", + "*.cpp", + ]), +) + +mongo_cc_library( + name = "replicated_storage_service", + srcs = [ + "replicated_storage_service.cpp", + ], + hdrs = [ + "replicated_storage_service.h", + ], + deps = [ + "//src/mongo:base", + "//src/mongo/db:service_context", + "//src/mongo/db/rss:persistence_provider", + "//src/mongo/db/rss:service_lifecycle", + ], +) + +mongo_cc_library( + name = "persistence_provider", + hdrs = [ + "persistence_provider.h", + ], + deps = [ + "//src/mongo:base", + "//src/mongo/db:service_context", + ], +) + +mongo_cc_library( + name = "service_lifecycle", + hdrs = [ + "service_lifecycle.h", + ], + deps = [ + "//src/mongo:base", + "//src/mongo/db:service_context", + "//src/mongo/db/repl:repl_coordinator_interface", + ], +) + +mongo_cc_library( + name = "persistence_provider_impl", + deps = [ + "//src/mongo:base", + "//src/mongo/db/rss:persistence_provider", + "//src/mongo/db/rss/attached_storage:attached_persistence_provider", + ] + select({ + "//bazel/config:build_atlas_enabled": [ + "//src/mongo/db/modules/atlas/src/disagg_storage:disaggregated_persistence_provider", + ], + "//conditions:default": [], + }), +) + +mongo_cc_library( + name = "service_lifecycle_impl", + deps = [ + "//src/mongo:base", + "//src/mongo/db/rss:service_lifecycle", + "//src/mongo/db/rss/attached_storage:attached_service_lifecycle", + ] + select({ + "//bazel/config:build_atlas_enabled": [ + "//src/mongo/db/modules/atlas/src/disagg_storage:disaggregated_service_lifecycle", + ], + "//conditions:default": [], + }), +) diff --git a/src/mongo/db/rss/OWNERS.yml b/src/mongo/db/rss/OWNERS.yml new file mode 100644 index 00000000000..0ca1965b79f --- /dev/null +++ b/src/mongo/db/rss/OWNERS.yml @@ -0,0 +1,6 @@ +version: 1.0.0 +filters: + - "*": + approvers: + - 10gen/server-storage-execution + - 10gen/server-replication diff --git a/src/mongo/db/rss/attached_storage/BUILD.bazel b/src/mongo/db/rss/attached_storage/BUILD.bazel new file mode 100644 index 00000000000..1eec9cea6e5 --- /dev/null +++ b/src/mongo/db/rss/attached_storage/BUILD.bazel @@ -0,0 +1,43 @@ +load("//bazel:mongo_src_rules.bzl", "mongo_cc_benchmark", "mongo_cc_library", "mongo_cc_unit_test") + +package(default_visibility = ["//visibility:public"]) + +exports_files( + glob([ + "*.h", + "*.cpp", + ]), +) + +mongo_cc_library( + name = "attached_persistence_provider", + srcs = [ + "attached_persistence_provider.cpp", + ], + hdrs = [ + "attached_persistence_provider.h", + ], + deps = [ + "//src/mongo:base", + "//src/mongo/db:service_context", + "//src/mongo/db/rss:replicated_storage_service", + ], +) + +mongo_cc_library( + name = "attached_service_lifecycle", + srcs = [ + "attached_service_lifecycle.cpp", + ], + hdrs = [ + "attached_service_lifecycle.h", + ], + deps = [ + "//src/mongo:base", + "//src/mongo/db:service_context", + "//src/mongo/db/admission:flow_control", + "//src/mongo/db/repl:repl_coordinator_impl", + "//src/mongo/db/repl:serveronly_repl", + "//src/mongo/db/rss:replicated_storage_service", + ], +) diff --git a/src/mongo/db/rss/attached_storage/attached_persistence_provider.cpp b/src/mongo/db/rss/attached_storage/attached_persistence_provider.cpp new file mode 100644 index 00000000000..ff033605bd3 --- /dev/null +++ b/src/mongo/db/rss/attached_storage/attached_persistence_provider.cpp @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/rss/attached_storage/attached_persistence_provider.h" + +#include "mongo/base/string_data.h" +#include "mongo/db/rss/replicated_storage_service.h" +#include "mongo/db/service_context.h" + +namespace mongo::rss { +namespace { +// Checkpoint every 60 seconds by default. +constexpr double kDefaultAttachedSyncDelaySeconds = 60.0; + +ServiceContext::ConstructorActionRegisterer registerAttachedPersistenceProvider{ + "AttachedPersistenceProvider", [](ServiceContext* service) { + auto& rss = ReplicatedStorageService::get(service); + rss.setPersistenceProvider(std::make_unique()); + }}; + +} // namespace + +std::string AttachedPersistenceProvider::name() const { + return "Attached Storage"; +} + +boost::optional AttachedPersistenceProvider::getSentinelDataTimestamp() const { + return boost::none; +} + +std::string AttachedPersistenceProvider::getWiredTigerConfig(int) const { + return ""; +} + +bool AttachedPersistenceProvider::shouldUseReplicatedCatalogIdentifiers() const { + return false; +} + +bool AttachedPersistenceProvider::shouldUseReplicatedRecordIds() const { + return false; +} + +bool AttachedPersistenceProvider::shouldUseOplogWritesForFlowControlSampling() const { + return true; +} + +bool AttachedPersistenceProvider::shouldStepDownForShutdown() const { + return true; +} + +bool AttachedPersistenceProvider::shouldDelayDataAccessDuringStartup() const { + return false; +} + +bool AttachedPersistenceProvider::shouldAvoidDuplicateCheckpoints() const { + return false; +} + +bool AttachedPersistenceProvider::supportsLocalCollections() const { + return true; +} + +bool AttachedPersistenceProvider::supportsUnstableCheckpoints() const { + return true; +} + +bool AttachedPersistenceProvider::supportsTableLogging() const { + return true; +} + +bool AttachedPersistenceProvider::supportsMultiDocumentTransactions() const { + return true; +} + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/attached_storage/attached_persistence_provider.h b/src/mongo/db/rss/attached_storage/attached_persistence_provider.h new file mode 100644 index 00000000000..3e447438a34 --- /dev/null +++ b/src/mongo/db/rss/attached_storage/attached_persistence_provider.h @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/rss/persistence_provider.h" + +namespace mongo::rss { + +class AttachedPersistenceProvider : public PersistenceProvider { +public: + std::string name() const override; + + /** + * We do not have any specific initialization requirements. + */ + boost::optional getSentinelDataTimestamp() const override; + + /** + * We do not have any additional WT config to add. + */ + std::string getWiredTigerConfig(int) const override; + + /** + * Replicated catalog identifiers aren't compatible with attached storage as of right now, as a + * node may create a local collection whose catalog identifier collides with that of a + * replicated collection created on another node. + */ + bool shouldUseReplicatedCatalogIdentifiers() const override; + + /** + * Attached storage does not require replicated RecordIds to function correctly. + */ + bool shouldUseReplicatedRecordIds() const override; + + /** + * Flow control is based on the rate of generation of oplog data and the ability of the + * secondaries to keep the majority commit point relatively up-to-date. + */ + bool shouldUseOplogWritesForFlowControlSampling() const override; + + /** + * Stepping down prior to shut down allows for a graceful and quick election most of the time. + */ + bool shouldStepDownForShutdown() const override; + + /** + * We can safely initialize the catalog immediately after starting the storage engine. + */ + bool shouldDelayDataAccessDuringStartup() const override; + + /** + * Running a duplicate checkpoint for a given timestamp has little effect other than being + * slightly inefficient, so there's no need to use extra synchronization to avoid it. + */ + bool shouldAvoidDuplicateCheckpoints() const override; + + /** + * We can support local, fully unreplicated collections. + */ + bool supportsLocalCollections() const override; + + /** + * We can support unstable checkpoints. + */ + bool supportsUnstableCheckpoints() const override; + + /** + * We can support table logging. + */ + bool supportsTableLogging() const override; + + /** + * We can support multi-document transactions. + */ + bool supportsMultiDocumentTransactions() const override; +}; + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/attached_storage/attached_service_lifecycle.cpp b/src/mongo/db/rss/attached_storage/attached_service_lifecycle.cpp new file mode 100644 index 00000000000..e89e561dc46 --- /dev/null +++ b/src/mongo/db/rss/attached_storage/attached_service_lifecycle.cpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/rss/attached_storage/attached_service_lifecycle.h" + +#include "mongo/db/admission/flow_control.h" +#include "mongo/db/global_settings.h" +#include "mongo/db/repl/replication_consistency_markers_impl.h" +#include "mongo/db/repl/replication_coordinator_external_state_impl.h" +#include "mongo/db/repl/replication_coordinator_impl.h" +#include "mongo/db/repl/storage_interface.h" +#include "mongo/db/repl/topology_coordinator.h" +#include "mongo/db/rss/replicated_storage_service.h" +#include "mongo/db/storage/storage_options.h" +#include "mongo/executor/network_interface_factory.h" +#include "mongo/executor/thread_pool_task_executor.h" +#include "mongo/rpc/metadata/egress_metadata_hook_list.h" + +namespace mongo::rss { +namespace { +// Checkpoint every 60 seconds by default. +constexpr double kDefaultAttachedSyncDelaySeconds = 60.0; + +ServiceContext::ConstructorActionRegisterer registerAttachedServiceLifecycle{ + "AttachedServiceLifecycle", [](ServiceContext* service) { + auto& rss = ReplicatedStorageService::get(service); + rss.setServiceLifecycle(std::make_unique()); + }}; + +auto makeReplicationExecutor(ServiceContext* serviceContext) { + ThreadPool::Options tpOptions; + tpOptions.threadNamePrefix = "ReplCoord-"; + tpOptions.poolName = "ReplCoordThreadPool"; + tpOptions.maxThreads = 50; + tpOptions.onCreateThread = [serviceContext](const std::string& threadName) { + Client::initThread(threadName, + serviceContext->getService(ClusterRole::ShardServer), + Client::noSession(), + ClientOperationKillableByStepdown{false}); + }; + auto hookList = std::make_unique(); + hookList->addHook(std::make_unique(serviceContext)); + return executor::ThreadPoolTaskExecutor::create( + std::make_unique(tpOptions), + executor::makeNetworkInterface("ReplNetwork", nullptr, std::move(hookList))); +} +} // namespace + +AttachedServiceLifecycle::AttachedServiceLifecycle() + : _initializedUsingDefaultSyncDelay{[]() { + if (storageGlobalParams.syncdelay.load() < 0.0) { + storageGlobalParams.syncdelay.store(kDefaultAttachedSyncDelaySeconds); + return true; + } // namespace mongo::rss + return false; + }()} {} + +void AttachedServiceLifecycle::initializeFlowControl(ServiceContext* svcCtx) { + FlowControl::set( + svcCtx, std::make_unique(svcCtx, repl::ReplicationCoordinator::get(svcCtx))); +} + +void AttachedServiceLifecycle::initializeStorageEngineExtensions(ServiceContext*) {} + +std::unique_ptr +AttachedServiceLifecycle::initializeReplicationCoordinator(ServiceContext* svcCtx) { + auto storageInterface = repl::StorageInterface::get(svcCtx); + auto replicationProcess = repl::ReplicationProcess::get(svcCtx); + + repl::TopologyCoordinator::Options topoCoordOptions; + topoCoordOptions.maxSyncSourceLagSecs = Seconds(repl::maxSyncSourceLagSecs); + topoCoordOptions.clusterRole = serverGlobalParams.clusterRole; + + return std::make_unique( + svcCtx, + getGlobalReplSettings(), + std::make_unique( + svcCtx, storageInterface, replicationProcess), + makeReplicationExecutor(svcCtx), + std::make_unique(topoCoordOptions), + replicationProcess, + storageInterface, + SecureRandom().nextInt64()); +} + +void AttachedServiceLifecycle::initializeStateRequiredForStorageAccess(ServiceContext*) {} + +void AttachedServiceLifecycle::shutdownStateRequiredForStorageAccess(ServiceContext*, + BSONObjBuilder*) {} + +bool AttachedServiceLifecycle::initializedUsingDefaultSyncDelay() const { + return _initializedUsingDefaultSyncDelay; +} + +bool AttachedServiceLifecycle::shouldKeepThreadAliveUntilStorageEngineHasShutDown( + const StringData) const { + return false; +} + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/attached_storage/attached_service_lifecycle.h b/src/mongo/db/rss/attached_storage/attached_service_lifecycle.h new file mode 100644 index 00000000000..e39a32bafdb --- /dev/null +++ b/src/mongo/db/rss/attached_storage/attached_service_lifecycle.h @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/rss/service_lifecycle.h" + +namespace mongo::rss { + +class AttachedServiceLifecycle : public ServiceLifecycle { +public: + AttachedServiceLifecycle(); + + /** + * Initializes flow control based on oplog write rate. + */ + void initializeFlowControl(ServiceContext*) override; + + /** + * There are no storage engine extensions utilized. + */ + void initializeStorageEngineExtensions(ServiceContext*) override; + + /** + * Initializes a 'repl::ReplicationCoordinatorImpl'. + */ + std::unique_ptr initializeReplicationCoordinator( + ServiceContext*) override; + + /** + * There is no additional state required for storage access. + */ + void initializeStateRequiredForStorageAccess(ServiceContext*) override; + + /** + * There is no additional state required for storage access. + */ + void shutdownStateRequiredForStorageAccess(ServiceContext*, BSONObjBuilder*) override; + + bool initializedUsingDefaultSyncDelay() const override; + + /** + * There are no specific persistence threads that must outlive the storage engine. + */ + bool shouldKeepThreadAliveUntilStorageEngineHasShutDown(StringData) const override; + +private: + const bool _initializedUsingDefaultSyncDelay; +}; + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/persistence_provider.h b/src/mongo/db/rss/persistence_provider.h new file mode 100644 index 00000000000..39689ea33f8 --- /dev/null +++ b/src/mongo/db/rss/persistence_provider.h @@ -0,0 +1,131 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonelement.h" + +#include +#include + +#include + + +namespace mongo { +namespace rss { + +/** + * This class provides an abstraction around the persistence layer underlying the storage and + * replication subsystems. Depending on the configuration, the implementation may be backed by a + * local filesystem, a remote service, etc. The interface is built primarily around capabilities and + * expected behaviors, allowing consumers to act based on these flags, rather than needing to reason + * about how a particular provider would behave in a given context. + */ +class PersistenceProvider { +public: + virtual ~PersistenceProvider() = default; + + /** + * The name of this provider, for use in e.g. logging and error messages. + */ + virtual std::string name() const = 0; + + /** + * If not none, the KVEngine will use the returned Timestamp during initialization as the + * initial data timestamp. + */ + virtual boost::optional getSentinelDataTimestamp() const = 0; + + /** + * Additional configuration that shoudld be added to the WiredTiger config string for the + * 'wiredtiger_open' call. The 'flattenLeafPageDelta' is expected to be the corresponding + * WiredTigerConfig member value. + */ + virtual std::string getWiredTigerConfig(int flattenLeafPageDelta) const = 0; + + /** + * If true, the provider expects that all catalog identifiers will be replicated and identical + * between nodes. + */ + virtual bool shouldUseReplicatedCatalogIdentifiers() const = 0; + + /** + * If true, the provider expects that RecordIds will be replicated (either explicitly or + * implicitly) and identical between nodes. + */ + virtual bool shouldUseReplicatedRecordIds() const = 0; + + /** + * If true, writes to the oplog should be used as the unit of progress for flow control + * sampling. + */ + virtual bool shouldUseOplogWritesForFlowControlSampling() const = 0; + + /** + * If true, the node should step down prior to shutdown in order to minimize unavailability. + */ + virtual bool shouldStepDownForShutdown() const = 0; + + /** + * If true, data may not be availabile immediately after starting the storage engine, so systems + * like the catalog should not be initialized immediately. + */ + virtual bool shouldDelayDataAccessDuringStartup() const = 0; + + /** + * If true, the system should take precautions to avoid taking multiple checkopints for the same + * stable timestamp. The underlying key-value engine likely does not provide the necessary + * coordination by default. + */ + virtual bool shouldAvoidDuplicateCheckpoints() const = 0; + + /** + * If true, the storage provider supports the use of local, unreplicated collections. + */ + virtual bool supportsLocalCollections() const = 0; + + /** + * If true, the provider can support unstable checkpoints. + */ + virtual bool supportsUnstableCheckpoints() const = 0; + + /** + * If true, the provider can support logging (i.e. journaling) on individual tables. + */ + virtual bool supportsTableLogging() const = 0; + + /** + * If true, the provider supports multi-document transactions. + */ + virtual bool supportsMultiDocumentTransactions() const = 0; +}; + +} // namespace rss +} // namespace mongo diff --git a/src/mongo/db/rss/replicated_storage_service.cpp b/src/mongo/db/rss/replicated_storage_service.cpp new file mode 100644 index 00000000000..2d632c3e2fe --- /dev/null +++ b/src/mongo/db/rss/replicated_storage_service.cpp @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/rss/replicated_storage_service.h" + +namespace mongo::rss { +namespace { +const auto getReplicatedStorageService = + ServiceContext::declareDecoration(); +} // namespace + +ReplicatedStorageService& ReplicatedStorageService::get(ServiceContext* svcCtx) { + return getReplicatedStorageService(svcCtx); +} + +ReplicatedStorageService& ReplicatedStorageService::get(OperationContext* opCtx) { + return get(opCtx->getServiceContext()); +} + +PersistenceProvider& ReplicatedStorageService::getPersistenceProvider() { + invariant(_provider); + return *_provider; +} +void ReplicatedStorageService::setPersistenceProvider(std::unique_ptr&& p) { + _provider = std::move(p); +} + +ServiceLifecycle& ReplicatedStorageService::getServiceLifecycle() { + invariant(_lifecycle); + return *_lifecycle; +} + +void ReplicatedStorageService::setServiceLifecycle(std::unique_ptr&& l) { + _lifecycle = std::move(l); +} + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/replicated_storage_service.h b/src/mongo/db/rss/replicated_storage_service.h new file mode 100644 index 00000000000..9cdef505893 --- /dev/null +++ b/src/mongo/db/rss/replicated_storage_service.h @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/operation_context.h" +#include "mongo/db/rss/persistence_provider.h" +#include "mongo/db/rss/service_lifecycle.h" +#include "mongo/db/service_context.h" + +namespace mongo::rss { + +class ReplicatedStorageService { +public: + static ReplicatedStorageService& get(ServiceContext*); + static ReplicatedStorageService& get(OperationContext*); + + PersistenceProvider& getPersistenceProvider(); + void setPersistenceProvider(std::unique_ptr&&); + + ServiceLifecycle& getServiceLifecycle(); + void setServiceLifecycle(std::unique_ptr&&); + +private: + std::unique_ptr _provider; + std::unique_ptr _lifecycle; +}; + +} // namespace mongo::rss diff --git a/src/mongo/db/rss/service_lifecycle.h b/src/mongo/db/rss/service_lifecycle.h new file mode 100644 index 00000000000..b9c636dca66 --- /dev/null +++ b/src/mongo/db/rss/service_lifecycle.h @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2025-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/service_context.h" + +#include +#include +#include + +namespace mongo { +namespace rss { + +/** + * This class provides an abstraction for a set of functionalities related to the service lifecycle + * (i.e. startup and shutdown). + * + * The implementation details are generally closely related to the configured 'PersistenceProvider', + * but we separate it out from that class since that class is primarily focused on + * capabilities/behaviors, while this class instead represents a set of setup/teardown and related + * routines. + */ +class ServiceLifecycle { +public: + virtual ~ServiceLifecycle() = default; + + /** + * Initializes the flow control algorithm for the current service configuration. + */ + virtual void initializeFlowControl(ServiceContext*) = 0; + + /** + * Initializes any storage engine extensions necessary for the current service configuration. + */ + virtual void initializeStorageEngineExtensions(ServiceContext*) = 0; + + /** + * Initializes and returns the replication coordinator appropriate for the current service + * configuration. + */ + virtual std::unique_ptr initializeReplicationCoordinator( + ServiceContext*) = 0; + + /** + * Initializes any state required to access 'repl::StorageInterface'. This method will be run + * prior to 'initializeReplicationCoordinator'. + */ + virtual void initializeStateRequiredForStorageAccess(ServiceContext*) = 0; + + /** + * Tears down any state set up by 'initializeStateRequiredForStorageAccess'. + */ + virtual void shutdownStateRequiredForStorageAccess(ServiceContext*, BSONObjBuilder*) = 0; + + /** + * If true, this instance was initialized using the default syncdelay parameter rather than any + * user-configured value. + */ + virtual bool initializedUsingDefaultSyncDelay() const = 0; + + /** + * If true, the named thread must be kept alive until the storage engine has shut down. + */ + virtual bool shouldKeepThreadAliveUntilStorageEngineHasShutDown( + StringData threadName) const = 0; +}; + +} // namespace rss +} // namespace mongo diff --git a/src/mongo/db/service_context_d_test_fixture.cpp b/src/mongo/db/service_context_d_test_fixture.cpp index 3b0ab4a6886..e0cb324ca17 100644 --- a/src/mongo/db/service_context_d_test_fixture.cpp +++ b/src/mongo/db/service_context_d_test_fixture.cpp @@ -216,7 +216,7 @@ MongoDScopedGlobalServiceContextForTest::~MongoDScopedGlobalServiceContextForTes std::swap(storageGlobalParams.engineSetByUser, _stashedStorageParams.engineSetByUser); std::swap(storageGlobalParams.repair, _stashedStorageParams.repair); - storageGlobalParams.reset(); + storageGlobalParams.reset_forTest(); } } // namespace mongo diff --git a/src/mongo/db/startup_recovery.cpp b/src/mongo/db/startup_recovery.cpp index 1dde27f5cd8..e9c3b661cb7 100644 --- a/src/mongo/db/startup_recovery.cpp +++ b/src/mongo/db/startup_recovery.cpp @@ -89,6 +89,7 @@ #include "mongo/db/repl/repl_set_member_in_standalone_mode.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/storage_interface.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/control/journal_flusher.h" @@ -878,6 +879,14 @@ void startupRecovery(OperationContext* opCtx, StorageEngine* storageEngine, StorageEngine::LastShutdownState lastShutdownState, BSONObjBuilder* startupTimeElapsedBuilder = nullptr) { + auto& rss = rss::ReplicatedStorageService::get(opCtx); + if (rss.getPersistenceProvider().shouldDelayDataAccessDuringStartup()) { + LOGV2(10985327, + "Skip startupRecovery; it will be handled later when WT loads the " + "checkpoint"); + return; + } + invariant(!storageGlobalParams.repair); ServiceContext* svcCtx = opCtx->getServiceContext(); diff --git a/src/mongo/db/storage/BUILD.bazel b/src/mongo/db/storage/BUILD.bazel index ae2bc37e0c7..7b2651235e1 100644 --- a/src/mongo/db/storage/BUILD.bazel +++ b/src/mongo/db/storage/BUILD.bazel @@ -262,6 +262,7 @@ mongo_cc_library( name = "mdb_catalog", srcs = [ "mdb_catalog.cpp", + "//src/mongo/db/rss:persistence_provider.h", "//src/mongo/db/storage/kv:kv_engine.h", ], hdrs = [ @@ -270,6 +271,7 @@ mongo_cc_library( deps = [ ":record_store_base", "//src/mongo/db:server_base", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/storage:feature_document_util", "//src/mongo/db/storage:ident", ], @@ -423,6 +425,7 @@ mongo_cc_library( ], hdrs = [ "oplog_truncate_markers.h", + "//src/mongo/db/rss:persistence_provider.h", "//src/mongo/db/storage/kv:kv_engine.h", ], deps = [ @@ -501,6 +504,7 @@ mongo_cc_library( ":storage_repair_observer", "//src/mongo/db:server_base", "//src/mongo/db:shard_role", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/storage/kv:kv_drop_pending_ident_reaper", ], ) diff --git a/src/mongo/db/storage/devnull/BUILD.bazel b/src/mongo/db/storage/devnull/BUILD.bazel index 110ee95f55d..085e255218d 100644 --- a/src/mongo/db/storage/devnull/BUILD.bazel +++ b/src/mongo/db/storage/devnull/BUILD.bazel @@ -18,6 +18,7 @@ mongo_cc_library( hdrs = [ "devnull_kv_engine.h", "ephemeral_catalog_record_store.h", + "//src/mongo/db/rss:persistence_provider.h", "//src/mongo/db/storage/kv:kv_engine.h", ], deps = [ diff --git a/src/mongo/db/storage/devnull/devnull_kv_engine.h b/src/mongo/db/storage/devnull/devnull_kv_engine.h index 9ae22cc4e4e..0859b76c426 100644 --- a/src/mongo/db/storage/devnull/devnull_kv_engine.h +++ b/src/mongo/db/storage/devnull/devnull_kv_engine.h @@ -68,7 +68,8 @@ public: std::unique_ptr newRecoveryUnit() override; - Status createRecordStore(const NamespaceString& nss, + Status createRecordStore(const rss::PersistenceProvider&, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) override { return Status::OK(); @@ -89,6 +90,7 @@ public: KeyFormat keyFormat) override; Status createSortedDataInterface( + const rss::PersistenceProvider&, RecoveryUnit&, const NamespaceString& nss, const UUID& uuid, diff --git a/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp b/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp index 2717cc81c28..e49c3df0a39 100644 --- a/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp +++ b/src/mongo/db/storage/kv/kv_drop_pending_ident_reaper_test.cpp @@ -90,7 +90,8 @@ public: return nullptr; } - Status createRecordStore(const NamespaceString& nss, + Status createRecordStore(const rss::PersistenceProvider&, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) override { return Status::OK(); @@ -108,6 +109,7 @@ public: return {}; } Status createSortedDataInterface( + const rss::PersistenceProvider&, RecoveryUnit&, const NamespaceString& nss, const UUID& uuid, diff --git a/src/mongo/db/storage/kv/kv_engine.h b/src/mongo/db/storage/kv/kv_engine.h index 379df06e9a0..ad8d380046a 100644 --- a/src/mongo/db/storage/kv/kv_engine.h +++ b/src/mongo/db/storage/kv/kv_engine.h @@ -32,6 +32,7 @@ #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/bson/timestamp.h" +#include "mongo/db/rss/persistence_provider.h" #include "mongo/db/storage/compact_options.h" #include "mongo/db/storage/record_store.h" #include "mongo/db/storage/sorted_data_interface.h" @@ -126,7 +127,8 @@ public: * * Creates a 'RecordStore' and generated from the provided 'options'. */ - virtual Status createRecordStore(const NamespaceString& nss, + virtual Status createRecordStore(const rss::PersistenceProvider&, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) = 0; @@ -201,6 +203,7 @@ public: virtual bool underCachePressure(int concurrentWriteOuts, int concurrentReadOuts) = 0; virtual Status createSortedDataInterface( + const rss::PersistenceProvider&, RecoveryUnit&, const NamespaceString& nss, const UUID& uuid, @@ -257,10 +260,11 @@ public: * This recovery process makes no guarantees about the integrity of data recovered or even that * it still exists when recovered. */ - virtual Status recoverOrphanedIdent(const NamespaceString& nss, + virtual Status recoverOrphanedIdent(const rss::PersistenceProvider& provider, + const NamespaceString& nss, StringData ident, const RecordStore::Options& recordStoreOptions) { - auto status = createRecordStore(nss, ident, recordStoreOptions); + auto status = createRecordStore(provider, nss, ident, recordStoreOptions); if (status.isOK()) { return {ErrorCodes::DataModifiedByRepair, "Orphan recovery created a new record store"}; } @@ -375,6 +379,22 @@ public: */ virtual void setJournalListener(JournalListener* jl) = 0; + /** + * See `StorageEngine::setLastMaterializedLsn` + */ + virtual void setLastMaterializedLsn(uint64_t lsn) {} + + /** + * Configures the specified checkpoint as the starting point for recovery. + */ + virtual void setRecoveryCheckpointMetadata(StringData checkpointMetadata) {} + + /** + * Configures the storage engine as the leader, allowing it to flush checkpoints to remote + * storage. + */ + virtual void promoteToLeader() {} + /** * See `StorageEngine::setStableTimestamp` */ diff --git a/src/mongo/db/storage/kv/kv_engine_test_harness.cpp b/src/mongo/db/storage/kv/kv_engine_test_harness.cpp index bb64ae5ed9e..07cac0f87ae 100644 --- a/src/mongo/db/storage/kv/kv_engine_test_harness.cpp +++ b/src/mongo/db/storage/kv/kv_engine_test_harness.cpp @@ -47,6 +47,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/record_id.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/db/storage/key_format.h" #include "mongo/db/storage/key_string/key_string.h" @@ -116,8 +117,10 @@ protected: auto clientAndCtx = makeClientAndCtx("opCtx"); auto opCtx = clientAndCtx.opCtx(); KVEngine* engine = helper->getEngine(); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); ASSERT_OK( - engine->createRecordStore(NamespaceString::createNamespaceString_forTest("catalog"), + engine->createRecordStore(provider, + NamespaceString::createNamespaceString_forTest("catalog"), "collection-catalog", RecordStore::Options{})); @@ -231,7 +234,8 @@ protected: const RecordStore::Options& recordStoreOptions, boost::optional uuid) { auto opCtx = _makeOperationContext(engine); - ASSERT_OK(engine->createRecordStore(nss, ident, recordStoreOptions)); + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); + ASSERT_OK(engine->createRecordStore(provider, nss, ident, recordStoreOptions)); auto rs = engine->getRecordStore(opCtx.get(), nss, ident, recordStoreOptions, uuid); ASSERT(rs); return rs; @@ -348,8 +352,14 @@ TEST_F(KVEngineTestHarness, SimpleSorted1) { { auto opCtx = _makeOperationContext(engine); auto& ru = *shard_role_details::getRecoveryUnit(opCtx.get()); - ASSERT_OK(engine->createSortedDataInterface( - ru, kNss, kUUID, kIdent, config, boost::none /* storageEngineIndexOptions */)); + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); + ASSERT_OK(engine->createSortedDataInterface(provider, + ru, + kNss, + kUUID, + kIdent, + config, + boost::none /* storageEngineIndexOptions */)); sorted = engine->getSortedDataInterface( opCtx.get(), ru, kNss, kUUID, kIdent, config, kRecordStoreOptions.keyFormat); ASSERT(sorted); diff --git a/src/mongo/db/storage/kv/kv_engine_timestamps_test.cpp b/src/mongo/db/storage/kv/kv_engine_timestamps_test.cpp index acdda1064c3..368b968da4c 100644 --- a/src/mongo/db/storage/kv/kv_engine_timestamps_test.cpp +++ b/src/mongo/db/storage/kv/kv_engine_timestamps_test.cpp @@ -39,6 +39,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/record_id.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/db/storage/kv/kv_engine.h" @@ -237,7 +238,9 @@ public: const auto nss = NamespaceString::createNamespaceString_forTest("a.b"); const auto ident = "collection-ident"; RecordStore::Options options; - ASSERT_OK(engine->createRecordStore(nss, ident, options)); + auto& provider = + rss::ReplicatedStorageService::get(getGlobalServiceContext()).getPersistenceProvider(); + ASSERT_OK(engine->createRecordStore(provider, nss, ident, options)); rs = engine->getRecordStore(op, nss, ident, options, UUID::gen()); ASSERT(rs); } diff --git a/src/mongo/db/storage/mdb_catalog.cpp b/src/mongo/db/storage/mdb_catalog.cpp index 09e95b0a291..d1b06a0987a 100644 --- a/src/mongo/db/storage/mdb_catalog.cpp +++ b/src/mongo/db/storage/mdb_catalog.cpp @@ -34,6 +34,7 @@ #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/record_id.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/storage/feature_document_util.h" #include "mongo/db/storage/kv/kv_engine.h" #include "mongo/db/storage/record_store.h" @@ -242,7 +243,9 @@ StatusWith> MDBCatalog::createRecordStoreForEntry( const MDBCatalog::EntryIdentifier& entry, const boost::optional& uuid, const RecordStore::Options& recordStoreOptions) { - Status status = _engine->createRecordStore(entry.nss, entry.ident, recordStoreOptions); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + Status status = + _engine->createRecordStore(provider, entry.nss, entry.ident, recordStoreOptions); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/storage/storage_engine.h b/src/mongo/db/storage/storage_engine.h index d28cf95902b..a1751f1fbb9 100644 --- a/src/mongo/db/storage/storage_engine.h +++ b/src/mongo/db/storage/storage_engine.h @@ -715,6 +715,26 @@ public: */ virtual boost::optional getLastStableRecoveryTimestamp() const = 0; + /** + * Sets the last materialized LSN, marking the highest phylog LSN + * that has been successfully written to the page server and should have no holes. + * + * TODO: Revisit how to handle cases where mongod speaks with a log server + * in a non-local zone due to failover. + */ + virtual void setLastMaterializedLsn(uint64_t lsn) = 0; + + /** + * Configures the specified checkpoint as the starting point for recovery. + */ + virtual void setRecoveryCheckpointMetadata(StringData checkpointMetadata) = 0; + + /** + * Configures the storage engine as the leader, allowing it to flush checkpoints to remote + * storage. + */ + virtual void promoteToLeader() = 0; + /** * Sets the highest timestamp at which the storage engine is allowed to take a checkpoint. This * timestamp must not decrease unless force=true is set, in which case we force the stable diff --git a/src/mongo/db/storage/storage_engine_impl.cpp b/src/mongo/db/storage/storage_engine_impl.cpp index 2303b1520d2..3971b8b34cf 100644 --- a/src/mongo/db/storage/storage_engine_impl.cpp +++ b/src/mongo/db/storage/storage_engine_impl.cpp @@ -38,6 +38,7 @@ #include "mongo/db/local_catalog/catalog_raii.h" #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/storage/backup_cursor_hooks.h" #include "mongo/db/storage/deferred_drop_record_store.h" #include "mongo/db/storage/disk_space_monitor.h" @@ -130,6 +131,15 @@ StorageEngineImpl::StorageEngineImpl(OperationContext* opCtx, invariant(prevRecoveryUnit->isNoop()); shard_role_details::setRecoveryUnit( opCtx, _engine->newRecoveryUnit(), WriteUnitOfWork::RecoveryUnitState::kNotInUnitOfWork); + + auto& rss = rss::ReplicatedStorageService::get(opCtx->getServiceContext()); + if (rss.getPersistenceProvider().shouldDelayDataAccessDuringStartup()) { + LOGV2(10985326, + "Skip loading catalog on startup; it will be handled later when WT loads the " + "checkpoint"); + return; + } + // If we throw in this constructor, make sure to destroy the RecoveryUnit instance created above // before '_engine' is destroyed. ScopeGuard recoveryUnitResetGuard([&] { @@ -181,8 +191,9 @@ void StorageEngineImpl::loadMDBCatalog(OperationContext* opCtx, if (!catalogExists) { WriteUnitOfWork uow(opCtx); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); auto status = _engine->createRecordStore( - kCatalogInfoNamespace, ident::kMbdCatalog, catalogRecordStoreOpts); + provider, kCatalogInfoNamespace, ident::kMbdCatalog, catalogRecordStoreOpts); // BadValue is usually caused by invalid configuration string. // We still fassert() but without a stack trace. @@ -422,8 +433,9 @@ Status StorageEngineImpl::_recoverOrphanedCollection(OperationContext* opCtx, WriteUnitOfWork wuow(opCtx); const auto recordStoreOptions = _catalog->getParsedRecordStoreOptions(opCtx, catalogId, collectionName); - Status status = - _engine->recoverOrphanedIdent(collectionName, collectionIdent, recordStoreOptions); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + Status status = _engine->recoverOrphanedIdent( + provider, collectionName, collectionIdent, recordStoreOptions); bool dataModified = status.code() == ErrorCodes::DataModifiedByRepair; if (!status.isOK() && !dataModified) { @@ -706,6 +718,18 @@ void StorageEngineImpl::setJournalListener(JournalListener* jl) { _engine->setJournalListener(jl); } +void StorageEngineImpl::setLastMaterializedLsn(uint64_t lsn) { + _engine->setLastMaterializedLsn(lsn); +} + +void StorageEngineImpl::setRecoveryCheckpointMetadata(StringData checkpointMetadata) { + _engine->setRecoveryCheckpointMetadata(checkpointMetadata); +} + +void StorageEngineImpl::promoteToLeader() { + _engine->promoteToLeader(); +} + void StorageEngineImpl::setStableTimestamp(Timestamp stableTimestamp, bool force) { _engine->setStableTimestamp(stableTimestamp, force); } diff --git a/src/mongo/db/storage/storage_engine_impl.h b/src/mongo/db/storage/storage_engine_impl.h index cf8e7fef645..7012935221f 100644 --- a/src/mongo/db/storage/storage_engine_impl.h +++ b/src/mongo/db/storage/storage_engine_impl.h @@ -144,6 +144,12 @@ public: void cleanShutdown(ServiceContext* svcCtx, bool memLeakAllowed) override; + void setLastMaterializedLsn(uint64_t lsn) override; + + void setRecoveryCheckpointMetadata(StringData checkpointMetadata) override; + + void promoteToLeader() override; + void setStableTimestamp(Timestamp stableTimestamp, bool force = false) override; Timestamp getStableTimestamp() const override; diff --git a/src/mongo/db/storage/storage_engine_mock.h b/src/mongo/db/storage/storage_engine_mock.h index 7f76e0d07b1..4a8e729197c 100644 --- a/src/mongo/db/storage/storage_engine_mock.h +++ b/src/mongo/db/storage/storage_engine_mock.h @@ -129,7 +129,14 @@ public: boost::optional getLastStableRecoveryTimestamp() const final { MONGO_UNREACHABLE; } - void setStableTimestamp(Timestamp stableTimestamp, bool force = false) final {} + + void setLastMaterializedLsn(uint64_t lsn) final {} + + void setRecoveryCheckpointMetadata(StringData checkpointMetadata) final {} + + void promoteToLeader() final {} + + void setStableTimestamp(Timestamp stableTimestamp, bool force = false) override {} Timestamp getStableTimestamp() const override { return Timestamp(); } diff --git a/src/mongo/db/storage/storage_engine_test_fixture.h b/src/mongo/db/storage/storage_engine_test_fixture.h index 03d24b05bf2..f05b9f4d815 100644 --- a/src/mongo/db/storage/storage_engine_test_fixture.h +++ b/src/mongo/db/storage/storage_engine_test_fixture.h @@ -36,6 +36,7 @@ #include "mongo/db/local_catalog/durable_catalog.h" #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/repl/storage_interface_impl.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context_d_test_fixture.h" #include "mongo/db/storage/kv/kv_engine.h" #include "mongo/db/storage/mdb_catalog.h" @@ -117,8 +118,9 @@ public: */ Status createCollTable(OperationContext* opCtx, NamespaceString collName) { const std::string identName = _storageEngine->generateNewCollectionIdent(collName.dbName()); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); return _storageEngine->getEngine()->createRecordStore( - collName, identName, RecordStore::Options{}); + provider, collName, identName, RecordStore::Options{}); } Status dropIndexTable(OperationContext* opCtx, NamespaceString nss, StringData indexName) { diff --git a/src/mongo/db/storage/storage_options.cpp b/src/mongo/db/storage/storage_options.cpp index 55795e04a71..cd2228bf656 100644 --- a/src/mongo/db/storage/storage_options.cpp +++ b/src/mongo/db/storage/storage_options.cpp @@ -45,10 +45,14 @@ namespace mongo { StorageGlobalParams::StorageGlobalParams() { - reset(); + _reset(); } -void StorageGlobalParams::reset() { +void StorageGlobalParams::reset_forTest() { + _reset(); +} + +void StorageGlobalParams::_reset() { engine = "wiredTiger"; engineSetByUser = false; dbpath = kDefaultDbPath; @@ -60,7 +64,7 @@ void StorageGlobalParams::reset() { noTableScan.store(false); directoryperdb = false; - syncdelay.store(60.0); + syncdelay.store(-1.0); queryableBackupMode = false; groupCollections = false; oplogMinRetentionHours.store(0.0); diff --git a/src/mongo/db/storage/storage_options.h b/src/mongo/db/storage/storage_options.h index 7cf3c982d21..770696cfda8 100644 --- a/src/mongo/db/storage/storage_options.h +++ b/src/mongo/db/storage/storage_options.h @@ -44,7 +44,7 @@ namespace mongo { struct StorageGlobalParams { StorageGlobalParams(); - void reset(); + void reset_forTest(); // Returns the directory path used by the spill storage engine to store spilled data. std::string getSpillDbPath() const; @@ -109,13 +109,14 @@ struct StorageGlobalParams { // --syncdelay // Delay in seconds between triggering the next checkpoint after the completion of the previous - // one. A value of 0 indicates that checkpointing will be skipped. + // one. A value of 0 indicates that checkpointing will be skipped. A value <0 + // will result in using the default value for the configured persistence provider. // Do not set this value on production systems. // In almost every situation, you should use the default setting. // This parameter is both a server parameter and a configuration parameter, and to resolve - // conflicts between the two the default must be set here. + // conflicts between the two, a default sentinel (<0) must be set here. static constexpr double kMaxSyncdelaySecs = 60 * 60; // 1hr - AtomicWord syncdelay{60.0}; // seconds between checkpoints + AtomicWord syncdelay{-1.0}; // seconds between checkpoints // --queryableBackupMode // Prevents user-originating operations from performing writes to the server. Internally @@ -139,6 +140,9 @@ struct StorageGlobalParams { // Test-only option. Disables table logging. bool forceDisableTableLogging = false; + +private: + void _reset(); }; extern StorageGlobalParams storageGlobalParams; diff --git a/src/mongo/db/storage/wiredtiger/BUILD.bazel b/src/mongo/db/storage/wiredtiger/BUILD.bazel index 31796f67474..e24205f1519 100644 --- a/src/mongo/db/storage/wiredtiger/BUILD.bazel +++ b/src/mongo/db/storage/wiredtiger/BUILD.bazel @@ -112,6 +112,7 @@ mongo_cc_library( "//src/mongo/db:server_base", "//src/mongo/db:server_feature_flags", "//src/mongo/db:service_context", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/storage:container", "//src/mongo/db/storage:exceptions", "//src/mongo/db/storage:execution_context", @@ -173,6 +174,7 @@ mongo_cc_library( deps = [ ":storage_wiredtiger_core", "//src/mongo/db:service_context_test_fixture", + "//src/mongo/db/rss:persistence_provider_impl", "//src/mongo/db/storage:record_store_test_harness", "//src/mongo/util:clock_source_mock", ], @@ -228,6 +230,7 @@ mongo_cc_unit_test( "//src/mongo/db/storage:storage_options", "//src/mongo/db/storage/kv:kv_engine_test_harness", "//src/mongo/idl:server_parameter_test_controller", + "//src/mongo/idl:server_parameter_test_util", "//src/mongo/util:clock_source_mock", ], ) diff --git a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.cpp index e87e44f2062..22c21e2b058 100644 --- a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.cpp +++ b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.cpp @@ -69,7 +69,7 @@ SpillWiredTigerKVEngine::SpillWiredTigerKVEngine(const std::string& canonicalNam } std::string config = - generateWTOpenConfigString(_wtConfig, wtExtensions.getOpenExtensionsConfig()); + generateWTOpenConfigString(_wtConfig, wtExtensions.getOpenExtensionsConfig(), ""); LOGV2(10158000, "Opening spill WiredTiger", "config"_attr = config); auto startTime = Date_t::now(); diff --git a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.h b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.h index 864410495e1..278e5e0e664 100644 --- a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.h +++ b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine.h @@ -94,7 +94,8 @@ public: MONGO_UNREACHABLE; } - Status createRecordStore(const NamespaceString& nss, + Status createRecordStore(const rss::PersistenceProvider&, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) override { MONGO_UNREACHABLE; @@ -126,6 +127,7 @@ public: } Status createSortedDataInterface( + const rss::PersistenceProvider&, RecoveryUnit&, const NamespaceString& nss, const UUID& uuid, diff --git a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine_test.cpp b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine_test.cpp index 07dd5d80727..1950c295b81 100644 --- a/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine_test.cpp +++ b/src/mongo/db/storage/wiredtiger/spill_wiredtiger_kv_engine_test.cpp @@ -32,10 +32,12 @@ #include "mongo/base/init.h" // IWYU pragma: keep #include "mongo/base/string_data.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_test_fixture.h" #include "mongo/db/storage/wiredtiger/wiredtiger_extensions.h" #include "mongo/db/storage/wiredtiger/wiredtiger_global_options_gen.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/unittest/temp_dir.h" #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source_mock.h" diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp index 3b844b698fe..09bb3a5bf4d 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp @@ -34,6 +34,7 @@ #include "mongo/db/service_context.h" #include "mongo/util/assert_util.h" #include "mongo/util/decorable.h" +#include "mongo/util/str.h" #include #include @@ -49,8 +50,33 @@ ServiceContext::ConstructorActionRegisterer setWiredTigerCustomizationHooks{ const auto getCustomizationHooks = ServiceContext::declareDecoration>(); + +const auto getWiredTigerCustomizationHooksRegistry = + ServiceContext::declareDecoration(); + } // namespace + +WiredTigerCustomizationHooksRegistry& WiredTigerCustomizationHooksRegistry::get( + ServiceContext* service) { + return getWiredTigerCustomizationHooksRegistry(service); +} + + +void WiredTigerCustomizationHooksRegistry::addHook( + std::unique_ptr custHook) { + invariant(custHook); + _hooks.push_back(std::move(custHook)); +} + +std::string WiredTigerCustomizationHooksRegistry::getTableCreateConfig(StringData tableName) const { + str::stream config; + for (const auto& h : _hooks) { + config << h->getTableCreateConfig(tableName); + } + return config; +} + void WiredTigerCustomizationHooks::set(ServiceContext* service, std::unique_ptr customHooks) { auto& hooks = getCustomizationHooks(service); diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h index 7029574a80e..57230008217 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h @@ -31,6 +31,7 @@ #include #include +#include namespace mongo { class StringData; @@ -58,4 +59,27 @@ public: virtual std::string getTableCreateConfig(StringData tableName); }; +/** + * Registry to store multiple WiredTiger customization hooks. + */ +class WiredTigerCustomizationHooksRegistry { +public: + static WiredTigerCustomizationHooksRegistry& get(ServiceContext* serviceContext); + + /** + * Adds a WiredTiger customization hook to the registry. Multiple hooks can be + * added, and their configurations will be combined. + */ + void addHook(std::unique_ptr custHook); + + /** + * Gets a combined configuration string from all hooks in the registry for + * the provided table name during the `WT_SESSION::create` call. + */ + std::string getTableCreateConfig(StringData tableName) const; + +private: + std::vector> _hooks; +}; + } // namespace mongo diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.h b/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.h index 559f001c9a8..0d6b1b3b6cf 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.h @@ -69,6 +69,7 @@ public: std::string liveRestoreSource; int liveRestoreThreads; double liveRestoreReadSizeMB; + int flattenLeafPageDelta; std::string collectionBlockCompressor; bool useIndexPrefixCompression; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.idl b/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.idl index 30970fd068c..9d5da36ba3e 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.idl +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_global_options.idl @@ -126,6 +126,18 @@ server_parameters: lte: 100 redact: false + wiredTigerFlattenLeafPageDelta: + description: >- + WiredTiger page read rewrites the leaf pages with deltas to a new disk image if + successful. We will use a ternary where 0=Disabled, 1=Enabled if disaggregatedStorageEnabled is true, 2=Enabled unconditionally. + set_at: startup + cpp_varname: "wiredTigerGlobalOptions.flattenLeafPageDelta" + default: 1 + validator: + gte: 0 + lte: 2 + redact: false + wiredTigerEvictionDirtyTargetGB: description: >- Absolute dirty cache eviction target. Once eviction begins, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp index 6b849fb4f2c..8b780282e54 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp @@ -173,6 +173,8 @@ StatusWith WiredTigerIndex::generateCreateString(const std::string& ss << WiredTigerCustomizationHooks::get(getGlobalServiceContext()) ->getTableCreateConfig(tableName); + ss << WiredTigerCustomizationHooksRegistry::get(getGlobalServiceContext()) + .getTableCreateConfig(tableName); ss << sysIndexConfig << ","; ss << collIndexConfig << ","; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp index 15d3c0f0479..fe69135d4d3 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp @@ -34,6 +34,7 @@ #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/storage_engine.h" #include "mongo/db/storage/storage_engine_impl.h" @@ -141,7 +142,9 @@ public: } } - WiredTigerKVEngineBase::WiredTigerConfig wtConfig = getWiredTigerConfigFromStartupOptions(); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + WiredTigerKVEngineBase::WiredTigerConfig wtConfig = + getWiredTigerConfigFromStartupOptions(provider); wtConfig.cacheSizeMB = cacheMB; wtConfig.inMemory = params.inMemory; if (params.inMemory) { @@ -154,6 +157,7 @@ public: &opCtx->fastClockSource(), std::move(wtConfig), WiredTigerExtensions::get(opCtx->getServiceContext()), + provider, params.repair, isReplSet, shouldRecoverFromOplogAsStandalone, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp index 1a96b28ba42..43746f811c6 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp @@ -39,6 +39,7 @@ #include "mongo/db/commands/server_status_metric.h" #include "mongo/db/index_names.h" #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/db/server_options.h" #include "mongo/db/server_parameter.h" @@ -193,9 +194,9 @@ bool WiredTigerFileVersion::shouldDowngrade(bool hasRecoveryTimestamp, bool isRe _startupVersion == StartupVersion::IS_42; } - // (Generic FCV reference): Only consider downgrading when FCV has been fully downgraded to last - // continuous or last LTS. It's possible for WiredTiger to introduce a data format change in a - // continuous release. This FCV gate must remain across binary version releases. + // (Generic FCV reference): Only consider downgrading when FCV has been fully downgraded to + // last continuous or last LTS. It's possible for WiredTiger to introduce a data format + // change in a continuous release. This FCV gate must remain across binary version releases. const auto currentVersion = fcvSnapshot.getVersion(); if (currentVersion != multiversion::GenericFCV::kLastContinuous && currentVersion != multiversion::GenericFCV::kLastLTS) { @@ -209,9 +210,9 @@ bool WiredTigerFileVersion::shouldDowngrade(bool hasRecoveryTimestamp, bool isRe } if (hasRecoveryTimestamp) { - // If we're not running with `--replSet`, don't allow downgrades if the node needed to run - // replication recovery. Having a recovery timestamp implies recovery must be run, but it - // was not. + // If we're not running with `--replSet`, don't allow downgrades if the node needed to + // run replication recovery. Having a recovery timestamp implies recovery must be run, + // but it was not. return false; } @@ -236,28 +237,28 @@ std::string WiredTigerFileVersion::getDowngradeString() { } // With the introduction of continuous releases, there are two downgrade paths from kLatest. - // Either to kLastContinuous or kLastLTS. It's possible for the data format to differ between - // kLastContinuous and kLastLTS and we'll need to handle that appropriately here. We only - // consider downgrading when FCV has been fully downgraded. + // Either to kLastContinuous or kLastLTS. It's possible for the data format to differ + // between kLastContinuous and kLastLTS and we'll need to handle that appropriately here. We + // only consider downgrading when FCV has been fully downgraded. const auto currentVersion = fcvSnapshot.getVersion(); - // (Generic FCV reference): This FCV check should exist across LTS binary versions because the - // logic for keeping the WiredTiger release version compatible with the server FCV version will - // be the same across different LTS binary versions. + // (Generic FCV reference): This FCV check should exist across LTS binary versions because + // the logic for keeping the WiredTiger release version compatible with the server FCV + // version will be the same across different LTS binary versions. if (currentVersion == multiversion::GenericFCV::kLastContinuous) { // If the data format between kLatest and kLastContinuous differs, change the // 'kLastContinuousWTRelease' version. return kLastContinuousWTRelease; - // (Generic FCV reference): This FCV check should exist across LTS binary versions because - // the logic for keeping the WiredTiger release version compatible with the server FCV - // version will be the same across different LTS binary versions. + // (Generic FCV reference): This FCV check should exist across LTS binary versions + // because the logic for keeping the WiredTiger release version compatible with the + // server FCV version will be the same across different LTS binary versions. } else if (currentVersion == multiversion::GenericFCV::kLastLTS) { // If the data format between kLatest and kLastLTS differs, change the // 'kLastLTSWTRelease' version. return kLastLTSWTRelease; } - // We're in a state that's not ready to downgrade. Use the latest WiredTiger version for this - // binary. + // We're in a state that's not ready to downgrade. Use the latest WiredTiger version for + // this binary. return kLatestWTRelease; } @@ -308,8 +309,9 @@ private: AtomicWord _shuttingDown{false}; stdx::mutex _mutex; // protects _condvar - // The session sweeper thread idles on this condition variable for a particular time duration - // between cleaning up expired sessions. It can be triggered early to expedite shutdown. + // The session sweeper thread idles on this condition variable for a particular time + // duration between cleaning up expired sessions. It can be triggered early to expedite + // shutdown. stdx::condition_variable _condvar; }; @@ -341,7 +343,8 @@ void setKeyOnCursor(WT_CURSOR* c, const std::variant, int6 } // namespace std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerConfig& wtConfig, - StringData extensionsConfig) { + StringData extensionsConfig, + StringData providerConfig) { std::stringstream ss; ss << "create,"; ss << "cache_size=" << wtConfig.cacheSizeMB << "M,"; @@ -365,6 +368,7 @@ std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerC ss << "config_base=false,"; ss << "statistics=(fast),"; + // TODO: SERVER-109794 move this block into the corresponding persistence providers. if (wtConfig.inMemory) { invariant(!wtConfig.logEnabled); // If we've requested an ephemeral instance we store everything into memory instead of @@ -380,6 +384,8 @@ std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerC } } + ss << providerConfig; + ss << "builtin_extension_config=(zstd=(compression_level=" << wtConfig.zstdCompressorLevel << ")),"; @@ -406,24 +412,25 @@ std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerC ss << "),"; } if constexpr (kAddressSanitizerEnabled || kThreadSanitizerEnabled) { - // For applications using WT, advancing a cursor invalidates the data/memory that cursor was - // pointing to. WT performs the optimization of managing its own memory. The unit of memory - // allocation is a page. Walking a cursor from one key/value to the next often lands on the - // same page, which has the effect of keeping the address of the prior key/value valid. For - // a bug to occur, the cursor must move across pages, and the prior page must be - // evicted. While rare, this can happen, resulting in reading random memory. + // For applications using WT, advancing a cursor invalidates the data/memory that cursor + // was pointing to. WT performs the optimization of managing its own memory. The unit of + // memory allocation is a page. Walking a cursor from one key/value to the next often + // lands on the same page, which has the effect of keeping the address of the prior + // key/value valid. For a bug to occur, the cursor must move across pages, and the prior + // page must be evicted. While rare, this can happen, resulting in reading random + // memory. // - // The cursor copy debug mode will instead cause WT to malloc/free memory for each key/value - // a cursor is positioned on. Thus, enabling when using with address sanitizer will catch - // many cases of dereferencing invalid cursor positions. Note, there is a known caveat: a - // free/malloc for roughly the same allocation size can often return the same memory - // address. This is a scenario where the address sanitizer is not able to detect a - // use-after-free error. + // The cursor copy debug mode will instead cause WT to malloc/free memory for each + // key/value a cursor is positioned on. Thus, enabling when using with address sanitizer + // will catch many cases of dereferencing invalid cursor positions. Note, there is a + // known caveat: a free/malloc for roughly the same allocation size can often return the + // same memory address. This is a scenario where the address sanitizer is not able to + // detect a use-after-free error. // - // Additionally, WT does not use the standard C thread model and thus TSAN can report false - // data races when touching memory that was allocated within WT. The cursor_copy mode - // alleviates this by copying all returned data to its own buffer before leaving the storage - // engine. + // Additionally, WT does not use the standard C thread model and thus TSAN can report + // false data races when touching memory that was allocated within WT. The cursor_copy + // mode alleviates this by copying all returned data to its own buffer before leaving + // the storage engine. ss << "debug_mode=(cursor_copy=true),"; } if constexpr (kThreadSanitizerEnabled) { @@ -435,9 +442,9 @@ std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerC if (TestingProctor::instance().isEnabled()) { // Enable debug write-ahead logging for all tables when testing is enabled. // - // If MongoDB startup fails, there may be clues from the previous run still left in the WT - // log files that can provide some insight into how the system got into a bad state. When - // testing is enabled, keep around some of these files for investigative purposes. + // If MongoDB startup fails, there may be clues from the previous run still left in the + // WT log files that can provide some insight into how the system got into a bad state. + // When testing is enabled, keep around some of these files for investigative purposes. // // We strive to keep 4 minutes of logs. Increase the retention for tests that take // checkpoints more often. @@ -641,6 +648,7 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, ClockSource* clockSource, WiredTigerConfig wtConfig, const WiredTigerExtensions& wtExtensions, + const rss::PersistenceProvider& provider, bool repair, bool isReplSet, bool shouldRecoverFromOplogAsStandalone, @@ -653,7 +661,8 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, _inRepairMode(repair), _isReplSet(isReplSet), _shouldRecoverFromOplogAsStandalone(shouldRecoverFromOplogAsStandalone), - _inStandaloneMode(inStandaloneMode) { + _inStandaloneMode(inStandaloneMode), + _supportsTableLogging(provider.supportsTableLogging()) { _pinnedOplogTimestamp.store(Timestamp::max().asULL()); boost::filesystem::path journalPath = path; journalPath /= "journal"; @@ -672,7 +681,9 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, } std::string config = - generateWTOpenConfigString(_wtConfig, wtExtensions.getOpenExtensionsConfig()); + generateWTOpenConfigString(_wtConfig, + wtExtensions.getOpenExtensionsConfig(), + provider.getWiredTigerConfig(_wtConfig.flattenLeafPageDelta)); LOGV2(22315, "Opening WiredTiger", "config"_attr = config); auto startTime = Date_t::now(); @@ -681,6 +692,11 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, _eventHandler.setStartupSuccessful(); _wtOpenConfig = config; + if (provider.getSentinelDataTimestamp().has_value()) { + setInitialDataTimestamp(provider.getSentinelDataTimestamp().value()); + setLastMaterializedLsn(provider.getSentinelDataTimestamp().value().asULL()); + } + { char buf[(2 * 8 /*bytes in hex*/) + 1 /*nul terminator*/]; invariantWTOK(_conn->query_timestamp(_conn, buf, "get=recovery"), nullptr); @@ -739,10 +755,10 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, setOldestTimestamp(_recoveryTimestamp, false); } - // Pin the oldest timestamp prior to calling `setStableTimestamp` as that attempts to - // advance the oldest timestamp. We do this pinning to give features such as resharding - // an opportunity to re-pin the oldest timestamp after a restart. The assumptions this - // relies on are that: + // Pin the oldest timestamp prior to calling `setStableTimestamp` as that attempts + // to advance the oldest timestamp. We do this pinning to give features such as + // resharding an opportunity to re-pin the oldest timestamp after a restart. The + // assumptions this relies on are that: // // 1) The feature stores the desired pin timestamp in some local collection. // 2) This temporary pinning lasts long enough for the catalog to be loaded and @@ -762,9 +778,9 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, } if (isEphemeral() && !TestingProctor::instance().isEnabled()) { - // We do not maintain any snapshot history for the ephemeral storage engine in production - // because replication and sharded transactions do not currently run on the inMemory engine. - // It is live in testing, however. + // We do not maintain any snapshot history for the ephemeral storage engine in + // production because replication and sharded transactions do not currently run on the + // inMemory engine. It is live in testing, however. minSnapshotHistoryWindowInSeconds.store(0); } @@ -803,14 +819,14 @@ void WiredTigerKVEngine::notifyReplStartupRecoveryComplete(RecoveryUnit& ru) { // timestamp upon exiting startup recovery. This is because it is not safe to begin taking // stable checkpoints while the oldest timestamp is ahead of the stable timestamp. // - // If we recover from an unstable checkpoint, such as in the startup recovery for restore case - // after we have finished oplog replay, we will start up with a null stable timestamp. As a - // result, we can safely advance the oldest timestamp. + // If we recover from an unstable checkpoint, such as in the startup recovery for restore + // case after we have finished oplog replay, we will start up with a null stable timestamp. + // As a result, we can safely advance the oldest timestamp. // // If we recover with a stable checkpoint, the stable timestamp will be set to the previous // value. In this case, we expect the oldest timestamp to be advanced in lockstep with the - // stable timestamp during any recovery process, and so the oldest timestamp should never exceed - // the stable timestamp. + // stable timestamp during any recovery process, and so the oldest timestamp should never + // exceed the stable timestamp. const Timestamp oldest = getOldestTimestamp(); const Timestamp stable = getStableTimestamp(); uassert(8470600, @@ -864,9 +880,9 @@ void WiredTigerKVEngine::_openWiredTiger(const std::string& path, const std::str } if (_eventHandler.isWtIncompatible()) { - // WT 4.4+ will refuse to startup on datafiles left behind by 4.0 and earlier. This behavior - // is enforced outside of `require_min`. This condition is detected via a specific error - // message from WiredTiger. + // WT 4.4+ will refuse to startup on datafiles left behind by 4.0 and earlier. This + // behavior is enforced outside of `require_min`. This condition is detected via a + // specific error message from WiredTiger. if (_inRepairMode) { // In case this process was started with `--repair`, remove the "repair incomplete" // file. @@ -954,9 +970,9 @@ void WiredTigerKVEngine::cleanShutdown(bool memLeakAllowed) { syncSizeInfo(/*syncToDisk=*/true); // The size storer has to be destructed after the session cache has shut down. This sets the - // shutdown flag internally in the session cache. As operations get interrupted during shutdown, - // they release their session back to the session cache. If the shutdown flag has been set, - // released sessions will skip flushing the size storer. + // shutdown flag internally in the session cache. As operations get interrupted during + // shutdown, they release their session back to the session cache. If the shutdown flag has + // been set, released sessions will skip flushing the size storer. _sizeStorer.reset(); _waitUntilDurableSession.reset(); @@ -1030,8 +1046,8 @@ Status WiredTigerKVEngine::_salvageIfNeeded(const char* uri) { int rc = session.verify(uri, nullptr); // WT may return EBUSY if the database contains dirty data. If we checkpoint and retry the - // operation it will attempt to clean up the dirty elements during checkpointing, thus allowing - // the operation to succeed if it was the only reason to fail. + // operation it will attempt to clean up the dirty elements during checkpointing, thus + // allowing the operation to succeed if it was the only reason to fail. if (rc == EBUSY) { _checkpoint(session); rc = session.verify(uri, nullptr); @@ -1094,8 +1110,8 @@ Status WiredTigerKVEngine::_rebuildIdent(WiredTigerSession& session, const char* LOGV2_WARNING(22353, "Rebuilding ident", "ident"_attr = identName); - // This is safe to call after moving the file because it only reads from the metadata, and not - // the data file itself. + // This is safe to call after moving the file because it only reads from the metadata, and + // not the data file itself. auto swMetadata = WiredTigerUtil::getMetadataCreate(session, uri); if (!swMetadata.isOK()) { auto status = swMetadata.getStatus(); @@ -1137,11 +1153,11 @@ void WiredTigerKVEngine::flushAllFiles(OperationContext* opCtx, bool callerHolds const Timestamp stableTimestamp = getStableTimestamp(); const Timestamp initialDataTimestamp = getInitialDataTimestamp(); - uassert( - 5841000, - "Cannot take checkpoints when the stable timestamp is less than the initial data timestamp", - initialDataTimestamp == Timestamp::kAllowUnstableCheckpointsSentinel || - stableTimestamp >= initialDataTimestamp); + uassert(5841000, + "Cannot take checkpoints when the stable timestamp is less than the initial data " + "timestamp", + initialDataTimestamp == Timestamp::kAllowUnstableCheckpointsSentinel || + stableTimestamp >= initialDataTimestamp); // Immediately flush the size storer information to disk. When the node is fsync locked for // operations such as backup, it's imperative that we copy the most up-to-date data files. @@ -1151,7 +1167,8 @@ void WiredTigerKVEngine::flushAllFiles(OperationContext* opCtx, bool callerHolds Fsync fsyncType = !isEphemeral() ? Fsync::kCheckpointStableTimestamp : Fsync::kCheckpointAll; // We will skip updating the journal listener if the caller holds read locks. - // The JournalListener may do writes, and taking write locks would conflict with the read locks. + // The JournalListener may do writes, and taking write locks would conflict with the read + // locks. UseJournalListener useListener = callerHoldsReadLock ? UseJournalListener::kSkip : UseJournalListener::kUpdate; @@ -1192,10 +1209,9 @@ void WiredTigerKVEngine::endBackup() { Status WiredTigerKVEngine::disableIncrementalBackup() { // Opening an incremental backup cursor with the "force_stop=true" configuration option then - // closing the cursor will set a flag in WiredTiger that causes it to release all incremental - // information and resources. - // Opening a subsequent incremental backup cursor will reset the flag in WiredTiger and - // reinstate incremental backup history. + // closing the cursor will set a flag in WiredTiger that causes it to release all + // incremental information and resources. Opening a subsequent incremental backup cursor + // will reset the flag in WiredTiger and reinstate incremental backup history. uassert(31401, "Cannot open backup cursor with in-memory storage engine.", !isEphemeral()); auto sessionRaii = std::make_unique(_connection.get()); @@ -1276,9 +1292,9 @@ public: const auto wiredTigerLogFilePrefix = "WiredTigerLog"; if (std::string(filename).find(wiredTigerLogFilePrefix) == 0) { - // If extendBackupCursor() is called prior to the StreamingCursor running into log - // files, we must ensure that subsequent calls to getNextBatch() do not return - // duplicate files. + // If extendBackupCursor() is called prior to the StreamingCursor running into + // log files, we must ensure that subsequent calls to getNextBatch() do not + // return duplicate files. if ((_wtBackup->logFilePathsSeenByExtendBackupCursor).find(filePath.string()) != (_wtBackup->logFilePathsSeenByExtendBackupCursor).end()) { break; @@ -1296,9 +1312,9 @@ public: if (options.incrementalBackup && options.srcBackupName) { // For a subsequent incremental backup, each BackupBlock corresponds to changes - // made to data files since the initial incremental backup. Each BackupBlock has a - // maximum size of options.blockSizeMB. Incremental backups open a duplicate cursor, - // which is stored in _wtBackup->dupCursor. + // made to data files since the initial incremental backup. Each BackupBlock has + // a maximum size of options.blockSizeMB. Incremental backups open a duplicate + // cursor, which is stored in _wtBackup->dupCursor. // // 'kvBackupBlocks' is an out parameter. Status status = _getNextIncrementalBatchForFile( @@ -1308,9 +1324,9 @@ public: return status; } } else { - // For a full backup or the initial incremental backup, each BackupBlock corresponds - // to an entire file. Full backups cannot open an incremental cursor, even if they - // are the initial incremental backup. + // For a full backup or the initial incremental backup, each BackupBlock + // corresponds to an entire file. Full backups cannot open an incremental + // cursor, even if they are the initial incremental backup. const std::uint64_t length = options.incrementalBackup ? fileSize : 0; std::string ident = extractIdentFromPath( boost::filesystem::path(storageGlobalParams.dbpath), filePath); @@ -1393,8 +1409,9 @@ private: KVBackupBlock(ident, filePath.string(), offset, size, fileSize)); } - // If the file is unchanged, push a BackupBlock with offset=0 and length=0. This allows us - // to distinguish between an unchanged file and a deleted file in an incremental backup. + // If the file is unchanged, push a BackupBlock with offset=0 and length=0. This allows + // us to distinguish between an unchanged file and a deleted file in an incremental + // backup. if (fileUnchangedFlag) { std::string ident = extractIdentFromPath(boost::filesystem::path(storageGlobalParams.dbpath), filePath); @@ -1443,20 +1460,21 @@ WiredTigerKVEngine::beginNonBlockingBackup(const StorageEngine::BackupOptions& o stdx::lock_guard backupCursorLk(_wtBackup.wtBackupCursorMutex); - // Create ongoingBackup.lock file to signal recovery that it should delete WiredTiger.backup if - // we have an unclean shutdown with the cursor still open. + // Create ongoingBackup.lock file to signal recovery that it should delete WiredTiger.backup + // if we have an unclean shutdown with the cursor still open. { boost::filesystem::ofstream ongoingBackup(getOngoingBackupPath()); } - // Oplog truncation thread won't remove oplog since the checkpoint pinned by the backup cursor. + // Oplog truncation thread won't remove oplog since the checkpoint pinned by the backup + // cursor. stdx::lock_guard lock(_oplogPinnedByBackupMutex); _oplogPinnedByBackup = Timestamp(_oplogNeededForCrashRecovery.load()); ScopeGuard pinOplogGuard([&] { _oplogPinnedByBackup = boost::none; }); // Persist the sizeStorer information to disk before opening the backup cursor. We aren't - // guaranteed to have the most up-to-date size information after the backup as writes can still - // occur during a nonblocking backup. + // guaranteed to have the most up-to-date size information after the backup as writes can + // still occur during a nonblocking backup. syncSizeInfo(true); // This cursor will be freed by the backupSession being closed as the session is uncached @@ -1515,8 +1533,8 @@ StatusWith> WiredTigerKVEngine::extendBackupCursor() { // Persist the sizeStorer information to disk before extending the backup cursor. syncSizeInfo(true); - // The "target=(\"log:\")" configuration string for the cursor will ensure that we only see the - // log files when iterating on the cursor. + // The "target=(\"log:\")" configuration string for the cursor will ensure that we only see + // the log files when iterating on the cursor. WT_CURSOR* cursor = nullptr; int wtRet = _backupSession->open_cursor(nullptr, _wtBackup.cursor, "target=(\"log:\")", &cursor); @@ -1544,11 +1562,12 @@ StatusWith> WiredTigerKVEngine::extendBackupCursor() { return wtRCToStatus(wtRet, *_backupSession); } - // Once all the backup cursors have been opened on a sharded cluster, we need to ensure that the - // data being copied from each shard is at the same point-in-time across the entire cluster to - // have a consistent view of the data. For shards that opened their backup cursor before the - // established point-in-time for backup, they will need to create a full copy of the additional - // journal files returned by this method to ensure a consistent backup of the data is taken. + // Once all the backup cursors have been opened on a sharded cluster, we need to ensure that + // the data being copied from each shard is at the same point-in-time across the entire + // cluster to have a consistent view of the data. For shards that opened their backup cursor + // before the established point-in-time for backup, they will need to create a full copy of + // the additional journal files returned by this method to ensure a consistent backup of the + // data is taken. return getUniqueFiles(filePaths, _wtBackup.logFilePathsSeenByGetNextBatch); } @@ -1563,7 +1582,8 @@ void WiredTigerKVEngine::syncSizeInfo(bool sync) const { if (!sync) { // ignore, we'll try again later. LOGV2(7437300, - "Encountered storage unavailable error while flushing collection size info, " + "Encountered storage unavailable error while flushing collection size " + "info, " "will retry again later", "error"_attr = ex.what()); return; @@ -1591,7 +1611,8 @@ void WiredTigerKVEngine::setSortedDataInterfaceExtraOptions(const std::string& o _indexOptions = options; } -Status WiredTigerKVEngine::_createRecordStore(const NamespaceString& nss, +Status WiredTigerKVEngine::_createRecordStore(const rss::PersistenceProvider& provider, + const NamespaceString& nss, StringData ident, KeyFormat keyFormat, const BSONObj& storageEngineCollectionOptions, @@ -1602,8 +1623,8 @@ Status WiredTigerKVEngine::_createRecordStore(const NamespaceString& nss, wtTableConfig.keyFormat = keyFormat; wtTableConfig.blockCompressor = wiredTigerGlobalOptions.collectionBlockCompressor; wtTableConfig.extraCreateOptions = _rsOptions; - wtTableConfig.logEnabled = - WiredTigerUtil::useTableLogging(nss, _isReplSet, _shouldRecoverFromOplogAsStandalone); + wtTableConfig.logEnabled = WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone); if (customBlockCompressor) { wtTableConfig.blockCompressor = *customBlockCompressor; @@ -1615,11 +1636,11 @@ Status WiredTigerKVEngine::_createRecordStore(const NamespaceString& nss, return customConfigString.getStatus(); } - // It's imperative that any custom options, beyond the default '_rsOptions' are appended at the - // end of the 'extraCreateOptions' for table configuration. WiredTiger will take the last - // value specified of a field in the config string. For example: if '_rsOptions' and the - // 'customConfigString' both specify field 'blockCompressor=', the latter will be - // used by WiredTiger. + // It's imperative that any custom options, beyond the default '_rsOptions' are appended at + // the end of the 'extraCreateOptions' for table configuration. WiredTiger will take the + // last value specified of a field in the config string. For example: if '_rsOptions' and + // the 'customConfigString' both specify field 'blockCompressor=', the latter + // will be used by WiredTiger. wtTableConfig.extraCreateOptions = str::stream() << _rsOptions << "," << customConfigString.getValue(); @@ -1656,7 +1677,8 @@ Status WiredTigerKVEngine::importRecordStore(StringData ident, return wtRCToStatus(session.create(uri.c_str(), config.c_str()), session); } -Status WiredTigerKVEngine::recoverOrphanedIdent(const NamespaceString& nss, +Status WiredTigerKVEngine::recoverOrphanedIdent(const rss::PersistenceProvider& provider, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) { #ifdef _WIN32 @@ -1665,8 +1687,8 @@ Status WiredTigerKVEngine::recoverOrphanedIdent(const NamespaceString& nss, invariant(_inRepairMode); // Moves the data file to a temporary name so that a new RecordStore can be created with the - // same ident name. We will delete the new empty collection and rename the data file back so it - // can be salvaged. + // same ident name. We will delete the new empty collection and rename the data file back so + // it can be salvaged. boost::optional identFilePath = getDataFilePathForIdent(ident); if (!identFilePath) { @@ -1690,7 +1712,7 @@ Status WiredTigerKVEngine::recoverOrphanedIdent(const NamespaceString& nss, LOGV2(22333, "Creating new RecordStore", logAttrs(nss)); - status = createRecordStore(nss, ident, options); + status = createRecordStore(provider, nss, ident, options); if (!status.isOK()) { return status; } @@ -1739,7 +1761,10 @@ std::unique_ptr WiredTigerKVEngine::getRecordStore(OperationContext const RecordStore::Options& options, boost::optional uuid) { std::unique_ptr ret; + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); if (options.isOplog) { + const bool isLogged = WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone); ret = std::make_unique( this, WiredTigerRecoveryUnit::get(*shard_role_details::getRecoveryUnit(opCtx)), @@ -1750,6 +1775,7 @@ std::unique_ptr WiredTigerKVEngine::getRecordStore(OperationContext .oplogMaxSize = options.oplogMaxSize, .sizeStorer = _sizeStorer.get(), .tracksSizeAdjustments = true, + .isLogged = isLogged, .forceUpdateWithFullDocument = options.forceUpdateWithFullDocument}); getOplogManager()->stop(); @@ -1758,7 +1784,7 @@ std::unique_ptr WiredTigerKVEngine::getRecordStore(OperationContext bool isLogged = [&] { if (!nss.isEmpty()) { return WiredTigerUtil::useTableLogging( - nss, _isReplSet, _shouldRecoverFromOplogAsStandalone); + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone); } fassert(8423353, ident.starts_with("internal-")); return !_isReplSet && !_shouldRecoverFromOplogAsStandalone; @@ -1768,8 +1794,8 @@ std::unique_ptr WiredTigerKVEngine::getRecordStore(OperationContext .ident = std::string{ident}, .engineName = _canonicalName, .keyFormat = options.keyFormat, - // Record stores for clustered collections need to guarantee uniqueness by preventing - // overwrites. + // Record stores for clustered collections need to guarantee uniqueness by + // preventing overwrites. .overwrite = options.allowOverwrite, .isLogged = isLogged, .forceUpdateWithFullDocument = options.forceUpdateWithFullDocument, @@ -1797,6 +1823,7 @@ std::unique_ptr WiredTigerKVEngine::getRecordStore(OperationContext } Status WiredTigerKVEngine::createSortedDataInterface( + const rss::PersistenceProvider& provider, RecoveryUnit& ru, const NamespaceString& nss, const UUID& uuid, @@ -1818,7 +1845,8 @@ Status WiredTigerKVEngine::createSortedDataInterface( collIndexOptions, NamespaceStringUtil::serializeForCatalog(nss), indexConfig, - WiredTigerUtil::useTableLogging(nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); + WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); if (!result.isOK()) { return result.getStatus(); } @@ -1873,6 +1901,7 @@ std::unique_ptr WiredTigerKVEngine::getSortedDataInterface( StringData ident, const IndexConfig& config, KeyFormat keyFormat) { + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); if (config.isIdIndex) { return std::make_unique( @@ -1882,7 +1911,8 @@ std::unique_ptr WiredTigerKVEngine::getSortedDataInterface( uuid, ident, config, - WiredTigerUtil::useTableLogging(nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); + WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); } if (config.unique) { return std::make_unique( @@ -1893,7 +1923,8 @@ std::unique_ptr WiredTigerKVEngine::getSortedDataInterface( ident, keyFormat, config, - WiredTigerUtil::useTableLogging(nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); + WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); } return std::make_unique( @@ -1904,7 +1935,8 @@ std::unique_ptr WiredTigerKVEngine::getSortedDataInterface( ident, keyFormat, config, - WiredTigerUtil::useTableLogging(nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); + WiredTigerUtil::useTableLogging( + provider, nss, _isReplSet, _shouldRecoverFromOplogAsStandalone)); } std::unique_ptr WiredTigerKVEngine::getTemporaryRecordStore(RecoveryUnit& ru, @@ -1963,9 +1995,9 @@ void WiredTigerKVEngine::alterIdentMetadata(RecoveryUnit& ru, bool isForceUpdateMetadata) { std::string uri = WiredTigerUtil::buildTableUri(ident); if (!isForceUpdateMetadata) { - // Explicitly disallows metadata change, specifically index data format change, on indexes - // of version 11 and 12. This is extra defensive and can be reconsidered if we expand the - // use of 'alterIdentMetadata()' to also modify non-data-format properties. + // Explicitly disallows metadata change, specifically index data format change, on + // indexes of version 11 and 12. This is extra defensive and can be reconsidered if we + // expand the use of 'alterIdentMetadata()' to also modify non-data-format properties. invariant(!WiredTigerUtil::checkApplicationMetadataFormatVersion( *WiredTigerRecoveryUnit::get(ru).getSessionNoTxn(), uri, @@ -1974,8 +2006,8 @@ void WiredTigerKVEngine::alterIdentMetadata(RecoveryUnit& ru, .isOK()); } - // Make the alter call to update metadata without taking exclusive lock to avoid conflicts with - // concurrent operations. + // Make the alter call to update metadata without taking exclusive lock to avoid conflicts + // with concurrent operations. std::string alterString = WiredTigerIndex::generateAppMetadataString(config) + "exclusive_refreshed=false,"; auto status = alterMetadata(uri, alterString); @@ -1991,8 +2023,8 @@ Status WiredTigerKVEngine::alterMetadata(StringData uri, StringData config) { auto ret = session.alter(uriNullTerminated.c_str(), configNullTerminated.c_str()); // WT may return EBUSY if the database contains dirty data. If we checkpoint and retry the - // operation it will attempt to clean up the dirty elements during checkpointing, thus allowing - // the operation to succeed if it was the only reason to fail. + // operation it will attempt to clean up the dirty elements during checkpointing, thus + // allowing the operation to succeed if it was the only reason to fail. if (ret == EBUSY) { _checkpoint(session); ret = session.alter(uriNullTerminated.c_str(), configNullTerminated.c_str()); @@ -2048,11 +2080,11 @@ void WiredTigerKVEngine::dropIdentForImport(Interruptible& interruptible, // Don't wait for the global checkpoint lock to be obtained in WiredTiger as it can take a // substantial amount of time to be obtained if there is a concurrent checkpoint running. We - // will wait until we obtain exclusive access to the underlying table file though. As it isn't - // user visible at this stage in the import it should be readily available unless a backup - // cursor is open. In short, using "checkpoint_wait=false" and "lock_wait=true" means that we - // can potentially be waiting for a short period of time for WT_SESSION::drop() to run, but - // would rather get EBUSY than wait a long time for a checkpoint to complete. + // will wait until we obtain exclusive access to the underlying table file though. As it + // isn't user visible at this stage in the import it should be readily available unless a + // backup cursor is open. In short, using "checkpoint_wait=false" and "lock_wait=true" means + // that we can potentially be waiting for a short period of time for WT_SESSION::drop() to + // run, but would rather get EBUSY than wait a long time for a checkpoint to complete. const std::string config = "checkpoint_wait=false,lock_wait=true,remove_files=false"; Status dropStatus = Status::OK(); size_t attempt = 0; @@ -2085,6 +2117,7 @@ void WiredTigerKVEngine::_checkpoint(WiredTigerSession& session, bool useTimesta if (useTimestamp) { wtRet = session.checkpoint("use_timestamp=true"); } else { + invariant(_wtConfig.providerSupportsUnstableCheckpoints); wtRet = session.checkpoint("use_timestamp=false"); } if (EBUSY == wtRet) { @@ -2112,9 +2145,9 @@ void WiredTigerKVEngine::_checkpoint(WiredTigerSession& session) try { return; } - // Limits the actions of concurrent checkpoint callers as we update some internal data during a - // checkpoint. WT has a mutex of its own to only have one checkpoint active at all times so this - // is only to protect our internal updates. + // Limits the actions of concurrent checkpoint callers as we update some internal data + // during a checkpoint. WT has a mutex of its own to only have one checkpoint active at all + // times so this is only to protect our internal updates. // TODO: SERVER-64507: Investigate whether we can smartly rely on one checkpointer if two or // more threads checkpoint at the same time. stdx::lock_guard lk(_checkpointMutex); @@ -2122,32 +2155,35 @@ void WiredTigerKVEngine::_checkpoint(WiredTigerSession& session) try { const Timestamp stableTimestamp = getStableTimestamp(); const Timestamp initialDataTimestamp = getInitialDataTimestamp(); - // The amount of oplog to keep is primarily dictated by a user setting. However, in unexpected - // cases, durable, recover to a timestamp storage engines may need to play forward from an oplog - // entry that would otherwise be truncated by the user setting. Furthermore, the entries in - // prepared or large transactions can refer to previous entries in the same transaction. + // The amount of oplog to keep is primarily dictated by a user setting. However, in + // unexpected cases, durable, recover to a timestamp storage engines may need to play + // forward from an oplog entry that would otherwise be truncated by the user setting. + // Furthermore, the entries in prepared or large transactions can refer to previous entries + // in the same transaction. // // Live (replication) rollback will replay the oplog from exactly the stable timestamp. With - // prepared or large transactions, it may require some additional entries prior to the stable - // timestamp. These requirements are summarized in getOplogNeededForRollback. Truncating the - // oplog at this point is sufficient for in-memory configurations, but could cause an - // unrecoverable scenario if the node crashed and has to play from the last stable checkpoint. + // prepared or large transactions, it may require some additional entries prior to the + // stable timestamp. These requirements are summarized in getOplogNeededForRollback. + // Truncating the oplog at this point is sufficient for in-memory configurations, but could + // cause an unrecoverable scenario if the node crashed and has to play from the last stable + // checkpoint. // // By recording the oplog needed for rollback "now", then taking a stable checkpoint, we can - // safely assume that the oplog needed for crash recovery has caught up to the recorded value. - // After the checkpoint, this value will be published such that actors which truncate the oplog - // can read an updated value. + // safely assume that the oplog needed for crash recovery has caught up to the recorded + // value. After the checkpoint, this value will be published such that actors which truncate + // the oplog can read an updated value. // Three cases: // - // First, initialDataTimestamp is Timestamp(0, 1) -> Take full checkpoint. This is when there is - // no consistent view of the data (e.g: during initial sync). + // First, initialDataTimestamp is Timestamp(0, 1) -> Take full checkpoint. This is when + // there is no consistent view of the data (e.g: during initial sync). // - // Second, stableTimestamp < initialDataTimestamp: Skip checkpoints. The data on disk is prone - // to being rolled back. Hold off on checkpoints. Hope that the stable timestamp surpasses the - // data on disk, allowing storage to persist newer copies to disk. + // Second, stableTimestamp < initialDataTimestamp: Skip checkpoints. The data on disk is + // prone to being rolled back. Hold off on checkpoints. Hope that the stable timestamp + // surpasses the data on disk, allowing storage to persist newer copies to disk. // - // Third, stableTimestamp >= initialDataTimestamp: Take stable checkpoint. Steady state case. + // Third, stableTimestamp >= initialDataTimestamp: Take stable checkpoint. Steady state + // case. if (initialDataTimestamp.asULL() <= 1) { _checkpoint(session, /*useTimestamp=*/false); @@ -2162,6 +2198,12 @@ void WiredTigerKVEngine::_checkpoint(WiredTigerSession& session) try { "Stable timestamp is behind the initial data timestamp, skipping a checkpoint.", "stableTimestamp"_attr = stableTimestamp.toString(), "initialDataTimestamp"_attr = initialDataTimestamp.toString()); + } else if (!_wtConfig.safeToTakeDuplicateCheckpoints && + stableTimestamp == getCheckpointTimestamp()) { + LOGV2_FOR_RECOVERY(10985349, + 2, + "Stable timestamp hasn't advanced, skipping a checkpoint.", + "stableTimestamp"_attr = stableTimestamp); } else { auto oplogNeededForRollback = getOplogNeededForRollback(); @@ -2191,7 +2233,16 @@ void WiredTigerKVEngine::checkpoint() { void WiredTigerKVEngine::forceCheckpoint(bool useStableTimestamp) { WiredTigerManagedSession session = _connection->getUninterruptibleSession(); - return _checkpoint(*session, useStableTimestamp); + if (_wtConfig.safeToTakeDuplicateCheckpoints) + return _checkpoint(*session, useStableTimestamp); + else { + invariant(useStableTimestamp); + // The checkpoint call above ensures that concurrent checkpoint requests are serialized + // at the WT layer. However, sometimes we need to serialize checkpoint requests at the + // MDB layer, which this checkpoint call provides. This prevents issuing a new + // checkpoint for a stable timestamp that has already been checkpointed. + return _checkpoint(*session); + } } bool WiredTigerKVEngine::hasIdent(RecoveryUnit& ru, StringData ident) const { @@ -2280,13 +2331,29 @@ bool WiredTigerKVEngine::_removeIdentDirectoryIfEmpty(StringData ident, size_t s void WiredTigerKVEngine::setJournalListener(JournalListener* jl) { stdx::unique_lock lk(_journalListenerMutex); - // A JournalListener can only be set once. Otherwise, accessing a copy of the _journalListener - // pointer without a mutex would be unsafe. + // A JournalListener can only be set once. Otherwise, accessing a copy of the + // _journalListener pointer without a mutex would be unsafe. invariant(!_journalListener); _journalListener = jl; } +void WiredTigerKVEngine::setLastMaterializedLsn(uint64_t lsn) { + auto lastMaterializedLsnConfig = fmt::format("disaggregated=(last_materialized_lsn={})", lsn); + invariantWTOK(_conn->reconfigure(_conn, lastMaterializedLsnConfig.c_str()), nullptr); +} + +void WiredTigerKVEngine::setRecoveryCheckpointMetadata(StringData checkpointMetadata) { + auto getCkptMetaConfigString = + fmt::format("disaggregated=(checkpoint_meta=\"{}\")", checkpointMetadata); + invariantWTOK(_conn->reconfigure(_conn, getCkptMetaConfigString.c_str()), nullptr); +} + +void WiredTigerKVEngine::promoteToLeader() { + static constexpr char leaderConfig[] = "disaggregated=(role=\"leader\")"; + invariantWTOK(_conn->reconfigure(_conn, leaderConfig), nullptr); +} + void WiredTigerKVEngine::setStableTimestamp(Timestamp stableTimestamp, bool force) { if (stableTimestamp.isNull()) { return; @@ -2298,20 +2365,20 @@ void WiredTigerKVEngine::setStableTimestamp(Timestamp stableTimestamp, bool forc return; } - // Communicate to WiredTiger what the "stable timestamp" is. Timestamp-aware checkpoints will - // only persist to disk transactions committed with a timestamp earlier than the "stable - // timestamp". + // Communicate to WiredTiger what the "stable timestamp" is. Timestamp-aware checkpoints + // will only persist to disk transactions committed with a timestamp earlier than the + // "stable timestamp". // // After passing the "stable timestamp" to WiredTiger, communicate it to the - // `CheckpointThread`. It's not obvious a stale stable timestamp in the `CheckpointThread` is - // safe. Consider the following arguments: + // `CheckpointThread`. It's not obvious a stale stable timestamp in the `CheckpointThread` + // is safe. Consider the following arguments: // - // Setting the "stable timestamp" is only meaningful when the "initial data timestamp" is real - // (i.e: not `kAllowUnstableCheckpointsSentinel`). In this normal case, the `stableTimestamp` - // input must be greater than the current value. The only effect this can have in the - // `CheckpointThread` is to transition it from a state of not taking any checkpoints, to - // taking "stable checkpoints". In the transitioning case, it's imperative for the "stable - // timestamp" to have first been communicated to WiredTiger. + // Setting the "stable timestamp" is only meaningful when the "initial data timestamp" is + // real (i.e: not `kAllowUnstableCheckpointsSentinel`). In this normal case, the + // `stableTimestamp` input must be greater than the current value. The only effect this can + // have in the `CheckpointThread` is to transition it from a state of not taking any + // checkpoints, to taking "stable checkpoints". In the transitioning case, it's imperative + // for the "stable timestamp" to have first been communicated to WiredTiger. std::string stableTSConfigString; auto ts = stableTimestamp.asULL(); if (force) { @@ -2322,8 +2389,8 @@ void WiredTigerKVEngine::setStableTimestamp(Timestamp stableTimestamp, bool forc } invariantWTOK(_conn->set_timestamp(_conn, stableTSConfigString.c_str()), nullptr); - // After publishing a stable timestamp to WT, we can record the updated stable timestamp value - // for the necessary oplog to keep. + // After publishing a stable timestamp to WT, we can record the updated stable timestamp + // value for the necessary oplog to keep. _stableTimestamp.store(stableTimestamp.asULL()); // If 'force' is set, then we have already set the oldest timestamp equal to the stable @@ -2363,8 +2430,8 @@ void WiredTigerKVEngine::setOldestTimestamp(Timestamp newOldestTimestamp, bool f return; } - // This mutex is not intended to synchronize updates to the oldest timestamp, but to ensure that - // there are no races with pinning the oldest timestamp. + // This mutex is not intended to synchronize updates to the oldest timestamp, but to ensure + // that there are no races with pinning the oldest timestamp. stdx::lock_guard lock(_oldestTimestampPinRequestsMutex); const Timestamp currOldestTimestamp = Timestamp(_oldestTimestamp.load()); for (const auto& it : _oldestTimestampPinRequests) { @@ -2373,13 +2440,13 @@ void WiredTigerKVEngine::setOldestTimestamp(Timestamp newOldestTimestamp, bool f } if (force) { - // Components that register a pinned timestamp must synchronize with events that invalidate - // their snapshots, unpin themselves and either fail themselves, or reacquire a new snapshot - // after the rollback event. + // Components that register a pinned timestamp must synchronize with events that + // invalidate their snapshots, unpin themselves and either fail themselves, or reacquire + // a new snapshot after the rollback event. // // Forcing the oldest timestamp forward -- potentially past a pin request raises the - // question of whether the pin should be honored. For now we will invariant there is no pin, - // but the invariant can be relaxed if there's a use-case to support. + // question of whether the pin should be honored. For now we will invariant there is no + // pin, but the invariant can be relaxed if there's a use-case to support. invariant(_oldestTimestampPinRequests.empty()); } @@ -2420,8 +2487,8 @@ Timestamp WiredTigerKVEngine::_calculateHistoryLagFromStableTimestamp(Timestamp if (stableTimestamp.getSecs() < static_cast(minSnapshotHistoryWindowInSeconds.load())) { // The history window is larger than the timestamp history thus far. We must wait for - // the history to reach the window size before moving oldest_timestamp forward. This should - // only happen in unit tests. + // the history to reach the window size before moving oldest_timestamp forward. This + // should only happen in unit tests. return Timestamp(); } @@ -2508,10 +2575,10 @@ StatusWith WiredTigerKVEngine::recoverToStableTimestamp(Interruptible // Shut down the cache before rollback and restart afterwards. _connection->shuttingDown(); - // The rollback_to_stable operation requires all open cursors to be closed or reset before the - // call, otherwise EBUSY will be returned. Occasionally, there could be an operation that hasn't - // been killed yet, such as the CappedInsertNotifier for a yielded oplog getMore. We will retry - // rollback_to_stable until the system quiesces. + // The rollback_to_stable operation requires all open cursors to be closed or reset before + // the call, otherwise EBUSY will be returned. Occasionally, there could be an operation + // that hasn't been killed yet, such as the CappedInsertNotifier for a yielded oplog + // getMore. We will retry rollback_to_stable until the system quiesces. size_t attempts = 0; do { ret = _conn->rollback_to_stable(_conn, nullptr); @@ -2550,16 +2617,16 @@ StatusWith WiredTigerKVEngine::recoverToStableTimestamp(Interruptible } Timestamp WiredTigerKVEngine::getAllDurableTimestamp() const { - // Fetch the latest all_durable value from the storage engine. This value will be a timestamp - // that has no holes (uncommitted transactions with lower timestamps) behind it. + // Fetch the latest all_durable value from the storage engine. This value will be a + // timestamp that has no holes (uncommitted transactions with lower timestamps) behind it. char buf[(2 * 8 /* bytes in hex */) + 1 /* null terminator */]; invariantWTOK(_conn->query_timestamp(_conn, buf, "get=all_durable"), nullptr); uint64_t ts; fassert(38002, NumberParser{}.base(16)(buf, &ts)); - // If all_durable is 0, treat this as lowest possible timestamp; we need to see all pre-existing - // data but no new (timestamped) data. + // If all_durable is 0, treat this as lowest possible timestamp; we need to see all + // pre-existing data but no new (timestamped) data. return Timestamp{ts == 0 ? StorageEngine::kMinimumTimestamp : ts}; } @@ -2599,8 +2666,8 @@ boost::optional WiredTigerKVEngine::getLastStableRecoveryTimestamp() } StatusWith WiredTigerKVEngine::getOplogNeededForRollback() const { - // Get the current stable timestamp and use it throughout this function, ignoring updates from - // another thread. + // Get the current stable timestamp and use it throughout this function, ignoring updates + // from another thread. auto stableTimestamp = _stableTimestamp.load(); // Only one thread can set or execute this callback. @@ -2641,12 +2708,13 @@ Timestamp WiredTigerKVEngine::getPinnedOplog() const { { stdx::lock_guard lock(_oplogPinnedByBackupMutex); if (!storageGlobalParams.allowOplogTruncation) { - // If oplog truncation is not allowed, then return the min timestamp so that no history - // is ever allowed to be deleted. + // If oplog truncation is not allowed, then return the min timestamp so that no + // history is ever allowed to be deleted. return Timestamp::min(); } if (_oplogPinnedByBackup) { - // All the oplog since `_oplogPinnedByBackup` should remain intact during the backup. + // All the oplog since `_oplogPinnedByBackup` should remain intact during the + // backup. return std::min(_oplogPinnedByBackup.value(), pinned); } } @@ -2692,9 +2760,9 @@ StatusWith WiredTigerKVEngine::pinOldestTimestamp( } if (ru.inUnitOfWork()) { - // If we've moved the pin and are in a `WriteUnitOfWork`, assume the caller has a write that - // should be atomic with this pin request. If the `WriteUnitOfWork` is rolled back, either - // unpin the oldest timestamp or repin the previous value. + // If we've moved the pin and are in a `WriteUnitOfWork`, assume the caller has a write + // that should be atomic with this pin request. If the `WriteUnitOfWork` is rolled back, + // either unpin the oldest timestamp or repin the previous value. ru.onRollback([this, svcName = requestingServiceName, previousTimestamp]( OperationContext*) { if (previousTimestamp.isNull()) { @@ -2776,12 +2844,13 @@ Status WiredTigerKVEngine::oplogDiskLocRegister(RecoveryUnit& ru, return ru.setTimestamp(opTime); } - // This handles non-primary (secondary) state behavior; we simply set the oplog visiblity read - // timestamp here, as there cannot be visible holes prior to the opTime passed in. + // This handles non-primary (secondary) state behavior; we simply set the oplog visiblity + // read timestamp here, as there cannot be visible holes prior to the opTime passed in. getOplogManager()->setOplogReadTimestamp(opTime); - // Inserts and updates usually notify waiters on commit, but the oplog collection has special - // visibility rules and waiters must be notified whenever the oplog read timestamp is forwarded. + // Inserts and updates usually notify waiters on commit, but the oplog collection has + // special visibility rules and waiters must be notified whenever the oplog read timestamp + // is forwarded. oplogRecordStore->capped()->notifyWaitersIfNeeded(); return Status::OK(); } @@ -2818,8 +2887,8 @@ bool WiredTigerKVEngine::waitUntilUnjournaledWritesDurable(OperationContext* opC // writes to unjournaled tables. // // If 'stableCheckpoint' is set, then we will only checkpoint data up to and including the - // stable_timestamp set on WT at the time of the checkpoint. Otherwise, we will checkpoint all - // of the data. + // stable_timestamp set on WT at the time of the checkpoint. Otherwise, we will checkpoint + // all of the data. Fsync fsyncType = stableCheckpoint ? Fsync::kCheckpointStableTimestamp : Fsync::kCheckpointAll; waitUntilDurable(opCtx, fsyncType, UseJournalListener::kUpdate); @@ -2839,6 +2908,16 @@ void WiredTigerKVEngine::waitUntilDurable(OperationContext* opCtx, return; } + // Storage engine does not support WiredTiger logging, we skip flushing the journal and only + // perform checkpoints. + if (!_supportsTableLogging && syncType == Fsync::kJournal) { + auto [journalListener, token] = _getJournalListenerWithToken(opCtx, useListener); + if (token) { + journalListener->onDurable(*token); + } + return; + } + WiredTigerConnection::BlockShutdown blockShutdown(_connection.get()); uassert(ErrorCodes::ShutdownInProgress, @@ -2846,10 +2925,10 @@ void WiredTigerKVEngine::waitUntilDurable(OperationContext* opCtx, !_connection->isShuttingDown()); // Stable checkpoints are only meaningful in a replica set. Replication sets the "stable - // timestamp". If the stable timestamp is unset, WiredTiger takes a full checkpoint, which is - // incidentally what we want. A "true" stable checkpoint (a stable timestamp was set on the - // WT_CONNECTION, i.e: replication is on) requires `forceCheckpoint` to be true and journaling - // to be enabled. + // timestamp". If the stable timestamp is unset, WiredTiger takes a full checkpoint, which + // is incidentally what we want. A "true" stable checkpoint (a stable timestamp was set on + // the WT_CONNECTION, i.e: replication is on) requires `forceCheckpoint` to be true and + // journaling to be enabled. if (syncType == Fsync::kCheckpointStableTimestamp && _isReplSet) { invariant(!isEphemeral()); } @@ -2880,8 +2959,9 @@ void WiredTigerKVEngine::waitUntilDurable(OperationContext* opCtx, if (current != start) { // Someone else synced already since we read lastSyncTime, so we're done! - // Unconditionally unlock mutex here to run operations that do not require synchronization. - // The JournalListener is the only operation that meets this criteria currently. + // Unconditionally unlock mutex here to run operations that do not require + // synchronization. The JournalListener is the only operation that meets this criteria + // currently. lk.unlock(); if (token) { journalListener->onDurable(*token); @@ -3119,11 +3199,10 @@ Status WiredTigerKVEngine::_drop(WiredTigerSession& session, const char* uri, co session.get_last_error(&err, &sub_level_err, &err_msg); - // We should never run into these situations when we are already in the process of dropping the - // table. - // TODO: SERVER-100890 Re-enable this invariant once we have fixed the bug that causes this to - // fail. - // invariant(sub_level_err != WT_UNCOMMITTED_DATA); + // We should never run into these situations when we are already in the process of dropping + // the table. + // TODO: SERVER-100890 Re-enable this invariant once we have fixed the bug that causes this + // to fail. invariant(sub_level_err != WT_UNCOMMITTED_DATA); invariant(sub_level_err != WT_CONFLICT_TABLE_LOCK); invariant(sub_level_err != WT_CONFLICT_SCHEMA_LOCK); @@ -3139,7 +3218,8 @@ Status WiredTigerKVEngine::_drop(WiredTigerSession& session, const char* uri, co return wtRCToStatus(ret, session); } -WiredTigerKVEngineBase::WiredTigerConfig getWiredTigerConfigFromStartupOptions() { +WiredTigerKVEngineBase::WiredTigerConfig getWiredTigerConfigFromStartupOptions( + const rss::PersistenceProvider& provider) { WiredTigerKVEngineBase::WiredTigerConfig wtConfig; wtConfig.sessionMax = wiredTigerGlobalOptions.sessionMax; @@ -3151,6 +3231,9 @@ WiredTigerKVEngineBase::WiredTigerConfig getWiredTigerConfigFromStartupOptions() wtConfig.liveRestoreThreadsMax = wiredTigerGlobalOptions.liveRestoreThreads; wtConfig.liveRestoreReadSizeMB = wiredTigerGlobalOptions.liveRestoreReadSizeMB; wtConfig.statisticsLogWaitSecs = wiredTigerGlobalOptions.statisticsLogDelaySecs; + wtConfig.providerSupportsUnstableCheckpoints = provider.supportsUnstableCheckpoints(); + wtConfig.safeToTakeDuplicateCheckpoints = !provider.shouldAvoidDuplicateCheckpoints(); + wtConfig.flattenLeafPageDelta = wiredTigerGlobalOptions.flattenLeafPageDelta; wtConfig.extraOpenOptions = wiredTigerGlobalOptions.engineConfig; if (wtConfig.extraOpenOptions.find("session_max=") != std::string::npos) { diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h index 8461fbb9bd8..89927075ca1 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h @@ -36,6 +36,7 @@ #include "mongo/bson/timestamp.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/persistence_provider.h" #include "mongo/db/storage/journal_listener.h" #include "mongo/db/storage/key_format.h" #include "mongo/db/storage/kv/kv_engine.h" @@ -220,6 +221,13 @@ public: bool prefetchEnabled{true}; // Specifies whether restore is enabled. bool restoreEnabled{true}; + // Specifies whether unstable checkpoints are supported by the underlying + // PersistenceProvider. + bool providerSupportsUnstableCheckpoints{true}; + // Specifies whether it is safe to take duplicate checkpoints on the same stable timestamp. + bool safeToTakeDuplicateCheckpoints{true}; + // Specifies whether the value for the flatten_leaf_page_delta configuration parameter. + int flattenLeafPageDelta{1}; // This specifies the value for the log.compressor configuration parameter. std::string logCompressor{"snappy"}; // This specifies the value for the live_restore.path configuration parameter. @@ -346,6 +354,7 @@ public: ClockSource* cs, WiredTigerConfig wtConfig, const WiredTigerExtensions& wtExtensions, + const rss::PersistenceProvider& provider, bool repair, bool isReplSet, bool shouldRecoverFromOplogAsStandalone, @@ -385,11 +394,13 @@ public: std::unique_ptr newRecoveryUnit() override; - Status createRecordStore(const NamespaceString& ns, + Status createRecordStore(const rss::PersistenceProvider& provider, + const NamespaceString& ns, StringData ident, const RecordStore::Options& options) override { // Parameters required for a standard WiredTigerRecordStore. - return _createRecordStore(ns, + return _createRecordStore(provider, + ns, ident, options.keyFormat, options.storageEngineCollectionOptions, @@ -411,6 +422,7 @@ public: KeyFormat keyFormat) override; Status createSortedDataInterface( + const rss::PersistenceProvider&, RecoveryUnit&, const NamespaceString& nss, const UUID& uuid, @@ -487,7 +499,8 @@ public: Status repairIdent(RecoveryUnit& ru, StringData ident) override; - Status recoverOrphanedIdent(const NamespaceString& nss, + Status recoverOrphanedIdent(const rss::PersistenceProvider&, + const NamespaceString& nss, StringData ident, const RecordStore::Options& options) override; @@ -503,6 +516,12 @@ public: void setJournalListener(JournalListener* jl) final; + void setLastMaterializedLsn(uint64_t lsn) final; + + void setRecoveryCheckpointMetadata(StringData checkpointMetadata) final; + + void promoteToLeader() final; + void setStableTimestamp(Timestamp stableTimestamp, bool force) override; void setInitialDataTimestamp(Timestamp initialDataTimestamp) override; @@ -719,7 +738,8 @@ private: StorageEngine::DropIdentCallback callback; }; - Status _createRecordStore(const NamespaceString& ns, + Status _createRecordStore(const rss::PersistenceProvider& provider, + const NamespaceString& ns, StringData ident, KeyFormat keyFormat, const BSONObj& storageEngineCollectionOptions, @@ -883,18 +903,22 @@ private: bool _isReplSet; bool _shouldRecoverFromOplogAsStandalone; Atomic _inStandaloneMode; + + const bool _supportsTableLogging; }; /** * Generates config string for wiredtiger_open() from the given config options. */ std::string generateWTOpenConfigString(const WiredTigerKVEngineBase::WiredTigerConfig& wtConfig, - StringData extensionsConfig); + StringData extensionsConfig, + StringData providerConfig); /** * Returns a WiredTigerKVEngineBase::WiredTigerConfig populated with config values provided at * startup. */ -WiredTigerKVEngineBase::WiredTigerConfig getWiredTigerConfigFromStartupOptions(); +WiredTigerKVEngineBase::WiredTigerConfig getWiredTigerConfigFromStartupOptions( + const rss::PersistenceProvider&); } // namespace mongo diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp index c6b2a6c3d0c..7316fba36ff 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp @@ -46,6 +46,7 @@ #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/operation_context.h" #include "mongo/db/record_id.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_test_fixture.h" @@ -116,7 +117,9 @@ private: std::unique_ptr makeEngine() { // Use a small journal for testing to account for the unlikely event that the underlying // filesystem does not support fast allocation of a file of zeros. - WiredTigerKVEngineBase::WiredTigerConfig wtConfig = getWiredTigerConfigFromStartupOptions(); + auto& provider = rss::ReplicatedStorageService::get(_svcCtx).getPersistenceProvider(); + WiredTigerKVEngineBase::WiredTigerConfig wtConfig = + getWiredTigerConfigFromStartupOptions(provider); wtConfig.cacheSizeMB = 1; wtConfig.extraOpenOptions = "log=(file_max=1m,prealloc=false)"; // Faithfully simulate being in replica set mode for timestamping tests which requires @@ -129,6 +132,7 @@ private: _cs.get(), std::move(wtConfig), WiredTigerExtensions::get(_svcCtx), + provider, _forRepair, isReplSet, shouldRecoverFromOplogAsStandalone, @@ -175,7 +179,8 @@ TEST_F(WiredTigerKVEngineRepairTest, OrphanedDataFilesCanBeRecovered) { std::string ident = "collection-1234"; RecordStore::Options options; NamespaceString nss = NamespaceString::createNamespaceString_forTest("a.b"); - ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options)); + auto& provider = rss::ReplicatedStorageService::get(opCtxPtr.get()).getPersistenceProvider(); + ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options)); auto rs = _helper.getWiredTigerKVEngine()->getRecordStore( opCtxPtr.get(), nss, ident, options, UUID::gen()); ASSERT(rs); @@ -227,7 +232,8 @@ TEST_F(WiredTigerKVEngineRepairTest, OrphanedDataFilesCanBeRecovered) { boost::filesystem::rename(tmpFile, *dataFilePath, err); ASSERT(!err) << err.message(); - auto status = _helper.getWiredTigerKVEngine()->recoverOrphanedIdent(nss, ident, options); + auto status = + _helper.getWiredTigerKVEngine()->recoverOrphanedIdent(provider, nss, ident, options); ASSERT_EQ(ErrorCodes::DataModifiedByRepair, status.code()); #endif } @@ -239,7 +245,8 @@ TEST_F(WiredTigerKVEngineRepairTest, UnrecoverableOrphanedDataFilesAreRebuilt) { NamespaceString nss = NamespaceString::createNamespaceString_forTest("a.b"); std::string ident = "collection-1234"; RecordStore::Options options; - ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options)); + auto& provider = rss::ReplicatedStorageService::get(opCtxPtr.get()).getPersistenceProvider(); + ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options)); UUID uuid = UUID::gen(); auto rs = @@ -292,7 +299,8 @@ TEST_F(WiredTigerKVEngineRepairTest, UnrecoverableOrphanedDataFilesAreRebuilt) { // This should recreate an empty data file successfully and move the old one to a name that ends // in ".corrupt". - auto status = _helper.getWiredTigerKVEngine()->recoverOrphanedIdent(nss, ident, options); + auto status = + _helper.getWiredTigerKVEngine()->recoverOrphanedIdent(provider, nss, ident, options); ASSERT_EQ(ErrorCodes::DataModifiedByRepair, status.code()) << status.reason(); boost::filesystem::path corruptFile = (dataFilePath->string() + ".corrupt"); @@ -408,7 +416,8 @@ TEST_F(WiredTigerKVEngineTest, CreateRecordStoreFailsWithExistingIdent) { NamespaceString nss = NamespaceString::createNamespaceString_forTest("a.b"); std::string ident = "collection-1234"; RecordStore::Options options; - ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options)); + auto& provider = rss::ReplicatedStorageService::get(opCtxPtr.get()).getPersistenceProvider(); + ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options)); // A new record store must always have its own storage table uniquely identified by the ident. // Otherwise, multiple record stores could point to the same storage resource and lead to data @@ -417,7 +426,8 @@ TEST_F(WiredTigerKVEngineTest, CreateRecordStoreFailsWithExistingIdent) { // Validate the server throws when trying to create a new record store with an ident already in // use. - const auto status = _helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options); + const auto status = + _helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options); ASSERT_NOT_OK(status); ASSERT_EQ(status.code(), ErrorCodes::ObjectAlreadyExists); } @@ -434,7 +444,8 @@ TEST_F(WiredTigerKVEngineTest, IdentDrop) { std::string ident = "collection-1234"; RecordStore::Options options; - ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options)); + auto& provider = rss::ReplicatedStorageService::get(opCtxPtr.get()).getPersistenceProvider(); + ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options)); const boost::optional dataFilePath = _helper.getWiredTigerKVEngine()->getDataFilePathForIdent(ident); @@ -447,7 +458,7 @@ TEST_F(WiredTigerKVEngineTest, IdentDrop) { // Because the underlying file was not removed, it will be renamed out of the way by WiredTiger // when creating a new table with the same ident. - ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options)); + ASSERT_OK(_helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options)); const boost::filesystem::path renamedFilePath = dataFilePath->generic_string() + ".1"; ASSERT(boost::filesystem::exists(*dataFilePath)); @@ -924,7 +935,10 @@ protected: StatusWith createIdent(StringData ns, StringData ident) { NamespaceString nss = NamespaceString::createNamespaceString_forTest(ns); RecordStore::Options options; - Status stat = _helper.getWiredTigerKVEngine()->createRecordStore(nss, ident, options); + auto& provider = + rss::ReplicatedStorageService::get(getGlobalServiceContext()).getPersistenceProvider(); + Status stat = + _helper.getWiredTigerKVEngine()->createRecordStore(provider, nss, ident, options); if (!stat.isOK()) { return stat; } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict_test.cpp index 557649b3730..0f92cff684a 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict_test.cpp @@ -29,6 +29,7 @@ #include "mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/prepare_conflict_tracker.h" #include "mongo/db/storage/recovery_unit.h" @@ -56,7 +57,9 @@ namespace { std::unique_ptr makeKVEngine(ServiceContext* serviceContext, const std::string& path, ClockSource* clockSource) { - WiredTigerKVEngineBase::WiredTigerConfig wtConfig = getWiredTigerConfigFromStartupOptions(); + auto& provider = rss::ReplicatedStorageService::get(serviceContext).getPersistenceProvider(); + WiredTigerKVEngineBase::WiredTigerConfig wtConfig = + getWiredTigerConfigFromStartupOptions(provider); wtConfig.cacheSizeMB = 1; return std::make_unique( /*canonicalName=*/"", @@ -64,6 +67,7 @@ std::unique_ptr makeKVEngine(ServiceContext* serviceContext, clockSource, std::move(wtConfig), WiredTigerExtensions::get(serviceContext), + provider, /*repair=*/false, /*isReplSet=*/false, /*shouldRecoverFromOplogAsStandalone=*/false, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp index 7cdca2295d8..ce991908ebf 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp @@ -221,18 +221,14 @@ std::string WiredTigerRecordStore::generateCreateString( ss << "block_compressor=" << wtTableConfig.blockCompressor << ","; + // TODO: Replace WiredTigerCustomizationHooks with WiredTigerCustomizationHooksRegistry. ss << WiredTigerCustomizationHooks::get(getGlobalServiceContext()) ->getTableCreateConfig(tableName); + ss << WiredTigerCustomizationHooksRegistry::get(getGlobalServiceContext()) + .getTableCreateConfig(tableName); ss << wtTableConfig.extraCreateOptions << ","; - if (isOplog) { - // force file for oplog - ss << "type=file,"; - // Tune down to 10m. See SERVER-16247 - ss << "memory_page_max=10m,"; - } - // By default, WiredTiger silently ignores a create table command if the specified ident already // exists - even if the existing table has a different configuration. // @@ -1419,7 +1415,7 @@ WiredTigerRecordStore::Oplog::Oplog(WiredTigerKVEngine* engine, .engineName = oplogParams.engineName, .keyFormat = KeyFormat::Long, .overwrite = true, - .isLogged = true, + .isLogged = oplogParams.isLogged, .forceUpdateWithFullDocument = oplogParams.forceUpdateWithFullDocument, .inMemory = oplogParams.inMemory, .sizeStorer = oplogParams.sizeStorer, diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h index e7a90394ecf..ca099829560 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h @@ -418,6 +418,7 @@ public: int64_t oplogMaxSize; WiredTigerSizeStorer* sizeStorer; bool tracksSizeAdjustments; + bool isLogged; bool forceUpdateWithFullDocument; }; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test.cpp index 6bbc97cc4ef..6ee4ad2ddf7 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test.cpp @@ -40,6 +40,7 @@ #include "mongo/bson/json.h" #include "mongo/db/client.h" #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/kv/kv_engine.h" #include "mongo/db/storage/record_store_test_harness.h" @@ -657,6 +658,45 @@ TEST(WiredTigerRecordStoreTest, CursorInActiveTxnAfterSeek) { } } +TEST(WiredTigerRecordStoreTest, CreateOnExistingIdentFails) { + const std::unique_ptr harnessHelper(newRecordStoreHarnessHelper()); + const ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext()); + Lock::GlobalLock globalLock(opCtx.get(), MODE_X); + + const std::string ns = "testRecordStore"; + const NamespaceString nss = NamespaceString::createNamespaceString_forTest(ns); + const std::string uri = WiredTigerUtil::kTableUriPrefix + ns; + bool isReplSet = false; + bool shouldRecoverFromOplogAsStandalone = + repl::ReplSettings::shouldRecoverFromOplogAsStandalone(); + WiredTigerRecordStore::WiredTigerTableConfig wtTableConfig; + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); + wtTableConfig.logEnabled = WiredTigerUtil::useTableLogging( + provider, nss, isReplSet, shouldRecoverFromOplogAsStandalone); + const std::string config = WiredTigerRecordStore::generateCreateString( + NamespaceStringUtil::serializeForCatalog(nss), wtTableConfig); + { + WriteUnitOfWork uow(opCtx.get()); + WiredTigerRecoveryUnit* ru = + checked_cast(shard_role_details::getRecoveryUnit(opCtx.get())); + WiredTigerSession* s = ru->getSession(); + invariantWTOK(s->create(uri.c_str(), config.c_str()), *s); + uow.commit(); + } + + { + WriteUnitOfWork uow(opCtx.get()); + WiredTigerRecoveryUnit* ru = + checked_cast(shard_role_details::getRecoveryUnit(opCtx.get())); + WiredTigerSession* s = ru->getSession(); + const auto ret = s->create(uri.c_str(), config.c_str()); + ASSERT_EQ(EEXIST, ret); + const auto status = wtRCToStatus(ret, *s); + ASSERT_NOT_OK(status); + uow.commit(); + } +} + // Verify clustered record stores. // This test case complements StorageEngineTest:TemporaryRecordStoreClustered which verifies // clustered temporary record stores. @@ -673,8 +713,9 @@ TEST(WiredTigerRecordStoreTest, ClusteredRecordStore) { wtTableConfig.blockCompressor = wiredTigerGlobalOptions.collectionBlockCompressor; bool isReplSet = false; bool shouldRecoverFromOplogAsStandalone = false; - wtTableConfig.logEnabled = - WiredTigerUtil::useTableLogging(nss, isReplSet, shouldRecoverFromOplogAsStandalone); + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); + wtTableConfig.logEnabled = WiredTigerUtil::useTableLogging( + provider, nss, isReplSet, shouldRecoverFromOplogAsStandalone); const std::string config = WiredTigerRecordStore::generateCreateString( NamespaceStringUtil::serializeForCatalog(nss), wtTableConfig); { @@ -692,8 +733,8 @@ TEST(WiredTigerRecordStoreTest, ClusteredRecordStore) { params.engineName = std::string{kWiredTigerEngineName}; params.keyFormat = KeyFormat::String; params.overwrite = false; - params.isLogged = - WiredTigerUtil::useTableLogging(nss, isReplSet, shouldRecoverFromOplogAsStandalone); + params.isLogged = WiredTigerUtil::useTableLogging( + provider, nss, isReplSet, shouldRecoverFromOplogAsStandalone); params.forceUpdateWithFullDocument = false; params.inMemory = false; params.sizeStorer = nullptr; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test_harness.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test_harness.cpp index deed3c2269b..fdb1aff1047 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test_harness.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store_test_harness.cpp @@ -33,6 +33,7 @@ #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/wiredtiger/wiredtiger_record_store.h" #include "mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h" @@ -57,7 +58,10 @@ std::string _testLoggingSettings(std::string extraStrings) { WiredTigerHarnessHelper::WiredTigerHarnessHelper(Options options, StringData extraStrings) : _dbpath("wt_test") { - WiredTigerKVEngineBase::WiredTigerConfig wtConfig = getWiredTigerConfigFromStartupOptions(); + auto& provider = + rss::ReplicatedStorageService::get(getGlobalServiceContext()).getPersistenceProvider(); + WiredTigerKVEngineBase::WiredTigerConfig wtConfig = + getWiredTigerConfigFromStartupOptions(provider); wtConfig.cacheSizeMB = 1; wtConfig.extraOpenOptions = _testLoggingSettings(std::string{extraStrings}); _isReplSet = options == Options::ReplicationEnabled; @@ -68,6 +72,7 @@ WiredTigerHarnessHelper::WiredTigerHarnessHelper(Options options, StringData ext &_cs, std::move(wtConfig), WiredTigerExtensions::get(serviceContext()), + provider, false, _isReplSet, shouldRecoverFromOplogAsStandalone, @@ -81,7 +86,8 @@ std::unique_ptr WiredTigerHarnessHelper::newRecordStore( const RecordStore::Options& recordStoreOptions, boost::optional uuid) { ServiceContext::UniqueOperationContext opCtx(newOperationContext()); - const auto res = _engine->createRecordStore(nss, ident, recordStoreOptions); + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); + const auto res = _engine->createRecordStore(provider, nss, ident, recordStoreOptions); return _engine->getRecordStore(opCtx.get(), nss, ident, recordStoreOptions, uuid); } @@ -100,11 +106,12 @@ std::unique_ptr WiredTigerHarnessHelper::newOplogRecordStoreNoInit( oplogRecordStoreOptions.isCapped = true; // Large enough not to exceed capped limits. oplogRecordStoreOptions.oplogMaxSize = 1024 * 1024 * 1024; + ServiceContext::UniqueOperationContext opCtx(newOperationContext()); + auto& provider = rss::ReplicatedStorageService::get(opCtx.get()).getPersistenceProvider(); const auto res = _engine->createRecordStore( - NamespaceString::kRsOplogNamespace, ident, oplogRecordStoreOptions); + provider, NamespaceString::kRsOplogNamespace, ident, oplogRecordStoreOptions); // Cannot use 'getRecordStore', which automatically starts the the oplog manager. - ServiceContext::UniqueOperationContext opCtx(newOperationContext()); return std::make_unique( _engine.get(), WiredTigerRecoveryUnit::get(*shard_role_details::getRecoveryUnit(opCtx.get())), @@ -116,6 +123,7 @@ std::unique_ptr WiredTigerHarnessHelper::newOplogRecordStoreNoInit( .oplogMaxSize = oplogRecordStoreOptions.oplogMaxSize, .sizeStorer = nullptr, .tracksSizeAdjustments = true, + .isLogged = true, .forceUpdateWithFullDocument = false}); } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp index c19b60caa4f..8deac93ccb6 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp @@ -38,6 +38,7 @@ #include "mongo/db/local_catalog/shard_role_api/transaction_resources.h" #include "mongo/db/namespace_string.h" #include "mongo/db/record_id.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/record_store.h" #include "mongo/db/storage/recovery_unit_test_harness.h" @@ -70,7 +71,10 @@ namespace { class WiredTigerRecoveryUnitHarnessHelper final : public RecoveryUnitHarnessHelper { public: WiredTigerRecoveryUnitHarnessHelper() : _dbpath("wt_test") { - WiredTigerKVEngineBase::WiredTigerConfig wtConfig = getWiredTigerConfigFromStartupOptions(); + auto& provider = + rss::ReplicatedStorageService::get(serviceContext()).getPersistenceProvider(); + WiredTigerKVEngineBase::WiredTigerConfig wtConfig = + getWiredTigerConfigFromStartupOptions(provider); wtConfig.cacheSizeMB = 1; // Use a replica set so that writes to replicated collections are not journaled and thus @@ -81,6 +85,7 @@ public: &_cs, std::move(wtConfig), WiredTigerExtensions::get(serviceContext()), + provider, false /* repair */, true /* isReplSet */, false /* shouldRecoverFromOplogAsStandalone */, @@ -107,7 +112,8 @@ public: std::string ident = "collection-" + ns; std::replace(ident.begin(), ident.end(), '.', '-'); NamespaceString nss = NamespaceString::createNamespaceString_forTest(ns); - const auto res = _engine->createRecordStore(nss, ident, RecordStore::Options{}); + auto& provider = rss::ReplicatedStorageService::get(opCtx).getPersistenceProvider(); + const auto res = _engine->createRecordStore(provider, nss, ident, RecordStore::Options{}); return _engine->getRecordStore(opCtx, nss, ident, RecordStore::Options{}, UUID::gen()); } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp index 91e217d3d55..779ed6cee14 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/json.h" #include "mongo/db/commands/server_status_metric.h" #include "mongo/db/operation_context.h" +#include "mongo/db/rss/persistence_provider.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/db/storage/execution_context.h" #include "mongo/db/storage/snapshot_window_options_gen.h" @@ -870,7 +871,8 @@ void WiredTigerUtil::validateTableLogging(WiredTigerSession& session, } } -bool WiredTigerUtil::useTableLogging(const NamespaceString& nss, +bool WiredTigerUtil::useTableLogging(const rss::PersistenceProvider& provider, + const NamespaceString& nss, bool isReplSet, bool shouldRecoverFromOplogAsStandalone) { if (storageGlobalParams.forceDisableTableLogging) { @@ -881,7 +883,12 @@ bool WiredTigerUtil::useTableLogging(const NamespaceString& nss, // We only turn off logging in the case of: // 1) Replication is enabled (the typical deployment), or - // 2) We're running as a standalone with recoverFromOplogAsStandalone=true + // 2) This is a disaggregated storage mongod, or + // 3) We're running as a standalone with recoverFromOplogAsStandalone=true + if (!provider.supportsTableLogging()) { + return false; + } + const bool journalWritesBecauseStandalone = !isReplSet && !shouldRecoverFromOplogAsStandalone; if (journalWritesBecauseStandalone) { return true; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_util.h b/src/mongo/db/storage/wiredtiger/wiredtiger_util.h index 91a0339750a..1636376c68b 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_util.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_util.h @@ -29,6 +29,7 @@ #pragma once +#include "mongo/db/rss/persistence_provider.h" #include "mongo/db/storage/wiredtiger/wiredtiger_error_util.h" #include "mongo/db/storage/wiredtiger/wiredtiger_event_handler.h" #include "mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h" @@ -328,7 +329,8 @@ public: boost::optional indexName, ValidateResultsIf& validationResult); - static bool useTableLogging(const NamespaceString& nss, + static bool useTableLogging(const rss::PersistenceProvider& provider, + const NamespaceString& nss, bool isReplSet, bool shouldRecoverFromOplogAsStandalone); diff --git a/src/mongo/db/transaction/BUILD.bazel b/src/mongo/db/transaction/BUILD.bazel index bcf3fa892e2..6fac9ed3861 100644 --- a/src/mongo/db/transaction/BUILD.bazel +++ b/src/mongo/db/transaction/BUILD.bazel @@ -115,6 +115,7 @@ mongo_cc_library( "//src/mongo/db/repl:apply_ops_command_info", "//src/mongo/db/repl:repl_server_parameters", "//src/mongo/db/repl:replica_set_aware_service", + "//src/mongo/db/rss:replicated_storage_service", "//src/mongo/db/session:session_catalog_mongod", "//src/mongo/db/stats:top", "//src/mongo/db/stats:transaction_stats", diff --git a/src/mongo/db/transaction/transaction_participant.cpp b/src/mongo/db/transaction/transaction_participant.cpp index a6df097f132..1dfbbb2d677 100644 --- a/src/mongo/db/transaction/transaction_participant.cpp +++ b/src/mongo/db/transaction/transaction_participant.cpp @@ -73,6 +73,7 @@ #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/storage_interface.h" #include "mongo/db/replication_state_transition_lock_guard.h" +#include "mongo/db/rss/replicated_storage_service.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context.h" @@ -1065,6 +1066,13 @@ void TransactionParticipant::Participant::beginOrContinue( TxnNumberAndRetryCounter txnNumberAndRetryCounter, boost::optional autocommit, TransactionActions action) { + auto& rss = rss::ReplicatedStorageService::get(opCtx); + if (!rss.getPersistenceProvider().supportsMultiDocumentTransactions() && autocommit) { + uasserted(ErrorCodes::NotImplemented, + str::stream() << rss.getPersistenceProvider().name() + << "does not support multi-document transactions"); + } + opCtx->setActiveTransactionParticipant(); if (_isInternalSessionForRetryableWrite()) { diff --git a/src/mongo/dbtests/BUILD.bazel b/src/mongo/dbtests/BUILD.bazel index 1a3a969cc11..9f39084f087 100644 --- a/src/mongo/dbtests/BUILD.bazel +++ b/src/mongo/dbtests/BUILD.bazel @@ -35,6 +35,7 @@ mongo_cc_library( "//src/mongo/db/index_builds:index_builds_coordinator_mongod", "//src/mongo/db/local_catalog:catalog_impl", "//src/mongo/db/op_observer", + "//src/mongo/db/rss/attached_storage:attached_persistence_provider", "//src/mongo/db/s:sharding_runtime_d", "//src/mongo/db/storage:storage_control", "//src/mongo/db/storage:storage_options", diff --git a/src/mongo/executor/mock_network_fixture_test.cpp b/src/mongo/executor/mock_network_fixture_test.cpp index 327f9e95b4c..9d0d8ce3d9b 100644 --- a/src/mongo/executor/mock_network_fixture_test.cpp +++ b/src/mongo/executor/mock_network_fixture_test.cpp @@ -237,7 +237,7 @@ TEST_F(MockNetworkTest, MockFixtureSimilarExpectationsOverride) { const auto deadline = net().now() + Milliseconds(100); mock().runUntil(deadline); - // The command matcher superceded the BSON matcher so we have an some unmatched expectation. + // The command matcher superseded the BSON matcher so we have an some unmatched expectation. ASSERT_THROWS_CODE(mock().verifyExpectations(), DBException, (ErrorCodes::Error)5015501); ASSERT(!specificExp.isSatisfied()); diff --git a/src/mongo/mongo_config_header.py b/src/mongo/mongo_config_header.py index d3ebc5d0744..374a741c5f8 100644 --- a/src/mongo/mongo_config_header.py +++ b/src/mongo/mongo_config_header.py @@ -432,6 +432,7 @@ def get_config_header_substs(): ("@mongo_config_tcmalloc_gperf@", "MONGO_CONFIG_TCMALLOC_GPERF"), ("@mongo_config_streams@", "MONGO_CONFIG_STREAMS"), ("@mongo_config_otel@", "MONGO_CONFIG_OTEL"), + ("@mongo_config_disagg_storage@", "MONGO_CONFIG_DISAGG_STORAGE"), ( "@mongo_config_mutex_observation@", "MONGO_CONFIG_MUTEX_OBSERVATION", diff --git a/src/third_party/OWNERS.yml b/src/third_party/OWNERS.yml index 5038b6a55d4..d5ed03ff1d4 100644 --- a/src/third_party/OWNERS.yml +++ b/src/third_party/OWNERS.yml @@ -30,6 +30,9 @@ filters: - "folly": approvers: - 10gen/server-workload-scheduling + - "googletest_restricted_for_disagg_only": + approvers: + - 10gen/server-disagg-storage - "gperftools": approvers: - 10gen/server-workload-scheduling diff --git a/src/third_party/googletest_restricted_for_disagg_only/BUILD.bazel b/src/third_party/googletest_restricted_for_disagg_only/BUILD.bazel new file mode 100644 index 00000000000..0eb506e275a --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/BUILD.bazel @@ -0,0 +1,48 @@ +load("//bazel:mongo_src_rules.bzl", "mongo_cc_library") + +package(default_visibility = ["//visibility:public"]) + +mongo_cc_library( + name = "gtest", + srcs = glob( + include = [ + "dist/googletest/src/*.cc", + "dist/googletest/src/*.h", + "dist/googletest/include/gtest/**/*.h", + "dist/googlemock/src/*.cc", + "dist/googlemock/include/gmock/**/*.h", + ], + exclude = [ + "dist/googletest/src/gtest-all.cc", + "dist/googletest/src/gtest_main.cc", + "dist/googlemock/src/gmock-all.cc", + "dist/googlemock/src/gmock_main.cc", + ], + ), + hdrs = glob([ + "dist/googletest/include/gmock/*.h", + "dist/googletest/include/gtest/*.h", + ]), + defines = [ + "GTEST_DONT_DEFINE_ASSERT_EQ=1", + "GTEST_DONT_DEFINE_ASSERT_FALSE=1", + "GTEST_DONT_DEFINE_ASSERT_GE=1", + "GTEST_DONT_DEFINE_ASSERT_GT=1", + "GTEST_DONT_DEFINE_ASSERT_LE=1", + "GTEST_DONT_DEFINE_ASSERT_LT=1", + "GTEST_DONT_DEFINE_ASSERT_NE=1", + "GTEST_DONT_DEFINE_ASSERT_TRUE=1", + "GTEST_DONT_DEFINE_ASSERT_XY=1", + "GTEST_DONT_DEFINE_FAIL=1", + "GTEST_DONT_DEFINE_SUCCEED=1", + "GTEST_DONT_DEFINE_TEST=1", + "GTEST_DONT_DEFINE_TEST_F=1", + "GTEST_NO_ABSL_FLAGS=1", + ], + includes = [ + "dist/googlemock", + "dist/googlemock/include", + "dist/googletest", + "dist/googletest/include", + ], +) diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/LICENSE b/src/third_party/googletest_restricted_for_disagg_only/dist/LICENSE new file mode 100644 index 00000000000..1941a11f8ce --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/LICENSE @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/README.md b/src/third_party/googletest_restricted_for_disagg_only/dist/README.md new file mode 100644 index 00000000000..598cf31242b --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/README.md @@ -0,0 +1,133 @@ +# GoogleTest + +### Announcements + +#### Documentation Updates + +Our documentation is now live on GitHub Pages at +https://google.github.io/googletest/. We recommend browsing the documentation on +GitHub Pages rather than directly in the repository. + +#### Release 1.17.0 + +[Release 1.17.0](https://github.com/google/googletest/releases/tag/v1.17.0) is +now available. + +The 1.17.x branch [requires at least C++17]((https://opensource.google/documentation/policies/cplusplus-support#c_language_standard). + +#### Continuous Integration + +We use Google's internal systems for continuous integration. + +#### Coming Soon + +* We are planning to take a dependency on + [Abseil](https://github.com/abseil/abseil-cpp). + +## Welcome to **GoogleTest**, Google's C++ test framework! + +This repository is a merger of the formerly separate GoogleTest and GoogleMock +projects. These were so closely related that it makes sense to maintain and +release them together. + +### Getting Started + +See the [GoogleTest User's Guide](https://google.github.io/googletest/) for +documentation. We recommend starting with the +[GoogleTest Primer](https://google.github.io/googletest/primer.html). + +More information about building GoogleTest can be found at +[googletest/README.md](googletest/README.md). + +## Features + +* xUnit test framework: \ + Googletest is based on the [xUnit](https://en.wikipedia.org/wiki/XUnit) + testing framework, a popular architecture for unit testing +* Test discovery: \ + Googletest automatically discovers and runs your tests, eliminating the need + to manually register your tests +* Rich set of assertions: \ + Googletest provides a variety of assertions, such as equality, inequality, + exceptions, and more, making it easy to test your code +* User-defined assertions: \ + You can define your own assertions with Googletest, making it simple to + write tests that are specific to your code +* Death tests: \ + Googletest supports death tests, which verify that your code exits in a + certain way, making it useful for testing error-handling code +* Fatal and non-fatal failures: \ + You can specify whether a test failure should be treated as fatal or + non-fatal with Googletest, allowing tests to continue running even if a + failure occurs +* Value-parameterized tests: \ + Googletest supports value-parameterized tests, which run multiple times with + different input values, making it useful for testing functions that take + different inputs +* Type-parameterized tests: \ + Googletest also supports type-parameterized tests, which run with different + data types, making it useful for testing functions that work with different + data types +* Various options for running tests: \ + Googletest provides many options for running tests including running + individual tests, running tests in a specific order and running tests in + parallel + +## Supported Platforms + +GoogleTest follows Google's +[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support). +See +[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md) +for a list of currently supported versions of compilers, platforms, and build +tools. + +## Who Is Using GoogleTest? + +In addition to many internal projects at Google, GoogleTest is also used by the +following notable projects: + +* The [Chromium projects](https://www.chromium.org/) (behind the Chrome + browser and Chrome OS). +* The [LLVM](https://llvm.org/) compiler. +* [Protocol Buffers](https://github.com/google/protobuf), Google's data + interchange format. +* The [OpenCV](https://opencv.org/) computer vision library. + +## Related Open Source Projects + +[GTest Runner](https://github.com/nholthaus/gtest-runner) is a Qt5 based +automated test-runner and Graphical User Interface with powerful features for +Windows and Linux platforms. + +[GoogleTest UI](https://github.com/ospector/gtest-gbar) is a test runner that +runs your test binary, allows you to track its progress via a progress bar, and +displays a list of test failures. Clicking on one shows failure text. GoogleTest +UI is written in C#. + +[GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event +listener for GoogleTest that implements the +[TAP protocol](https://en.wikipedia.org/wiki/Test_Anything_Protocol) for test +result output. If your test runner understands TAP, you may find it useful. + +[gtest-parallel](https://github.com/google/gtest-parallel) is a test runner that +runs tests from your binary in parallel to provide significant speed-up. + +[GoogleTest Adapter](https://marketplace.visualstudio.com/items?itemName=DavidSchuldenfrei.gtest-adapter) +is a VS Code extension allowing to view GoogleTest in a tree view and run/debug +your tests. + +[C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) is a VS +Code extension allowing to view GoogleTest in a tree view and run/debug your +tests. + +[Cornichon](https://pypi.org/project/cornichon/) is a small Gherkin DSL parser +that generates stub code for GoogleTest. + +## Contributing Changes + +Please read +[`CONTRIBUTING.md`](https://github.com/google/googletest/blob/main/CONTRIBUTING.md) +for details on how to contribute to this project. + +Happy testing! diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-actions.h b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-actions.h new file mode 100644 index 00000000000..5fe50e3d8e3 --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-actions.h @@ -0,0 +1,2404 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// Users can, however, define any local functors (e.g. a lambda) that +// can be used as actions. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' on +// https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ + +#ifndef _WIN32_WCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gmock/internal/gmock-pp.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100) + +namespace testing { + +// To implement an action Foo, define: +// 1. a class FooAction that implements the ActionInterface interface, and +// 2. a factory function that creates an Action object from a +// const FooAction*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Action objects can now be copied like plain values. + +namespace internal { + +// BuiltInDefaultValueGetter::Get() returns a +// default-constructed T value. BuiltInDefaultValueGetter::Get() crashes with an error. +// +// This primary template is used when kDefaultConstructible is true. +template +struct BuiltInDefaultValueGetter { + static T Get() { return T(); } +}; +template +struct BuiltInDefaultValueGetter { + static T Get() { + Assert(false, __FILE__, __LINE__, + "Default action undefined for the function return type."); +#if defined(__GNUC__) || defined(__clang__) + __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(0); +#else + return Invalid(); + // The above statement will never be reached, but is required in + // order for this function to compile. +#endif + } +}; + +// BuiltInDefaultValue::Get() returns the "built-in" default value +// for type T, which is NULL when T is a raw pointer type, 0 when T is +// a numeric type, false when T is bool, or "" when T is string or +// std::string. In addition, in C++11 and above, it turns a +// default-constructed T value if T is default constructible. For any +// other type T, the built-in default T value is undefined, and the +// function will abort the process. +template +class BuiltInDefaultValue { + public: + // This function returns true if and only if type T has a built-in default + // value. + static bool Exists() { return ::std::is_default_constructible::value; } + + static T Get() { + return BuiltInDefaultValueGetter< + T, ::std::is_default_constructible::value>::Get(); + } +}; + +// This partial specialization says that we use the same built-in +// default value for T and const T. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return BuiltInDefaultValue::Exists(); } + static T Get() { return BuiltInDefaultValue::Get(); } +}; + +// This partial specialization defines the default values for pointer +// types. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return true; } + static T* Get() { return nullptr; } +}; + +// The following specializations define the default values for +// specific types we care about. +#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ + template <> \ + class BuiltInDefaultValue { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ + } + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, ""); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0'); + +// There's no need for a default action for signed wchar_t, as that +// type is the same as wchar_t for gcc, and invalid for MSVC. +// +// There's also no need for a default action for unsigned wchar_t, as +// that type is the same as unsigned int for gcc, and invalid for +// MSVC. +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U); // NOLINT +#endif + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); + +#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ + +// Partial implementations of metaprogramming types from the standard library +// not available in C++11. + +template +struct negation + // NOLINTNEXTLINE + : std::integral_constant {}; + +// Base case: with zero predicates the answer is always true. +template +struct conjunction : std::true_type {}; + +// With a single predicate, the answer is that predicate. +template +struct conjunction : P1 {}; + +// With multiple predicates the answer is the first predicate if that is false, +// and we recurse otherwise. +template +struct conjunction + : std::conditional, P1>::type {}; + +template +struct disjunction : std::false_type {}; + +template +struct disjunction : P1 {}; + +template +struct disjunction + // NOLINTNEXTLINE + : std::conditional, P1>::type {}; + +template +using void_t = void; + +// Detects whether an expression of type `From` can be implicitly converted to +// `To` according to [conv]. In C++17, [conv]/3 defines this as follows: +// +// An expression e can be implicitly converted to a type T if and only if +// the declaration T t=e; is well-formed, for some invented temporary +// variable t ([dcl.init]). +// +// [conv]/2 implies we can use function argument passing to detect whether this +// initialization is valid. +// +// Note that this is distinct from is_convertible, which requires this be valid: +// +// To test() { +// return declval(); +// } +// +// In particular, is_convertible doesn't give the correct answer when `To` and +// `From` are the same non-moveable type since `declval` will be an rvalue +// reference, defeating the guaranteed copy elision that would otherwise make +// this function work. +// +// REQUIRES: `From` is not cv void. +template +struct is_implicitly_convertible { + private: + // A function that accepts a parameter of type T. This can be called with type + // U successfully only if U is implicitly convertible to T. + template + static void Accept(T); + + // A function that creates a value of type T. + template + static T Make(); + + // An overload be selected when implicit conversion from T to To is possible. + template (Make()))> + static std::true_type TestImplicitConversion(int); + + // A fallback overload selected in all other cases. + template + static std::false_type TestImplicitConversion(...); + + public: + using type = decltype(TestImplicitConversion(0)); + static constexpr bool value = type::value; +}; + +// Like std::invoke_result_t from C++17, but works only for objects with call +// operators (not e.g. member function pointers, which we don't need specific +// support for in OnceAction because std::function deals with them). +template +using call_result_t = decltype(std::declval()(std::declval()...)); + +template +struct is_callable_r_impl : std::false_type {}; + +// Specialize the struct for those template arguments where call_result_t is +// well-formed. When it's not, the generic template above is chosen, resulting +// in std::false_type. +template +struct is_callable_r_impl>, R, F, Args...> + : std::conditional< + std::is_void::value, // + std::true_type, // + is_implicitly_convertible, R>>::type {}; + +// Like std::is_invocable_r from C++17, but works only for objects with call +// operators. See the note on call_result_t. +template +using is_callable_r = is_callable_r_impl; + +// Like std::as_const from C++17. +template +typename std::add_const::type& as_const(T& t) { + return t; +} + +} // namespace internal + +// Specialized for function types below. +template +class OnceAction; + +// An action that can only be used once. +// +// This is accepted by WillOnce, which doesn't require the underlying action to +// be copy-constructible (only move-constructible), and promises to invoke it as +// an rvalue reference. This allows the action to work with move-only types like +// std::move_only_function in a type-safe manner. +// +// For example: +// +// // Assume we have some API that needs to accept a unique pointer to some +// // non-copyable object Foo. +// void AcceptUniquePointer(std::unique_ptr foo); +// +// // We can define an action that provides a Foo to that API. Because It +// // has to give away its unique pointer, it must not be called more than +// // once, so its call operator is &&-qualified. +// struct ProvideFoo { +// std::unique_ptr foo; +// +// void operator()() && { +// AcceptUniquePointer(std::move(Foo)); +// } +// }; +// +// // This action can be used with WillOnce. +// EXPECT_CALL(mock, Call) +// .WillOnce(ProvideFoo{std::make_unique(...)}); +// +// // But a call to WillRepeatedly will fail to compile. This is correct, +// // since the action cannot correctly be used repeatedly. +// EXPECT_CALL(mock, Call) +// .WillRepeatedly(ProvideFoo{std::make_unique(...)}); +// +// A less-contrived example would be an action that returns an arbitrary type, +// whose &&-qualified call operator is capable of dealing with move-only types. +template +class OnceAction final { + private: + // True iff we can use the given callable type (or lvalue reference) directly + // via StdFunctionAdaptor. + template + using IsDirectlyCompatible = internal::conjunction< + // It must be possible to capture the callable in StdFunctionAdaptor. + std::is_constructible::type, Callable>, + // The callable must be compatible with our signature. + internal::is_callable_r::type, + Args...>>; + + // True iff we can use the given callable type via StdFunctionAdaptor once we + // ignore incoming arguments. + template + using IsCompatibleAfterIgnoringArguments = internal::conjunction< + // It must be possible to capture the callable in a lambda. + std::is_constructible::type, Callable>, + // The callable must be invocable with zero arguments, returning something + // convertible to Result. + internal::is_callable_r::type>>; + + public: + // Construct from a callable that is directly compatible with our mocked + // signature: it accepts our function type's arguments and returns something + // convertible to our result type. + template ::type>>, + IsDirectlyCompatible> // + ::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + : function_(StdFunctionAdaptor::type>( + {}, std::forward(callable))) {} + + // As above, but for a callable that ignores the mocked function's arguments. + template ::type>>, + // Exclude callables for which the overload above works. + // We'd rather provide the arguments if possible. + internal::negation>, + IsCompatibleAfterIgnoringArguments>::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + // Call the constructor above with a callable + // that ignores the input arguments. + : OnceAction(IgnoreIncomingArguments::type>{ + std::forward(callable)}) {} + + // We are naturally copyable because we store only an std::function, but + // semantically we should not be copyable. + OnceAction(const OnceAction&) = delete; + OnceAction& operator=(const OnceAction&) = delete; + OnceAction(OnceAction&&) = default; + + // Invoke the underlying action callable with which we were constructed, + // handing it the supplied arguments. + Result Call(Args... args) && { + return function_(std::forward(args)...); + } + + private: + // An adaptor that wraps a callable that is compatible with our signature and + // being invoked as an rvalue reference so that it can be used as an + // StdFunctionAdaptor. This throws away type safety, but that's fine because + // this is only used by WillOnce, which we know calls at most once. + // + // Once we have something like std::move_only_function from C++23, we can do + // away with this. + template + class StdFunctionAdaptor final { + public: + // A tag indicating that the (otherwise universal) constructor is accepting + // the callable itself, instead of e.g. stealing calls for the move + // constructor. + struct CallableTag final {}; + + template + explicit StdFunctionAdaptor(CallableTag, F&& callable) + : callable_(std::make_shared(std::forward(callable))) {} + + // Rather than explicitly returning Result, we return whatever the wrapped + // callable returns. This allows for compatibility with existing uses like + // the following, when the mocked function returns void: + // + // EXPECT_CALL(mock_fn_, Call) + // .WillOnce([&] { + // [...] + // return 0; + // }); + // + // Such a callable can be turned into std::function. If we use an + // explicit return type of Result here then it *doesn't* work with + // std::function, because we'll get a "void function should not return a + // value" error. + // + // We need not worry about incompatible result types because the SFINAE on + // OnceAction already checks this for us. std::is_invocable_r_v itself makes + // the same allowance for void result types. + template + internal::call_result_t operator()( + ArgRefs&&... args) const { + return std::move(*callable_)(std::forward(args)...); + } + + private: + // We must put the callable on the heap so that we are copyable, which + // std::function needs. + std::shared_ptr callable_; + }; + + // An adaptor that makes a callable that accepts zero arguments callable with + // our mocked arguments. + template + struct IgnoreIncomingArguments { + internal::call_result_t operator()(Args&&...) { + return std::move(callable)(); + } + + Callable callable; + }; + + std::function function_; +}; + +// When an unexpected function call is encountered, Google Mock will +// let it return a default value if the user has specified one for its +// return type, or if the return type has a built-in default value; +// otherwise Google Mock won't know what value to return and will have +// to abort the process. +// +// The DefaultValue class allows a user to specify the +// default value for a type T that is both copyable and publicly +// destructible (i.e. anything that can be used as a function return +// type). The usage is: +// +// // Sets the default value for type T to be foo. +// DefaultValue::Set(foo); +template +class DefaultValue { + public: + // Sets the default value for type T; requires T to be + // copy-constructable and have a public destructor. + static void Set(T x) { + delete producer_; + producer_ = new FixedValueProducer(x); + } + + // Provides a factory function to be called to generate the default value. + // This method can be used even if T is only move-constructible, but it is not + // limited to that case. + typedef T (*FactoryFunction)(); + static void SetFactory(FactoryFunction factory) { + delete producer_; + producer_ = new FactoryValueProducer(factory); + } + + // Unsets the default value for type T. + static void Clear() { + delete producer_; + producer_ = nullptr; + } + + // Returns true if and only if the user has set the default value for type T. + static bool IsSet() { return producer_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T if the user has set one; + // otherwise returns the built-in default value. Requires that Exists() + // is true, which ensures that the return value is well-defined. + static T Get() { + return producer_ == nullptr ? internal::BuiltInDefaultValue::Get() + : producer_->Produce(); + } + + private: + class ValueProducer { + public: + virtual ~ValueProducer() = default; + virtual T Produce() = 0; + }; + + class FixedValueProducer : public ValueProducer { + public: + explicit FixedValueProducer(T value) : value_(value) {} + T Produce() override { return value_; } + + private: + const T value_; + FixedValueProducer(const FixedValueProducer&) = delete; + FixedValueProducer& operator=(const FixedValueProducer&) = delete; + }; + + class FactoryValueProducer : public ValueProducer { + public: + explicit FactoryValueProducer(FactoryFunction factory) + : factory_(factory) {} + T Produce() override { return factory_(); } + + private: + const FactoryFunction factory_; + FactoryValueProducer(const FactoryValueProducer&) = delete; + FactoryValueProducer& operator=(const FactoryValueProducer&) = delete; + }; + + static ValueProducer* producer_; +}; + +// This partial specialization allows a user to set default values for +// reference types. +template +class DefaultValue { + public: + // Sets the default value for type T&. + static void Set(T& x) { // NOLINT + address_ = &x; + } + + // Unsets the default value for type T&. + static void Clear() { address_ = nullptr; } + + // Returns true if and only if the user has set the default value for type T&. + static bool IsSet() { return address_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T& if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T& Get() { + return address_ == nullptr ? internal::BuiltInDefaultValue::Get() + : *address_; + } + + private: + static T* address_; +}; + +// This specialization allows DefaultValue::Get() to +// compile. +template <> +class DefaultValue { + public: + static bool Exists() { return true; } + static void Get() {} +}; + +// Points to the user-set default value for type T. +template +typename DefaultValue::ValueProducer* DefaultValue::producer_ = nullptr; + +// Points to the user-set default value for type T&. +template +T* DefaultValue::address_ = nullptr; + +// Implement this interface to define an action for function type F. +template +class ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + ActionInterface() = default; + virtual ~ActionInterface() = default; + + // Performs the action. This method is not const, as in general an + // action can have side effects and be stateful. For example, a + // get-the-next-element-from-the-collection action will need to + // remember the current element. + virtual Result Perform(const ArgumentTuple& args) = 0; + + private: + ActionInterface(const ActionInterface&) = delete; + ActionInterface& operator=(const ActionInterface&) = delete; +}; + +template +class Action; + +// An Action is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function of type +// R(Args...) is called. The implementation of Action is just a +// std::shared_ptr to const ActionInterface. Don't inherit from Action! You +// can view an object implementing ActionInterface as a concrete action +// (including its current state), and an Action object as a handle to it. +template +class Action { + private: + using F = R(Args...); + + // Adapter class to allow constructing Action from a legacy ActionInterface. + // New code should create Actions from functors instead. + struct ActionAdapter { + // Adapter must be copyable to satisfy std::function requirements. + ::std::shared_ptr> impl_; + + template + typename internal::Function::Result operator()(InArgs&&... args) { + return impl_->Perform( + ::std::forward_as_tuple(::std::forward(args)...)); + } + }; + + template + using IsCompatibleFunctor = std::is_constructible, G>; + + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + // Constructs a null Action. Needed for storing Action objects in + // STL containers. + Action() = default; + + // Construct an Action from a specified callable. + // This cannot take std::function directly, because then Action would not be + // directly constructible from lambda (it would require two conversions). + template < + typename G, + typename = typename std::enable_if, std::is_constructible, + G>>::value>::type> + Action(G&& fun) { // NOLINT + Init(::std::forward(fun), IsCompatibleFunctor()); + } + + // Constructs an Action from its implementation. + explicit Action(ActionInterface* impl) + : fun_(ActionAdapter{::std::shared_ptr>(impl)}) {} + + // This constructor allows us to turn an Action object into an + // Action, as long as F's arguments can be implicitly converted + // to Func's and Func's return type can be implicitly converted to F's. + template + Action(const Action& action) // NOLINT + : fun_(action.fun_) {} + + // Returns true if and only if this is the DoDefault() action. + bool IsDoDefault() const { return fun_ == nullptr; } + + // Performs the action. Note that this method is const even though + // the corresponding method in ActionInterface is not. The reason + // is that a const Action means that it cannot be re-bound to + // another concrete action, not that the concrete action it binds to + // cannot change state. (Think of the difference between a const + // pointer and a pointer to const.) + Result Perform(ArgumentTuple args) const { + if (IsDoDefault()) { + internal::IllegalDoDefault(__FILE__, __LINE__); + } + return internal::Apply(fun_, ::std::move(args)); + } + + // An action can be used as a OnceAction, since it's obviously safe to call it + // once. + operator OnceAction() const { // NOLINT + // Return a OnceAction-compatible callable that calls Perform with the + // arguments it is provided. We could instead just return fun_, but then + // we'd need to handle the IsDoDefault() case separately. + struct OA { + Action action; + + R operator()(Args... args) && { + return action.Perform( + std::forward_as_tuple(std::forward(args)...)); + } + }; + + return OA{*this}; + } + + private: + template + friend class Action; + + template + void Init(G&& g, ::std::true_type) { + fun_ = ::std::forward(g); + } + + template + void Init(G&& g, ::std::false_type) { + fun_ = IgnoreArgs::type>{::std::forward(g)}; + } + + template + struct IgnoreArgs { + template + Result operator()(const InArgs&...) const { + return function_impl(); + } + template + Result operator()(const InArgs&...) { + return function_impl(); + } + + FunctionImpl function_impl; + }; + + // fun_ is an empty function if and only if this is the DoDefault() action. + ::std::function fun_; +}; + +// The PolymorphicAction class template makes it easy to implement a +// polymorphic action (i.e. an action that can be used in mock +// functions of than one type, e.g. Return()). +// +// To define a polymorphic action, a user first provides a COPYABLE +// implementation class that has a Perform() method template: +// +// class FooAction { +// public: +// template +// Result Perform(const ArgumentTuple& args) const { +// // Processes the arguments and returns a result, using +// // std::get(args) to get the N-th (0-based) argument in the tuple. +// } +// ... +// }; +// +// Then the user creates the polymorphic action using +// MakePolymorphicAction(object) where object has type FooAction. See +// the definition of Return(void) and SetArgumentPointee(value) for +// complete examples. +template +class PolymorphicAction { + public: + explicit PolymorphicAction(const Impl& impl) : impl_(impl) {} + + template + operator Action() const { + return Action(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + Result Perform(const ArgumentTuple& args) override { + return impl_.template Perform(args); + } + + private: + Impl impl_; + }; + + Impl impl_; +}; + +// Creates an Action from its implementation and returns it. The +// created Action object owns the implementation. +template +Action MakeAction(ActionInterface* impl) { + return Action(impl); +} + +// Creates a polymorphic action from its implementation. This is +// easier to use than the PolymorphicAction constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicAction(foo); +// vs +// PolymorphicAction(foo); +template +inline PolymorphicAction MakePolymorphicAction(const Impl& impl) { + return PolymorphicAction(impl); +} + +namespace internal { + +// Helper struct to specialize ReturnAction to execute a move instead of a copy +// on return. Useful for move-only types, but could be used on any type. +template +struct ByMoveWrapper { + explicit ByMoveWrapper(T value) : payload(std::move(value)) {} + T payload; +}; + +// The general implementation of Return(R). Specializations follow below. +template +class ReturnAction final { + public: + explicit ReturnAction(R value) : value_(std::move(value)) {} + + template >, // + negation>, // + std::is_convertible, // + std::is_move_constructible>::value>::type> + operator OnceAction() && { // NOLINT + return Impl(std::move(value_)); + } + + template >, // + negation>, // + std::is_convertible, // + std::is_copy_constructible>::value>::type> + operator Action() const { // NOLINT + return Impl(value_); + } + + private: + // Implements the Return(x) action for a mock function that returns type U. + template + class Impl final { + public: + // The constructor used when the return value is allowed to move from the + // input value (i.e. we are converting to OnceAction). + explicit Impl(R&& input_value) + : state_(new State(std::move(input_value))) {} + + // The constructor used when the return value is not allowed to move from + // the input value (i.e. we are converting to Action). + explicit Impl(const R& input_value) : state_(new State(input_value)) {} + + U operator()() && { return std::move(state_->value); } + U operator()() const& { return state_->value; } + + private: + // We put our state on the heap so that the compiler-generated copy/move + // constructors work correctly even when U is a reference-like type. This is + // necessary only because we eagerly create State::value (see the note on + // that symbol for details). If we instead had only the input value as a + // member then the default constructors would work fine. + // + // For example, when R is std::string and U is std::string_view, value is a + // reference to the string backed by input_value. The copy constructor would + // copy both, so that we wind up with a new input_value object (with the + // same contents) and a reference to the *old* input_value object rather + // than the new one. + struct State { + explicit State(const R& input_value_in) + : input_value(input_value_in), + // Make an implicit conversion to Result before initializing the U + // object we store, avoiding calling any explicit constructor of U + // from R. + // + // This simulates the language rules: a function with return type U + // that does `return R()` requires R to be implicitly convertible to + // U, and uses that path for the conversion, even U Result has an + // explicit constructor from R. + value(ImplicitCast_(internal::as_const(input_value))) {} + + // As above, but for the case where we're moving from the ReturnAction + // object because it's being used as a OnceAction. + explicit State(R&& input_value_in) + : input_value(std::move(input_value_in)), + // For the same reason as above we make an implicit conversion to U + // before initializing the value. + // + // Unlike above we provide the input value as an rvalue to the + // implicit conversion because this is a OnceAction: it's fine if it + // wants to consume the input value. + value(ImplicitCast_(std::move(input_value))) {} + + // A copy of the value originally provided by the user. We retain this in + // addition to the value of the mock function's result type below in case + // the latter is a reference-like type. See the std::string_view example + // in the documentation on Return. + R input_value; + + // The value we actually return, as the type returned by the mock function + // itself. + // + // We eagerly initialize this here, rather than lazily doing the implicit + // conversion automatically each time Perform is called, for historical + // reasons: in 2009-11, commit a070cbd91c (Google changelist 13540126) + // made the Action conversion operator eagerly convert the R value to + // U, but without keeping the R alive. This broke the use case discussed + // in the documentation for Return, making reference-like types such as + // std::string_view not safe to use as U where the input type R is a + // value-like type such as std::string. + // + // The example the commit gave was not very clear, nor was the issue + // thread (https://github.com/google/googlemock/issues/86), but it seems + // the worry was about reference-like input types R that flatten to a + // value-like type U when being implicitly converted. An example of this + // is std::vector::reference, which is often a proxy type with an + // reference to the underlying vector: + // + // // Helper method: have the mock function return bools according + // // to the supplied script. + // void SetActions(MockFunction& mock, + // const std::vector& script) { + // for (size_t i = 0; i < script.size(); ++i) { + // EXPECT_CALL(mock, Call(i)).WillOnce(Return(script[i])); + // } + // } + // + // TEST(Foo, Bar) { + // // Set actions using a temporary vector, whose operator[] + // // returns proxy objects that references that will be + // // dangling once the call to SetActions finishes and the + // // vector is destroyed. + // MockFunction mock; + // SetActions(mock, {false, true}); + // + // EXPECT_FALSE(mock.AsStdFunction()(0)); + // EXPECT_TRUE(mock.AsStdFunction()(1)); + // } + // + // This eager conversion helps with a simple case like this, but doesn't + // fully make these types work in general. For example the following still + // uses a dangling reference: + // + // TEST(Foo, Baz) { + // MockFunction()> mock; + // + // // Return the same vector twice, and then the empty vector + // // thereafter. + // auto action = Return(std::initializer_list{ + // "taco", "burrito", + // }); + // + // EXPECT_CALL(mock, Call) + // .WillOnce(action) + // .WillOnce(action) + // .WillRepeatedly(Return(std::vector{})); + // + // EXPECT_THAT(mock.AsStdFunction()(), + // ElementsAre("taco", "burrito")); + // EXPECT_THAT(mock.AsStdFunction()(), + // ElementsAre("taco", "burrito")); + // EXPECT_THAT(mock.AsStdFunction()(), IsEmpty()); + // } + // + U value; + }; + + const std::shared_ptr state_; + }; + + R value_; +}; + +// A specialization of ReturnAction when R is ByMoveWrapper for some T. +// +// This version applies the type system-defeating hack of moving from T even in +// the const call operator, checking at runtime that it isn't called more than +// once, since the user has declared their intent to do so by using ByMove. +template +class ReturnAction> final { + public: + explicit ReturnAction(ByMoveWrapper wrapper) + : state_(new State(std::move(wrapper.payload))) {} + + T operator()() const { + GTEST_CHECK_(!state_->called) + << "A ByMove() action must be performed at most once."; + + state_->called = true; + return std::move(state_->value); + } + + private: + // We store our state on the heap so that we are copyable as required by + // Action, despite the fact that we are stateful and T may not be copyable. + struct State { + explicit State(T&& value_in) : value(std::move(value_in)) {} + + T value; + bool called = false; + }; + + const std::shared_ptr state_; +}; + +// Implements the ReturnNull() action. +class ReturnNullAction { + public: + // Allows ReturnNull() to be used in any pointer-returning function. In C++11 + // this is enforced by returning nullptr, and in non-C++11 by asserting a + // pointer type on compile time. + template + static Result Perform(const ArgumentTuple&) { + return nullptr; + } +}; + +// Implements the Return() action. +class ReturnVoidAction { + public: + // Allows Return() to be used in any void-returning function. + template + static void Perform(const ArgumentTuple&) { + static_assert(std::is_void::value, "Result should be void."); + } +}; + +// Implements the polymorphic ReturnRef(x) action, which can be used +// in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefAction { + public: + // Constructs a ReturnRefAction object from the reference to be returned. + explicit ReturnRefAction(T& ref) : ref_(ref) {} // NOLINT + + // This template type conversion operator allows ReturnRef(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRef(x) when Return(x) + // should be used, and generates some helpful error message. + static_assert(std::is_reference::value, + "use Return instead of ReturnRef to return a value"); + return Action(new Impl(ref_)); + } + + private: + // Implements the ReturnRef(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(T& ref) : ref_(ref) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return ref_; } + + private: + T& ref_; + }; + + T& ref_; +}; + +// Implements the polymorphic ReturnRefOfCopy(x) action, which can be +// used in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefOfCopyAction { + public: + // Constructs a ReturnRefOfCopyAction object from the reference to + // be returned. + explicit ReturnRefOfCopyAction(const T& value) : value_(value) {} // NOLINT + + // This template type conversion operator allows ReturnRefOfCopy(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRefOfCopy(x) when Return(x) + // should be used, and generates some helpful error message. + static_assert(std::is_reference::value, + "use Return instead of ReturnRefOfCopy to return a value"); + return Action(new Impl(value_)); + } + + private: + // Implements the ReturnRefOfCopy(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const T& value) : value_(value) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return value_; } + + private: + T value_; + }; + + const T value_; +}; + +// Implements the polymorphic ReturnRoundRobin(v) action, which can be +// used in any function that returns the element_type of v. +template +class ReturnRoundRobinAction { + public: + explicit ReturnRoundRobinAction(std::vector values) { + GTEST_CHECK_(!values.empty()) + << "ReturnRoundRobin requires at least one element."; + state_->values = std::move(values); + } + + template + T operator()(Args&&...) const { + return state_->Next(); + } + + private: + struct State { + T Next() { + T ret_val = values[i++]; + if (i == values.size()) i = 0; + return ret_val; + } + + std::vector values; + size_t i = 0; + }; + std::shared_ptr state_ = std::make_shared(); +}; + +// Implements the polymorphic DoDefault() action. +class DoDefaultAction { + public: + // This template type conversion operator allows DoDefault() to be + // used in any function. + template + operator Action() const { + return Action(); + } // NOLINT +}; + +// Implements the Assign action to set a given pointer referent to a +// particular value. +template +class AssignAction { + public: + AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {} + + template + void Perform(const ArgumentTuple& /* args */) const { + *ptr_ = value_; + } + + private: + T1* const ptr_; + const T2 value_; +}; + +#ifndef GTEST_OS_WINDOWS_MOBILE + +// Implements the SetErrnoAndReturn action to simulate return from +// various system calls and libc functions. +template +class SetErrnoAndReturnAction { + public: + SetErrnoAndReturnAction(int errno_value, T result) + : errno_(errno_value), result_(result) {} + template + Result Perform(const ArgumentTuple& /* args */) const { + errno = errno_; + return result_; + } + + private: + const int errno_; + const T result_; +}; + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetArgumentPointee(x) action for any function +// whose N-th argument (0-based) is a pointer to x's type. +template +struct SetArgumentPointeeAction { + A value; + + template + void operator()(const Args&... args) const { + *::std::get(std::tie(args...)) = value; + } +}; + +// Implements the Invoke(object_ptr, &Class::Method) action. +template +struct InvokeMethodAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + template + auto operator()(Args&&... args) const + -> decltype((obj_ptr->*method_ptr)(std::forward(args)...)) { + return (obj_ptr->*method_ptr)(std::forward(args)...); + } +}; + +// Implements the InvokeWithoutArgs(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. InvokeWithoutArgs(f) can be used as an +// Action as long as f's type is compatible with F. +template +struct InvokeWithoutArgsAction { + FunctionImpl function_impl; + + // Allows InvokeWithoutArgs(f) to be used as any action whose type is + // compatible with f. + template + auto operator()(const Args&...) -> decltype(function_impl()) { + return function_impl(); + } +}; + +// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action. +template +struct InvokeMethodWithoutArgsAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + using ReturnType = + decltype((std::declval()->*std::declval())()); + + template + ReturnType operator()(const Args&...) const { + return (obj_ptr->*method_ptr)(); + } +}; + +// Implements the IgnoreResult(action) action. +template +class IgnoreResultAction { + public: + explicit IgnoreResultAction(const A& action) : action_(action) {} + + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename internal::Function::Result Result; + + // Asserts at compile time that F returns void. + static_assert(std::is_void::value, "Result type should be void."); + + return Action(new Impl(action_)); + } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const A& action) : action_(action) {} + + void Perform(const ArgumentTuple& args) override { + // Performs the action and ignores its result. + action_.Perform(args); + } + + private: + // Type OriginalFunction is the same as F except that its return + // type is IgnoredValue. + typedef + typename internal::Function::MakeResultIgnoredValue OriginalFunction; + + const Action action_; + }; + + const A action_; +}; + +template +struct WithArgsAction { + InnerAction inner_action; + + // The signature of the function as seen by the inner action, given an out + // action with the given result and argument types. + template + using InnerSignature = + R(typename std::tuple_element>::type...); + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>...)>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + struct OA { + OnceAction> inner_action; + + R operator()(Args&&... args) && { + return std::move(inner_action) + .Call(std::get( + std::forward_as_tuple(std::forward(args)...))...); + } + }; + + return OA{std::move(inner_action)}; + } + + // As above, but in the case where we want to create a OnceAction from a const + // WithArgsAction. This is fine as long as the inner action doesn't need to + // move any of its state to create a OnceAction. + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>...)>>::value, + int>::type = 0> + operator OnceAction() const& { // NOLINT + struct OA { + OnceAction> inner_action; + + R operator()(Args&&... args) && { + return std::move(inner_action) + .Call(std::get( + std::forward_as_tuple(std::forward(args)...))...); + } + }; + + return OA{inner_action}; + } + + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>...)>>::value, + int>::type = 0> + operator Action() const { // NOLINT + Action> converted(inner_action); + + return [converted](Args&&... args) -> R { + return converted.Perform(std::forward_as_tuple( + std::get(std::forward_as_tuple(std::forward(args)...))...)); + }; + } +}; + +template +class DoAllAction; + +// Base case: only a single action. +template +class DoAllAction { + public: + struct UserConstructorTag {}; + + template + explicit DoAllAction(UserConstructorTag, T&& action) + : final_action_(std::forward(action)) {} + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + // We support conversion to OnceAction whenever the sub-action does. + template >::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return std::move(final_action_); + } + + // We also support conversion to OnceAction whenever the sub-action supports + // conversion to Action (since any Action can also be a OnceAction). + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + negation< + std::is_convertible>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return Action(std::move(final_action_)); + } + + // We support conversion to Action whenever the sub-action does. + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>::value, + int>::type = 0> + operator Action() const { // NOLINT + return final_action_; + } + + private: + FinalAction final_action_; +}; + +// Recursive case: support N actions by calling the initial action and then +// calling through to the base class containing N-1 actions. +template +class DoAllAction + : private DoAllAction { + private: + using Base = DoAllAction; + + // The type of reference that should be provided to an initial action for a + // mocked function parameter of type T. + // + // There are two quirks here: + // + // * Unlike most forwarding functions, we pass scalars through by value. + // This isn't strictly necessary because an lvalue reference would work + // fine too and be consistent with other non-reference types, but it's + // perhaps less surprising. + // + // For example if the mocked function has signature void(int), then it + // might seem surprising for the user's initial action to need to be + // convertible to Action. This is perhaps less + // surprising for a non-scalar type where there may be a performance + // impact, or it might even be impossible, to pass by value. + // + // * More surprisingly, `const T&` is often not a const reference type. + // By the reference collapsing rules in C++17 [dcl.ref]/6, if T refers to + // U& or U&& for some non-scalar type U, then InitialActionArgType is + // U&. In other words, we may hand over a non-const reference. + // + // So for example, given some non-scalar type Obj we have the following + // mappings: + // + // T InitialActionArgType + // ------- ----------------------- + // Obj const Obj& + // Obj& Obj& + // Obj&& Obj& + // const Obj const Obj& + // const Obj& const Obj& + // const Obj&& const Obj& + // + // In other words, the initial actions get a mutable view of an non-scalar + // argument if and only if the mock function itself accepts a non-const + // reference type. They are never given an rvalue reference to an + // non-scalar type. + // + // This situation makes sense if you imagine use with a matcher that is + // designed to write through a reference. For example, if the caller wants + // to fill in a reference argument and then return a canned value: + // + // EXPECT_CALL(mock, Call) + // .WillOnce(DoAll(SetArgReferee<0>(17), Return(19))); + // + template + using InitialActionArgType = + typename std::conditional::value, T, const T&>::type; + + public: + struct UserConstructorTag {}; + + template + explicit DoAllAction(UserConstructorTag, T&& initial_action, + U&&... other_actions) + : Base({}, std::forward(other_actions)...), + initial_action_(std::forward(initial_action)) {} + + // We support conversion to OnceAction whenever both the initial action and + // the rest support conversion to OnceAction. + template < + typename R, typename... Args, + typename std::enable_if< + conjunction...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + OnceAction...)> initial_action; + OnceAction remaining_actions; + + R operator()(Args... args) && { + std::move(initial_action) + .Call(static_cast>(args)...); + + return std::move(remaining_actions).Call(std::forward(args)...); + } + }; + + return OA{ + std::move(initial_action_), + std::move(static_cast(*this)), + }; + } + + // We also support conversion to OnceAction whenever the initial action + // supports conversion to Action (since any Action can also be a OnceAction). + // + // The remaining sub-actions must also be compatible, but we don't need to + // special case them because the base class deals with them. + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + negation...)>>>, + std::is_convertible...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return DoAll( + Action...)>(std::move(initial_action_)), + std::move(static_cast(*this))); + } + + // We support conversion to Action whenever both the initial action and the + // rest support conversion to Action. + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + std::is_convertible...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator Action() const { // NOLINT + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + Action...)> initial_action; + Action remaining_actions; + + R operator()(Args... args) const { + initial_action.Perform(std::forward_as_tuple( + static_cast>(args)...)); + + return remaining_actions.Perform( + std::forward_as_tuple(std::forward(args)...)); + } + }; + + return OA{ + initial_action_, + static_cast(*this), + }; + } + + private: + InitialAction initial_action_; +}; + +template +struct ReturnNewAction { + T* operator()() const { + return internal::Apply( + [](const Params&... unpacked_params) { + return new T(unpacked_params...); + }, + params); + } + std::tuple params; +}; + +template +struct ReturnArgAction { + template ::type> + auto operator()(Args&&... args) const -> decltype(std::get( + std::forward_as_tuple(std::forward(args)...))) { + return std::get(std::forward_as_tuple(std::forward(args)...)); + } +}; + +template +struct SaveArgAction { + Ptr pointer; + + template + void operator()(const Args&... args) const { + *pointer = std::get(std::tie(args...)); + } +}; + +template +struct SaveArgByMoveAction { + Ptr pointer; + + template + void operator()(Args&&... args) const { + *pointer = std::move(std::get(std::tie(args...))); + } +}; + +template +struct SaveArgPointeeAction { + Ptr pointer; + + template + void operator()(const Args&... args) const { + *pointer = *std::get(std::tie(args...)); + } +}; + +template +struct SetArgRefereeAction { + T value; + + template + void operator()(Args&&... args) const { + using argk_type = + typename ::std::tuple_element>::type; + static_assert(std::is_lvalue_reference::value, + "Argument must be a reference type."); + std::get(std::tie(args...)) = value; + } +}; + +template +struct SetArrayArgumentAction { + I1 first; + I2 last; + + template + void operator()(const Args&... args) const { + auto value = std::get(std::tie(args...)); + for (auto it = first; it != last; ++it, (void)++value) { + *value = *it; + } + } +}; + +template +struct DeleteArgAction { + template + void operator()(const Args&... args) const { + delete std::get(std::tie(args...)); + } +}; + +template +struct ReturnPointeeAction { + Ptr pointer; + template + auto operator()(const Args&...) const -> decltype(*pointer) { + return *pointer; + } +}; + +#if GTEST_HAS_EXCEPTIONS +template +struct ThrowAction { + T exception; + // We use a conversion operator to adapt to any return type. + template + operator Action() const { // NOLINT + T copy = exception; + return [copy](Args...) -> R { throw copy; }; + } +}; +struct RethrowAction { + std::exception_ptr exception; + template + operator Action() const { // NOLINT + return [ex = exception](Args...) -> R { std::rethrow_exception(ex); }; + } +}; +#endif // GTEST_HAS_EXCEPTIONS + +} // namespace internal + +// An Unused object can be implicitly constructed from ANY value. +// This is handy when defining actions that ignore some or all of the +// mock function arguments. For example, given +// +// MOCK_METHOD3(Foo, double(const string& label, double x, double y)); +// MOCK_METHOD3(Bar, double(int index, double x, double y)); +// +// instead of +// +// double DistanceToOriginWithLabel(const string& label, double x, double y) { +// return sqrt(x*x + y*y); +// } +// double DistanceToOriginWithIndex(int index, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)) +// .WillOnce(Invoke(DistanceToOriginWithLabel)); +// EXPECT_CALL(mock, Bar(5, _, _)) +// .WillOnce(Invoke(DistanceToOriginWithIndex)); +// +// you could write +// +// // We can declare any uninteresting argument as Unused. +// double DistanceToOrigin(Unused, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin)); +// EXPECT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin)); +typedef internal::IgnoredValue Unused; + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. All but the last action will have a readonly view of the +// arguments. +template +internal::DoAllAction::type...> DoAll( + Action&&... action) { + return internal::DoAllAction::type...>( + {}, std::forward(action)...); +} + +// WithArg(an_action) creates an action that passes the k-th +// (0-based) argument of the mock function to an_action and performs +// it. It adapts an action accepting one argument to one that accepts +// multiple arguments. For convenience, we also provide +// WithArgs(an_action) (defined below) as a synonym. +template +internal::WithArgsAction::type, k> WithArg( + InnerAction&& action) { + return {std::forward(action)}; +} + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. +template +internal::WithArgsAction::type, k, ks...> +WithArgs(InnerAction&& action) { + return {std::forward(action)}; +} + +// WithoutArgs(inner_action) can be used in a mock function with a +// non-empty argument list to perform inner_action, which takes no +// argument. In other words, it adapts an action accepting no +// argument to one that accepts (and ignores) arguments. +template +internal::WithArgsAction::type> WithoutArgs( + InnerAction&& action) { + return {std::forward(action)}; +} + +// Creates an action that returns a value. +// +// The returned type can be used with a mock function returning a non-void, +// non-reference type U as follows: +// +// * If R is convertible to U and U is move-constructible, then the action can +// be used with WillOnce. +// +// * If const R& is convertible to U and U is copy-constructible, then the +// action can be used with both WillOnce and WillRepeatedly. +// +// The mock expectation contains the R value from which the U return value is +// constructed (a move/copy of the argument to Return). This means that the R +// value will survive at least until the mock object's expectations are cleared +// or the mock object is destroyed, meaning that U can safely be a +// reference-like type such as std::string_view: +// +// // The mock function returns a view of a copy of the string fed to +// // Return. The view is valid even after the action is performed. +// MockFunction mock; +// EXPECT_CALL(mock, Call).WillOnce(Return(std::string("taco"))); +// const std::string_view result = mock.AsStdFunction()(); +// EXPECT_EQ("taco", result); +// +template +internal::ReturnAction Return(R value) { + return internal::ReturnAction(std::move(value)); +} + +// Creates an action that returns NULL. +inline PolymorphicAction ReturnNull() { + return MakePolymorphicAction(internal::ReturnNullAction()); +} + +// Creates an action that returns from a void function. +inline PolymorphicAction Return() { + return MakePolymorphicAction(internal::ReturnVoidAction()); +} + +// Creates an action that returns the reference to a variable. +template +inline internal::ReturnRefAction ReturnRef(R& x) { // NOLINT + return internal::ReturnRefAction(x); +} + +// Prevent using ReturnRef on reference to temporary. +template +internal::ReturnRefAction ReturnRef(R&&) = delete; + +// Creates an action that returns the reference to a copy of the +// argument. The copy is created when the action is constructed and +// lives as long as the action. +template +inline internal::ReturnRefOfCopyAction ReturnRefOfCopy(const R& x) { + return internal::ReturnRefOfCopyAction(x); +} + +// DEPRECATED: use Return(x) directly with WillOnce. +// +// Modifies the parent action (a Return() action) to perform a move of the +// argument instead of a copy. +// Return(ByMove()) actions can only be executed once and will assert this +// invariant. +template +internal::ByMoveWrapper ByMove(R x) { + return internal::ByMoveWrapper(std::move(x)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template +internal::ReturnRoundRobinAction ReturnRoundRobin(std::vector vals) { + return internal::ReturnRoundRobinAction(std::move(vals)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template +internal::ReturnRoundRobinAction ReturnRoundRobin( + std::initializer_list vals) { + return internal::ReturnRoundRobinAction(std::vector(vals)); +} + +// Creates an action that does the default action for the give mock function. +inline internal::DoDefaultAction DoDefault() { + return internal::DoDefaultAction(); +} + +// Creates an action that sets the variable pointed by the N-th +// (0-based) function argument to 'value'. +template +internal::SetArgumentPointeeAction SetArgPointee(T value) { + return {std::move(value)}; +} + +// The following version is DEPRECATED. +template +internal::SetArgumentPointeeAction SetArgumentPointee(T value) { + return {std::move(value)}; +} + +// Creates an action that sets a pointer referent to a given value. +template +PolymorphicAction> Assign(T1* ptr, T2 val) { + return MakePolymorphicAction(internal::AssignAction(ptr, val)); +} + +#ifndef GTEST_OS_WINDOWS_MOBILE + +// Creates an action that sets errno and returns the appropriate error. +template +PolymorphicAction> SetErrnoAndReturn( + int errval, T result) { + return MakePolymorphicAction( + internal::SetErrnoAndReturnAction(errval, result)); +} + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Various overloads for Invoke(). + +// Legacy function. +// Actions can now be implicitly constructed from callables. No need to create +// wrapper objects. +// This function exists for backwards compatibility. +template +typename std::decay::type Invoke(FunctionImpl&& function_impl) { + return std::forward(function_impl); +} + +// Creates an action that invokes the given method on the given object +// with the mock function's arguments. +template +internal::InvokeMethodAction Invoke(Class* obj_ptr, + MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that invokes 'function_impl' with no argument. +template +internal::InvokeWithoutArgsAction::type> +InvokeWithoutArgs(FunctionImpl function_impl) { + return {std::move(function_impl)}; +} + +// Creates an action that invokes the given method on the given object +// with no argument. +template +internal::InvokeMethodWithoutArgsAction InvokeWithoutArgs( + Class* obj_ptr, MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that performs an_action and throws away its +// result. In other words, it changes the return type of an_action to +// void. an_action MUST NOT return void, or the code won't compile. +template +inline internal::IgnoreResultAction IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef(derived) +// +// N.B. ByRef is redundant with std::ref, std::cref and std::reference_wrapper. +// However, it may still be used for consistency with ByMove(). +template +inline ::std::reference_wrapper ByRef(T& l_value) { // NOLINT + return ::std::reference_wrapper(l_value); +} + +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +template +internal::ReturnNewAction::type...> ReturnNew( + Params&&... params) { + return {std::forward_as_tuple(std::forward(params)...)}; +} + +// Action ReturnArg() returns the k-th argument of the mock function. +template +internal::ReturnArgAction ReturnArg() { + return {}; +} + +// Action SaveArg(pointer) saves the k-th (0-based) argument of the +// mock function to *pointer. +template +internal::SaveArgAction SaveArg(Ptr pointer) { + return {pointer}; +} + +// Action SaveArgByMove(pointer) moves the k-th (0-based) argument of the +// mock function into *pointer. +template +internal::SaveArgByMoveAction SaveArgByMove(Ptr pointer) { + return {pointer}; +} + +// Action SaveArgPointee(pointer) saves the value pointed to +// by the k-th (0-based) argument of the mock function to *pointer. +template +internal::SaveArgPointeeAction SaveArgPointee(Ptr pointer) { + return {pointer}; +} + +// Action SetArgReferee(value) assigns 'value' to the variable +// referenced by the k-th (0-based) argument of the mock function. +template +internal::SetArgRefereeAction::type> SetArgReferee( + T&& value) { + return {std::forward(value)}; +} + +// Action SetArrayArgument(first, last) copies the elements in +// source range [first, last) to the array pointed to by the k-th +// (0-based) argument, which can be either a pointer or an +// iterator. The action does not take ownership of the elements in the +// source range. +template +internal::SetArrayArgumentAction SetArrayArgument(I1 first, + I2 last) { + return {first, last}; +} + +// Action DeleteArg() deletes the k-th (0-based) argument of the mock +// function. +template +internal::DeleteArgAction DeleteArg() { + return {}; +} + +// This action returns the value pointed to by 'pointer'. +template +internal::ReturnPointeeAction ReturnPointee(Ptr pointer) { + return {pointer}; +} + +#if GTEST_HAS_EXCEPTIONS +// Action Throw(exception) can be used in a mock function of any type +// to throw the given exception. Any copyable value can be thrown, +// except for std::exception_ptr, which is likely a mistake if +// thrown directly. +template +typename std::enable_if< + !std::is_base_of::type>::value, + internal::ThrowAction::type>>::type +Throw(T&& exception) { + return {std::forward(exception)}; +} +// Action Rethrow(exception_ptr) can be used in a mock function of any type +// to rethrow any exception_ptr. Note that the same object is thrown each time. +inline internal::RethrowAction Rethrow(std::exception_ptr exception) { + return {std::move(exception)}; +} +#endif // GTEST_HAS_EXCEPTIONS + +namespace internal { + +// A macro from the ACTION* family (defined later in gmock-generated-actions.h) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// Builds an implementation of an Action<> for some particular signature, using +// a class defined by an ACTION* macro. +template +struct ActionImpl; + +template +struct ImplBase { + struct Holder { + // Allows each copy of the Action<> to get to the Impl. + explicit operator const Impl&() const { return *ptr; } + std::shared_ptr ptr; + }; + using type = typename std::conditional::value, + Impl, Holder>::type; +}; + +template +struct ActionImpl : ImplBase::type { + using Base = typename ImplBase::type; + using function_type = R(Args...); + using args_type = std::tuple; + + ActionImpl() = default; // Only defined if appropriate for Base. + explicit ActionImpl(std::shared_ptr impl) : Base{std::move(impl)} {} + + R operator()(Args&&... arg) const { + static constexpr size_t kMaxArgs = + sizeof...(Args) <= 10 ? sizeof...(Args) : 10; + return Apply(std::make_index_sequence{}, + std::make_index_sequence<10 - kMaxArgs>{}, + args_type{std::forward(arg)...}); + } + + template + R Apply(std::index_sequence, std::index_sequence, + const args_type& args) const { + // Impl need not be specific to the signature of action being implemented; + // only the implementing function body needs to have all of the specific + // types instantiated. Up to 10 of the args that are provided by the + // args_type get passed, followed by a dummy of unspecified type for the + // remainder up to 10 explicit args. + static constexpr ExcessiveArg kExcessArg{}; + return static_cast(*this) + .template gmock_PerformImpl< + /*function_type=*/function_type, /*return_type=*/R, + /*args_type=*/args_type, + /*argN_type=*/ + typename std::tuple_element::type...>( + /*args=*/args, std::get(args)..., + ((void)excess_id, kExcessArg)...); + } +}; + +// Stores a default-constructed Impl as part of the Action<>'s +// std::function<>. The Impl should be trivial to copy. +template +::testing::Action MakeAction() { + return ::testing::Action(ActionImpl()); +} + +// Stores just the one given instance of Impl. +template +::testing::Action MakeAction(std::shared_ptr impl) { + return ::testing::Action(ActionImpl(std::move(impl))); +} + +#define GMOCK_INTERNAL_ARG_UNUSED(i, data, el) \ + , [[maybe_unused]] const arg##i##_type& arg##i +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_ \ + [[maybe_unused]] const args_type& args GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_ARG_UNUSED, , 10) + +#define GMOCK_INTERNAL_ARG(i, data, el) , const arg##i##_type& arg##i +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_ \ + const args_type& args GMOCK_PP_REPEAT(GMOCK_INTERNAL_ARG, , 10) + +#define GMOCK_INTERNAL_TEMPLATE_ARG(i, data, el) , typename arg##i##_type +#define GMOCK_ACTION_TEMPLATE_ARGS_NAMES_ \ + GMOCK_PP_TAIL(GMOCK_PP_REPEAT(GMOCK_INTERNAL_TEMPLATE_ARG, , 10)) + +#define GMOCK_INTERNAL_TYPENAME_PARAM(i, data, param) , typename param##_type +#define GMOCK_ACTION_TYPENAME_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPENAME_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_PARAM(i, data, param) , param##_type +#define GMOCK_ACTION_TYPE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_GVALUE_PARAM(i, data, param) \ + , param##_type gmock_p##i +#define GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_GVALUE_PARAM(i, data, param) \ + , std::forward(gmock_p##i) +#define GMOCK_ACTION_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_INIT_PARAM(i, data, param) \ + , param(::std::forward(gmock_p##i)) +#define GMOCK_ACTION_INIT_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_INIT_PARAM, , params)) + +#define GMOCK_INTERNAL_FIELD_PARAM(i, data, param) param##_type param; +#define GMOCK_ACTION_FIELD_PARAMS_(params) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_FIELD_PARAM, , params) + +#define GMOCK_INTERNAL_ACTION(name, full_name, params) \ + template \ + class full_name { \ + public: \ + explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : impl_(std::make_shared( \ + GMOCK_ACTION_GVALUE_PARAMS_(params))) {} \ + full_name(const full_name&) = default; \ + full_name(full_name&&) noexcept = default; \ + template \ + operator ::testing::Action() const { \ + return ::testing::internal::MakeAction(impl_); \ + } \ + \ + private: \ + class gmock_Impl { \ + public: \ + explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : GMOCK_ACTION_INIT_PARAMS_(params) {} \ + template \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + GMOCK_ACTION_FIELD_PARAMS_(params) \ + }; \ + std::shared_ptr impl_; \ + }; \ + template \ + [[nodiscard]] inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)); \ + template \ + inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) { \ + return full_name( \ + GMOCK_ACTION_GVALUE_PARAMS_(params)); \ + } \ + template \ + template \ + return_type \ + full_name::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +} // namespace internal + +// Similar to GMOCK_INTERNAL_ACTION, but no bound parameters are stored. +#define ACTION(name) \ + class name##Action { \ + public: \ + explicit name##Action() noexcept {} \ + name##Action(const name##Action&) noexcept {} \ + template \ + operator ::testing::Action() const { \ + return ::testing::internal::MakeAction(); \ + } \ + \ + private: \ + class gmock_Impl { \ + public: \ + template \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + }; \ + }; \ + [[nodiscard]] inline name##Action name(); \ + inline name##Action name() { return name##Action(); } \ + template \ + return_type name##Action::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP, (__VA_ARGS__)) + +#define ACTION_P2(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP2, (__VA_ARGS__)) + +#define ACTION_P3(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP3, (__VA_ARGS__)) + +#define ACTION_P4(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP4, (__VA_ARGS__)) + +#define ACTION_P5(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP5, (__VA_ARGS__)) + +#define ACTION_P6(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP6, (__VA_ARGS__)) + +#define ACTION_P7(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP7, (__VA_ARGS__)) + +#define ACTION_P8(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP8, (__VA_ARGS__)) + +#define ACTION_P9(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP9, (__VA_ARGS__)) + +#define ACTION_P10(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP10, (__VA_ARGS__)) + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100 + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-cardinalities.h b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-cardinalities.h new file mode 100644 index 00000000000..533e604f326 --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-cardinalities.h @@ -0,0 +1,159 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include + +#include +#include // NOLINT + +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() = default; + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true if and only if call_count calls will saturate this + // cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a std::shared_ptr +// to const CardinalityInterface. Don't inherit from Cardinality! +class GTEST_API_ Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() = default; + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will saturate this + // cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + + private: + std::shared_ptr impl_; +}; + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-function-mocker.h b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-function-mocker.h new file mode 100644 index 00000000000..d2cb13cd834 --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-function-mocker.h @@ -0,0 +1,519 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements MOCK_METHOD. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ + +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-pp.h" + +namespace testing { +namespace internal { +template +using identity_t = T; + +template +struct ThisRefAdjuster { + template + using AdjustT = typename std::conditional< + std::is_const::type>::value, + typename std::conditional::value, + const T&, const T&&>::type, + typename std::conditional::value, T&, + T&&>::type>::type; + + template + static AdjustT Adjust(const MockType& mock) { + return static_cast>(const_cast(mock)); + } +}; + +constexpr bool PrefixOf(const char* a, const char* b) { + return *a == 0 || (*a == *b && internal::PrefixOf(a + 1, b + 1)); +} + +template +constexpr bool StartsWith(const char (&prefix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(prefix, str); +} + +template +constexpr bool EndsWith(const char (&suffix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(suffix, str + M - N); +} + +template +constexpr bool Equals(const char (&a)[N], const char (&b)[M]) { + return N == M && internal::PrefixOf(a, b); +} + +template +constexpr bool ValidateSpec(const char (&spec)[N]) { + return internal::Equals("const", spec) || + internal::Equals("override", spec) || + internal::Equals("final", spec) || + internal::Equals("noexcept", spec) || + (internal::StartsWith("noexcept(", spec) && + internal::EndsWith(")", spec)) || + internal::Equals("ref(&)", spec) || + internal::Equals("ref(&&)", spec) || + (internal::StartsWith("Calltype(", spec) && + internal::EndsWith(")", spec)); +} + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; +} // namespace testing + +#define MOCK_METHOD(...) \ + GMOCK_INTERNAL_WARNING_PUSH() \ + GMOCK_INTERNAL_WARNING_CLANG(ignored, "-Wunused-member-function") \ + GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__) \ + GMOCK_INTERNAL_WARNING_POP() + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_1(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_2(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \ + GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ()) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \ + GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \ + GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \ + GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \ + (GMOCK_INTERNAL_SIGNATURE(_Ret, _Args))) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_6(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_7(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_WRONG_ARITY(...) \ + static_assert( \ + false, \ + "MOCK_METHOD must be called with 3 or 4 arguments. _Ret, " \ + "_MethodName, _Args and optionally _Spec. _Args and _Spec must be " \ + "enclosed in parentheses. If _Ret is a type with unprotected commas, " \ + "it must also be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Tuple) \ + static_assert( \ + GMOCK_PP_IS_ENCLOSED_PARENS(_Tuple), \ + GMOCK_PP_STRINGIZE(_Tuple) " should be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(_N, ...) \ + static_assert( \ + std::is_function<__VA_ARGS__>::value, \ + "Signature must be a function type, maybe return type contains " \ + "unprotected comma."); \ + static_assert( \ + ::testing::tuple_size::ArgumentTuple>::value == _N, \ + "This method does not take " GMOCK_PP_STRINGIZE( \ + _N) " arguments. Parenthesize all types with unprotected commas.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT, ~, _Spec) + +#define GMOCK_INTERNAL_MOCK_METHOD_IMPL(_N, _MethodName, _Constness, \ + _Override, _Final, _NoexceptSpec, \ + _CallType, _RefSpec, _Signature) \ + typename ::testing::internal::Function::Result \ + GMOCK_INTERNAL_EXPAND(_CallType) \ + _MethodName(GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) \ + _RefSpec _NoexceptSpec GMOCK_PP_IF(_Override, override, ) \ + GMOCK_PP_IF(_Final, final, ) { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .SetOwnerAndName(this, #_MethodName); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .Invoke(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, _Signature, _N)); \ + } \ + ::testing::MockSpec gmock_##_MethodName( \ + GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) _RefSpec { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName).RegisterOwner(this); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .With(GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_ARGUMENT, , _N)); \ + } \ + ::testing::MockSpec gmock_##_MethodName( \ + const ::testing::internal::WithoutMatchers&, \ + GMOCK_PP_IF(_Constness, const, )::testing::internal::Function< \ + GMOCK_PP_REMOVE_PARENS(_Signature)>*) const _RefSpec _NoexceptSpec { \ + return ::testing::internal::ThisRefAdjuster::Adjust(*this) \ + .gmock_##_MethodName(GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N)); \ + } \ + mutable ::testing::FunctionMocker \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) + +#define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__ + +// Valid modifiers. +#define GMOCK_INTERNAL_HAS_CONST(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_OVERRIDE(_Tuple) \ + GMOCK_PP_HAS_COMMA( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_OVERRIDE, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_FINAL(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_FINAL, ~, _Tuple)) + +#define GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT, ~, _Tuple) + +#define GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \ + _elem, ) + +#define GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE, ~, _Tuple) + +#define GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + +#define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple) + +#define GMOCK_INTERNAL_REF_SPEC_IF_REF(_i, _, _elem) \ + GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + +#ifdef GMOCK_INTERNAL_STRICT_SPEC_ASSERT +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + ::testing::internal::ValidateSpec(GMOCK_PP_STRINGIZE(_elem)), \ + "Token \'" GMOCK_PP_STRINGIZE( \ + _elem) "\' cannot be recognized as a valid specification " \ + "modifier. Is a ',' missing?"); +#else +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem))) == 1, \ + GMOCK_PP_STRINGIZE( \ + _elem) " cannot be recognized as a valid specification modifier."); +#endif // GMOCK_INTERNAL_STRICT_SPEC_ASSERT + +// Modifiers implementation. +#define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CONST_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CONST_I_const , + +#define GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_OVERRIDE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_OVERRIDE_I_override , + +#define GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_FINAL_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_FINAL_I_final , + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_NOEXCEPT_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT_I_noexcept , + +#define GMOCK_INTERNAL_DETECT_REF(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_REF_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_REF_I_ref , + +#define GMOCK_INTERNAL_UNPACK_ref(x) x + +#define GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CALLTYPE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CALLTYPE_I_Calltype , + +#define GMOCK_INTERNAL_UNPACK_Calltype(...) __VA_ARGS__ + +// Note: The use of `identity_t` here allows _Ret to represent return types that +// would normally need to be specified in a different way. For example, a method +// returning a function pointer must be written as +// +// fn_ptr_return_t (*method(method_args_t...))(fn_ptr_args_t...) +// +// But we only support placing the return type at the beginning. To handle this, +// we wrap all calls in identity_t, so that a declaration will be expanded to +// +// identity_t method(method_args_t...) +// +// This allows us to work around the syntactic oddities of function/method +// types. +#define GMOCK_INTERNAL_SIGNATURE(_Ret, _Args) \ + ::testing::internal::identity_t( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_TYPE, _, _Args)) + +#define GMOCK_INTERNAL_GET_TYPE(_i, _, _elem) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_elem), GMOCK_PP_REMOVE_PARENS, \ + GMOCK_PP_IDENTITY) \ + (_elem) + +#define GMOCK_INTERNAL_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_FORWARD_ARG(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::std::forward(gmock_a##_i) + +#define GMOCK_INTERNAL_MATCHER_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_MATCHER_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_MATCHER_ARGUMENT(_i, _1, _2) \ + GMOCK_PP_COMMA_IF(_i) \ + gmock_a##_i + +#define GMOCK_INTERNAL_A_MATCHER_ARGUMENT(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::testing::A() + +#define GMOCK_INTERNAL_ARG_O(_i, ...) \ + typename ::testing::internal::Function<__VA_ARGS__>::template Arg<_i>::type + +#define GMOCK_INTERNAL_MATCHER_O(_i, ...) \ + const ::testing::Matcher::template Arg<_i>::type>& + +#define MOCK_METHOD0(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 0, __VA_ARGS__) +#define MOCK_METHOD1(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 1, __VA_ARGS__) +#define MOCK_METHOD2(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 2, __VA_ARGS__) +#define MOCK_METHOD3(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 3, __VA_ARGS__) +#define MOCK_METHOD4(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 4, __VA_ARGS__) +#define MOCK_METHOD5(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 5, __VA_ARGS__) +#define MOCK_METHOD6(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 6, __VA_ARGS__) +#define MOCK_METHOD7(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 7, __VA_ARGS__) +#define MOCK_METHOD8(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 8, __VA_ARGS__) +#define MOCK_METHOD9(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 9, __VA_ARGS__) +#define MOCK_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, , m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T(m, ...) MOCK_METHOD0(m, __VA_ARGS__) +#define MOCK_METHOD1_T(m, ...) MOCK_METHOD1(m, __VA_ARGS__) +#define MOCK_METHOD2_T(m, ...) MOCK_METHOD2(m, __VA_ARGS__) +#define MOCK_METHOD3_T(m, ...) MOCK_METHOD3(m, __VA_ARGS__) +#define MOCK_METHOD4_T(m, ...) MOCK_METHOD4(m, __VA_ARGS__) +#define MOCK_METHOD5_T(m, ...) MOCK_METHOD5(m, __VA_ARGS__) +#define MOCK_METHOD6_T(m, ...) MOCK_METHOD6(m, __VA_ARGS__) +#define MOCK_METHOD7_T(m, ...) MOCK_METHOD7(m, __VA_ARGS__) +#define MOCK_METHOD8_T(m, ...) MOCK_METHOD8(m, __VA_ARGS__) +#define MOCK_METHOD9_T(m, ...) MOCK_METHOD9(m, __VA_ARGS__) +#define MOCK_METHOD10_T(m, ...) MOCK_METHOD10(m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T(m, ...) MOCK_CONST_METHOD0(m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T(m, ...) MOCK_CONST_METHOD1(m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T(m, ...) MOCK_CONST_METHOD2(m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T(m, ...) MOCK_CONST_METHOD3(m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T(m, ...) MOCK_CONST_METHOD4(m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T(m, ...) MOCK_CONST_METHOD5(m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T(m, ...) MOCK_CONST_METHOD6(m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T(m, ...) MOCK_CONST_METHOD7(m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T(m, ...) MOCK_CONST_METHOD8(m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T(m, ...) MOCK_CONST_METHOD9(m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T(m, ...) MOCK_CONST_METHOD10(m, __VA_ARGS__) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 0, __VA_ARGS__) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 1, __VA_ARGS__) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 2, __VA_ARGS__) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 3, __VA_ARGS__) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 4, __VA_ARGS__) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 5, __VA_ARGS__) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 6, __VA_ARGS__) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 7, __VA_ARGS__) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 8, __VA_ARGS__) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 9, __VA_ARGS__) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHODN(constness, ct, Method, args_num, ...) \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + args_num, ::testing::internal::identity_t<__VA_ARGS__>); \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + args_num, Method, GMOCK_PP_NARG0(constness), 0, 0, , ct, , \ + (::testing::internal::identity_t<__VA_ARGS__>)) + +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ diff --git a/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-matchers.h b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-matchers.h new file mode 100644 index 00000000000..aa3a5eb16fc --- /dev/null +++ b/src/third_party/googletest_restricted_for_disagg_only/dist/googlemock/include/gmock/gmock-matchers.h @@ -0,0 +1,5891 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// std::string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on +// https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md +// +// This file also implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface interface if necessary. +// +// See googletest/include/gtest/gtest-matchers.h for the definition of class +// Matcher, class MatcherInterface, and others. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gmock/internal/gmock-pp.h" +#include "gtest/gtest.h" + +// MSVC warning C5046 is new as of VS2017 version 15.8. +#if defined(_MSC_VER) && _MSC_VER >= 1915 +#define GMOCK_MAYBE_5046_ 5046 +#else +#define GMOCK_MAYBE_5046_ +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4251 GMOCK_MAYBE_5046_ /* class A needs to have dll-interface to be used by + clients of class B */ + /* Symbol involving type with internal linkage not defined */) + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface interface, and +// 2. a factory function that creates a Matcher object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + std::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + StringMatchResultListener(const StringMatchResultListener&) = delete; + StringMatchResultListener& operator=(const StringMatchResultListener&) = + delete; +}; + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)) or a value (for +// example, "hello"). +template +class MatcherCastImpl { + public: + static Matcher Cast(const M& polymorphic_matcher_or_value) { + // M can be a polymorphic matcher, in which case we want to use + // its conversion operator to create Matcher. Or it can be a value + // that should be passed to the Matcher's constructor. + // + // We can't call Matcher(polymorphic_matcher_or_value) when M is a + // polymorphic matcher because it'll be ambiguous if T has an implicit + // constructor from M (this usually happens when T has an implicit + // constructor from any type). + // + // It won't work to unconditionally implicit_cast + // polymorphic_matcher_or_value to Matcher because it won't trigger + // a user-defined conversion from M to T if one exists (assuming M is + // a value). + return CastImpl(polymorphic_matcher_or_value, + std::is_convertible>{}, + std::is_convertible{}); + } + + private: + template + static Matcher CastImpl(const M& polymorphic_matcher_or_value, + std::true_type /* convertible_to_matcher */, + std::integral_constant) { + // M is implicitly convertible to Matcher, which means that either + // M is a polymorphic matcher or Matcher has an implicit constructor + // from M. In both cases using the implicit conversion will produce a + // matcher. + // + // Even if T has an implicit constructor from M, it won't be called because + // creating Matcher would require a chain of two user-defined conversions + // (first to create T from M and then to create Matcher from T). + return polymorphic_matcher_or_value; + } + + // M can't be implicitly converted to Matcher, so M isn't a polymorphic + // matcher. It's a value of a type implicitly convertible to T. Use direct + // initialization to create a matcher. + static Matcher CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::true_type /* convertible_to_T */) { + return Matcher(ImplicitCast_(value)); + } + + // M can't be implicitly converted to either Matcher or T. Attempt to use + // polymorphic matcher Eq(value) in this case. + // + // Note that we first attempt to perform an implicit cast on the value and + // only fall back to the polymorphic Eq() matcher afterwards because the + // latter calls bool operator==(const Lhs& lhs, const Rhs& rhs) in the end + // which might be undefined even when Rhs is implicitly convertible to Lhs + // (e.g. std::pair vs. std::pair). + // + // We don't define this method inline as we need the declaration of Eq(). + static Matcher CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::false_type /* convertible_to_T */); +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template +class MatcherCastImpl> { + public: + static Matcher Cast(const Matcher& source_matcher) { + return Matcher(new Impl(source_matcher)); + } + + private: + // If it's possible to implicitly convert a `const T&` to U, then `Impl` can + // take that as input to avoid a copy. Otherwise, such as when `T` is a + // non-const reference type or a type explicitly constructible only from a + // non-const reference, then `Impl` must use `T` as-is (potentially copying). + using ImplArgT = + typename std::conditional::value, + const T&, T>::type; + + class Impl : public MatcherInterface { + public: + explicit Impl(const Matcher& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + bool MatchAndExplain(ImplArgT x, + MatchResultListener* listener) const override { + using FromType = typename std::remove_cv::type>::type>::type; + using ToType = typename std::remove_cv::type>::type>::type; + // Do not allow implicitly converting base*/& to derived*/&. + static_assert( + // Do not trigger if only one of them is a pointer. That implies a + // regular conversion and not a down_cast. + (std::is_pointer::type>::value != + std::is_pointer::type>::value) || + std::is_same::value || + !std::is_base_of::value, + "Can't implicitly convert from to "); + + // Do the cast to `U` explicitly if necessary. + // Otherwise, let implicit conversions do the trick. + using CastType = typename std::conditional< + std::is_convertible::value, ImplArgT&, U>::type; + + return source_matcher_.MatchAndExplain(static_cast(x), + listener); + } + + void DescribeTo(::std::ostream* os) const override { + source_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher source_matcher_; + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template +class MatcherCastImpl> { + public: + static Matcher Cast(const Matcher& matcher) { return matcher; } +}; + +// Template specialization for parameterless Matcher. +template +class MatcherBaseImpl { + public: + MatcherBaseImpl() = default; + + template + operator ::testing::Matcher() const { // NOLINT(runtime/explicit) + return ::testing::Matcher(new + typename Derived::template gmock_Impl()); + } +}; + +// Template specialization for Matcher with parameters. +template