summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorApollon Oikonomopoulos <apoikos@debian.org>2018-03-22 12:08:05 +0200
committerApollon Oikonomopoulos <apoikos@debian.org>2018-03-22 12:08:05 +0200
commit72ad42c506a506ef238d4b1fdcf2b9da5668bfb9 (patch)
treedf94bdab3d6f16e14016f4547b9e86f368ba8150
parent648fcfa3cf11ba8fdb7bb64c52f3159b4351ae3c (diff)
parentc49e99631589113663b1a3ac691870421965a315 (diff)
Update upstream source from tag 'upstream/3.4.14'
Update to upstream version '3.4.14' with Debian dir 8c079cd0bdbb1831aa5b89a3b7d4c1b4a1b891a2
-rw-r--r--README56
-rw-r--r--buildscripts/burn_in_tests.py5
-rwxr-xr-xbuildscripts/hang_analyzer.py54
-rw-r--r--buildscripts/package_test/.kitchen.yml4
-rw-r--r--buildscripts/package_test/recipes/install_mongodb.rb10
-rw-r--r--buildscripts/resmokeconfig/suites/aggregation_read_concern_majority_passthrough.yml39
-rw-r--r--buildscripts/resmokeconfig/suites/read_concern_majority_passthrough.yml99
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_resync_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml78
-rw-r--r--buildscripts/resmokelib/core/programs.py17
-rw-r--r--buildscripts/resmokelib/logging/buildlogger.py152
-rw-r--r--buildscripts/resmokelib/logging/config.py8
-rw-r--r--buildscripts/resmokelib/testing/fixtures/interface.py13
-rw-r--r--buildscripts/resmokelib/testing/fixtures/masterslave.py9
-rw-r--r--buildscripts/resmokelib/testing/fixtures/replicaset.py32
-rw-r--r--buildscripts/resmokelib/testing/fixtures/shardedcluster.py33
-rw-r--r--buildscripts/resmokelib/testing/fixtures/standalone.py10
-rw-r--r--buildscripts/resmokelib/testing/testcases.py27
-rwxr-xr-xbuildscripts/scons.py28
-rw-r--r--buildscripts/tests/resmokelib/logging/test_buildlogger.py67
-rw-r--r--distsrc/README88
-rw-r--r--docs/building.md2
-rw-r--r--etc/drivers_nightly.yml14
-rw-r--r--etc/evergreen.yml400
-rw-r--r--etc/longevity.yml115
-rw-r--r--etc/perf.yml17
-rw-r--r--etc/system_perf.yml403
-rw-r--r--jstests/aggregation/extras/utils.js2
-rw-r--r--jstests/auth/mongos_cache_invalidation.js11
-rw-r--r--jstests/auth/scram-credentials-invalid.js45
-rw-r--r--jstests/auth/system_authorization_indexes.js66
-rw-r--r--jstests/concurrency/fsm_libs/cluster.js19
-rw-r--r--jstests/concurrency/fsm_workloads/compact.js17
-rw-r--r--jstests/concurrency/fsm_workloads/remove_multiple_documents.js11
-rw-r--r--jstests/concurrency/fsm_workloads/remove_where.js9
-rw-r--r--jstests/core/apply_ops1.js49
-rw-r--r--jstests/core/apply_ops_atomicity.js10
-rw-r--r--jstests/core/autocomplete.js42
-rw-r--r--jstests/core/batch_write_command_delete.js2
-rw-r--r--jstests/core/batch_write_command_insert.js2
-rw-r--r--jstests/core/batch_write_command_update.js2
-rw-r--r--jstests/core/bypass_doc_validation.js2
-rw-r--r--jstests/core/capped6.js7
-rw-r--r--jstests/core/collation_plan_cache.js9
-rw-r--r--jstests/core/collection_info_cache_race.js4
-rw-r--r--jstests/core/collmod.js2
-rw-r--r--jstests/core/collmod_bad_spec.js2
-rw-r--r--jstests/core/commands_that_do_not_write_do_not_accept_wc.js2
-rw-r--r--jstests/core/constructors.js2
-rw-r--r--jstests/core/count10.js7
-rw-r--r--jstests/core/count_plan_summary.js10
-rw-r--r--jstests/core/crud_api.js2
-rw-r--r--jstests/core/diagdata.js26
-rw-r--r--jstests/core/doc_validation.js2
-rw-r--r--jstests/core/doc_validation_invalid_validators.js2
-rw-r--r--jstests/core/doc_validation_options.js1
-rw-r--r--jstests/core/dropdb_race.js2
-rw-r--r--jstests/core/elemMatchProjection.js50
-rw-r--r--jstests/core/error2.js1
-rw-r--r--jstests/core/eval0.js2
-rw-r--r--jstests/core/eval1.js1
-rw-r--r--jstests/core/eval3.js1
-rw-r--r--jstests/core/eval4.js1
-rw-r--r--jstests/core/eval5.js1
-rw-r--r--jstests/core/eval6.js1
-rw-r--r--jstests/core/eval7.js2
-rw-r--r--jstests/core/eval9.js2
-rw-r--r--jstests/core/eval_mr.js2
-rw-r--r--jstests/core/eval_nolock.js1
-rw-r--r--jstests/core/evala.js1
-rw-r--r--jstests/core/evalb.js2
-rw-r--r--jstests/core/evald.js2
-rw-r--r--jstests/core/evale.js2
-rw-r--r--jstests/core/evalg.js2
-rw-r--r--jstests/core/evalh.js2
-rw-r--r--jstests/core/evalj.js2
-rw-r--r--jstests/core/existsa.js212
-rw-r--r--jstests/core/fsync.js2
-rw-r--r--jstests/core/fts_dotted_prefix_fields.js15
-rw-r--r--jstests/core/fts_trailing_fields.js22
-rw-r--r--jstests/core/function_string_representations.js38
-rw-r--r--jstests/core/geo_2d_trailing_fields.js45
-rw-r--r--jstests/core/geo_s2cursorlimitskip.js7
-rw-r--r--jstests/core/geo_update_btree.js2
-rw-r--r--jstests/core/getlog2.js7
-rw-r--r--jstests/core/index_elemmatch2.js63
-rw-r--r--jstests/core/index_filter_commands.js20
-rw-r--r--jstests/core/index_stats.js7
-rw-r--r--jstests/core/js3.js1
-rw-r--r--jstests/core/js7.js2
-rw-r--r--jstests/core/js9.js2
-rw-r--r--jstests/core/js_jit.js40
-rw-r--r--jstests/core/list_collections1.js8
-rw-r--r--jstests/core/list_collections_filter.js4
-rw-r--r--jstests/core/list_indexes.js8
-rw-r--r--jstests/core/list_indexes_invalidation.js2
-rw-r--r--jstests/core/list_namespaces_invalidation.js2
-rw-r--r--jstests/core/max_time_ms.js8
-rw-r--r--jstests/core/mr4.js6
-rw-r--r--jstests/core/mr_killop.js3
-rw-r--r--jstests/core/mr_optim.js15
-rw-r--r--jstests/core/no_db_created.js4
-rw-r--r--jstests/core/notablescan.js7
-rw-r--r--jstests/core/operation_latency_histogram.js6
-rw-r--r--jstests/core/plan_cache_clear.js7
-rw-r--r--jstests/core/plan_cache_list_plans.js7
-rw-r--r--jstests/core/plan_cache_list_shapes.js7
-rw-r--r--jstests/core/plan_cache_shell_helpers.js7
-rw-r--r--jstests/core/profile2.js2
-rw-r--r--jstests/core/profile_getmore.js13
-rw-r--r--jstests/core/profile_insert.js2
-rw-r--r--jstests/core/recursion.js6
-rw-r--r--jstests/core/regex_not_id.js6
-rw-r--r--jstests/core/remove8.js1
-rw-r--r--jstests/core/rename4.js2
-rw-r--r--jstests/core/rename_stayTemp.js2
-rw-r--r--jstests/core/shell_connection_strings.js33
-rw-r--r--jstests/core/shell_writeconcern.js3
-rw-r--r--jstests/core/stages_delete.js6
-rw-r--r--jstests/core/startup_log.js209
-rw-r--r--jstests/core/storefunc.js2
-rw-r--r--jstests/core/top.js6
-rw-r--r--jstests/core/update_affects_indexes.js100
-rw-r--r--jstests/core/update_multi5.js4
-rw-r--r--jstests/core/views/invalid_system_views.js2
-rw-r--r--jstests/core/views/views_all_commands.js2
-rw-r--r--jstests/core/views/views_basic.js2
-rw-r--r--jstests/core/views/views_change.js3
-rw-r--r--jstests/core/views/views_collation.js117
-rw-r--r--jstests/core/views/views_find.js2
-rw-r--r--jstests/core/views/views_rename.js17
-rw-r--r--jstests/core/views/views_stats.js6
-rw-r--r--jstests/core/views/views_validation.js1
-rw-r--r--jstests/core/write_result.js2
-rw-r--r--jstests/hooks/validate_collections.js15
-rw-r--r--jstests/libs/analyze_plan.js71
-rw-r--r--jstests/libs/ftdc.js102
-rw-r--r--jstests/libs/override_methods/override_helpers.js117
-rw-r--r--jstests/libs/override_methods/set_majority_read_and_write_concerns.js178
-rw-r--r--jstests/libs/override_methods/set_read_and_write_concerns.js216
-rw-r--r--jstests/libs/override_methods/set_read_preference_secondary.js162
-rw-r--r--jstests/multiVersion/initial_sync_last_stable_from_latest.js15
-rw-r--r--jstests/multiVersion/initial_sync_latest_from_last_stable.js14
-rw-r--r--jstests/multiVersion/initialsync.js58
-rw-r--r--jstests/multiVersion/libs/initial_sync.js50
-rw-r--r--jstests/multiVersion/libs/multi_rs.js1
-rw-r--r--jstests/multiVersion/set_feature_compatibility_version.js34
-rw-r--r--jstests/noPassthrough/ftdc_setdirectory.js118
-rw-r--r--jstests/noPassthrough/geo_near_fcv32.js85
-rw-r--r--jstests/noPassthrough/libs/backup_restore.js20
-rw-r--r--jstests/noPassthrough/non_atomic_apply_ops_logging.js62
-rw-r--r--jstests/noPassthrough/partial_unique_indexes.js47
-rw-r--r--jstests/noPassthrough/skip_sharding_configuration_checks.js53
-rw-r--r--jstests/noPassthroughWithMongod/apply_ops_index_collation.js80
-rw-r--r--jstests/noPassthroughWithMongod/ftdc_params.js54
-rw-r--r--jstests/noPassthroughWithMongod/host_connection_string_validation.js4
-rw-r--r--jstests/noPassthroughWithMongod/initial_sync_oplog_rollover.js2
-rw-r--r--jstests/noPassthroughWithMongod/replset_host_connection_validation.js64
-rw-r--r--jstests/replsets/apply_batches_totalMillis.js63
-rw-r--r--jstests/replsets/apply_ops_concurrent_non_atomic_different_db.js11
-rw-r--r--jstests/replsets/apply_ops_concurrent_non_atomic_same_collection.js11
-rw-r--r--jstests/replsets/apply_ops_concurrent_non_atomic_same_db.js11
-rw-r--r--jstests/replsets/clean_shutdown_oplog_state.js2
-rw-r--r--jstests/replsets/index_delete.js2
-rw-r--r--jstests/replsets/initial_sync_rename_collection_unsafe.js61
-rw-r--r--jstests/replsets/last_vote.js6
-rw-r--r--jstests/replsets/libs/apply_ops_concurrent_non_atomic.js232
-rw-r--r--jstests/replsets/libs/apply_ops_insert_write_conflict.js10
-rw-r--r--jstests/replsets/no_flapping_during_network_partition.js2
-rw-r--r--jstests/replsets/noop_writes_wait_for_write_concern.js235
-rw-r--r--jstests/replsets/read_committed_with_catalog_changes.js2
-rw-r--r--jstests/replsets/startParallelShell.js33
-rw-r--r--jstests/replsets/too_stale_secondary.js2
-rw-r--r--jstests/sharding/auth.js3
-rw-r--r--jstests/sharding/auto_rebalance_parallel.js68
-rw-r--r--jstests/sharding/autosplit.js7
-rw-r--r--jstests/sharding/mapReduce_inSharded_outSharded.js128
-rw-r--r--jstests/sharding/migrateBig_balancer.js8
-rw-r--r--jstests/sharding/movechunk_commit_changelog_stats.js41
-rw-r--r--jstests/sharding/printShardingStatus.js11
-rw-r--r--jstests/sharding/shard_existing_coll_chunk_count.js165
-rw-r--r--jstests/sharding/shard_identity_rollback.js5
-rw-r--r--jstests/sharding/write_cmd_auto_split.js6
-rwxr-xr-xrpm/init.d-mongod1
-rw-r--r--rpm/init.d-mongod.suse1
-rw-r--r--rpm/mongod.service3
-rw-r--r--site_scons/site_tools/jstoh.py8
-rw-r--r--src/mongo/SConscript18
-rw-r--r--src/mongo/base/error_codes.err6
-rw-r--r--src/mongo/base/parse_number_test.cpp6
-rw-r--r--src/mongo/base/validate_locale.cpp13
-rw-r--r--src/mongo/bson/bson_comparator_interface_base.h11
-rw-r--r--src/mongo/bson/bsonelement_comparator_interface.h13
-rw-r--r--src/mongo/bson/bsonobj.cpp1
-rw-r--r--src/mongo/bson/bsonobj.h4
-rw-r--r--src/mongo/bson/util/builder.h11
-rw-r--r--src/mongo/client/connection_string.h17
-rw-r--r--src/mongo/client/fetcher.cpp18
-rw-r--r--src/mongo/client/fetcher.h11
-rw-r--r--src/mongo/client/fetcher_test.cpp4
-rw-r--r--src/mongo/client/mongo_uri.cpp340
-rw-r--r--src/mongo/client/mongo_uri.h41
-rw-r--r--src/mongo/client/mongo_uri_test.cpp292
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json203
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json247
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-options.json25
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json373
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json466
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json311
-rw-r--r--src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json56
-rw-r--r--src/mongo/crypto/mechanism_scram.cpp4
-rw-r--r--src/mongo/crypto/mechanism_scram.h15
-rw-r--r--src/mongo/db/SConscript7
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/auth_index_d.cpp147
-rw-r--r--src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp7
-rw-r--r--src/mongo/db/auth/user.h11
-rw-r--r--src/mongo/db/auth/user_document_parser_test.cpp2
-rw-r--r--src/mongo/db/bson/dotted_path_support.cpp2
-rw-r--r--src/mongo/db/bson/dotted_path_support.h3
-rw-r--r--src/mongo/db/catalog/apply_ops.cpp439
-rw-r--r--src/mongo/db/catalog/apply_ops.h2
-rw-r--r--src/mongo/db/catalog/database.cpp2
-rw-r--r--src/mongo/db/catalog/rename_collection.cpp10
-rw-r--r--src/mongo/db/commands/apply_ops_cmd.cpp23
-rw-r--r--src/mongo/db/commands/create_indexes.cpp8
-rw-r--r--src/mongo/db/commands/dbcommands.cpp25
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.cpp40
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.h13
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp19
-rw-r--r--src/mongo/db/commands/mr.cpp6
-rw-r--r--src/mongo/db/commands/pipeline_command.cpp5
-rw-r--r--src/mongo/db/concurrency/SConscript13
-rw-r--r--src/mongo/db/concurrency/d_concurrency.cpp8
-rw-r--r--src/mongo/db/concurrency/d_concurrency.h6
-rw-r--r--src/mongo/db/concurrency/d_concurrency_test.cpp321
-rw-r--r--src/mongo/db/concurrency/global_lock_acquisition_tracker.cpp45
-rw-r--r--src/mongo/db/concurrency/global_lock_acquisition_tracker.h (renamed from src/mongo/db/s/collection_range_deleter.h)53
-rw-r--r--src/mongo/db/concurrency/lock_manager.cpp2
-rw-r--r--src/mongo/db/concurrency/lock_state.cpp9
-rw-r--r--src/mongo/db/concurrency/locker.h14
-rw-r--r--src/mongo/db/db.cpp14
-rw-r--r--src/mongo/db/dbhelpers.cpp34
-rw-r--r--src/mongo/db/dbhelpers.h5
-rw-r--r--src/mongo/db/dbwebserver.cpp1
-rw-r--r--src/mongo/db/ftdc/SConscript31
-rw-r--r--src/mongo/db/ftdc/collector.cpp1
-rw-r--r--src/mongo/db/ftdc/compressor_test.cpp2
-rw-r--r--src/mongo/db/ftdc/constants.h6
-rw-r--r--src/mongo/db/ftdc/controller.cpp31
-rw-r--r--src/mongo/db/ftdc/controller.h15
-rw-r--r--src/mongo/db/ftdc/controller_test.cpp2
-rw-r--r--src/mongo/db/ftdc/ftdc_mongod.cpp323
-rw-r--r--src/mongo/db/ftdc/ftdc_mongod.h56
-rw-r--r--src/mongo/db/ftdc/ftdc_mongos.cpp153
-rw-r--r--src/mongo/db/ftdc/ftdc_mongos.h43
-rw-r--r--src/mongo/db/ftdc/ftdc_server.cpp350
-rw-r--r--src/mongo/db/ftdc/ftdc_server.h103
-rw-r--r--src/mongo/db/ftdc/ftdc_system_stats.h1
-rw-r--r--src/mongo/db/ftdc/util.cpp13
-rw-r--r--src/mongo/db/ftdc/util.h5
-rw-r--r--src/mongo/db/ftdc/util_test.cpp21
-rw-r--r--src/mongo/db/fts/unicode/string.cpp9
-rw-r--r--src/mongo/db/index/expression_keys_private.cpp5
-rw-r--r--src/mongo/db/instance.cpp9
-rw-r--r--src/mongo/db/log_process_details.cpp17
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp40
-rw-r--r--src/mongo/db/matcher/expression_leaf.h22
-rw-r--r--src/mongo/db/matcher/expression_leaf_test.cpp66
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp7
-rw-r--r--src/mongo/db/mongod_options.cpp23
-rw-r--r--src/mongo/db/mongod_options.h2
-rw-r--r--src/mongo/db/namespace_string-inl.h2
-rw-r--r--src/mongo/db/namespace_string_test.cpp12
-rw-r--r--src/mongo/db/op_observer_impl.cpp36
-rw-r--r--src/mongo/db/operation_context.cpp2
-rw-r--r--src/mongo/db/operation_context.h34
-rw-r--r--src/mongo/db/operation_context_test.cpp2
-rw-r--r--src/mongo/db/ops/delete.cpp8
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.cpp8
-rw-r--r--src/mongo/db/ops/modifier_add_to_set_test.cpp37
-rw-r--r--src/mongo/db/ops/modifier_bit.cpp8
-rw-r--r--src/mongo/db/ops/modifier_bit_test.cpp37
-rw-r--r--src/mongo/db/ops/modifier_compare.cpp7
-rw-r--r--src/mongo/db/ops/modifier_compare_test.cpp37
-rw-r--r--src/mongo/db/ops/modifier_current_date.cpp10
-rw-r--r--src/mongo/db/ops/modifier_current_date_test.cpp37
-rw-r--r--src/mongo/db/ops/modifier_inc.cpp7
-rw-r--r--src/mongo/db/ops/modifier_inc_test.cpp37
-rw-r--r--src/mongo/db/ops/modifier_interface.h6
-rw-r--r--src/mongo/db/ops/modifier_push.cpp9
-rw-r--r--src/mongo/db/ops/modifier_push_test.cpp83
-rw-r--r--src/mongo/db/ops/modifier_set.cpp6
-rw-r--r--src/mongo/db/ops/modifier_set_test.cpp37
-rw-r--r--src/mongo/db/ops/update.cpp17
-rw-r--r--src/mongo/db/ops/update_driver.cpp20
-rw-r--r--src/mongo/db/pipeline/SConscript2
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near.cpp10
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.cpp46
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.h37
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp69
-rw-r--r--src/mongo/db/query/SConscript44
-rw-r--r--src/mongo/db/query/canonical_query_test.cpp3
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp8
-rw-r--r--src/mongo/db/query/index_bounds_builder.h11
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp50
-rw-r--r--src/mongo/db/query/plan_enumerator.cpp52
-rw-r--r--src/mongo/db/query/plan_enumerator.h11
-rw-r--r--src/mongo/db/query/planner_access.cpp95
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp53
-rw-r--r--src/mongo/db/query/planner_ixselect.h3
-rw-r--r--src/mongo/db/query/planner_ixselect_test.cpp79
-rw-r--r--src/mongo/db/query/query_planner_geo_test.cpp62
-rw-r--r--src/mongo/db/query/query_planner_text_test.cpp73
-rw-r--r--src/mongo/db/query/query_solution.h7
-rw-r--r--src/mongo/db/range_deleter.cpp20
-rw-r--r--src/mongo/db/range_deleter.h2
-rw-r--r--src/mongo/db/range_deleter_db_env.cpp2
-rw-r--r--src/mongo/db/range_deleter_db_env.h5
-rw-r--r--src/mongo/db/range_deleter_mock_env.cpp1
-rw-r--r--src/mongo/db/range_deleter_mock_env.h1
-rw-r--r--src/mongo/db/repl/SConscript2
-rw-r--r--src/mongo/db/repl/bgsync.cpp46
-rw-r--r--src/mongo/db/repl/bgsync.h2
-rw-r--r--src/mongo/db/repl/collection_cloner.cpp6
-rw-r--r--src/mongo/db/repl/database_cloner.cpp3
-rw-r--r--src/mongo/db/repl/databases_cloner.cpp71
-rw-r--r--src/mongo/db/repl/databases_cloner.h30
-rw-r--r--src/mongo/db/repl/databases_cloner_test.cpp90
-rw-r--r--src/mongo/db/repl/initial_syncer.cpp3
-rw-r--r--src/mongo/db/repl/master_slave.cpp41
-rw-r--r--src/mongo/db/repl/oplog.cpp131
-rw-r--r--src/mongo/db/repl/oplog.h9
-rw-r--r--src/mongo/db/repl/oplog_fetcher.cpp33
-rw-r--r--src/mongo/db/repl/oplog_fetcher.h10
-rw-r--r--src/mongo/db/repl/repl_client_info.cpp18
-rw-r--r--src/mongo/db/repl/repl_set_config.cpp11
-rw-r--r--src/mongo/db/repl/replication_coordinator_external_state_impl.cpp7
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.cpp20
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp39
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp11
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp28
-rw-r--r--src/mongo/db/repl/replication_executor_test.cpp15
-rw-r--r--src/mongo/db/repl/sync_source_feedback.cpp39
-rw-r--r--src/mongo/db/repl/sync_source_feedback.h9
-rw-r--r--src/mongo/db/repl/sync_source_resolver.cpp78
-rw-r--r--src/mongo/db/repl/sync_source_resolver.h15
-rw-r--r--src/mongo/db/repl/sync_tail.cpp58
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl.cpp62
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl.h6
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl_test.cpp35
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl_v1_test.cpp35
-rw-r--r--src/mongo/db/s/SConscript1
-rw-r--r--src/mongo/db/s/balancer/balancer.cpp5
-rw-r--r--src/mongo/db/s/balancer/balancer.h3
-rw-r--r--src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.cpp13
-rw-r--r--src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h3
-rw-r--r--src/mongo/db/s/balancer/balancer_policy.cpp78
-rw-r--r--src/mongo/db/s/balancer/balancer_policy.h64
-rw-r--r--src/mongo/db/s/balancer/balancer_policy_test.cpp146
-rw-r--r--src/mongo/db/s/collection_range_deleter.cpp222
-rw-r--r--src/mongo/db/s/collection_sharding_state.h2
-rw-r--r--src/mongo/db/s/metadata_manager.cpp124
-rw-r--r--src/mongo/db/s/metadata_manager.h52
-rw-r--r--src/mongo/db/s/metadata_manager_test.cpp192
-rw-r--r--src/mongo/db/s/migration_chunk_cloner_source.h6
-rw-r--r--src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp23
-rw-r--r--src/mongo/db/s/migration_chunk_cloner_source_legacy.h2
-rw-r--r--src/mongo/db/s/migration_destination_manager.cpp40
-rw-r--r--src/mongo/db/s/migration_destination_manager.h5
-rw-r--r--src/mongo/db/s/migration_destination_manager_legacy_commands.cpp11
-rw-r--r--src/mongo/db/s/migration_source_manager.cpp22
-rw-r--r--src/mongo/db/s/migration_source_manager.h3
-rw-r--r--src/mongo/db/s/sharding_state.cpp109
-rw-r--r--src/mongo/db/s/sharding_state.h29
-rw-r--r--src/mongo/db/sorter/SConscript2
-rw-r--r--src/mongo/db/sorter/sorter.cpp33
-rw-r--r--src/mongo/db/stats/range_deleter_server_status.cpp5
-rw-r--r--src/mongo/db/storage/SConscript15
-rw-r--r--src/mongo/db/storage/encryption_hooks.cpp96
-rw-r--r--src/mongo/db/storage/encryption_hooks.h99
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp11
-rw-r--r--src/mongo/db/storage/key_string.cpp6
-rw-r--r--src/mongo/db/storage/key_string_test.cpp102
-rw-r--r--src/mongo/db/storage/mmap_v1/mmap_v1_engine.cpp4
-rw-r--r--src/mongo/db/storage/wiredtiger/SConscript1
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp34
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h75
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp1
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp8
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp29
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp15
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h6
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_server_status.cpp3
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp56
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.h19
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_size_storer.cpp2
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.cpp1
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp8
-rw-r--r--src/mongo/db/views/resolved_view.cpp29
-rw-r--r--src/mongo/db/views/resolved_view.h22
-rw-r--r--src/mongo/db/views/resolved_view_test.cpp91
-rw-r--r--src/mongo/db/views/view_catalog.cpp9
-rw-r--r--src/mongo/db/views/view_catalog_test.cpp38
-rw-r--r--src/mongo/db/views/view_sharding_check.cpp6
-rw-r--r--src/mongo/dbtests/dbhelper_tests.cpp8
-rw-r--r--src/mongo/dbtests/jstests.cpp44
-rw-r--r--src/mongo/dbtests/rollbacktests.cpp12
-rw-r--r--src/mongo/executor/connection_pool.cpp247
-rw-r--r--src/mongo/executor/network_interface_asio_auth.cpp1
-rw-r--r--src/mongo/executor/task_executor.h2
-rw-r--r--src/mongo/executor/task_executor_test_common.cpp8
-rw-r--r--src/mongo/rpc/get_status_from_command_result.cpp43
-rw-r--r--src/mongo/rpc/get_status_from_command_result.h13
-rw-r--r--src/mongo/s/catalog/type_tags.cpp27
-rw-r--r--src/mongo/s/catalog/type_tags_test.cpp5
-rw-r--r--src/mongo/s/client/shard_remote.cpp3
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_ftdc_commands.cpp27
-rw-r--r--src/mongo/s/commands/cluster_shard_collection_cmd.cpp18
-rw-r--r--src/mongo/s/commands/cluster_write.cpp25
-rw-r--r--src/mongo/s/move_chunk_request.cpp3
-rw-r--r--src/mongo/s/server.cpp6
-rw-r--r--src/mongo/s/sharding_initialization.cpp24
-rw-r--r--src/mongo/scripting/deadline_monitor.cpp2
-rw-r--r--src/mongo/scripting/deadline_monitor.h6
-rw-r--r--src/mongo/scripting/deadline_monitor_test.cpp13
-rw-r--r--src/mongo/scripting/engine.cpp16
-rw-r--r--src/mongo/scripting/engine.h4
-rw-r--r--src/mongo/scripting/mozjs/PosixNSPR.cpp13
-rw-r--r--src/mongo/scripting/mozjs/bson.cpp10
-rw-r--r--src/mongo/scripting/mozjs/db.cpp53
-rw-r--r--src/mongo/scripting/mozjs/db.h5
-rw-r--r--src/mongo/scripting/mozjs/dbcollection.cpp10
-rw-r--r--src/mongo/scripting/mozjs/dbcollection.h5
-rw-r--r--src/mongo/scripting/mozjs/dbquery.cpp25
-rw-r--r--src/mongo/scripting/mozjs/dbquery.h6
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp195
-rw-r--r--src/mongo/scripting/mozjs/implscope.h8
-rw-r--r--src/mongo/scripting/mozjs/mongohelpers.js8
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.cpp4
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.h2
-rw-r--r--src/mongo/shell/bench.cpp14
-rw-r--r--src/mongo/shell/dbshell.cpp169
-rw-r--r--src/mongo/shell/mongo.js5
-rw-r--r--src/mongo/shell/replsettest.js16
-rw-r--r--src/mongo/shell/servers_misc.js20
-rw-r--r--src/mongo/shell/shardingtest.js6
-rw-r--r--src/mongo/shell/utils.js15
-rw-r--r--src/mongo/shell/utils_sh.js130
-rw-r--r--src/mongo/transport/SConscript4
-rw-r--r--src/mongo/transport/message_compressor_manager.cpp30
-rw-r--r--src/mongo/transport/message_compressor_manager_test.cpp89
-rw-r--r--src/mongo/transport/message_compressor_snappy.cpp17
-rw-r--r--src/mongo/util/SConscript11
-rw-r--r--src/mongo/util/assert_util.cpp5
-rw-r--r--src/mongo/util/cmdline_utils/censor_cmdline.cpp2
-rw-r--r--src/mongo/util/concurrency/with_lock.h111
-rw-r--r--src/mongo/util/net/hostandport.cpp4
-rw-r--r--src/mongo/util/net/miniwebserver.cpp1
-rw-r--r--src/mongo/util/net/ssl_manager.cpp64
-rw-r--r--src/mongo/util/options_parser/options_parser.cpp5
-rw-r--r--src/mongo/util/options_parser/options_parser_test.cpp76
-rw-r--r--src/mongo/util/password.cpp4
-rw-r--r--src/mongo/util/producer_consumer_queue.h559
-rw-r--r--src/mongo/util/producer_consumer_queue_test.cpp531
-rw-r--r--src/mongo/util/tcmalloc_server_status_section.cpp26
-rw-r--r--src/third_party/SConscript2
-rw-r--r--src/third_party/gperftools-2.5/src/base/spinlock.cc77
-rw-r--r--src/third_party/gperftools-2.5/src/base/spinlock.h6
-rw-r--r--src/third_party/gperftools-2.5/src/page_heap.cc15
-rw-r--r--src/third_party/gperftools-2.5/src/page_heap.h14
-rw-r--r--src/third_party/gperftools-2.5/src/tcmalloc.cc53
-rwxr-xr-xsrc/third_party/pcre-8.41/132html (renamed from src/third_party/pcre-8.39/132html)0
-rw-r--r--src/third_party/pcre-8.41/AUTHORS (renamed from src/third_party/pcre-8.39/AUTHORS)6
-rw-r--r--src/third_party/pcre-8.41/COPYING (renamed from src/third_party/pcre-8.39/COPYING)0
-rw-r--r--src/third_party/pcre-8.41/ChangeLog (renamed from src/third_party/pcre-8.39/ChangeLog)94
-rwxr-xr-xsrc/third_party/pcre-8.41/CheckMan (renamed from src/third_party/pcre-8.39/CheckMan)0
-rwxr-xr-xsrc/third_party/pcre-8.41/CleanTxt (renamed from src/third_party/pcre-8.39/CleanTxt)0
-rwxr-xr-xsrc/third_party/pcre-8.41/Detrail (renamed from src/third_party/pcre-8.39/Detrail)0
-rw-r--r--src/third_party/pcre-8.41/HACKING (renamed from src/third_party/pcre-8.39/HACKING)0
-rw-r--r--src/third_party/pcre-8.41/INSTALL (renamed from src/third_party/pcre-8.39/INSTALL)0
-rw-r--r--src/third_party/pcre-8.41/LICENCE (renamed from src/third_party/pcre-8.39/LICENCE)6
-rw-r--r--src/third_party/pcre-8.41/NEWS (renamed from src/third_party/pcre-8.39/NEWS)12
-rw-r--r--src/third_party/pcre-8.41/NON-AUTOTOOLS-BUILD (renamed from src/third_party/pcre-8.39/NON-AUTOTOOLS-BUILD)0
-rw-r--r--src/third_party/pcre-8.41/NON-UNIX-USE (renamed from src/third_party/pcre-8.39/NON-UNIX-USE)0
-rwxr-xr-xsrc/third_party/pcre-8.41/PrepareRelease (renamed from src/third_party/pcre-8.39/PrepareRelease)0
-rw-r--r--src/third_party/pcre-8.41/README (renamed from src/third_party/pcre-8.39/README)0
-rwxr-xr-xsrc/third_party/pcre-8.41/RunGrepTest (renamed from src/third_party/pcre-8.39/RunGrepTest)0
-rwxr-xr-xsrc/third_party/pcre-8.41/RunTest (renamed from src/third_party/pcre-8.39/RunTest)0
-rw-r--r--src/third_party/pcre-8.41/RunTest.bat (renamed from src/third_party/pcre-8.39/RunTest.bat)0
-rw-r--r--src/third_party/pcre-8.41/SConscript (renamed from src/third_party/pcre-8.39/SConscript)0
-rwxr-xr-xsrc/third_party/pcre-8.41/ar-lib (renamed from src/third_party/pcre-8.39/ar-lib)0
-rw-r--r--src/third_party/pcre-8.41/build_posix/config.h (renamed from src/third_party/pcre-8.39/build_posix/config.h)6
-rw-r--r--src/third_party/pcre-8.41/build_solaris/config.h (renamed from src/third_party/pcre-8.39/build_solaris/config.h)6
-rwxr-xr-xsrc/third_party/pcre-8.41/build_windows/config.h (renamed from src/third_party/pcre-8.39/build_windows/config.h)0
-rw-r--r--src/third_party/pcre-8.41/dftables.c (renamed from src/third_party/pcre-8.39/dftables.c)0
-rw-r--r--src/third_party/pcre-8.41/libpcre.pc.in (renamed from src/third_party/pcre-8.39/libpcre.pc.in)0
-rw-r--r--src/third_party/pcre-8.41/libpcre16.pc.in (renamed from src/third_party/pcre-8.39/libpcre16.pc.in)0
-rw-r--r--src/third_party/pcre-8.41/libpcre32.pc.in (renamed from src/third_party/pcre-8.39/libpcre32.pc.in)0
-rw-r--r--src/third_party/pcre-8.41/libpcrecpp.pc.in (renamed from src/third_party/pcre-8.39/libpcrecpp.pc.in)0
-rw-r--r--src/third_party/pcre-8.41/libpcreposix.pc.in (renamed from src/third_party/pcre-8.39/libpcreposix.pc.in)0
-rw-r--r--src/third_party/pcre-8.41/makevp.bat (renamed from src/third_party/pcre-8.39/makevp.bat)0
-rw-r--r--src/third_party/pcre-8.41/makevp_c.txt (renamed from src/third_party/pcre-8.39/makevp_c.txt)0
-rw-r--r--src/third_party/pcre-8.41/makevp_l.txt (renamed from src/third_party/pcre-8.39/makevp_l.txt)0
-rw-r--r--src/third_party/pcre-8.41/pcre-config.in (renamed from src/third_party/pcre-8.39/pcre-config.in)0
-rw-r--r--src/third_party/pcre-8.41/pcre.h (renamed from src/third_party/pcre-8.39/pcre.h)4
-rw-r--r--src/third_party/pcre-8.41/pcre.h.generic (renamed from src/third_party/pcre-8.39/pcre.h.generic)4
-rw-r--r--src/third_party/pcre-8.41/pcre.h.in (renamed from src/third_party/pcre-8.39/pcre.h.in)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_byte_order.c (renamed from src/third_party/pcre-8.39/pcre16_byte_order.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_chartables.c (renamed from src/third_party/pcre-8.39/pcre16_chartables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_compile.c (renamed from src/third_party/pcre-8.39/pcre16_compile.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_config.c (renamed from src/third_party/pcre-8.39/pcre16_config.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_dfa_exec.c (renamed from src/third_party/pcre-8.39/pcre16_dfa_exec.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_exec.c (renamed from src/third_party/pcre-8.39/pcre16_exec.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_fullinfo.c (renamed from src/third_party/pcre-8.39/pcre16_fullinfo.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_get.c (renamed from src/third_party/pcre-8.39/pcre16_get.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_globals.c (renamed from src/third_party/pcre-8.39/pcre16_globals.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_jit_compile.c (renamed from src/third_party/pcre-8.39/pcre16_jit_compile.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_maketables.c (renamed from src/third_party/pcre-8.39/pcre16_maketables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_newline.c (renamed from src/third_party/pcre-8.39/pcre16_newline.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_ord2utf16.c (renamed from src/third_party/pcre-8.39/pcre16_ord2utf16.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_printint.c (renamed from src/third_party/pcre-8.39/pcre16_printint.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_refcount.c (renamed from src/third_party/pcre-8.39/pcre16_refcount.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_string_utils.c (renamed from src/third_party/pcre-8.39/pcre16_string_utils.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_study.c (renamed from src/third_party/pcre-8.39/pcre16_study.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_tables.c (renamed from src/third_party/pcre-8.39/pcre16_tables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_ucd.c (renamed from src/third_party/pcre-8.39/pcre16_ucd.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_utf16_utils.c (renamed from src/third_party/pcre-8.39/pcre16_utf16_utils.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_valid_utf16.c (renamed from src/third_party/pcre-8.39/pcre16_valid_utf16.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_version.c (renamed from src/third_party/pcre-8.39/pcre16_version.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre16_xclass.c (renamed from src/third_party/pcre-8.39/pcre16_xclass.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_byte_order.c (renamed from src/third_party/pcre-8.39/pcre32_byte_order.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_chartables.c (renamed from src/third_party/pcre-8.39/pcre32_chartables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_compile.c (renamed from src/third_party/pcre-8.39/pcre32_compile.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_config.c (renamed from src/third_party/pcre-8.39/pcre32_config.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_dfa_exec.c (renamed from src/third_party/pcre-8.39/pcre32_dfa_exec.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_exec.c (renamed from src/third_party/pcre-8.39/pcre32_exec.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_fullinfo.c (renamed from src/third_party/pcre-8.39/pcre32_fullinfo.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_get.c (renamed from src/third_party/pcre-8.39/pcre32_get.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_globals.c (renamed from src/third_party/pcre-8.39/pcre32_globals.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_jit_compile.c (renamed from src/third_party/pcre-8.39/pcre32_jit_compile.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_maketables.c (renamed from src/third_party/pcre-8.39/pcre32_maketables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_newline.c (renamed from src/third_party/pcre-8.39/pcre32_newline.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_ord2utf32.c (renamed from src/third_party/pcre-8.39/pcre32_ord2utf32.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_printint.c (renamed from src/third_party/pcre-8.39/pcre32_printint.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_refcount.c (renamed from src/third_party/pcre-8.39/pcre32_refcount.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_string_utils.c (renamed from src/third_party/pcre-8.39/pcre32_string_utils.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_study.c (renamed from src/third_party/pcre-8.39/pcre32_study.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_tables.c (renamed from src/third_party/pcre-8.39/pcre32_tables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_ucd.c (renamed from src/third_party/pcre-8.39/pcre32_ucd.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_utf32_utils.c (renamed from src/third_party/pcre-8.39/pcre32_utf32_utils.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_valid_utf32.c (renamed from src/third_party/pcre-8.39/pcre32_valid_utf32.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_version.c (renamed from src/third_party/pcre-8.39/pcre32_version.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre32_xclass.c (renamed from src/third_party/pcre-8.39/pcre32_xclass.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_byte_order.c (renamed from src/third_party/pcre-8.39/pcre_byte_order.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_chartables.c (renamed from src/third_party/pcre-8.39/pcre_chartables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_chartables.c.dist (renamed from src/third_party/pcre-8.39/pcre_chartables.c.dist)0
-rw-r--r--src/third_party/pcre-8.41/pcre_compile.c (renamed from src/third_party/pcre-8.39/pcre_compile.c)96
-rw-r--r--src/third_party/pcre-8.41/pcre_config.c (renamed from src/third_party/pcre-8.39/pcre_config.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_dfa_exec.c (renamed from src/third_party/pcre-8.39/pcre_dfa_exec.c)4
-rw-r--r--src/third_party/pcre-8.41/pcre_exec.c (renamed from src/third_party/pcre-8.39/pcre_exec.c)2
-rw-r--r--src/third_party/pcre-8.41/pcre_fullinfo.c (renamed from src/third_party/pcre-8.39/pcre_fullinfo.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_get.c (renamed from src/third_party/pcre-8.39/pcre_get.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_globals.c (renamed from src/third_party/pcre-8.39/pcre_globals.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_internal.h (renamed from src/third_party/pcre-8.39/pcre_internal.h)11
-rw-r--r--src/third_party/pcre-8.41/pcre_jit_compile.c (renamed from src/third_party/pcre-8.39/pcre_jit_compile.c)962
-rw-r--r--src/third_party/pcre-8.41/pcre_jit_test.c (renamed from src/third_party/pcre-8.39/pcre_jit_test.c)1
-rw-r--r--src/third_party/pcre-8.41/pcre_maketables.c (renamed from src/third_party/pcre-8.39/pcre_maketables.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_newline.c (renamed from src/third_party/pcre-8.39/pcre_newline.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_ord2utf8.c (renamed from src/third_party/pcre-8.39/pcre_ord2utf8.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_printint.c (renamed from src/third_party/pcre-8.39/pcre_printint.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_refcount.c (renamed from src/third_party/pcre-8.39/pcre_refcount.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_scanner.cc (renamed from src/third_party/pcre-8.39/pcre_scanner.cc)0
-rw-r--r--src/third_party/pcre-8.41/pcre_scanner.h (renamed from src/third_party/pcre-8.39/pcre_scanner.h)0
-rw-r--r--src/third_party/pcre-8.41/pcre_scanner_unittest.cc (renamed from src/third_party/pcre-8.39/pcre_scanner_unittest.cc)1
-rw-r--r--src/third_party/pcre-8.41/pcre_string_utils.c (renamed from src/third_party/pcre-8.39/pcre_string_utils.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_stringpiece.cc (renamed from src/third_party/pcre-8.39/pcre_stringpiece.cc)0
-rw-r--r--src/third_party/pcre-8.41/pcre_stringpiece.h (renamed from src/third_party/pcre-8.39/pcre_stringpiece.h)4
-rw-r--r--src/third_party/pcre-8.41/pcre_stringpiece.h.in (renamed from src/third_party/pcre-8.39/pcre_stringpiece.h.in)4
-rw-r--r--src/third_party/pcre-8.41/pcre_stringpiece_unittest.cc (renamed from src/third_party/pcre-8.39/pcre_stringpiece_unittest.cc)1
-rw-r--r--src/third_party/pcre-8.41/pcre_study.c (renamed from src/third_party/pcre-8.39/pcre_study.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_tables.c (renamed from src/third_party/pcre-8.39/pcre_tables.c)4
-rw-r--r--src/third_party/pcre-8.41/pcre_ucd.c (renamed from src/third_party/pcre-8.39/pcre_ucd.c)14
-rw-r--r--src/third_party/pcre-8.41/pcre_valid_utf8.c (renamed from src/third_party/pcre-8.39/pcre_valid_utf8.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_version.c (renamed from src/third_party/pcre-8.39/pcre_version.c)0
-rw-r--r--src/third_party/pcre-8.41/pcre_xclass.c (renamed from src/third_party/pcre-8.39/pcre_xclass.c)0
-rw-r--r--src/third_party/pcre-8.41/pcrecpp.cc (renamed from src/third_party/pcre-8.39/pcrecpp.cc)0
-rw-r--r--src/third_party/pcre-8.41/pcrecpp.h (renamed from src/third_party/pcre-8.39/pcrecpp.h)0
-rw-r--r--src/third_party/pcre-8.41/pcrecpp_internal.h (renamed from src/third_party/pcre-8.39/pcrecpp_internal.h)0
-rw-r--r--src/third_party/pcre-8.41/pcrecpp_unittest.cc (renamed from src/third_party/pcre-8.39/pcrecpp_unittest.cc)1
-rw-r--r--src/third_party/pcre-8.41/pcrecpparg.h (renamed from src/third_party/pcre-8.39/pcrecpparg.h)0
-rw-r--r--src/third_party/pcre-8.41/pcrecpparg.h.in (renamed from src/third_party/pcre-8.39/pcrecpparg.h.in)0
-rw-r--r--src/third_party/pcre-8.41/pcredemo.c (renamed from src/third_party/pcre-8.39/pcredemo.c)0
-rw-r--r--src/third_party/pcre-8.41/pcregexp.pas (renamed from src/third_party/pcre-8.39/pcregexp.pas)0
-rw-r--r--src/third_party/pcre-8.41/pcregrep.c (renamed from src/third_party/pcre-8.39/pcregrep.c)25
-rw-r--r--src/third_party/pcre-8.41/pcreposix.c (renamed from src/third_party/pcre-8.39/pcreposix.c)6
-rw-r--r--src/third_party/pcre-8.41/pcreposix.h (renamed from src/third_party/pcre-8.39/pcreposix.h)0
-rw-r--r--src/third_party/pcre-8.41/pcretest.c (renamed from src/third_party/pcre-8.39/pcretest.c)37
-rwxr-xr-xsrc/third_party/pcre-8.41/perltest.pl (renamed from src/third_party/pcre-8.39/perltest.pl)0
-rw-r--r--src/third_party/pcre-8.41/ucp.h (renamed from src/third_party/pcre-8.39/ucp.h)0
-rw-r--r--src/third_party/scripts/pcre_get_sources.sh2
-rw-r--r--src/third_party/wiredtiger/build_linux/wiredtiger_config.h3
-rw-r--r--src/third_party/wiredtiger/build_posix/configure.ac.in38
-rw-r--r--src/third_party/wiredtiger/build_win/wiredtiger_config.h3
-rw-r--r--src/third_party/wiredtiger/dist/api_data.py3
-rw-r--r--src/third_party/wiredtiger/dist/filelist1
-rw-r--r--src/third_party/wiredtiger/dist/flags.py2
-rw-r--r--src/third_party/wiredtiger/dist/s_string.ok2
-rw-r--r--src/third_party/wiredtiger/dist/stat_data.py8
-rw-r--r--src/third_party/wiredtiger/import.data2
-rw-r--r--src/third_party/wiredtiger/src/async/async_worker.c5
-rw-r--r--src/third_party/wiredtiger/src/btree/bt_cursor.c42
-rw-r--r--src/third_party/wiredtiger/src/btree/bt_delete.c9
-rw-r--r--src/third_party/wiredtiger/src/btree/bt_handle.c41
-rw-r--r--src/third_party/wiredtiger/src/btree/bt_random.c3
-rw-r--r--src/third_party/wiredtiger/src/btree/bt_walk.c121
-rw-r--r--src/third_party/wiredtiger/src/config/config_def.c46
-rw-r--r--src/third_party/wiredtiger/src/conn/conn_api.c47
-rw-r--r--src/third_party/wiredtiger/src/conn/conn_dhandle.c19
-rw-r--r--src/third_party/wiredtiger/src/conn/conn_log.c35
-rw-r--r--src/third_party/wiredtiger/src/conn/conn_open.c3
-rw-r--r--src/third_party/wiredtiger/src/cursor/cur_index.c4
-rw-r--r--src/third_party/wiredtiger/src/cursor/cur_join.c2
-rw-r--r--src/third_party/wiredtiger/src/cursor/cur_table.c4
-rw-r--r--src/third_party/wiredtiger/src/evict/evict_lru.c218
-rw-r--r--src/third_party/wiredtiger/src/evict/evict_page.c40
-rw-r--r--src/third_party/wiredtiger/src/include/btmem.h4
-rw-r--r--src/third_party/wiredtiger/src/include/btree.h38
-rw-r--r--src/third_party/wiredtiger/src/include/cache.h3
-rw-r--r--src/third_party/wiredtiger/src/include/connection.h7
-rw-r--r--src/third_party/wiredtiger/src/include/extern.h11
-rw-r--r--src/third_party/wiredtiger/src/include/extern_posix.h2
-rw-r--r--src/third_party/wiredtiger/src/include/extern_win.h2
-rw-r--r--src/third_party/wiredtiger/src/include/flags.h8
-rw-r--r--src/third_party/wiredtiger/src/include/misc.i39
-rw-r--r--src/third_party/wiredtiger/src/include/os.h4
-rw-r--r--src/third_party/wiredtiger/src/include/session.h6
-rw-r--r--src/third_party/wiredtiger/src/include/stat.h8
-rw-r--r--src/third_party/wiredtiger/src/include/txn.h2
-rw-r--r--src/third_party/wiredtiger/src/include/txn.i10
-rw-r--r--src/third_party/wiredtiger/src/include/wiredtiger.in78
-rw-r--r--src/third_party/wiredtiger/src/log/log.c66
-rw-r--r--src/third_party/wiredtiger/src/log/log_slot.c20
-rw-r--r--src/third_party/wiredtiger/src/lsm/lsm_manager.c4
-rw-r--r--src/third_party/wiredtiger/src/meta/meta_table.c17
-rw-r--r--src/third_party/wiredtiger/src/meta/meta_turtle.c6
-rw-r--r--src/third_party/wiredtiger/src/os_posix/os_mtx_cond.c33
-rw-r--r--src/third_party/wiredtiger/src/os_posix/os_time.c27
-rw-r--r--src/third_party/wiredtiger/src/os_win/os_time.c15
-rw-r--r--src/third_party/wiredtiger/src/reconcile/rec_write.c218
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_alter.c13
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_create.c6
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_drop.c4
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_list.c55
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_open.c68
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_rename.c2
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_stat.c11
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_truncate.c2
-rw-r--r--src/third_party/wiredtiger/src/schema/schema_worker.c9
-rw-r--r--src/third_party/wiredtiger/src/session/session_api.c15
-rw-r--r--src/third_party/wiredtiger/src/session/session_dhandle.c5
-rw-r--r--src/third_party/wiredtiger/src/support/err.c13
-rw-r--r--src/third_party/wiredtiger/src/support/rand.c12
-rw-r--r--src/third_party/wiredtiger/src/support/stat.c29
-rw-r--r--src/third_party/wiredtiger/src/support/time.c89
-rw-r--r--src/third_party/wiredtiger/src/txn/txn.c21
-rw-r--r--src/third_party/wiredtiger/src/txn/txn_log.c31
-rw-r--r--src/third_party/wiredtiger/src/txn/txn_recover.c38
-rw-r--r--src/third_party/wiredtiger/test/mciproject.yml14
-rw-r--r--src/third_party/wiredtiger/test/recovery/random-abort.c4
-rw-r--r--src/third_party/wiredtiger/test/suite/test_bug018.py98
-rw-r--r--src/third_party/wiredtiger/test/suite/test_inmem01.py3
-rw-r--r--src/third_party/wiredtiger/test/suite/test_las.py60
-rw-r--r--src/third_party/wiredtiger/test/suite/test_txn02.py1
-rw-r--r--src/third_party/wiredtiger/test/suite/test_txn05.py11
-rw-r--r--src/third_party/wiredtiger/test/suite/test_txn09.py4
-rw-r--r--src/third_party/wiredtiger/test/suite/test_txn16.py140
-rw-r--r--version.json4
682 files changed, 16256 insertions, 5608 deletions
diff --git a/README b/README
index 858f8a1d2c8..5a772b66e7d 100644
--- a/README
+++ b/README
@@ -4,23 +4,26 @@ Welcome to MongoDB 3.4!
COMPONENTS
- mongod - The database process.
- mongos - Sharding controller.
+ mongod - The database server.
+ mongos - Sharding router.
mongo - The database shell (uses interactive javascript).
UTILITIES
- mongodump - MongoDB dump tool - for backups, snapshots, etc.
- mongorestore - MongoDB restore a dump
- mongoexport - Export a single collection to JSON or CSV
- mongoimport - Import from JSON or CSV
- mongofiles - Utility for putting and getting files from MongoDB GridFS
- mongostat - Show performance statistics
- mongoreplay - Workload capture and analysis tool
+ mongodump - Create a binary dump of the contents of a database.
+ mongorestore - Restore data from the output created by mongodump.
+ mongoexport - Export the contents of a collection to JSON or CSV.
+ mongoimport - Import data from JSON, CSV or TSV.
+ mongofiles - Put, get and delete files from GridFS.
+ mongostat - Show the status of a running mongod/mongos.
+ bsondump - Convert BSON files into human-readable formats.
+ mongooplog - Poll the oplog and apply to a local server.
+ mongoreplay - Traffic capture and replay tool.
+ mongotop - Track time spent reading and writing data.
BUILDING
- See docs/building.md, also www.mongodb.org search for "Building".
+ See docs/building.md.
RUNNING
@@ -30,7 +33,7 @@ RUNNING
To run a single server database:
- $ mkdir /data/db
+ $ sudo mkdir -p /data/db
$ ./mongod
$
$ # The mongo javascript shell connects to localhost and test database by default:
@@ -40,7 +43,12 @@ RUNNING
DRIVERS
Client drivers for most programming languages are available at
- mongodb.org. Use the shell ("mongo") for administrative tasks.
+ https://docs.mongodb.com/manual/applications/drivers/. Use the shell
+ ("mongo") for administrative tasks.
+
+BUG REPORTS
+
+ See https://github.com/mongodb/mongo/wiki/Submit-Bug-Reports.
PACKAGING
@@ -49,26 +57,28 @@ PACKAGING
DOCUMENTATION
- http://www.mongodb.org/
+ https://docs.mongodb.com/manual/
+
+CLOUD HOSTED MONGODB
-CLOUD MANAGED MONGODB
+ https://www.mongodb.com/cloud/atlas
- http://cloud.mongodb.com/
+MAIL LISTS
-MAIL LISTS AND IRC
+ https://groups.google.com/forum/#!forum/mongodb-user
- http://dochub.mongodb.org/core/community
+ A forum for technical questions about using MongoDB.
+
+ https://groups.google.com/forum/#!forum/mongodb-dev
+
+ A forum for technical questions about building and developing MongoDB.
LEARN MONGODB
- http://university.mongodb.com/
+ https://university.mongodb.com/
LICENSE
Most MongoDB source files (src/mongo folder and below) are made available
- under the terms of the GNU Affero General Public License (AGPL). See
+ under the terms of the GNU Affero General Public License (GNU AGPLv3). See
individual files for details.
-
- As an exception, the files in the client/, debian/, rpm/,
- utils/mongoutils, and all subdirectories thereof are made available under
- the terms of the Apache License, version 2.0.
diff --git a/buildscripts/burn_in_tests.py b/buildscripts/burn_in_tests.py
index d171a5a6bc1..6e39685ef8b 100644
--- a/buildscripts/burn_in_tests.py
+++ b/buildscripts/burn_in_tests.py
@@ -17,7 +17,7 @@ import sys
import urlparse
import yaml
-API_SERVER_DEFAULT = "http://evergreen-api.mongodb.com:8080"
+API_SERVER_DEFAULT = "https://evergreen.mongodb.com"
# Get relative imports to work when the package is not installed on the PYTHONPATH.
if __name__ == "__main__" and __package__ is None:
@@ -418,6 +418,8 @@ def main():
tests_by_task = _load_tests_file(values.test_list_file)
# If there are no tests to run, carry on.
if tests_by_task is None:
+ test_results = {"failures": 0, "results": []}
+ _write_report_file(test_results, values.report_file)
sys.exit(0)
# Run the executor finder.
@@ -438,6 +440,7 @@ def main():
# If there are no changed tests, exit cleanly.
if not changed_tests:
print "No new or modified tests found."
+ _write_report_file({}, values.test_list_outfile)
sys.exit(0)
suites = resmokelib.parser.get_suites(values, changed_tests)
tests_by_executor = create_executor_list(suites, exclude_suites)
diff --git a/buildscripts/hang_analyzer.py b/buildscripts/hang_analyzer.py
index 73e1cbe6d41..09245904a7f 100755
--- a/buildscripts/hang_analyzer.py
+++ b/buildscripts/hang_analyzer.py
@@ -24,6 +24,7 @@ import signal
import subprocess
import sys
import tempfile
+import traceback
import time
from distutils import spawn
from optparse import OptionParser
@@ -53,7 +54,7 @@ def call(a, logger):
if ret != 0:
logger.error("Bad exit code %d" % (ret))
- raise Exception()
+ raise Exception("Bad exit code %d from %s" % (ret, " ".join(a)))
# Copied from python 2.7 version of subprocess.py
@@ -375,14 +376,14 @@ class GDBDumper(object):
base, ext = os.path.splitext(logger.mongo_process_filename)
raw_stacks_filename = base + '_raw_stacks' + ext
raw_stacks_commands = [
- 'echo \\nWriting raw stacks to %s.\\n' % raw_stacks_filename,
- # This sends output to log file rather than stdout until we turn logging off.
- 'set logging redirect on',
- 'set logging file ' + raw_stacks_filename,
- 'set logging on',
- 'thread apply all bt',
- 'set logging off',
- ]
+ 'echo \\nWriting raw stacks to %s.\\n' % raw_stacks_filename,
+ # This sends output to log file rather than stdout until we turn logging off.
+ 'set logging redirect on',
+ 'set logging file ' + raw_stacks_filename,
+ 'set logging on',
+ 'thread apply all bt',
+ 'set logging off',
+ ]
cmds = [
"set interactive-mode off",
@@ -732,28 +733,38 @@ def main():
# a signal handler to wait for the signal since it supports POSIX signals.
if _is_windows:
root_logger.info("Calling SetEvent to signal python process %s with PID %d" %
- (process_name, pid))
+ (process_name, pid))
signal_event_object(root_logger, pid)
else:
root_logger.info("Sending signal SIGUSR1 to python process %s with PID %d" %
- (process_name, pid))
+ (process_name, pid))
signal_process(root_logger, pid, signal.SIGUSR1)
+ trapped_exceptions = []
+
# Dump all processes, except python & java.
for (pid, process_name) in [(p, pn) for (p, pn) in processes
if not re.match("^(java|python)", pn)]:
process_logger = get_process_logger(options.debugger_output, pid, process_name)
- dbg.dump_info(
- root_logger,
- process_logger,
- pid,
- process_name,
- options.dump_core and check_dump_quota(max_dump_size_bytes, dbg.get_dump_ext()))
+ try:
+ dbg.dump_info(
+ root_logger,
+ process_logger,
+ pid,
+ process_name,
+ options.dump_core and check_dump_quota(max_dump_size_bytes, dbg.get_dump_ext()))
+ except Exception as err:
+ root_logger.info("Error encountered when invoking debugger %s" % err)
+ trapped_exceptions.append(traceback.format_exc())
# Dump java processes using jstack.
for (pid, process_name) in [(p, pn) for (p, pn) in processes if pn.startswith("java")]:
process_logger = get_process_logger(options.debugger_output, pid, process_name)
- jstack.dump_info(root_logger, process_logger, pid, process_name)
+ try:
+ jstack.dump_info(root_logger, process_logger, pid, process_name)
+ except Exception as err:
+ root_logger.info("Error encountered when invoking debugger %s" % err)
+ trapped_exceptions.append(traceback.format_exc())
# Signal go processes to ensure they print out stack traces, and die on POSIX OSes.
# On Windows, this will simply kill the process since python emulates SIGABRT as
@@ -761,10 +772,15 @@ def main():
# Note: The stacktrace output may be captured elsewhere (i.e. resmoke).
for (pid, process_name) in [(p, pn) for (p, pn) in processes if pn in go_processes]:
root_logger.info("Sending signal SIGABRT to go process %s with PID %d" %
- (process_name, pid))
+ (process_name, pid))
signal_process(root_logger, pid, signal.SIGABRT)
root_logger.info("Done analyzing all processes for hangs")
+ for exception in trapped_exceptions:
+ root_logger.info(exception)
+ if trapped_exceptions:
+ sys.exit(1)
+
if __name__ == "__main__":
main()
diff --git a/buildscripts/package_test/.kitchen.yml b/buildscripts/package_test/.kitchen.yml
index e9b6989d221..3b4a476551c 100644
--- a/buildscripts/package_test/.kitchen.yml
+++ b/buildscripts/package_test/.kitchen.yml
@@ -2,11 +2,13 @@
driver:
name: ec2
region: us-east-1
+ vpc_mode: true
+ vpc_id: <%= ENV['KITCHEN_VPC'] %>
subnet_id: <%= ENV['KITCHEN_SUBNET'] %>
security_group_ids:
- <%= ENV['KITCHEN_SECURITY_GROUP'] %>
aws_ssh_key_id: <%= ENV['KITCHEN_SSH_KEY_ID'] %>
- interface: dns
+ interface: private
associate_public_ip: true
verifier:
diff --git a/buildscripts/package_test/recipes/install_mongodb.rb b/buildscripts/package_test/recipes/install_mongodb.rb
index dcf7499289e..9b4ea703b2b 100644
--- a/buildscripts/package_test/recipes/install_mongodb.rb
+++ b/buildscripts/package_test/recipes/install_mongodb.rb
@@ -84,6 +84,16 @@ if platform_family? 'suse'
EOD
end
+ %w(
+ SLES12-Pool
+ SLES12-Updates
+ ).each do |repo|
+ execute "add #{repo}" do
+ command "zypper addrepo --check --refresh --name \"#{repo}\" http://smt-ec2.susecloud.net/repo/SUSE/Products/SLE-SERVER/12/x86_64/product?credentials=SMT-http_smt-ec2_susecloud_net 'SMT-http_smt-ec2_susecloud_net:#{repo}'"
+ not_if "zypper lr | grep #{repo}"
+ end
+ end
+
execute 'install mongod' do
command 'zypper -n install `find . -name "*server*.rpm"`'
cwd homedir
diff --git a/buildscripts/resmokeconfig/suites/aggregation_read_concern_majority_passthrough.yml b/buildscripts/resmokeconfig/suites/aggregation_read_concern_majority_passthrough.yml
index bc227b09279..72b838b3c07 100644
--- a/buildscripts/resmokeconfig/suites/aggregation_read_concern_majority_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/aggregation_read_concern_majority_passthrough.yml
@@ -6,11 +6,28 @@ selector:
- jstests/aggregation/expressions/*.js
- jstests/aggregation/sources/*/*.js
exclude_files:
- - jstests/aggregation/bugs/server18198.js # Uses a mocked mongo client to test read preference.
- - jstests/aggregation/mongos_slaveok.js # Majority read on secondary requires afterOpTime.
- - jstests/aggregation/sources/facet/use_cases.js # Cannot specify write concern when
- # secondaryThrottle is not set.
- - jstests/aggregation/testSlave.js # Majority read on secondary requires afterOpTime.
+ # These tests fail due to the inability to specify a writeConcern when secondaryThrottle is not
+ # set as part of the moveChunk command.
+ - jstests/aggregation/sources/facet/use_cases.js
+ # These test fail because afterOpTime is required to guarantee a secondary has advanced its
+ # majority-committed snapshot.
+ - jstests/aggregation/mongos_slaveok.js
+ - jstests/aggregation/testSlave.js
+ exclude_with_any_tags:
+ ##
+ # The next three tags correspond to the special errors thrown by the
+ # set_read_and_write_concerns.js override when it refuses to replace the readConcern or
+ # writeConcern of a particular command. Above each tag are the message(s) that cause the tag to
+ # be warranted.
+ ##
+ # "Cowardly refusing to override read concern of command: ..."
+ - assumes_read_concern_unchanged
+ # "Cowardly refusing to override write concern of command: ..."
+ - assumes_write_concern_unchanged
+ # "Cowardly refusing to run test with overridden write concern when it uses a command that can
+ # only perform w=1 writes: ..."
+ - requires_collmod_command
+ - requires_eval_command
executor:
js_test:
@@ -18,13 +35,19 @@ executor:
shell_options:
global_vars:
TestData:
+ defaultReadConcernLevel: majority
enableMajorityReadConcern: ''
- eval: "var testingReplication = true; load('jstests/libs/override_methods/set_majority_read_and_write_concerns.js');"
+ eval: >-
+ var testingReplication = true;
+ load('jstests/libs/override_methods/set_read_and_write_concerns.js');
readMode: commands
hooks:
- - class: ValidateCollections
+ # The CheckReplDBHash hook waits until all operations have replicated to and have been applied
+ # on the secondaries, so we run the ValidateCollections hook after it to ensure we're
+ # validating the entire contents of the collection.
- class: CheckReplOplogs
- class: CheckReplDBHash
+ - class: ValidateCollections
fixture:
class: ReplicaSetFixture
mongod_options:
@@ -33,5 +56,3 @@ executor:
enableTestCommands: 1
numInitialSyncAttempts: 1
num_nodes: 2
- # Needs to be set for any ephemeral or no-journaling storage engine
- write_concern_majority_journal_default: false
diff --git a/buildscripts/resmokeconfig/suites/read_concern_majority_passthrough.yml b/buildscripts/resmokeconfig/suites/read_concern_majority_passthrough.yml
index f47e424e1cf..22e84bc9c63 100644
--- a/buildscripts/resmokeconfig/suites/read_concern_majority_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/read_concern_majority_passthrough.yml
@@ -3,79 +3,47 @@ selector:
roots:
- jstests/core/**/*.js
exclude_files:
- # Tests that won't work with an injected 'majority' readConcern
- # and/or an injected 'majority' writeConcern. Where a function is
- # listed the reason is we don't have a reliable solution to override
- # the write concern for that function.
- - jstests/core/batch_write_command*.js # these tests use various write concerns
- - jstests/core/bench_test*.js # benchRun() used for writes
- - jstests/core/capped_update.js # uses godinsert and can't run under replication.
- - jstests/core/crud_api.js # has specific w:0 tests
- - jstests/core/error2.js # db.eval() used
- - jstests/core/eval0.js # db.eval() used
- - jstests/core/eval1.js # db.eval() used
- - jstests/core/eval3.js # db.eval() used
- - jstests/core/eval4.js # db.eval() used
- - jstests/core/eval5.js # db.eval() used
- - jstests/core/eval6.js # db.eval() used
- - jstests/core/eval7.js # db.eval() used
- - jstests/core/eval9.js # db.eval() used
- - jstests/core/evala.js # db.eval() used
- - jstests/core/evalb.js # db.eval() used
- - jstests/core/evald.js # db.eval() used
- - jstests/core/evale.js # db.eval() used
- - jstests/core/evalg.js # db.eval() used
- - jstests/core/eval_mr.js # db.eval() used
- - jstests/core/eval_nolock.js # db.eval() used
- - jstests/core/geo_s2cursorlimitskip.js # drops system.profile collection and counts ops.
- - jstests/core/js3.js # db.dbEval() used
- - jstests/core/js7.js # db.eval() used
- - jstests/core/js9.js # db.eval() used
- - jstests/core/mr_merge.js # mr temp tables aren't replicated
- - jstests/core/mr_merge2.js # mr temp tables aren't replicated
- - jstests/core/mr_outreduce.js # mr temp tables aren't replicated
- - jstests/core/mr_outreduce2.js # mr temp tables aren't replicated
- - jstests/core/opcounters_active.js # off by n problem with opcounters
- - jstests/core/opcounters_write_cmd.js # off by n problem with opcounters
- - jstests/core/profile_agg.js # system.profile not replicated
- - jstests/core/profile_count.js # system.profile not replicated
- - jstests/core/profile_delete.js # system.profile not replicated
- - jstests/core/profile_distinct.js # system.profile not replicated
- - jstests/core/profile_find.js # system.profile not replicated
- - jstests/core/profile_findandmodify.js # system.profile not replicated
- - jstests/core/profile_geonear.js # system.profile not replicated
- - jstests/core/profile_getmore.js # system.profile not replicated
- - jstests/core/profile_group.js # system.profile not replicated
- - jstests/core/profile_insert.js # system.profile not replicated
- - jstests/core/profile_mapreduce.js # system.profile not replicated
- - jstests/core/profile_update.js # system.profile not replicated
- - jstests/core/profile1.js # system.profile not replicated
- - jstests/core/profile2.js # system.profile not replicated
- - jstests/core/profile3.js # system.profile not replicated
- - jstests/core/read_after_optime.js # verifies read after optime fails on standalone
- - jstests/core/remove8.js # db.eval() used
- - jstests/core/rename4.js # db.eval() used
- - jstests/core/shell1.js # tests setSlaveOk() variations on standalone mongod
- - jstests/core/shellkillop.js # db.eval() used
- - jstests/core/shell_writeconcern.js # checks write concern shell helpers
- - jstests/core/storefunc.js # db.eval() used
- - jstests/core/write_result.js # Tests invalid writeConcern, we shouldn't override.
- # Tests that need triaging & remediation | blacklist decision
- # Comments list possible problem point under review.
- - jstests/core/capped6.js # Uses captrunc test command.
- - jstests/core/convert_to_capped_nonexistant.js # Uses convertToCapped and captrunc command.
- - jstests/core/stages_delete.js # Uses stageDebug command for deletes.
+ # These tests are not expected to pass with replica-sets:
+ - jstests/core/dbadmin.js
+ - jstests/core/opcounters_write_cmd.js
+ - jstests/core/read_after_optime.js
+ - jstests/core/capped_update.js
+ # These tests use benchRun(), which isn't configured to use the overridden writeConcern.
+ - jstests/core/bench_test*.js
+ exclude_with_any_tags:
+ ##
+ # The next three tags correspond to the special errors thrown by the
+ # set_read_and_write_concerns.js override when it refuses to replace the readConcern or
+ # writeConcern of a particular command. Above each tag are the message(s) that cause the tag to
+ # be warranted.
+ ##
+ # "Cowardly refusing to override read concern of command: ..."
+ - assumes_read_concern_unchanged
+ # "Cowardly refusing to override write concern of command: ..."
+ - assumes_write_concern_unchanged
+ # "Cowardly refusing to run test with overridden write concern when it uses a command that can
+ # only perform w=1 writes: ..."
+ - requires_collmod_command
+ - requires_eval_command
executor:
js_test:
config:
shell_options:
- eval: "var testingReplication = true; load('jstests/libs/override_methods/set_majority_read_and_write_concerns.js');"
+ global_vars:
+ TestData:
+ defaultReadConcernLevel: majority
+ eval: >-
+ var testingReplication = true;
+ load('jstests/libs/override_methods/set_read_and_write_concerns.js');
readMode: commands
hooks:
- - class: ValidateCollections
+ # The CheckReplDBHash hook waits until all operations have replicated to and have been applied
+ # on the secondaries, so we run the ValidateCollections hook after it to ensure we're
+ # validating the entire contents of the collection.
- class: CheckReplOplogs
- class: CheckReplDBHash
+ - class: ValidateCollections
- class: CleanEveryN
n: 20
fixture:
@@ -84,7 +52,6 @@ executor:
set_parameters:
enableTestCommands: 1
numInitialSyncAttempts: 1
+ writePeriodicNoops: 1
enableMajorityReadConcern: ''
num_nodes: 2
- # Needs to be set for any ephemeral or no-journaling storage engine
- write_concern_majority_journal_default: false
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
index 832e0ab9bfc..114ebcab372 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
@@ -14,6 +14,7 @@ selector:
- jstests/core/commands_that_do_not_write_do_not_accept_wc.js
- jstests/core/constructors.js
- jstests/core/eval_mr.js
+ - jstests/core/function_string_representations.js
- jstests/core/geo_big_polygon3.js
- jstests/core/geo_mapreduce.js
- jstests/core/geo_mapreduce2.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_resync_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_resync_jscore_passthrough.yml
index 3b48456c92e..1e51713a475 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_resync_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_resync_jscore_passthrough.yml
@@ -14,6 +14,7 @@ selector:
- jstests/core/commands_that_do_not_write_do_not_accept_wc.js
- jstests/core/constructors.js
- jstests/core/eval_mr.js
+ - jstests/core/function_string_representations.js
- jstests/core/geo_big_polygon3.js
- jstests/core/geo_mapreduce.js
- jstests/core/geo_mapreduce2.js
diff --git a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml
index 0bafa32a1e5..acf8ae53e96 100644
--- a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml
+++ b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml
@@ -50,6 +50,7 @@ selector:
- jstests/sharding/shard2.js
- jstests/sharding/shard3.js
- jstests/sharding/shard_collection_basic.js
+ - jstests/sharding/shard_existing_coll_chunk_count.js
- jstests/sharding/sharding_balance1.js
- jstests/sharding/sharding_balance2.js
- jstests/sharding/sharding_balance3.js
diff --git a/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml b/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml
index f4c741badd4..c7ff1b24395 100644
--- a/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml
+++ b/buildscripts/resmokeconfig/suites/sharding_last_stable_mongos_and_mixed_shards.yml
@@ -95,6 +95,8 @@ selector:
# TODO (SERVER-27379): Cannot create views when featureCompatibilityVersion is 3.2.
# Unblacklist when the featureCompatibilityVersion is 3.4 or higher.
- jstests/sharding/views.js
+ # SERVER-20392: Reenable when backported to 3.4 and released as last-stable.
+ - jstests/sharding/shard_existing_coll_chunk_count.js
executor:
js_test:
diff --git a/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml b/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml
new file mode 100644
index 00000000000..388046d2782
--- /dev/null
+++ b/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml
@@ -0,0 +1,78 @@
+test_kind: js_test
+
+selector:
+ js_test:
+ roots:
+ - jstests/core/**/*.js
+ exclude_files:
+ # These tests are not expected to pass with replica-sets:
+ - jstests/core/dbadmin.js
+ - jstests/core/opcounters_write_cmd.js
+ - jstests/core/read_after_optime.js
+ - jstests/core/capped_update.js
+ # The connection_string_validation.js test does not expect the mongo shell to be using a replica
+ # set connection string.
+ - jstests/core/connection_string_validation.js
+ # These tests attempt to read from the "system.profile" collection, which may be missing entries
+ # if a write was performed on the primary of the replica set instead.
+ - jstests/core/*profile*.js
+ # The shellkillop.js test spawns a parallel shell without using startParallelShell() and
+ # therefore doesn't inherit the w="majority" write concern when performing its writes.
+ - jstests/core/shellkillop.js
+ exclude_with_any_tags:
+ ##
+ # The next three tags correspond to the special errors thrown by the
+ # set_read_and_write_concerns.js override when it refuses to replace the readConcern or
+ # writeConcern of a particular command. Above each tag are the message(s) that cause the tag to
+ # be warranted.
+ ##
+ # "Cowardly refusing to override read concern of command: ..."
+ - assumes_read_concern_unchanged
+ # "Cowardly refusing to override write concern of command: ..."
+ - assumes_write_concern_unchanged
+ # "Cowardly refusing to run test with overridden write concern when it uses a command that can
+ # only perform w=1 writes: ..."
+ - requires_collmod_command
+ - requires_eval_command
+ ##
+ # The next tag corresponds to the special error thrown by the set_read_preference_secondary.js
+ # override when it refuses to replace the readPreference of a particular command. Above each tag
+ # are the message(s) that cause the tag to be warranted.
+ ##
+ # "Cowardly refusing to override read preference of command: ..."
+ # "Cowardly refusing to run test with overridden read preference when it reads from a
+ # non-replicated collection: ..."
+ - assumes_read_preference_unchanged
+
+executor:
+ js_test:
+ config:
+ shell_options:
+ global_vars:
+ TestData:
+ defaultReadConcernLevel: local
+ eval: >-
+ testingReplication = true;
+ load('jstests/libs/override_methods/set_read_and_write_concerns.js');
+ load('jstests/libs/override_methods/set_read_preference_secondary.js');
+ readMode: commands
+ hooks:
+ # The CheckReplDBHash hook waits until all operations have replicated to and have been applied
+ # on the secondaries, so we run the ValidateCollections hook after it to ensure we're
+ # validating the entire contents of the collection.
+ - class: CheckReplOplogs
+ - class: CheckReplDBHash
+ - class: ValidateCollections
+ - class: CleanEveryN
+ n: 20
+ fixture:
+ class: ReplicaSetFixture
+ mongod_options:
+ set_parameters:
+ enableTestCommands: 1
+ numInitialSyncAttempts: 1
+ # This suite requires w="majority" writes to be applied on all secondaries. By using a 2-node
+ # replica set and having secondaries vote, the majority of the replica set is all nodes.
+ num_nodes: 2
+ voting_secondaries: true
+ use_replica_set_connection_string: true
diff --git a/buildscripts/resmokelib/core/programs.py b/buildscripts/resmokelib/core/programs.py
index e4cadc8e006..d94cd438ee0 100644
--- a/buildscripts/resmokelib/core/programs.py
+++ b/buildscripts/resmokelib/core/programs.py
@@ -115,8 +115,8 @@ def mongos_program(logger, executable=None, process_kwargs=None, **kwargs):
return _process.Process(logger, args, **process_kwargs)
-def mongo_shell_program(logger, executable=None, filename=None, process_kwargs=None,
- isMainTest=True, **kwargs):
+def mongo_shell_program(logger, executable=None, connection_string=None, filename=None,
+ process_kwargs=None, isMainTest=True, **kwargs):
"""
Returns a Process instance that starts a mongo shell with arguments
constructed from 'kwargs'.
@@ -184,9 +184,22 @@ def mongo_shell_program(logger, executable=None, filename=None, process_kwargs=N
if config.SHELL_WRITE_MODE is not None:
kwargs["writeMode"] = config.SHELL_WRITE_MODE
+ if connection_string is not None:
+ # The --host and --port options are ignored by the mongo shell when an explicit connection
+ # string is specified. We remove these options to avoid any ambiguity with what server the
+ # logged mongo shell invocation will connect to.
+ if "port" in kwargs:
+ kwargs.pop("port")
+
+ if "host" in kwargs:
+ kwargs.pop("host")
+
# Apply the rest of the command line arguments.
_apply_kwargs(args, kwargs)
+ if connection_string is not None:
+ args.append(connection_string)
+
# Have the mongos shell run the specified file.
args.append(filename)
diff --git a/buildscripts/resmokelib/logging/buildlogger.py b/buildscripts/resmokelib/logging/buildlogger.py
index f058372df8c..e8e37d2c004 100644
--- a/buildscripts/resmokelib/logging/buildlogger.py
+++ b/buildscripts/resmokelib/logging/buildlogger.py
@@ -5,7 +5,9 @@ Defines handlers for communicating with a buildlogger server.
from __future__ import absolute_import
import functools
+import httplib
import urllib2
+import json
from . import handlers
from . import loggers
@@ -131,6 +133,47 @@ def new_test_id(build_id, build_config, test_filename, test_command):
return response["id"]
+class _LogsSplitter(object):
+ """Class with static methods used to split list of log lines into smaller batches."""
+
+ @staticmethod
+ def split_logs(log_lines, max_size):
+ """
+ Splits the log lines into batches of size less than or equal to max_size.
+
+ Args:
+ log_lines: A list of log lines.
+ max_size: The maximum size in bytes a batch of log lines can have in JSON.
+ Returns:
+ A list of list of log lines. Each item is a list is a list of log lines
+ satisfying the size requirement.
+ """
+ if not max_size:
+ return [log_lines]
+
+ def line_size(line):
+ """
+ Computes the encoded JSON size of a log line as part of an array.
+ 2 is added to each string size to account for the array representation of the logs,
+ as each line is preceded by a '[' or a space and followed by a ',' or a ']'.
+ """
+ return len(json.dumps(line, encoding="utf-8")) + 2
+
+ curr_logs = []
+ curr_logs_size = 0
+ split_logs = []
+ for line in log_lines:
+ size = line_size(line)
+ if curr_logs_size + size > max_size:
+ split_logs.append(curr_logs)
+ curr_logs = []
+ curr_logs_size = 0
+ curr_logs.append(line)
+ curr_logs_size += size
+ split_logs.append(curr_logs)
+ return split_logs
+
+
class _BaseBuildloggerHandler(handlers.BufferedHandler):
"""
Base class of the buildlogger handler for the global logs and the
@@ -138,8 +181,8 @@ class _BaseBuildloggerHandler(handlers.BufferedHandler):
"""
def __init__(self,
- build_id,
build_config,
+ endpoint,
capacity=_SEND_AFTER_LINES,
interval_secs=_SEND_AFTER_SECS):
"""
@@ -157,8 +200,9 @@ class _BaseBuildloggerHandler(handlers.BufferedHandler):
username,
password)
- self.build_id = build_id
+ self.endpoint = endpoint
self.retry_buffer = []
+ self.max_size = None
def process_record(self, record):
"""
@@ -177,12 +221,55 @@ class _BaseBuildloggerHandler(handlers.BufferedHandler):
"""
Convenience method for subclasses to use when making POST requests.
"""
-
return self.http_handler.post(*args, **kwargs)
def _append_logs(self, log_lines):
- raise NotImplementedError("_append_logs must be implemented by _BaseBuildloggerHandler"
- " subclasses")
+ """
+ Sends a POST request to the handlers endpoint with the logs that have been captured.
+
+ Returns:
+ The number of log lines that have been successfully sent.
+ """
+ lines_sent = 0
+ for chunk in _LogsSplitter.split_logs(log_lines, self.max_size):
+ chunk_lines_sent = self.__append_logs_chunk(chunk)
+ lines_sent += chunk_lines_sent
+ if chunk_lines_sent < len(chunk):
+ # Not all lines have been sent. We stop here.
+ break
+ return lines_sent
+
+ def __append_logs_chunk(self, log_lines_chunk):
+ """
+ Sends a log lines chunk, handles 413 Request Entity Too Large errors and retries
+ if necessary.
+
+ Returns:
+ The number of log lines that have been successfully sent.
+ """
+ try:
+ self.post(self.endpoint, data=log_lines_chunk)
+ return len(log_lines_chunk)
+ except urllib2.HTTPError as err:
+ # Handle the "Request Entity Too Large" error, set the max size and retry.
+ if err.code == httplib.REQUEST_ENTITY_TOO_LARGE:
+ response_data = json.load(err, "utf-8")
+ if isinstance(response_data, dict) and "max_size" in response_data:
+ new_max_size = response_data["max_size"]
+ if self.max_size and new_max_size >= self.max_size:
+ loggers._BUILDLOGGER_FALLBACK.exception(
+ "Received an HTTP 413 code, but already had max_size set")
+ return 0
+ loggers._BUILDLOGGER_FALLBACK.warning(
+ "Received an HTTP 413 code, updating the request max_size to %s",
+ new_max_size)
+ self.max_size = new_max_size
+ return self._append_logs(log_lines_chunk)
+ loggers._BUILDLOGGER_FALLBACK.exception("Encountered an error.")
+ return 0
+ except:
+ loggers._BUILDLOGGER_FALLBACK.exception("Encountered an error.")
+ return 0
def _flush_buffer_with_lock(self, buf, close_called):
"""
@@ -196,9 +283,10 @@ class _BaseBuildloggerHandler(handlers.BufferedHandler):
self.retry_buffer.extend(buf)
- if self._append_logs(self.retry_buffer):
- self.retry_buffer = []
- elif close_called:
+ nb_sent = self._append_logs(self.retry_buffer)
+ if nb_sent:
+ self.retry_buffer = self.retry_buffer[nb_sent:]
+ if close_called and self.retry_buffer:
# Request to the buildlogger server returned an error, so use the fallback logger to
# avoid losing the log messages entirely.
for (_, message) in self.retry_buffer:
@@ -215,29 +303,14 @@ class BuildloggerTestHandler(_BaseBuildloggerHandler):
Buildlogger handler for the test logs.
"""
- def __init__(self, build_id, build_config, test_id, **kwargs):
- """
- Initializes the buildlogger handler with the build id, test id,
- and credentials.
- """
-
- _BaseBuildloggerHandler.__init__(self, build_id, build_config, **kwargs)
-
- self.test_id = test_id
-
- @_log_on_error
- def _append_logs(self, log_lines):
- """
- Sends a POST request to the APPEND_TEST_LOGS_ENDPOINT with the
- logs that have been captured.
- """
+ def __init__(self, build_config, build_id, test_id,
+ capacity=_SEND_AFTER_LINES, interval_secs=_SEND_AFTER_SECS):
+ """Initializes the buildlogger handler with the credentials, build id, and test id."""
endpoint = APPEND_TEST_LOGS_ENDPOINT % {
- "build_id": self.build_id,
- "test_id": self.test_id,
+ "build_id": build_id,
+ "test_id": test_id,
}
-
- response = self.post(endpoint, data=log_lines)
- return response is not None
+ _BaseBuildloggerHandler.__init__(self, build_config, endpoint, capacity, interval_secs)
@_log_on_error
def _finish_test(self, failed=False):
@@ -245,12 +318,7 @@ class BuildloggerTestHandler(_BaseBuildloggerHandler):
Sends a POST request to the APPEND_TEST_LOGS_ENDPOINT with the
test status.
"""
- endpoint = APPEND_TEST_LOGS_ENDPOINT % {
- "build_id": self.build_id,
- "test_id": self.test_id,
- }
-
- self.post(endpoint, headers={
+ self.post(self.endpoint, headers={
"X-Sendlogs-Test-Done": "true",
"X-Sendlogs-Test-Failed": "true" if failed else "false",
})
@@ -271,12 +339,8 @@ class BuildloggerGlobalHandler(_BaseBuildloggerHandler):
Buildlogger handler for the global logs.
"""
- @_log_on_error
- def _append_logs(self, log_lines):
- """
- Sends a POST request to the APPEND_GLOBAL_LOGS_ENDPOINT with
- the logs that have been captured.
- """
- endpoint = APPEND_GLOBAL_LOGS_ENDPOINT % {"build_id": self.build_id}
- response = self.post(endpoint, data=log_lines)
- return response is not None
+ def __init__(self, build_config, build_id,
+ capacity=_SEND_AFTER_LINES, interval_secs=_SEND_AFTER_SECS):
+ """Initializes the buildlogger handler with the credentials and build id."""
+ endpoint = APPEND_GLOBAL_LOGS_ENDPOINT % {"build_id": build_id}
+ _BaseBuildloggerHandler.__init__(self, build_config, endpoint, capacity, interval_secs)
diff --git a/buildscripts/resmokelib/logging/config.py b/buildscripts/resmokelib/logging/config.py
index c3960bbafd3..c3216b2d879 100644
--- a/buildscripts/resmokelib/logging/config.py
+++ b/buildscripts/resmokelib/logging/config.py
@@ -67,8 +67,8 @@ def apply_buildlogger_global_handler(logger, logging_config, build_id=None, buil
log_format = logger_info.get("format", _DEFAULT_FORMAT)
formatter = formatters.ISO8601Formatter(fmt=log_format)
- handler = buildlogger.BuildloggerGlobalHandler(build_id,
- build_config,
+ handler = buildlogger.BuildloggerGlobalHandler(build_config,
+ build_id,
**handler_info)
handler.setFormatter(formatter)
else:
@@ -98,8 +98,8 @@ def apply_buildlogger_test_handler(logger,
log_format = logger_info.get("format", _DEFAULT_FORMAT)
formatter = formatters.ISO8601Formatter(fmt=log_format)
- handler = buildlogger.BuildloggerTestHandler(build_id,
- build_config,
+ handler = buildlogger.BuildloggerTestHandler(build_config,
+ build_id,
test_id,
**handler_info)
handler.setFormatter(formatter)
diff --git a/buildscripts/resmokelib/testing/fixtures/interface.py b/buildscripts/resmokelib/testing/fixtures/interface.py
index b4b0066a5aa..83418502ead 100644
--- a/buildscripts/resmokelib/testing/fixtures/interface.py
+++ b/buildscripts/resmokelib/testing/fixtures/interface.py
@@ -79,13 +79,22 @@ class Fixture(object):
"""
return True
- def get_connection_string(self):
+ def get_internal_connection_string(self):
"""
Returns the connection string for this fixture. This is NOT a
driver connection string, but a connection string of the format
expected by the mongo::ConnectionString class.
"""
- raise NotImplementedError("get_connection_string must be implemented by Fixture subclasses")
+ raise NotImplementedError(
+ "get_internal_connection_string must be implemented by Fixture subclasses")
+
+ def get_driver_connection_url(self):
+ """
+ Return the mongodb connection string as defined here:
+ https://docs.mongodb.com/manual/reference/connection-string/
+ """
+ raise NotImplementedError(
+ "get_driver_connection_url must be implemented by Fixture subclasses")
def __str__(self):
return "%s (Job #%d)" % (self.__class__.__name__, self.job_num)
diff --git a/buildscripts/resmokelib/testing/fixtures/masterslave.py b/buildscripts/resmokelib/testing/fixtures/masterslave.py
index 469c7ac0816..fb444cfe097 100644
--- a/buildscripts/resmokelib/testing/fixtures/masterslave.py
+++ b/buildscripts/resmokelib/testing/fixtures/masterslave.py
@@ -5,7 +5,6 @@ Master/slave fixture for executing JSTests against.
from __future__ import absolute_import
import os.path
-import socket
import pymongo
@@ -160,6 +159,12 @@ class MasterSlaveFixture(interface.ReplFixture):
mongod_options = self.mongod_options.copy()
mongod_options.update(self.slave_options)
mongod_options["slave"] = ""
- mongod_options["source"] = "%s:%d" % (socket.gethostname(), self.port)
+ mongod_options["source"] = self.master.get_internal_connection_string()
mongod_options["dbpath"] = os.path.join(self._dbpath_prefix, "slave")
return self._new_mongod(mongod_logger, mongod_options)
+
+ def get_internal_connection_string(self):
+ return self.master.get_internal_connection_string()
+
+ def get_driver_connection_url(self):
+ return self.master.get_driver_connection_url()
diff --git a/buildscripts/resmokelib/testing/fixtures/replicaset.py b/buildscripts/resmokelib/testing/fixtures/replicaset.py
index 141bbd60ea1..0ec3de30280 100644
--- a/buildscripts/resmokelib/testing/fixtures/replicaset.py
+++ b/buildscripts/resmokelib/testing/fixtures/replicaset.py
@@ -37,7 +37,8 @@ class ReplicaSetFixture(interface.ReplFixture):
write_concern_majority_journal_default=None,
auth_options=None,
replset_config_options=None,
- voting_secondaries=True):
+ voting_secondaries=False,
+ use_replica_set_connection_string=False):
interface.ReplFixture.__init__(self, logger, job_num)
@@ -50,6 +51,7 @@ class ReplicaSetFixture(interface.ReplFixture):
self.auth_options = auth_options
self.replset_config_options = utils.default_if_none(replset_config_options, {})
self.voting_secondaries = voting_secondaries
+ self.use_replica_set_connection_string = use_replica_set_connection_string
# 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
@@ -97,7 +99,7 @@ class ReplicaSetFixture(interface.ReplFixture):
# Initiate the replica set.
members = []
for (i, node) in enumerate(self.nodes):
- member_info = {"_id": i, "host": node.get_connection_string()}
+ member_info = {"_id": i, "host": node.get_internal_connection_string()}
if i > 0:
member_info["priority"] = 0
if i >= 7 or not self.voting_secondaries:
@@ -107,7 +109,7 @@ class ReplicaSetFixture(interface.ReplFixture):
members.append(member_info)
if self.initial_sync_node:
members.append({"_id": self.initial_sync_node_idx,
- "host": self.initial_sync_node.get_connection_string(),
+ "host": self.initial_sync_node.get_internal_connection_string(),
"priority": 0,
"hidden": 1,
"votes": 0})
@@ -291,11 +293,27 @@ class ReplicaSetFixture(interface.ReplFixture):
return logging.loggers.new_logger(logger_name, parent=self.logger)
- def get_connection_string(self):
+ def get_internal_connection_string(self):
if self.replset_name is None:
- raise ValueError("Must call setup() before calling get_connection_string()")
+ raise ValueError("Must call setup() before calling get_internal_connection_string()")
- conn_strs = [node.get_connection_string() for node in self.nodes]
+ conn_strs = [node.get_internal_connection_string() for node in self.nodes]
if self.initial_sync_node:
- conn_strs.append(self.initial_sync_node.get_connection_string())
+ conn_strs.append(self.initial_sync_node.get_internal_connection_string())
return self.replset_name + "/" + ",".join(conn_strs)
+
+ def get_driver_connection_url(self):
+ if self.replset_name is None:
+ raise ValueError("Must call setup() before calling get_driver_connection_url()")
+
+ if self.use_replica_set_connection_string:
+ # We use a replica set connection string when all nodes are electable because we
+ # anticipate the client will want to gracefully handle any failovers.
+ conn_strs = [node.get_internal_connection_string() for node in self.nodes]
+ if self.initial_sync_node:
+ conn_strs.append(self.initial_sync_node.get_internal_connection_string())
+ return "mongodb://" + ",".join(conn_strs) + "/?replicaSet=" + self.replset_name
+ else:
+ # We return a direct connection to the expected pimary when only the first node is
+ # electable because we want the client to error out if a stepdown occurs.
+ return self.nodes[0].get_driver_connection_url()
diff --git a/buildscripts/resmokelib/testing/fixtures/shardedcluster.py b/buildscripts/resmokelib/testing/fixtures/shardedcluster.py
index ac7e597f24b..2e2db535d6d 100644
--- a/buildscripts/resmokelib/testing/fixtures/shardedcluster.py
+++ b/buildscripts/resmokelib/testing/fixtures/shardedcluster.py
@@ -170,11 +170,14 @@ class ShardedClusterFixture(interface.Fixture):
all(shard.is_running() for shard in self.shards) and
self.mongos is not None and self.mongos.is_running())
- def get_connection_string(self):
+ def get_internal_connection_string(self):
if self.mongos is None:
- raise ValueError("Must call setup() before calling get_connection_string()")
+ raise ValueError("Must call setup() before calling get_internal_connection_string()")
- return "%s:%d" % (socket.gethostname(), self.mongos.port)
+ return self.mongos.get_internal_connection_string()
+
+ def get_driver_connection_url(self):
+ return "mongodb://" + self.get_internal_connection_string()
def _new_configsvr(self):
"""
@@ -229,16 +232,11 @@ class ShardedClusterFixture(interface.Fixture):
mongos_logger = logging.loggers.new_logger(logger_name, parent=self.logger)
mongos_options = copy.deepcopy(self.mongos_options)
- configdb_hostname = socket.gethostname()
if self.separate_configsvr:
- configdb_replset = ShardedClusterFixture._CONFIGSVR_REPLSET_NAME
- configdb_port = self.configsvr.port
- mongos_options["configdb"] = "%s/%s:%d" % (configdb_replset,
- configdb_hostname,
- configdb_port)
+ mongos_options["configdb"] = self.configsvr.get_internal_connection_string()
else:
- mongos_options["configdb"] = "%s:%d" % (configdb_hostname, self.shards[0].port)
+ mongos_options["configdb"] = "localhost:%d" % (self.shards[0].port)
return _MongoSFixture(mongos_logger,
self.job_num,
@@ -254,9 +252,9 @@ class ShardedClusterFixture(interface.Fixture):
for more details.
"""
- hostname = socket.gethostname()
- self.logger.info("Adding %s:%d as a shard..." % (hostname, shard.port))
- client.admin.command({"addShard": "%s:%d" % (hostname, shard.port)})
+ connection_string = shard.get_internal_connection_string()
+ self.logger.info("Adding %s as a shard...", connection_string)
+ client.admin.command({"addShard": connection_string})
class _MongoSFixture(interface.Fixture):
@@ -356,3 +354,12 @@ class _MongoSFixture(interface.Fixture):
def is_running(self):
return self.mongos is not None and self.mongos.poll() is None
+
+ def get_internal_connection_string(self):
+ if self.mongos is None:
+ raise ValueError("Must call setup() before calling get_internal_connection_string()")
+
+ return "localhost:%d" % self.port
+
+ def get_driver_connection_url(self):
+ return "mongodb://" + self.get_internal_connection_string()
diff --git a/buildscripts/resmokelib/testing/fixtures/standalone.py b/buildscripts/resmokelib/testing/fixtures/standalone.py
index ba62b3d2b8c..bc69775c285 100644
--- a/buildscripts/resmokelib/testing/fixtures/standalone.py
+++ b/buildscripts/resmokelib/testing/fixtures/standalone.py
@@ -7,7 +7,6 @@ from __future__ import absolute_import
import os
import os.path
import shutil
-import socket
import time
import pymongo
@@ -146,8 +145,11 @@ class MongoDFixture(interface.Fixture):
def is_running(self):
return self.mongod is not None and self.mongod.poll() is None
- def get_connection_string(self):
+ def get_internal_connection_string(self):
if self.mongod is None:
- raise ValueError("Must call setup() before calling get_connection_string()")
+ raise ValueError("Must call setup() before calling get_internal_connection_string()")
- return "%s:%d" % (socket.gethostname(), self.port)
+ return "localhost:%d" % self.port
+
+ def get_driver_connection_url(self):
+ return "mongodb://" + self.get_internal_connection_string()
diff --git a/buildscripts/resmokelib/testing/testcases.py b/buildscripts/resmokelib/testing/testcases.py
index b4029fc6ea8..21d35215a29 100644
--- a/buildscripts/resmokelib/testing/testcases.py
+++ b/buildscripts/resmokelib/testing/testcases.py
@@ -187,7 +187,7 @@ class CPPIntegrationTestCase(TestCase):
def configure(self, fixture, *args, **kwargs):
TestCase.configure(self, fixture, *args, **kwargs)
- self.program_options["connectionString"] = self.fixture.get_connection_string()
+ self.program_options["connectionString"] = self.fixture.get_internal_connection_string()
def run_test(self):
try:
@@ -362,6 +362,19 @@ class JSTestCase(TestCase):
# Directory already exists.
pass
+ process_kwargs = self.shell_options.get("process_kwargs", {}).copy()
+
+ if "KRB5_CONFIG" in process_kwargs and "KRB5CCNAME" not in process_kwargs:
+ # Use a job-specific credential cache for JavaScript tests involving Kerberos.
+ krb5_dir = os.path.join(data_dir, "krb5")
+ try:
+ os.makedirs(krb5_dir)
+ except os.error:
+ pass
+ process_kwargs["KRB5CCNAME"] = "DIR:" + os.path.join(krb5_dir, ".")
+
+ self.shell_options["process_kwargs"] = process_kwargs
+
def _get_data_dir(self, global_vars):
"""
Returns the value that the mongo shell should set for the
@@ -408,11 +421,13 @@ class JSTestCase(TestCase):
is_main_test = True
if thread_id > 0:
is_main_test = False
- return core.programs.mongo_shell_program(logger,
- executable=self.shell_executable,
- filename=self.js_filename,
- isMainTest=is_main_test,
- **self.shell_options)
+ return core.programs.mongo_shell_program(
+ logger,
+ executable=self.shell_executable,
+ filename=self.js_filename,
+ connection_string=self.fixture.get_driver_connection_url(),
+ isMainTest=is_main_test,
+ **self.shell_options)
def _run_test_in_thread(self, thread_id):
# Make a logger for each thread.
diff --git a/buildscripts/scons.py b/buildscripts/scons.py
index e7860162eb6..b0b9cfa834e 100755
--- a/buildscripts/scons.py
+++ b/buildscripts/scons.py
@@ -1,16 +1,26 @@
-#!/usr/bin/python
+#!/usr/bin/env python2
+
+from __future__ import print_function
import os
-import subprocess
import sys
SCONS_VERSION = os.environ.get('SCONS_VERSION', "2.5.0")
-mongodb_root = os.path.dirname(os.path.dirname(__file__))
-scons_dir = os.path.join(mongodb_root, 'src', 'third_party', 'scons-' + SCONS_VERSION)
+mongodb_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+scons_dir = os.path.join(mongodb_root, 'src', 'third_party','scons-' + SCONS_VERSION,
+ 'scons-local-' + SCONS_VERSION)
+
+if not os.path.exists(scons_dir):
+ print("Could not find SCons in '%s'" % (scons_dir))
+ sys.exit(1)
+
+sys.path = [scons_dir] + sys.path
+
+try:
+ import SCons.Script
+except ImportError:
+ print("Could not find SCons in '%s'" % (scons_dir))
+ sys.exit(1)
-if sys.platform == 'win32':
- args = [sys.executable, os.path.join(scons_dir, 'scons.py')] + sys.argv[1:]
- sys.exit(subprocess.call(args))
-else:
- os.execv(os.path.join(scons_dir, 'scons.py'), sys.argv)
+SCons.Script.main()
diff --git a/buildscripts/tests/resmokelib/logging/test_buildlogger.py b/buildscripts/tests/resmokelib/logging/test_buildlogger.py
new file mode 100644
index 00000000000..7bc705948db
--- /dev/null
+++ b/buildscripts/tests/resmokelib/logging/test_buildlogger.py
@@ -0,0 +1,67 @@
+"""Unit tests for the buildscripts.resmokelib.logging.buildlogger module."""
+
+from __future__ import absolute_import
+
+import json
+import unittest
+
+from buildscripts.resmokelib.logging import buildlogger
+
+
+class TestLogsSplitter(unittest.TestCase):
+ """Unit tests for the _LogsSplitter class."""
+
+ def test_split_no_logs(self):
+ logs = []
+ max_size = 10
+ self.assertEqual([[]], buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ def test_split_no_max_size(self):
+ logs = self.__generate_logs(size=30)
+ max_size = None
+ self.assertEqual([logs], buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ def test_split_max_size_smaller(self):
+ logs = self.__generate_logs(size=20)
+ max_size = 30
+ self.assertEqual([logs], buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ def test_split_max_size_equal(self):
+ logs = self.__generate_logs(size=30)
+ max_size = 30
+ self.assertEqual([logs], buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ def test_split_max_size_larger(self):
+ logs = self.__generate_logs(size=31)
+ max_size = 30
+ self.assertEqual(
+ [logs[0:-1], logs[-1:]],
+ buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ logs = self.__generate_logs(size=149)
+ max_size = 19
+ self.assertEqual(
+ [logs[0:3], logs[3:6], logs[6:9], logs[9:12], logs[12:15],
+ logs[15:18], logs[18:21], logs[21:24], logs[24:27], logs[27:]],
+ buildlogger._LogsSplitter.split_logs(logs, max_size))
+
+ def check_split_sizes(self, splits, max_size):
+ for split in splits:
+ self.assertTrue(TestLogsSplitter.size(split) <= max_size)
+
+ def __generate_logs(self, size):
+ # The size of [ "x" ] is 5. This is the minimum size we generate.
+ self.assertTrue(size >= 5)
+ # Each new "x" adds 5 to the size.
+ nb_lines = size / 5
+ # Each additional "x" on a line adds 1 to the size.
+ last_line_extra = size % 5
+ logs = ["x"] * nb_lines
+ logs[-1] += "x" * last_line_extra
+ self.assertEqual(size, TestLogsSplitter.size(logs))
+ return logs
+
+ @staticmethod
+ def size(logs):
+ """Returns the size of the log lines when represented in JSON."""
+ return len(json.dumps(logs, encoding="utf-8"))
diff --git a/distsrc/README b/distsrc/README
index 8cc83742981..5a772b66e7d 100644
--- a/distsrc/README
+++ b/distsrc/README
@@ -1,22 +1,30 @@
MongoDB README
-Welcome to MongoDB!
-
+Welcome to MongoDB 3.4!
+
COMPONENTS
- bin/mongod - The database process.
- bin/mongos - Sharding controller.
- bin/mongo - The database shell (uses interactive javascript).
-
-UTILITIES
-
- bin/mongodump - MongoDB dump tool - for backups, snapshots, etc..
- bin/mongorestore - MongoDB restore a dump
- bin/mongoexport - Export a single collection to test (JSON, CSV)
- bin/mongoimport - Import from JSON or CSV
- bin/mongofiles - Utility for putting and getting files from MongoDB GridFS
- bin/mongostat - Show performance statistics
-
+ mongod - The database server.
+ mongos - Sharding router.
+ mongo - The database shell (uses interactive javascript).
+
+UTILITIES
+
+ mongodump - Create a binary dump of the contents of a database.
+ mongorestore - Restore data from the output created by mongodump.
+ mongoexport - Export the contents of a collection to JSON or CSV.
+ mongoimport - Import data from JSON, CSV or TSV.
+ mongofiles - Put, get and delete files from GridFS.
+ mongostat - Show the status of a running mongod/mongos.
+ bsondump - Convert BSON files into human-readable formats.
+ mongooplog - Poll the oplog and apply to a local server.
+ mongoreplay - Traffic capture and replay tool.
+ mongotop - Track time spent reading and writing data.
+
+BUILDING
+
+ See docs/building.md.
+
RUNNING
For command line options invoke:
@@ -25,28 +33,52 @@ RUNNING
To run a single server database:
- $ mkdir /data/db
+ $ sudo mkdir -p /data/db
$ ./mongod
$
$ # The mongo javascript shell connects to localhost and test database by default:
- $ ./mongo
+ $ ./mongo
> help
DRIVERS
- Client drivers for most programming languages are available at mongodb.org. Use the
- shell ("mongo") for administrative tasks.
+ Client drivers for most programming languages are available at
+ https://docs.mongodb.com/manual/applications/drivers/. Use the shell
+ ("mongo") for administrative tasks.
+
+BUG REPORTS
+
+ See https://github.com/mongodb/mongo/wiki/Submit-Bug-Reports.
+
+PACKAGING
+
+ Packages are created dynamically by the package.py script located in the
+ buildscripts directory. This will generate RPM and Debian packages.
DOCUMENTATION
- http://www.mongodb.org/
-
-MAIL LISTS AND IRC
-
- http://dochub.mongodb.org/core/community
+ https://docs.mongodb.com/manual/
+
+CLOUD HOSTED MONGODB
+
+ https://www.mongodb.com/cloud/atlas
+
+MAIL LISTS
+
+ https://groups.google.com/forum/#!forum/mongodb-user
+
+ A forum for technical questions about using MongoDB.
+
+ https://groups.google.com/forum/#!forum/mongodb-dev
+
+ A forum for technical questions about building and developing MongoDB.
+
+LEARN MONGODB
+
+ https://university.mongodb.com/
-32 BIT BUILD NOTES
+LICENSE
- MongoDB uses memory mapped files. If built as a 32 bit executable, you will
- not be able to work with large (multi-gigabyte) databases. However, 32 bit
- builds work fine with small development databases.
+ Most MongoDB source files (src/mongo folder and below) are made available
+ under the terms of the GNU Affero General Public License (GNU AGPLv3). See
+ individual files for details.
diff --git a/docs/building.md b/docs/building.md
index 6008a9b606f..48f1a188396 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -4,7 +4,7 @@ Building MongoDB
To build MongoDB, you will need:
* A modern C++ compiler. One of the following is required.
- * GCC 4.8.2 or newer
+ * GCC 5.3.0 or newer
* Clang 3.4 (or Apple XCode 5.1.1 Clang) or newer
* Visual Studio 2013 Update 2 or newer
* Python 2.7
diff --git a/etc/drivers_nightly.yml b/etc/drivers_nightly.yml
index f039112ea85..464f844ac57 100644
--- a/etc/drivers_nightly.yml
+++ b/etc/drivers_nightly.yml
@@ -3,7 +3,7 @@
#####################################################
### Instructions for Adding a Driver ###
-# The Major sections are:
+## The Major sections are:
# VARIABLES
# FUNCTIONS (MongoDB and Drivers)
@@ -218,8 +218,9 @@ functions:
- command: shell.exec
type: test
params:
- working_dir: "src/${DRIVER_WORKING_DIRECTORY}"
+ working_dir: src
script: |
+ cd ${DRIVER_WORKING_DIRECTORY}
${PREPARE_SHELL}
echo $DRIVERS_TOOLS
echo $MONGO_ORCHESTRATION_HOME
@@ -318,8 +319,9 @@ functions:
"setup drivers environment":
- command: shell.exec
params:
- working_dir: "src/${DRIVER_WORKING_DIRECTORY}"
+ working_dir: src
script: |
+ cd "${DRIVER_WORKING_DIRECTORY}"
# Get the current unique version of this checkout
if [ "${is_patch}" = "true" ]; then
CURRENT_VERSION=$(git describe)-patch-${version_id}
@@ -433,10 +435,7 @@ tasks:
if [ `which strip` ]; then
echo "found strip"
find . -maxdepth 1 -type f -iname "mongo*" -exec strip {} \;
- strip mongo/mongo
fi
- # On RHEL, the mongo shell binary is in a mongo directory
- mv mongo/mongo ./mongodb-binaries/bin/ || true
find . -maxdepth 1 -type f -iname "mongo*" -exec mv {} ./mongodb-binaries/bin/ \;
${compress} mongodb-binaries.${ext|tgz} mongodb-binaries/
@@ -584,7 +583,6 @@ axes:
display_name: "Python 2.6"
variables:
### There is no python toolchain on RHEL 6.2, using system python
- ### PYTHON_BINARY: "/opt/python/2.6/bin/python"
PYTHON_BINARY: "/usr/bin/python"
DRIVER_WORKING_DIRECTORY: "mongo-python-driver"
DRIVER_TEST_UPLOAD_DIRECTORY: "src/mongo-python-driver/xunit-results/TEST-*.xml"
@@ -629,7 +627,7 @@ modules:
- name: mongo-python-driver
repo: git@github.com:mongodb/mongo-python-driver.git
- branch: evergreen
+ branch: master
#######################################
# BUILDVARIANTS #
diff --git a/etc/evergreen.yml b/etc/evergreen.yml
index fae0563fbde..520a49b77f5 100644
--- a/etc/evergreen.yml
+++ b/etc/evergreen.yml
@@ -113,7 +113,7 @@ variables:
- enterprise-debian71-64
- enterprise-debian81-64
- enterprise-linux-64-amazon-ami
- - enterprise-osx-107
+ - enterprise-osx-108
- enterprise-rhel-62-64-bit
- enterprise-rhel-62-64-bit-coverage
- enterprise-rhel-62-64-bit-inmem
@@ -145,17 +145,15 @@ variables:
- linux-64-ephemeralForTest
- linux-64-lsm
- linux-64-repeated-execution
- - osx-107
- - osx-107-debug
- - osx-107-ssl
+ - osx-108
+ - osx-108-debug
+ - osx-108-ssl
- rhel62
- rhel70
- - solaris-64-bit
- suse11
- suse12
- ubuntu1204
- ubuntu1404
- - ubuntu1404-rocksdb
- ubuntu1604
- ubuntu1604-arm64
- ubuntu1604-asan
@@ -238,9 +236,6 @@ functions:
if [ "Windows_NT" = "$OS" ]; then
typeperf -qx PhysicalDisk | grep Disk | grep -v _Total > disk_counters.txt
typeperf -cf disk_counters.txt -si 5 -o mongo-diskstats
- # Solaris: iostat -T d option for timestamp.
- elif iostat -T d -Mx > /dev/null 2>&1; then
- iostat -T d -Mx 5 > mongo-diskstats
# Linux: iostat -t option for timestamp.
elif iostat -tdmx > /dev/null 2>&1; then
iostat -tdmx 5 > mongo-diskstats
@@ -285,11 +280,8 @@ functions:
get_pids() { proc_pids=$(pgrep $1); }
get_process_info() {
proc_name=$(ps -p $1 -o comm=);
- # prstat is available on Solaris
- if [ ! -z $(which prstat 2> /dev/null) ]; then
- proc_threads=$(prstat -p $1 1 1 | grep $1 | cut -f2 -d "/");
# /proc is available on Linux platforms
- elif [ -f /proc/$1/status ]; then
+ if [ -f /proc/$1/status ]; then
${set_sudo}
proc_threads=$($sudo grep Threads /proc/$1/status | sed "s/\s//g" | cut -f2 -d ":");
else
@@ -365,19 +357,6 @@ functions:
mv mongodb*/mongo.{debug,dSYM,pdb} mongodb*/mongod.{debug,dSYM,pdb} mongodb*/mongos.{debug,dSYM,pdb} . 2>/dev/null || true
rm -r mongodb*/*.{debug,dSYM,pdb} mongo-debugsymbols.tgz 2>/dev/null || true
- "build rocksdb" :
- command: shell.exec
- params:
- script: |
- set -o errexit
- set -o verbose
- if [ "${build_rocksdb|}" = "true" ]; then
- rm -rf rocksdb
- git clone https://github.com/facebook/rocksdb.git
- cd rocksdb
- make CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ static_lib
- fi
-
"build new tools" :
command: shell.exec
params:
@@ -441,6 +420,13 @@ functions:
set -o errexit
set -o verbose
+ # Set the TMPDIR environment variable to be a directory in the task's working
+ # directory so that temporary files created by processes spawned by resmoke.py get
+ # cleaned up after the task completes. This also ensures the spawned processes
+ # aren't impacted by limited space in the mount point for the /tmp directory.
+ export TMPDIR="${workdir}/tmp"
+ mkdir -p $TMPDIR
+
# check if virtualenv is set up
if [ -d "venv" ]; then
if [ "Windows_NT" = "$OS" ]; then
@@ -524,7 +510,7 @@ functions:
path_value="$path_value:${task_path_suffix}"
fi
- ${resmoke_wrapper} PATH="$path_value" ${san_symbolizer} ${san_options} ${rlp_environment} ${python|/opt/mongodbtoolchain/v2/bin/python2} buildscripts/resmoke.py ${resmoke_args} $extra_args ${test_flags} --log=buildlogger --reportFile=report.json
+ ${resmoke_wrapper} PATH="$path_value" ${san_symbolizer} ${lang_environment} ${san_options} ${rlp_environment} ${python|/opt/mongodbtoolchain/v2/bin/python2} buildscripts/resmoke.py ${resmoke_args} $extra_args ${test_flags} --log=buildlogger --reportFile=report.json
"do jepsen setup" :
@@ -852,10 +838,18 @@ functions:
script: |
set -o errexit
+ # Override the aws credentials with the kitchen specific credentials
+ cat <<EOF > ~/.aws/credentials
+ [default]
+ aws_access_key_id = ${kitchen_aws_key}
+ aws_secret_access_key = ${kitchen_aws_secret}
+ EOF
+
export KITCHEN_ARTIFACTS_URL="https://s3.amazonaws.com/mciuploads/${project}/${build_variant}/${revision}/artifacts/${build_id}.tgz"
export KITCHEN_SECURITY_GROUP="${kitchen_security_group}"
export KITCHEN_SSH_KEY_ID="${kitchen_ssh_key_id}"
export KITCHEN_SUBNET="${kitchen_subnet}"
+ export KITCHEN_VPC="${kitchen_vpc}"
for i in {1..3}
do
@@ -1125,6 +1119,12 @@ post:
if [ ! -f $new_binary_file ]; then
mv $binary_file_location $new_binary_file
fi
+ # On Windows if a .pdb symbol file exists, include it in the archive.
+ pdb_file=$(echo $binary_file_location | sed "s/\.exe/.pdb/")
+ if [ -f $pdb_file ]; then
+ new_pdb_file=unittest_binaries/$(echo $pdb_file | sed "s/.*\///")
+ mv $pdb_file $new_pdb_file
+ fi
done
done
- command: archive.targz_pack
@@ -1239,14 +1239,12 @@ tasks:
directory: src
revisions: # for each module include revision as <module_name> : ${<module_name>_rev}
enterprise: ${enterprise_rev}
- rocksdb: ${rocksdb_rev}
- command: git.apply_patch
params:
directory: src
- func: "get buildnumber"
- func: "setup credentials"
- func: "build new tools" # noop if ${newtools} is not "true"
- - func: "build rocksdb" # noop if ${build_rocksdb} is not "true"
- command: shell.exec
params:
working_dir: src
@@ -1295,14 +1293,6 @@ tasks:
${strip_command|/usr/bin/strip} mongobridge
fi
- # If this is a scheduled build, we check for changes against the last scheduled commit.
- if [ "${is_patch}" != "true" ]; then
- burn_in_args="--checkEvergreen"
- fi
-
- # Capture a list of new and modified tests.
- ${python|/opt/mongodbtoolchain/v2/bin/python2} buildscripts/burn_in_tests.py --branch=${branch_name} --buildVariant=${build_variant} --testListOutfile=jstests/new_tests.json --noExec $burn_in_args
-
# On windows we need to make sure the paths in unittests.txt are compatible with cygwin tar
sed 's|\\|/|g' build/unittests.txt > build/unittests-tarlist.txt
${tar|tar} -czvf mongodb-unittests.tgz -T build/unittests-tarlist.txt build/unittests.txt
@@ -1421,7 +1411,6 @@ tasks:
directory: src
revisions: # for each module include revision as <module_name> : ${<module_name>_rev}
enterprise: ${enterprise_rev}
- rocksdb: ${rocksdb_rev}
- command: git.apply_patch
params:
directory: src
@@ -1440,7 +1429,38 @@ tasks:
depends_on:
- name: compile
commands:
+ - command: git.get_project
+ # The repository is cloned in a directory distinct from src for the modified test detection
+ # because the extraction of the artifacts performed in the 'do setup' causes
+ # 'git diff --name-only' to see all tests as modified on Windows (git 1.9.5). See SERVER-30634.
+ params:
+ directory: burn_in_tests_clonedir
+ revisions: # for each module include revision as <module_name> : ${<module_name>_rev}
+ enterprise: ${enterprise_rev}
+ - command: shell.exec
- func: "do setup"
+ - func: "set up virtualenv"
+ - command: shell.exec
+ params:
+ working_dir: burn_in_tests_clonedir
+ script: |
+ set -o errexit
+ # Create a symbolic link to the venv in the src directory so activate_virtualenv can use it
+ # if it exists.
+ ln -s ../src/venv venv
+ ${activate_virtualenv}
+ set -o verbose
+ # If this is a scheduled build, we check for changes against the last scheduled commit.
+ if [ "${is_patch}" != "true" ]; then
+ burn_in_args="--checkEvergreen"
+ fi
+ # Copy the dbtest executable from the src dir because burn_in_tests.py calls it to get the
+ # list of dbtest suites.
+ cp ../src/dbtest${exe} .
+ # Capture a list of new and modified tests.
+ ${python|/opt/mongodbtoolchain/v2/bin/python2} buildscripts/burn_in_tests.py --branch=${branch_name} --buildVariant=${build_variant} --testListOutfile=jstests/new_tests.json --noExec $burn_in_args
+ # Copy the results to the src dir.
+ cp jstests/new_tests.json ../src/jstests/new_tests.json
- func: "do multiversion setup"
- func: "run tests"
vars:
@@ -2717,6 +2737,17 @@ tasks:
run_multiple_jobs: true
- <<: *task_template
+ name: write_concern_majority_passthrough_WT
+ depends_on:
+ - name: jsCore_WT
+ commands:
+ - func: "do setup"
+ - func: "run tests"
+ vars:
+ resmoke_args: --suites=write_concern_majority_passthrough --storageEngine=wiredTiger
+ run_multiple_jobs: true
+
+- <<: *task_template
name: replica_sets
commands:
- func: "do setup"
@@ -3097,7 +3128,7 @@ tasks:
/usr/local/bin/notary-client.py --key-name "server-3.4" --auth-token-file ${workdir}/src/signing_auth_token --comment "Evergreen Automatic Signing ${revision} - ${build_variant} - ${branch_name}" --notary-url http://notary-service.build.10gen.cc:5000 --skip-missing mongodb-${push_name}-${push_arch}-${suffix}.${ext|tgz} mongodb-shell-${push_name}-${push_arch}-${suffix}.${ext|tgz} mongodb-${push_name}-${push_arch}-debugsymbols-${suffix}.${ext|tgz} mongodb-win32-${push_arch}-${suffix}.msi mongodb-src-${src_suffix}.${ext|tar.gz}
if [ "${has_packages|}" = "true" ]; then
- CURATOR_RELEASE="88f34a9f1c79db7ea9597b6e85eb5995d03714ba"
+ CURATOR_RELEASE="ea8d75dcc1a587111e7418e2428fb67e267af9fe"
curl -L -O http://boxes.10gen.com/build/curator/curator-dist-rhel70-$CURATOR_RELEASE.tar.gz
tar -zxvf curator-dist-rhel70-$CURATOR_RELEASE.tar.gz
./curator repo --config ./etc/repo_config.yaml --distro ${packager_distro} --edition ${repo_edition} --version ${version} --arch ${packager_arch} --packages repo
@@ -3513,11 +3544,6 @@ modules:
prefix: src/mongo/db/modules
branch: v3.4
-- name: rocksdb
- repo: git@github.com:mongodb-partners/mongo-rocks.git
- prefix: src/mongo/db/modules
- branch: v3.4
-
#######################################
# Buildvariants #
#######################################
@@ -3650,6 +3676,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
- name: linux-64-repeated-execution
@@ -3767,6 +3794,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: linux-64-duroff
display_name: Linux (No Journal)
@@ -4159,6 +4187,7 @@ buildvariants:
push_bucket: downloads.mongodb.org
push_name: linux
push_arch: x86_64-ubuntu1604
+ lang_environment: LANG=C
compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 -j$(grep -c ^processor /proc/cpuinfo) --release CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
multiversion_platform_arch: "ubuntu1604"
multiversion_edition: "targeted"
@@ -4283,6 +4312,7 @@ buildvariants:
push_arch: arm64-enterprise-ubuntu1604
compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 -j$(grep -c ^processor /proc/cpuinfo) CCFLAGS="-march=armv8-a+crc -mtune=generic" --release CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
+ max_jobs: 8 # Avoid starting too many mongod's on ARM test servers
variant_excluded_flags: requires_mmapv1
has_packages: true
packager_script: packager-enterprise.py
@@ -4370,6 +4400,7 @@ buildvariants:
push_arch: arm64-ubuntu1604
compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 -j$(grep -c ^processor /proc/cpuinfo) --release CCFLAGS="-march=armv8-a+crc -mtune=generic" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
+ max_jobs: 8 # Avoid starting too many mongod's on ARM test servers
variant_excluded_flags: requires_mmapv1
has_packages: true
packager_script: packager.py
@@ -4381,62 +4412,8 @@ buildvariants:
- name: compile
distros:
- ubuntu1604-arm64-large
- - name: aggregation_WT
- - name: aggregation_auth
- - name: auth_WT
- name: dbtest_WT
- - name: failpoints
- - name: failpoints_auth
- - name: gle_auth_WT
- - name: gle_auth_write_cmd_WT
- - name: gle_auth_basics_passthrough_WT
- - name: gle_auth_basics_passthrough_write_cmd_WT
- - name: sharding_gle_auth_basics_passthrough_WT
- - name: sharding_gle_auth_basics_passthrough_write_cmd_WT
- name: jsCore_WT
- - name: jsCore_compatibility_WT
- - name: jsCore_decimal_WT
- - name: jstestfuzz_WT
- - name: jstestfuzz_concurrent_WT
- - name: jstestfuzz_concurrent_replication_WT
- - name: jstestfuzz_concurrent_sharded_WT
- - name: jstestfuzz_replication_WT
- - name: jstestfuzz_sharded_WT
- - name: mongosTest
- - name: noPassthrough_WT
- - name: noPassthroughWithMongod_WT
- - name: bulk_gle_passthrough_WT
- - name: parallel_WT
- - name: parallel_compatibility_WT
- - name: concurrency_WT
- distros:
- # There is a known performance issue with the ubuntu1604-arm64-1.linaro.build.10gen.cc host
- # that's part of the ubuntu1604-arm64-large pool. We run the concurrency tests only on the
- # hosts from the ubuntu1604-arm64-small pool to work around this issue.
- - ubuntu1604-arm64-small
- - name: concurrency_replication_WT
- distros:
- - ubuntu1604-arm64-small
- - name: concurrency_sharded_WT
- distros:
- - ubuntu1604-arm64-small
- - name: concurrency_simultaneous_WT
- distros:
- - ubuntu1604-arm64-small
- - name: replica_sets_WT
- - name: replica_sets_auth
- - name: replica_sets_jscore_passthrough_WT
- - name: master_slave_WT
- - name: master_slave_auth
- - name: master_slave_jscore_passthrough_WT
- - name: sharding_WT
- - name: sharding_auth
- - name: slow1_WT
- - name: serial_run_WT
- - name: sharding_jscore_passthrough_WT
- - name: ssl
- - name: sslSpecial
- - name: tool_WT
- name: unittests
- name: push
distros:
@@ -4529,6 +4506,7 @@ buildvariants:
- ubuntu1604-zseries-large
- ubuntu1604-zseries-small
batchtime: 1440 # 1 day
+ stepback: false
expansions:
gorootvars: PATH=/opt/mongodbtoolchain/v2/bin:$PATH
tooltags: -gccgoflags "$(pkg-config --libs --cflags libssl libcrypto libsasl2)" -tags 'sasl ssl'
@@ -4536,7 +4514,7 @@ buildvariants:
push_bucket: downloads.10gen.com
push_name: linux
push_arch: s390x-enterprise-ubuntu1604
- compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 --release -j$(grep -c ^processor /proc/cpuinfo) CCFLAGS="-march=z196 -mtune=zEC12" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
+ compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 --release -j3 CCFLAGS="-march=z196 -mtune=zEC12" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
num_jobs_available: 2
variant_excluded_flags: requires_mmapv1
has_packages: true
@@ -4621,6 +4599,7 @@ buildvariants:
- name: sslSpecial
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- ubuntu1604-test
@@ -5055,6 +5034,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- rhel70-small
@@ -5194,6 +5174,9 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
+ distros:
+ - windows-64-vs2015-large
- name: enterprise-windows-64
display_name: "* Enterprise Windows"
@@ -5447,8 +5430,8 @@ buildvariants:
# OSX buildvariants #
###########################################
-- name: osx-107
- display_name: OS X 10.7
+- name: osx-108
+ display_name: OS X 10.8
run_on:
- macos-1012
batchtime: 1440 # 1 day
@@ -5457,8 +5440,8 @@ buildvariants:
push_bucket: downloads.mongodb.org
push_name: osx
push_arch: x86_64
- gorootvars: CGO_CFLAGS=-mmacosx-version-min=10.7 CGO_LDFLAGS=-mmacosx-version-min=10.7
- compile_flags: --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.7 --libc++
+ gorootvars: CGO_CFLAGS=-mmacosx-version-min=10.8 CGO_LDFLAGS=-mmacosx-version-min=10.8
+ compile_flags: --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.8 --libc++
python: python2.7
num_jobs_available: 1
build_mongoreplay: true
@@ -5547,8 +5530,8 @@ buildvariants:
distros:
- rhel70-small
-- name: osx-107-ssl
- display_name: SSL OS X 10.7
+- name: osx-108-ssl
+ display_name: SSL OS X 10.8
run_on:
- macos-1012
batchtime: 1440 # 1 day
@@ -5558,8 +5541,8 @@ buildvariants:
push_name: osx-ssl
push_arch: x86_64
tooltags: "-tags ssl"
- gorootvars: CGO_CPPFLAGS=-I/opt/mongodbtoolchain/v2/include CGO_CFLAGS=-mmacosx-version-min=10.7 CGO_LDFLAGS=-mmacosx-version-min=10.7
- compile_flags: --ssl --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.7 --libc++ CPPPATH=/opt/mongodbtoolchain/v2/include
+ gorootvars: CGO_CPPFLAGS=-I/opt/mongodbtoolchain/v2/include CGO_CFLAGS=-mmacosx-version-min=10.8 CGO_LDFLAGS=-mmacosx-version-min=10.8
+ compile_flags: --ssl --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.8 --libc++ CPPPATH=/opt/mongodbtoolchain/v2/include
python: python2.7
num_jobs_available: 1
build_mongoreplay: true
@@ -5644,12 +5627,13 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- rhel70-small
-- name: osx-107-debug
- display_name: "* OS X 10.7 DEBUG"
+- name: osx-108-debug
+ display_name: "* OS X 10.8 DEBUG"
run_on:
- macos-1012
expansions:
@@ -5658,8 +5642,8 @@ buildvariants:
push_name: osx-debug
push_arch: x86_64
num_jobs_available: 1
- gorootvars: CGO_CFLAGS=-mmacosx-version-min=10.7 CGO_LDFLAGS=-mmacosx-version-min=10.7
- compile_flags: --dbg=on --opt=on --allocator=system -j$(sysctl -n hw.logicalcpu) --osx-version-min=10.7 --libc++
+ gorootvars: CGO_CFLAGS=-mmacosx-version-min=10.8 CGO_LDFLAGS=-mmacosx-version-min=10.8
+ compile_flags: --dbg=on --opt=on --allocator=system -j$(sysctl -n hw.logicalcpu) --osx-version-min=10.8 --libc++
python: python2.7
build_mongoreplay: true
tasks:
@@ -5689,8 +5673,8 @@ buildvariants:
- name: tool_WT
- name: unittests
-- name: enterprise-osx-107
- display_name: Enterprise OS X 10.7
+- name: enterprise-osx-108
+ display_name: Enterprise OS X 10.8
modules:
- enterprise
run_on:
@@ -5702,8 +5686,8 @@ buildvariants:
push_name: osx
push_arch: x86_64-enterprise
tooltags: "-tags 'ssl sasl'"
- gorootvars: CGO_CPPFLAGS=-I/opt/mongodbtoolchain/v2/include CGO_CFLAGS=-mmacosx-version-min=10.7 CGO_LDFLAGS=-mmacosx-version-min=10.7
- compile_flags: --ssl --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.7 --libc++ CPPPATH=/opt/mongodbtoolchain/v2/include
+ gorootvars: CGO_CPPFLAGS=-I/opt/mongodbtoolchain/v2/include CGO_CFLAGS=-mmacosx-version-min=10.8 CGO_LDFLAGS=-mmacosx-version-min=10.8
+ compile_flags: --ssl --allocator=system -j$(sysctl -n hw.logicalcpu) --release --osx-version-min=10.8 --libc++ CPPPATH=/opt/mongodbtoolchain/v2/include
python: python2.7
num_jobs_available: 1
build_mongoreplay: true
@@ -5961,6 +5945,9 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
+ distros:
+ - rhel62-large
- name: package
distros:
- ubuntu1604-packer
@@ -6113,6 +6100,7 @@ buildvariants:
- name: ssl
- name: sslSpecial
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: enterprise-rhel-70-64-bit
display_name: Enterprise RHEL 7.0
@@ -6526,6 +6514,7 @@ buildvariants:
- name: sslSpecial
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- rhel70-small
@@ -6628,6 +6617,7 @@ buildvariants:
- name: sslSpecial
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- rhel70-small
@@ -6763,6 +6753,7 @@ buildvariants:
# - name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- rhel62-small
@@ -6921,6 +6912,7 @@ buildvariants:
push_path: linux
push_bucket: downloads.10gen.com
push_name: linux
+ lang_environment: LANG=C
push_arch: x86_64-enterprise-ubuntu1604
compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 --release -j$(grep -c ^processor /proc/cpuinfo) CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
@@ -7053,6 +7045,7 @@ buildvariants:
- suse12-zseries-build
- suse12-zseries-test
batchtime: 1440 # 1 day
+ stepback: false
expansions:
gorootvars: PATH=/opt/mongodbtoolchain/v2/bin:$PATH
tooltags: -gccgoflags "$(pkg-config --libs --cflags libssl libsasl2)" -tags 'sasl ssl'
@@ -7060,7 +7053,7 @@ buildvariants:
push_bucket: downloads.10gen.com
push_name: linux
push_arch: s390x-enterprise-suse12
- compile_flags: --ssl MONGO_DISTMOD=suse12 --release -j$(( $(grep -c ^processor /proc/cpuinfo) / 2 )) CCFLAGS="-march=z196 -mtune=zEC12" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
+ compile_flags: --ssl MONGO_DISTMOD=suse12 --release -j3 CCFLAGS="-march=z196 -mtune=zEC12" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
variant_excluded_flags: requires_mmapv1
has_packages: true
@@ -7142,6 +7135,7 @@ buildvariants:
- name: sslSpecial
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- suse12-test
@@ -7327,6 +7321,7 @@ buildvariants:
run_on:
- suse11-zseries-build
batchtime: 1440 # 1 day
+ stepback: false
expansions:
gorootvars: PATH=/opt/mongodbtoolchain/v2/bin:$PATH
tooltags: -gccgoflags "$(pkg-config --libs --cflags libssl libsasl2)" -tags 'sasl ssl'
@@ -7334,7 +7329,7 @@ buildvariants:
push_bucket: downloads.10gen.com
push_name: linux
push_arch: s390x-enterprise-suse11
- compile_flags: --ssl MONGO_DISTMOD=suse11 --release -j$(grep -c ^processor /proc/cpuinfo) CCFLAGS="-march=z9-109 -mtune=z10" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy --use-s390x-crc32=off
+ compile_flags: --ssl MONGO_DISTMOD=suse11 --release -j3 CCFLAGS="-march=z9-109 -mtune=z10" CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy --use-s390x-crc32=off
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
variant_excluded_flags: requires_mmapv1
has_packages: true
@@ -7413,6 +7408,7 @@ buildvariants:
- name: sslSpecial
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: push
distros:
- suse11-test
@@ -7535,94 +7531,6 @@ buildvariants:
###########################################
-# Solaris buildvariants #
-###########################################
-
-- name: solaris-64-bit
- display_name: "* Solaris"
- run_on:
- - solaris
- expansions:
- push_path: sunos5
- push_bucket: downloads.mongodb.org
- push_name: sunos5
- push_arch: x86_64
- gorootvars: PATH=/opt/mongodbtoolchain/v2/bin:$PATH
- tooltags: -gccgoflags "-lsocket -lnsl"
- compile_flags: CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy -j$(kstat cpu | sort -u | grep -c "^module") --release CCFLAGS="-m64" LINKFLAGS="-m64 -static-libstdc++ -static-libgcc" OBJCOPY=/opt/mongodbtoolchain/bin/objcopy
- num_jobs_available: $(( $(kstat cpu | sort -u | grep -c "^module") / 2 ))
- use_scons_cache: true
- tasks:
- - name: compile
- - name: aggregation
- - name: aggregation_WT
- - name: auth
- - name: auth_WT
- - name: bulk_gle_passthrough
- - name: bulk_gle_passthrough_WT
- - name: concurrency
- - name: concurrency_WT
- - name: concurrency_replication
- - name: concurrency_replication_WT
- - name: concurrency_sharded
- - name: concurrency_sharded_WT
- - name: concurrency_simultaneous
- - name: concurrency_simultaneous_WT
- - name: dbtest
- - name: dbtest_WT
- - name: disk
- - name: durability
- - name: failpoints
- - name: httpinterface
- - name: jsCore
- - name: jsCore_compatibility
- - name: jsCore_compatibility_WT
- - name: jsCore_decimal
- - name: jsCore_decimal_WT
- - name: jsCore_WT
- - name: jstestfuzz
- - name: jstestfuzz_WT
- - name: jstestfuzz_replication
- - name: jstestfuzz_replication_WT
- - name: jstestfuzz_sharded
- - name: jstestfuzz_sharded_WT
- - name: mmap
- - name: mongosTest
- - name: noPassthrough
- - name: noPassthroughWithMongod
- - name: noPassthroughWithMongod_WT
- - name: noPassthrough_WT
- - name: parallel
- - name: parallel_compatibility
- - name: parallel_compatibility_WT
- - name: parallel_WT
- - name: replica_sets
- - name: replica_sets_WT
- - name: replica_sets_jscore_passthrough
- - name: replica_sets_jscore_passthrough_WT
- - name: master_slave
- - name: master_slave_WT
- - name: master_slave_jscore_passthrough
- - name: master_slave_jscore_passthrough_WT
- - name: sharding
- - name: sharded_collections_jscore_passthrough
- - name: sharded_collections_jscore_passthrough_WT
- - name: sharding_jscore_passthrough
- - name: sharding_jscore_passthrough_WT
- - name: sharding_jscore_passthrough_wire_ops_WT
- - name: sharding_WT
- - name: slow1
- - name: slow1_WT
- - name: serial_run
- - name: serial_run_WT
- - name: tool
- - name: tool_WT
- - name: unittests
- - name: push
- distros:
- - rhel70-small
-
-###########################################
# Debian buildvariants #
###########################################
@@ -8075,6 +7983,7 @@ buildvariants:
- name: sslSpecial
- name: tool
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: linux-64-ephemeralForTest
display_name: Linux (ephemeralForTest)
@@ -8232,6 +8141,7 @@ buildvariants:
- name: sslSpecial
- name: tool
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: enterprise-rhel-72-s390x-inmem
display_name: Enterprise RHEL 7.2 s390x (inMemory) DEBUG
@@ -8319,75 +8229,7 @@ buildvariants:
- name: sslSpecial
- name: tool
- name: unittests
-
-- name: ubuntu1404-rocksdb
- display_name: Ubuntu 14.04 (RocksDB)
- modules:
- - rocksdb
- run_on:
- - ubuntu1404-test
- batchtime: 1440 # 1 day
- expansions:
- build_rocksdb: true
- compile_flags: -j$(grep -c ^processor /proc/cpuinfo) --dbg=off --opt=on CPPPATH=$(readlink -f ../rocksdb/include/) LIBPATH=$(readlink -f ../rocksdb/) LIBS=rocksdb CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy VARIANT_DIR=release --runtime-hardening=off
- num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
- test_flags: --storageEngine=rocksdb
- use_scons_cache: true
- build_mongoreplay: true
- tasks:
- - name: compile
- distros:
- - ubuntu1404-build
- - name: aggregation
- - name: aggregation_WT
- - name: aggregation_auth
- - name: auth
- - name: dbtest
- # - name: disk
- # - name: durability
- # - name: dur_jscore_passthrough
- - name: failpoints
- - name: failpoints_auth
- - name: gle_auth
- - name: gle_auth_write_cmd
- - name: gle_auth_basics_passthrough
- - name: gle_auth_basics_passthrough_write_cmd
- - name: sharding_gle_auth_basics_passthrough
- - name: sharding_gle_auth_basics_passthrough_write_cmd
- - name: jsCore
- - name: jsCore_auth
- - name: jsCore_compatibility
- - name: jstestfuzz
- - name: jstestfuzz_concurrent_WT
- - name: jstestfuzz_concurrent_replication_WT
- - name: jstestfuzz_concurrent_sharded_WT
- - name: jstestfuzz_replication
- - name: jstestfuzz_sharded
- - name: noPassthrough
- - name: noPassthroughWithMongod
- - name: bulk_gle_passthrough
- - name: parallel
- - name: parallel_compatibility
- - name: concurrency
- - name: concurrency_replication
- - name: concurrency_sharded
- # We use machines with more RAM to avoid OOM issues when validating collections.
- distros:
- - ubuntu1404-build
- - name: concurrency_simultaneous
- - name: replica_sets
- - name: replica_sets_auth
- - name: replica_sets_jscore_passthrough
- - name: master_slave
- - name: master_slave_WT
- - name: master_slave_auth
- - name: master_slave_jscore_passthrough
- - name: sharding
- - name: sharding_auth
- - name: slow1
- - name: serial_run
- - name: sharding_jscore_passthrough
- - name: unittests
+ - name: write_concern_majority_passthrough_WT
###########################################
# Experimental buildvariants #
@@ -8405,6 +8247,7 @@ buildvariants:
tooltags: "-tags 'ssl'"
# We need llvm-symbolizer in the PATH for ASAN for clang-3.7 or later.
variant_path_suffix: /usr/lib/llvm-3.8/bin
+ lang_environment: LANG=C
san_options: LSAN_OPTIONS="suppressions=etc/lsan.suppressions" ASAN_OPTIONS=detect_leaks=1
compile_flags: CC=/usr/bin/clang-3.8 CXX=/usr/bin/clang++-3.8 CPPDEFINES="_GLIBCXX_USE_CXX11_ABI=0" --dbg=on --opt=on --allocator=system --sanitize=address --ssl -j$(grep -c ^processor /proc/cpuinfo) --nostrip VARIANT_DIR=build
multiversion_platform_arch: "ubuntu1604"
@@ -8538,6 +8381,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: ubuntu1604-asan
display_name: ~ ASAN SSL Ubuntu 16.04
@@ -8549,6 +8393,7 @@ buildvariants:
tooltags: "-tags 'ssl'"
# We need llvm-symbolizer in the PATH for ASAN for clang-3.7 or later.
variant_path_suffix: /usr/lib/llvm-3.8/bin
+ lang_environment: LANG=C
san_options: LSAN_OPTIONS="suppressions=etc/lsan.suppressions" ASAN_OPTIONS=detect_leaks=1
compile_flags: CC=/usr/bin/clang-3.8 CXX=/usr/bin/clang++-3.8 CPPDEFINES="_GLIBCXX_USE_CXX11_ABI=0" --opt=on --allocator=system --sanitize=address --ssl -j$(grep -c ^processor /proc/cpuinfo) --nostrip VARIANT_DIR=build
num_jobs_available: $(($(grep -c ^processor /proc/cpuinfo) / 3)) # Avoid starting too many mongod's under ASAN build.
@@ -8580,6 +8425,7 @@ buildvariants:
variant_path_suffix: /usr/lib/llvm-3.8/bin
gorootvars: GOROOT=/opt/go PATH="/opt/go/bin:$PATH"
tooltags: "-tags 'ssl sasl'"
+ lang_environment: LANG=C
san_options: UBSAN_OPTIONS="print_stacktrace=1"
compile_flags: CC=/usr/bin/clang-3.8 CXX=/usr/bin/clang++-3.8 --dbg=on --opt=on --allocator=system --sanitize=undefined --ssl -j$(grep -c ^processor /proc/cpuinfo) --nostrip CXXFLAGS="-nostdlib -nostdinc++" LIBPATH=/opt/mongodbtoolchain/v2/lib/gcc/x86_64-mongodb-linux/5.4.0 CPPPATH="/opt/mongodbtoolchain/v2/include/c++/5.4.0/ /opt/mongodbtoolchain/v2/include/c++/5.4.0/x86_64-mongodb-linux" CPPDEFINES="_GLIBCXX_USE_CXX11_ABI=0" VARIANT_DIR=build
multiversion_platform_arch: "ubuntu1604"
@@ -8712,6 +8558,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: enterprise-ubuntu-dynamic-1604-64-bit
display_name: "* Shared Library Enterprise Ubuntu 16.04"
@@ -8720,6 +8567,7 @@ buildvariants:
expansions:
gorootvars: GOROOT=/opt/go PATH="/opt/go/bin:$PATH"
tooltags: "-tags 'ssl sasl'"
+ lang_environment: LANG=C
compile_flags: --ssl MONGO_DISTMOD=ubuntu1604 -j$(grep -c ^processor /proc/cpuinfo) CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy --link-model=dynamic
num_jobs_available: $(grep -c ^processor /proc/cpuinfo)
use_scons_cache: true
@@ -8861,6 +8709,7 @@ buildvariants:
- name: tool
- name: tool_WT
- name: unittests
+ - name: write_concern_majority_passthrough_WT
- name: initsync-3dot2-rhel-62
display_name: "~ Initial Sync 3.2 Enterprise RHEL 6.2"
@@ -8962,3 +8811,6 @@ buildvariants:
- rhel62-large
- name: slow1
- name: slow1_WT
+ - name: write_concern_majority_passthrough_WT
+ distros:
+ - rhel62-large
diff --git a/etc/longevity.yml b/etc/longevity.yml
index f6da942e0c1..9cdf9502fa7 100644
--- a/etc/longevity.yml
+++ b/etc/longevity.yml
@@ -9,45 +9,22 @@ post:
params:
working_dir: work
script: |
- set -v
source ./dsienv.sh
- $DSI_PATH/bin/make_artifact.sh
+ make_artifact.sh
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
- local_file: work/reports.tgz
- remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/${task_name}-${build_id}.${ext|tgz}
+ local_file: work/dsi-artifacts.tgz
+ remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/dsi-artifacts-${task_name}-${build_id}-${execution}.${ext|tgz}
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
- display_name: test-log
- - command: s3.put
- params:
- aws_key: ${aws_key}
- aws_secret: ${aws_secret}
- local_file: work/reports/graphs/timeseries-p1.html
- remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/timeseries-p1-${task_name}-${build_id}.html
- bucket: mciuploads
- permissions: public-read
- content_type: text/html
- display_name: timeseries-p1.html
+ display_name: Dsi Artifacts - Execution ${execution}
- command: attach.results
params:
file_location: work/report.json
- - command: shell.exec
- # destroy the cluster
- params:
- working_dir: work
- script: |
- set -e
- set -o verbose
- # call terraform destroy twice to avoid AWS timeout
- yes yes | ./terraform destroy --var-file=cluster.json
- yes yes | ./terraform destroy --var-file=cluster.json
- # clean all file to be safe
- rm -rf *
- echo "Cluster DESTROYED."
+ - func: "destroy cluster"
- command: shell.exec
params:
working_dir: src
@@ -79,12 +56,17 @@ functions:
working_dir: work
script: |
cat > bootstrap.yml <<EOF
- cluster_type: ${cluster}
+ infrastructure_provisioning: ${cluster}
platform: ${platform}
- setup: ${setup}
+ mongodb_setup: ${setup}
storageEngine: ${storageEngine}
- test: ${test}.longevity
+ test_control: ${test}.longevity
production: true
+ workloads_dir: ../src/workloads/workloads
+ ycsb_dir: ../src/YCSB/YCSB
+
+ # compositions of expansions
+ mongodb_binary_archive: "https://s3.amazonaws.com/mciuploads/${project}/${version_id}/${revision}/${platform}/mongod-${version_id}.tar.gz"
EOF
cat > runtime.yml <<EOF
@@ -106,11 +88,6 @@ functions:
ext: ${ext}
script_flags : ${script_flags}
dsi_rev: ${dsi_rev}
- compare_task: ${compare_task}
- workloads_rev: ${workloads_rev}
-
- # compositions of expansions
- mongodb_binary_archive: "https://s3.amazonaws.com/mciuploads/${project}/${version_id}/${revision}/${platform}/mongod-${version_id}.tar.gz"
EOF
- command: shell.exec
params:
@@ -133,8 +110,6 @@ functions:
- command: shell.exec
params:
working_dir: work
- # setup execution environment
- # configure environment
script: |
set -e
virtualenv ./venv
@@ -147,35 +122,25 @@ functions:
set -v
set -e
source work/dsienv.sh
- $DSI_PATH/bin/setup-dsi-env.sh
+ setup-dsi-env.sh
ls -a work
- "infrastructure provisioning":
+ "deploy cluster":
- command: shell.exec
- # call infrastructure-provisioning.sh. This will either create a cluster, or update tags on existing instances.
params:
working_dir: work
script: |
set -e
set -v
source ./dsienv.sh
- export PRODUCTION=true
- $DSI_PATH/bin/infrastructure_provisioning.sh ${cluster}
-
- "configure mongodb cluster":
- - command: shell.exec
- # bring up the mongod
- params:
- working_dir: work
- script: |
- set -e
- set -o verbose
- source ./dsienv.sh
source ./venv/bin/activate
- $DSI_PATH/bin/mongodb_setup.py && echo "${setup} MongoDB Cluster STARTED."
+ infrastructure_provisioning.py
+ workload_setup.py
+ mongodb_setup.py
"run test":
- command: shell.exec
+ type: test
params:
working_dir: work
script: |
@@ -183,9 +148,7 @@ functions:
set -v
source ./dsienv.sh
source ./venv/bin/activate
- echo "Run test for ${test}-${storageEngine} with setup ${setup}"
- $DSI_PATH/bin/run_test.py ${storageEngine} ${test} ${cluster}
- echo "Complete test for ${test} with setup ${setup}!"
+ test_control.py
- command: "json.send"
params:
name: "perf"
@@ -199,27 +162,18 @@ functions:
script: |
set -e
set -o verbose
- source ./dsienv.sh
# Longevity runs so rarely, we simply teardown the cluster when done.
- # Note that nowadays infrastructure_teardown.sh is actually copying the terraform.tfstate into /data/infrastructure_provisioning
+ # Note that nowadays infrastructure_teardown.py is actually copying the terraform.tfstate into /data/infrastructure_provisioning
# but as of this writing the rhel70-perf-longevity distro didn't actually use the teardown hook.
source ./dsienv.sh
- $DSI_PATH/bin/infrastructure_teardown.sh
+ source ./venv/bin/activate
+ infrastructure_teardown.py
echo "Cluster DESTROYED."
echo
echo "All perf results"
cd ..
cat perf.json | egrep "name|ops_per_sec"
- "make test log artifact":
- - command: shell.exec
- params:
- working_dir: work
- script: |
- set -v
- source ./dsienv.sh
- $DSI_PATH/bin/make_artifact.sh
-
"analyze":
- command: json.get_history
params:
@@ -242,8 +196,8 @@ functions:
set -o verbose
TAG="3.2.1-Baseline"
PROJECT="mongo-longevity"
- OVERRIDEFILE="../src/dsi/dsi/analysis/v3.4/longevity_override.json"
- python -u ../src/dsi/dsi/analysis/post_run_check.py ${script_flags} --reports-analysis reports --perf-file reports/perf.json --rev ${revision} -f history.json -t tags.json --refTag $TAG --overrideFile $OVERRIDEFILE --project_id $PROJECT --task_name ${task_name} --variant ${build_variant}
+ OVERRIDEFILE="../src/dsi/dsi/analysis/${branch_name}/longevity_override.json"
+ python -u ../src/dsi/dsi/analysis/post_run_check.py ${script_flags} --reports-analysis reports --perf-file perf.json --rev ${revision} -f history.json -t tags.json --refTag $TAG --overrideFile $OVERRIDEFILE --project_id $PROJECT --task_name ${task_name} --variant ${build_variant}
tasks:
- name: compile
@@ -313,13 +267,8 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "ycsb"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "ycsb"
- - func: "make test log artifact"
- func: "analyze"
vars:
script_flags: --ycsb-throughput-analysis reports
@@ -334,13 +283,8 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "ycsb"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "ycsb"
- - func: "make test log artifact"
- func: "analyze"
vars:
script_flags: --ycsb-throughput-analysis reports
@@ -359,6 +303,11 @@ modules:
branch: master
+
+#######################################
+# Linux Buildvariants #
+#######################################
+
buildvariants:
- name: linux-wt-shard
display_name: Linux WT Shard
diff --git a/etc/perf.yml b/etc/perf.yml
index 3dbc7baa61d..db116a42887 100644
--- a/etc/perf.yml
+++ b/etc/perf.yml
@@ -86,7 +86,7 @@ functions:
set -v
chmod +x mongod
chmod +x mongo
- git clone https://github.com/mongodb/mongo-perf perf
+ git clone git@github.com:mongodb/mongo-perf.git perf
cd perf
git describe --tags
- command: shell.exec
@@ -158,11 +158,9 @@ functions:
script: |
set -o errexit
source ./venv/bin/activate
- REFTAGS="3.4.2-Baseline 3.2.12-Baseline 3.0.14-Baseline" # These are project specific
+ REFTAGS="3.4.9-Baseline 3.2.17-Baseline 3.0.15-Baseline" # These are project specific
# Project Opts are project specific, but don't match expansion from elsewhere.
- # We could change dsi/analysis/v3.4 to dsi/analysis/perf-3.4 in DSI to get dsi/analysis/${project}/perf_override.json
- # Note project_id performance doesn't match string perf used elsewhere for ${project}
- PROJECT_OPTS="--overrideFile ../dsi/analysis/v3.4/perf_override.json --project_id performance"
+ PROJECT_OPTS="--overrideFile ../dsi/analysis/${branch_name}/perf_override.json --project_id performance"
python -u ../dsi/analysis/dashboard_gen.py --rev ${revision} -f history.json -t tags.json --refTag $REFTAGS $PROJECT_OPTS --variant ${build_variant} --task ${task_name} --jira-user ${perf_jira_user} --jira-password ${perf_jira_pw} || true
- command: "json.send"
params:
@@ -182,8 +180,8 @@ functions:
# appropriate flags if it's `true`.
reports_analysis_flags="--reports-analysis . --perf-file perf/perf.json"
cmd_flags=$([ "${reports_analysis}" = "true" ] && echo "$reports_analysis_flags" || echo "")
- REFTAG="3.2.12-Baseline"
- OVERRIDE="../dsi/analysis/v3.4/perf_override.json" # Note use of v3.4 here cannot use ${project}
+ REFTAG="3.2.17-Baseline"
+ OVERRIDE="../dsi/analysis/${branch_name}/perf_override.json"
python ../dsi/analysis/perf_regression_check.py $cmd_flags -f history.json --rev ${revision} -t tags.json --refTag $REFTAG --overrideFile $OVERRIDE --variant ${build_variant} --task ${task_name} --threshold 0.10 --threadThreshold 0.15
"run perf tests":
- command: shell.exec
@@ -196,6 +194,7 @@ functions:
source ./venv/bin/activate
pip install argparse
- command: shell.exec
+ type : test
params:
working_dir: src
script: |
@@ -495,7 +494,7 @@ buildvariants:
- name: linux-wt-repl
display_name: 1-Node ReplSet Linux inMemory
- batchtime: 360 # 6 hours
+ batchtime: 90 # 1.5 hours
expansions:
mongod_exec_wrapper: *exec_wrapper
perf_exec_wrapper: *perf_wrapper
@@ -511,7 +510,7 @@ buildvariants:
- name: linux-mmap-repl
display_name: 1-Node ReplSet Linux MMAPv1
- batchtime: 360 # 6 hours
+ batchtime: 90 # 1.5 hours
expansions:
mongod_exec_wrapper: *exec_wrapper
perf_exec_wrapper: *perf_wrapper
diff --git a/etc/system_perf.yml b/etc/system_perf.yml
index 79f2a3d1a89..137bc2b4a13 100644
--- a/etc/system_perf.yml
+++ b/etc/system_perf.yml
@@ -10,37 +10,41 @@ post:
working_dir: work
script: |
source ./dsienv.sh
- $DSI_PATH/bin/make_artifact.sh
+ make_artifact.sh
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
- local_file: work/reports.tgz
- remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/${task_name}-${build_id}.${ext|tgz}
+ local_file: work/dsi-artifacts.tgz
+ remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/dsi-artifacts-${task_name}-${build_id}-${execution}.${ext|tgz}
bucket: mciuploads
permissions: public-read
content_type: ${content_type|application/x-gzip}
- display_name: test-log
+ display_name: Dsi Artifacts - Execution ${execution}
- command: s3.put
params:
aws_key: ${aws_key}
aws_secret: ${aws_secret}
- local_file: work/reports/graphs/timeseries-p1.html
- remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/timeseries-p1-${task_name}-${build_id}.html
+ local_file: src/workloads/workloads/jsdoc/jsdocs-redirect.html
+ remote_file: ${project}/${build_variant}/${revision}/${task_id}/${version_id}/logs/workloads-${task_name}-${build_id}.html
bucket: mciuploads
permissions: public-read
content_type: text/html
- display_name: timeseries-p1.html
+ display_name: workloads documentation
- command: attach.results
params:
file_location: work/report.json
+ - command: "json.send"
+ params:
+ name: "perf"
+ file: "work/perf.json"
- command: shell.exec
params:
working_dir: work
script: |
source ./dsienv.sh
if [ -e /data/infrastructure_provisioning/terraform/provisioned.${cluster} ]; then
- $DSI_PATH/bin/mark_idle.sh
+ mark_idle.sh
fi
- command: shell.exec
@@ -75,12 +79,15 @@ functions:
working_dir: work
script: |
cat > bootstrap.yml <<EOF
- cluster_type: ${cluster}
+ infrastructure_provisioning: ${cluster}
platform: ${platform}
- setup: ${setup}
+ mongodb_setup: ${setup}
storageEngine: ${storageEngine}
- test: ${test}
+ test_control: ${test}
production: true
+ mongodb_binary_archive: "https://s3.amazonaws.com/mciuploads/${project}/${version_id}/${revision}/${platform}/mongod-${version_id}.tar.gz"
+ workloads_dir: ../src/workloads/workloads
+ ycsb_dir: ../src/YCSB/YCSB
EOF
cat > runtime.yml <<EOF
@@ -102,11 +109,7 @@ functions:
ext: ${ext}
script_flags : ${script_flags}
dsi_rev: ${dsi_rev}
- compare_task: ${compare_task}
workloads_rev: ${workloads_rev}
-
- # compositions of expansions
- mongodb_binary_archive: "https://s3.amazonaws.com/mciuploads/${project}/${version_id}/${revision}/${platform}/mongod-${version_id}.tar.gz"
EOF
- command: shell.exec
params:
@@ -136,44 +139,32 @@ functions:
virtualenv ./venv
source ./venv/bin/activate
pip install -r ../src/dsi/dsi/requirements.txt
- python ../src/dsi/dsi/bin/bootstrap.py --production
- ls
- pwd
+ python ../src/dsi/dsi/bin/bootstrap.py
- command: shell.exec
params:
script: |
set -v
set -e
source work/dsienv.sh
- $DSI_PATH/bin/setup-dsi-env.sh
+ setup-dsi-env.sh
ls -a work
- "infrastructure provisioning":
+ "deploy cluster":
- command: shell.exec
- # call infrastructure-provisioning.sh. This will either create a cluster, or update tags on existing instances.
params:
working_dir: work
script: |
set -e
set -v
source ./dsienv.sh
- export PRODUCTION=true
- $DSI_PATH/bin/infrastructure_provisioning.sh ${cluster}
-
- "configure mongodb cluster":
- - command: shell.exec
- # bring up the mongod
- params:
- working_dir: work
- script: |
- set -e
- set -o verbose
- source ./dsienv.sh
source ./venv/bin/activate
- $DSI_PATH/bin/mongodb_setup.py && echo "${setup} MongoDB Cluster STARTED."
+ infrastructure_provisioning.py
+ workload_setup.py
+ mongodb_setup.py
"run test":
- command: shell.exec
+ type: test
params:
working_dir: work
script: |
@@ -181,21 +172,12 @@ functions:
set -v
source ./dsienv.sh
source ./venv/bin/activate
- echo "Run test for ${test}-${storageEngine} with setup ${setup}"
- $DSI_PATH/bin/run_test.py ${storageEngine} ${test} ${cluster}
+ test_control.py
- command: "json.send"
params:
name: "perf"
file: "work/perf.json"
- "make test log artifact":
- - command: shell.exec
- params:
- working_dir: work
- script: |
- source ./dsienv.sh
- $DSI_PATH/bin/make_artifact.sh
-
"analyze":
- command: json.get_history
params:
@@ -216,8 +198,8 @@ functions:
silent: true
script: |
set -o errexit
- TAGS="3.2.15-Baseline"
- OVERRIDEFILE="../src/dsi/dsi/analysis/v3.4/system_perf_override.json"
+ TAGS="3.2.17-Baseline 3.4.9-Baseline"
+ OVERRIDEFILE="../src/dsi/dsi/analysis/${branch_name}/system_perf_override.json"
python -u ../src/dsi/dsi/analysis/dashboard_gen.py --rev ${revision} -f history.json -t tags.json --refTag $TAGS --overrideFile $OVERRIDEFILE --project_id sys-perf --variant ${build_variant} --task ${task_name} --jira-user ${perf_jira_user} --jira-password ${perf_jira_pw} || true
- command: "json.send"
params:
@@ -231,48 +213,9 @@ functions:
script: |
set -o errexit
set -o verbose
- TAG="3.2.15-Baseline"
- OVERRIDEFILE="../src/dsi/dsi/analysis/v3.4/system_perf_override.json"
- python -u ../src/dsi/dsi/analysis/post_run_check.py ${script_flags} --reports-analysis reports --perf-file reports/perf.json --rev ${revision} -f history.json -t tags.json --refTag $TAG --overrideFile $OVERRIDEFILE --project_id sys-perf --variant ${build_variant} --task ${task_name}
-
- "compare":
- - command: shell.exec
- params:
- script: |
- set -o verbose
- rm -rf ./src ./work
- mkdir src
- mkdir work
- - command: manifest.load
- - command: git.get_project
- params:
- directory: src
- revisions: # for each module include revision as <module_name> : ${<module_name>_rev}
- dsi: ${dsi_rev}
- - command: json.get
- params:
- task: ${compare_task}
- variant : ${variant1}
- file: "work/standalone.json"
- name: "perf"
- - command: json.get
- params:
- task: ${compare_task}
- variant : ${variant2}
- file: "work/oplog.json"
- name: "perf"
- - command: shell.exec
- type : test
- params:
- working_dir: work
- script: |
- set -o errexit
- set -o verbose
- python -u ../src/dsi/dsi/analysis/compare.py -b standalone.json -c oplog.json
- - command: "json.send"
- params:
- name: "perf"
- file: "work/perf.json"
+ TAG="3.2.17-Baseline"
+ OVERRIDEFILE="../src/dsi/dsi/analysis/${branch_name}/system_perf_override.json"
+ python -u ../src/dsi/dsi/analysis/post_run_check.py ${script_flags} --reports-analysis reports --perf-file perf.json --rev ${revision} -f history.json -t tags.json --refTag $TAG --overrideFile $OVERRIDEFILE --project_id sys-perf --variant ${build_variant} --task ${task_name}
#######################################
# Tasks #
@@ -323,8 +266,17 @@ tasks:
then
echo "Fetching JS test DB correctness checks from directory jstests/hooks"
cp -a jstests/hooks/* mongodb/jstests/hooks
+
+ echo "Now adding our own special run_validate_collections.js wrapper"
+ mv mongodb/jstests/hooks/run_validate_collections.js mongodb/jstests/hooks/run_validate_collections.actual.js
+
+ cat << EOF > mongodb/jstests/hooks/run_validate_collections.js
+ print("NOTE: run_validate_collections.js will skip the oplog!");
+ TestData = { skipValidationNamespaces: ['local.oplog.rs'] };
+ load('jstests/hooks/run_validate_collections.actual.js');
+ EOF
fi
- tar cvf mongodb.tar mongodb
+ tar cf mongodb.tar mongodb
gzip mongodb.tar
- command: s3.put
params:
@@ -341,25 +293,20 @@ tasks:
depends_on:
- name: compile
variant: linux-standalone
+ priority: 5
commands:
- func: "prepare environment"
vars:
storageEngine: "wiredTiger"
test: "ycsb"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "ycsb"
- - func: "make test log artifact"
- func: "analyze"
vars:
script_flags: --ycsb-throughput-analysis reports
- name: industry_benchmarks_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -368,20 +315,46 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "ycsb"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
+ - func: "deploy cluster"
+ - func: "run test"
+ - func: "analyze"
vars:
- storageEngine: "mmapv1"
+ script_flags: --ycsb-throughput-analysis reports
+
+- name: industry_benchmarks_wmajority_WT
+ priority: 5
+ depends_on:
+ - name: compile
+ variant: linux-standalone
+ commands:
+ - func: "prepare environment"
+ vars:
+ storageEngine: "wiredTiger"
+ test: "ycsb-wmajority"
+ - func: "deploy cluster"
- func: "run test"
+ - func: "analyze"
+ vars:
+ script_flags: --ycsb-throughput-analysis reports
+
+- name: industry_benchmarks_wmajority_MMAPv1
+ priority: 5
+ depends_on:
+ - name: compile
+ variant: linux-standalone
+ commands:
+ - func: "prepare environment"
vars:
storageEngine: "mmapv1"
- test: "ycsb"
- - func: "make test log artifact"
+ test: "ycsb-wmajority"
+ - func: "deploy cluster"
+ - func: "run test"
- func: "analyze"
vars:
script_flags: --ycsb-throughput-analysis reports
- name: core_workloads_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -390,18 +363,12 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "core"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "core"
- - func: "make test log artifact"
- func: "analyze"
- name: core_workloads_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -410,18 +377,12 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "core"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "core"
- - func: "make test log artifact"
- func: "analyze"
- name: non_sharded_workloads_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -430,18 +391,12 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "non_sharded"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "non_sharded"
- - func: "make test log artifact"
- func: "analyze"
- name: non_sharded_workloads_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -450,18 +405,12 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "non_sharded"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "non_sharded"
- - func: "make test log artifact"
- func: "analyze"
- name: mongos_workloads_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -470,18 +419,12 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "mongos"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "mongos"
- - func: "make test log artifact"
- func: "analyze"
- name: mongos_workloads_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -491,18 +434,12 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "mongos"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "mongos"
- - func: "make test log artifact"
- func: "analyze"
- name: move_chunk_workloads_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -511,18 +448,12 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "move_chunk"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "move_chunk"
- - func: "make test log artifact"
- func: "analyze"
- name: move_chunk_workloads_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -531,18 +462,12 @@ tasks:
vars:
storageEngine: "mmapv1"
test: "move_chunk"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "move_chunk"
- - func: "make test log artifact"
- func: "analyze"
- name: secondary_performance_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -550,21 +475,15 @@ tasks:
- func: "prepare environment"
vars:
storageEngine: "wiredTiger"
+ # Unfortunately the dash/underscore style is different for mongodb_setup and test_control
test: "secondary_performance"
- setup: "replica-2node"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
- setup: "replica-2node"
+ setup: "secondary-performance"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "secondary_performance"
- - func: "make test log artifact"
- func: "analyze"
- name: secondary_performance_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -572,85 +491,15 @@ tasks:
- func: "prepare environment"
vars:
storageEngine: "mmapv1"
+ # Unfortunately the dash/underscore style is different for mongodb_setup and test_control
test: "secondary_performance"
- setup: "replica-2node"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
- setup: "replica-2node"
+ setup: "secondary-performance"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "secondary_performance"
- - func: "make test log artifact"
- - func: "analyze"
-
-- name: industry_benchmarks_WT_oplog_comp
- depends_on:
- - name: industry_benchmarks_WT
- variant: linux-standalone
- status : "*"
- - name: industry_benchmarks_WT
- variant: linux-1-node-replSet
- status: "*"
- commands:
- - func: "compare"
- vars:
- compare_task: "industry_benchmarks_WT"
- variant1: "linux-standalone"
- variant2: "linux-1-node-replSet"
- - func: "analyze"
-
-- name: industry_benchmarks_MMAPv1_oplog_comp
- depends_on:
- - name: industry_benchmarks_MMAPv1
- variant: linux-standalone
- status: "*"
- - name: industry_benchmarks_MMAPv1
- variant: linux-1-node-replSet
- status: "*"
- commands:
- - func: "compare"
- vars:
- compare_task: "industry_benchmarks_MMAPv1"
- variant1: "linux-standalone"
- variant2: "linux-1-node-replSet"
- - func: "analyze"
-
-- name: core_workloads_WT_oplog_comp
- depends_on:
- - name: core_workloads_WT
- variant: linux-standalone
- status: "*"
- - name: core_workloads_WT
- variant: linux-1-node-replSet
- status: "*"
- commands:
- - func: "compare"
- vars:
- compare_task: "core_workloads_WT"
- variant1: "linux-standalone"
- variant2: "linux-1-node-replSet"
- - func: "analyze"
-
-- name: core_workloads_MMAPv1_oplog_comp
- depends_on:
- - name: core_workloads_MMAPv1
- variant: linux-standalone
- status: "*"
- - name: core_workloads_MMAPv1
- variant: linux-1-node-replSet
- status: "*"
- commands:
- - func: "compare"
- vars:
- compare_task: "core_workloads_MMAPv1"
- variant1: "linux-standalone"
- variant2: "linux-1-node-replSet"
- func: "analyze"
- name: initialsync_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -658,19 +507,13 @@ tasks:
- func: "prepare environment"
vars:
storageEngine: "wiredTiger"
- test: "initialSync"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ test: "initialsync"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "initialSync"
- - func: "make test log artifact"
- func: "analyze"
- name: initialsync_MMAPv1
+ priority: 5
depends_on:
- name: compile
variant : linux-standalone
@@ -678,19 +521,13 @@ tasks:
- func: "prepare environment"
vars:
storageEngine: "mmapv1"
- test: "initialSync"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "mmapv1"
+ test: "initialsync"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "mmapv1"
- test: "initialSync"
- - func: "make test log artifact"
- func: "analyze"
- name: initialsync-logkeeper_WT
+ priority: 5
depends_on:
- name: compile
variant: linux-standalone
@@ -700,18 +537,10 @@ tasks:
vars:
storageEngine: "wiredTiger"
test: "initialsync-logkeeper"
- - func: "infrastructure provisioning"
- - func: "configure mongodb cluster"
- vars:
- storageEngine: "wiredTiger"
+ - func: "deploy cluster"
- func: "run test"
- vars:
- storageEngine: "wiredTiger"
- test: "initialsync-logkeeper"
- - func: "make test log artifact"
- func: "analyze"
-
#######################################
# Modules #
#######################################
@@ -798,6 +627,8 @@ buildvariants:
- name: industry_benchmarks_WT
- name: core_workloads_WT
- name: industry_benchmarks_MMAPv1
+ - name: industry_benchmarks_wmajority_WT
+ - name: industry_benchmarks_wmajority_MMAPv1
- name: core_workloads_MMAPv1
- name: mongos_workloads_WT
- name: mongos_workloads_MMAPv1
@@ -820,6 +651,8 @@ buildvariants:
- name: industry_benchmarks_WT
- name: core_workloads_WT
- name: industry_benchmarks_MMAPv1
+ - name: industry_benchmarks_wmajority_WT
+ - name: industry_benchmarks_wmajority_MMAPv1
- name: core_workloads_MMAPv1
- name: secondary_performance_WT
- name: secondary_performance_MMAPv1
@@ -858,19 +691,3 @@ buildvariants:
- "rhel70-perf-initialsync-logkeeper"
tasks:
- name: initialsync-logkeeper_WT
-
-- name: linux-oplog-compare
- display_name: Linux Oplog Compare
- batchtime: 10080 # 7 days
- modules: *modules
- expansions:
- compile_flags: -j$(grep -c ^processor /proc/cpuinfo) CC=/opt/mongodbtoolchain/v2/bin/gcc CXX=/opt/mongodbtoolchain/v2/bin/g++ OBJCOPY=/opt/mongodbtoolchain/v2/bin/objcopy
- use_scons_cache: true
- project: *project
- run_on:
- - "rhel70-perf-single"
- tasks:
- - name: industry_benchmarks_WT_oplog_comp
- - name: core_workloads_WT_oplog_comp
- - name: industry_benchmarks_MMAPv1_oplog_comp
- - name: core_workloads_MMAPv1_oplog_comp
diff --git a/jstests/aggregation/extras/utils.js b/jstests/aggregation/extras/utils.js
index 2dd1038388c..68b2597c533 100644
--- a/jstests/aggregation/extras/utils.js
+++ b/jstests/aggregation/extras/utils.js
@@ -276,7 +276,7 @@ function assertErrorCode(coll, pipe, code, errmsg) {
var cursorRes = coll.runCommand("aggregate", cmd);
if (cursorRes.ok) {
var followupBatchSize = 0; // default
- var cursor = new DBCommandCursor(coll.getMongo(), cursorRes, followupBatchSize);
+ var cursor = new DBCommandCursor(cursorRes._mongo, cursorRes, followupBatchSize);
var error = assert.throws(function() {
cursor.itcount();
diff --git a/jstests/auth/mongos_cache_invalidation.js b/jstests/auth/mongos_cache_invalidation.js
index e36445cac5e..b53656de3a1 100644
--- a/jstests/auth/mongos_cache_invalidation.js
+++ b/jstests/auth/mongos_cache_invalidation.js
@@ -61,7 +61,7 @@ db3.auth('spencer', 'pwd');
* At this point we have 3 handles to the "test" database, each of which are on connections to
* different mongoses. "db1", "db2", and "db3" are all auth'd as spencer@test and will be used
* to verify that user and role data changes get propaged to their mongoses.
- * "db2" is connected to a mongos with a 10 second user cache invalidation interval,
+ * "db2" is connected to a mongos with a 5 second user cache invalidation interval,
* while "db3" is connected to a mongos with a 10 minute cache invalidation interval.
*/
@@ -202,12 +202,9 @@ db3.auth('spencer', 'pwd');
assert.commandFailedWithCode(db1.foo.runCommand("collStats"), authzErrorCode);
// s1/db2 should update its cache in 10 seconds.
- assert.soon(
- function() {
- return db2.foo.runCommand("collStats").code == authzErrorCode;
- },
- "Mongos did not update its user cache after 10 seconds",
- 6 * 1000); // Give an extra 1 second to avoid races
+ assert.soon(function() {
+ return db2.foo.runCommand("collStats").code == authzErrorCode;
+ }, "Mongos did not update its user cache after 10 seconds", 10 * 1000);
// We manually invalidate the cache on s2/db3.
db3.adminCommand("invalidateUserCache");
diff --git a/jstests/auth/scram-credentials-invalid.js b/jstests/auth/scram-credentials-invalid.js
new file mode 100644
index 00000000000..16c0c204d12
--- /dev/null
+++ b/jstests/auth/scram-credentials-invalid.js
@@ -0,0 +1,45 @@
+// Ensure that attempting to use SCRAM-SHA-1 auth on a
+// user with invalid SCRAM-SHA-1 credentials fails gracefully.
+
+(function() {
+ 'use strict';
+
+ function runTest(mongod) {
+ assert(mongod);
+ const admin = mongod.getDB('admin');
+ const test = mongod.getDB('test');
+
+ admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles});
+ assert(admin.auth('admin', 'pass'));
+
+ test.createUser({user: 'user', pwd: 'pass', roles: jsTest.basicUserRoles});
+
+ // Give the test user an invalid set of SCRAM-SHA-1 credentials.
+ assert.eq(admin.system.users
+ .update({_id: "test.user"}, {
+ $set: {
+ "credentials.SCRAM-SHA-1": {
+ salt: "AAAA",
+ storedKey: "AAAA",
+ serverKey: "AAAA",
+ iterationCount: 10000
+ }
+ }
+ })
+ .nModified,
+ 1,
+ "Should have updated one document for user@test");
+ admin.logout();
+
+ assert(!test.auth({user: 'user', pwd: 'pass'}));
+
+ assert.soon(function() {
+ const log = cat(mongod.fullOptions.logFile);
+ return /Unable to perform SCRAM-SHA-1 auth.* invalid SCRAM credentials/.test(log);
+ }, "No warning issued for invalid SCRAM-SHA-1 credendials doc", 30 * 1000, 5 * 1000);
+ }
+
+ const mongod = MongoRunner.runMongod({auth: "", useLogFiles: true});
+ runTest(mongod);
+ MongoRunner.stopMongod(mongod);
+})();
diff --git a/jstests/auth/system_authorization_indexes.js b/jstests/auth/system_authorization_indexes.js
new file mode 100644
index 00000000000..496f78578a4
--- /dev/null
+++ b/jstests/auth/system_authorization_indexes.js
@@ -0,0 +1,66 @@
+/** Ensure that authorization system collections' indexes are correctly generated.
+ *
+ * This test requires users to persist across a restart.
+ * @tags: [requires_persistence]
+ */
+
+(function() {
+ let conn = MongoRunner.runMongod({smallfiles: ""});
+ let db = conn.getDB("admin");
+
+ // TEST: User and role collections start off with no indexes
+ assert.eq(0, db.system.users.getIndexes().length);
+ assert.eq(0, db.system.roles.getIndexes().length);
+
+ // TEST: User and role creation generates indexes
+ db.createUser({user: "user", pwd: "pwd", roles: []});
+ assert.eq(2, db.system.users.getIndexes().length);
+
+ db.createRole({role: "role", privileges: [], roles: []});
+ assert.eq(2, db.system.roles.getIndexes().length);
+
+ // TEST: Destroying admin.system.users index and restarting will recreate it
+ assert.commandWorked(db.system.users.dropIndexes());
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({restart: conn, cleanData: false});
+ db = conn.getDB("admin");
+ assert.eq(2, db.system.users.getIndexes().length);
+ assert.eq(2, db.system.roles.getIndexes().length);
+
+ // TEST: Destroying admin.system.roles index and restarting will recreate it
+ assert.commandWorked(db.system.roles.dropIndexes());
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({restart: conn, cleanData: false});
+ db = conn.getDB("admin");
+ assert.eq(2, db.system.users.getIndexes().length);
+ assert.eq(2, db.system.roles.getIndexes().length);
+
+ // TEST: Destroying both authorization indexes and restarting will recreate them
+ assert.commandWorked(db.system.users.dropIndexes());
+ assert.commandWorked(db.system.roles.dropIndexes());
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({restart: conn, cleanData: false});
+ db = conn.getDB("admin");
+ assert.eq(2, db.system.users.getIndexes().length);
+ assert.eq(2, db.system.roles.getIndexes().length);
+
+ // TEST: Destroying the admin.system.users index and restarting will recreate it, even if
+ // admin.system.roles does not exist
+ db.dropDatabase();
+ db.createUser({user: "user", pwd: "pwd", roles: []});
+ assert.commandWorked(db.system.users.dropIndexes());
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({restart: conn, cleanData: false});
+ db = conn.getDB("admin");
+ assert.eq(2, db.system.users.getIndexes().length);
+
+ // TEST: Destroying the admin.system.roles index and restarting will recreate it, even if
+ // admin.system.users does not exist
+ db.dropDatabase();
+ db.createRole({role: "role", privileges: [], roles: []});
+ assert.commandWorked(db.system.roles.dropIndexes());
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({restart: conn, cleanData: false});
+ db = conn.getDB("admin");
+ assert.eq(2, db.system.roles.getIndexes().length);
+})();
diff --git a/jstests/concurrency/fsm_libs/cluster.js b/jstests/concurrency/fsm_libs/cluster.js
index a5705a409af..f3e2429f898 100644
--- a/jstests/concurrency/fsm_libs/cluster.js
+++ b/jstests/concurrency/fsm_libs/cluster.js
@@ -573,6 +573,25 @@ var Cluster = function(options) {
return data;
};
+
+ this.isRunningWiredTigerLSM = function isRunningWiredTigerLSM() {
+ var adminDB = this.getDB('admin');
+
+ if (this.isSharded()) {
+ // Get the storage engine the sharded cluster is configured to use from one of the
+ // shards since mongos won't report it.
+ adminDB = st.shard0.getDB('admin');
+ }
+
+ var res = adminDB.runCommand({getCmdLineOpts: 1});
+ assert.commandWorked(res, 'failed to get command line options');
+
+ var wiredTigerOptions = res.parsed.storage.wiredTiger || {};
+ var wiredTigerCollectionConfig = wiredTigerOptions.collectionConfig || {};
+ var wiredTigerConfigString = wiredTigerCollectionConfig.configString || '';
+
+ return wiredTigerConfigString === 'type=lsm';
+ };
};
/**
diff --git a/jstests/concurrency/fsm_workloads/compact.js b/jstests/concurrency/fsm_workloads/compact.js
index 8f91f52bf5e..e86e6ef555f 100644
--- a/jstests/concurrency/fsm_workloads/compact.js
+++ b/jstests/concurrency/fsm_workloads/compact.js
@@ -87,12 +87,27 @@ var $config = (function() {
dropCollections(db, pattern);
};
+ var skip = function skip(cluster) {
+ if (cluster.isRunningWiredTigerLSM()) {
+ // There is a known hang during concurrent FSM workloads with the compact command used
+ // with wiredTiger LSM variants. Bypass this command for the wiredTiger LSM variant
+ // until a fix is available for WT-2523.
+ return {
+ skip: true,
+ msg: 'WT-2523: compact command can cause hang using WT LSM index during ' +
+ 'concurrent workloads'
+ };
+ }
+ return {skip: false};
+ };
+
return {
threadCount: 15,
iterations: 10,
states: states,
transitions: transitions,
teardown: teardown,
- data: data
+ data: data,
+ skip: skip
};
})();
diff --git a/jstests/concurrency/fsm_workloads/remove_multiple_documents.js b/jstests/concurrency/fsm_workloads/remove_multiple_documents.js
index bfd64cd6790..1461ca0cc03 100644
--- a/jstests/concurrency/fsm_workloads/remove_multiple_documents.js
+++ b/jstests/concurrency/fsm_workloads/remove_multiple_documents.js
@@ -34,8 +34,17 @@ var $config = (function() {
}
};
+ var skip = function skip(cluster) {
+ // When the balancer is enabled, the nRemoved result may be inaccurate as
+ // a chunk migration may be active, causing the count function to assert.
+ if (cluster.isBalancerEnabled()) {
+ return {skip: true, msg: 'does not run when balancer is enabled.'};
+ }
+ return {skip: false};
+ };
+
var transitions = {init: {count: 1}, count: {remove: 1}, remove: {remove: 0.825, count: 0.125}};
- return {threadCount: 10, iterations: 20, states: states, transitions: transitions};
+ return {threadCount: 10, iterations: 20, states: states, transitions: transitions, skip: skip};
})();
diff --git a/jstests/concurrency/fsm_workloads/remove_where.js b/jstests/concurrency/fsm_workloads/remove_where.js
index f9c0e6a2c03..3ef214c769b 100644
--- a/jstests/concurrency/fsm_workloads/remove_where.js
+++ b/jstests/concurrency/fsm_workloads/remove_where.js
@@ -38,5 +38,14 @@ var $config = extendWorkload($config, function($config, $super) {
/* no-op to prevent index from being created */
};
+ $config.skip = function skip(cluster) {
+ // When the balancer is enabled, the nRemoved result may be inaccurate as
+ // a chunk migration may be active, causing the count function to assert.
+ if (cluster.isBalancerEnabled()) {
+ return {skip: true, msg: 'does not run when balancer is enabled.'};
+ }
+ return {skip: false};
+ };
+
return $config;
});
diff --git a/jstests/core/apply_ops1.js b/jstests/core/apply_ops1.js
index 8ce15b1df22..7088145c718 100644
--- a/jstests/core/apply_ops1.js
+++ b/jstests/core/apply_ops1.js
@@ -286,9 +286,54 @@
'applyOps should fail on unknown operation type "x" with valid "ns" value');
assert.eq(0, t.find().count(), "Non-zero amount of documents in collection to start");
+
+ /**
+ * Test function for running CRUD operations on non-existent namespaces using various
+ * combinations of invalid namespaces (collection/database), allowAtomic and alwaysUpsert.
+ *
+ * Leave 'expectedErrorCode' undefined if this command is expected to run successfully.
+ */
+ function testCrudOperationOnNonExistentNamespace(optype, o, o2, expectedErrorCode) {
+ expectedErrorCode = expectedErrorCode || ErrorCodes.OK;
+ const t2 = db.getSiblingDB('apply_ops1_no_such_db').getCollection('t');
+ [t, t2].forEach(coll => {
+ const op = {op: optype, ns: coll.getFullName(), o: o, o2: o2};
+ [false, true].forEach(allowAtomic => {
+ [false, true].forEach(alwaysUpsert => {
+ const cmd = {
+ applyOps: [op],
+ allowAtomic: allowAtomic,
+ alwaysUpsert: alwaysUpsert
+ };
+ jsTestLog('Testing applyOps on non-existent namespace: ' + tojson(cmd));
+ if (expectedErrorCode === ErrorCodes.OK) {
+ assert.commandWorked(db.adminCommand(cmd));
+ } else {
+ assert.commandFailedWithCode(db.adminCommand(cmd), expectedErrorCode);
+ }
+ });
+ });
+ });
+ }
+
+ // Insert and update operations on non-existent collections/databases should return
+ // NamespaceNotFound.
+ testCrudOperationOnNonExistentNamespace('i', {_id: 0}, {}, ErrorCodes.NamespaceNotFound);
+ testCrudOperationOnNonExistentNamespace('u', {x: 0}, {_id: 0}, ErrorCodes.NamespaceNotFound);
+
+ // Delete operations on non-existent collections/databases should return OK for idempotency
+ // reasons.
+ testCrudOperationOnNonExistentNamespace('d', {_id: 0}, {});
+
assert.commandFailed(
- db.adminCommand({applyOps: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}),
- "Applying an insert operation on a non-existent collection should fail");
+ db.adminCommand({
+ applyOps: [{
+ "op": "c",
+ "ns": "admin.$cmd",
+ "o": {applyOps: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}
+ }]
+ }),
+ "Applying a nested insert operation on a non-existent collection should fail");
assert.commandWorked(db.createCollection(t.getName()));
var a = assert.commandWorked(
diff --git a/jstests/core/apply_ops_atomicity.js b/jstests/core/apply_ops_atomicity.js
index 911ce32a311..e704815a889 100644
--- a/jstests/core/apply_ops_atomicity.js
+++ b/jstests/core/apply_ops_atomicity.js
@@ -29,10 +29,12 @@
var newDBName = "apply_ops_atomicity";
var newDB = db.getSiblingDB(newDBName);
assert.commandWorked(newDB.dropDatabase());
- // Do an update on a non-existent database, since only 'u' ops can implicitly create
- // collections.
- assert.commandWorked(newDB.runCommand(
- {applyOps: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]}));
+ // Updates on a non-existent database no longer implicitly create collections and will fail with
+ // a NamespaceNotFound error.
+ assert.commandFailedWithCode(newDB.runCommand({
+ applyOps: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]
+ }),
+ ErrorCodes.NamespaceNotFound);
var sawTooManyLocksError = false;
diff --git a/jstests/core/autocomplete.js b/jstests/core/autocomplete.js
new file mode 100644
index 00000000000..6eb6e21a7a3
--- /dev/null
+++ b/jstests/core/autocomplete.js
@@ -0,0 +1,42 @@
+/**
+ * Validate auto complete works for various javascript types implemented by C++.
+ */
+(function() {
+ 'use strict';
+
+ function testAutoComplete(prefix) {
+ // This method updates a global object with an array of strings on success.
+ shellAutocomplete(prefix);
+ return __autocomplete__;
+ }
+
+ // Create a collection
+ db.auto_complete_coll.insert({});
+
+ // Validate DB auto completion
+ const db_stuff = testAutoComplete('db.');
+
+ // Verify we enumerate built-in methods
+ assert.contains('db.prototype', db_stuff);
+ assert.contains('db.hasOwnProperty', db_stuff);
+ assert.contains('db.toString(', db_stuff);
+
+ // Verify we have some methods we added
+ assert.contains('db.adminCommand(', db_stuff);
+ assert.contains('db.runCommand(', db_stuff);
+
+ // Verify we enumerate collections
+ assert.contains('db.auto_complete_coll', db_stuff);
+
+ // Validate Collection autocompletion
+ const coll_stuff = testAutoComplete('db.auto_complete_coll.');
+
+ // Verify we enumerate built-in methods
+ assert.contains('db.auto_complete_coll.prototype', coll_stuff);
+ assert.contains('db.auto_complete_coll.hasOwnProperty', coll_stuff);
+ assert.contains('db.auto_complete_coll.toString(', coll_stuff);
+
+ // Verify we have some methods we added
+ assert.contains('db.auto_complete_coll.aggregate(', coll_stuff);
+ assert.contains('db.auto_complete_coll.runCommand(', coll_stuff);
+})(); \ No newline at end of file
diff --git a/jstests/core/batch_write_command_delete.js b/jstests/core/batch_write_command_delete.js
index 2aefcea6a7f..99b5f8e3a61 100644
--- a/jstests/core/batch_write_command_delete.js
+++ b/jstests/core/batch_write_command_delete.js
@@ -1,3 +1,5 @@
+// @tags: [assumes_write_concern_unchanged]
+
//
// Ensures that mongod respects the batch write protocols for delete
//
diff --git a/jstests/core/batch_write_command_insert.js b/jstests/core/batch_write_command_insert.js
index 274f35513e7..3fa6cf98756 100644
--- a/jstests/core/batch_write_command_insert.js
+++ b/jstests/core/batch_write_command_insert.js
@@ -1,3 +1,5 @@
+// @tags: [assumes_write_concern_unchanged]
+
//
// Ensures that mongod respects the batch write protocol for inserts
//
diff --git a/jstests/core/batch_write_command_update.js b/jstests/core/batch_write_command_update.js
index 2d9d2d699b2..987525e2515 100644
--- a/jstests/core/batch_write_command_update.js
+++ b/jstests/core/batch_write_command_update.js
@@ -1,3 +1,5 @@
+// @tags: [assumes_write_concern_unchanged]
+
//
// Ensures that mongod respects the batch write protocols for updates
//
diff --git a/jstests/core/bypass_doc_validation.js b/jstests/core/bypass_doc_validation.js
index d9bca81ab6d..c2cdfff0bc9 100644
--- a/jstests/core/bypass_doc_validation.js
+++ b/jstests/core/bypass_doc_validation.js
@@ -1,5 +1,7 @@
// Test the bypassDocumentValidation flag with some database commands. The test uses relevant shell
// helpers when they're available for the respective server commands.
+//
+// @tags: [requires_collmod_command]
(function() {
'use strict';
diff --git a/jstests/core/capped6.js b/jstests/core/capped6.js
index e94e7ea44e8..ca216fbe96a 100644
--- a/jstests/core/capped6.js
+++ b/jstests/core/capped6.js
@@ -1,4 +1,11 @@
// Test NamespaceDetails::cappedTruncateAfter via "captrunc" command
+//
+// @tags: [
+// # This test attempts to perform read operations on a capped collection after truncating
+// # documents using the captrunc command. The writes from the captrunc command aren't guaranteed
+// # to become visible until a later w="majority" write occurs.
+// assumes_write_concern_unchanged,
+// ]
(function() {
var coll = db.capped6;
diff --git a/jstests/core/collation_plan_cache.js b/jstests/core/collation_plan_cache.js
index 0eec77388e4..790bbbadaa6 100644
--- a/jstests/core/collation_plan_cache.js
+++ b/jstests/core/collation_plan_cache.js
@@ -1,4 +1,11 @@
// Integration testing for the plan cache and index filter commands with collation.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
(function() {
'use strict';
@@ -237,4 +244,4 @@
assert.eq(0,
coll.runCommand('planCacheListFilters').filters.length,
'unexpected number of plan cache filters');
-})(); \ No newline at end of file
+})();
diff --git a/jstests/core/collection_info_cache_race.js b/jstests/core/collection_info_cache_race.js
index d57fc3340db..8fcde050e99 100644
--- a/jstests/core/collection_info_cache_race.js
+++ b/jstests/core/collection_info_cache_race.js
@@ -5,9 +5,9 @@ var coll = db.collection_info_cache_race;
coll.drop();
assert.commandWorked(db.createCollection(coll.getName(), {autoIndexId: false}));
// Fails when SERVER-16502 was not fixed, due to invariant
-assert.writeOK(coll.save({_id: false}, {writeConcern: {w: 1}}));
+assert.writeOK(coll.save({_id: false}));
coll.drop();
assert.commandWorked(db.createCollection(coll.getName(), {autoIndexId: false}));
assert.eq(null, coll.findOne());
-assert.writeOK(coll.save({_id: false}, {writeConcern: {w: 1}}));
+assert.writeOK(coll.save({_id: false}));
diff --git a/jstests/core/collmod.js b/jstests/core/collmod.js
index 16f9694560c..e366041bd99 100644
--- a/jstests/core/collmod.js
+++ b/jstests/core/collmod.js
@@ -1,5 +1,7 @@
// Basic js tests for the collMod command.
// Test setting the usePowerOf2Sizes flag, and modifying TTL indexes.
+//
+// @tags: [requires_collmod_command]
function debug(x) {
// printjson( x );
diff --git a/jstests/core/collmod_bad_spec.js b/jstests/core/collmod_bad_spec.js
index ccce81fd4b1..c3d5e7a148e 100644
--- a/jstests/core/collmod_bad_spec.js
+++ b/jstests/core/collmod_bad_spec.js
@@ -2,6 +2,8 @@
//
// Tests that a collMod with a bad specification does not cause any changes, and does not crash the
// server.
+//
+// @tags: [requires_collmod_command]
(function() {
"use strict";
diff --git a/jstests/core/commands_that_do_not_write_do_not_accept_wc.js b/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
index ef5f8762a42..a9c72e108a1 100644
--- a/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
+++ b/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
@@ -2,6 +2,8 @@
* This file tests commands that do not support write concern. It passes both valid and invalid
* writeConcern fields to commands and expects the commands to fail with a writeConcernNotSupported
* error.
+ *
+ * @tags: [assumes_write_concern_unchanged]
*/
(function() {
diff --git a/jstests/core/constructors.js b/jstests/core/constructors.js
index 9e2cd26bbe8..93842780b42 100644
--- a/jstests/core/constructors.js
+++ b/jstests/core/constructors.js
@@ -1,4 +1,6 @@
// Tests to see what validity checks are done for 10gen specific object construction
+//
+// @tags: [requires_eval_command]
// Takes a list of constructors and returns a new list with an extra entry for each constructor with
// "new" prepended
diff --git a/jstests/core/count10.js b/jstests/core/count10.js
index 2a1853c399a..453775c97f5 100644
--- a/jstests/core/count10.js
+++ b/jstests/core/count10.js
@@ -1,4 +1,11 @@
// Test that interrupting a count returns an error code.
+//
+// @tags: [
+// # This test attempts to perform a count command and find it using the currentOp command. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
t = db.count10;
t.drop();
diff --git a/jstests/core/count_plan_summary.js b/jstests/core/count_plan_summary.js
index 48891d21e8e..365f289c457 100644
--- a/jstests/core/count_plan_summary.js
+++ b/jstests/core/count_plan_summary.js
@@ -1,5 +1,11 @@
-// Test that the plan summary string appears in db.currentOp() for
-// count operations. SERVER-14064.
+// Test that the plan summary string appears in db.currentOp() for count operations. SERVER-14064.
+//
+// @tags: [
+// # This test attempts to perform a find command and find it using the currentOp command. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
var t = db.jstests_count_plan_summary;
t.drop();
diff --git a/jstests/core/crud_api.js b/jstests/core/crud_api.js
index c9dbfb40c85..d572d4b90a5 100644
--- a/jstests/core/crud_api.js
+++ b/jstests/core/crud_api.js
@@ -1,3 +1,5 @@
+// @tags: [assumes_write_concern_unchanged]
+
(function() {
"use strict";
diff --git a/jstests/core/diagdata.js b/jstests/core/diagdata.js
index 490e4a3eb2b..6938f8c5102 100644
--- a/jstests/core/diagdata.js
+++ b/jstests/core/diagdata.js
@@ -1,4 +1,5 @@
// Test that verifies getDiagnosticData returns FTDC data
+load('jstests/libs/ftdc.js');
(function() {
"use strict";
@@ -6,28 +7,5 @@
// Verify we require admin database
assert.commandFailed(db.diagdata.runCommand("getDiagnosticData"));
- // We need to retry a few times if run this test immediately after mongod is started as FTDC may
- // not have run yet.
- var foundGoodDocument = false;
-
- for (var i = 0; i < 60; ++i) {
- var result = db.adminCommand("getDiagnosticData");
- assert.commandWorked(result);
-
- var data = result.data;
-
- if (!data.hasOwnProperty("start")) {
- // Wait a little longer for FTDC to start
- sleep(500);
- } else {
- // Check for a few common properties to ensure we got data
- assert(data.hasOwnProperty("serverStatus"),
- "does not have 'serverStatus' in '" + tojson(data) + "'");
- assert(data.hasOwnProperty("end"), "does not have 'end' in '" + tojson(data) + "'");
- foundGoodDocument = true;
- }
- }
- assert(foundGoodDocument,
- "getDiagnosticData failed to return a non-empty command, is FTDC running?");
-
+ verifyGetDiagnosticData(db.getSiblingDB('admin'));
})();
diff --git a/jstests/core/doc_validation.js b/jstests/core/doc_validation.js
index a30763869e7..95dbae1b219 100644
--- a/jstests/core/doc_validation.js
+++ b/jstests/core/doc_validation.js
@@ -1,4 +1,6 @@
// Test basic inserts and updates with document validation.
+//
+// @tags: [requires_collmod_command]
(function() {
"use strict";
diff --git a/jstests/core/doc_validation_invalid_validators.js b/jstests/core/doc_validation_invalid_validators.js
index b78b31c0977..70262f5de43 100644
--- a/jstests/core/doc_validation_invalid_validators.js
+++ b/jstests/core/doc_validation_invalid_validators.js
@@ -1,5 +1,7 @@
// Verify invalid validator statements won't work and that we
// can't create validated collections on restricted databases.
+//
+// @tags: [requires_collmod_command]
(function() {
"use strict";
diff --git a/jstests/core/doc_validation_options.js b/jstests/core/doc_validation_options.js
index 8a96685e48f..d1434af6d45 100644
--- a/jstests/core/doc_validation_options.js
+++ b/jstests/core/doc_validation_options.js
@@ -1,3 +1,4 @@
+// @tags: [requires_collmod_command]
(function() {
"use strict";
diff --git a/jstests/core/dropdb_race.js b/jstests/core/dropdb_race.js
index bd5e7e5ddba..d8b49d08174 100644
--- a/jstests/core/dropdb_race.js
+++ b/jstests/core/dropdb_race.js
@@ -1,4 +1,6 @@
// test dropping a db with simultaneous commits
+//
+// @tags: [assumes_write_concern_unchanged]
m = db.getMongo();
baseName = "jstests_dur_droprace";
diff --git a/jstests/core/elemMatchProjection.js b/jstests/core/elemMatchProjection.js
index 4e80e8a296e..5060d8a6346 100644
--- a/jstests/core/elemMatchProjection.js
+++ b/jstests/core/elemMatchProjection.js
@@ -4,35 +4,59 @@ t.drop();
date1 = new Date();
+// Generate monotonically increasing _id values. ObjectIds generated by the shell are not guaranteed
+// to be monotically increasing, and we will depend on the _id sort order later in the test.
+var currentId = 0;
+function nextId() {
+ return ++currentId;
+}
+
// Insert various styles of arrays
for (i = 0; i < 100; i++) {
- t.insert({group: 1, x: [1, 2, 3, 4, 5]});
- t.insert({group: 2, x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}]});
+ t.insert({_id: nextId(), group: 1, x: [1, 2, 3, 4, 5]});
+ t.insert({_id: nextId(), group: 2, x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}]});
t.insert({
+ _id: nextId(),
group: 3,
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
});
- t.insert({group: 3, x: [{a: 1, b: 3}, {a: -6, c: 3}]});
- t.insert({group: 4, x: [{a: 1, b: 4}, {a: -6, c: 3}]});
- t.insert({group: 5, x: [new Date(), 5, 10, 'string', new ObjectId(), 123.456]});
+ t.insert({_id: nextId(), group: 3, x: [{a: 1, b: 3}, {a: -6, c: 3}]});
+ t.insert({_id: nextId(), group: 4, x: [{a: 1, b: 4}, {a: -6, c: 3}]});
+ t.insert({_id: nextId(), group: 5, x: [new Date(), 5, 10, 'string', new ObjectId(), 123.456]});
t.insert({
+ _id: nextId(),
group: 6,
x: [{a: 'string', b: date1}, {a: new ObjectId(), b: 1.2345}, {a: 'string2', b: date1}]
});
- t.insert({group: 7, x: [{y: [1, 2, 3, 4]}]});
- t.insert({group: 8, x: [{y: [{a: 1, b: 2}, {a: 3, b: 4}]}]});
- t.insert({group: 9, x: [{y: [{a: 1, b: 2}, {a: 3, b: 4}]}, {z: [{a: 1, b: 2}, {a: 3, b: 4}]}]});
- t.insert({group: 10, x: [{a: 1, b: 2}, {a: 3, b: 4}], y: [{c: 1, d: 2}, {c: 3, d: 4}]});
- t.insert({group: 10, x: [{a: 1, b: 2}, {a: 3, b: 4}], y: [{c: 1, d: 2}, {c: 3, d: 4}]});
+ t.insert({_id: nextId(), group: 7, x: [{y: [1, 2, 3, 4]}]});
+ t.insert({_id: nextId(), group: 8, x: [{y: [{a: 1, b: 2}, {a: 3, b: 4}]}]});
+ t.insert({
+ _id: nextId(),
+ group: 9,
+ x: [{y: [{a: 1, b: 2}, {a: 3, b: 4}]}, {z: [{a: 1, b: 2}, {a: 3, b: 4}]}]
+ });
+ t.insert({
+ _id: nextId(),
+ group: 10,
+ x: [{a: 1, b: 2}, {a: 3, b: 4}],
+ y: [{c: 1, d: 2}, {c: 3, d: 4}]
+ });
+ t.insert({
+ _id: nextId(),
+ group: 10,
+ x: [{a: 1, b: 2}, {a: 3, b: 4}],
+ y: [{c: 1, d: 2}, {c: 3, d: 4}]
+ });
t.insert({
+ _id: nextId(),
group: 11,
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
covered: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
});
- t.insert({group: 12, x: {y: [{a: 1, b: 1}, {a: 1, b: 2}]}});
- t.insert({group: 13, x: [{a: 1, b: 1}, {a: 1, b: 2}]});
- t.insert({group: 13, x: [{a: 1, b: 2}, {a: 1, b: 1}]});
+ t.insert({_id: nextId(), group: 12, x: {y: [{a: 1, b: 1}, {a: 1, b: 2}]}});
+ t.insert({_id: nextId(), group: 13, x: [{a: 1, b: 1}, {a: 1, b: 2}]});
+ t.insert({_id: nextId(), group: 13, x: [{a: 1, b: 2}, {a: 1, b: 1}]});
}
t.ensureIndex({
group: 1,
diff --git a/jstests/core/error2.js b/jstests/core/error2.js
index 6f0b95bc17e..fb6a8e6e3b2 100644
--- a/jstests/core/error2.js
+++ b/jstests/core/error2.js
@@ -1,4 +1,5 @@
// Test that client gets stack trace on failed invoke
+// @tags: [requires_eval_command]
f = db.jstests_error2;
diff --git a/jstests/core/eval0.js b/jstests/core/eval0.js
index 5802f2597cb..c21c6be66c6 100644
--- a/jstests/core/eval0.js
+++ b/jstests/core/eval0.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
assert.writeOK(db.evalprep.insert({}), "db must exist for eval to succeed");
db.evalprep.drop();
assert.eq(17,
diff --git a/jstests/core/eval1.js b/jstests/core/eval1.js
index 8b139cae02a..b5bffac892e 100644
--- a/jstests/core/eval1.js
+++ b/jstests/core/eval1.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval1;
t.drop();
diff --git a/jstests/core/eval3.js b/jstests/core/eval3.js
index c4f8be21056..b95837a6817 100644
--- a/jstests/core/eval3.js
+++ b/jstests/core/eval3.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval3;
t.drop();
diff --git a/jstests/core/eval4.js b/jstests/core/eval4.js
index 0d120b393de..9b0c2a49d82 100644
--- a/jstests/core/eval4.js
+++ b/jstests/core/eval4.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval4;
t.drop();
diff --git a/jstests/core/eval5.js b/jstests/core/eval5.js
index 46bd679dd77..815365ac8b9 100644
--- a/jstests/core/eval5.js
+++ b/jstests/core/eval5.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval5;
t.drop();
diff --git a/jstests/core/eval6.js b/jstests/core/eval6.js
index 31258f6917b..96b43b3516c 100644
--- a/jstests/core/eval6.js
+++ b/jstests/core/eval6.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval6;
t.drop();
diff --git a/jstests/core/eval7.js b/jstests/core/eval7.js
index 80197fcdde6..3bace093db1 100644
--- a/jstests/core/eval7.js
+++ b/jstests/core/eval7.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
assert.writeOK(db.evalprep.insert({}), "db must exist for eval to succeed");
db.evalprep.drop();
diff --git a/jstests/core/eval9.js b/jstests/core/eval9.js
index 1480ff6519c..82c230a8a95 100644
--- a/jstests/core/eval9.js
+++ b/jstests/core/eval9.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
assert.writeOK(db.evalprep.insert({}), "db must exist for eval to succeed");
db.evalprep.drop();
diff --git a/jstests/core/eval_mr.js b/jstests/core/eval_mr.js
index 4a3dc8dad6c..33ca96eea01 100644
--- a/jstests/core/eval_mr.js
+++ b/jstests/core/eval_mr.js
@@ -1,4 +1,6 @@
// Test that the eval command can't be used to invoke the mapReduce command. SERVER-17889.
+//
+// @tags: [requires_eval_command]
(function() {
"use strict";
db.eval_mr.drop();
diff --git a/jstests/core/eval_nolock.js b/jstests/core/eval_nolock.js
index 9511784becb..0fde2666f5d 100644
--- a/jstests/core/eval_nolock.js
+++ b/jstests/core/eval_nolock.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.eval_nolock;
t.drop();
diff --git a/jstests/core/evala.js b/jstests/core/evala.js
index 7ccf33ac754..09241eeedee 100644
--- a/jstests/core/evala.js
+++ b/jstests/core/evala.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.evala;
t.drop();
diff --git a/jstests/core/evalb.js b/jstests/core/evalb.js
index 3391c4cc4f2..2de16f4ea83 100644
--- a/jstests/core/evalb.js
+++ b/jstests/core/evalb.js
@@ -1,5 +1,7 @@
// Check the return value of a db.eval function running a database query, and ensure the function's
// contents are logged in the profile log.
+//
+// @tags: [requires_eval_command]
// Use a reserved database name to avoid a conflict in the parallel test suite.
var stddb = db;
diff --git a/jstests/core/evald.js b/jstests/core/evald.js
index 8049d2ba8ae..43e74fc4600 100644
--- a/jstests/core/evald.js
+++ b/jstests/core/evald.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
t = db.jstests_evald;
t.drop();
diff --git a/jstests/core/evale.js b/jstests/core/evale.js
index 1ddc8519fc6..20384e0d741 100644
--- a/jstests/core/evale.js
+++ b/jstests/core/evale.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
t = db.jstests_evale;
t.drop();
diff --git a/jstests/core/evalg.js b/jstests/core/evalg.js
index 18503659217..56cb1dedd5f 100644
--- a/jstests/core/evalg.js
+++ b/jstests/core/evalg.js
@@ -1,4 +1,6 @@
// SERVER-17499: Test behavior of getMore on aggregation cursor under eval command.
+//
+// @tags: [requires_eval_command]
db.evalg.drop();
for (var i = 0; i < 102; ++i) {
db.evalg.insert({});
diff --git a/jstests/core/evalh.js b/jstests/core/evalh.js
index 11e672f6bf4..b9c0d486d59 100644
--- a/jstests/core/evalh.js
+++ b/jstests/core/evalh.js
@@ -1,5 +1,7 @@
/**
* Test that db.eval does not support auth.
+ *
+ * @tags: [requires_eval_command]
*/
(function() {
'use strict';
diff --git a/jstests/core/evalj.js b/jstests/core/evalj.js
index f2326fff365..d6ef46430de 100644
--- a/jstests/core/evalj.js
+++ b/jstests/core/evalj.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
(function() {
"use strict";
diff --git a/jstests/core/existsa.js b/jstests/core/existsa.js
index e9430b489a3..d98fd3f2d68 100644
--- a/jstests/core/existsa.js
+++ b/jstests/core/existsa.js
@@ -1,101 +1,111 @@
-// Sparse indexes are disallowed for $exists:false queries. SERVER-3918
-
-t = db.jstests_existsa;
-t.drop();
-
-t.save({});
-t.save({a: 1});
-t.save({a: {x: 1}, b: 1});
-
-/** Configure testing of an index { <indexKeyField>:1 }. */
-function setIndex(_indexKeyField) {
- indexKeyField = _indexKeyField;
- indexKeySpec = {};
- indexKeySpec[indexKeyField] = 1;
- t.ensureIndex(indexKeySpec, {sparse: true});
-}
-setIndex('a');
-
-/** @return count when hinting the index to use. */
-function hintedCount(query) {
- return t.find(query).hint(indexKeySpec).itcount();
-}
-
-/** The query field does not exist and the sparse index is not used without a hint. */
-function assertMissing(query, expectedMissing, expectedIndexedMissing) {
- expectedMissing = expectedMissing || 1;
- expectedIndexedMissing = expectedIndexedMissing || 0;
- assert.eq(expectedMissing, t.count(query));
- // We also shouldn't get a different count depending on whether
- // an index is used or not.
- assert.eq(expectedIndexedMissing, hintedCount(query));
-}
-
-/** The query field exists and the sparse index is used without a hint. */
-function assertExists(query, expectedExists) {
- expectedExists = expectedExists || 2;
- assert.eq(expectedExists, t.count(query));
- // An $exists:true predicate generates no index filters. Add another predicate on the index key
- // to trigger use of the index.
- andClause = {};
- andClause[indexKeyField] = {$ne: null};
- Object.extend(query, {$and: [andClause]});
- assert.eq(expectedExists, t.count(query));
- assert.eq(expectedExists, hintedCount(query));
-}
-
-/** The query field exists and the sparse index is not used without a hint. */
-function assertExistsUnindexed(query, expectedExists) {
- expectedExists = expectedExists || 2;
- assert.eq(expectedExists, t.count(query));
- // Even with another predicate on the index key, the sparse index is disallowed.
- andClause = {};
- andClause[indexKeyField] = {$ne: null};
- Object.extend(query, {$and: [andClause]});
- assert.eq(expectedExists, t.count(query));
- assert.eq(expectedExists, hintedCount(query));
-}
-
-// $exists:false queries match the proper number of documents and disallow the sparse index.
-assertMissing({a: {$exists: false}});
-assertMissing({a: {$not: {$exists: true}}});
-assertMissing({$and: [{a: {$exists: false}}]});
-assertMissing({$or: [{a: {$exists: false}}]});
-assertMissing({$nor: [{a: {$exists: true}}]});
-assertMissing({'a.x': {$exists: false}}, 2, 1);
-
-// Currently a sparse index is disallowed even if the $exists:false query is on a different field.
-assertMissing({b: {$exists: false}}, 2, 1);
-assertMissing({b: {$exists: false}, a: {$ne: 6}}, 2, 1);
-assertMissing({b: {$not: {$exists: true}}}, 2, 1);
-
-// Top level $exists:true queries match the proper number of documents
-// and use the sparse index on { a : 1 }.
-assertExists({a: {$exists: true}});
-
-// Nested $exists queries match the proper number of documents and disallow the sparse index.
-assertExistsUnindexed({$nor: [{a: {$exists: false}}]});
-assertExistsUnindexed({$nor: [{'a.x': {$exists: false}}]}, 1);
-assertExistsUnindexed({a: {$not: {$exists: false}}});
-
-// Nested $exists queries disallow the sparse index in some cases where it is not strictly
-// necessary to do so. (Descriptive tests.)
-assertExistsUnindexed({$nor: [{b: {$exists: false}}]}, 1); // Unindexed field.
-assertExists({$or: [{a: {$exists: true}}]}); // $exists:true not $exists:false.
-
-// Behavior is similar with $elemMatch.
-t.drop();
-t.save({a: [{}]});
-t.save({a: [{b: 1}]});
-t.save({a: [{b: 1}]});
-setIndex('a.b');
-
-assertMissing({a: {$elemMatch: {b: {$exists: false}}}});
-// A $elemMatch predicate is treated as nested, and the index should be used for $exists:true.
-assertExists({a: {$elemMatch: {b: {$exists: true}}}});
-
-// A non sparse index will not be disallowed.
-t.drop();
-t.save({});
-t.ensureIndex({a: 1});
-assert.eq(1, t.find({a: {$exists: false}}).itcount());
+/**
+ * Tests that sparse indexes are disallowed for $exists:false queries.
+ */
+(function() {
+ "use strict";
+
+ const coll = db.jstests_existsa;
+ coll.drop();
+
+ assert.writeOK(coll.insert({}));
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.insert({a: {x: 1}, b: 1}));
+
+ let indexKeySpec = {};
+ let indexKeyField = '';
+
+ /** Configure testing of an index { <indexKeyField>:1 }. */
+ function setIndex(_indexKeyField) {
+ indexKeyField = _indexKeyField;
+ indexKeySpec = {};
+ indexKeySpec[indexKeyField] = 1;
+ coll.ensureIndex(indexKeySpec, {sparse: true});
+ }
+ setIndex('a');
+
+ /** @return count when hinting the index to use. */
+ function hintedCount(query) {
+ return coll.find(query).hint(indexKeySpec).itcount();
+ }
+
+ /** The query field does not exist and the sparse index is not used without a hint. */
+ function assertMissing(query, expectedMissing = 1, expectedIndexedMissing = 0) {
+ assert.eq(expectedMissing, coll.count(query));
+ // We also shouldn't get a different count depending on whether
+ // an index is used or not.
+ assert.eq(expectedIndexedMissing, hintedCount(query));
+ }
+
+ /** The query field exists and the sparse index is used without a hint. */
+ function assertExists(query, expectedExists = 2) {
+ assert.eq(expectedExists, coll.count(query));
+ // An $exists:true predicate generates no index filters. Add another predicate on the index
+ // key to trigger use of the index.
+ let andClause = {};
+ andClause[indexKeyField] = {$ne: null};
+ Object.extend(query, {$and: [andClause]});
+ assert.eq(expectedExists, coll.count(query));
+ assert.eq(expectedExists, hintedCount(query));
+ }
+
+ /** The query field exists and the sparse index is not used without a hint. */
+ function assertExistsUnindexed(query, expectedExists = 2) {
+ assert.eq(expectedExists, coll.count(query));
+ // Even with another predicate on the index key, the sparse index is disallowed.
+ let andClause = {};
+ andClause[indexKeyField] = {$ne: null};
+ Object.extend(query, {$and: [andClause]});
+ assert.eq(expectedExists, coll.count(query));
+ assert.eq(expectedExists, hintedCount(query));
+ }
+
+ // $exists:false queries match the proper number of documents and disallow the sparse index.
+ assertMissing({a: {$exists: false}});
+ assertMissing({a: {$not: {$exists: true}}});
+ assertMissing({$and: [{a: {$exists: false}}]});
+ assertMissing({$or: [{a: {$exists: false}}]});
+ assertMissing({$nor: [{a: {$exists: true}}]});
+ assertMissing({'a.x': {$exists: false}}, 2, 1);
+
+ // Currently a sparse index is disallowed even if the $exists:false query is on a different
+ // field.
+ assertMissing({b: {$exists: false}}, 2, 1);
+ assertMissing({b: {$exists: false}, a: {$ne: 6}}, 2, 1);
+ assertMissing({b: {$not: {$exists: true}}}, 2, 1);
+
+ // Top level $exists:true queries match the proper number of documents
+ // and use the sparse index on { a : 1 }.
+ assertExists({a: {$exists: true}});
+
+ // Nested $exists queries match the proper number of documents and disallow the sparse index.
+ assertExistsUnindexed({$nor: [{a: {$exists: false}}]});
+ assertExistsUnindexed({$nor: [{'a.x': {$exists: false}}]}, 1);
+ assertExistsUnindexed({a: {$not: {$exists: false}}});
+
+ // Nested $exists queries disallow the sparse index in some cases where it is not strictly
+ // necessary to do so. (Descriptive tests.)
+ assertExistsUnindexed({$nor: [{b: {$exists: false}}]}, 1); // Unindexed field.
+ assertExists({$or: [{a: {$exists: true}}]}); // $exists:true not $exists:false.
+
+ // Behavior is similar with $elemMatch.
+ coll.drop();
+ assert.writeOK(coll.insert({a: [{}]}));
+ assert.writeOK(coll.insert({a: [{b: 1}]}));
+ assert.writeOK(coll.insert({a: [{b: [1]}]}));
+ setIndex('a.b');
+
+ assertMissing({a: {$elemMatch: {b: {$exists: false}}}});
+
+ // A $elemMatch predicate is treated as nested, and the index should be used for $exists:true.
+ assertExists({a: {$elemMatch: {b: {$exists: true}}}});
+
+ // A $not within $elemMatch should not attempt to use a sparse index for $exists:false.
+ assertExistsUnindexed({'a.b': {$elemMatch: {$not: {$exists: false}}}}, 1);
+ assertExistsUnindexed({'a.b': {$elemMatch: {$gt: 0, $not: {$exists: false}}}}, 1);
+
+ // A non sparse index will not be disallowed.
+ coll.drop();
+ assert.writeOK(coll.insert({}));
+ coll.ensureIndex({a: 1});
+ assert.eq(1, coll.find({a: {$exists: false}}).itcount());
+})();
diff --git a/jstests/core/fsync.js b/jstests/core/fsync.js
index 13ff20e4177..2ad625d88ba 100644
--- a/jstests/core/fsync.js
+++ b/jstests/core/fsync.js
@@ -6,6 +6,8 @@
* - Confirm that writes can progress after fsyncUnlock
* - Confirm that the command can be run repeatedly without breaking things
* - Confirm that the pseudo commands and eval can perform fsyncLock/Unlock
+ *
+ * @tags: [requires_eval_command]
*/
(function() {
"use strict";
diff --git a/jstests/core/fts_dotted_prefix_fields.js b/jstests/core/fts_dotted_prefix_fields.js
new file mode 100644
index 00000000000..f811c4a7203
--- /dev/null
+++ b/jstests/core/fts_dotted_prefix_fields.js
@@ -0,0 +1,15 @@
+// Test that text search works correct when the text index has dotted paths as the non-text
+// prefixes.
+(function() {
+ "use strict";
+
+ let coll = db.fts_dotted_prefix_fields;
+ coll.drop();
+ assert.commandWorked(coll.createIndex({"a.x": 1, "a.y": 1, "b.x": 1, "b.y": 1, words: "text"}));
+ assert.writeOK(coll.insert({a: {x: 1, y: 2}, b: {x: 3, y: 4}, words: "lorem ipsum dolor sit"}));
+ assert.writeOK(coll.insert({a: {x: 1, y: 2}, b: {x: 5, y: 4}, words: "lorem ipsum dolor sit"}));
+
+ assert.eq(1,
+ coll.find({$text: {$search: "lorem ipsum"}, "a.x": 1, "a.y": 2, "b.x": 3, "b.y": 4})
+ .itcount());
+}());
diff --git a/jstests/core/fts_trailing_fields.js b/jstests/core/fts_trailing_fields.js
new file mode 100644
index 00000000000..2c7f79b423d
--- /dev/null
+++ b/jstests/core/fts_trailing_fields.js
@@ -0,0 +1,22 @@
+// Tests for predicates which can use the trailing field of a text index.
+(function() {
+ "use strict";
+
+ const coll = db.fts_trailing_fields;
+
+ coll.drop();
+ assert.commandWorked(coll.createIndex({a: 1, b: "text", c: 1}));
+ assert.writeOK(coll.insert({a: 2, b: "lorem ipsum"}));
+
+ assert.eq(0, coll.find({a: 2, $text: {$search: "lorem"}, c: {$exists: true}}).itcount());
+ assert.eq(1, coll.find({a: 2, $text: {$search: "lorem"}, c: null}).itcount());
+ assert.eq(1, coll.find({a: 2, $text: {$search: "lorem"}, c: {$exists: false}}).itcount());
+
+ // An equality predicate on the leading field isn't useful, but it shouldn't cause any problems.
+ // Same with an $elemMatch predicate on one of the trailing fields.
+ coll.drop();
+ assert.commandWorked(coll.createIndex({a: 1, b: "text", "c.d": 1}));
+ assert.writeOK(coll.insert({a: 2, b: "lorem ipsum", c: {d: 3}}));
+ assert.eq(0, coll.find({a: [1, 2], $text: {$search: "lorem"}}).itcount());
+ assert.eq(0, coll.find({a: 2, $text: {$search: "lorem"}, c: {$elemMatch: {d: 3}}}).itcount());
+}());
diff --git a/jstests/core/function_string_representations.js b/jstests/core/function_string_representations.js
new file mode 100644
index 00000000000..af66e9160a9
--- /dev/null
+++ b/jstests/core/function_string_representations.js
@@ -0,0 +1,38 @@
+/** Demonstrate that mapReduce can accept functions represented by strings.
+ * Some drivers do not have a type which represents a Javascript function. These languages represent
+ * the arguments to mapReduce as strings.
+ */
+
+(function() {
+ "use strict";
+
+ var col = db.function_string_representations;
+ col.drop();
+ assert.writeOK(col.insert({
+ _id: "abc123",
+ ord_date: new Date("Oct 04, 2012"),
+ status: 'A',
+ price: 25,
+ items: [{sku: "mmm", qty: 5, price: 2.5}, {sku: "nnn", qty: 5, price: 2.5}]
+ }));
+
+ var mapFunction = "function() {emit(this._id, this.price);}";
+ var reduceFunction = "function(keyCustId, valuesPrices) {return Array.sum(valuesPrices);}";
+ assert.commandWorked(col.mapReduce(mapFunction, reduceFunction, {out: "map_reduce_example"}));
+
+ // Provided strings may end with semicolons and/or whitespace
+ mapFunction += " ; ";
+ reduceFunction += " ; ";
+ assert.commandWorked(col.mapReduce(mapFunction, reduceFunction, {out: "map_reduce_example"}));
+
+ // $where exhibits the same behavior
+ var whereFunction = "function() {return this.price === 25;}";
+ assert.eq(1, col.find({$where: whereFunction}).itcount());
+
+ whereFunction += ";";
+ assert.eq(1, col.find({$where: whereFunction}).itcount());
+
+ // db.eval does not need to be tested, as it accepts code fragments, not functions.
+ // system.js does not need to be tested, as its contents types' are preserved, and
+ // strings are not promoted into functions.
+})();
diff --git a/jstests/core/geo_2d_trailing_fields.js b/jstests/core/geo_2d_trailing_fields.js
new file mode 100644
index 00000000000..8f9c881ae4c
--- /dev/null
+++ b/jstests/core/geo_2d_trailing_fields.js
@@ -0,0 +1,45 @@
+// Tests for predicates which can use the trailing field of a 2d index.
+(function() {
+ "use strict";
+
+ const coll = db.geo_2d_trailing_fields;
+
+ const isMaster = assert.commandWorked(db.adminCommand({isMaster: 1}));
+ const isMongos = (isMaster.msg === "isdbgrid");
+
+ coll.drop();
+ assert.commandWorked(coll.createIndex({a: "2d", b: 1}));
+ assert.writeOK(coll.insert({a: [0, 0]}));
+
+ // Verify that $near queries handle existence predicates over the trailing fields correctly.
+ if (!isMongos) {
+ assert.eq(0, coll.find({a: {$near: [0, 0]}, b: {$exists: true}}).itcount());
+ assert.eq(1, coll.find({a: {$near: [0, 0]}, b: null}).itcount());
+ assert.eq(1, coll.find({a: {$near: [0, 0]}, b: {$exists: false}}).itcount());
+ }
+
+ // Verify that non-near 2d queries handle existence predicates over the trailing fields
+ // correctly.
+ assert.eq(0,
+ coll.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: true}}).itcount());
+ assert.eq(1, coll.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, b: null}).itcount());
+ assert.eq(1,
+ coll.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: false}}).itcount());
+
+ coll.drop();
+ assert.commandWorked(coll.createIndex({a: "2d", "b.c": 1}));
+ assert.writeOK(coll.insert({a: [0, 0], b: [{c: 2}, {c: 3}]}));
+
+ // Verify that $near queries correctly handle predicates which cannot be covered due to array
+ // semantics.
+ if (!isMongos) {
+ assert.eq(0, coll.find({a: {$near: [0, 0]}, "b.c": [2, 3]}).itcount());
+ assert.eq(0, coll.find({a: {$near: [0, 0]}, "b.c": {$type: "array"}}).itcount());
+ }
+
+ // Verify that non-near 2d queries correctly handle predicates which cannot be covered due to
+ // array semantics.
+ assert.eq(0, coll.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, "b.c": [2, 3]}).itcount());
+ assert.eq(
+ 0, coll.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, "b.c": {$type: "array"}}).itcount());
+}());
diff --git a/jstests/core/geo_s2cursorlimitskip.js b/jstests/core/geo_s2cursorlimitskip.js
index 427fbf8fe29..dc645dc68af 100644
--- a/jstests/core/geo_s2cursorlimitskip.js
+++ b/jstests/core/geo_s2cursorlimitskip.js
@@ -1,4 +1,11 @@
// Test various cursor behaviors
+//
+// @tags: [
+// # This test attempts to enable profiling on a server and then get profiling data by reading
+// # from the "system.profile" collection. The former operation must be routed to the primary in
+// # a replica set, whereas the latter may be routed to a secondary.
+// assumes_read_preference_unchanged,
+// ]
var testDB = db.getSiblingDB("geo_s2cursorlimitskip");
var t = testDB.geo_s2getmmm;
diff --git a/jstests/core/geo_update_btree.js b/jstests/core/geo_update_btree.js
index a85d4274415..b4fa24df57e 100644
--- a/jstests/core/geo_update_btree.js
+++ b/jstests/core/geo_update_btree.js
@@ -1,4 +1,6 @@
// Tests whether the geospatial search is stable under btree updates
+//
+// @tags: [assumes_write_concern_unchanged]
var coll = db.getCollection("jstests_geo_update_btree");
coll.drop();
diff --git a/jstests/core/getlog2.js b/jstests/core/getlog2.js
index 597a85e20ee..e5287ea8c1b 100644
--- a/jstests/core/getlog2.js
+++ b/jstests/core/getlog2.js
@@ -1,4 +1,11 @@
// tests getlog as well as slow querying logging
+//
+// @tags: [
+// # This test attempts to perform a find command and see that it ran using the getLog command.
+// # The former operation may be routed to a secondary in the replica set, whereas the latter must
+// # be routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
glcol = db.getLogTest2;
glcol.drop();
diff --git a/jstests/core/index_elemmatch2.js b/jstests/core/index_elemmatch2.js
new file mode 100644
index 00000000000..ecd24035284
--- /dev/null
+++ b/jstests/core/index_elemmatch2.js
@@ -0,0 +1,63 @@
+/**
+ * Test that queries containing $elemMatch correctly use an index if each child expression is
+ * compatible with the index.
+ */
+(function() {
+ "use strict";
+
+ load("jstests/libs/analyze_plan.js");
+
+ const coll = db.elemMatch_index;
+ coll.drop();
+
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.insert({a: [{}]}));
+ assert.writeOK(coll.insert({a: [1, null]}));
+ assert.writeOK(coll.insert({a: [{type: "Point", coordinates: [0, 0]}]}));
+
+ assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));
+
+ function assertIndexResults(coll, query, useIndex, nReturned) {
+ const explainPlan = coll.find(query).explain("executionStats");
+ assert.eq(isIxscan(explainPlan.queryPlanner.winningPlan), useIndex);
+ assert.eq(explainPlan.executionStats.nReturned, nReturned);
+ }
+
+ assertIndexResults(coll, {a: {$elemMatch: {$exists: false}}}, false, 0);
+
+ // An $elemMatch predicate is treated as nested, and the index should be used for $exists:true.
+ assertIndexResults(coll, {a: {$elemMatch: {$exists: true}}}, true, 3);
+
+ // $not within $elemMatch should not attempt to use a sparse index for $exists:false.
+ assertIndexResults(coll, {a: {$elemMatch: {$not: {$exists: false}}}}, false, 3);
+ assertIndexResults(coll, {a: {$elemMatch: {$gt: 0, $not: {$exists: false}}}}, false, 1);
+
+ // $geo within $elemMatch should not attempt to use a non-geo index.
+ assertIndexResults(
+ coll,
+ {
+ a: {
+ $elemMatch: {
+ $geoWithin: {
+ $geometry:
+ {type: "Polygon", coordinates: [[[0, 0], [0, 1], [1, 0], [0, 0]]]}
+ }
+ }
+ }
+ },
+ false,
+ 1);
+
+ // $in with a null value within $elemMatch should use a sparse index.
+ assertIndexResults(coll, {a: {$elemMatch: {$in: [null]}}}, true, 1);
+
+ // $eq with a null value within $elemMatch should use a sparse index.
+ assertIndexResults(coll, {a: {$elemMatch: {$eq: null}}}, true, 1);
+
+ // A negated regex within $elemMatch should not use an index, sparse or not.
+ assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
+
+ coll.dropIndexes();
+ assert.commandWorked(coll.createIndex({a: 1}));
+ assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
+})();
diff --git a/jstests/core/index_filter_commands.js b/jstests/core/index_filter_commands.js
index 8684be3b2b9..58f78d0514e 100644
--- a/jstests/core/index_filter_commands.js
+++ b/jstests/core/index_filter_commands.js
@@ -6,20 +6,24 @@
* Displays index filters for all query shapes in a collection.
*
* - planCacheClearFilters
- * Clears index filter for a single query shape or,
- * if the query shape is omitted, all filters for the collection.
+ * Clears index filter for a single query shape or, if the query shape is omitted, all filters for
+ * the collection.
*
* - planCacheSetFilter
* Sets index filter for a query shape. Overrides existing filter.
*
- * Not a lot of data access in this test suite. Hint commands
- * manage a non-persistent mapping in the server of
- * query shape to list of index specs.
+ * Not a lot of data access in this test suite. Hint commands manage a non-persistent mapping in the
+ * server of query shape to list of index specs.
*
- * Only time we might need to execute a query is to check the plan
- * cache state. We would do this with the planCacheListPlans command
- * on the same query shape with the index filters.
+ * Only time we might need to execute a query is to check the plan cache state. We would do this
+ * with the planCacheListPlans command on the same query shape with the index filters.
*
+ * @tags: [
+ * # This test attempts to perform queries with plan cache filters set up. The former operation
+ * # may be routed to a secondary in the replica set, whereas the latter must be routed to the
+ * # primary.
+ * assumes_read_preference_unchanged,
+ * ]
*/
load("jstests/libs/analyze_plan.js");
diff --git a/jstests/core/index_stats.js b/jstests/core/index_stats.js
index 60b37fd571e..ee4d13d4d0a 100644
--- a/jstests/core/index_stats.js
+++ b/jstests/core/index_stats.js
@@ -1,3 +1,10 @@
+// @tags: [
+// # This test attempts to perform write operations and get index usage statistics using the
+// # $indexStats stage. The former operation must be routed to the primary in a replica set,
+// # whereas the latter may be routed to a secondary.
+// assumes_read_preference_unchanged,
+// ]
+
(function() {
"use strict";
diff --git a/jstests/core/js3.js b/jstests/core/js3.js
index 4d46c25bbf7..c808e7ec75a 100644
--- a/jstests/core/js3.js
+++ b/jstests/core/js3.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.jstests_js3;
diff --git a/jstests/core/js7.js b/jstests/core/js7.js
index 810f4692d4f..99cddd114f8 100644
--- a/jstests/core/js7.js
+++ b/jstests/core/js7.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
t = db.jstests_js7;
t.drop();
diff --git a/jstests/core/js9.js b/jstests/core/js9.js
index 515fa883aea..e0703ac39ea 100644
--- a/jstests/core/js9.js
+++ b/jstests/core/js9.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
c = db.jstests_js9;
c.drop();
diff --git a/jstests/core/js_jit.js b/jstests/core/js_jit.js
new file mode 100644
index 00000000000..4ccdd2917ae
--- /dev/null
+++ b/jstests/core/js_jit.js
@@ -0,0 +1,40 @@
+/**
+ * Validate various native types continue to work when run in JITed code.
+ *
+ * In SERVER-30362, the JIT would not compile natives types which had custom getProperty
+ * implementations correctly. We force the JIT to kick in by using large loops.
+ */
+(function() {
+ 'use strict';
+
+ function testDBCollection() {
+ const c = new DBCollection(null, null, "foo", "test.foo");
+ for (let i = 0; i < 100000; i++) {
+ if (c.toString() != "test.foo") {
+ throw i;
+ }
+ }
+ }
+
+ function testDB() {
+ const c = new DB(null, "test");
+ for (let i = 0; i < 100000; i++) {
+ if (c.toString() != "test") {
+ throw i;
+ }
+ }
+ }
+
+ function testDBQuery() {
+ const c = DBQuery('a', 'b', 'c', 'd');
+ for (let i = 0; i < 100000; i++) {
+ if (c.toString() != "DBQuery: d -> null") {
+ throw i;
+ }
+ }
+ }
+
+ testDBCollection();
+ testDB();
+ testDBQuery();
+})(); \ No newline at end of file
diff --git a/jstests/core/list_collections1.js b/jstests/core/list_collections1.js
index c8c3f92fbc9..ff1aac304ff 100644
--- a/jstests/core/list_collections1.js
+++ b/jstests/core/list_collections1.js
@@ -72,8 +72,8 @@
//
var getListCollectionsCursor = function(options, subsequentBatchSize) {
- return new DBCommandCursor(
- mydb.getMongo(), mydb.runCommand("listCollections", options), subsequentBatchSize);
+ var res = mydb.runCommand("listCollections", options);
+ return new DBCommandCursor(res._mongo, res, subsequentBatchSize);
};
var cursorCountMatching = function(cursor, pred) {
@@ -282,9 +282,9 @@
assert.commandWorked(mydb.createCollection("quux"));
res = mydb.runCommand("listCollections", {cursor: {batchSize: 0}});
- cursor = new DBCommandCursor(mydb.getMongo(), res, 2);
+ cursor = new DBCommandCursor(res._mongo, res, 2);
cursor.close();
- cursor = new DBCommandCursor(mydb.getMongo(), res, 2);
+ cursor = new DBCommandCursor(res._mongo, res, 2);
assert.throws(function() {
cursor.hasNext();
});
diff --git a/jstests/core/list_collections_filter.js b/jstests/core/list_collections_filter.js
index e0d18f055d0..0516d57a417 100644
--- a/jstests/core/list_collections_filter.js
+++ b/jstests/core/list_collections_filter.js
@@ -19,8 +19,8 @@
filter = {};
}
- var cursor = new DBCommandCursor(mydb.getMongo(),
- mydb.runCommand("listCollections", {filter: filter}));
+ var res = mydb.runCommand("listCollections", {filter: filter});
+ var cursor = new DBCommandCursor(res._mongo, res);
function stripToName(result) {
return result.name;
}
diff --git a/jstests/core/list_indexes.js b/jstests/core/list_indexes.js
index de0f4473980..6ec9a7a13e1 100644
--- a/jstests/core/list_indexes.js
+++ b/jstests/core/list_indexes.js
@@ -27,8 +27,8 @@
//
var getListIndexesCursor = function(coll, options, subsequentBatchSize) {
- return new DBCommandCursor(
- coll.getDB().getMongo(), coll.runCommand("listIndexes", options), subsequentBatchSize);
+ var res = coll.runCommand("listIndexes", options);
+ return new DBCommandCursor(res._mongo, res, subsequentBatchSize);
};
var cursorGetIndexSpecs = function(cursor) {
@@ -163,9 +163,9 @@
assert.commandWorked(coll.ensureIndex({c: 1}, {unique: true}));
res = coll.runCommand("listIndexes", {cursor: {batchSize: 0}});
- cursor = new DBCommandCursor(coll.getDB().getMongo(), res, 2);
+ cursor = new DBCommandCursor(res._mongo, res, 2);
cursor.close();
- cursor = new DBCommandCursor(coll.getDB().getMongo(), res, 2);
+ cursor = new DBCommandCursor(res._mongo, res, 2);
assert.throws(function() {
cursor.hasNext();
});
diff --git a/jstests/core/list_indexes_invalidation.js b/jstests/core/list_indexes_invalidation.js
index b8cbe5eb134..9fe94de5efc 100644
--- a/jstests/core/list_indexes_invalidation.js
+++ b/jstests/core/list_indexes_invalidation.js
@@ -19,7 +19,7 @@
printjson(res);
// Ensure the cursor has data, rename or drop the collection, and exhaust the cursor.
- let cursor = new DBCommandCursor(db.getMongo(), res);
+ let cursor = new DBCommandCursor(res._mongo, res);
let errMsg =
'expected more data from command ' + tojson(cmd) + ', with result ' + tojson(res);
assert(cursor.hasNext(), errMsg);
diff --git a/jstests/core/list_namespaces_invalidation.js b/jstests/core/list_namespaces_invalidation.js
index 6f8033b5fe4..85eb26510bb 100644
--- a/jstests/core/list_namespaces_invalidation.js
+++ b/jstests/core/list_namespaces_invalidation.js
@@ -1,4 +1,6 @@
// SERVER-27996/SERVER-28022 Missing invalidation for system.namespaces writes
+//
+// @tags: [requires_collmod_command]
(function() {
'use strict';
let dbInvalidName = 'system_namespaces_invalidations';
diff --git a/jstests/core/max_time_ms.js b/jstests/core/max_time_ms.js
index 0442ffcba68..efe3dabcc6d 100644
--- a/jstests/core/max_time_ms.js
+++ b/jstests/core/max_time_ms.js
@@ -1,4 +1,12 @@
// Tests query/command option $maxTimeMS.
+//
+// @tags: [
+// # This test attempts to perform read operations after having enabled the maxTimeAlwaysTimeOut
+// # failpoint. The former operations may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// requires_collmod_command,
+// ]
var t = db.max_time_ms;
var exceededTimeLimit = 50; // ErrorCodes::ExceededTimeLimit
diff --git a/jstests/core/mr4.js b/jstests/core/mr4.js
index 58ea303f7e8..683583acecd 100644
--- a/jstests/core/mr4.js
+++ b/jstests/core/mr4.js
@@ -9,7 +9,7 @@ t.save({x: 4, tags: ["b", "c"]});
m = function() {
this.tags.forEach(function(z) {
- emit(z, {count: xx});
+ emit(z, {count: xx.val});
});
};
@@ -21,7 +21,7 @@ r = function(key, values) {
return {count: total};
};
-res = t.mapReduce(m, r, {out: "mr4_out", scope: {xx: 1}});
+res = t.mapReduce(m, r, {out: "mr4_out", scope: {xx: {val: 1}}});
z = res.convertToSingleObject();
assert.eq(3, Object.keySet(z).length, "A1");
@@ -31,7 +31,7 @@ assert.eq(3, z.c.count, "A4");
res.drop();
-res = t.mapReduce(m, r, {scope: {xx: 2}, out: "mr4_out"});
+res = t.mapReduce(m, r, {scope: {xx: {val: 2}}, out: "mr4_out"});
z = res.convertToSingleObject();
assert.eq(3, Object.keySet(z).length, "A1");
diff --git a/jstests/core/mr_killop.js b/jstests/core/mr_killop.js
index 78e98f0bcaa..e85986e2fc0 100644
--- a/jstests/core/mr_killop.js
+++ b/jstests/core/mr_killop.js
@@ -4,7 +4,7 @@ t = db.jstests_mr_killop;
t.drop();
t2 = db.jstests_mr_killop_out;
t2.drop();
-
+db.adminCommand({"configureFailPoint": 'mr_killop_test_fp', "mode": 'alwaysOn'});
function debug(x) {
// printjson( x );
}
@@ -171,3 +171,4 @@ var loop = function() {
};
runMRTests(loop, false);
runFinalizeTests(loop, false);
+db.adminCommand({"configureFailPoint": 'mr_killop_test_fp', "mode": 'off'});
diff --git a/jstests/core/mr_optim.js b/jstests/core/mr_optim.js
index 7437753ca67..1c525ae3de3 100644
--- a/jstests/core/mr_optim.js
+++ b/jstests/core/mr_optim.js
@@ -3,8 +3,17 @@
t = db.mr_optim;
t.drop();
+// We drop the output collection to ensure the test can be run multiple times successfully. We
+// explicitly avoid using the DBCollection#drop() shell helper to avoid implicitly sharding the
+// collection during the sharded_collections_jscore_passthrough.yml test suite when reading the
+// results from the output collection in the reformat() function.
+var res = db.runCommand({drop: "mr_optim_out"});
+if (res.ok !== 1) {
+ assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
+}
+
for (var i = 0; i < 1000; ++i) {
- t.save({a: Math.random(1000), b: Math.random(10000)});
+ assert.writeOK(t.save({a: Math.random(1000), b: Math.random(10000)}));
}
function m() {
@@ -21,7 +30,7 @@ function reformat(r) {
if (r.results)
cursor = r.results;
else
- cursor = r.find();
+ cursor = r.find().sort({_id: 1});
cursor.forEach(function(z) {
x[z._id] = z.value;
});
@@ -43,4 +52,4 @@ res.drop();
assert.eq(x, x2, "object from inline and collection are not equal");
-t.drop(); \ No newline at end of file
+t.drop();
diff --git a/jstests/core/no_db_created.js b/jstests/core/no_db_created.js
index 3491914d470..b67b193494c 100644
--- a/jstests/core/no_db_created.js
+++ b/jstests/core/no_db_created.js
@@ -1,4 +1,6 @@
// checks that operations do not create a database
+//
+// @tags: [requires_collmod_command]
(function() {
"use strict";
@@ -32,4 +34,4 @@
noDB(mydb);
assert.writeOK(coll.insert({}));
mydb.dropDatabase();
-}()); \ No newline at end of file
+}());
diff --git a/jstests/core/notablescan.js b/jstests/core/notablescan.js
index 80306c08cf2..bb4c170a603 100644
--- a/jstests/core/notablescan.js
+++ b/jstests/core/notablescan.js
@@ -1,4 +1,11 @@
// check notablescan mode
+//
+// @tags: [
+// # This test attempts to perform read operations after having enabled the notablescan server
+// # parameter. The former operations may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
t = db.test_notablescan;
t.drop();
diff --git a/jstests/core/operation_latency_histogram.js b/jstests/core/operation_latency_histogram.js
index 1e3f1a59b95..947a8be6520 100644
--- a/jstests/core/operation_latency_histogram.js
+++ b/jstests/core/operation_latency_histogram.js
@@ -1,4 +1,10 @@
// Checks that histogram counters for collections are updated as we expect.
+//
+// This test attempts to perform write operations and get latency statistics using the $collStats
+// stage. The former operation must be routed to the primary in a replica set, whereas the latter
+// may be routed to a secondary.
+//
+// @tags: [assumes_read_preference_unchanged]
(function() {
"use strict";
diff --git a/jstests/core/plan_cache_clear.js b/jstests/core/plan_cache_clear.js
index 8f9cf0ea302..778239616b5 100644
--- a/jstests/core/plan_cache_clear.js
+++ b/jstests/core/plan_cache_clear.js
@@ -1,5 +1,12 @@
// Test clearing of the plan cache, either manually through the planCacheClear command,
// or due to system events such as an index build.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect/manipulate the server's plan cache
+// # entries. The former operation may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
var t = db.jstests_plan_cache_clear;
t.drop();
diff --git a/jstests/core/plan_cache_list_plans.js b/jstests/core/plan_cache_list_plans.js
index 7ca599483ff..3359980ab07 100644
--- a/jstests/core/plan_cache_list_plans.js
+++ b/jstests/core/plan_cache_list_plans.js
@@ -1,4 +1,11 @@
// Test the planCacheListPlans command.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
var t = db.jstests_plan_cache_list_plans;
t.drop();
diff --git a/jstests/core/plan_cache_list_shapes.js b/jstests/core/plan_cache_list_shapes.js
index 1c9ecdf9e1b..61c3111cd8a 100644
--- a/jstests/core/plan_cache_list_shapes.js
+++ b/jstests/core/plan_cache_list_shapes.js
@@ -1,5 +1,12 @@
// Test the planCacheListQueryShapes command, which returns a list of query shapes
// for the queries currently cached in the collection.
+//
+// @tags: [
+// # This test attempts to perform queries with plan cache filters set up. The former operation
+// # may be routed to a secondary in the replica set, whereas the latter must be routed to the
+// # primary.
+// assumes_read_preference_unchanged,
+// ]
var t = db.jstests_plan_cache_list_shapes;
t.drop();
diff --git a/jstests/core/plan_cache_shell_helpers.js b/jstests/core/plan_cache_shell_helpers.js
index dc990b19dcc..6c4ff185014 100644
--- a/jstests/core/plan_cache_shell_helpers.js
+++ b/jstests/core/plan_cache_shell_helpers.js
@@ -1,4 +1,11 @@
// Test the shell helpers which wrap the plan cache commands.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// ]
var t = db.jstests_plan_cache_shell_helpers;
t.drop();
diff --git a/jstests/core/profile2.js b/jstests/core/profile2.js
index bb1605abd1e..9da6410d2ac 100644
--- a/jstests/core/profile2.js
+++ b/jstests/core/profile2.js
@@ -24,7 +24,7 @@ assert(result.hasOwnProperty('millis'));
assert(result.hasOwnProperty('query'));
assert.eq('string', typeof(result.query));
// String value is truncated.
-assert(result.query.match(/filter: { a: "a+\.\.\." } }$/));
+assert(result.query.match(/filter: { a: "a+\.\.\." }/));
assert.commandWorked(coll.getDB().runCommand({profile: 0}));
coll.getDB().system.profile.drop();
diff --git a/jstests/core/profile_getmore.js b/jstests/core/profile_getmore.js
index a9272567b1a..3f6f492597e 100644
--- a/jstests/core/profile_getmore.js
+++ b/jstests/core/profile_getmore.js
@@ -24,12 +24,13 @@
var cursor = coll.find({a: {$gt: 0}}).sort({a: 1}).batchSize(2);
cursor.next(); // Perform initial query and consume first of 2 docs returned.
- var cursorId = getLatestProfilerEntry(testDB).cursorid; // Save cursorid from find.
+ var cursorId =
+ getLatestProfilerEntry(testDB, {op: "query"}).cursorid; // Save cursorid from find.
cursor.next(); // Consume second of 2 docs from initial query.
cursor.next(); // getMore performed, leaving open cursor.
- var profileObj = getLatestProfilerEntry(testDB);
+ var profileObj = getLatestProfilerEntry(testDB, {op: "getmore"});
assert.eq(profileObj.ns, coll.getFullName(), tojson(profileObj));
assert.eq(profileObj.op, "getmore", tojson(profileObj));
@@ -67,7 +68,7 @@
cursor.next(); // Consume second of 2 docs from initial query.
cursor.next(); // getMore performed, leaving open cursor.
- profileObj = getLatestProfilerEntry(testDB);
+ profileObj = getLatestProfilerEntry(testDB, {op: "getmore"});
assert.eq(profileObj.hasSortStage, true, tojson(profileObj));
@@ -83,7 +84,7 @@
cursor.next(); // Perform initial query and consume first of 3 docs returned.
cursor.itcount(); // Exhaust the cursor.
- profileObj = getLatestProfilerEntry(testDB);
+ profileObj = getLatestProfilerEntry(testDB, {op: "getmore"});
assert(profileObj.hasOwnProperty("cursorid"),
tojson(profileObj)); // cursorid should always be present on getMore.
@@ -101,12 +102,12 @@
assert.commandWorked(coll.createIndex({a: 1}));
var cursor = coll.aggregate([{$match: {a: {$gte: 0}}}], {cursor: {batchSize: 0}});
- var cursorId = getLatestProfilerEntry(testDB).cursorid;
+ var cursorId = getLatestProfilerEntry(testDB, {"command.aggregate": coll.getName()}).cursorid;
assert.neq(0, cursorId);
cursor.next(); // Consume the result set.
- profileObj = getLatestProfilerEntry(testDB);
+ profileObj = getLatestProfilerEntry(testDB, {op: "getmore"});
assert.eq(profileObj.ns, coll.getFullName(), tojson(profileObj));
assert.eq(profileObj.op, "getmore", tojson(profileObj));
diff --git a/jstests/core/profile_insert.js b/jstests/core/profile_insert.js
index 3994c896bda..836ec2ecff2 100644
--- a/jstests/core/profile_insert.js
+++ b/jstests/core/profile_insert.js
@@ -1,4 +1,6 @@
// Confirms that profiled insert execution contains all expected metrics with proper values.
+//
+// @tags: [assumes_write_concern_unchanged]
(function() {
"use strict";
diff --git a/jstests/core/recursion.js b/jstests/core/recursion.js
index 926250be20d..1db491c8ae9 100644
--- a/jstests/core/recursion.js
+++ b/jstests/core/recursion.js
@@ -1,5 +1,7 @@
-// Basic tests for a form of stack recursion that's been shown to cause C++
-// side stack overflows in the past. See SERVER-19614.
+// Basic tests for a form of stack recursion that's been shown to cause C++ side stack overflows in
+// the past. See SERVER-19614.
+//
+// @tags: [requires_eval_command]
(function() {
"use strict";
diff --git a/jstests/core/regex_not_id.js b/jstests/core/regex_not_id.js
index 1f15250f240..35b2c858867 100644
--- a/jstests/core/regex_not_id.js
+++ b/jstests/core/regex_not_id.js
@@ -3,10 +3,10 @@
var testColl = db.regex_not_id;
testColl.drop();
-assert.writeOK(testColl.insert({_id: "ABCDEF1"}, {writeConcern: {w: 1}}));
+assert.writeOK(testColl.insert({_id: "ABCDEF1"}));
// Should be an error.
-assert.writeError(testColl.insert({_id: /^A/}, {writeConcern: {w: 1}}));
+assert.writeError(testColl.insert({_id: /^A/}));
// _id doesn't have to be first; still disallowed
-assert.writeError(testColl.insert({xxx: "ABCDEF", _id: /ABCDEF/}, {writeConcern: {w: 1}})); \ No newline at end of file
+assert.writeError(testColl.insert({xxx: "ABCDEF", _id: /ABCDEF/}));
diff --git a/jstests/core/remove8.js b/jstests/core/remove8.js
index 563e4708cf9..3c9fd6a11a1 100644
--- a/jstests/core/remove8.js
+++ b/jstests/core/remove8.js
@@ -1,3 +1,4 @@
+// @tags: [requires_eval_command]
t = db.remove8;
t.drop();
diff --git a/jstests/core/rename4.js b/jstests/core/rename4.js
index 185193deaa9..756918db5f6 100644
--- a/jstests/core/rename4.js
+++ b/jstests/core/rename4.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
t = db.jstests_rename4;
t.drop();
diff --git a/jstests/core/rename_stayTemp.js b/jstests/core/rename_stayTemp.js
index d8451af2d2d..8dee8f89fea 100644
--- a/jstests/core/rename_stayTemp.js
+++ b/jstests/core/rename_stayTemp.js
@@ -11,7 +11,7 @@ function ns(coll) {
function istemp(name) {
var result = db.runCommand("listCollections", {filter: {name: name}});
assert(result.ok);
- var collections = new DBCommandCursor(db.getMongo(), result).toArray();
+ var collections = new DBCommandCursor(result._mongo, result).toArray();
assert.eq(1, collections.length);
return collections[0].options.temp ? true : false;
}
diff --git a/jstests/core/shell_connection_strings.js b/jstests/core/shell_connection_strings.js
new file mode 100644
index 00000000000..22861e6ce25
--- /dev/null
+++ b/jstests/core/shell_connection_strings.js
@@ -0,0 +1,33 @@
+// Test mongo shell connect strings.
+(function() {
+ 'use strict';
+
+ const mongod = new MongoURI(db.getMongo().host).servers[0];
+ const host = mongod.host;
+ const port = mongod.port;
+
+ function testConnect(ok, ...args) {
+ const exitCode = runMongoProgram('mongo', '--eval', ';', ...args);
+ if (ok) {
+ assert.eq(exitCode, 0, "failed to connect with `" + args.join(' ') + "`");
+ } else {
+ assert.neq(
+ exitCode, 0, "unexpectedly succeeded connecting with `" + args.join(' ') + "`");
+ }
+ }
+
+ testConnect(true, `${host}:${port}`);
+ testConnect(true, `${host}:${port}/test`);
+ testConnect(true, `${host}:${port}/admin`);
+ testConnect(true, host, '--port', port);
+ testConnect(true, '--host', host, '--port', port, 'test');
+ testConnect(true, '--host', host, '--port', port, 'admin');
+ testConnect(true, `mongodb://${host}:${port}/test`);
+ testConnect(true, `mongodb://${host}:${port}/test?connectTimeoutMS=10000`);
+
+ // if a full URI is provided, you cannot also specify host or port
+ testConnect(false, `${host}/test`, '--port', port);
+ testConnect(false, `mongodb://${host}:${port}/test`, '--port', port);
+ testConnect(false, `mongodb://${host}:${port}/test`, '--host', host);
+ testConnect(false, `mongodb://${host}:${port}/test`, '--host', host, '--port', port);
+})();
diff --git a/jstests/core/shell_writeconcern.js b/jstests/core/shell_writeconcern.js
index f3f190061cf..e3e7a23a9aa 100644
--- a/jstests/core/shell_writeconcern.js
+++ b/jstests/core/shell_writeconcern.js
@@ -1,7 +1,10 @@
"use strict";
+
// check that shell writeconcern work correctly
// 1.) tests that it can be set on each level and is inherited
// 2.) tests that each operation (update/insert/remove/save) take and ensure a write concern
+//
+// @tags: [assumes_write_concern_unchanged]
var collA = db.shell_wc_a;
var collB = db.shell_wc_b;
diff --git a/jstests/core/stages_delete.js b/jstests/core/stages_delete.js
index f8e7380c75a..98a270cedf3 100644
--- a/jstests/core/stages_delete.js
+++ b/jstests/core/stages_delete.js
@@ -1,3 +1,9 @@
+// @tags: [
+// # This test attempts to remove documents using the stageDebug command, which doesn't support
+// # specifying a writeConcern.
+// assumes_write_concern_unchanged,
+// ]
+
// Test basic delete stage functionality.
var coll = db.stages_delete;
var collScanStage = {cscan: {args: {direction: 1}, filter: {deleteMe: true}}};
diff --git a/jstests/core/startup_log.js b/jstests/core/startup_log.js
index 3b0cbe3464d..c73013d1744 100644
--- a/jstests/core/startup_log.js
+++ b/jstests/core/startup_log.js
@@ -1,101 +1,108 @@
-load('jstests/aggregation/extras/utils.js');
-
-(function() {
- 'use strict';
-
- // Check that smallArray is entirely contained by largeArray
- // returns false if a member of smallArray is not in largeArray
- function arrayIsSubset(smallArray, largeArray) {
- for (var i = 0; i < smallArray.length; i++) {
- if (!Array.contains(largeArray, smallArray[i])) {
- print("Could not find " + smallArray[i] + " in largeArray");
- return false;
- }
- }
-
- return true;
- }
-
- // Test startup_log
- var stats = db.getSisterDB("local").startup_log.stats();
- assert(stats.capped);
-
- var latestStartUpLog =
- db.getSisterDB("local").startup_log.find().sort({$natural: -1}).limit(1).next();
- var serverStatus = db._adminCommand("serverStatus");
- var cmdLine = db._adminCommand("getCmdLineOpts").parsed;
-
- // Test that the startup log has the expected keys
- var verbose = false;
- var expectedKeys =
- ["_id", "hostname", "startTime", "startTimeLocal", "cmdLine", "pid", "buildinfo"];
- var keys = Object.keySet(latestStartUpLog);
- assert(arrayEq(expectedKeys, keys, verbose), 'startup_log keys failed');
-
- // Tests _id implicitly - should be comprised of host-timestamp
- // Setup expected startTime and startTimeLocal from the supplied timestamp
- var _id = latestStartUpLog._id.split('-'); // _id should consist of host-timestamp
- var _idUptime = _id.pop();
- var _idHost = _id.join('-');
- var uptimeSinceEpochRounded = Math.floor(_idUptime / 1000) * 1000;
- var startTime = new Date(uptimeSinceEpochRounded); // Expected startTime
-
- assert.eq(_idHost, latestStartUpLog.hostname, "Hostname doesn't match one from _id");
- assert.eq(serverStatus.host.split(':')[0],
- latestStartUpLog.hostname,
- "Hostname doesn't match one in server status");
- assert.closeWithinMS(startTime,
- latestStartUpLog.startTime,
- "StartTime doesn't match one from _id",
- 2000); // Expect less than 2 sec delta
- assert.eq(cmdLine, latestStartUpLog.cmdLine, "cmdLine doesn't match that from getCmdLineOpts");
- assert.eq(serverStatus.pid, latestStartUpLog.pid, "pid doesn't match that from serverStatus");
-
- // Test buildinfo
- var buildinfo = db.runCommand("buildinfo");
- delete buildinfo.ok; // Delete extra meta info not in startup_log
- var isMaster = db._adminCommand("ismaster");
-
- // Test buildinfo has the expected keys
- var expectedKeys = [
- "version",
- "gitVersion",
- "allocator",
- "versionArray",
- "javascriptEngine",
- "openssl",
- "buildEnvironment",
- "debug",
- "maxBsonObjectSize",
- "bits",
- "modules"
- ];
-
- var keys = Object.keySet(latestStartUpLog.buildinfo);
- // Disabled to check
- assert(arrayIsSubset(expectedKeys, keys),
- "buildinfo keys failed! \n expected:\t" + expectedKeys + "\n actual:\t" + keys);
- assert.eq(buildinfo,
- latestStartUpLog.buildinfo,
- "buildinfo doesn't match that from buildinfo command");
-
- // Test version and version Array
- var version = latestStartUpLog.buildinfo.version.split('-')[0];
- var versionArray = latestStartUpLog.buildinfo.versionArray;
- var versionArrayCleaned = versionArray.slice(0, 3);
- if (versionArray[3] == -100) {
- versionArrayCleaned[2] -= 1;
- }
-
- assert.eq(serverStatus.version,
- latestStartUpLog.buildinfo.version,
- "Mongo version doesn't match that from ServerStatus");
- assert.eq(
- version, versionArrayCleaned.join('.'), "version doesn't match that from the versionArray");
- var jsEngine = latestStartUpLog.buildinfo.javascriptEngine;
- assert((jsEngine == "none") || jsEngine.startsWith("mozjs"));
- assert.eq(isMaster.maxBsonObjectSize,
- latestStartUpLog.buildinfo.maxBsonObjectSize,
- "maxBsonObjectSize doesn't match one from ismaster");
-
-})();
+/**
+ * This test attempts to read from the "local.startup_log" collection and assert that it has an
+ * entry matching the server's response from the "getCmdLineOpts" command. The former operation may
+ * be routed to a secondary in the replica set, whereas the latter must be routed to the primary.
+ *
+ * @tags: [assumes_read_preference_unchanged]
+ */
+load('jstests/aggregation/extras/utils.js');
+
+(function() {
+ 'use strict';
+
+ // Check that smallArray is entirely contained by largeArray
+ // returns false if a member of smallArray is not in largeArray
+ function arrayIsSubset(smallArray, largeArray) {
+ for (var i = 0; i < smallArray.length; i++) {
+ if (!Array.contains(largeArray, smallArray[i])) {
+ print("Could not find " + smallArray[i] + " in largeArray");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Test startup_log
+ var stats = db.getSisterDB("local").startup_log.stats();
+ assert(stats.capped);
+
+ var latestStartUpLog =
+ db.getSisterDB("local").startup_log.find().sort({$natural: -1}).limit(1).next();
+ var serverStatus = db._adminCommand("serverStatus");
+ var cmdLine = db._adminCommand("getCmdLineOpts").parsed;
+
+ // Test that the startup log has the expected keys
+ var verbose = false;
+ var expectedKeys =
+ ["_id", "hostname", "startTime", "startTimeLocal", "cmdLine", "pid", "buildinfo"];
+ var keys = Object.keySet(latestStartUpLog);
+ assert(arrayEq(expectedKeys, keys, verbose), 'startup_log keys failed');
+
+ // Tests _id implicitly - should be comprised of host-timestamp
+ // Setup expected startTime and startTimeLocal from the supplied timestamp
+ var _id = latestStartUpLog._id.split('-'); // _id should consist of host-timestamp
+ var _idUptime = _id.pop();
+ var _idHost = _id.join('-');
+ var uptimeSinceEpochRounded = Math.floor(_idUptime / 1000) * 1000;
+ var startTime = new Date(uptimeSinceEpochRounded); // Expected startTime
+
+ assert.eq(_idHost, latestStartUpLog.hostname, "Hostname doesn't match one from _id");
+ assert.eq(serverStatus.host.split(':')[0],
+ latestStartUpLog.hostname,
+ "Hostname doesn't match one in server status");
+ assert.closeWithinMS(startTime,
+ latestStartUpLog.startTime,
+ "StartTime doesn't match one from _id",
+ 2000); // Expect less than 2 sec delta
+ assert.eq(cmdLine, latestStartUpLog.cmdLine, "cmdLine doesn't match that from getCmdLineOpts");
+ assert.eq(serverStatus.pid, latestStartUpLog.pid, "pid doesn't match that from serverStatus");
+
+ // Test buildinfo
+ var buildinfo = db.runCommand("buildinfo");
+ delete buildinfo.ok; // Delete extra meta info not in startup_log
+ var isMaster = db._adminCommand("ismaster");
+
+ // Test buildinfo has the expected keys
+ var expectedKeys = [
+ "version",
+ "gitVersion",
+ "allocator",
+ "versionArray",
+ "javascriptEngine",
+ "openssl",
+ "buildEnvironment",
+ "debug",
+ "maxBsonObjectSize",
+ "bits",
+ "modules"
+ ];
+
+ var keys = Object.keySet(latestStartUpLog.buildinfo);
+ // Disabled to check
+ assert(arrayIsSubset(expectedKeys, keys),
+ "buildinfo keys failed! \n expected:\t" + expectedKeys + "\n actual:\t" + keys);
+ assert.eq(buildinfo,
+ latestStartUpLog.buildinfo,
+ "buildinfo doesn't match that from buildinfo command");
+
+ // Test version and version Array
+ var version = latestStartUpLog.buildinfo.version.split('-')[0];
+ var versionArray = latestStartUpLog.buildinfo.versionArray;
+ var versionArrayCleaned = versionArray.slice(0, 3);
+ if (versionArray[3] == -100) {
+ versionArrayCleaned[2] -= 1;
+ }
+
+ assert.eq(serverStatus.version,
+ latestStartUpLog.buildinfo.version,
+ "Mongo version doesn't match that from ServerStatus");
+ assert.eq(
+ version, versionArrayCleaned.join('.'), "version doesn't match that from the versionArray");
+ var jsEngine = latestStartUpLog.buildinfo.javascriptEngine;
+ assert((jsEngine == "none") || jsEngine.startsWith("mozjs"));
+ assert.eq(isMaster.maxBsonObjectSize,
+ latestStartUpLog.buildinfo.maxBsonObjectSize,
+ "maxBsonObjectSize doesn't match one from ismaster");
+
+})();
diff --git a/jstests/core/storefunc.js b/jstests/core/storefunc.js
index 8598e9cc62b..15abc56421e 100644
--- a/jstests/core/storefunc.js
+++ b/jstests/core/storefunc.js
@@ -1,3 +1,5 @@
+// @tags: [requires_eval_command]
+
// Use a private sister database to avoid conflicts with other tests that use system.js
var testdb = db.getSisterDB("storefunc");
var res;
diff --git a/jstests/core/top.js b/jstests/core/top.js
index 819b41b0981..3d98f5a7b2d 100644
--- a/jstests/core/top.js
+++ b/jstests/core/top.js
@@ -1,5 +1,11 @@
/**
* 1. check top numbers are correct
+ *
+ * This test attempts to perform read operations and get statistics using the top command. The
+ * former operation may be routed to a secondary in the replica set, whereas the latter must be
+ * routed to the primary.
+ *
+ * @tags: [assumes_read_preference_unchanged]
*/
(function() {
load("jstests/libs/stats.js");
diff --git a/jstests/core/update_affects_indexes.js b/jstests/core/update_affects_indexes.js
new file mode 100644
index 00000000000..91db3ebe565
--- /dev/null
+++ b/jstests/core/update_affects_indexes.js
@@ -0,0 +1,100 @@
+// This is a regression test for SERVER-32048. It checks that index keys are correctly updated when
+// an update modifier implicitly creates a new array element.
+(function() {
+ "use strict";
+
+ let coll = db.update_affects_indexes;
+ coll.drop();
+ let indexKeyPattern = {"a.b": 1};
+ assert.commandWorked(coll.createIndex(indexKeyPattern));
+
+ // Tests that the document 'docId' has all the index keys in 'expectedKeys' and none of the
+ // index keys in 'unexpectedKeys'.
+ function assertExpectedIndexKeys(docId, expectedKeys, unexpectedKeys) {
+ for (let key of expectedKeys) {
+ let res = coll.find(docId).hint(indexKeyPattern).min(key).returnKey().toArray();
+ assert.eq(1, res.length, tojson(res));
+ assert.eq(key, res[0]);
+ }
+
+ for (let key of unexpectedKeys) {
+ let res = coll.find(docId).hint(indexKeyPattern).min(key).returnKey().toArray();
+ if (res.length > 0) {
+ assert.eq(1, res.length, tojson(res));
+ assert.neq(key, res[0]);
+ }
+ }
+ }
+
+ // $set implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 0, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 0}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 0}, {$set: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 0}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $set implicitly creates array element beyond end of array.
+ assert.writeOK(coll.insert({_id: 1, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 1}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 1}, {$set: {"a.3.c": 0}}));
+ assertExpectedIndexKeys({_id: 1}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $set implicitly creates array element in empty array (no index key changes needed).
+ assert.writeOK(coll.insert({_id: 2, a: []}));
+ assertExpectedIndexKeys({_id: 2}, [{"a.b": null}], []);
+ assert.writeOK(coll.update({_id: 2}, {$set: {"a.0.c": 0}}));
+ assertExpectedIndexKeys({_id: 2}, [{"a.b": null}], []);
+
+ // $inc implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 3, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 3}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 3}, {$inc: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 3}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $mul implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 4, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 4}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 4}, {$mul: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 4}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $addToSet implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 5, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 5}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 5}, {$addToSet: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 5}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $bit implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 6, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 6}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 6}, {$bit: {"a.1.c": {and: NumberInt(1)}}}));
+ assertExpectedIndexKeys({_id: 6}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $min implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 7, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 7}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 7}, {$min: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 7}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $max implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 8, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 8}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 8}, {$max: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 8}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $currentDate implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 9, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 9}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 9}, {$currentDate: {"a.1.c": true}}));
+ assertExpectedIndexKeys({_id: 9}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $push implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 10, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 10}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 10}, {$push: {"a.1.c": 0}}));
+ assertExpectedIndexKeys({_id: 10}, [{"a.b": 0}, {"a.b": null}], []);
+
+ // $pushAll implicitly creates array element at end of array.
+ assert.writeOK(coll.insert({_id: 11, a: [{b: 0}]}));
+ assertExpectedIndexKeys({_id: 11}, [{"a.b": 0}], [{"a.b": null}]);
+ assert.writeOK(coll.update({_id: 11}, {$pushAll: {"a.1.c": [0]}}));
+ assertExpectedIndexKeys({_id: 11}, [{"a.b": 0}, {"a.b": null}], []);
+}());
diff --git a/jstests/core/update_multi5.js b/jstests/core/update_multi5.js
index e610462a620..9e9550bc554 100644
--- a/jstests/core/update_multi5.js
+++ b/jstests/core/update_multi5.js
@@ -7,8 +7,8 @@
assert.writeOK(t.insert({path: 'r1', subscribers: [1, 2]}));
assert.writeOK(t.insert({path: 'r2', subscribers: [3, 4]}));
- var res = assert.writeOK(t.update(
- {}, {$addToSet: {subscribers: 5}}, {upsert: false, multi: true, writeConcern: {w: 1}}));
+ var res =
+ assert.writeOK(t.update({}, {$addToSet: {subscribers: 5}}, {upsert: false, multi: true}));
assert.eq(res.nMatched, 2, tojson(res));
diff --git a/jstests/core/views/invalid_system_views.js b/jstests/core/views/invalid_system_views.js
index 3ba282d2ca1..49ac600ba8b 100644
--- a/jstests/core/views/invalid_system_views.js
+++ b/jstests/core/views/invalid_system_views.js
@@ -1,6 +1,8 @@
/**
* Tests that invalid view definitions in system.views do not impact valid commands on existing
* collections.
+ *
+ * @tags: [requires_collmod_command]
*/
(function() {
"use strict";
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index 0115672dcda..e1798803a1b 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -48,6 +48,8 @@
*
* skipStandalone
* If true, do not run this command on a standalone mongod.
+ *
+ * @tags: [requires_collmod_command]
*/
(function() {
diff --git a/jstests/core/views/views_basic.js b/jstests/core/views/views_basic.js
index d6032b105df..b4a2f177982 100644
--- a/jstests/core/views/views_basic.js
+++ b/jstests/core/views/views_basic.js
@@ -13,7 +13,7 @@
let res = viewsDB.runCommand(cmd);
assert.commandWorked(res);
- let cursor = new DBCommandCursor(db.getMongo(), res, 5);
+ let cursor = new DBCommandCursor(res._mongo, res, 5);
let actual = cursor.toArray();
assert(arrayEq(actual, expected),
"actual: " + tojson(cursor.toArray()) + ", expected:" + tojson(expected));
diff --git a/jstests/core/views/views_change.js b/jstests/core/views/views_change.js
index 002284095c5..a62e7e7f04f 100644
--- a/jstests/core/views/views_change.js
+++ b/jstests/core/views/views_change.js
@@ -1,6 +1,7 @@
/**
* Tests the behavior of views when the backing view or collection is changed.
- * @tags: [requires_find_command]
+ *
+ * @tags: [requires_collmod_command, requires_find_command]
*/
(function() {
"use strict";
diff --git a/jstests/core/views/views_collation.js b/jstests/core/views/views_collation.js
index 9e4ed7feb30..38557358ce9 100644
--- a/jstests/core/views/views_collation.js
+++ b/jstests/core/views/views_collation.js
@@ -1,9 +1,13 @@
/**
* Tests the behavior of operations when interacting with a view's default collation.
+ *
+ * @tags: [requires_collmod_command]
*/
(function() {
"use strict";
+ load("jstests/libs/analyze_plan.js");
+
let viewsDB = db.getSiblingDB("views_collation");
assert.commandWorked(viewsDB.dropDatabase());
assert.commandWorked(viewsDB.runCommand({create: "simpleCollection"}));
@@ -58,6 +62,15 @@
assert.commandWorked(viewsDB.runCommand({count: "filView"}));
assert.commandWorked(viewsDB.runCommand({distinct: "filView", key: "x"}));
+ // Explain of operations that do not specify a collation succeed.
+ assert.commandWorked(viewsDB.runCommand({aggregate: "filView", pipeline: [], explain: true}));
+ assert.commandWorked(
+ viewsDB.runCommand({explain: {find: "filView"}, verbosity: "allPlansExecution"}));
+ assert.commandWorked(
+ viewsDB.runCommand({explain: {count: "filView"}, verbosity: "allPlansExecution"}));
+ assert.commandWorked(viewsDB.runCommand(
+ {explain: {distinct: "filView", key: "x"}, verbosity: "allPlansExecution"}));
+
// Operations with a matching collation succeed.
assert.commandWorked(
viewsDB.runCommand({aggregate: "filView", pipeline: [], collation: {locale: "fil"}}));
@@ -66,6 +79,18 @@
assert.commandWorked(
viewsDB.runCommand({distinct: "filView", key: "x", collation: {locale: "fil"}}));
+ // Explain of operations with a matching collation succeed.
+ assert.commandWorked(viewsDB.runCommand(
+ {aggregate: "filView", pipeline: [], explain: true, collation: {locale: "fil"}}));
+ assert.commandWorked(viewsDB.runCommand(
+ {explain: {find: "filView", collation: {locale: "fil"}}, verbosity: "allPlansExecution"}));
+ assert.commandWorked(viewsDB.runCommand(
+ {explain: {count: "filView", collation: {locale: "fil"}}, verbosity: "allPlansExecution"}));
+ assert.commandWorked(viewsDB.runCommand({
+ explain: {distinct: "filView", key: "x", collation: {locale: "fil"}},
+ verbosity: "allPlansExecution"
+ }));
+
// Attempting to override the non-simple default collation of a view fails.
assert.commandFailedWithCode(
viewsDB.runCommand({aggregate: "filView", pipeline: [], collation: {locale: "en"}}),
@@ -90,6 +115,46 @@
viewsDB.runCommand({distinct: "filView", key: "x", collation: {locale: "simple"}}),
ErrorCodes.OptionNotSupportedOnView);
+ // Attempting to override the default collation of a view with explain fails.
+ assert.commandFailedWithCode(
+ viewsDB.runCommand(
+ {aggregate: "filView", pipeline: [], explain: true, collation: {locale: "en"}}),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(
+ viewsDB.runCommand(
+ {aggregate: "filView", pipeline: [], explain: true, collation: {locale: "simple"}}),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {find: "filView", collation: {locale: "fr"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {find: "filView", collation: {locale: "simple"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {count: "filView", collation: {locale: "zh"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {count: "filView", collation: {locale: "simple"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {distinct: "filView", key: "x", collation: {locale: "es"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+ assert.commandFailedWithCode(viewsDB.runCommand({
+ explain: {distinct: "filView", key: "x", collation: {locale: "simple"}},
+ verbosity: "allPlansExecution"
+ }),
+ ErrorCodes.OptionNotSupportedOnView);
+
const lookupSimpleView = {
$lookup: {from: "simpleView", localField: "x", foreignField: "x", as: "result"}
};
@@ -253,4 +318,56 @@
viewsDB.runCommand(
{collMod: "esView", viewOn: "simpleCollection", pipeline: [graphLookupFilView]}),
ErrorCodes.OptionNotSupportedOnView);
+
+ // Make sure that when an operation does not specify the collation, it correctly uses the
+ // default collation associated with the view. For this, we set up a new backing collection with
+ // a case-insensitive view.
+ assert.commandWorked(viewsDB.runCommand({create: "case_sensitive_coll"}));
+ assert.commandWorked(viewsDB.runCommand({
+ create: "case_insensitive_view",
+ viewOn: "case_sensitive_coll",
+ collation: {locale: "en", strength: 1}
+ }));
+
+ assert.writeOK(viewsDB.case_sensitive_coll.insert({f: "case"}));
+ assert.writeOK(viewsDB.case_sensitive_coll.insert({f: "Case"}));
+ assert.writeOK(viewsDB.case_sensitive_coll.insert({f: "CASE"}));
+
+ let explain, cursorStage;
+
+ // Test that aggregate against a view with a default collation correctly uses the collation.
+ assert.eq(1, viewsDB.case_sensitive_coll.aggregate([{$match: {f: "case"}}]).itcount());
+ assert.eq(3, viewsDB.case_insensitive_view.aggregate([{$match: {f: "case"}}]).itcount());
+ explain = viewsDB.case_insensitive_view.explain().aggregate([{$match: {f: "case"}}]);
+ cursorStage = getAggPlanStage(explain, "$cursor");
+ assert.neq(null, cursorStage, tojson(explain));
+ assert.eq(1, cursorStage.$cursor.queryPlanner.collation.strength, tojson(cursorStage));
+
+ // Test that count against a view with a default collation correctly uses the collation.
+ assert.eq(1, viewsDB.case_sensitive_coll.count({f: "case"}));
+ assert.eq(3, viewsDB.case_insensitive_view.count({f: "case"}));
+ explain = viewsDB.case_insensitive_view.explain().count({f: "case"});
+ cursorStage = getAggPlanStage(explain, "$cursor");
+ assert.neq(null, cursorStage, tojson(explain));
+ assert.eq(1, cursorStage.$cursor.queryPlanner.collation.strength, tojson(cursorStage));
+
+ // Test that distinct against a view with a default collation correctly uses the collation.
+ assert.eq(3, viewsDB.case_sensitive_coll.distinct("f").length);
+ assert.eq(1, viewsDB.case_insensitive_view.distinct("f").length);
+ explain = viewsDB.case_insensitive_view.explain().distinct("f");
+ cursorStage = getAggPlanStage(explain, "$cursor");
+ assert.neq(null, cursorStage, tojson(explain));
+ assert.eq(1, cursorStage.$cursor.queryPlanner.collation.strength, tojson(cursorStage));
+
+ // Test that find against a view with a default collation correctly uses the collation.
+ let findRes = viewsDB.runCommand({find: "case_sensitive_coll", filter: {f: "case"}});
+ assert.commandWorked(findRes);
+ assert.eq(1, findRes.cursor.firstBatch.length);
+ findRes = viewsDB.runCommand({find: "case_insensitive_view", filter: {f: "case"}});
+ assert.commandWorked(findRes);
+ assert.eq(3, findRes.cursor.firstBatch.length);
+ explain = viewsDB.runCommand({explain: {find: "case_insensitive_view", filter: {f: "case"}}});
+ cursorStage = getAggPlanStage(explain, "$cursor");
+ assert.neq(null, cursorStage, tojson(explain));
+ assert.eq(1, cursorStage.$cursor.queryPlanner.collation.strength, tojson(cursorStage));
}());
diff --git a/jstests/core/views/views_find.js b/jstests/core/views/views_find.js
index c196e980ce5..53c9d4e8b98 100644
--- a/jstests/core/views/views_find.js
+++ b/jstests/core/views/views_find.js
@@ -15,7 +15,7 @@
let assertFindResultEq = function(cmd, expected, ordered) {
let res = viewsDB.runCommand(cmd);
assert.commandWorked(res);
- let arr = new DBCommandCursor(db.getMongo(), res, 5).toArray();
+ let arr = new DBCommandCursor(res._mongo, res, 5).toArray();
let errmsg = tojson({expected: expected, got: arr});
if (typeof(ordered) === "undefined" || !ordered)
diff --git a/jstests/core/views/views_rename.js b/jstests/core/views/views_rename.js
new file mode 100644
index 00000000000..ad656671ea1
--- /dev/null
+++ b/jstests/core/views/views_rename.js
@@ -0,0 +1,17 @@
+(function() {
+ // SERVER-30406 Test that renaming system.views correctly invalidates the view catalog
+ 'use strict';
+ db.view.drop();
+ db.coll.drop();
+ assert.commandWorked(db.createView("view", "coll", []));
+ assert.writeOK(db.coll.insert({_id: 1}));
+ assert.eq(db.view.find().count(), 1, "couldn't find document in view");
+ assert.commandWorked(db.system.views.renameCollection("views", /*dropTarget*/ true));
+ assert.eq(db.view.find().count(),
+ 0,
+ "find on view should have returned no results after renaming away system.views");
+ assert.commandWorked(db.views.renameCollection("system.views"));
+ assert.eq(db.view.find().count(),
+ 1,
+ "find on view should have worked again after renaming system.views back in place");
+})();
diff --git a/jstests/core/views/views_stats.js b/jstests/core/views/views_stats.js
index 75feb857c9a..22261e9fa81 100644
--- a/jstests/core/views/views_stats.js
+++ b/jstests/core/views/views_stats.js
@@ -1,4 +1,10 @@
// Test that top and latency histogram statistics are recorded for views.
+//
+// This test attempts to perform write operations and get latency statistics using the $collStats
+// stage. The former operation must be routed to the primary in a replica set, whereas the latter
+// may be routed to a secondary.
+//
+// @tags: [assumes_read_preference_unchanged]
(function() {
"use strict";
diff --git a/jstests/core/views/views_validation.js b/jstests/core/views/views_validation.js
index 84c7f1d3510..b32750a082f 100644
--- a/jstests/core/views/views_validation.js
+++ b/jstests/core/views/views_validation.js
@@ -1,3 +1,4 @@
+// @tags: [requires_collmod_command]
(function() {
"use strict";
let viewsDb = db.getSiblingDB("views_validation");
diff --git a/jstests/core/write_result.js b/jstests/core/write_result.js
index 86486089c68..453be4ca1c9 100644
--- a/jstests/core/write_result.js
+++ b/jstests/core/write_result.js
@@ -1,6 +1,8 @@
//
// Tests the behavior of single writes using write commands
//
+// @tags: [assumes_write_concern_unchanged]
+//
var coll = db.write_result;
coll.drop();
diff --git a/jstests/hooks/validate_collections.js b/jstests/hooks/validate_collections.js
index aeb38a98bf5..e64bb39d305 100644
--- a/jstests/hooks/validate_collections.js
+++ b/jstests/hooks/validate_collections.js
@@ -71,6 +71,21 @@ function validateCollections(db, obj) {
filter = {$or: [filter, {type: {$exists: false}}]};
}
+ // Optionally skip collections.
+ if (Array.isArray(jsTest.options().skipValidationNamespaces) &&
+ jsTest.options().skipValidationNamespaces.length > 0) {
+ let skippedCollections = [];
+ for (let ns of jsTest.options().skipValidationNamespaces) {
+ // Strip off the database name from 'ns' to extract the collName.
+ const collName = ns.replace(new RegExp('^' + db.getName() + '\.'), '');
+ // Skip the collection 'collName' if the db name was removed from 'ns'.
+ if (collName !== ns) {
+ skippedCollections.push({name: {$ne: collName}});
+ }
+ }
+ filter = {$and: [filter, ...skippedCollections]};
+ }
+
let collInfo = db.getCollectionInfos(filter);
for (var collDocument of collInfo) {
var coll = db.getCollection(collDocument["name"]);
diff --git a/jstests/libs/analyze_plan.js b/jstests/libs/analyze_plan.js
index 62511ae1fac..5e52ad6aba3 100644
--- a/jstests/libs/analyze_plan.js
+++ b/jstests/libs/analyze_plan.js
@@ -115,3 +115,74 @@ function getChunkSkips(root) {
return 0;
}
+
+/**
+ * Given the root stage of agg explain's JSON representation of a query plan ('root'), returns all
+ * subdocuments whose stage is 'stage'. This can either be an agg stage name like "$cursor" or
+ * "$sort", or a query stage name like "IXSCAN" or "SORT".
+ *
+ * Returns an empty array if the plan does not have the requested stage. Asserts that agg explain
+ * structure matches expected format.
+ */
+function getAggPlanStages(root, stage) {
+ let results = [];
+
+ function getDocumentSources(docSourceArray) {
+ let results = [];
+ for (let i = 0; i < docSourceArray.length; i++) {
+ let properties = Object.getOwnPropertyNames(docSourceArray[i]);
+ assert.eq(1, properties.length);
+ if (properties[0] === stage) {
+ results.push(docSourceArray[i]);
+ }
+ }
+ return results;
+ }
+
+ if (root.hasOwnProperty("stages")) {
+ assert(root.stages.constructor === Array);
+
+ results = results.concat(getDocumentSources(root.stages));
+
+ assert(root.stages[0].hasOwnProperty("$cursor"));
+ assert(root.stages[0].$cursor.hasOwnProperty("queryPlanner"));
+ assert(root.stages[0].$cursor.queryPlanner.hasOwnProperty("winningPlan"));
+ results =
+ results.concat(getPlanStages(root.stages[0].$cursor.queryPlanner.winningPlan, stage));
+ }
+
+ if (root.hasOwnProperty("shards")) {
+ for (let elem in root.shards) {
+ assert(root.shards[elem].stages.constructor === Array);
+
+ results = results.concat(getDocumentSources(root.shards[elem].stages));
+
+ assert(root.shards[elem].stages[0].hasOwnProperty("$cursor"));
+ assert(root.shards[elem].stages[0].$cursor.hasOwnProperty("queryPlanner"));
+ assert(root.shards[elem].stages[0].$cursor.queryPlanner.hasOwnProperty("winningPlan"));
+ results = results.concat(
+ getPlanStages(root.shards[elem].stages[0].$cursor.queryPlanner.winningPlan, stage));
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Given the root stage of agg explain's JSON representation of a query plan ('root'), returns the
+ * subdocument with its stage as 'stage'. Returns null if the plan does not have such a stage.
+ * Asserts that no more than one stage is a match.
+ */
+function getAggPlanStage(root, stage) {
+ let planStageList = getAggPlanStages(root, stage);
+
+ if (planStageList.length === 0) {
+ return null;
+ } else {
+ assert.eq(1,
+ planStageList.length,
+ "getAggPlanStage expects to find 0 or 1 matching stages. planStageList: " +
+ tojson(planStageList));
+ return planStageList[0];
+ }
+}
diff --git a/jstests/libs/ftdc.js b/jstests/libs/ftdc.js
new file mode 100644
index 00000000000..7d327d39852
--- /dev/null
+++ b/jstests/libs/ftdc.js
@@ -0,0 +1,102 @@
+/**
+ * Utility test functions for FTDC
+ */
+'use strict';
+
+/**
+ * Verify that getDiagnosticData is working correctly.
+ */
+function verifyGetDiagnosticData(adminDb) {
+ // We need to retry a few times if run this test immediately after mongod is started as FTDC may
+ // not have run yet.
+ var foundGoodDocument = false;
+
+ for (var i = 0; i < 60 && foundGoodDocument == false; ++i) {
+ var result = adminDb.runCommand("getDiagnosticData");
+ assert.commandWorked(result);
+
+ var data = result.data;
+
+ if (!data.hasOwnProperty("start")) {
+ // Wait a little longer for FTDC to start
+ jsTestLog("Running getDiagnosticData: " + tojson(result));
+
+ sleep(500);
+ } else {
+ // Check for a few common properties to ensure we got data
+ assert(data.hasOwnProperty("serverStatus"),
+ "does not have 'serverStatus' in '" + tojson(data) + "'");
+ assert(data.hasOwnProperty("end"), "does not have 'end' in '" + tojson(data) + "'");
+ foundGoodDocument = true;
+
+ jsTestLog("Got good getDiagnosticData: " + tojson(result));
+ }
+ }
+
+ assert(foundGoodDocument,
+ "getDiagnosticData failed to return a non-empty command, is FTDC running?");
+}
+
+/**
+ * Validate all the common FTDC parameters are set correctly and can be manipulated.
+ */
+function verifyCommonFTDCParameters(adminDb, isEnabled) {
+ // Are we running against MongoS?
+ var isMongos = ("isdbgrid" == adminDb.runCommand("ismaster").msg);
+
+ // Check the defaults are correct
+ //
+ function getparam(field) {
+ var q = {getParameter: 1};
+ q[field] = 1;
+
+ var ret = adminDb.runCommand(q);
+ return ret[field];
+ }
+
+ // Verify the defaults are as we documented them
+ assert.eq(getparam("diagnosticDataCollectionEnabled"), isEnabled);
+ assert.eq(getparam("diagnosticDataCollectionPeriodMillis"), 1000);
+ assert.eq(getparam("diagnosticDataCollectionDirectorySizeMB"), 200);
+ assert.eq(getparam("diagnosticDataCollectionFileSizeMB"), 10);
+ assert.eq(getparam("diagnosticDataCollectionSamplesPerChunk"), 300);
+ assert.eq(getparam("diagnosticDataCollectionSamplesPerInterimUpdate"), 10);
+
+ function setparam(obj) {
+ var ret = adminDb.runCommand(Object.extend({setParameter: 1}, obj));
+ return ret;
+ }
+
+ if (!isMongos) {
+ // The MongoS specific behavior for diagnosticDataCollectionEnabled is tested in
+ // ftdc_setdirectory.js.
+ assert.commandWorked(setparam({"diagnosticDataCollectionEnabled": 1}));
+ }
+ assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 100}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 1}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 2}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 2}));
+
+ // Negative tests - set values below minimums
+ assert.commandFailed(setparam({"diagnosticDataCollectionPeriodMillis": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerChunk": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 1}));
+
+ // Negative test - set file size bigger then directory size
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionFileSizeMB": 100}));
+
+ // Negative test - set directory size less then file size
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 100}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 50}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+
+ // Reset
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 10}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 200}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 1000}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 300}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 10}));
+} \ No newline at end of file
diff --git a/jstests/libs/override_methods/override_helpers.js b/jstests/libs/override_methods/override_helpers.js
new file mode 100644
index 00000000000..12bd4b54739
--- /dev/null
+++ b/jstests/libs/override_methods/override_helpers.js
@@ -0,0 +1,117 @@
+/**
+ * The OverrideHelpers object defines convenience methods for overriding commands and functions in
+ * the mongo shell.
+ */
+var OverrideHelpers = (function() {
+ "use strict";
+
+ function isAggregationWithOutStage(commandName, commandObj) {
+ if (commandName !== "aggregate" || typeof commandObj !== "object" || commandObj === null) {
+ return false;
+ }
+
+ if (!Array.isArray(commandObj.pipeline) || commandObj.pipeline.length === 0) {
+ return false;
+ }
+
+ const lastStage = commandObj.pipeline[commandObj.pipeline.length - 1];
+ if (typeof lastStage !== "object" || lastStage === null) {
+ return false;
+ }
+
+ return Object.keys(lastStage)[0] === "$out";
+ }
+
+ function isMapReduceWithInlineOutput(commandName, commandObj) {
+ if ((commandName !== "mapReduce" && commandName !== "mapreduce") ||
+ typeof commandObj !== "object" || commandObj === null) {
+ return false;
+ }
+
+ if (typeof commandObj.out !== "object") {
+ return false;
+ }
+
+ return commandObj.out.hasOwnProperty("inline");
+ }
+
+ function prependOverrideInParallelShell(overrideFile) {
+ const startParallelShellOriginal = startParallelShell;
+
+ startParallelShell = function(jsCode, port, noConnect) {
+ let newCode;
+ if (typeof jsCode === "function") {
+ // Load the override file and immediately invoke the supplied function.
+ newCode = `load("${overrideFile}"); (${jsCode})();`;
+ } else {
+ newCode = `load("${overrideFile}"); ${jsCode};`;
+ }
+
+ return startParallelShellOriginal(newCode, port, noConnect);
+ };
+ }
+
+ function overrideRunCommand(overrideFunc) {
+ const DBQueryOriginal = DBQuery;
+ const mongoRunCommandOriginal = Mongo.prototype.runCommand;
+ const mongoRunCommandWithMetadataOriginal = Mongo.prototype.runCommandWithMetadata;
+
+ DBQuery = function(
+ mongo, db, collection, ns, query, fields, limit, skip, batchSize, options) {
+ // If the query isn't being run against the "$cmd" or "$cmd.sys" namespaces, then it
+ // represents an OP_QUERY find on that collection. We skip calling overrideFunc() in
+ // this case because the operation doesn't represent a command.
+ if (!(collection instanceof DBCollection &&
+ (collection.getName() === "$cmd" || collection.getName().startsWith("$cmd.")))) {
+ return DBQueryOriginal.apply(this, arguments);
+ }
+
+ // Due to the function signatures of Mongo.prototype.runCommand() and
+ // Mongo.prototype.runCommandWithMetadata(), the overrideFunc() function expects that
+ // the Mongo connection object is passed as the first argument and also represents the
+ // 'this' parameter. As a workaround, we bind the appropriate 'this' value to the
+ // DBQueryOriginal constructor ahead of time.
+ const commandName = Object.keys(query)[0];
+ return overrideFunc(
+ mongo,
+ db.getName(),
+ commandName,
+ query,
+ DBQueryOriginal.bind(this),
+ (query) =>
+ [mongo, db, collection, ns, query, fields, limit, skip, batchSize, options]);
+ };
+
+ // Copy any properties (e.g. DBQuery.Option) that are set on DBQueryOriginal.
+ Object.keys(DBQueryOriginal).forEach(function(key) {
+ DBQuery[key] = DBQueryOriginal[key];
+ });
+
+ Mongo.prototype.runCommand = function(dbName, commandObj, options) {
+ const commandName = Object.keys(commandObj)[0];
+ return overrideFunc(this,
+ dbName,
+ commandName,
+ commandObj,
+ mongoRunCommandOriginal,
+ (commandObj) => [dbName, commandObj, options]);
+ };
+
+ Mongo.prototype.runCommandWithMetadata = function(dbName, metadata, commandArgs) {
+ const commandName = Object.keys(commandArgs)[0];
+ return overrideFunc(this,
+ dbName,
+ commandName,
+ commandArgs,
+ mongoRunCommandWithMetadataOriginal,
+ (commandArgs) => [dbName, metadata, commandArgs]);
+ };
+ }
+
+ return {
+ isAggregationWithOutStage: isAggregationWithOutStage,
+ isMapReduceWithInlineOutput: isMapReduceWithInlineOutput,
+ prependOverrideInParallelShell: prependOverrideInParallelShell,
+ overrideRunCommand: overrideRunCommand,
+ };
+})();
diff --git a/jstests/libs/override_methods/set_majority_read_and_write_concerns.js b/jstests/libs/override_methods/set_majority_read_and_write_concerns.js
deleted file mode 100644
index d3bb4449ed4..00000000000
--- a/jstests/libs/override_methods/set_majority_read_and_write_concerns.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * Use prototype overrides to set a read concern of "majority" and a write concern of "majority"
- * while running core tests.
- */
-(function() {
- "use strict";
- var defaultWriteConcern = {
- w: "majority",
- // Use a "signature" value that won't typically match a value assigned in normal use.
- wtimeout: 60321
- };
- var defaultReadConcern = {level: "majority"};
-
- var originalDBQuery = DBQuery;
-
- DBQuery = function(mongo, db, collection, ns, query, fields, limit, skip, batchSize, options) {
- if (ns.endsWith("$cmd")) {
- if (query.hasOwnProperty("writeConcern") &&
- bsonWoCompare(query.writeConcern, defaultWriteConcern) !== 0) {
- jsTestLog("Warning: DBQuery overriding existing writeConcern of: " +
- tojson(query.writeConcern));
- query.writeConcern = defaultWriteConcern;
- }
- }
-
- return originalDBQuery.apply(this, arguments);
- };
-
- DBQuery.Option = originalDBQuery.Option;
-
- var originalStartParallelShell = startParallelShell;
- startParallelShell = function(jsCode, port, noConnect) {
- var newCode;
- var overridesFile = "jstests/libs/override_methods/set_majority_read_and_write_concerns.js";
-
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return originalStartParallelShell(newCode, port, noConnect);
- };
-
- DB.prototype._runCommandImpl = function(dbName, obj, options) {
- var cmdName = "";
- for (var fieldName in obj) {
- cmdName = fieldName;
- break;
- }
-
- // These commands directly support a writeConcern argument.
- var commandsToForceWriteConcern = [
- "_mergeAuthzCollections",
- "appendOplogNote",
- "applyOps",
- "authSchemaUpgrade",
- "captrunc",
- "cleanupOrphaned",
- "clone",
- "cloneCollection",
- "cloneCollectionAsCapped",
- // "collMod", SERVER-25196 - not supported
- "convertToCapped",
- "copydb",
- "create",
- "createIndexes",
- "createRole",
- "createUser",
- "delete",
- "drop",
- "dropDatabase",
- "dropAllRolesFromDatabase",
- "dropAllUsersFromDatabase",
- "dropDatabase",
- "dropIndexes",
- "dropRole",
- "dropUser",
- "emptycapped",
- "findAndModify",
- "findandmodify",
- "godinsert",
- "grantPrivilegesToRole",
- "grantRolesToRole",
- "grantRolesToUser",
- "insert",
- "mapReduceFinish",
- "mergeAuthzCollections",
- "moveChunk",
- "movePrimary",
- "reIndex",
- "remove",
- "renameCollection",
- "resvChunkStart",
- "revokePriviligesFromRole",
- "revokeRolesFromRole",
- "revokeRolesFromUser",
- "update",
- "updateRole",
- "updateUser",
- ];
-
- // These are reading commands that support majority readConcern.
- var commandsToForceReadConcern = [
- "count",
- "distinct",
- "find",
- "geoNear",
- "geoSearch",
- "group",
- ];
-
- var forceWriteConcern = Array.contains(commandsToForceWriteConcern, cmdName);
- var forceReadConcern = Array.contains(commandsToForceReadConcern, cmdName);
-
- if (cmdName === "aggregate") {
- // Aggregate can be either a read or a write depending on whether it has a $out stage.
- // $out is required to be the last stage of the pipeline.
- var stages = obj.pipeline;
- const lastStage = stages && Array.isArray(stages) && (stages.length !== 0)
- ? stages[stages.length - 1]
- : undefined;
- const hasOut =
- lastStage && (typeof lastStage === 'object') && lastStage.hasOwnProperty('$out');
- if (hasOut) {
- forceWriteConcern = true;
- } else {
- forceReadConcern = true;
- }
- }
-
- else if (cmdName === "mapReduce") {
- var stages = obj.pipeline;
- const lastStage = stages && Array.isArray(stages) && (stages.length !== 0)
- ? stages[stages.length - 1]
- : undefined;
- const hasOut =
- lastStage && (typeof lastStage === 'object') && lastStage.hasOwnProperty('$out');
- if (hasOut) {
- forceWriteConcern = true;
- }
- }
-
- if (forceWriteConcern) {
- if (obj.hasOwnProperty("writeConcern")) {
- if (bsonWoCompare(obj.writeConcern, defaultWriteConcern) !== 0) {
- jsTestLog("Warning: _runCommandImpl overriding existing writeConcern of: " +
- tojson(obj.writeConcern));
- obj.writeConcern = defaultWriteConcern;
- }
- } else {
- obj.writeConcern = defaultWriteConcern;
- }
-
- } else if (forceReadConcern) {
- if (obj.hasOwnProperty("readConcern")) {
- if (bsonWoCompare(obj.readConcern, defaultReadConcern) !== 0) {
- jsTestLog("Warning: _runCommandImpl overriding existing readConcern of: " +
- tojson(obj.readConcern));
- obj.readConcern = defaultReadConcern;
- }
- } else {
- obj.readConcern = defaultReadConcern;
- }
- }
-
- var res = this.getMongo().runCommand(dbName, obj, options);
-
- return res;
- };
-
- // Use a majority write concern if the operation does not specify one.
- DBCollection.prototype.getWriteConcern = function() {
- return new WriteConcern(defaultWriteConcern);
- };
-
-})();
diff --git a/jstests/libs/override_methods/set_read_and_write_concerns.js b/jstests/libs/override_methods/set_read_and_write_concerns.js
new file mode 100644
index 00000000000..f371644c8f7
--- /dev/null
+++ b/jstests/libs/override_methods/set_read_and_write_concerns.js
@@ -0,0 +1,216 @@
+/**
+ * Use prototype overrides to set read concern and write concern while running tests.
+ */
+(function() {
+ "use strict";
+
+ load("jstests/libs/override_methods/override_helpers.js");
+
+ if (typeof TestData === "undefined" || !TestData.hasOwnProperty("defaultReadConcernLevel")) {
+ throw new Error(
+ "The readConcern level to use must be set as the 'defaultReadConcernLevel'" +
+ " property on the global TestData object");
+ }
+
+ const kDefaultReadConcern = {level: TestData.defaultReadConcernLevel};
+ const kDefaultWriteConcern =
+ (TestData.hasOwnProperty("defaultWriteConcern")) ? TestData.defaultWriteConcern : {
+ w: "majority",
+ // Use a "signature" value that won't typically match a value assigned in normal use.
+ // This way the wtimeout set by this override is distinguishable in the server logs.
+ wtimeout: 5 * 60 * 1000 + 321, // 300321ms
+ };
+
+ const kCommandsSupportingReadConcern = new Set([
+ "aggregate",
+ "count",
+ "distinct",
+ "find",
+ "geoNear",
+ "geoSearch",
+ "group",
+ "parallelCollectionScan",
+ ]);
+
+ const kCommandsSupportingWriteConcern = new Set([
+ "_configsvrAddShard",
+ "_configsvrAddShardToZone",
+ "_configsvrCommitChunkMerge",
+ "_configsvrCommitChunkMigration",
+ "_configsvrCommitChunkSplit",
+ "_configsvrCreateDatabase",
+ "_configsvrEnableSharding",
+ "_configsvrMoveChunk",
+ "_configsvrMovePrimary",
+ "_configsvrRemoveShard",
+ "_configsvrRemoveShardFromZone",
+ "_configsvrShardCollection",
+ "_configsvrUpdateZoneKeyRange",
+ "_mergeAuthzCollections",
+ "_recvChunkStart",
+ "appendOplogNote",
+ "applyOps",
+ "authSchemaUpgrade",
+ "aggregate",
+ "captrunc",
+ "cleanupOrphaned",
+ "clone",
+ "cloneCollection",
+ "cloneCollectionAsCapped",
+ // "collMod", SERVER-25196 - not supported
+ "convertToCapped",
+ "copydb",
+ "create",
+ "createIndexes",
+ "createRole",
+ "createUser",
+ "delete",
+ "deleteIndexes",
+ "drop",
+ "dropAllRolesFromDatabase",
+ "dropAllUsersFromDatabase",
+ "dropDatabase",
+ "dropIndexes",
+ "dropRole",
+ "dropUser",
+ "emptycapped",
+ "findAndModify",
+ "findandmodify",
+ "godinsert",
+ "grantPrivilegesToRole",
+ "grantRolesToRole",
+ "grantRolesToUser",
+ "insert",
+ "mapReduce",
+ "mapreduce",
+ "mapreduce.shardedfinish",
+ "moveChunk",
+ "renameCollection",
+ "revokePrivilegesFromRole",
+ "revokeRolesFromRole",
+ "revokeRolesFromUser",
+ "setFeatureCompatibilityVersion",
+ "update",
+ "updateRole",
+ "updateUser",
+ ]);
+
+ function runCommandWithReadAndWriteConcerns(
+ conn, dbName, commandName, commandObj, func, makeFuncArgs) {
+ if (typeof commandObj !== "object" || commandObj === null) {
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
+
+ // If the command is in a wrapped form, then we look for the actual command object inside
+ // the query/$query object.
+ let commandObjUnwrapped = commandObj;
+ if (commandName === "query" || commandName === "$query") {
+ commandObjUnwrapped = commandObj[commandName];
+ commandName = Object.keys(commandObjUnwrapped)[0];
+ }
+
+ if (commandName === "collMod" || commandName === "eval" || commandName === "$eval") {
+ throw new Error("Cowardly refusing to run test with overridden write concern when it" +
+ " uses a command that can only perform w=1 writes: " +
+ tojson(commandObj));
+ }
+
+ let shouldForceReadConcern = kCommandsSupportingReadConcern.has(commandName);
+ let shouldForceWriteConcern = kCommandsSupportingWriteConcern.has(commandName);
+
+ if (commandName === "aggregate") {
+ if (OverrideHelpers.isAggregationWithOutStage(commandName, commandObjUnwrapped)) {
+ // The $out stage can only be used with readConcern={level: "local"}.
+ shouldForceReadConcern = false;
+ } else {
+ // A writeConcern can only be used with a $out stage.
+ shouldForceWriteConcern = false;
+ }
+
+ if (commandObjUnwrapped.explain) {
+ // Attempting to specify a readConcern while explaining an aggregation would always
+ // return an error prior to SERVER-30582 and it otherwise only compatible with
+ // readConcern={level: "local"}.
+ shouldForceReadConcern = false;
+ }
+ } else if (OverrideHelpers.isMapReduceWithInlineOutput(commandName, commandObjUnwrapped)) {
+ // A writeConcern can only be used with non-inline output.
+ shouldForceWriteConcern = false;
+ } else if (commandObj[commandName] === "system.profile") {
+ // Writes to the "system.profile" collection aren't guaranteed to be visible in the same
+ // majority-committed snapshot as the command they originated from. We don't override
+ // the readConcern for operations on the "system.profile" collection so that tests which
+ // assert on its contents continue to succeed.
+ shouldForceReadConcern = false;
+ }
+
+ const inWrappedForm = commandObj !== commandObjUnwrapped;
+
+ if (shouldForceReadConcern) {
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ if (inWrappedForm) {
+ commandObjUnwrapped = Object.assign({}, commandObjUnwrapped);
+ commandObj[Object.keys(commandObj)[0]] = commandObjUnwrapped;
+ } else {
+ commandObjUnwrapped = commandObj;
+ }
+
+ if (commandObjUnwrapped.hasOwnProperty("readConcern")) {
+ let readConcern = commandObjUnwrapped.readConcern;
+
+ if (typeof readConcern !== "object" || readConcern === null ||
+ (readConcern.hasOwnProperty("level") &&
+ bsonWoCompare({_: readConcern.level}, {_: kDefaultReadConcern.level}) !== 0)) {
+ throw new Error("Cowardly refusing to override read concern of command: " +
+ tojson(commandObj));
+ }
+
+ // We create a copy of the readConcern object to avoid mutating the parameter the
+ // caller specified.
+ readConcern = Object.assign({}, readConcern, kDefaultReadConcern);
+ commandObjUnwrapped.readConcern = readConcern;
+ } else {
+ commandObjUnwrapped.readConcern = kDefaultReadConcern;
+ }
+ }
+
+ if (shouldForceWriteConcern) {
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ if (inWrappedForm) {
+ commandObjUnwrapped = Object.assign({}, commandObjUnwrapped);
+ commandObj[Object.keys(commandObj)[0]] = commandObjUnwrapped;
+ } else {
+ commandObjUnwrapped = commandObj;
+ }
+
+ if (commandObjUnwrapped.hasOwnProperty("writeConcern")) {
+ let writeConcern = commandObjUnwrapped.writeConcern;
+
+ if (typeof writeConcern !== "object" || writeConcern === null ||
+ (writeConcern.hasOwnProperty("w") &&
+ bsonWoCompare({_: writeConcern.w}, {_: kDefaultWriteConcern.w}) !== 0)) {
+ throw new Error("Cowardly refusing to override write concern of command: " +
+ tojson(commandObj));
+ }
+
+ // We create a copy of the writeConcern object to avoid mutating the parameter the
+ // caller specified.
+ writeConcern = Object.assign({}, writeConcern, kDefaultWriteConcern);
+ commandObjUnwrapped.writeConcern = writeConcern;
+ } else {
+ commandObjUnwrapped.writeConcern = kDefaultWriteConcern;
+ }
+ }
+
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
+
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/set_read_and_write_concerns.js");
+
+ OverrideHelpers.overrideRunCommand(runCommandWithReadAndWriteConcerns);
+})();
diff --git a/jstests/libs/override_methods/set_read_preference_secondary.js b/jstests/libs/override_methods/set_read_preference_secondary.js
new file mode 100644
index 00000000000..d1d26433c5c
--- /dev/null
+++ b/jstests/libs/override_methods/set_read_preference_secondary.js
@@ -0,0 +1,162 @@
+/**
+ * Use prototype overrides to set read preference to "secondary" when running tests.
+ */
+(function() {
+ "use strict";
+
+ load("jstests/libs/override_methods/override_helpers.js");
+
+ const kReadPreferenceSecondary = {mode: "secondary"};
+ const kCommandsSupportingReadPreference = new Set([
+ "aggregate",
+ "collStats",
+ "count",
+ "dbStats",
+ "distinct",
+ "find",
+ "geoNear",
+ "geoSearch",
+ "group",
+ "mapReduce",
+ "mapreduce",
+ "parallelCollectionScan",
+ ]);
+
+ // This list of cursor-generating commands is incomplete. For example, "listCollections",
+ // "listIndexes", "parallelCollectionScan", and "repairCursor" are all missing from this list.
+ // If we ever add tests that attempt to run getMore or killCursors on cursors generated from
+ // those commands, then we should update the contents of this list and also handle any
+ // differences in the server's response format.
+ const kCursorGeneratingCommands = new Set(["aggregate", "find"]);
+
+ const CursorTracker = (function() {
+ const kNoCursor = new NumberLong(0);
+
+ const connectionsByCursorId = {};
+
+ return {
+ getConnectionUsedForCursor: function getConnectionUsedForCursor(cursorId) {
+ return (cursorId instanceof NumberLong) ? connectionsByCursorId[cursorId]
+ : undefined;
+ },
+
+ setConnectionUsedForCursor: function setConnectionUsedForCursor(cursorId, cursorConn) {
+ if (cursorId instanceof NumberLong &&
+ !bsonBinaryEqual({_: cursorId}, {_: kNoCursor})) {
+ connectionsByCursorId[cursorId] = cursorConn;
+ }
+ },
+ };
+ })();
+
+ function runCommandWithReadPreferenceSecondary(
+ conn, dbName, commandName, commandObj, func, makeFuncArgs) {
+ if (typeof commandObj !== "object" || commandObj === null) {
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
+
+ // If the command is in a wrapped form, then we look for the actual command object inside
+ // the query/$query object.
+ let commandObjUnwrapped = commandObj;
+ if (commandName === "query" || commandName === "$query") {
+ commandObjUnwrapped = commandObj[commandName];
+ commandName = Object.keys(commandObjUnwrapped)[0];
+ }
+
+ if (commandObj[commandName] === "system.profile") {
+ throw new Error("Cowardly refusing to run test with overridden read preference" +
+ " when it reads from a non-replicated collection: " +
+ tojson(commandObj));
+ }
+
+ if (conn.isReplicaSetConnection()) {
+ // When a "getMore" or "killCursors" command is issued on a replica set connection, we
+ // attempt to automatically route the command to the server the cursor(s) were
+ // originally established on. This makes it possible to use the
+ // set_read_preference_secondary.js override without needing to update calls of
+ // DB#runCommand() to explicitly track the connection that was used. If the connection
+ // is actually a direct connection to a mongod or mongos process, or if the cursor id
+ // cannot be found in the CursorTracker, then we'll fall back to using DBClientRS's
+ // server selection and send the operation to the current primary. It is possible that
+ // the test is trying to exercise the behavior around when an unknown cursor id is sent
+ // to the server.
+ if (commandName === "getMore") {
+ const cursorId = commandObjUnwrapped[commandName];
+ const cursorConn = CursorTracker.getConnectionUsedForCursor(cursorId);
+ if (cursorConn !== undefined) {
+ return func.apply(cursorConn, makeFuncArgs(commandObj));
+ }
+ } else if (commandName === "killCursors") {
+ const cursorIds = commandObjUnwrapped.cursors;
+ if (Array.isArray(cursorIds)) {
+ let cursorConn;
+
+ for (let cursorId of cursorIds) {
+ const otherCursorConn = CursorTracker.getConnectionUsedForCursor(cursorId);
+ if (cursorConn === undefined) {
+ cursorConn = otherCursorConn;
+ } else if (otherCursorConn !== undefined) {
+ // We set 'cursorConn' back to undefined and break out of the loop so
+ // that we don't attempt to automatically route the "killCursors"
+ // command when there are cursors from different servers.
+ cursorConn = undefined;
+ break;
+ }
+ }
+
+ if (cursorConn !== undefined) {
+ return func.apply(cursorConn, makeFuncArgs(commandObj));
+ }
+ }
+ }
+ }
+
+ let shouldForceReadPreference = kCommandsSupportingReadPreference.has(commandName);
+ if (OverrideHelpers.isAggregationWithOutStage(commandName, commandObjUnwrapped)) {
+ // An aggregation with a $out stage must be sent to the primary.
+ shouldForceReadPreference = false;
+ } else if ((commandName === "mapReduce" || commandName === "mapreduce") &&
+ !OverrideHelpers.isMapReduceWithInlineOutput(commandName, commandObjUnwrapped)) {
+ // A map-reduce operation with non-inline output must be sent to the primary.
+ shouldForceReadPreference = false;
+ }
+
+ if (shouldForceReadPreference) {
+ if (commandObj === commandObjUnwrapped) {
+ // We wrap the command object using a "query" field rather than a "$query" field to
+ // match the implementation of DB.prototype._attachReadPreferenceToCommand().
+ commandObj = {query: commandObj};
+ } else {
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ }
+
+ if (commandObj.hasOwnProperty("$readPreference") &&
+ !bsonBinaryEqual({_: commandObj.$readPreference}, {_: kReadPreferenceSecondary})) {
+ throw new Error("Cowardly refusing to override read preference of command: " +
+ tojson(commandObj));
+ }
+
+ commandObj.$readPreference = kReadPreferenceSecondary;
+ }
+
+ const serverResponse = func.apply(conn, makeFuncArgs(commandObj));
+
+ if (conn.isReplicaSetConnection() && kCursorGeneratingCommands.has(commandName) &&
+ serverResponse.ok === 1 && serverResponse.hasOwnProperty("cursor")) {
+ // We associate the cursor id returned by the server with the connection that was used
+ // to establish it so that we can attempt to automatically route subsequent "getMore"
+ // and "killCursors" commands.
+ CursorTracker.setConnectionUsedForCursor(serverResponse.cursor.id,
+ serverResponse._mongo);
+ }
+
+ return serverResponse;
+ }
+
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/set_read_preference_secondary.js");
+
+ OverrideHelpers.overrideRunCommand(runCommandWithReadPreferenceSecondary);
+})();
diff --git a/jstests/multiVersion/initial_sync_last_stable_from_latest.js b/jstests/multiVersion/initial_sync_last_stable_from_latest.js
new file mode 100644
index 00000000000..3f036c17fe9
--- /dev/null
+++ b/jstests/multiVersion/initial_sync_last_stable_from_latest.js
@@ -0,0 +1,15 @@
+/**
+ * Multiversion initial sync test. Tests that initial sync succeeds when a 'last-stable' version
+ * secondary syncs from a 'latest' version replica set.
+ */
+
+'use strict';
+
+load("./jstests/multiVersion/libs/initial_sync.js");
+
+var testName = "multiversion_initial_sync_last_stable_from_latest";
+let replSetVersion = "latest";
+let newSecondaryVersion = "last-stable";
+let fcv = "3.2";
+
+multversionInitialSyncTest(testName, replSetVersion, newSecondaryVersion, {}, fcv);
diff --git a/jstests/multiVersion/initial_sync_latest_from_last_stable.js b/jstests/multiVersion/initial_sync_latest_from_last_stable.js
new file mode 100644
index 00000000000..fd92b64e87a
--- /dev/null
+++ b/jstests/multiVersion/initial_sync_latest_from_last_stable.js
@@ -0,0 +1,14 @@
+/**
+ * Multiversion initial sync test. Tests that initial sync succeeds when a 'latest' version
+ * secondary syncs from a 'last-stable' version replica set.
+ */
+
+'use strict';
+
+load("./jstests/multiVersion/libs/initial_sync.js");
+
+var testName = "multiversion_initial_sync_latest_from_last_stable";
+let replSetVersion = "last-stable";
+let newSecondaryVersion = "latest";
+
+multversionInitialSyncTest(testName, replSetVersion, newSecondaryVersion, {});
diff --git a/jstests/multiVersion/initialsync.js b/jstests/multiVersion/initialsync.js
deleted file mode 100644
index e9a424fd05c..00000000000
--- a/jstests/multiVersion/initialsync.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Multiversion initial sync test.
-load("./jstests/multiVersion/libs/multi_rs.js");
-load("./jstests/replsets/rslib.js");
-
-var oldVersion = "last-stable";
-var newVersion = "latest";
-
-var name = "multiversioninitsync";
-
-var multitest = function(replSetVersion, newNodeVersion) {
- var nodes = {n1: {binVersion: replSetVersion}, n2: {binVersion: replSetVersion}};
-
- print("Start up a two-node " + replSetVersion + " replica set.");
- var rst = new ReplSetTest({name: name, nodes: nodes});
- rst.startSet();
- var config = rst.getReplSetConfig();
- // Set protocol version to 0 for 3.2 replset.
- if (replSetVersion == newVersion) {
- config.protocolVersion = 0;
- }
- rst.initiate(config);
-
- // Wait for a primary node.
- var primary = rst.getPrimary();
-
- // Insert some data and wait for replication.
- for (var i = 0; i < 25; i++) {
- primary.getDB("foo").foo.insert({_id: i});
- }
- rst.awaitReplication();
-
- print("Bring up a new node with version " + newNodeVersion + " and add to set.");
- rst.add({binVersion: newNodeVersion});
- rst.reInitiate();
-
- // Wait for a primary node.
- var primary = rst.getPrimary();
- var secondaries = rst.getSecondaries();
-
- print("Wait for new node to be synced.");
- rst.awaitReplication();
-
- rst.stopSet();
-};
-
-// *****************************************
-// Test A:
-// "Latest" version secondary is synced from
-// an old ReplSet.
-// *****************************************
-multitest(oldVersion, newVersion);
-
-// *****************************************
-// Test B:
-// Old Secondary is synced from a "latest"
-// version ReplSet.
-// *****************************************
-multitest(newVersion, oldVersion);
diff --git a/jstests/multiVersion/libs/initial_sync.js b/jstests/multiVersion/libs/initial_sync.js
new file mode 100644
index 00000000000..866f825c8b9
--- /dev/null
+++ b/jstests/multiVersion/libs/initial_sync.js
@@ -0,0 +1,50 @@
+
+'use strict';
+
+load("./jstests/multiVersion/libs/multi_rs.js");
+load("./jstests/replsets/rslib.js");
+
+/**
+ * Test that starts up a replica set with 2 nodes of version 'replSetVersion', inserts some data,
+ * then adds a new node to the replica set with version 'newNodeVersion' and waits for initial sync
+ * to complete. If the 'fcv' argument is given, sets the feature compatibility version of the
+ * replica set to 'fcv' before adding the third node.
+ */
+var multversionInitialSyncTest = function(
+ name, replSetVersion, newNodeVersion, configSettings, fcv) {
+
+ var nodes = {n1: {binVersion: replSetVersion}, n2: {binVersion: replSetVersion}};
+
+ jsTestLog("Starting up a two-node '" + replSetVersion + "' version replica set.");
+ var rst = new ReplSetTest({name: name, nodes: nodes});
+ rst.startSet();
+
+ var conf = rst.getReplSetConfig();
+ conf.settings = configSettings;
+ rst.initiate(conf);
+
+ // Wait for a primary node.
+ var primary = rst.getPrimary();
+
+ // Set 'featureCompatibilityVersion' if given.
+ if (fcv) {
+ jsTestLog("Setting FCV to '" + fcv + "' on the primary.");
+ assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: fcv}));
+ }
+
+ // Insert some data and wait for replication.
+ for (var i = 0; i < 25; i++) {
+ primary.getDB("foo").foo.insert({_id: i});
+ }
+ rst.awaitReplication();
+
+ jsTestLog("Bringing up a new node with version '" + newNodeVersion + "' and adding to set.");
+ rst.add({binVersion: newNodeVersion});
+ rst.reInitiate();
+
+ jsTestLog("Waiting for new node to be synced.");
+ rst.awaitReplication();
+ rst.awaitSecondaryNodes();
+
+ rst.stopSet();
+}; \ No newline at end of file
diff --git a/jstests/multiVersion/libs/multi_rs.js b/jstests/multiVersion/libs/multi_rs.js
index da976c7f4dc..87d5995ef48 100644
--- a/jstests/multiVersion/libs/multi_rs.js
+++ b/jstests/multiVersion/libs/multi_rs.js
@@ -25,6 +25,7 @@ ReplSetTest.prototype.upgradeSet = function(options, user, pwd) {
var node = nodesToUpgrade[i];
if (node == primary) {
node = this.stepdown(node);
+ this.waitForState(node, ReplSetTest.State.SECONDARY);
primary = this.getPrimary();
}
diff --git a/jstests/multiVersion/set_feature_compatibility_version.js b/jstests/multiVersion/set_feature_compatibility_version.js
index d5c73f26efb..73a5785aec9 100644
--- a/jstests/multiVersion/set_feature_compatibility_version.js
+++ b/jstests/multiVersion/set_feature_compatibility_version.js
@@ -54,6 +54,23 @@
// featureCompatibilityVersion cannot be set via setParameter.
assert.commandFailed(adminDB.runCommand({setParameter: 1, featureCompatibilityVersion: "3.2"}));
+ // setFeatureCompatibilityVersion fails to downgrade to FCV=3.2 if the write fails.
+ assert.commandWorked(adminDB.runCommand({
+ configureFailPoint: "failCollectionUpdates",
+ data: {collectionNS: "admin.system.version"},
+ mode: "alwaysOn"
+ }));
+ assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: "3.2"}));
+ res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1});
+ assert.commandWorked(res);
+ assert.eq(res.featureCompatibilityVersion, "3.4");
+ assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.4");
+ assert.commandWorked(adminDB.runCommand({
+ configureFailPoint: "failCollectionUpdates",
+ data: {collectionNS: "admin.system.version"},
+ mode: "off"
+ }));
+
// featureCompatibilityVersion can be set to 3.2.
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.2"}));
res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1});
@@ -70,6 +87,23 @@
"Expected index with name 'incompatible_with_version_32' to have been removed: " +
tojson(allIndexes));
+ // setFeatureCompatibilityVersion fails to upgrade to FCV=3.4 if the write fails.
+ assert.commandWorked(adminDB.runCommand({
+ configureFailPoint: "failCollectionUpdates",
+ data: {collectionNS: "admin.system.version"},
+ mode: "alwaysOn"
+ }));
+ assert.commandFailed(adminDB.runCommand({setFeatureCompatibilityVersion: "3.4"}));
+ res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1});
+ assert.commandWorked(res);
+ assert.eq(res.featureCompatibilityVersion, "3.2");
+ assert.eq(adminDB.system.version.findOne({_id: "featureCompatibilityVersion"}).version, "3.2");
+ assert.commandWorked(adminDB.runCommand({
+ configureFailPoint: "failCollectionUpdates",
+ data: {collectionNS: "admin.system.version"},
+ mode: "off"
+ }));
+
// featureCompatibilityVersion can be set to 3.4.
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: "3.4"}));
res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1});
diff --git a/jstests/noPassthrough/ftdc_setdirectory.js b/jstests/noPassthrough/ftdc_setdirectory.js
new file mode 100644
index 00000000000..c1173055e2a
--- /dev/null
+++ b/jstests/noPassthrough/ftdc_setdirectory.js
@@ -0,0 +1,118 @@
+/**
+ * Test that verifies FTDC works in mongos.
+ */
+load('jstests/libs/ftdc.js');
+
+(function() {
+ 'use strict';
+ let testPath1 = MongoRunner.toRealPath('ftdc_setdir1');
+ let testPath2 = MongoRunner.toRealPath('ftdc_setdir2');
+ let testPath3 = MongoRunner.toRealPath('ftdc_setdir3');
+ let testLog3 = testPath3 + "mongos_ftdc.log";
+
+ // Make the log file directory for mongos.
+ mkdir(testPath3);
+
+ // Startup 3 mongos:
+ // 1. Normal MongoS with no log file to verify FTDC can be startup at runtime with a path.
+ // 2. MongoS with explict diagnosticDataCollectionDirectoryPath setParameter at startup.
+ // 3. MongoS with log file to verify automatic FTDC path computation works.
+ let st = new ShardingTest({
+ shards: 1,
+ mongos: {
+ s0: {verbose: 0},
+ s1: {setParameter: {diagnosticDataCollectionDirectoryPath: testPath2}},
+ s2: {logpath: testLog3}
+ }
+ });
+
+ let admin1 = st.s0.getDB('admin');
+ let admin2 = st.s1.getDB('admin');
+ let admin3 = st.s2.getDB('admin');
+
+ function setParam(admin, obj) {
+ var ret = admin.runCommand(Object.extend({setParameter: 1}, obj));
+ return ret;
+ }
+
+ function getParam(admin, field) {
+ var q = {getParameter: 1};
+ q[field] = 1;
+
+ var ret = admin.runCommand(q);
+ assert.commandWorked(ret);
+ return ret[field];
+ }
+
+ // Verify FTDC can be started at runtime.
+ function verifyFTDCDisabledOnStartup() {
+ jsTestLog("Running verifyFTDCDisabledOnStartup");
+ verifyCommonFTDCParameters(admin1, false);
+
+ // 1. Try to enable and fail
+ assert.commandFailed(setParam(admin1, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 2. Set path and succeed
+ assert.commandWorked(
+ setParam(admin1, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 3. Set path again and fail
+ assert.commandFailed(
+ setParam(admin1, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 4. Enable successfully
+ assert.commandWorked(setParam(admin1, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 5. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin1);
+ }
+
+ // Verify FTDC is already running if there was a path set at startup.
+ function verifyFTDCStartsWithPath() {
+ jsTestLog("Running verifyFTDCStartsWithPath");
+ verifyCommonFTDCParameters(admin2, true);
+
+ // 1. Set path fail
+ assert.commandFailed(
+ setParam(admin2, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 2. Enable successfully
+ assert.commandWorked(setParam(admin2, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 3. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin2);
+ }
+
+ function normpath(path) {
+ return path.replace(/\\/g, "/");
+ }
+
+ // Verify FTDC is already running if there was a path set at startup.
+ function verifyFTDCStartsWithLogFile() {
+ jsTestLog("Running verifyFTDCStartsWithLogFile");
+ verifyCommonFTDCParameters(admin3, true);
+
+ // 1. Verify that path is computed correctly.
+ let computedPath = getParam(admin3, "diagnosticDataCollectionDirectoryPath");
+ assert.eq(normpath(computedPath), normpath(testPath3 + "mongos_ftdc.diagnostic.data"));
+
+ // 2. Set path fail
+ assert.commandFailed(
+ setParam(admin3, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 3. Enable successfully
+ assert.commandWorked(setParam(admin3, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 4. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin3);
+ }
+
+ verifyFTDCDisabledOnStartup();
+ verifyFTDCStartsWithPath();
+ verifyFTDCStartsWithLogFile();
+
+ st.stop();
+})();
diff --git a/jstests/noPassthrough/geo_near_fcv32.js b/jstests/noPassthrough/geo_near_fcv32.js
new file mode 100644
index 00000000000..10b331a411d
--- /dev/null
+++ b/jstests/noPassthrough/geo_near_fcv32.js
@@ -0,0 +1,85 @@
+/**
+ * Confirms that $geoNear aggregation and geoNear command succeed when FCV is 3.2.
+ */
+(function() {
+ "use strict";
+
+ const conn = MongoRunner.runMongod({});
+ assert.neq(null, conn, "mongod was unable to start up");
+
+ const testDB = conn.getDB("geo_near_fcv32");
+ testDB.test32.drop();
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.2"}));
+
+ // Create 2dsphere index.
+ assert.commandWorked(testDB.test32.createIndex({loc: "2dsphere"}));
+
+ // Assert that $geoNear aggregate command does not fail due to collation errors when FCV is 3.2.
+ assert.eq(0,
+ testDB.test32
+ .aggregate([{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1.23, 1.23]},
+ distanceField: "distance",
+ spherical: true
+ }
+ }])
+ .itcount());
+
+ // Assert that specifying the simple collation in $geoNear aggregate fails with FCV 3.2.
+ assert.throws(function() {
+ testDB.test32.aggregate([{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1.23, 1.23]},
+ distanceField: "distance",
+ spherical: true,
+ collation: {locale: "simple"},
+ }
+ }]);
+ });
+
+ // Assert that specifying the simple collation in geoNear command fails with FCV 3.2.
+ assert.commandFailed(testDB.runCommand({
+ geoNear: "test32",
+ near: {type: "Point", coordinates: [1.23, 1.23]},
+ spherical: true,
+ collation: {locale: "simple"}
+ }));
+
+ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "3.4"}));
+
+ // Create collection with case sensitive collation.
+ assert.commandWorked(
+ testDB.createCollection("test34", {collation: {locale: "en_US", strength: 2}}));
+ assert.commandWorked(testDB.test34.createIndex({loc: "2dsphere"}));
+ assert.writeOK(testDB.test34.insert({loc: [1.23, 1.23], str: "A"}));
+
+ // Assert that after upgrading FCV to 3.4 $geoNear aggregate inherits the collection's default
+ // collation.
+ assert.eq(1,
+ testDB.test34
+ .aggregate([{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1.23, 1.23]},
+ distanceField: "distance",
+ spherical: true,
+ query: {str: "a"},
+ }
+ }])
+ .itcount());
+
+ // Assert that the $geoNear aggregate accepts a specific collation and overrides the default
+ // collation in FCV 3.4.
+ assert.eq(1,
+ testDB.test34
+ .aggregate([{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1.23, 1.23]},
+ distanceField: "distance",
+ spherical: true,
+ query: {str: "à"},
+ }
+ }],
+ {collation: {locale: "en_US", strength: 1}})
+ .itcount());
+})();
diff --git a/jstests/noPassthrough/libs/backup_restore.js b/jstests/noPassthrough/libs/backup_restore.js
index b4e02415081..e66f0f8af97 100644
--- a/jstests/noPassthrough/libs/backup_restore.js
+++ b/jstests/noPassthrough/libs/backup_restore.js
@@ -352,15 +352,31 @@ var BackupRestoreTest = function(options) {
stopMongoProgramByPid(fsmPid);
// Wait up to 5 minutes until the new hidden node is in state SECONDARY.
+ jsTestLog('CRUD and FSM clients stopped. Waiting for hidden node ' + hiddenHost +
+ ' to become SECONDARY');
rst.waitForState(hiddenNode, ReplSetTest.State.SECONDARY);
// Wait for secondaries to finish catching up before shutting down.
+ jsTestLog(
+ 'Hidden node ' + hiddenHost +
+ ' is now SECONDARY. Waiting for CRUD and FSM operations to be applied on all nodes.');
+ rst.awaitReplication();
+
+ jsTestLog('CRUD and FSM operations successfully applied on all nodes. ' +
+ 'Waiting for all nodes to agree on the primary.');
rst.awaitNodesAgreeOnPrimary();
+
+ jsTestLog('All nodes agree on the primary. Getting current primary.');
primary = rst.getPrimary();
- assert.writeOK(primary.getDB("test").foo.insert(
- {}, {writeConcern: {w: rst.nodes.length, wtimeout: 10 * 60 * 1000}}));
+
+ jsTestLog('Inserting single document into primary ' + primary.host +
+ ' with writeConcern w:' + rst.nodes.length);
+ var writeResult = assert.writeOK(primary.getDB("test").foo.insert(
+ {}, {writeConcern: {w: rst.nodes.length, wtimeout: ReplSetTest.kDefaultTimeoutMS}}));
// Stop set.
+ jsTestLog('Insert operation successful: ' + tojson(writeResult) +
+ '. Stopping replica set.');
rst.stopSet();
// Cleanup the files from the test
diff --git a/jstests/noPassthrough/non_atomic_apply_ops_logging.js b/jstests/noPassthrough/non_atomic_apply_ops_logging.js
new file mode 100644
index 00000000000..f93e9d38189
--- /dev/null
+++ b/jstests/noPassthrough/non_atomic_apply_ops_logging.js
@@ -0,0 +1,62 @@
+// SERVER-28594 Ensure non-atomic ops are individually logged in applyOps
+// and atomic ops are collectively logged in applyOps.
+(function() {
+ "use strict";
+
+ let rst = new ReplSetTest({nodes: 1});
+ rst.startSet();
+ rst.initiate();
+
+ let primary = rst.getPrimary();
+ let testDB = primary.getDB("test");
+ let oplogColl = primary.getDB("local").oplog.rs;
+ let testCollName = "testColl";
+ let rerenamedCollName = "rerenamedColl";
+
+ testDB.runCommand({drop: testCollName});
+ testDB.runCommand({drop: rerenamedCollName});
+ assert.commandWorked(testDB.runCommand({create: testCollName}));
+ let testColl = testDB[testCollName];
+
+ // Ensure atomic apply ops logging only produces one oplog entry
+ // per call to apply ops and does not log individual operations
+ // separately.
+ assert.commandWorked(testDB.runCommand({
+ applyOps: [
+ {op: "i", ns: testColl.getFullName(), o: {_id: 1, a: "foo"}},
+ {op: "i", ns: testColl.getFullName(), o: {_id: 2, a: "bar"}}
+ ]
+ }));
+ assert.eq(oplogColl.find({"o.applyOps": {"$exists": true}}).count(), 1);
+ assert.eq(oplogColl.find({"op": "i"}).count(), 0);
+ // Ensure non-atomic apply ops logging produces an oplog entry for
+ // each operation in the apply ops call and no record of applyOps
+ // appears for these operations.
+ assert.commandWorked(testDB.runCommand({
+ applyOps: [
+ {
+ op: "c",
+ ns: "test.$cmd",
+ o: {
+ renameCollection: "test.testColl",
+ to: "test.renamedColl",
+ stayTemp: false,
+ dropTarget: false
+ }
+ },
+ {
+ op: "c",
+ ns: "test.$cmd",
+ o: {
+ renameCollection: "test.renamedColl",
+ to: "test." + rerenamedCollName,
+ stayTemp: false,
+ dropTarget: false
+ }
+ }
+ ]
+ }));
+ assert.eq(oplogColl.find({"o.renameCollection": {"$exists": true}}).count(), 2);
+ assert.eq(oplogColl.find({"o.applyOps": {"$exists": true}}).count(), 1);
+ rst.stopSet();
+})();
diff --git a/jstests/noPassthrough/partial_unique_indexes.js b/jstests/noPassthrough/partial_unique_indexes.js
new file mode 100644
index 00000000000..7c376872ade
--- /dev/null
+++ b/jstests/noPassthrough/partial_unique_indexes.js
@@ -0,0 +1,47 @@
+/**
+ * SERVER-32001: Test that indexing paths for non-unique, partial, unique, partial&unique
+ * crud operations correctly handle WriteConflictExceptions.
+ */
+(function() {
+ "strict";
+
+ let conn = MongoRunner.runMongod();
+ let testDB = conn.getDB("test");
+
+ let t = testDB.jstests_parallel_allops;
+ t.drop();
+
+ t.createIndex({x: 1, _id: 1}, {partialFilterExpression: {_id: {$lt: 500}}, unique: true});
+ t.createIndex({y: -1, _id: 1}, {unique: true});
+ t.createIndex({x: -1}, {partialFilterExpression: {_id: {$gte: 500}}, unique: false});
+ t.createIndex({y: 1}, {unique: false});
+
+ let _id = {"#RAND_INT": [0, 1000]};
+ let ops = [
+ {op: "remove", ns: t.getFullName(), query: {_id}},
+ {op: "update", ns: t.getFullName(), query: {_id}, update: {$inc: {x: 1}}, upsert: true},
+ {op: "update", ns: t.getFullName(), query: {_id}, update: {$inc: {y: 1}}, upsert: true},
+ ];
+
+ let seconds = 5;
+ let parallel = 5;
+ let host = testDB.getMongo().host;
+
+ let benchArgs = {ops, seconds, parallel, host};
+
+ assert.commandWorked(testDB.adminCommand({
+ configureFailPoint: 'WTWriteConflictExceptionForReads',
+ mode: {activationProbability: 0.01}
+ }));
+ assert.commandWorked(testDB.adminCommand(
+ {configureFailPoint: 'WTWriteConflictException', mode: {activationProbability: 0.01}}));
+ res = benchRun(benchArgs);
+ printjson({res});
+
+ assert.commandWorked(
+ testDB.adminCommand({configureFailPoint: 'WTWriteConflictException', mode: "off"}));
+ assert.commandWorked(
+ testDB.adminCommand({configureFailPoint: 'WTWriteConflictExceptionForReads', mode: "off"}));
+ res = t.validate();
+ assert(res.valid, tojson(res));
+})();
diff --git a/jstests/noPassthrough/skip_sharding_configuration_checks.js b/jstests/noPassthrough/skip_sharding_configuration_checks.js
new file mode 100644
index 00000000000..ff3e95440eb
--- /dev/null
+++ b/jstests/noPassthrough/skip_sharding_configuration_checks.js
@@ -0,0 +1,53 @@
+/**
+ * Starts standalone RS with skipShardingConfigurationChecks.
+ * @tags: [requires_persistence]
+ */
+(function() {
+ 'use strict';
+
+ function expectState(rst, state) {
+ assert.soon(function() {
+ var status = rst.status();
+ if (status.myState != state) {
+ print("Waiting for state " + state + " in replSetGetStatus output: " +
+ tojson(status));
+ }
+ return status.myState == state;
+ });
+ }
+
+ let configSvr = MongoRunner.runMongod(
+ {configsvr: "", setParameter: 'skipShardingConfigurationChecks=true'});
+ assert.eq(configSvr, null);
+
+ let shardSvr =
+ MongoRunner.runMongod({shardsvr: "", setParameter: 'skipShardingConfigurationChecks=true'});
+ assert.eq(shardSvr, null);
+
+ var st = new ShardingTest({name: "skipConfig", shards: {rs0: {nodes: 1}}});
+ var configRS = st.configRS;
+ var shardRS = st.rs0;
+
+ st.stopAllMongos();
+ shardRS.stopSet(15, true);
+ configRS.stopSet(undefined, true);
+
+ jsTestLog("Restarting configRS as a standalone ReplicaSet");
+
+ for (let i = 0; i < configRS.nodes.length; i++) {
+ delete configRS.nodes[i].fullOptions.configsvr;
+ configRS.nodes[i].fullOptions.setParameter = 'skipShardingConfigurationChecks=true';
+ }
+ configRS.startSet({}, true);
+ expectState(configRS, ReplSetTest.State.PRIMARY);
+ configRS.stopSet();
+
+ jsTestLog("Restarting shardRS as a standalone ReplicaSet");
+ for (let i = 0; i < shardRS.nodes.length; i++) {
+ delete shardRS.nodes[i].fullOptions.shardsvr;
+ shardRS.nodes[i].fullOptions.setParameter = 'skipShardingConfigurationChecks=true';
+ }
+ shardRS.startSet({}, true);
+ expectState(shardRS, ReplSetTest.State.PRIMARY);
+ shardRS.stopSet();
+})();
diff --git a/jstests/noPassthroughWithMongod/apply_ops_index_collation.js b/jstests/noPassthroughWithMongod/apply_ops_index_collation.js
new file mode 100644
index 00000000000..6248bc63b0f
--- /dev/null
+++ b/jstests/noPassthroughWithMongod/apply_ops_index_collation.js
@@ -0,0 +1,80 @@
+// Cannot implicitly shard accessed collections because of collection existing when none
+// expected.
+// @tags: [assumes_no_implicit_collection_creation_after_drop]
+
+// Tests creation of indexes using applyOps for collections with a non-simple default collation.
+// Indexes created through applyOps should be built exactly according to their index spec, without
+// inheriting the collection default collation, since this is how the oplog entries are replicated.
+// TODO SERVER-31435: Move this test into core once applyOps with createIndexes replicates
+// correctly.
+(function() {
+ "use strict";
+
+ load("jstests/libs/get_index_helpers.js");
+
+ const coll = db.apply_ops_index_collation;
+ coll.drop();
+ assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}}));
+
+ // An index created using an insert-style oplog entry with a non-simple collation does not
+ // inherit the collection default collation.
+ let res = assert.commandWorked(db.adminCommand({
+ applyOps: [{
+ op: "i",
+ ns: db.system.indexes.getFullName(),
+ o: {
+ v: 2,
+ key: {c: 1},
+ name: "c_1_en",
+ ns: coll.getFullName(),
+ collation: {
+ locale: "en_US",
+ caseLevel: false,
+ caseFirst: "off",
+ strength: 3,
+ numericOrdering: false,
+ alternate: "non-ignorable",
+ maxVariable: "punct",
+ normalization: false,
+ backwards: false,
+ version: "57.1"
+ }
+ }
+ }]
+ }));
+ let allIndexes = coll.getIndexes();
+ let spec = GetIndexHelpers.findByName(allIndexes, "c_1_en");
+ assert.neq(null, spec, "Index 'c_1_en' not found: " + tojson(allIndexes));
+ assert.eq(2, spec.v, tojson(spec));
+ assert.eq("en_US", spec.collation.locale, tojson(spec));
+
+ // An index created using an insert-style oplog entry with a simple collation does not inherit
+ // the collection default collation.
+ res = assert.commandWorked(db.adminCommand({
+ applyOps: [{
+ op: "i",
+ ns: db.system.indexes.getFullName(),
+ o: {v: 2, key: {c: 1}, name: "c_1", ns: coll.getFullName()}
+ }]
+ }));
+ allIndexes = coll.getIndexes();
+ spec = GetIndexHelpers.findByName(allIndexes, "c_1");
+ assert.neq(null, spec, "Index 'c_1' not found: " + tojson(allIndexes));
+ assert.eq(2, spec.v, tojson(spec));
+ assert(!spec.hasOwnProperty("collation"), tojson(spec));
+
+ // A v=1 index created using an insert-style oplog entry does not inherit the collection default
+ // collation.
+ res = assert.commandWorked(db.adminCommand({
+ applyOps: [{
+ op: "i",
+ ns: db.system.indexes.getFullName(),
+ o: {v: 1, key: {d: 1}, name: "d_1", ns: coll.getFullName()}
+ }]
+ }));
+ allIndexes = coll.getIndexes();
+ spec = GetIndexHelpers.findByName(allIndexes, "d_1");
+ assert.neq(null, spec, "Index 'd_1' not found: " + tojson(allIndexes));
+ assert.eq(1, spec.v, tojson(spec));
+ assert(!spec.hasOwnProperty("collation"), tojson(spec));
+})();
diff --git a/jstests/noPassthroughWithMongod/ftdc_params.js b/jstests/noPassthroughWithMongod/ftdc_params.js
index ced6c1b5675..08714040fcb 100644
--- a/jstests/noPassthroughWithMongod/ftdc_params.js
+++ b/jstests/noPassthroughWithMongod/ftdc_params.js
@@ -1,58 +1,10 @@
// FTDC test cases
//
+load('jstests/libs/ftdc.js');
+
(function() {
'use strict';
var admin = db.getSiblingDB("admin");
- // Check the defaults are correct
- //
- function getparam(field) {
- var q = {getParameter: 1};
- q[field] = 1;
-
- var ret = admin.runCommand(q);
- return ret[field];
- }
-
- // Verify the defaults are as we documented them
- assert.eq(getparam("diagnosticDataCollectionEnabled"), true);
- assert.eq(getparam("diagnosticDataCollectionPeriodMillis"), 1000);
- assert.eq(getparam("diagnosticDataCollectionDirectorySizeMB"), 200);
- assert.eq(getparam("diagnosticDataCollectionFileSizeMB"), 10);
- assert.eq(getparam("diagnosticDataCollectionSamplesPerChunk"), 300);
- assert.eq(getparam("diagnosticDataCollectionSamplesPerInterimUpdate"), 10);
-
- function setparam(obj) {
- var ret = admin.runCommand(Object.extend({setParameter: 1}, obj));
- return ret;
- }
-
- assert.commandWorked(setparam({"diagnosticDataCollectionEnabled": 1}));
- assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 100}));
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 1}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 2}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 2}));
-
- // Negative tests - set values below minimums
- assert.commandFailed(setparam({"diagnosticDataCollectionPeriodMillis": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerChunk": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 1}));
-
- // Negative test - set file size bigger then directory size
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
- assert.commandFailed(setparam({"diagnosticDataCollectionFileSizeMB": 100}));
-
- // Negative test - set directory size less then file size
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 100}));
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 50}));
- assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
-
- // Reset
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 10}));
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 200}));
- assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 1000}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 300}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 10}));
+ verifyCommonFTDCParameters(admin, true);
})();
diff --git a/jstests/noPassthroughWithMongod/host_connection_string_validation.js b/jstests/noPassthroughWithMongod/host_connection_string_validation.js
index a252ef39230..f7bac51db88 100644
--- a/jstests/noPassthroughWithMongod/host_connection_string_validation.js
+++ b/jstests/noPassthroughWithMongod/host_connection_string_validation.js
@@ -89,8 +89,8 @@
print("Testing " + (isGood ? "good" : "bad") + " connection string " + i + "...");
print(" * testing " + connectionString);
testHost(connectionString, isGood);
- print(" * testing mongodb://" + connectionString);
- testHost("mongodb://" + connectionString, isGood);
+ print(" * testing mongodb://" + encodeURIComponent(connectionString));
+ testHost("mongodb://" + encodeURIComponent(connectionString), isGood);
}
var i;
diff --git a/jstests/noPassthroughWithMongod/initial_sync_oplog_rollover.js b/jstests/noPassthroughWithMongod/initial_sync_oplog_rollover.js
index 3a47e14447c..d186a8f59ec 100644
--- a/jstests/noPassthroughWithMongod/initial_sync_oplog_rollover.js
+++ b/jstests/noPassthroughWithMongod/initial_sync_oplog_rollover.js
@@ -34,7 +34,7 @@
assert.writeOK(coll.insert({a: 1}));
function getFirstOplogEntry(conn) {
- return conn.getDB('local').oplog.rs.find().sort({ts: 1}).limit(1)[0];
+ return conn.getDB('local').oplog.rs.find().sort({$natural: 1}).limit(1)[0];
}
var firstOplogEntry = getFirstOplogEntry(primary);
diff --git a/jstests/noPassthroughWithMongod/replset_host_connection_validation.js b/jstests/noPassthroughWithMongod/replset_host_connection_validation.js
index 7e48de363e2..e65387dd705 100644
--- a/jstests/noPassthroughWithMongod/replset_host_connection_validation.js
+++ b/jstests/noPassthroughWithMongod/replset_host_connection_validation.js
@@ -1,5 +1,6 @@
// Test --host with a replica set.
(function() {
+ 'use strict';
const replSetName = 'hostTestReplSetName';
@@ -26,35 +27,56 @@
// Pass the inner test's exit code back as the outer test's exit code
if (exitCode != 0) {
- doassert("inner test failed with exit code " + exitcode);
+ doassert("inner test failed with exit code " + exitCode);
}
return;
}
- const testHost = function(host) {
- const exitCode = runMongoProgram('mongo', '--eval', ';', '--host', host);
- if (exitCode !== 0) {
- doassert("failed to connect with `--host " + host +
- "`, but expected success. Exit code: " + exitCode);
+ function testHost(host, uri, ok) {
+ const exitCode = runMongoProgram('mongo', '--eval', ';', '--host', host, uri);
+ if (ok) {
+ assert.eq(exitCode, 0, "failed to connect with `--host " + host + "`");
+ } else {
+ assert.neq(exitCode, 0, "unexpectedly succeeded to connect with `--host " + host + "`");
}
- };
-
- const connStrings = [
- `localhost:${port}`,
- `${replSetName}/localhost:${port}`,
- `mongodb://localhost:${port}/admin?replicaSet=${replSetName}`,
- `mongodb://localhost:${port}`,
- ];
-
- function runConnectionStringTestFor(i, connectionString) {
- print("Testing connection string " + i + "...");
- print(" * testing " + connectionString);
- testHost(connectionString);
}
- for (let i = 0; i < connStrings.length; ++i) {
- runConnectionStringTestFor(i, connStrings[i]);
+ function runConnectionStringTestFor(connectionString, uri, ok) {
+ print("* Testing: --host " + connectionString + " " + uri);
+ if (!ok) {
+ print(" This should fail");
+ }
+ testHost(connectionString, uri, ok);
+ }
+
+ function expSuccess(str) {
+ runConnectionStringTestFor(str, '', true);
+ if (!str.startsWith('mongodb://')) {
+ runConnectionStringTestFor(str, 'dbname', true);
+ }
}
+ function expFailure(str) {
+ runConnectionStringTestFor(str, '', false);
+ }
+
+ expSuccess(`localhost:${port}`);
+ expSuccess(`${replSetName}/localhost:${port}`);
+ expSuccess(`${replSetName}/localhost:${port},[::1]:${port}`);
+ expSuccess(`${replSetName}/localhost:${port},`);
+ expSuccess(`${replSetName}/localhost:${port},,`);
+ expSuccess(`mongodb://localhost:${port}/admin?replicaSet=${replSetName}`);
+ expSuccess(`mongodb://localhost:${port}`);
+
+ expFailure(',');
+ expFailure(',,');
+ expFailure(`${replSetName}/`);
+ expFailure(`${replSetName}/,`);
+ expFailure(`${replSetName}/,,`);
+ expFailure(`${replSetName}//not/a/socket`);
+ expFailure(`mongodb://localhost:${port}/admin?replicaSet=`);
+ expFailure('mongodb://localhost:');
+ expFailure(`mongodb://:${port}`);
+
jsTest.log("SUCCESSFUL test completion");
})();
diff --git a/jstests/replsets/apply_batches_totalMillis.js b/jstests/replsets/apply_batches_totalMillis.js
new file mode 100644
index 00000000000..9e093211cb6
--- /dev/null
+++ b/jstests/replsets/apply_batches_totalMillis.js
@@ -0,0 +1,63 @@
+/**
+ * serverStatus.metrics.repl.apply.batches.totalMillis is a cumulative measure of how much time a
+ * node spends applying batches. This test checks that it includes the time spent waiting for
+ * batches to finish, by comparing the time recorded after replicating a small and a large load.
+ */
+
+(function() {
+ "use strict";
+
+ // Gets the value of metrics.repl.apply.batches.totalMillis.
+ function getTotalMillis(node) {
+ return assert.commandWorked(node.adminCommand({serverStatus: 1}))
+ .metrics.repl.apply.batches.totalMillis;
+ }
+
+ // Do a bulk insert of documents as: {{key: 0}, {key: 1}, {key: 2}, ... , {key: num-1}}
+ function performBulkInsert(coll, key, num) {
+ let bulk = coll.initializeUnorderedBulkOp();
+ for (let i = 0; i < num; i++) {
+ let doc = {};
+ doc[key] = i;
+ bulk.insert(doc);
+ }
+ assert.writeOK(bulk.execute());
+ rst.awaitReplication();
+ }
+
+ let name = "apply_batches_totalMillis";
+ let rst = new ReplSetTest({name: name, nodes: 2});
+ rst.startSet();
+ rst.initiate();
+
+ let primary = rst.getPrimary();
+ let secondary = rst.getSecondary();
+ let coll = primary.getDB(name)["foo"];
+
+ // Perform an initial write on the system and ensure steady state.
+ assert.writeOK(coll.insert({init: 0}));
+ rst.awaitReplication();
+ let baseTime = getTotalMillis(secondary);
+
+ // Introduce a small load and wait for it to be replicated.
+ performBulkInsert(coll, "small", 1000);
+
+ // Record the time spent applying the small load.
+ let timeAfterSmall = getTotalMillis(secondary);
+ let deltaSmall = timeAfterSmall - baseTime;
+
+ // Insert a significantly larger load.
+ performBulkInsert(coll, "large", 20000);
+
+ // Record the time spent applying the large load.
+ let timeAfterLarge = getTotalMillis(secondary);
+ let deltaLarge = timeAfterLarge - timeAfterSmall;
+
+ jsTestLog(`Recorded deltas: {small: ${deltaSmall}ms, large: ${deltaLarge}ms}.`);
+
+ // We should have recorded at least as much time on the second load as we did on the first.
+ // This is a crude comparison that is only taken to check that the timer is used correctly.
+ assert(deltaLarge >= deltaSmall, "Expected a higher net totalMillis for the larger load.");
+ rst.stopSet();
+
+})(); \ No newline at end of file
diff --git a/jstests/replsets/apply_ops_concurrent_non_atomic_different_db.js b/jstests/replsets/apply_ops_concurrent_non_atomic_different_db.js
new file mode 100644
index 00000000000..05cb6f9e996
--- /dev/null
+++ b/jstests/replsets/apply_ops_concurrent_non_atomic_different_db.js
@@ -0,0 +1,11 @@
+(function() {
+ 'use strict';
+
+ load('jstests/replsets/libs/apply_ops_concurrent_non_atomic.js');
+
+ new ApplyOpsConcurrentNonAtomicTest({
+ ns1: 'test1.coll1',
+ ns2: 'test2.coll2',
+ requiresDocumentLevelConcurrency: false,
+ }).run();
+}());
diff --git a/jstests/replsets/apply_ops_concurrent_non_atomic_same_collection.js b/jstests/replsets/apply_ops_concurrent_non_atomic_same_collection.js
new file mode 100644
index 00000000000..004eeaaa52f
--- /dev/null
+++ b/jstests/replsets/apply_ops_concurrent_non_atomic_same_collection.js
@@ -0,0 +1,11 @@
+(function() {
+ 'use strict';
+
+ load('jstests/replsets/libs/apply_ops_concurrent_non_atomic.js');
+
+ new ApplyOpsConcurrentNonAtomicTest({
+ ns1: 'test.coll',
+ ns2: 'test.coll',
+ requiresDocumentLevelConcurrency: true,
+ }).run();
+}());
diff --git a/jstests/replsets/apply_ops_concurrent_non_atomic_same_db.js b/jstests/replsets/apply_ops_concurrent_non_atomic_same_db.js
new file mode 100644
index 00000000000..10f874382a5
--- /dev/null
+++ b/jstests/replsets/apply_ops_concurrent_non_atomic_same_db.js
@@ -0,0 +1,11 @@
+(function() {
+ 'use strict';
+
+ load('jstests/replsets/libs/apply_ops_concurrent_non_atomic.js');
+
+ new ApplyOpsConcurrentNonAtomicTest({
+ ns1: 'test.coll1',
+ ns2: 'test.coll2',
+ requiresDocumentLevelConcurrency: false,
+ }).run();
+}());
diff --git a/jstests/replsets/clean_shutdown_oplog_state.js b/jstests/replsets/clean_shutdown_oplog_state.js
index 5ac2f72d556..91b31fc66d7 100644
--- a/jstests/replsets/clean_shutdown_oplog_state.js
+++ b/jstests/replsets/clean_shutdown_oplog_state.js
@@ -10,7 +10,7 @@
var rst = new ReplSetTest({
name: "name",
nodes: 2,
- oplogSize: 100,
+ oplogSize: 500,
});
rst.startSet();
diff --git a/jstests/replsets/index_delete.js b/jstests/replsets/index_delete.js
index f7c45e2f2e0..1c352a18581 100644
--- a/jstests/replsets/index_delete.js
+++ b/jstests/replsets/index_delete.js
@@ -60,7 +60,7 @@ try {
} else {
return false;
}
- }, "index not started on secondary", 30000, 50);
+ }, "index not started on secondary");
} finally {
// Turn off failpoint and let the index build resumes.
assert.commandWorked(
diff --git a/jstests/replsets/initial_sync_rename_collection_unsafe.js b/jstests/replsets/initial_sync_rename_collection_unsafe.js
new file mode 100644
index 00000000000..a105e1c9287
--- /dev/null
+++ b/jstests/replsets/initial_sync_rename_collection_unsafe.js
@@ -0,0 +1,61 @@
+/**
+ * Tests that renameCollection commands do not abort initial sync when users specify
+ * 'allowUnsafeRenamesDuringInitialSync'.
+ */
+
+(function() {
+ 'use strict';
+
+ load("jstests/libs/check_log.js");
+
+ var parameters = TestData.setParameters;
+ if (parameters && parameters.indexOf("use3dot2InitialSync=true") != -1) {
+ jsTest.log("Skipping this test because use3dot2InitialSync was provided.");
+ return;
+ }
+
+ const basename = 'initial_sync_rename_collection_unsafe';
+
+ const rst = new ReplSetTest({name: basename, nodes: 1});
+ rst.startSet();
+ rst.initiate();
+
+ const dbName = 'd';
+ const primary = rst.getPrimary();
+ const primaryDB = primary.getDB(dbName);
+
+ assert.writeOK(primaryDB['foo'].save({}));
+
+ jsTestLog('Bring up a new node');
+ const secondary = rst.add({setParameter: {allowUnsafeRenamesDuringInitialSync: true}});
+ assert.commandWorked(secondary.adminCommand(
+ {configureFailPoint: 'initialSyncHangBeforeCopyingDatabases', mode: 'alwaysOn'}));
+ rst.reInitiate();
+ assert.eq(primary, rst.getPrimary(), 'Primary changed after reconfig');
+
+ // Wait for fail point message to be logged.
+ checkLog.contains(secondary,
+ 'initial sync - initialSyncHangBeforeCopyingDatabases fail point enabled');
+
+ jsTestLog('Rename collection on the primary');
+ assert.commandWorked(primaryDB['foo'].renameCollection('renamed'));
+
+ assert.commandWorked(secondary.adminCommand(
+ {configureFailPoint: 'initialSyncHangBeforeCopyingDatabases', mode: 'off'}));
+
+ checkLog.contains(secondary, 'allowUnsafeRenamesDuringInitialSync set to true');
+
+ jsTestLog('Wait for both nodes to be up-to-date');
+ rst.awaitSecondaryNodes();
+ rst.awaitReplication();
+
+ jsTestLog('Check that all collections were renamed correctly on the secondary');
+ const secondaryDB = secondary.getDB(dbName);
+ assert.eq(secondaryDB['renamed'].find().itcount(), 1, 'renamed collection does not exist');
+ assert.eq(secondaryDB['foo'].find().itcount(), 0, 'collection `foo` exists after rename');
+
+ let res = assert.commandWorked(secondary.adminCommand({replSetGetStatus: 1, initialSync: 1}));
+ assert.eq(res.initialSyncStatus.failedInitialSyncAttempts, 0);
+
+ rst.stopSet();
+})();
diff --git a/jstests/replsets/last_vote.js b/jstests/replsets/last_vote.js
index b9e0474217c..44d349f3237 100644
--- a/jstests/replsets/last_vote.js
+++ b/jstests/replsets/last_vote.js
@@ -134,9 +134,6 @@
"replSetRequestVotes response had the wrong term: " + tojson(response));
assert(!response.voteGranted,
"node granted vote in term before last vote doc: " + tojson(response));
- assert.eq(response.reason,
- "candidate's term is lower than mine",
- "replSetRequestVotes response had the wrong reason: " + tojson(response));
assertNodeHasLastVote(node0, term, rst.nodes[0]);
assertCurrentTerm(node0, term);
@@ -178,9 +175,6 @@
"replSetRequestVotes response had the wrong term: " + tojson(response));
assert(!response.voteGranted,
"node granted vote in term of last vote doc: " + tojson(response));
- assert.eq(response.reason,
- "already voted for another candidate this term",
- "replSetRequestVotes response had the wrong reason: " + tojson(response));
assertNodeHasLastVote(node0, term, rst.nodes[0]);
assertCurrentTerm(node0, term);
diff --git a/jstests/replsets/libs/apply_ops_concurrent_non_atomic.js b/jstests/replsets/libs/apply_ops_concurrent_non_atomic.js
new file mode 100644
index 00000000000..5e170378de2
--- /dev/null
+++ b/jstests/replsets/libs/apply_ops_concurrent_non_atomic.js
@@ -0,0 +1,232 @@
+/**
+ * This test ensures that multiple non-atomic applyOps commands can run concurrently.
+ * Prior to SERVER-29802, applyOps would acquire the global lock regardless of the
+ * atomicity of the operations (as a whole) being applied.
+ *
+ * Every instance of ApplyOpsConcurrentNonAtomicTest is configured with an "options" document
+ * with the following format:
+ * {
+ * ns1: <string>,
+ * ns1: <string>,
+ * requiresDocumentLevelConcurrency: <bool>,
+ * }
+ *
+ * ns1:
+ * Fully qualified namespace of first set of CRUD operations. For simplicity, only insert
+ * operations will be used. The set of documents generated for the inserts into ns1 will have
+ * _id values distinct from those generated for ns2.
+ *
+ * ns2:
+ * Fully qualified namespace of second set of CRUD operations. This may be the same namespace as
+ * ns1. As with ns1, only insert operations will be used.
+ *
+ * requiresDocumentLevelConcurrency:
+ * Set to true if this test case can only be run with a storage engine that supports document
+ * level concurrency.
+ */
+var ApplyOpsConcurrentNonAtomicTest = function(options) {
+ 'use strict';
+
+ load('jstests/concurrency/fsm_workload_helpers/server_types.js');
+
+ if (!(this instanceof ApplyOpsConcurrentNonAtomicTest)) {
+ return new ApplyOpsConcurrentNonAtomicTest(options);
+ }
+
+ // Capture the 'this' reference
+ var self = this;
+
+ self.options = options;
+
+ /**
+ * Logs message using test name as prefix.
+ */
+ function testLog(message) {
+ jsTestLog('ApplyOpsConcurrentNonAtomicTest: ' + message);
+ }
+
+ /**
+ * Creates an array of insert operations for applyOps into collection 'coll'.
+ */
+ function generateInsertOps(coll, numOps, id) {
+ // Explicit 'use strict' to prevent mozjs from injecting its own "use strict" directive
+ // (with incorrect indentation) when we convert this function into a string for
+ // startParallelShell().
+ 'use strict';
+ const ops = Array(numOps).fill('ignored').map((unused, i) => {
+ return {op: 'i', ns: coll.getFullName(), o: {_id: (id * numOps + i), id: id}};
+ });
+ return ops;
+ }
+
+ /**
+ * Runs applyOps in non-atomic mode to insert 'numOps' documents into collection 'coll'.
+ */
+ function applyOpsInsertNonAtomic(coll, numOps, id) {
+ 'use strict';
+ const ops = generateInsertOps(coll, numOps, id);
+ const mydb = coll.getDB();
+ assert.commandWorked(mydb.runCommand({applyOps: ops, allowAtomic: false}),
+ 'failed to insert documents into ' + coll.getFullName());
+ }
+
+ /**
+ * Parses 'numOps' and collection namespace from 'options' and runs applyOps to inserted
+ * generated documents.
+ *
+ * options format:
+ * {
+ * ns: <string>,
+ * numOps: <int>,
+ * id: <int>,
+ * }
+ *
+ * ns:
+ * Fully qualified namespace of collection to insert documents into.
+ *
+ * numOps:
+ * Number of insert operations to generate for applyOps command.
+ *
+ * id:
+ * Index of collection for applyOps. Used with 'numOps' to generate _id values that will not
+ * collide with collections with different indexes.
+ */
+ function insertFunction(options) {
+ 'use strict';
+
+ const coll = db.getMongo().getCollection(options.ns);
+ const numOps = options.numOps;
+ const id = options.id;
+
+ testLog('Starting to apply ' + numOps + ' operations in collection ' + coll.getFullName());
+ applyOpsInsertNonAtomic(coll, numOps, id);
+ testLog('Successfully applied ' + numOps + ' operations in collection ' +
+ coll.getFullName());
+ }
+
+ /**
+ * Creates a function for startParallelShell() to run that will insert documents into
+ * collection 'coll' using applyOps.
+ */
+ function createInsertFunction(coll, numOps, id) {
+ const options = {
+ ns: coll.getFullName(),
+ numOps: numOps,
+ id: id,
+ };
+ const functionName = 'insertFunction_' + coll.getFullName().replace(/\./g, '_');
+ const s = //
+ '\n\n' + //
+ 'const testLog = ' + testLog + ';\n\n' + //
+ 'const generateInsertOps = ' + generateInsertOps + ';\n\n' + //
+ 'const applyOpsInsertNonAtomic = ' + applyOpsInsertNonAtomic + ';\n\n' + //
+ 'const ' + functionName + ' = ' + insertFunction + ';\n\n' + //
+ functionName + '(' + tojson(options) + ');'; //
+ return s;
+ }
+
+ /**
+ * Returns number of insert operations reported by serverStatus.
+ * In 3.4 'opcountersRepl', not 'opcounters' was previously the correct field. Now non-atomic
+ * ops are now replicated as they are applied and are counted toward the global op counter.
+ */
+ function getInsertOpCount(serverStatus) {
+ return serverStatus.opcounters.insert;
+ }
+
+ /**
+ * Runs the test.
+ */
+ this.run = function() {
+ const options = this.options;
+
+ assert(options.ns1, 'collection 1 namespace not provided');
+ assert(options.ns2, 'collection 2 namespace not provided');
+
+ const replTest = new ReplSetTest({nodes: 1});
+ replTest.startSet();
+ replTest.initiate();
+
+ const primary = replTest.getPrimary();
+ const adminDb = primary.getDB('admin');
+
+ if (options.requiresDocumentLevelConcurrency &&
+ !supportsDocumentLevelConcurrency(adminDb)) {
+ testLog('Skipping test because storage engine does not support document level ' +
+ 'concurrency.');
+ return;
+ }
+
+ const coll1 = primary.getCollection(options.ns1);
+ const db1 = coll1.getDB();
+ const coll2 = primary.getCollection(options.ns2);
+ const db2 = coll2.getDB();
+
+ assert.commandWorked(db1.createCollection(coll1.getName()));
+ if (coll1.getFullName() !== coll2.getFullName()) {
+ assert.commandWorked(db2.createCollection(coll2.getName()));
+ }
+
+ // Enable fail point to pause applyOps between operations.
+ assert.commandWorked(primary.adminCommand(
+ {configureFailPoint: 'applyOpsPauseBetweenOperations', mode: 'alwaysOn'}));
+
+ // This logs each operation being applied.
+ const previousLogLevel =
+ assert.commandWorked(primary.setLogLevel(3, 'replication')).was.replication.verbosity;
+
+ testLog('Applying operations in collections ' + coll1.getFullName() + ' and ' +
+ coll2.getFullName());
+
+ const numOps = 100;
+ const insertProcess1 =
+ startParallelShell(createInsertFunction(coll1, numOps, 0), replTest.getPort(0));
+ const insertProcess2 =
+ startParallelShell(createInsertFunction(coll2, numOps, 1), replTest.getPort(0));
+
+ // The fail point will prevent applyOps from advancing past the first operation in each
+ // batch of operations. If applyOps is applying both sets of operations concurrently without
+ // holding the global lock, the insert opcounter will eventually be incremented to 2.
+ try {
+ let insertOpCount = 0;
+ const expectedFinalOpCount = 2;
+ assert.soon(
+ function() {
+ const serverStatus = adminDb.serverStatus();
+ insertOpCount = getInsertOpCount(serverStatus);
+ // This assertion may fail if the fail point is not implemented correctly within
+ // applyOps. This allows us to fail fast instead of waiting for the
+ // assert.soon() function to time out.
+ assert.lte(insertOpCount,
+ expectedFinalOpCount,
+ 'Expected at most ' + expectedFinalOpCount +
+ ' documents inserted with fail point enabled. ' +
+ 'Most recent insert operation count = ' + insertOpCount);
+ return insertOpCount === expectedFinalOpCount;
+ },
+ 'Insert operation count did not reach ' + expectedFinalOpCount +
+ ' as expected with fail point enabled. Most recent insert operation count = ' +
+ insertOpCount);
+ } finally {
+ assert.commandWorked(primary.adminCommand(
+ {configureFailPoint: 'applyOpsPauseBetweenOperations', mode: 'off'}));
+ }
+
+ insertProcess1();
+ insertProcess2();
+
+ testLog('Successfully applied operations in collections ' + coll1.getFullName() + ' and ' +
+ coll2.getFullName());
+
+ // Reset log level.
+ primary.setLogLevel(previousLogLevel, 'replication');
+
+ const serverStatus = adminDb.serverStatus();
+ assert.eq(200,
+ getInsertOpCount(serverStatus),
+ 'incorrect number of insert operations in server status after applyOps: ' +
+ tojson(serverStatus));
+
+ replTest.stopSet();
+ };
+};
diff --git a/jstests/replsets/libs/apply_ops_insert_write_conflict.js b/jstests/replsets/libs/apply_ops_insert_write_conflict.js
index 88b299c08b6..d0a47f5b6d8 100644
--- a/jstests/replsets/libs/apply_ops_insert_write_conflict.js
+++ b/jstests/replsets/libs/apply_ops_insert_write_conflict.js
@@ -36,14 +36,6 @@ var ApplyOpsInsertWriteConflictTest = function(options) {
return {op: 'i', ns: t.getFullName(), o: {_id: i}};
});
- if (!options.atomic) {
- // Adding a command to the list of operations to prevent the applyOps command from
- // applying
- // all the operations atomically.
- ops.push({ns: "test.$cmd", op: "c", o: {applyOps: []}});
- numOps++;
- }
-
// Probabilities for WCE are chosen based on empirical testing.
// The probability for WCE during an atomic applyOps should be much smaller than that for
// the non-atomic case because we have to attempt to re-apply the entire batch of 'numOps'
@@ -62,7 +54,7 @@ var ApplyOpsInsertWriteConflictTest = function(options) {
var previousLogLevel =
assert.commandWorked(primaryDB.setLogLevel(3, 'replication')).was.replication.verbosity;
- var applyOpsResult = primaryDB.adminCommand({applyOps: ops});
+ var applyOpsResult = primaryDB.adminCommand({applyOps: ops, allowAtomic: options.atomic});
// Reset log level.
primaryDB.setLogLevel(previousLogLevel, 'replication');
diff --git a/jstests/replsets/no_flapping_during_network_partition.js b/jstests/replsets/no_flapping_during_network_partition.js
index 5a289b2afdd..daab9be2bca 100644
--- a/jstests/replsets/no_flapping_during_network_partition.js
+++ b/jstests/replsets/no_flapping_during_network_partition.js
@@ -39,7 +39,7 @@
primary.disconnect(secondary);
jsTestLog("Wait long enough for the secondary to call for an election.");
- checkLog.contains(secondary, "can see a healthy primary of equal or greater priority");
+ checkLog.contains(secondary, "can see a healthy primary");
jsTestLog("Verify the primary and secondary do not change during the partition.");
assert.eq(primary, replTest.getPrimary());
diff --git a/jstests/replsets/noop_writes_wait_for_write_concern.js b/jstests/replsets/noop_writes_wait_for_write_concern.js
new file mode 100644
index 00000000000..61ffc518df4
--- /dev/null
+++ b/jstests/replsets/noop_writes_wait_for_write_concern.js
@@ -0,0 +1,235 @@
+/**
+ * This file tests that if a user initiates a write that becomes a noop due to being a duplicate
+ * operation, that we still wait for write concern. This is because we must wait for write concern
+ * on the write that made this a noop so that we can be sure it doesn't get rolled back if we
+ * acknowledge it.
+ */
+
+(function() {
+ "use strict";
+ load('jstests/libs/write_concern_util.js');
+
+ var name = 'noop_writes_wait_for_write_concern';
+ var replTest = new ReplSetTest({
+ name: name,
+ nodes: [{}, {rsConfig: {priority: 0}}, {rsConfig: {priority: 0}}],
+ });
+ replTest.startSet();
+ replTest.initiate();
+ // Stops node 1 so that all w:3 write concerns time out. We have 3 data bearing nodes so that
+ // 'dropDatabase' can satisfy its implicit writeConcern: majority but still time out from the
+ // explicit w:3 write concern.
+ replTest.stop(1);
+
+ var primary = replTest.getPrimary();
+ assert.eq(primary, replTest.nodes[0]);
+ var dbName = 'testDB';
+ var db = primary.getDB(dbName);
+ var collName = 'testColl';
+ var coll = db[collName];
+
+ function dropTestCollection() {
+ coll.drop();
+ assert.eq(0, coll.find().itcount(), "test collection not empty");
+ }
+
+ // Each entry in this array contains a command whose noop write concern behavior needs to be
+ // tested. Entries have the following structure:
+ // {
+ // req: <object>, // Command request object that will result in a noop
+ // // write after the setup function is called.
+ //
+ // setupFunc: <function()>, // Function to run to ensure that the request is a
+ // // noop.
+ //
+ // confirmFunc: <function(res)>, // Function to run after the command is run to ensure
+ // // that it executed properly. Accepts the result of
+ // // the noop request to validate it.
+ // }
+ var commands = [];
+
+ commands.push({
+ req: {applyOps: [{op: "i", ns: coll.getFullName(), o: {_id: 1}}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({_id: 1}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.applied, 1);
+ assert.eq(res.results[0], true);
+ assert.eq(coll.find().itcount(), 1);
+ assert.eq(coll.count({_id: 1}), 1);
+ }
+ });
+
+ // 'update' where the document to update does not exist.
+ commands.push({
+ req: {update: collName, updates: [{q: {a: 1}, u: {b: 2}}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.update({a: 1}, {b: 2}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.n, 0);
+ assert.eq(res.nModified, 0);
+ assert.eq(coll.find().itcount(), 1);
+ assert.eq(coll.count({b: 2}), 1);
+ }
+ });
+
+ // 'update' where the update has already been done.
+ commands.push({
+ req: {update: collName, updates: [{q: {a: 1}, u: {$set: {b: 2}}}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.update({a: 1}, {$set: {b: 2}}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.n, 1);
+ assert.eq(res.nModified, 0);
+ assert.eq(coll.find().itcount(), 1);
+ assert.eq(coll.count({a: 1, b: 2}), 1);
+ }
+ });
+
+ commands.push({
+ req: {delete: collName, deletes: [{q: {a: 1}, limit: 1}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.writeOK(coll.remove({a: 1}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.n, 0);
+ assert.eq(coll.count({a: 1}), 0);
+ }
+ });
+
+ commands.push({
+ req: {createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.commandWorked(
+ db.runCommand({createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.numIndexesBefore, res.numIndexesAfter);
+ }
+ });
+
+ // 'findAndModify' where the document to update does not exist.
+ commands.push({
+ req: {findAndModify: collName, query: {a: 1}, update: {b: 2}},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.commandWorked(
+ db.runCommand({findAndModify: collName, query: {a: 1}, update: {b: 2}}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.lastErrorObject.updatedExisting, false);
+ assert.eq(coll.find().itcount(), 1);
+ assert.eq(coll.count({b: 2}), 1);
+ }
+ });
+
+ // 'findAndModify' where the update has already been done.
+ commands.push({
+ req: {findAndModify: collName, query: {a: 1}, update: {$set: {b: 2}}},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.commandWorked(
+ db.runCommand({findAndModify: collName, query: {a: 1}, update: {$set: {b: 2}}}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.lastErrorObject.updatedExisting, true);
+ assert.eq(coll.find().itcount(), 1);
+ assert.eq(coll.count({a: 1, b: 2}), 1);
+ }
+ });
+
+ commands.push({
+ req: {dropDatabase: 1},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.commandWorked(db.runCommand({dropDatabase: 1}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ }
+ });
+
+ commands.push({
+ req: {drop: collName},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({a: 1}));
+ assert.commandWorked(db.runCommand({drop: collName}));
+ },
+ confirmFunc: function(res) {
+ assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
+ }
+ });
+
+ commands.push({
+ req: {create: collName},
+ setupFunc: function() {
+ assert.commandWorked(db.runCommand({create: collName}));
+ },
+ confirmFunc: function(res) {
+ assert.commandFailedWithCode(res, ErrorCodes.NamespaceExists);
+ }
+ });
+
+ commands.push({
+ req: {insert: collName, documents: [{_id: 1}]},
+ setupFunc: function() {
+ assert.writeOK(coll.insert({_id: 1}));
+ },
+ confirmFunc: function(res) {
+ assert.commandWorked(res);
+ assert.eq(res.n, 0);
+ assert.eq(res.writeErrors[0].code, ErrorCodes.DuplicateKey);
+ assert.eq(coll.count({_id: 1}), 1);
+ }
+ });
+
+ function testCommandWithWriteConcern(cmd) {
+ // Provide a small wtimeout that we expect to time out.
+ cmd.req.writeConcern = {w: 3, wtimeout: 1000};
+ jsTest.log("Testing " + tojson(cmd.req));
+
+ dropTestCollection();
+
+ cmd.setupFunc();
+
+ // We run the command on a different connection. If the the command were run on the
+ // same connection, then the client last op for the noop write would be set by the setup
+ // operation. By using a fresh connection the client last op begins as null.
+ // This test explicitly tests that write concern for noop writes works when the
+ // client last op has not already been set by a duplicate operation.
+ var shell2 = new Mongo(primary.host);
+
+ // We check the error code of 'res' in the 'confirmFunc'.
+ var res = shell2.getDB(dbName).runCommand(cmd.req);
+
+ try {
+ // Tests that the command receives a write concern error. If we don't wait for write
+ // concern on noop writes then we won't get a write concern error.
+ assertWriteConcernError(res);
+ cmd.confirmFunc(res);
+ } catch (e) {
+ // Make sure that we print out the response.
+ printjson(res);
+ throw e;
+ }
+ }
+
+ commands.forEach(function(cmd) {
+ testCommandWithWriteConcern(cmd);
+ });
+
+})(); \ No newline at end of file
diff --git a/jstests/replsets/read_committed_with_catalog_changes.js b/jstests/replsets/read_committed_with_catalog_changes.js
index 02d26759e26..7928b16d3ae 100644
--- a/jstests/replsets/read_committed_with_catalog_changes.js
+++ b/jstests/replsets/read_committed_with_catalog_changes.js
@@ -214,7 +214,7 @@ load("jstests/replsets/rslib.js"); // For startSetIfSupportsReadMajority.
"Expected read of " + coll.getFullName() + " to block");
}
- function assertReadsSucceed(coll, timeoutMs = 10000) {
+ function assertReadsSucceed(coll, timeoutMs = 20000) {
var res =
coll.runCommand('find', {"readConcern": {"level": "majority"}, "maxTimeMS": timeoutMs});
assert.commandWorked(res, 'reading from ' + coll.getFullName());
diff --git a/jstests/replsets/startParallelShell.js b/jstests/replsets/startParallelShell.js
new file mode 100644
index 00000000000..beca88d19a9
--- /dev/null
+++ b/jstests/replsets/startParallelShell.js
@@ -0,0 +1,33 @@
+// Test startParallelShell() in a replica set.
+
+var db;
+
+(function() {
+ 'use strict';
+
+ const setName = 'rs0';
+ const replSet = new ReplSetTest({name: setName, nodes: 3});
+ const nodes = replSet.nodeList();
+ replSet.startSet();
+ replSet.initiate();
+
+ const url = replSet.getURL();
+ print("* Connecting to " + url);
+ const mongo = new Mongo(url);
+ db = mongo.getDB('admin');
+ assert.eq(url, mongo.host, "replSet.getURL() should match active connection string");
+
+ print("* Starting parallel shell on --host " + db.getMongo().host);
+ startParallelShell('db.coll0.insert({test: "connString only"});');
+ assert.soon(function() {
+ return db.coll0.find({test: "connString only"}).count() === 1;
+ });
+
+ const uri = new MongoURI(url);
+ const port0 = uri.servers[0].port;
+ print("* Starting parallel shell w/ --port " + port0);
+ startParallelShell('db.coll0.insert({test: "explicit port"});', port0);
+ assert.soon(function() {
+ return db.coll0.find({test: "explicit port"}).count() === 1;
+ });
+})();
diff --git a/jstests/replsets/too_stale_secondary.js b/jstests/replsets/too_stale_secondary.js
index 369662e5f16..1a0c28b3454 100644
--- a/jstests/replsets/too_stale_secondary.js
+++ b/jstests/replsets/too_stale_secondary.js
@@ -34,7 +34,7 @@
"use strict";
function getFirstOplogEntry(conn) {
- return conn.getDB('local').oplog.rs.find().sort({ts: 1}).limit(1)[0];
+ return conn.getDB('local').oplog.rs.find().sort({$natural: 1}).limit(1)[0];
}
/**
diff --git a/jstests/sharding/auth.js b/jstests/sharding/auth.js
index 61b25f10dde..037532acaec 100644
--- a/jstests/sharding/auth.js
+++ b/jstests/sharding/auth.js
@@ -38,7 +38,7 @@
name: "auth",
mongos: 1,
shards: 0,
- other: {keyFile: "jstests/libs/key1", chunkSize: 1, enableAutoSplit: true},
+ other: {keyFile: "jstests/libs/key1", chunkSize: 1, enableAutoSplit: false},
});
if (s.getDB('admin').runCommand('buildInfo').bits < 64) {
@@ -167,6 +167,7 @@
s.getDB("test").foo.remove({});
var num = 10000;
+ assert.commandWorked(s.s.adminCommand({split: "test.foo", middle: {x: num / 2}}));
var bulk = s.getDB("test").foo.initializeUnorderedBulkOp();
for (i = 0; i < num; i++) {
bulk.insert(
diff --git a/jstests/sharding/auto_rebalance_parallel.js b/jstests/sharding/auto_rebalance_parallel.js
index 955319b8c5d..c7078a6898a 100644
--- a/jstests/sharding/auto_rebalance_parallel.js
+++ b/jstests/sharding/auto_rebalance_parallel.js
@@ -1,43 +1,71 @@
/**
* Tests that the cluster is balanced in parallel in one balancer round (standalone).
*/
+
(function() {
'use strict';
var st = new ShardingTest({shards: 4});
+ var config = st.s0.getDB('config');
assert.commandWorked(st.s0.adminCommand({enableSharding: 'TestDB'}));
st.ensurePrimaryShard('TestDB', st.shard0.shardName);
- assert.commandWorked(st.s0.adminCommand({shardCollection: 'TestDB.TestColl', key: {Key: 1}}));
- var coll = st.s0.getDB('TestDB').TestColl;
+ function prepareCollectionForBalance(collName) {
+ assert.commandWorked(st.s0.adminCommand({shardCollection: collName, key: {Key: 1}}));
+
+ var coll = st.s0.getCollection(collName);
+
+ // Create 4 chunks initially and ensure they get balanced within 1 balancer round
+ assert.writeOK(coll.insert({Key: 1, Value: 'Test value 1'}));
+ assert.writeOK(coll.insert({Key: 10, Value: 'Test value 10'}));
+ assert.writeOK(coll.insert({Key: 20, Value: 'Test value 20'}));
+ assert.writeOK(coll.insert({Key: 30, Value: 'Test value 30'}));
+
+ assert.commandWorked(st.splitAt(collName, {Key: 10}));
+ assert.commandWorked(st.splitAt(collName, {Key: 20}));
+ assert.commandWorked(st.splitAt(collName, {Key: 30}));
+
+ // Move two of the chunks to shard0001 so we have option to do parallel balancing
+ assert.commandWorked(st.moveChunk(collName, {Key: 20}, st.shard1.shardName));
+ assert.commandWorked(st.moveChunk(collName, {Key: 30}, st.shard1.shardName));
- // Create 4 chunks initially and ensure they get balanced within 1 balancer round
- assert.writeOK(coll.insert({Key: 1, Value: 'Test value 1'}));
- assert.writeOK(coll.insert({Key: 10, Value: 'Test value 10'}));
- assert.writeOK(coll.insert({Key: 20, Value: 'Test value 20'}));
- assert.writeOK(coll.insert({Key: 30, Value: 'Test value 30'}));
+ assert.eq(2, config.chunks.find({ns: collName, shard: st.shard0.shardName}).itcount());
+ assert.eq(2, config.chunks.find({ns: collName, shard: st.shard1.shardName}).itcount());
+ }
- assert.commandWorked(st.splitAt('TestDB.TestColl', {Key: 10}));
- assert.commandWorked(st.splitAt('TestDB.TestColl', {Key: 20}));
- assert.commandWorked(st.splitAt('TestDB.TestColl', {Key: 30}));
+ function checkCollectionBalanced(collName) {
+ assert.eq(1, config.chunks.find({ns: collName, shard: st.shard0.shardName}).itcount());
+ assert.eq(1, config.chunks.find({ns: collName, shard: st.shard1.shardName}).itcount());
+ assert.eq(1, config.chunks.find({ns: collName, shard: st.shard2.shardName}).itcount());
+ assert.eq(1, config.chunks.find({ns: collName, shard: st.shard3.shardName}).itcount());
+ }
- // Move two of the chunks to shard0001 so we have option to do parallel balancing
- assert.commandWorked(st.moveChunk('TestDB.TestColl', {Key: 20}, st.shard1.shardName));
- assert.commandWorked(st.moveChunk('TestDB.TestColl', {Key: 30}, st.shard1.shardName));
+ function countMoves(collName) {
+ return config.changelog.find({what: 'moveChunk.start', ns: collName}).itcount();
+ }
- assert.eq(2, st.s0.getDB('config').chunks.find({shard: st.shard0.shardName}).itcount());
- assert.eq(2, st.s0.getDB('config').chunks.find({shard: st.shard1.shardName}).itcount());
+ prepareCollectionForBalance('TestDB.TestColl1');
+ prepareCollectionForBalance('TestDB.TestColl2');
+
+ // Count the moveChunk start attempts accurately and ensure that only the correct number of
+ // migrations are scheduled
+ const testColl1InitialMoves = countMoves('TestDB.TestColl1');
+ const testColl2InitialMoves = countMoves('TestDB.TestColl2');
- // Do enable the balancer and wait for a single balancer round
st.startBalancer();
st.awaitBalancerRound();
+ st.awaitBalancerRound();
st.stopBalancer();
- assert.eq(1, st.s0.getDB('config').chunks.find({shard: st.shard0.shardName}).itcount());
- assert.eq(1, st.s0.getDB('config').chunks.find({shard: st.shard1.shardName}).itcount());
- assert.eq(1, st.s0.getDB('config').chunks.find({shard: st.shard2.shardName}).itcount());
- assert.eq(1, st.s0.getDB('config').chunks.find({shard: st.shard3.shardName}).itcount());
+ checkCollectionBalanced('TestDB.TestColl1');
+ checkCollectionBalanced('TestDB.TestColl2');
+
+ assert.eq(2, countMoves('TestDB.TestColl1') - testColl1InitialMoves);
+ assert.eq(2, countMoves('TestDB.TestColl2') - testColl2InitialMoves);
+
+ // Ensure there are no migration errors reported
+ assert.eq(0, config.changelog.find({what: 'moveChunk.error'}).itcount());
st.stop();
})();
diff --git a/jstests/sharding/autosplit.js b/jstests/sharding/autosplit.js
index bb34021487f..0eba386b6a3 100644
--- a/jstests/sharding/autosplit.js
+++ b/jstests/sharding/autosplit.js
@@ -4,7 +4,12 @@
(function() {
'use strict';
- var s = new ShardingTest({name: "auto1", shards: 2, mongos: 1, other: {enableAutoSplit: true}});
+ var s = new ShardingTest({
+ name: "auto1",
+ shards: 2,
+ mongos: 1,
+ other: {enableAutoSplit: true, chunkSize: 10},
+ });
assert.commandWorked(s.s0.adminCommand({enablesharding: "test"}));
s.ensurePrimaryShard('test', 'shard0001');
diff --git a/jstests/sharding/mapReduce_inSharded_outSharded.js b/jstests/sharding/mapReduce_inSharded_outSharded.js
index d1aba2599f0..d73eb517e98 100644
--- a/jstests/sharding/mapReduce_inSharded_outSharded.js
+++ b/jstests/sharding/mapReduce_inSharded_outSharded.js
@@ -1,60 +1,70 @@
-var verifyOutput = function(out) {
- printjson(out);
- assert.eq(out.counts.input, 51200, "input count is wrong");
- assert.eq(out.counts.emit, 51200, "emit count is wrong");
- assert.gt(out.counts.reduce, 99, "reduce count is wrong");
- assert.eq(out.counts.output, 512, "output count is wrong");
-};
-
-var st = new ShardingTest(
- {shards: 2, verbose: 1, mongos: 1, other: {chunkSize: 1, enableBalancer: true}});
-
-st.adminCommand({enablesharding: "mrShard"});
-st.ensurePrimaryShard('mrShard', 'shard0001');
-st.adminCommand({shardcollection: "mrShard.srcSharded", key: {"_id": 1}});
-
-var db = st.getDB("mrShard");
-
-var bulk = db.srcSharded.initializeUnorderedBulkOp();
-for (j = 0; j < 100; j++) {
- for (i = 0; i < 512; i++) {
- bulk.insert({j: j, i: i});
+(function() {
+ "use strict";
+
+ var verifyOutput = function(out) {
+ printjson(out);
+ assert.eq(out.counts.input, 51200, "input count is wrong");
+ assert.eq(out.counts.emit, 51200, "emit count is wrong");
+ assert.gt(out.counts.reduce, 99, "reduce count is wrong");
+ assert.eq(out.counts.output, 512, "output count is wrong");
+ };
+
+ var st = new ShardingTest(
+ {shards: 2, verbose: 1, mongos: 1, other: {chunkSize: 1, enableBalancer: true}});
+
+ var admin = st.s0.getDB('admin');
+
+ assert.commandWorked(admin.runCommand({enablesharding: "mrShard"}));
+ st.ensurePrimaryShard('mrShard', 'shard0001');
+ assert.commandWorked(
+ admin.runCommand({shardcollection: "mrShard.srcSharded", key: {"_id": 1}}));
+
+ var db = st.s0.getDB("mrShard");
+
+ var bulk = db.srcSharded.initializeUnorderedBulkOp();
+ for (var j = 0; j < 100; j++) {
+ for (var i = 0; i < 512; i++) {
+ bulk.insert({j: j, i: i});
+ }
+ }
+ assert.writeOK(bulk.execute());
+
+ function map() {
+ emit(this.i, 1);
+ }
+ function reduce(key, values) {
+ return Array.sum(values);
}
-}
-assert.writeOK(bulk.execute());
-
-function map() {
- emit(this.i, 1);
-}
-function reduce(key, values) {
- return Array.sum(values);
-}
-
-// sharded src sharded dst
-var suffix = "InShardedOutSharded";
-
-var out =
- db.srcSharded.mapReduce(map, reduce, {out: {replace: "mrReplace" + suffix, sharded: true}});
-verifyOutput(out);
-
-out = db.srcSharded.mapReduce(map, reduce, {out: {merge: "mrMerge" + suffix, sharded: true}});
-verifyOutput(out);
-
-out = db.srcSharded.mapReduce(map, reduce, {out: {reduce: "mrReduce" + suffix, sharded: true}});
-verifyOutput(out);
-
-out = db.srcSharded.mapReduce(map, reduce, {out: {inline: 1}});
-verifyOutput(out);
-assert(out.results != 'undefined', "no results for inline");
-
-out = db.srcSharded.mapReduce(
- map, reduce, {out: {replace: "mrReplace" + suffix, db: "mrShardOtherDB", sharded: true}});
-verifyOutput(out);
-
-out = db.runCommand({
- mapReduce: "srcSharded", // use new name mapReduce rather than mapreduce
- map: map,
- reduce: reduce,
- out: "mrBasic" + "srcSharded",
-});
-verifyOutput(out);
+
+ // sharded src sharded dst
+ var suffix = "InShardedOutSharded";
+
+ var out =
+ db.srcSharded.mapReduce(map, reduce, {out: {replace: "mrReplace" + suffix, sharded: true}});
+ verifyOutput(out);
+
+ out = db.srcSharded.mapReduce(map, reduce, {out: {merge: "mrMerge" + suffix, sharded: true}});
+ verifyOutput(out);
+
+ out = db.srcSharded.mapReduce(map, reduce, {out: {reduce: "mrReduce" + suffix, sharded: true}});
+ verifyOutput(out);
+
+ out = db.srcSharded.mapReduce(map, reduce, {out: {inline: 1}});
+ verifyOutput(out);
+ assert(out.results != 'undefined', "no results for inline");
+
+ out = db.srcSharded.mapReduce(
+ map, reduce, {out: {replace: "mrReplace" + suffix, db: "mrShardOtherDB", sharded: true}});
+ verifyOutput(out);
+
+ out = db.runCommand({
+ mapReduce: "srcSharded", // use new name mapReduce rather than mapreduce
+ map: map,
+ reduce: reduce,
+ out: "mrBasic" + "srcSharded",
+ });
+ verifyOutput(out);
+
+ st.stop();
+
+})();
diff --git a/jstests/sharding/migrateBig_balancer.js b/jstests/sharding/migrateBig_balancer.js
index 9eb50c2168e..03e98fa5493 100644
--- a/jstests/sharding/migrateBig_balancer.js
+++ b/jstests/sharding/migrateBig_balancer.js
@@ -31,11 +31,8 @@
assert.eq(40, coll.count(), "prep1");
- printjson(coll.stats());
-
- admin.printShardingStatus();
-
- admin.runCommand({shardcollection: "" + coll, key: {_id: 1}});
+ assert.commandWorked(admin.runCommand({shardcollection: "" + coll, key: {_id: 1}}));
+ st.printShardingStatus();
assert.lt(
5, mongos.getDB("config").chunks.find({ns: "test.stuff"}).count(), "not enough chunks");
@@ -56,5 +53,4 @@
}, "never migrated", 10 * 60 * 1000, 1000);
st.stop();
-
})();
diff --git a/jstests/sharding/movechunk_commit_changelog_stats.js b/jstests/sharding/movechunk_commit_changelog_stats.js
new file mode 100644
index 00000000000..d257bb6ed94
--- /dev/null
+++ b/jstests/sharding/movechunk_commit_changelog_stats.js
@@ -0,0 +1,41 @@
+//
+// Tests that the changelog entry for moveChunk.commit contains stats on the migration.
+//
+
+(function() {
+ 'use strict';
+
+ var st = new ShardingTest({mongos: 1, shards: 2});
+ var kDbName = 'db';
+
+ var mongos = st.s0;
+ var shard0 = st.shard0.shardName;
+ var shard1 = st.shard1.shardName;
+
+ assert.commandWorked(mongos.adminCommand({enableSharding: kDbName}));
+ st.ensurePrimaryShard(kDbName, shard0);
+
+ function assertCountsInChangelog() {
+ let changeLog = st.s.getDB('config').changelog.find({what: 'moveChunk.commit'}).toArray();
+ assert.gt(changeLog.length, 0);
+ for (let i = 0; i < changeLog.length; i++) {
+ assert(changeLog[i].details.hasOwnProperty('counts') ||
+ changeLog[i].details.hasOwnProperty('clonedBytes'));
+ }
+ }
+
+ var ns = kDbName + '.fooHashed';
+ assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: {_id: 'hashed'}}));
+
+ var aChunk = mongos.getDB('config').chunks.findOne({_id: RegExp(ns), shard: shard0});
+ assert(aChunk);
+
+ // Assert counts field exists in the changelog entry for moveChunk.commit
+ assert.commandWorked(
+ mongos.adminCommand({moveChunk: ns, bounds: [aChunk.min, aChunk.max], to: shard1}));
+ assertCountsInChangelog();
+
+ mongos.getDB(kDbName).fooHashed.drop();
+
+ st.stop();
+})(); \ No newline at end of file
diff --git a/jstests/sharding/printShardingStatus.js b/jstests/sharding/printShardingStatus.js
index 85330311fd4..cdc36999e51 100644
--- a/jstests/sharding/printShardingStatus.js
+++ b/jstests/sharding/printShardingStatus.js
@@ -7,6 +7,8 @@
var st = new ShardingTest({shards: 1, mongos: 2, config: 1, other: {smallfiles: true}});
+ var standalone = MongoRunner.runMongod();
+
var mongos = st.s0;
var admin = mongos.getDB("admin");
@@ -85,9 +87,10 @@
testBasicVerboseOnly(outputVerbose);
// Take a copy of the config db, in order to test the harder-to-setup cases below.
+ // Copy into a standalone to also test running printShardingStatus() against a config dump.
// TODO: Replace this manual copy with copydb once SERVER-13080 is fixed.
var config = mongos.getDB("config");
- var configCopy = mongos.getDB("configCopy");
+ var configCopy = standalone.getDB("configCopy");
config.getCollectionInfos().forEach(function(c) {
// Create collection with options.
assert.commandWorked(configCopy.createCollection(c.name, c.options));
@@ -140,11 +143,11 @@
configCopy.mongos.remove({});
var output = grabStatusOutput(configCopy, false);
- assertPresentInOutput(output, "most recently active mongoses:\n\tnone", "no mongoses");
+ assertPresentInOutput(output, "most recently active mongoses:\n none", "no mongoses");
var output = grabStatusOutput(configCopy, true);
assertPresentInOutput(
- output, "most recently active mongoses:\n\tnone", "no mongoses (verbose)");
+ output, "most recently active mongoses:\n none", "no mongoses (verbose)");
assert(mongos.getDB(dbName).dropDatabase());
@@ -237,5 +240,7 @@
assert(mongos.getDB("test").dropDatabase());
+ MongoRunner.stopMongod(standalone);
+
st.stop();
})();
diff --git a/jstests/sharding/shard_existing_coll_chunk_count.js b/jstests/sharding/shard_existing_coll_chunk_count.js
new file mode 100644
index 00000000000..60145fef712
--- /dev/null
+++ b/jstests/sharding/shard_existing_coll_chunk_count.js
@@ -0,0 +1,165 @@
+/**
+ * This test confirms that after sharding a collection with some pre-existing data,
+ * the resulting chunks aren't auto-split too aggressively.
+ */
+(function() {
+ 'use strict';
+
+ var s = new ShardingTest({
+ name: "shard_existing_coll_chunk_count",
+ shards: 1,
+ mongos: 1,
+ other: {enableAutoSplit: true},
+ });
+
+ assert.commandWorked(s.s.adminCommand({enablesharding: "test"}));
+
+ var collNum = 0;
+ var overhead = Object.bsonsize({_id: ObjectId(), i: 1, pad: ""});
+
+ var getNumberChunks = function(ns) {
+ return s.configRS.getPrimary().getDB("config").getCollection("chunks").count({ns});
+ };
+
+ var runCase = function(opts) {
+ // Expected options.
+ assert.gte(opts.docSize, 0);
+ assert.gte(opts.stages.length, 2);
+
+ // Compute padding.
+ if (opts.docSize < overhead) {
+ var pad = "";
+ } else {
+ var pad = (new Array(opts.docSize - overhead + 1)).join(' ');
+ }
+
+ collNum++;
+ var db = s.getDB("test");
+ var collName = "coll" + collNum;
+ var coll = db.getCollection(collName);
+ var i = 0;
+ var limit = 0;
+ var stageNum = 0;
+ var stage = opts.stages[stageNum];
+
+ // Insert initial docs.
+ var bulk = coll.initializeUnorderedBulkOp();
+ limit += stage.numDocsToInsert;
+ for (; i < limit; i++) {
+ bulk.insert({i, pad});
+ }
+ assert.writeOK(bulk.execute());
+
+ // Create shard key index.
+ assert.commandWorked(coll.createIndex({i: 1}));
+
+ // Shard collection.
+ assert.commandWorked(s.s.adminCommand({shardcollection: coll.getFullName(), key: {i: 1}}));
+
+ // Confirm initial number of chunks.
+ var numChunks = getNumberChunks(coll.getFullName());
+ assert.eq(numChunks,
+ stage.expectedNumChunks,
+ 'in ' + coll.getFullName() + ' expected ' + stage.expectedNumChunks +
+ ' initial chunks, but found ' + numChunks + '\nopts: ' + tojson(opts) +
+ '\nchunks:\n' + s.getChunksString(coll.getFullName()));
+
+ // Do the rest of the stages.
+ for (stageNum = 1; stageNum < opts.stages.length; stageNum++) {
+ stage = opts.stages[stageNum];
+
+ // Insert the later docs (one at a time, to maximise the autosplit effects).
+ limit += stage.numDocsToInsert;
+ for (; i < limit; i++) {
+ coll.insert({i, pad});
+ }
+
+ // Confirm number of chunks for this stage.
+ var numChunks = getNumberChunks(coll.getFullName());
+ assert.eq(numChunks,
+ stage.expectedNumChunks,
+ 'in ' + coll.getFullName() + ' expected ' + stage.expectedNumChunks +
+ ' chunks for stage ' + stageNum + ', but found ' + numChunks +
+ '\nopts: ' + tojson(opts) + '\nchunks:\n' +
+ s.getChunksString(coll.getFullName()));
+ }
+ };
+
+ // Original problematic case.
+ runCase({
+ docSize: 0,
+ stages: [
+ {numDocsToInsert: 20000, expectedNumChunks: 1},
+ {numDocsToInsert: 7, expectedNumChunks: 1},
+ {numDocsToInsert: 1000, expectedNumChunks: 1},
+ ],
+ });
+
+ // Original problematic case (worse).
+ runCase({
+ docSize: 0,
+ stages: [
+ {numDocsToInsert: 90000, expectedNumChunks: 1},
+ {numDocsToInsert: 7, expectedNumChunks: 1},
+ {numDocsToInsert: 1000, expectedNumChunks: 1},
+ ],
+ });
+
+ // Pathological case #1.
+ runCase({
+ docSize: 522,
+ stages: [
+ {numDocsToInsert: 8191, expectedNumChunks: 1},
+ {numDocsToInsert: 2, expectedNumChunks: 1},
+ {numDocsToInsert: 1000, expectedNumChunks: 1},
+ ],
+ });
+
+ // Pathological case #2.
+ runCase({
+ docSize: 522,
+ stages: [
+ {numDocsToInsert: 8192, expectedNumChunks: 1},
+ {numDocsToInsert: 8192, expectedNumChunks: 1},
+ ],
+ });
+
+ // Lower chunksize to 1MB, and restart the mongos for it to take.
+ assert.writeOK(
+ s.getDB("config").getCollection("settings").update({_id: "chunksize"}, {$set: {value: 1}}, {
+ upsert: true
+ }));
+ s.restartMongos(0);
+
+ // Original problematic case, scaled down to smaller chunksize.
+ runCase({
+ docSize: 0,
+ stages: [
+ {numDocsToInsert: 10000, expectedNumChunks: 1},
+ {numDocsToInsert: 10, expectedNumChunks: 1},
+ {numDocsToInsert: 20, expectedNumChunks: 1},
+ {numDocsToInsert: 40, expectedNumChunks: 1},
+ {numDocsToInsert: 1000, expectedNumChunks: 1},
+ ],
+ });
+
+ // Docs just smaller than half chunk size.
+ runCase({
+ docSize: 510 * 1024,
+ stages: [
+ {numDocsToInsert: 10, expectedNumChunks: 6},
+ {numDocsToInsert: 10, expectedNumChunks: 12},
+ ],
+ });
+
+ // Docs just larger than half chunk size.
+ runCase({
+ docSize: 514 * 1024,
+ stages: [
+ {numDocsToInsert: 10, expectedNumChunks: 10},
+ {numDocsToInsert: 10, expectedNumChunks: 20},
+ ],
+ });
+
+ s.stop();
+})();
diff --git a/jstests/sharding/shard_identity_rollback.js b/jstests/sharding/shard_identity_rollback.js
index c7b6fedaacc..37cc8b10726 100644
--- a/jstests/sharding/shard_identity_rollback.js
+++ b/jstests/sharding/shard_identity_rollback.js
@@ -89,8 +89,9 @@
}
},
function() {
- var oldPriOplog = priConn.getDB('local').oplog.rs.find().sort({ts: -1}).toArray();
- var newPriOplog = newPriConn.getDB('local').oplog.rs.find().sort({ts: -1}).toArray();
+ var oldPriOplog = priConn.getDB('local').oplog.rs.find().sort({$natural: -1}).toArray();
+ var newPriOplog =
+ newPriConn.getDB('local').oplog.rs.find().sort({$natural: -1}).toArray();
return "timed out waiting for original primary to shut down after rollback. " +
"Old primary oplog: " + tojson(oldPriOplog) + "; new primary oplog: " +
tojson(newPriOplog);
diff --git a/jstests/sharding/write_cmd_auto_split.js b/jstests/sharding/write_cmd_auto_split.js
index 1cf9b5ab39a..95151b1e7e9 100644
--- a/jstests/sharding/write_cmd_auto_split.js
+++ b/jstests/sharding/write_cmd_auto_split.js
@@ -40,7 +40,7 @@
assert.eq(1, configDB.chunks.find().itcount());
- for (var x = 0; x < 1100; x++) {
+ for (var x = 0; x < 3100; x++) {
assert.writeOK(testDB.runCommand({
update: 'update',
updates: [{q: {x: x}, u: {x: x, v: doc1k}, upsert: true}],
@@ -80,7 +80,7 @@
// Note: Estimated 'chunk size' tracked by mongos is initialized with a random value so
// we are going to be conservative.
- for (var x = 0; x < 1100; x += 400) {
+ for (var x = 0; x < 3100; x += 400) {
var docs = [];
for (var y = 0; y < 400; y++) {
@@ -101,7 +101,7 @@
assert.eq(1, configDB.chunks.find().itcount());
- for (var x = 0; x < 1100; x += 400) {
+ for (var x = 0; x < 3100; x += 400) {
var docs = [];
for (var y = 0; y < 400; y++) {
diff --git a/rpm/init.d-mongod b/rpm/init.d-mongod
index 56539ef4d42..4e172b9f15c 100755
--- a/rpm/init.d-mongod
+++ b/rpm/init.d-mongod
@@ -64,6 +64,7 @@ start()
ulimit -n 64000
ulimit -m unlimited
ulimit -u 64000
+ ulimit -l unlimited
echo -n $"Starting mongod: "
daemon --user "$MONGO_USER" --check $mongod "$NUMACTL $mongod $OPTIONS >/dev/null 2>&1"
diff --git a/rpm/init.d-mongod.suse b/rpm/init.d-mongod.suse
index fae1fb8b3f3..2216507a009 100644
--- a/rpm/init.d-mongod.suse
+++ b/rpm/init.d-mongod.suse
@@ -65,6 +65,7 @@ start()
ulimit -n 64000
ulimit -m unlimited
ulimit -u 64000
+ ulimit -l unlimited
echo -n "Starting mongod: "
$NUMACTL /sbin/start_daemon -u "$MONGO_USER" -p "$PIDFILEPATH" $mongod $OPTIONS >/dev/null 2>&1
diff --git a/rpm/mongod.service b/rpm/mongod.service
index 708bf663614..e4d48d08c1e 100644
--- a/rpm/mongod.service
+++ b/rpm/mongod.service
@@ -13,6 +13,7 @@ ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod.pid
+Type=forking
# file size
LimitFSIZE=infinity
# cpu time
@@ -23,6 +24,8 @@ LimitAS=infinity
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
+# locked memory
+LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
diff --git a/site_scons/site_tools/jstoh.py b/site_scons/site_tools/jstoh.py
index 62424a6cd4f..dc90b324b28 100644
--- a/site_scons/site_tools/jstoh.py
+++ b/site_scons/site_tools/jstoh.py
@@ -8,8 +8,8 @@ def jsToHeader(target, source):
h = [
'#include "mongo/base/string_data.h"',
+ '#include "mongo/scripting/engine.h"',
'namespace mongo {',
- 'struct JSFile{ const char* name; const StringData& source; };',
'namespace JSFiles{',
]
@@ -21,7 +21,7 @@ def jsToHeader(target, source):
objname = os.path.split(filename)[1].split('.')[0]
stringname = '_jscode_raw_' + objname
- h.append('const char ' + stringname + "[] = {")
+ h.append('constexpr char ' + stringname + "[] = {")
with open(filename, 'r') as f:
for line in f:
@@ -30,8 +30,8 @@ def jsToHeader(target, source):
h.append("0};")
# symbols aren't exported w/o this
h.append('extern const JSFile %s;' % objname)
- h.append('const JSFile %s = { "%s", StringData(%s) };' %
- (objname, filename.replace('\\', '/'), stringname))
+ h.append('const JSFile %s = { "%s", StringData(%s, sizeof(%s) - 1) };' %
+ (objname, filename.replace('\\', '/'), stringname, stringname))
h.append("} // namespace JSFiles")
h.append("} // namespace mongo")
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 2cfb2060e7f..1208446d70f 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -341,6 +341,7 @@ env.Install(
LIBDEPS=[
'db/conn_pool_options',
'db/commands/core',
+ 'db/ftdc/ftdc_mongos',
'db/mongodandmongos',
's/client/sharding_connection_hook',
's/commands/cluster_commands',
@@ -409,12 +410,17 @@ if not has_option('noshell') and usemozjs:
mongo_shell = shellEnv.Program(
"mongo",
["shell/dbshell.cpp"] + env.WindowsResourceFile("shell/shell.rc"),
- LIBDEPS=["$BUILD_DIR/third_party/shim_pcrecpp",
- "shell_core",
- "db/server_options_core",
- "client/clientdriver",
- "$BUILD_DIR/mongo/util/password",
- ])
+ LIBDEPS=[
+ "$BUILD_DIR/third_party/shim_pcrecpp",
+ "shell_core",
+ "db/server_options_core",
+ "client/clientdriver",
+ "$BUILD_DIR/mongo/util/password",
+ ],
+ LIBDEPS_PRIVATE=[
+ "$BUILD_DIR/mongo/client/connection_string",
+ ]
+ )
shellEnv.Install( '#/', mongo_shell )
else:
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index c8def6bd695..98d41461fce 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -202,6 +202,12 @@ error_code("CannotBuildIndexKeys", 201)
error_code("NetworkInterfaceExceededTimeLimit", 202)
error_code("TooManyLocks", 208)
error_code("UpdateOperationFailed", 218)
+error_code("FTDCPathNotSet", 219)
+error_code("FTDCPathAlreadySet", 220)
+error_code("ProducerConsumerQueueBatchTooLarge", 247)
+error_code("ProducerConsumerQueueEndClosed", 248)
+
+# Error codes 4000-8999 are reserved.
# Non-sequential error codes (for compatibility only)
error_code("SocketException", 9001)
diff --git a/src/mongo/base/parse_number_test.cpp b/src/mongo/base/parse_number_test.cpp
index 0ed768a7dd3..76b1018af21 100644
--- a/src/mongo/base/parse_number_test.cpp
+++ b/src/mongo/base/parse_number_test.cpp
@@ -290,9 +290,9 @@ TEST(Double, TestParsingNormal) {
// not parseable by the Windows SDK libc or the Solaris libc in the mode we build.
// See SERVER-14131.
- ASSERT_PARSES(double, "0xff", 0xff);
- ASSERT_PARSES(double, "-0xff", -0xff);
- ASSERT_PARSES(double, "0xabcab.defdefP-10", 0xabcab.defdefP-10);
+ ASSERT_PARSES(double, "0xff", 255);
+ ASSERT_PARSES(double, "-0xff", -255);
+ ASSERT_PARSES(double, "0xabcab.defdefP-10", 687.16784283419838);
#endif
}
diff --git a/src/mongo/base/validate_locale.cpp b/src/mongo/base/validate_locale.cpp
index ffb7190c804..b51eac53edb 100644
--- a/src/mongo/base/validate_locale.cpp
+++ b/src/mongo/base/validate_locale.cpp
@@ -32,6 +32,7 @@
#include <locale>
#include "mongo/base/init.h"
+#include "mongo/util/mongoutils/str.h"
namespace mongo {
@@ -40,13 +41,15 @@ MONGO_INITIALIZER_GENERAL(ValidateLocale, MONGO_NO_PREREQUISITES, MONGO_DEFAULT_
try {
// Validate that boost can correctly load the user's locale
boost::filesystem::path("/").has_root_directory();
- } catch (const std::runtime_error&) {
- return Status(ErrorCodes::BadValue,
- "Invalid or no user locale set."
+ } catch (const std::runtime_error& e) {
+ return Status(
+ ErrorCodes::BadValue,
+ str::stream()
+ << "Invalid or no user locale set. "
#ifndef _WIN32
- " Please ensure LANG and/or LC_* environment variables are set correctly."
+ << " Please ensure LANG and/or LC_* environment variables are set correctly. "
#endif
- );
+ << e.what());
}
#ifdef _WIN32
diff --git a/src/mongo/bson/bson_comparator_interface_base.h b/src/mongo/bson/bson_comparator_interface_base.h
index f70bec7b872..29d80916f72 100644
--- a/src/mongo/bson/bson_comparator_interface_base.h
+++ b/src/mongo/bson/bson_comparator_interface_base.h
@@ -28,9 +28,11 @@
#pragma once
+#include <boost/container/flat_set.hpp>
#include <initializer_list>
#include <map>
#include <set>
+#include <vector>
#include "mongo/base/disallow_copying.h"
#include "mongo/base/string_data_comparator_interface.h"
@@ -51,6 +53,9 @@ class BSONComparatorInterfaceBase {
MONGO_DISALLOW_COPYING(BSONComparatorInterfaceBase);
public:
+ BSONComparatorInterfaceBase(BSONComparatorInterfaceBase&& other) = default;
+ BSONComparatorInterfaceBase& operator=(BSONComparatorInterfaceBase&& other) = default;
+
/**
* A deferred comparison between two objects of type T, which can be converted into a boolean
* via the evaluate() method.
@@ -122,6 +127,8 @@ public:
using Set = std::set<T, LessThan>;
+ using FlatSet = boost::container::flat_set<T, LessThan>;
+
using UnorderedSet = stdx::unordered_set<T, Hasher, EqualTo>;
template <typename ValueType>
@@ -207,6 +214,10 @@ protected:
return Set(init, LessThan(this));
}
+ FlatSet makeFlatSet(const std::vector<T>& elements) const {
+ return FlatSet(elements.begin(), elements.end(), LessThan(this));
+ }
+
UnorderedSet makeUnorderedSet(std::initializer_list<T> init = {}) const {
return UnorderedSet(init, 0, Hasher(this), EqualTo(this));
}
diff --git a/src/mongo/bson/bsonelement_comparator_interface.h b/src/mongo/bson/bsonelement_comparator_interface.h
index c6223a2d352..d69397a68db 100644
--- a/src/mongo/bson/bsonelement_comparator_interface.h
+++ b/src/mongo/bson/bsonelement_comparator_interface.h
@@ -33,6 +33,9 @@
namespace mongo {
+typedef std::set<BSONElement, BSONElementCmpWithoutField> BSONElementSet;
+typedef std::multiset<BSONElement, BSONElementCmpWithoutField> BSONElementMultiSet;
+
/**
* A BSONElement::ComparatorInterface is an abstract class for comparing BSONElement objects. Usage
* for comparing two BSON elements, 'lhs' and 'rhs', where 'comparator' is an instance of a class
@@ -61,6 +64,14 @@ public:
}
/**
+ * Constructs a BSONEltFlatSet whose equivalence classes are given by this comparator. This
+ * comparator must outlive the returned set.
+ */
+ FlatSet makeBSONEltFlatSet(const std::vector<BSONElement>& elements) const {
+ return makeFlatSet(elements);
+ }
+
+ /**
* Constructs a BSONEltUnorderedSet whose equivalence classes are given by this
* comparator. This comparator must outlive the returned set.
*/
@@ -91,6 +102,8 @@ public:
using BSONEltSet = BSONComparatorInterfaceBase<BSONElement>::Set;
+using BSONEltFlatSet = BSONComparatorInterfaceBase<BSONElement>::FlatSet;
+
using BSONEltUnorderedSet = BSONComparatorInterfaceBase<BSONElement>::UnorderedSet;
template <typename ValueType>
diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp
index d9af2c706a9..bc0045f5681 100644
--- a/src/mongo/bson/bsonobj.cpp
+++ b/src/mongo/bson/bsonobj.cpp
@@ -33,6 +33,7 @@
#include "mongo/base/data_range.h"
#include "mongo/bson/bson_validate.h"
+#include "mongo/bson/bsonelement_comparator_interface.h"
#include "mongo/db/json.h"
#include "mongo/util/allocator.h"
#include "mongo/util/hex.h"
diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h
index 22ab6ff182f..0036fea6518 100644
--- a/src/mongo/bson/bsonobj.h
+++ b/src/mongo/bson/bsonobj.h
@@ -42,7 +42,6 @@
#include "mongo/base/string_data_comparator_interface.h"
#include "mongo/bson/bson_comparator_interface_base.h"
#include "mongo/bson/bsonelement.h"
-#include "mongo/bson/bsonelement_comparator_interface.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/oid.h"
#include "mongo/bson/timestamp.h"
@@ -53,9 +52,6 @@
namespace mongo {
-typedef std::set<BSONElement, BSONElementCmpWithoutField> BSONElementSet;
-typedef std::multiset<BSONElement, BSONElementCmpWithoutField> BSONElementMSet;
-
/**
C++ representation of a "BSON" object -- that is, an extended JSON-style
object in a binary representation.
diff --git a/src/mongo/bson/util/builder.h b/src/mongo/bson/util/builder.h
index b959c59236e..ba6945e3fbc 100644
--- a/src/mongo/bson/util/builder.h
+++ b/src/mongo/bson/util/builder.h
@@ -320,15 +320,16 @@ private:
}
/* "slow" portion of 'grow()' */
void NOINLINE_DECL grow_reallocate(int minSize) {
+ if (minSize > BufferMaxSize) {
+ std::stringstream ss;
+ ss << "BufBuilder attempted to grow() to " << minSize << " bytes, past the 64MB limit.";
+ msgasserted(13548, ss.str().c_str());
+ }
+
int a = 64;
while (a < minSize)
a = a * 2;
- if (a > BufferMaxSize) {
- std::stringstream ss;
- ss << "BufBuilder attempted to grow() to " << a << " bytes, past the 64MB limit.";
- msgasserted(13548, ss.str().c_str());
- }
_buf.realloc(a);
size = a;
}
diff --git a/src/mongo/client/connection_string.h b/src/mongo/client/connection_string.h
index e8fc8eb2637..2e888b56dad 100644
--- a/src/mongo/client/connection_string.h
+++ b/src/mongo/client/connection_string.h
@@ -28,11 +28,13 @@
#pragma once
+#include <sstream>
#include <string>
#include <vector>
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
+#include "mongo/bson/util/builder.h"
#include "mongo/stdx/mutex.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/net/hostandport.h"
@@ -157,6 +159,10 @@ public:
return _string < other._string;
}
+
+ friend std::ostream& operator<<(std::ostream&, const ConnectionString&);
+ friend StringBuilder& operator<<(StringBuilder&, const ConnectionString&);
+
private:
/**
* Creates a SET connection string with the specified set name and servers.
@@ -179,4 +185,15 @@ private:
static stdx::mutex _connectHookMutex;
static ConnectionHook* _connectHook;
};
+
+inline std::ostream& operator<<(std::ostream& ss, const ConnectionString& cs) {
+ ss << cs._string;
+ return ss;
+}
+
+inline StringBuilder& operator<<(StringBuilder& sb, const ConnectionString& cs) {
+ sb << cs._string;
+ return sb;
+}
+
} // namespace mongo
diff --git a/src/mongo/client/fetcher.cpp b/src/mongo/client/fetcher.cpp
index 741efe9afab..4918bbf76c2 100644
--- a/src/mongo/client/fetcher.cpp
+++ b/src/mongo/client/fetcher.cpp
@@ -171,7 +171,8 @@ Fetcher::Fetcher(executor::TaskExecutor* executor,
const BSONObj& findCmdObj,
const CallbackFn& work,
const BSONObj& metadata,
- Milliseconds timeout,
+ Milliseconds findNetworkTimeout,
+ Milliseconds getMoreNetworkTimeout,
std::unique_ptr<RemoteCommandRetryScheduler::RetryPolicy> firstCommandRetryPolicy)
: _executor(executor),
_source(source),
@@ -179,10 +180,11 @@ Fetcher::Fetcher(executor::TaskExecutor* executor,
_cmdObj(findCmdObj.getOwned()),
_metadata(metadata.getOwned()),
_work(work),
- _timeout(timeout),
+ _findNetworkTimeout(findNetworkTimeout),
+ _getMoreNetworkTimeout(getMoreNetworkTimeout),
_firstRemoteCommandScheduler(
_executor,
- RemoteCommandRequest(_source, _dbname, _cmdObj, _metadata, nullptr, _timeout),
+ RemoteCommandRequest(_source, _dbname, _cmdObj, _metadata, nullptr, _findNetworkTimeout),
stdx::bind(&Fetcher::_callback, this, stdx::placeholders::_1, kFirstBatchFieldName),
std::move(firstCommandRetryPolicy)) {
uassert(ErrorCodes::BadValue, "callback function cannot be null", work);
@@ -204,10 +206,6 @@ BSONObj Fetcher::getMetadataObject() const {
return _metadata;
}
-Milliseconds Fetcher::getTimeout() const {
- return _timeout;
-}
-
std::string Fetcher::toString() const {
return getDiagnosticString();
}
@@ -221,7 +219,8 @@ std::string Fetcher::getDiagnosticString() const {
output << " query: " << _cmdObj;
output << " query metadata: " << _metadata;
output << " active: " << _isActive_inlock();
- output << " timeout: " << _timeout;
+ output << " findNetworkTimeout: " << _findNetworkTimeout;
+ output << " getMoreNetworkTimeout: " << _getMoreNetworkTimeout;
output << " shutting down?: " << _isShuttingDown_inlock();
output << " first: " << _first;
output << " firstCommandScheduler: " << _firstRemoteCommandScheduler.toString();
@@ -316,7 +315,8 @@ Status Fetcher::_scheduleGetMore(const BSONObj& cmdObj) {
}
StatusWith<executor::TaskExecutor::CallbackHandle> scheduleResult =
_executor->scheduleRemoteCommand(
- RemoteCommandRequest(_source, _dbname, cmdObj, _metadata, nullptr, _timeout),
+ RemoteCommandRequest(
+ _source, _dbname, cmdObj, _metadata, nullptr, _getMoreNetworkTimeout),
stdx::bind(&Fetcher::_callback, this, stdx::placeholders::_1, kNextBatchFieldName));
if (!scheduleResult.isOK()) {
diff --git a/src/mongo/client/fetcher.h b/src/mongo/client/fetcher.h
index 050ac50f249..3af4c6f329d 100644
--- a/src/mongo/client/fetcher.h
+++ b/src/mongo/client/fetcher.h
@@ -130,7 +130,8 @@ public:
const BSONObj& cmdObj,
const CallbackFn& work,
const BSONObj& metadata = rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- Milliseconds timeout = RemoteCommandRequest::kNoTimeout,
+ Milliseconds findNetworkTimeout = RemoteCommandRequest::kNoTimeout,
+ Milliseconds getMoreNetworkTimeout = RemoteCommandRequest::kNoTimeout,
std::unique_ptr<RemoteCommandRetryScheduler::RetryPolicy> firstCommandRetryPolicy =
RemoteCommandRetryScheduler::makeNoRetryPolicy());
@@ -152,11 +153,6 @@ public:
BSONObj getMetadataObject() const;
/**
- * Returns timeout for remote commands to complete.
- */
- Milliseconds getTimeout() const;
-
- /**
* Returns diagnostic information.
*/
std::string getDiagnosticString() const;
@@ -260,7 +256,8 @@ private:
executor::TaskExecutor::CallbackHandle _getMoreCallbackHandle;
// Socket timeout
- Milliseconds _timeout;
+ Milliseconds _findNetworkTimeout;
+ Milliseconds _getMoreNetworkTimeout;
// First remote command scheduler.
RemoteCommandRetryScheduler _firstRemoteCommandScheduler;
diff --git a/src/mongo/client/fetcher_test.cpp b/src/mongo/client/fetcher_test.cpp
index 00dae071d3a..66fb026c6b6 100644
--- a/src/mongo/client/fetcher_test.cpp
+++ b/src/mongo/client/fetcher_test.cpp
@@ -242,6 +242,7 @@ TEST_F(FetcherTest, InvalidConstruction) {
unreachableCallback,
rpc::makeEmptyMetadata(),
RemoteCommandRequest::kNoTimeout,
+ RemoteCommandRequest::kNoTimeout,
std::unique_ptr<RemoteCommandRetryScheduler::RetryPolicy>()),
UserException,
ErrorCodes::BadValue,
@@ -273,7 +274,6 @@ TEST_F(FetcherTest, RemoteCommandRequestShouldContainCommandParametersPassedToCo
ASSERT_EQUALS(source, fetcher->getSource());
ASSERT_BSONOBJ_EQ(findCmdObj, fetcher->getCommandObject());
ASSERT_BSONOBJ_EQ(metadataObj, fetcher->getMetadataObject());
- ASSERT_EQUALS(timeout, fetcher->getTimeout());
ASSERT_OK(fetcher->schedule());
@@ -284,6 +284,7 @@ TEST_F(FetcherTest, RemoteCommandRequestShouldContainCommandParametersPassedToCo
ASSERT_TRUE(net->hasReadyRequests());
auto noi = net->getNextReadyRequest();
request = noi->getRequest();
+ ASSERT_EQUALS(timeout, request.timeout);
}
ASSERT_EQUALS(source, request.target);
@@ -1048,6 +1049,7 @@ TEST_F(FetcherTest, FetcherAppliesRetryPolicyToFirstCommandButNotToGetMoreReques
makeCallback(),
rpc::makeEmptyMetadata(),
executor::RemoteCommandRequest::kNoTimeout,
+ executor::RemoteCommandRequest::kNoTimeout,
std::move(policy));
callbackHook = appendGetMoreRequest;
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp
index e737479d05b..e2356881290 100644
--- a/src/mongo/client/mongo_uri.cpp
+++ b/src/mongo/client/mongo_uri.cpp
@@ -32,131 +32,323 @@
#include "mongo/client/mongo_uri.h"
+#include <utility>
+
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/util/hex.h"
#include "mongo/util/mongoutils/str.h"
-#include "mongo/util/password_digest.h"
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/predicate.hpp>
-#include <boost/algorithm/string/split.hpp>
-#include <boost/regex.hpp>
+
+namespace {
+constexpr std::array<char, 16> hexits{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+const mongo::StringData kURIPrefix{"mongodb://"};
+}
+
+/**
+ * RFC 3986 Section 2.1 - Percent Encoding
+ *
+ * Encode data elements in a way which will allow them to be embedded
+ * into a mongodb:// URI safely.
+ */
+void mongo::uriEncode(std::ostream& ss, StringData toEncode, StringData passthrough) {
+ for (const auto& c : toEncode) {
+ if ((c == '-') || (c == '_') || (c == '.') || (c == '~') || isalnum(c) ||
+ (passthrough.find(c) != std::string::npos)) {
+ ss << c;
+ } else {
+ // Encoding anything not included in section 2.3 "Unreserved characters"
+ ss << '%' << hexits[(c >> 4) & 0xF] << hexits[c & 0xF];
+ }
+ }
+}
+
+mongo::StatusWith<std::string> mongo::uriDecode(StringData toDecode) {
+ StringBuilder out;
+ for (size_t i = 0; i < toDecode.size(); ++i) {
+ const auto c = toDecode[i];
+ if (c == '%') {
+ if (i + 2 > toDecode.size()) {
+ return Status(ErrorCodes::FailedToParse,
+ "Encountered partial escape sequence at end of string");
+ }
+ out << fromHex(toDecode.substr(i + 1, 2));
+ i += 2;
+ } else {
+ out << c;
+ }
+ }
+ return out.str();
+}
namespace mongo {
namespace {
-const char kMongoDBURL[] =
- // scheme: non-capturing
- "mongodb://"
- // credentials: two inner captures for user and password
- "(?:([^:]+)(?::([^@]+))?@)?"
+/**
+ * Helper Method for MongoURI::parse() to split a string into exactly 2 pieces by a char delimeter
+ */
+std::pair<StringData, StringData> partitionForward(StringData str, const char c) {
+ const auto delim = str.find(c);
+ if (delim == std::string::npos) {
+ return {str, StringData()};
+ }
+ return {str.substr(0, delim), str.substr(delim + 1)};
+}
- // servers: grabs all host:port or UNIX socket names
- "((?:[^\\/]+|/.+\\.sock)(?:,(?:[^\\/]+|/.+\\.sock))*)"
+/**
+ * Helper method for MongoURI::parse() to split a string into exactly 2 pieces by a char delimiter
+ * searching backward from the end of the string.
+ */
+std::pair<StringData, StringData> partitionBackward(StringData str, const char c) {
+ const auto delim = str.rfind(c);
+ if (delim == std::string::npos) {
+ return {StringData(), str};
+ }
+ return {str.substr(0, delim), str.substr(delim + 1)};
+}
- // database and options are grouped together
- "(?:/"
+/**
+ * Breakout method for parsing application/x-www-form-urlencoded option pairs
+ *
+ * foo=bar&baz=qux&...
+ */
+StatusWith<MongoURI::OptionsMap> parseOptions(StringData options, StringData url) {
+ MongoURI::OptionsMap ret;
+ if (options.empty()) {
+ return ret;
+ }
- // database: matches anything but the chars that cannot be part of a MongoDB database name which
- // are (in order) - forward slash, back slash, dot, space, double-quote, dollar sign, asterisk,
- // less than, greater than, colon, pipe, question mark.
- "([^/\\\\\\.\\ \"\\$*<>:\\|\\?]*)?"
+ if (options.find('?') != std::string::npos) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << "URI Cannot Contain multiple questions marks for mongodb:// URL: "
+ << url);
+ }
- // options
- "(?:\\?([^&=?]+=[^&=?]+(?:&[^&=?]+=[^&=?]+)*))?"
+ const auto optionsStr = options.toString();
+ for (auto i =
+ boost::make_split_iterator(optionsStr, boost::first_finder("&", boost::is_iequal()));
+ i != std::remove_reference<decltype((i))>::type{};
+ ++i) {
+ const auto opt = boost::copy_range<std::string>(*i);
+ if (opt.empty()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << "Missing a key/value pair in the options for mongodb:// URL: "
+ << url);
+ }
- // close db/options group
- ")?";
+ const auto kvPair = partitionForward(opt, '=');
+ const auto keyRaw = kvPair.first;
+ if (keyRaw.empty()) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream()
+ << "Missing a key for key/value pair in the options for mongodb:// URL: "
+ << url);
+ }
+ const auto key = uriDecode(keyRaw);
+ if (!key.isOK()) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "Key '" << keyRaw
+ << "' in options cannot properly be URL decoded for mongodb:// URL: "
+ << url);
+ }
+ const auto valRaw = kvPair.second;
+ if (valRaw.empty()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Missing value for key '" << keyRaw
+ << "' in the options for mongodb:// URL: "
+ << url);
+ }
+ const auto val = uriDecode(valRaw);
+ if (!val.isOK()) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "Value '" << valRaw << "' for key '" << keyRaw
+ << "' in options cannot properly be URL decoded for mongodb:// URL: "
+ << url);
+ }
-} // namespace
+ ret[key.getValue()] = val.getValue();
+ }
+
+ return ret;
+}
+} // namespace
StatusWith<MongoURI> MongoURI::parse(const std::string& url) {
- if (!boost::algorithm::starts_with(url, "mongodb://")) {
- auto cs_status = ConnectionString::parse(url);
+ const StringData urlSD(url);
+
+ // 1. Validate and remove the scheme prefix mongodb://
+ if (!urlSD.startsWith(kURIPrefix)) {
+ const auto cs_status = ConnectionString::parse(url);
if (!cs_status.isOK()) {
return cs_status.getStatus();
}
-
return MongoURI(cs_status.getValue());
}
+ const auto uriWithoutPrefix = urlSD.substr(kURIPrefix.size());
+
+ // 2. Split the string by the first, unescaped / (if any), yielding:
+ // split[0]: User information and host identifers
+ // split[1]: Auth database and connection options
+ const auto userAndDb = partitionForward(uriWithoutPrefix, '/');
+ const auto userAndHostInfo = userAndDb.first;
+ const auto databaseAndOptions = userAndDb.second;
+
+ // 2.b Make sure that there are no question marks in the left side of the /
+ // as any options after the ? must still have the / delimeter
+ if (databaseAndOptions.empty() && userAndHostInfo.find('?') != std::string::npos) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream()
+ << "URI must contain slash delimeter between hosts and options for mongodb:// URL: "
+ << url);
+ }
- const boost::regex mongoUrlRe(kMongoDBURL);
-
- boost::smatch matches;
- if (!boost::regex_match(url, matches, mongoUrlRe)) {
+ // 3. Split the user information and host identifiers string by the last, unescaped @, yielding:
+ // split[0]: User information
+ // split[1]: Host identifiers;
+ const auto userAndHost = partitionBackward(userAndHostInfo, '@');
+ const auto userInfo = userAndHost.first;
+ const auto hostIdentifiers = userAndHost.second;
+
+ // 4. Validate, split (if applicable), and URL decode the user information, yielding:
+ // split[0] = username
+ // split[1] = password
+ const auto userAndPass = partitionForward(userInfo, ':');
+ const auto usernameSD = userAndPass.first;
+ const auto passwordSD = userAndPass.second;
+
+ const auto containsColonOrAt = [](StringData str) {
+ return (str.find(':') != std::string::npos) || (str.find('@') != std::string::npos);
+ };
+
+ if (containsColonOrAt(usernameSD)) {
return Status(ErrorCodes::FailedToParse,
- str::stream() << "Failed to parse mongodb:// URL: " << url);
+ str::stream() << "Username must be URL Encoded for mongodb:// URL: " << url);
}
-
- // We have the whole input plus 5 top level captures (user, password, host, db, options).
- invariant(matches.size() == 6);
-
- if (!matches[3].matched) {
- return Status(ErrorCodes::FailedToParse, "No server(s) specified");
+ if (containsColonOrAt(passwordSD)) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Password must be URL Encoded for mongodb:// URL: " << url);
}
- std::map<std::string, std::string> options;
+ // Get the username and make sure it did not fail to decode
+ const auto usernameWithStatus = uriDecode(usernameSD);
+ if (!usernameWithStatus.isOK()) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "Username cannot properly be URL decoded for mongodb:// URL: " << url);
+ }
+ const auto username = usernameWithStatus.getValue();
+
+ // Get the password and make sure it did not fail to decode
+ const auto passwordWithStatus = uriDecode(passwordSD);
+ if (!passwordWithStatus.isOK())
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "Password cannot properly be URL decoded for mongodb:// URL: " << url);
+ const auto password = passwordWithStatus.getValue();
+
+ // 5. Validate, split, and URL decode the host identifiers.
+ const auto hostIdentifiersStr = hostIdentifiers.toString();
+ std::vector<HostAndPort> servers;
+ for (auto i = boost::make_split_iterator(hostIdentifiersStr,
+ boost::first_finder(",", boost::is_iequal()));
+ i != std::remove_reference<decltype((i))>::type{};
+ ++i) {
+ const auto hostWithStatus = uriDecode(boost::copy_range<std::string>(*i));
+ if (!hostWithStatus.isOK()) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "Host cannot properly be URL decoded for mongodb:// URL: " << url);
+ }
- if (matches[5].matched) {
- const std::string optionsMatch = matches[5].str();
+ const auto host = hostWithStatus.getValue();
+ if (host.empty()) {
+ continue;
+ }
- std::vector<boost::iterator_range<std::string::const_iterator>> optionsTokens;
- boost::algorithm::split(optionsTokens, optionsMatch, boost::algorithm::is_any_of("=&"));
+ if ((host.find('/') != std::string::npos) && !StringData(host).endsWith(".sock")) {
+ return Status(
+ ErrorCodes::FailedToParse,
+ str::stream() << "'" << host << "' in '" << url
+ << "' appears to be a unix socket, but does not end in '.sock'");
+ }
- if (optionsTokens.size() % 2 != 0) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "Missing a key or value in the options for mongodb:// URL: "
- << url);
- ;
+ const auto statusHostAndPort = HostAndPort::parse(host);
+ if (!statusHostAndPort.isOK()) {
+ return statusHostAndPort.getStatus();
}
+ servers.push_back(statusHostAndPort.getValue());
+ }
+ if (servers.empty()) {
+ return Status(ErrorCodes::FailedToParse, "No server(s) specified");
+ }
- for (size_t i = 0; i != optionsTokens.size(); i = i + 2)
- options[std::string(optionsTokens[i].begin(), optionsTokens[i].end())] =
- std::string(optionsTokens[i + 1].begin(), optionsTokens[i + 1].end());
+ // 6. Split the auth database and connection options string by the first, unescaped ?, yielding:
+ // split[0] = auth database
+ // split[1] = connection options
+ const auto dbAndOpts = partitionForward(databaseAndOptions, '?');
+ const auto databaseSD = dbAndOpts.first;
+ const auto connectionOptions = dbAndOpts.second;
+ const auto databaseWithStatus = uriDecode(databaseSD);
+ if (!databaseWithStatus.isOK()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << "Database name cannot properly be URL decoded for mongodb:// URL: "
+ << url);
+ }
+ const auto database = databaseWithStatus.getValue();
+
+ // 7. Validate the database contains no prohibited characters
+ // Prohibited characters:
+ // slash ("/"), backslash ("\"), space (" "), double-quote ("""), or dollar sign ("$")
+ // period (".") is also prohibited, but drivers MAY allow periods
+ if (!database.empty() &&
+ !NamespaceString::validDBName(database,
+ NamespaceString::DollarInDbNameBehavior::Disallow)) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << "Database name cannot have reserved characters for mongodb:// URL: "
+ << url);
}
- OptionsMap::const_iterator optIter;
+ // 8. Validate, split, and URL decode the connection options
+ const auto optsWith = parseOptions(connectionOptions, url);
+ if (!optsWith.isOK()) {
+ return optsWith.getStatus();
+ }
+ const auto options = optsWith.getValue();
// If a replica set option was specified, store it in the 'setName' field.
- bool haveSetName;
+ const auto optIter = options.find("replicaSet");
std::string setName;
- if ((haveSetName = ((optIter = options.find("replicaSet")) != options.end()))) {
+ if (optIter != options.end()) {
setName = optIter->second;
+ invariant(!setName.empty());
}
- std::vector<HostAndPort> servers;
-
- {
- std::vector<std::string> servers_split;
- const std::string serversStr = matches[3].str();
- boost::algorithm::split(servers_split, serversStr, boost::is_any_of(","));
- for (auto&& s : servers_split) {
- auto statusHostAndPort = HostAndPort::parse(s);
- if (!statusHostAndPort.isOK()) {
- return statusHostAndPort.getStatus();
- }
-
- servers.push_back(statusHostAndPort.getValue());
- }
- }
-
- const bool direct = !haveSetName && (servers.size() == 1);
-
- if (!direct && setName.empty()) {
+ if ((servers.size() > 1) && setName.empty()) {
return Status(ErrorCodes::FailedToParse,
"Cannot list multiple servers in URL without 'replicaSet' option");
}
ConnectionString cs(
- direct ? ConnectionString::MASTER : ConnectionString::SET, servers, setName);
- return MongoURI(
- std::move(cs), matches[1].str(), matches[2].str(), matches[4].str(), std::move(options));
+ setName.empty() ? ConnectionString::MASTER : ConnectionString::SET, servers, setName);
+ return MongoURI(std::move(cs), username, password, database, std::move(options));
}
} // namespace mongo
diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h
index 7b6364761d5..f7a7c4aeb81 100644
--- a/src/mongo/client/mongo_uri.h
+++ b/src/mongo/client/mongo_uri.h
@@ -29,12 +29,14 @@
#pragma once
#include <map>
+#include <sstream>
#include <string>
#include <vector>
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/util/builder.h"
#include "mongo/client/connection_string.h"
#include "mongo/stdx/mutex.h"
#include "mongo/util/assert_util.h"
@@ -43,12 +45,38 @@
namespace mongo {
/**
+ * Encode a string for embedding in a URI.
+ * Replaces reserved bytes with %xx sequences.
+ *
+ * Optionally allows passthrough characters to remain unescaped.
+ */
+void uriEncode(std::ostream& ss, StringData str, StringData passthrough = ""_sd);
+inline std::string uriEncode(StringData str, StringData passthrough = ""_sd) {
+ std::ostringstream ss;
+ uriEncode(ss, str, passthrough);
+ return ss.str();
+}
+
+/**
+ * Decode a URI encoded string.
+ * Replaces + and %xx sequences with their original byte.
+ */
+StatusWith<std::string> uriDecode(StringData str);
+
+/**
* MongoURI handles parsing of URIs for mongodb, and falls back to old-style
* ConnectionString parsing. It's used primarily by the shell.
* It parses URIs with the following format:
*
* mongodb://[usr:pwd@]host1[:port1]...[,hostN[:portN]]][/[db][?options]]
*
+ * While this format is generally RFC 3986 compliant, some exceptions do exist:
+ * 1. The 'host' field, as defined by section 3.2.2 is expanded in the following ways:
+ * a. Multiple hosts may be specified as a comma separated list.
+ * b. Hosts may take the form of absolute paths for unix domain sockets.
+ * i. Sockets must end in the suffix '.sock'
+ * 2. The 'fragment' field, as defined by section 3.5 is not permitted.
+ *
* For a complete list of URI string options, see
* https://wiki.mongodb.com/display/DH/Connection+String+Format
*
@@ -125,6 +153,9 @@ public:
MongoURI() = default;
+ friend std::ostream& operator<<(std::ostream&, const MongoURI&);
+ friend StringBuilder& operator<<(StringBuilder&, const MongoURI&);
+
private:
MongoURI(ConnectionString connectString,
const std::string& user,
@@ -146,4 +177,14 @@ private:
OptionsMap _options;
};
+inline std::ostream& operator<<(std::ostream& ss, const MongoURI& uri) {
+ ss << uri._connectString;
+ return ss;
+}
+
+inline StringBuilder& operator<<(StringBuilder& sb, const MongoURI& uri) {
+ sb << uri._connectString;
+ return sb;
+}
+
} // namespace mongo
diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp
index 6a5f6c9882a..c32b1c0b5c5 100644
--- a/src/mongo/client/mongo_uri_test.cpp
+++ b/src/mongo/client/mongo_uri_test.cpp
@@ -28,11 +28,17 @@
#include "mongo/platform/basic.h"
-#include "mongo/client/mongo_uri.h"
+#include <fstream>
#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsontypes.h"
+#include "mongo/bson/json.h"
+#include "mongo/client/mongo_uri.h"
#include "mongo/unittest/unittest.h"
+#include <boost/filesystem/operations.hpp>
+
namespace {
using mongo::MongoURI;
@@ -60,16 +66,77 @@ const URITestCase validCases[] = {
{"mongodb://user@127.0.0.1", "user", "", kMaster, "", 1, 0, ""},
- {"mongodb://127.0.0.1/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
-
{"mongodb://localhost/?foo=bar", "", "", kMaster, "", 1, 1, ""},
+ {"mongodb://localhost,/?foo=bar", "", "", kMaster, "", 1, 1, ""},
+
{"mongodb://user:pwd@127.0.0.1:1234", "user", "pwd", kMaster, "", 1, 0, ""},
{"mongodb://user@127.0.0.1:1234", "user", "", kMaster, "", 1, 0, ""},
{"mongodb://127.0.0.1:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
+ {"mongodb://127.0.0.1/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
+
+ {"mongodb://user:pwd@127.0.0.1,/dbName?foo=a&c=b", "user", "pwd", kMaster, "", 1, 2, "dbName"},
+
+ {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=replName",
+ "user",
+ "pwd",
+ kSet,
+ "replName",
+ 2,
+ 2,
+ "dbname"},
+
+ {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/"
+ "dbname?a=b&replicaSet=replName",
+ "needs encoding%#!<>",
+ "pwd",
+ kSet,
+ "replName",
+ 2,
+ 2,
+ "dbname"},
+
+ {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/"
+ "db@name?a=b&replicaSet=replName",
+ "needs encoding%#!<>",
+ "pwd",
+ kSet,
+ "replName",
+ 2,
+ 2,
+ "db@name"},
+
+ {"mongodb://user:needs%20encoding%25%23!%3C%3E@127.0.0.1,127.0.0.2/"
+ "dbname?a=b&replicaSet=replName",
+ "user",
+ "needs encoding%#!<>",
+ kSet,
+ "replName",
+ 2,
+ 2,
+ "dbname"},
+
+ {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=needs%20encoding%25%23!%3C%3E",
+ "user",
+ "pwd",
+ kSet,
+ "needs encoding%#!<>",
+ 2,
+ 2,
+ "dbname"},
+
+ {"mongodb://user:pwd@127.0.0.1,127.0.0.2/needsencoding%40hello?a=b&replicaSet=replName",
+ "user",
+ "pwd",
+ kSet,
+ "replName",
+ 2,
+ 2,
+ "needsencoding@hello"},
+
{"mongodb://user:pwd@127.0.0.1,127.0.0.2/?replicaSet=replName",
"user",
"pwd",
@@ -259,21 +326,27 @@ const URITestCase validCases[] = {
1,
2,
""},
- {"mongodb:///tmp/mongodb-27017.sock", "", "", kMaster, "", 1, 0, ""},
- {"mongodb:///tmp/mongodb-27017.sock,/tmp/mongodb-27018.sock/?replicaSet=replName",
+ {"mongodb://%2Ftmp%2Fmongodb-27017.sock", "", "", kMaster, "", 1, 0, ""},
+
+ {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replName",
"",
"",
kSet,
"replName",
2,
1,
- ""}};
+ ""},
+};
const InvalidURITestCase invalidCases[] = {
// No host.
{"mongodb://"},
+ {"mongodb://usr:pwd@/dbname?a=b"},
+
+ // Username and password must be encoded (cannot have ':' or '@')
+ {"mongodb://usr:pwd:@127.0.0.1/dbName?foo=a&c=b"},
// Needs a "/" after the hosts and before the options.
{"mongodb://localhost:27017,localhost:27018?replicaSet=missingSlash"},
@@ -282,32 +355,109 @@ const InvalidURITestCase invalidCases[] = {
{"mongodb://localhost:27017localhost:27018"},
// Domain sockets have to end in ".sock".
- {"mongodb:///notareal/domainsock"},
+ {"mongodb://%2Fnotareal%2Fdomainsock"},
+
+ // Database name cannot contain slash ("/"), backslash ("\"), space (" "), double-quote ("""),
+ // or dollar sign ("$")
+ {"mongodb://usr:pwd@localhost:27017/db$name?a=b"},
+ {"mongodb://usr:pwd@localhost:27017/db/name?a=b"},
+ {"mongodb://usr:pwd@localhost:27017/db\\name?a=b"},
+ {"mongodb://usr:pwd@localhost:27017/db name?a=b"},
+ {"mongodb://usr:pwd@localhost:27017/db\"name?a=b"},
+
+ // Options must have a key
+ {"mongodb://usr:pwd@localhost:27017/dbname?=b"},
+
+ // Cannot skip a key value pair
+ {"mongodb://usr:pwd@localhost:27017/dbname?a=b&&b=c"},
+
+ // Multiple Unix domain sockets and auth DB resembling a socket (relative path)
+ {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.sock?replicaSet=replName"},
+
+ // Multiple Unix domain sockets with auth DB resembling a path (relative path)
+ {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.shoe?replicaSet=replName"},
+
+ // Multiple Unix domain sockets and auth DB resembling a socket (absolute path)
+ {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/"
+ "admin.sock?replicaSet=replName"},
+
+ // Multiple Unix domain sockets with auth DB resembling a path (absolute path)
+ {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/"
+ "admin.shoe?replicaSet=replName"},
+
+ // Missing value in key value pair for options
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d"},
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d="},
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&h=&c=b&d=6"},
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&h&c=b&d=6"},
+
+ // Missing a hostname, or unparsable hostname(s)
+ {"mongodb://,/dbName"},
+ {"mongodb://user:pwd@,/dbName"},
+ {"mongodb://localhost:1234:5678/dbName"},
// Options can't have multiple question marks. Only one.
{"mongodb://localhost:27017/?foo=a?c=b&d=e?asdf=foo"},
+
+ // Missing a key in key value pair for options
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&=d&c=b"},
+
+ // Missing an entire key-value pair
+ {"mongodb://127.0.0.1:1234/dbName?foo=a&&c=b"},
};
+// Helper Method to take a filename for a json file and return the array of tests inside of it
+mongo::BSONObj getBsonFromJsonFile(std::string fileName) {
+ boost::filesystem::path directoryPath = boost::filesystem::current_path();
+ boost::filesystem::path filePath(directoryPath / "src" / "mongo" / "client" /
+ "mongo_uri_tests" / fileName);
+ std::string filename(filePath.string());
+ std::ifstream infile(filename.c_str());
+ std::string data((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>());
+ if (data.empty()) {
+ // v3.4 test infra is unable to see these extra files.
+ return mongo::BSONObj();
+ }
+ mongo::BSONObj obj = mongo::fromjson(data);
+ ASSERT_TRUE(obj.valid(mongo::BSONVersion::kLatest));
+ ASSERT_TRUE(obj.hasField("tests"));
+ mongo::BSONObj arr = obj.getField("tests").embeddedObject().getOwned();
+ ASSERT_TRUE(arr.couldBeArray());
+ return arr;
+}
+
+// Helper method to take a BSONElement and either extract its string or return an empty string
+std::string returnStringFromElementOrNull(mongo::BSONElement element) {
+ ASSERT_TRUE(!element.eoo());
+ if (element.type() == mongo::jstNULL) {
+ return std::string();
+ }
+ ASSERT_EQ(element.type(), mongo::String);
+ return element.String();
+}
+
+// Helper method to take a valid test case, parse() it, and assure the output is correct
+void testValidURIFormat(URITestCase testCase) {
+ mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
+ std::string errMsg;
+ const auto cs_status = MongoURI::parse(testCase.URI);
+ ASSERT_OK(cs_status);
+ auto result = cs_status.getValue();
+ ASSERT_EQ(testCase.uname, result.getUser());
+ ASSERT_EQ(testCase.password, result.getPassword());
+ ASSERT_EQ(testCase.type, result.type());
+ ASSERT_EQ(testCase.setname, result.getSetName());
+ ASSERT_EQ(testCase.numservers, result.getServers().size());
+ ASSERT_EQ(testCase.numOptions, result.getOptions().size());
+ ASSERT_EQ(testCase.database, result.getDatabase());
+}
+
TEST(MongoURI, GoodTrickyURIs) {
const size_t numCases = sizeof(validCases) / sizeof(validCases[0]);
for (size_t i = 0; i != numCases; ++i) {
const URITestCase testCase = validCases[i];
- mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
- std::string errMsg;
- auto cs_status = MongoURI::parse(testCase.URI);
- if (!cs_status.getStatus().toString().empty()) {
- mongo::unittest::log() << "error with uri: " << cs_status.getStatus().toString();
- }
- ASSERT_TRUE(cs_status.isOK());
- auto result = cs_status.getValue();
- ASSERT_EQ(testCase.uname, result.getUser());
- ASSERT_EQ(testCase.password, result.getPassword());
- ASSERT_EQ(testCase.type, result.type());
- ASSERT_EQ(testCase.setname, result.getSetName());
- ASSERT_EQ(testCase.numservers, result.getServers().size());
- ASSERT_EQ(testCase.numOptions, result.getOptions().size());
- ASSERT_EQ(testCase.database, result.getDatabase());
+ testValidURIFormat(testCase);
}
}
@@ -318,7 +468,7 @@ TEST(MongoURI, InvalidURIs) {
const InvalidURITestCase testCase = invalidCases[i];
mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
auto cs_status = MongoURI::parse(testCase.URI);
- ASSERT_FALSE(cs_status.isOK());
+ ASSERT_NOT_OK(cs_status);
}
}
@@ -357,4 +507,100 @@ TEST(MongoURI, CloneURIForServer) {
ASSERT_EQ(clonedURIOptions.at("ssl"), "true");
}
+/**
+ * These tests come from the Mongo Uri Specifications for the drivers found at:
+ * https://github.com/mongodb/specifications/tree/master/source/connection-string/tests
+ * They have been slighly altered as the Drivers specification is slighly different
+ * from the server specification.
+ */
+TEST(MongoURI, specTests) {
+ const std::string files[] = {
+ "mongo-uri-valid-auth.json",
+ "mongo-uri-options.json",
+ "mongo-uri-unix-sockets-absolute.json",
+ "mongo-uri-unix-sockets-relative.json",
+ "mongo-uri-warnings.json",
+ "mongo-uri-host-identifiers.json",
+ "mongo-uri-invalid.json",
+ };
+
+ for (const auto& file : files) {
+ const auto testBson = getBsonFromJsonFile(file);
+
+ for (const auto& testElement : testBson) {
+ ASSERT_EQ(testElement.type(), mongo::Object);
+ const auto test = testElement.Obj();
+
+ // First extract the valid field and the uri field
+ const auto validDoc = test.getField("valid");
+ ASSERT_FALSE(validDoc.eoo());
+ ASSERT_TRUE(validDoc.isBoolean());
+ const auto valid = validDoc.Bool();
+
+ const auto uriDoc = test.getField("uri");
+ ASSERT_FALSE(uriDoc.eoo());
+ ASSERT_EQ(uriDoc.type(), mongo::String);
+ const auto uri = uriDoc.String();
+
+ if (!valid) {
+ // This uri string is invalid --> parse the uri and ensure it fails
+ const InvalidURITestCase testCase = {uri};
+ mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
+ auto cs_status = MongoURI::parse(testCase.URI);
+ ASSERT_NOT_OK(cs_status);
+ } else {
+ // This uri is valid -- > parse the remaining necessary fields
+
+ // parse the auth options
+ std::string database, username, password;
+
+ const auto auth = test.getField("auth");
+ ASSERT_FALSE(auth.eoo());
+ if (auth.type() != mongo::jstNULL) {
+ ASSERT_EQ(auth.type(), mongo::Object);
+ const auto authObj = auth.embeddedObject();
+ database = returnStringFromElementOrNull(authObj.getField("db"));
+ username = returnStringFromElementOrNull(authObj.getField("username"));
+ password = returnStringFromElementOrNull(authObj.getField("password"));
+ }
+
+ // parse the hosts
+ const auto hosts = test.getField("hosts");
+ ASSERT_FALSE(hosts.eoo());
+ ASSERT_EQ(hosts.type(), mongo::Array);
+ const auto numHosts = static_cast<size_t>(hosts.Obj().nFields());
+
+ // parse the options
+ mongo::ConnectionString::ConnectionType connectionType = kMaster;
+ size_t numOptions = 0;
+ std::string setName;
+ const auto optionsElement = test.getField("options");
+ ASSERT_FALSE(optionsElement.eoo());
+ if (optionsElement.type() != mongo::jstNULL) {
+ ASSERT_EQ(optionsElement.type(), mongo::Object);
+ const auto optionsObj = optionsElement.Obj();
+ numOptions = optionsObj.nFields();
+ const auto replsetElement = optionsObj.getField("replicaSet");
+ if (!replsetElement.eoo()) {
+ ASSERT_EQ(replsetElement.type(), mongo::String);
+ setName = replsetElement.String();
+ connectionType = kSet;
+ }
+ }
+
+ // Create the URITestCase abnd
+ const URITestCase testCase = {uri,
+ username,
+ password,
+ connectionType,
+ setName,
+ numHosts,
+ numOptions,
+ database};
+ testValidURIFormat(testCase);
+ }
+ }
+ }
+}
+
} // namespace
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json
new file mode 100644
index 00000000000..8a1fda580a8
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json
@@ -0,0 +1,203 @@
+{
+ "tests": [
+ {
+ "description": "Single IPv4 host without port",
+ "uri": "mongodb://127.0.0.1",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ipv4",
+ "host": "127.0.0.1",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single IPv4 host with port",
+ "uri": "mongodb://127.0.0.1:27018",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ipv4",
+ "host": "127.0.0.1",
+ "port": 27018
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single IP literal host without port",
+ "uri": "mongodb://[::1]",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ip_literal",
+ "host": "::1",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single IP literal host with port",
+ "uri": "mongodb://[::1]:27019",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ip_literal",
+ "host": "::1",
+ "port": 27019
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single hostname without port",
+ "uri": "mongodb://example.com",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single hostname with port",
+ "uri": "mongodb://example.com:27020",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": 27020
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Single hostname (resembling IPv4) without port",
+ "uri": "mongodb://256.0.0.1",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "256.0.0.1",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Multiple hosts (mixed formats)",
+ "uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019",
+ "valid": false,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ipv4",
+ "host": "127.0.0.1",
+ "port": null
+ },
+ {
+ "type": "ip_literal",
+ "host": "::1",
+ "port": 27018
+ },
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": 27019
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "Multiple hosts (mixed formats)",
+ "uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019/?replicaSet=replset",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "ipv4",
+ "host": "127.0.0.1",
+ "port": null
+ },
+ {
+ "type": "ip_literal",
+ "host": "::1",
+ "port": 27018
+ },
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": 27019
+ }
+ ],
+ "auth": null,
+ "options": {
+ "replicaSet": "replset"
+ }
+ },
+ {
+ "description": "UTF-8 hosts",
+ "uri": "mongodb://bücher.example.com,umläut.example.com/",
+ "valid": false,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "bücher.example.com",
+ "port": null
+ },
+ {
+ "type": "hostname",
+ "host": "umläut.example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "UTF-8 hosts",
+ "uri": "mongodb://bücher.example.com,umläut.example.com/?replicaSet=replset",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "bücher.example.com",
+ "port": null
+ },
+ {
+ "type": "hostname",
+ "host": "umläut.example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": {
+ "replicaSet": "replset"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json b/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json
new file mode 100644
index 00000000000..3ee82bd535e
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json
@@ -0,0 +1,247 @@
+{
+ "tests": [
+ {
+ "auth": null,
+ "description": "Empty string",
+ "hosts": null,
+ "options": null,
+ "uri": "",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Missing host",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Double colon in host identifier",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost::27017",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Double colon in host identifier and trailing slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost::27017/",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Double colon in host identifier with missing host and port",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://::",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Double colon in host identifier with missing port",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost,localhost::",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Double colon in host identifier and second host",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost::27017,abc",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (negative number) with hostname",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost:-1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (zero) with hostname",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost:0/",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (positive number) with hostname",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost:65536",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (positive number) with hostname and trailing slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost:65536/",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (non-numeric string) with hostname",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://localhost:foo",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (negative number) with IP literal",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://[::1]:-1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (zero) with IP literal",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://[::1]:0/",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (positive number) with IP literal",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://[::1]:65536",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (positive number) with IP literal and trailing slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://[::1]:65536/",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Invalid port (non-numeric string) with IP literal",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://[::1]:foo",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Missing delimiting slash between hosts and options",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://example.com?w=1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Incomplete key value pair for option",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://example.com/?w",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username with password containing an unescaped colon",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice:foo:bar@127.0.0.1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username with password containing an unescaped colon",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice:foo:bar@127.0.0.1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username containing an unescaped at-sign",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice@@127.0.0.1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username with password containing an unescaped at-sign",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice@foo:bar@127.0.0.1",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username containing an unescaped slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice/@localhost/db",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username containing unescaped slash with password",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice/bob:foo@localhost/db",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Username with password containing an unescaped slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb://alice:foo/bar@localhost/db",
+ "valid": false,
+ "warning": null
+ },
+ {
+ "auth": null,
+ "description": "Host with unescaped slash",
+ "hosts": null,
+ "options": null,
+ "uri": "mongodb:///tmp/mongodb-27017.sock/",
+ "valid": false,
+ "warning": null
+ }
+ ]
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-options.json b/src/mongo/client/mongo_uri_tests/mongo-uri-options.json
new file mode 100644
index 00000000000..4c2bded9e72
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-options.json
@@ -0,0 +1,25 @@
+{
+ "tests": [
+ {
+ "description": "Option names are normalized to lowercase",
+ "uri": "mongodb://alice:secret@example.com/admin?AUTHMechanism=MONGODB-CR",
+ "valid": true,
+ "warning": false,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": null
+ }
+ ],
+ "auth": {
+ "username": "alice",
+ "password": "secret",
+ "db": "admin"
+ },
+ "options": {
+ "authmechanism": "MONGODB-CR"
+ }
+ }
+ ]
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json
new file mode 100644
index 00000000000..d167c315bbc
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json
@@ -0,0 +1,373 @@
+{
+ "tests": [
+ {
+ "auth": null,
+ "description": "Unix domain socket (absolute path with trailing slash)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock/",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket (absolute path without trailing slash)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket (absolute path with spaces in path)",
+ "hosts": [
+ {
+ "host": "/tmp/ /mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (absolute paths)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (absolute path and ipv4)",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": 27017,
+ "type": "ipv4"
+ },
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (absolute path and hostname resembling relative path)",
+ "hosts": [
+ {
+ "host": "mongodb-27017.sock",
+ "port": null,
+ "type": "hostname"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (absolute paths)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (absolute path and ipv4)",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": 27017,
+ "type": "ipv4"
+ },
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (absolute path and hostname resembling relative path)",
+ "hosts": [
+ {
+ "host": "mongodb-27017.sock",
+ "port": null,
+ "type": "hostname"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "foo",
+ "username": "alice"
+ },
+ "description": "Unix domain socket with auth database (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:foo@%2Ftmp%2Fmongodb-27017.sock/admin",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket with path resembling socket file (absolute path with trailing slash)",
+ "hosts": [
+ {
+ "host": "/tmp/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket with path resembling socket file (absolute path without trailing slash)",
+ "hosts": [
+ {
+ "host": "/tmp/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Unix domain socket with path resembling socket file and auth (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://bob:bar@%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/admin",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets and auth DB (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets and auth DB (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1
+ },
+ "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1,
+ "replicaSet":"replset"
+ },
+ "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (absolute path)",
+ "hosts": [
+ {
+ "host": "/tmp/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1,
+ "replicaSet":"replset"
+ },
+ "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?replicaSet=replset&w=1",
+ "valid": true,
+ "warning": false
+ }
+ ]
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json
new file mode 100644
index 00000000000..8da45c4e1b9
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json
@@ -0,0 +1,466 @@
+{
+ "tests": [
+ {
+ "auth": null,
+ "description": "Unix domain socket (relative path with trailing slash)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock/",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket (relative path without trailing slash)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket (relative path with spaces)",
+ "hosts": [
+ {
+ "host": "rel/ /mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2F %2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (relative paths)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (relative paths)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (relative and absolute paths)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple Unix domain sockets (relative and absolute paths)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "/tmp/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (relative path and ipv4)",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": 27017,
+ "type": "ipv4"
+ },
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (relative path and ipv4)",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": 27017,
+ "type": "ipv4"
+ },
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (relative path and hostname resembling relative path)",
+ "hosts": [
+ {
+ "host": "mongodb-27017.sock",
+ "port": null,
+ "type": "hostname"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Multiple hosts (relative path and hostname resembling relative path)",
+ "hosts": [
+ {
+ "host": "mongodb-27017.sock",
+ "port": null,
+ "type": "hostname"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock/?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "foo",
+ "username": "alice"
+ },
+ "description": "Unix domain socket with auth database (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:foo@rel%2Fmongodb-27017.sock/admin",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket with path resembling socket file (relative path with trailing slash)",
+ "hosts": [
+ {
+ "host": "rel/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock/",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": null,
+ "description": "Unix domain socket with path resembling socket file (relative path without trailing slash)",
+ "hosts": [
+ {
+ "host": "rel/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Unix domain socket with path resembling socket file and auth (relative path)",
+ "hosts": [
+ {
+ "host": "rel/path.to.sock/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://bob:bar@rel%2Fpath.to.sock%2Fmongodb-27017.sock/admin",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets and auth DB resembling a socket (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets and auth DB resembling a socket (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets with auth DB resembling a path (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": null,
+ "username": null
+ },
+ "description": "Multiple Unix domain sockets with auth DB resembling a path (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1
+ },
+ "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1,
+ "replicaSet": "replset"
+ },
+ "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "Multiple Unix domain sockets with auth and query string (relative path)",
+ "hosts": [
+ {
+ "host": "rel/mongodb-27017.sock",
+ "port": null,
+ "type": "unix"
+ },
+ {
+ "host": "rel/mongodb-27018.sock",
+ "port": null,
+ "type": "unix"
+ }
+ ],
+ "options": {
+ "w": 1,
+ "replicaSet": "replset",
+ "b": 4
+ },
+ "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset&b=4",
+ "valid": true,
+ "warning": false
+ }
+ ]
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json
new file mode 100644
index 00000000000..a44236e7b62
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json
@@ -0,0 +1,311 @@
+{
+ "tests": [
+ {
+ "auth": {
+ "db": null,
+ "password": "foo",
+ "username": "alice"
+ },
+ "description": "User info for single IPv4 host without database",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:foo@127.0.0.1",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "test",
+ "password": "foo",
+ "username": "alice"
+ },
+ "description": "User info for single IPv4 host with database",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:foo@127.0.0.1/test",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "User info for single IP literal host without database",
+ "hosts": [
+ {
+ "host": "::1",
+ "port": 27018,
+ "type": "ip_literal"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://bob:bar@[::1]:27018",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "bar",
+ "username": "bob"
+ },
+ "description": "User info for single IP literal host with database",
+ "hosts": [
+ {
+ "host": "::1",
+ "port": 27018,
+ "type": "ip_literal"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://bob:bar@[::1]:27018/admin",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": "baz",
+ "username": "eve"
+ },
+ "description": "User info for single hostname without database",
+ "hosts": [
+ {
+ "host": "example.com",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://eve:baz@example.com",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "db2",
+ "password": "baz",
+ "username": "eve"
+ },
+ "description": "User info for single hostname with database",
+ "hosts": [
+ {
+ "host": "example.com",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://eve:baz@example.com/db2",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": "secret",
+ "username": "alice"
+ },
+ "description": "User info for multiple hosts without database",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ },
+ {
+ "host": "example.com",
+ "port": 27018,
+ "type": "hostname"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:secret@127.0.0.1,example.com:27018",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "secret",
+ "username": "alice"
+ },
+ "description": "User info for multiple hosts with database",
+ "hosts": [
+ {
+ "host": "example.com",
+ "port": null,
+ "type": "hostname"
+ },
+ {
+ "host": "::1",
+ "port": 27019,
+ "type": "ip_literal"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:secret@example.com,[::1]:27019/admin",
+ "valid": false,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": null,
+ "username": "alice"
+ },
+ "description": "Username without password",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice@127.0.0.1",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": "",
+ "username": "alice"
+ },
+ "description": "Username with empty password",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://alice:@127.0.0.1",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "my=db",
+ "password": null,
+ "username": "@l:ce/="
+ },
+ "description": "Escaped username and database without password",
+ "hosts": [
+ {
+ "host": "example.com",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": null,
+ "uri": "mongodb://%40l%3Ace%2F%3D@example.com/my%3Ddb",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin=",
+ "password": "fizzb@zz=",
+ "username": "$am"
+ },
+ "description": "Escaped user info and database (MONGODB-CR)",
+ "hosts": [
+ {
+ "host": "127.0.0.1",
+ "port": null,
+ "type": "ipv4"
+ }
+ ],
+ "options": {
+ "authmechanism": "MONGODB-CR"
+ },
+ "uri": "mongodb://%24am:fizzb%40zz%3D@127.0.0.1/admin%3D?authMechanism=MONGODB-CR",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": null,
+ "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry"
+ },
+ "description": "Escaped username (MONGODB-X509)",
+ "hosts": [
+ {
+ "host": "localhost",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": {
+ "authmechanism": "MONGODB-X509"
+ },
+ "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": null,
+ "password": "secret",
+ "username": "user@EXAMPLE.COM"
+ },
+ "description": "Escaped username (GSSAPI)",
+ "hosts": [
+ {
+ "host": "localhost",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": {
+ "authmechanism": "GSSAPI",
+ "authmechanismproperties": {
+ "CANONICALIZE_HOST_NAME": true,
+ "SERVICE_NAME": "other"
+ }
+ },
+ "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI",
+ "valid": true,
+ "warning": false
+ },
+ {
+ "auth": {
+ "db": "admin",
+ "password": "secret",
+ "username": "alice"
+ },
+ "description": "At-signs in options aren't part of the userinfo",
+ "hosts": [
+ {
+ "host": "example.com",
+ "port": null,
+ "type": "hostname"
+ }
+ ],
+ "options": {
+ "replicaSet": "my@replicaset"
+ },
+ "uri": "mongodb://alice:secret@example.com/admin?replicaSet=my@replicaset",
+ "valid": true,
+ "warning": false
+ }
+ ]
+}
diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json b/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json
new file mode 100644
index 00000000000..a04246847ab
--- /dev/null
+++ b/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json
@@ -0,0 +1,56 @@
+{
+ "tests": [
+ {
+ "description": "Unrecognized option keys are ignored",
+ "uri": "mongodb://example.com/?foo=bar",
+ "valid": true,
+ "warning": true,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": {
+ "foo": "bar"
+ }
+ },
+ {
+ "description": "Unsupported option values are ignored",
+ "uri": "mongodb://example.com/?fsync=ifPossible",
+ "valid": true,
+ "warning": true,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": {
+ "fsync": "ifPossible"
+ }
+ },
+ {
+ "description": "Deprecated (or unknown) options are ignored if replacement exists",
+ "uri": "mongodb://example.com/?wtimeout=5&wtimeoutMS=10",
+ "valid": true,
+ "warning": true,
+ "hosts": [
+ {
+ "type": "hostname",
+ "host": "example.com",
+ "port": null
+ }
+ ],
+ "auth": null,
+ "options": {
+ "wtimeoutMS": 10,
+ "wtimeout": 5
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/mongo/crypto/mechanism_scram.cpp b/src/mongo/crypto/mechanism_scram.cpp
index 8cd59142d67..6a2757d383d 100644
--- a/src/mongo/crypto/mechanism_scram.cpp
+++ b/src/mongo/crypto/mechanism_scram.cpp
@@ -226,6 +226,10 @@ bool verifyClientProof(StringData clientProof, StringData storedKey, StringData
SHA1Block computedStoredKey =
SHA1Block::computeHash(clientSignature.data(), clientSignature.size());
+ if (storedKey.size() != computedStoredKey.size()) {
+ return false;
+ }
+
return consttimeMemEqual(reinterpret_cast<const unsigned char*>(storedKey.rawData()),
computedStoredKey.data(),
computedStoredKey.size());
diff --git a/src/mongo/crypto/mechanism_scram.h b/src/mongo/crypto/mechanism_scram.h
index b12cebbf2e1..6045f037078 100644
--- a/src/mongo/crypto/mechanism_scram.h
+++ b/src/mongo/crypto/mechanism_scram.h
@@ -53,15 +53,22 @@ const std::string serverKeyFieldName = "serverKey";
*/
struct SCRAMPresecrets {
SCRAMPresecrets(std::string hashedPassword,
- std::vector<std::uint8_t> salt,
- size_t iterationCount)
+ std::vector<std::uint8_t> salt_,
+ size_t iterationCount_)
: hashedPassword(std::move(hashedPassword)),
- salt(std::move(salt)),
- iterationCount(iterationCount) {}
+ salt(std::move(salt_)),
+ iterationCount(iterationCount_) {
+ uassert(ErrorCodes::BadValue,
+ "Invalid salt for SCRAM mechanism",
+ salt.size() >= kSaltLengthMin);
+ }
std::string hashedPassword;
std::vector<std::uint8_t> salt;
size_t iterationCount;
+
+private:
+ static const size_t kSaltLengthMin = 16;
};
inline bool operator==(const SCRAMPresecrets& lhs, const SCRAMPresecrets& rhs) {
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index e2be152f126..ded6480875c 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -445,6 +445,7 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/db/serveronly',
'$BUILD_DIR/mongo/util/options_parser/options_parser_init',
+ 'repl/replica_set_messages',
'server_options',
],
)
@@ -766,9 +767,12 @@ env.Library(
'log_process_details.cpp',
],
LIBDEPS=[
+ 'repl/repl_coordinator_global',
+ 'repl/repl_coordinator_interface',
+ 'repl/replica_set_messages',
+ 'server_options',
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/util/net/network',
- 'server_options',
],
)
@@ -818,3 +822,4 @@ asioEnv.CppIntegrationTest(
'$BUILD_DIR/mongo/executor/network_interface_asio_fixture',
],
)
+
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index 792314035b5..fc85149de7f 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -138,6 +138,7 @@ env.Library('authmongod',
],
LIBDEPS=[
'authservercommon',
+ '$BUILD_DIR/mongo/db/catalog/index_key_validate',
'$BUILD_DIR/mongo/db/db_raii',
'$BUILD_DIR/mongo/db/dbdirectclient',
'$BUILD_DIR/mongo/db/dbhelpers',
diff --git a/src/mongo/db/auth/auth_index_d.cpp b/src/mongo/db/auth/auth_index_d.cpp
index 427900c8377..47987ac40c4 100644
--- a/src/mongo/db/auth/auth_index_d.cpp
+++ b/src/mongo/db/auth/auth_index_d.cpp
@@ -34,15 +34,20 @@
#include "mongo/base/init.h"
#include "mongo/base/status.h"
+#include "mongo/client/index_spec.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/index_catalog.h"
+#include "mongo/db/catalog/index_create.h"
+#include "mongo/db/catalog/index_key_validate.h"
#include "mongo/db/client.h"
-#include "mongo/db/client.h"
+#include "mongo/db/concurrency/write_conflict_exception.h"
+#include "mongo/db/curop.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/storage/storage_options.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/log.h"
@@ -58,6 +63,8 @@ BSONObj v3SystemUsersKeyPattern;
BSONObj v3SystemRolesKeyPattern;
std::string v3SystemUsersIndexName;
std::string v3SystemRolesIndexName;
+IndexSpec v3SystemUsersIndexSpec;
+IndexSpec v3SystemRolesIndexSpec;
MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) {
v1SystemUsersKeyPattern = BSON("user" << 1 << "userSource" << 1);
@@ -76,13 +83,66 @@ MONGO_INITIALIZER(AuthIndexKeyPatterns)(InitializerContext*) {
<< AuthorizationManager::ROLE_DB_FIELD_NAME
<< "_1");
+ v3SystemUsersIndexSpec.addKeys(v3SystemUsersKeyPattern);
+ v3SystemUsersIndexSpec.unique();
+ v3SystemUsersIndexSpec.name(v3SystemUsersIndexName);
+
+ v3SystemRolesIndexSpec.addKeys(v3SystemRolesKeyPattern);
+ v3SystemRolesIndexSpec.unique();
+ v3SystemRolesIndexSpec.name(v3SystemRolesIndexName);
+
return Status::OK();
}
+void generateSystemIndexForExistingCollection(OperationContext* opCtx,
+ Collection* collection,
+ const NamespaceString& ns,
+ const IndexSpec& spec) {
+ // Do not try and generate any system indexes in read only mode.
+ if (storageGlobalParams.readOnly) {
+ warning() << "Running in queryable backup mode. Unable to create authorization index on "
+ << ns;
+ return;
+ }
+
+ try {
+ auto indexSpecStatus = index_key_validate::validateIndexSpec(
+ spec.toBSON(), ns, serverGlobalParams.featureCompatibility);
+ BSONObj indexSpec = fassertStatusOK(40452, indexSpecStatus);
+
+ log() << "No authorization index detected on " << ns
+ << " collection. Attempting to recover by creating an index with spec: " << indexSpec;
+
+ MultiIndexBlock indexer(opCtx, collection);
+
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
+ fassertStatusOK(40453, indexer.init(indexSpec));
+ }
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_END(opCtx, "authorization index regeneration", ns.ns());
+
+ fassertStatusOK(40454, indexer.insertAllDocumentsInCollection());
+
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
+ WriteUnitOfWork wunit(opCtx);
+
+ indexer.commit();
+
+ wunit.commit();
+ }
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_END(opCtx, "authorization index regeneration", ns.ns());
+
+ log() << "Authorization index construction on " << ns << " is complete";
+ } catch (const DBException& e) {
+ severe() << "Failed to regenerate index for " << ns << ". Exception: " << e.what();
+ throw;
+ }
+}
+
} // namespace
Status verifySystemIndexes(OperationContext* txn) {
- const NamespaceString systemUsers = AuthorizationManager::usersCollectionNamespace;
+ const NamespaceString& systemUsers = AuthorizationManager::usersCollectionNamespace;
+ const NamespaceString& systemRoles = AuthorizationManager::rolesCollectionNamespace;
// Make sure the old unique index from v2.4 on system.users doesn't exist.
ScopedTransaction scopedXact(txn, MODE_IX);
@@ -91,23 +151,54 @@ Status verifySystemIndexes(OperationContext* txn) {
return Status::OK();
}
- Collection* collection = autoDb.getDb()->getCollection(NamespaceString(systemUsers));
- if (!collection) {
- return Status::OK();
+ Collection* collection = autoDb.getDb()->getCollection(systemUsers);
+ if (collection) {
+ IndexCatalog* indexCatalog = collection->getIndexCatalog();
+ invariant(indexCatalog);
+
+ // Make sure the old unique index from v2.4 on system.users doesn't exist.
+ std::vector<IndexDescriptor*> indexes;
+ indexCatalog->findIndexesByKeyPattern(txn, v1SystemUsersKeyPattern, false, &indexes);
+
+ if (!indexes.empty()) {
+ fassert(ErrorCodes::AmbiguousIndexKeyPattern, indexes.size() == 1);
+ return Status(ErrorCodes::AuthSchemaIncompatible,
+ "Old 2.4 style user index identified. "
+ "The authentication schema needs to be updated by "
+ "running authSchemaUpgrade on a 2.6 server.");
+ }
+
+ // Ensure that system indexes exist for the user collection
+ indexCatalog->findIndexesByKeyPattern(txn, v3SystemUsersKeyPattern, false, &indexes);
+ if (indexes.empty()) {
+ try {
+ generateSystemIndexForExistingCollection(
+ txn, collection, systemUsers, v3SystemUsersIndexSpec);
+ } catch (...) {
+ return exceptionToStatus();
+ }
+ }
}
- IndexCatalog* indexCatalog = collection->getIndexCatalog();
- std::vector<IndexDescriptor*> indexes;
- indexCatalog->findIndexesByKeyPattern(txn, v1SystemUsersKeyPattern, false, &indexes);
-
- if (indexCatalog && !indexes.empty()) {
- fassert(ErrorCodes::AmbiguousIndexKeyPattern, indexes.size() == 1);
- return Status(ErrorCodes::AuthSchemaIncompatible,
- "Old 2.4 style user index identified. "
- "The authentication schema needs to be updated by "
- "running authSchemaUpgrade on a 2.6 server.");
+ // Ensure that system indexes exist for the roles collection, if it exists.
+ collection = autoDb.getDb()->getCollection(systemRoles);
+ if (collection) {
+ IndexCatalog* indexCatalog = collection->getIndexCatalog();
+ invariant(indexCatalog);
+
+ std::vector<IndexDescriptor*> indexes;
+ indexCatalog->findIndexesByKeyPattern(txn, v3SystemRolesKeyPattern, false, &indexes);
+ if (indexes.empty()) {
+ try {
+ generateSystemIndexForExistingCollection(
+ txn, collection, systemRoles, v3SystemRolesIndexSpec);
+ } catch (...) {
+ return exceptionToStatus();
+ }
+ }
}
+
return Status::OK();
}
@@ -115,19 +206,21 @@ void createSystemIndexes(OperationContext* txn, Collection* collection) {
invariant(collection);
const NamespaceString& ns = collection->ns();
if (ns == AuthorizationManager::usersCollectionNamespace) {
- collection->getIndexCatalog()->createIndexOnEmptyCollection(
- txn,
- BSON("name" << v3SystemUsersIndexName << "ns" << collection->ns().ns() << "key"
- << v3SystemUsersKeyPattern
- << "unique"
- << true));
+ auto indexSpec = fassertStatusOK(
+ 40455,
+ index_key_validate::validateIndexSpec(
+ v3SystemUsersIndexSpec.toBSON(), ns, serverGlobalParams.featureCompatibility));
+
+ fassertStatusOK(
+ 40456, collection->getIndexCatalog()->createIndexOnEmptyCollection(txn, indexSpec));
} else if (ns == AuthorizationManager::rolesCollectionNamespace) {
- collection->getIndexCatalog()->createIndexOnEmptyCollection(
- txn,
- BSON("name" << v3SystemRolesIndexName << "ns" << collection->ns().ns() << "key"
- << v3SystemRolesKeyPattern
- << "unique"
- << true));
+ auto indexSpec = fassertStatusOK(
+ 40457,
+ index_key_validate::validateIndexSpec(
+ v3SystemRolesIndexSpec.toBSON(), ns, serverGlobalParams.featureCompatibility));
+
+ fassertStatusOK(
+ 40458, collection->getIndexCatalog()->createIndexOnEmptyCollection(txn, indexSpec));
}
}
diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp
index f48ab9b8953..900d0b818fc 100644
--- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp
+++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp
@@ -198,6 +198,12 @@ StatusWith<bool> SaslSCRAMSHA1ServerConversation::_firstStep(std::vector<string>
_creds.scram.serverKey = scramCreds[scram::serverKeyFieldName].String();
}
+ if (!_creds.scram.isValid()) {
+ return Status(ErrorCodes::AuthenticationFailed,
+ "Unable to perform SCRAM-SHA-1 authentication for a user with missing "
+ "or invalid SCRAM credentials");
+ }
+
// Generate server-first-message
// Create text-based nonce as base64 encoding of a binary blob of length multiple of 3
const int nonceLenQWords = 3;
@@ -280,6 +286,7 @@ StatusWith<bool> SaslSCRAMSHA1ServerConversation::_secondStep(const std::vector<
// ClientSignature := HMAC(StoredKey, AuthMessage)
// ClientKey := ClientSignature XOR ClientProof
// ServerSignature := HMAC(ServerKey, AuthMessage)
+ invariant(_creds.scram.isValid());
if (!scram::verifyClientProof(
base64::decode(clientProof), base64::decode(_creds.scram.storedKey), _authMessage)) {
diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h
index d4aea7e442b..cdef6e52ddd 100644
--- a/src/mongo/db/auth/user.h
+++ b/src/mongo/db/auth/user.h
@@ -66,6 +66,17 @@ public:
std::string salt;
std::string serverKey;
std::string storedKey;
+
+ bool isValid() const {
+ // 160bit -> 20octets -> * 4/3 -> 26.667 -> padded to 28
+ const size_t kEncodedSHA1Length = 28;
+ // 128bit -> 16octets -> * 4/3 -> 21.333 -> padded to 24
+ const size_t kEncodedSaltLength = 24;
+
+ return (salt.size() == kEncodedSaltLength) &&
+ (serverKey.size() == kEncodedSHA1Length) &&
+ (storedKey.size() == kEncodedSHA1Length);
+ }
};
struct CredentialData {
CredentialData() : password(""), scram(), isExternal(false) {}
diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp
index 273eaff86f5..9ab688750d8 100644
--- a/src/mongo/db/auth/user_document_parser_test.cpp
+++ b/src/mongo/db/auth/user_document_parser_test.cpp
@@ -434,6 +434,7 @@ TEST_F(V2UserDocumentParsing, V2CredentialExtraction) {
<< BSON("MONGODB-CR"
<< "a"))));
ASSERT(user->getCredentials().password == "a");
+ ASSERT(!user->getCredentials().scram.isValid());
ASSERT(!user->getCredentials().isExternal);
// Credentials are {external:true if users's db is $external
@@ -446,6 +447,7 @@ TEST_F(V2UserDocumentParsing, V2CredentialExtraction) {
<< "credentials"
<< BSON("external" << true))));
ASSERT(user->getCredentials().password.empty());
+ ASSERT(!user->getCredentials().scram.isValid());
ASSERT(user->getCredentials().isExternal);
}
diff --git a/src/mongo/db/bson/dotted_path_support.cpp b/src/mongo/db/bson/dotted_path_support.cpp
index 1a9fda88501..31a92a5c54c 100644
--- a/src/mongo/db/bson/dotted_path_support.cpp
+++ b/src/mongo/db/bson/dotted_path_support.cpp
@@ -176,7 +176,7 @@ void extractAllElementsAlongPath(const BSONObj& obj,
void extractAllElementsAlongPath(const BSONObj& obj,
StringData path,
- BSONElementMSet& elements,
+ BSONElementMultiSet& elements,
bool expandArrayOnTrailingField,
std::set<size_t>* arrayComponents) {
const size_t initialDepth = 0;
diff --git a/src/mongo/db/bson/dotted_path_support.h b/src/mongo/db/bson/dotted_path_support.h
index c9c6676c75a..0a3d072c56a 100644
--- a/src/mongo/db/bson/dotted_path_support.h
+++ b/src/mongo/db/bson/dotted_path_support.h
@@ -31,6 +31,7 @@
#include <cstddef>
#include <set>
+#include "mongo/bson/bsonelement_comparator_interface.h"
#include "mongo/bson/bsonobj.h"
namespace mongo {
@@ -104,7 +105,7 @@ void extractAllElementsAlongPath(const BSONObj& obj,
void extractAllElementsAlongPath(const BSONObj& obj,
StringData path,
- BSONElementMSet& elements,
+ BSONElementMultiSet& elements,
bool expandArrayOnTrailingField = true,
std::set<std::size_t>* arrayComponents = nullptr);
diff --git a/src/mongo/db/catalog/apply_ops.cpp b/src/mongo/db/catalog/apply_ops.cpp
index 672a37a10c7..ec5c7681f0e 100644
--- a/src/mongo/db/catalog/apply_ops.cpp
+++ b/src/mongo/db/catalog/apply_ops.cpp
@@ -32,6 +32,7 @@
#include "mongo/db/catalog/apply_ops.h"
+#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database.h"
#include "mongo/db/catalog/database_holder.h"
@@ -42,21 +43,31 @@
#include "mongo/db/curop.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
+#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/matcher/extensions_callback_disallow_extensions.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/db/op_observer.h"
#include "mongo/db/operation_context.h"
+#include "mongo/db/query/collation/collation_spec.h"
#include "mongo/db/repl/oplog.h"
#include "mongo/db/repl/replication_coordinator_global.h"
#include "mongo/db/service_context.h"
+#include "mongo/rpc/get_status_from_command_result.h"
+#include "mongo/util/fail_point_service.h"
#include "mongo/util/log.h"
namespace mongo {
namespace {
+
+const auto kPreconditionFieldName = "preCondition"_sd;
+
+// If enabled, causes loop in _applyOps() to hang after applying current operation.
+MONGO_FP_DECLARE(applyOpsPauseBetweenOperations);
+
/**
* Return true iff the applyOpsCmd can be executed in a single WriteUnitOfWork.
*/
-bool canBeAtomic(const BSONObj& applyOpCmd) {
+bool _areOpsCrudOnly(const BSONObj& applyOpCmd) {
for (const auto& elem : applyOpCmd.firstElement().Obj()) {
const char* names[] = {"ns", "op"};
BSONElement fields[2];
@@ -89,14 +100,11 @@ bool canBeAtomic(const BSONObj& applyOpCmd) {
return true;
}
-Status _applyOps(OperationContext* txn,
+Status _applyOps(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& applyOpCmd,
BSONObjBuilder* result,
int* numApplied) {
- dassert(txn->lockState()->isLockHeldForMode(
- ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_GLOBAL), MODE_X));
-
BSONObj ops = applyOpCmd.firstElement().Obj();
// apply
@@ -107,148 +115,169 @@ Status _applyOps(OperationContext* txn,
BSONArrayBuilder ab;
const bool alwaysUpsert =
applyOpCmd.hasField("alwaysUpsert") ? applyOpCmd["alwaysUpsert"].trueValue() : true;
- const bool haveWrappingWUOW = txn->lockState()->inAWriteUnitOfWork();
-
- {
- repl::UnreplicatedWritesBlock uwb(txn);
+ const bool haveWrappingWUOW = opCtx->lockState()->inAWriteUnitOfWork();
- while (i.more()) {
- BSONElement e = i.next();
- const BSONObj& opObj = e.Obj();
+ while (i.more()) {
+ BSONElement e = i.next();
+ const BSONObj& opObj = e.Obj();
- // Ignore 'n' operations.
- const char* opType = opObj["op"].valuestrsafe();
- if (*opType == 'n')
- continue;
+ // Ignore 'n' operations.
+ const char* opType = opObj["op"].valuestrsafe();
+ if (*opType == 'n')
+ continue;
- const std::string ns = opObj["ns"].String();
+ const NamespaceString nss(opObj["ns"].String());
- // Need to check this here, or OldClientContext may fail an invariant.
- if (*opType != 'c' && !NamespaceString(ns).isValid())
- return {ErrorCodes::InvalidNamespace, "invalid ns: " + ns};
+ // Need to check this here, or OldClientContext may fail an invariant.
+ if (*opType != 'c' && !nss.isValid())
+ return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()};
- Status status(ErrorCodes::InternalError, "");
+ Status status(ErrorCodes::InternalError, "");
- if (haveWrappingWUOW) {
- invariant(*opType != 'c');
+ if (haveWrappingWUOW) {
+ invariant(opCtx->lockState()->isW());
+ invariant(*opType != 'c');
+ auto db = dbHolder().get(opCtx, nss.ns());
+ if (!db) {
+ throw DBException(
+ "cannot create a database in atomic applyOps mode; will retry without "
+ "atomicity",
+ ErrorCodes::NamespaceNotFound);
+ }
- if (!dbHolder().get(txn, ns)) {
- throw DBException(
- "cannot create a database in atomic applyOps mode; will retry without "
- "atomicity",
- ErrorCodes::NamespaceNotFound);
- }
+ // When processing an update on a non-existent collection, applyOperation_inlock()
+ // returns UpdateOperationFailed on updates and allows the collection to be
+ // implicitly created on upserts. We detect both cases here and fail early with
+ // NamespaceNotFound.
+ auto collection = db->getCollection(nss);
+ if (!collection && !nss.isSystemDotIndexes() && (*opType == 'i' || *opType == 'u')) {
+ throw DBException(str::stream() << "cannot apply insert or update operation on "
+ "a non-existent namespace "
+ << nss.ns()
+ << ": "
+ << redact(opObj),
+ ErrorCodes::NamespaceNotFound);
+ }
- OldClientContext ctx(txn, ns);
- status = repl::applyOperation_inlock(txn, ctx.db(), opObj, alwaysUpsert);
- if (!status.isOK())
- return status;
- logOpForDbHash(txn, ns.c_str());
- } else {
- try {
- // Run operations under a nested lock as a hack to prevent yielding.
- //
- // The list of operations is supposed to be applied atomically; yielding
- // would break atomicity by allowing an interruption or a shutdown to occur
- // after only some operations are applied. We are already locked globally
- // at this point, so taking a DBLock on the namespace creates a nested lock,
- // and yields are disallowed for operations that hold a nested lock.
- //
- // We do not have a wrapping WriteUnitOfWork so it is possible for a journal
- // commit to happen with a subset of ops applied.
- Lock::GlobalWrite globalWriteLockDisallowTempRelease(txn->lockState());
-
- // Ensures that yielding will not happen (see the comment above).
- DEV {
- Locker::LockSnapshot lockSnapshot;
- invariant(!txn->lockState()->saveLockStateAndUnlock(&lockSnapshot));
- };
-
- MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
- if (*opType == 'c') {
- status = repl::applyCommand_inlock(txn, opObj, true);
+ OldClientContext ctx(opCtx, nss.ns());
+ status = repl::applyOperation_inlock(opCtx, ctx.db(), opObj, alwaysUpsert);
+ if (!status.isOK())
+ return status;
+ logOpForDbHash(opCtx, nss.ns().c_str());
+ } else {
+ try {
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
+ if (*opType == 'c') {
+ invariant(opCtx->lockState()->isW());
+ uassertStatusOK(status = repl::applyCommand_inlock(opCtx, opObj, true));
+ } else {
+ const char* names[] = {"o", "ns"};
+ BSONElement fields[2];
+ opObj.getFields(2, names, fields);
+ BSONElement& fieldO = fields[0];
+ BSONElement& fieldNs = fields[1];
+ const StringData ns = fieldNs.valueStringData();
+ NamespaceString requestNss{ns};
+
+ if (nss.isSystemDotIndexes()) {
+ BSONObj indexSpec;
+ NamespaceString indexNss;
+ std::tie(indexSpec, indexNss) =
+ repl::prepForApplyOpsIndexInsert(fieldO, opObj, requestNss);
+ if (!indexSpec["collation"]) {
+ // If the index spec does not include a collation, explicitly
+ // specify the simple collation, so the index does not inherit the
+ // collection default collation.
+ auto indexVersion = indexSpec["v"];
+ // The index version is populated by prepForApplyOpsIndexInsert().
+ invariant(indexVersion);
+ if (indexVersion.isNumber() &&
+ (indexVersion.numberInt() >=
+ static_cast<int>(IndexDescriptor::IndexVersion::kV2))) {
+ BSONObjBuilder bob;
+ bob.append("collation", CollationSpec::kSimpleSpec);
+ bob.appendElements(indexSpec);
+ indexSpec = bob.obj();
+ }
+ }
+ BSONObjBuilder command;
+ command.append("createIndexes", indexNss.coll());
+ {
+ BSONArrayBuilder indexes(command.subarrayStart("indexes"));
+ indexes.append(indexSpec);
+ indexes.doneFast();
+ }
+ const BSONObj commandObj = command.done();
+
+ DBDirectClient client(opCtx);
+ BSONObj infoObj;
+ client.runCommand(nsToDatabase(ns), commandObj, infoObj);
+ status = getStatusFromCommandResult(infoObj);
} else {
- OldClientContext ctx(txn, ns);
-
- status =
- repl::applyOperation_inlock(txn, ctx.db(), opObj, alwaysUpsert);
+ AutoGetCollection autoColl(opCtx, nss, MODE_IX);
+ if (!autoColl.getCollection() && !nss.isSystemDotIndexes()) {
+ // For idempotency reasons, return success on delete operations.
+ if (*opType == 'd') {
+ status = Status::OK();
+ } else {
+ throw DBException(
+ str::stream()
+ << "cannot apply insert or update operation on"
+ " a non-existent namespace "
+ << nss.ns()
+ << ": "
+ << mongo::redact(opObj),
+ ErrorCodes::NamespaceNotFound);
+ }
+ } else {
+ OldClientContext ctx(opCtx, nss.ns());
+ status = repl::applyOperation_inlock(
+ opCtx, ctx.db(), opObj, alwaysUpsert);
+ }
}
}
- MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "applyOps", ns);
- } catch (const DBException& ex) {
- ab.append(false);
- result->append("applied", ++(*numApplied));
- result->append("code", ex.getCode());
- result->append("codeName",
- ErrorCodes::errorString(ErrorCodes::fromInt(ex.getCode())));
- result->append("errmsg", ex.what());
- result->append("results", ab.arr());
- return Status(ErrorCodes::UnknownError, ex.what());
}
- WriteUnitOfWork wuow(txn);
- logOpForDbHash(txn, ns.c_str());
- wuow.commit();
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_END(opCtx, "applyOps", nss.ns());
+ } catch (const DBException& ex) {
+ ab.append(false);
+ result->append("applied", ++(*numApplied));
+ result->append("code", ex.getCode());
+ result->append("codeName",
+ ErrorCodes::errorString(ErrorCodes::fromInt(ex.getCode())));
+ result->append("errmsg", ex.what());
+ result->append("results", ab.arr());
+ return ex.toStatus();
}
-
- ab.append(status.isOK());
- if (!status.isOK()) {
- log() << "applyOps error applying: " << status;
- errors++;
- }
-
- (*numApplied)++;
+ WriteUnitOfWork wuow(opCtx);
+ logOpForDbHash(opCtx, nss.ns().c_str());
+ wuow.commit();
}
- result->append("applied", *numApplied);
- result->append("results", ab.arr());
- } // set replicatedWrites back to original value
-
- if (txn->writesAreReplicated()) {
- // We want this applied atomically on slaves
- // so we re-wrap without the pre-condition for speed
-
- std::string tempNS = str::stream() << dbName << ".$cmd";
-
- // TODO: possibly use mutable BSON to remove preCondition field
- // once it is available
- BSONObjBuilder cmdBuilder;
-
- for (auto elem : applyOpCmd) {
- auto name = elem.fieldNameStringData();
- if (name == "preCondition")
- continue;
- if (name == "bypassDocumentValidation")
- continue;
- cmdBuilder.append(elem);
+ ab.append(status.isOK());
+ if (!status.isOK()) {
+ log() << "applyOps error applying: " << status;
+ errors++;
}
- const BSONObj cmdRewritten = cmdBuilder.done();
-
- auto opObserver = getGlobalServiceContext()->getOpObserver();
- invariant(opObserver);
- if (haveWrappingWUOW) {
- opObserver->onApplyOps(txn, tempNS, cmdRewritten);
- } else {
- // When executing applyOps outside of a wrapping WriteUnitOfWOrk, always logOp the
- // command regardless of whether the individial ops succeeded and rely on any
- // failures to also on secondaries. This isn't perfect, but it's what the command
- // has always done and is part of its "correct" behavior.
- while (true) {
- try {
- WriteUnitOfWork wunit(txn);
- opObserver->onApplyOps(txn, tempNS, cmdRewritten);
-
- wunit.commit();
- break;
- } catch (const WriteConflictException& wce) {
- LOG(2) << "WriteConflictException while logging applyOps command, retrying.";
- txn->recoveryUnit()->abandonSnapshot();
- continue;
- }
+ (*numApplied)++;
+
+ if (MONGO_FAIL_POINT(applyOpsPauseBetweenOperations)) {
+ // While holding a database lock under MMAPv1, we would be implicitly holding the
+ // flush lock here. This would prevent other threads from acquiring the global
+ // lock or any database locks. We release all locks temporarily while the fail
+ // point is enabled to allow other threads to make progress.
+ boost::optional<Lock::TempRelease> release;
+ auto storageEngine = opCtx->getServiceContext()->getGlobalStorageEngine();
+ if (storageEngine->isMmapV1() && !opCtx->lockState()->isW()) {
+ release.emplace(opCtx->lockState());
}
+ MONGO_FAIL_POINT_PAUSE_WHILE_SET(applyOpsPauseBetweenOperations);
}
}
+ result->append("applied", *numApplied);
+ result->append("results", ab.arr());
+
if (errors != 0) {
return Status(ErrorCodes::UnknownError, "applyOps had one or more errors applying ops");
}
@@ -256,94 +285,144 @@ Status _applyOps(OperationContext* txn,
return Status::OK();
}
-Status preconditionOK(OperationContext* txn, const BSONObj& applyOpCmd, BSONObjBuilder* result) {
- dassert(txn->lockState()->isLockHeldForMode(
- ResourceId(RESOURCE_GLOBAL, ResourceId::SINGLETON_GLOBAL), MODE_X));
-
- if (applyOpCmd["preCondition"].type() == Array) {
- BSONObjIterator i(applyOpCmd["preCondition"].Obj());
- while (i.more()) {
- BSONObj preCondition = i.next().Obj();
- if (preCondition["ns"].type() != BSONType::String) {
- return {ErrorCodes::InvalidNamespace,
- str::stream() << "ns in preCondition must be a string, but found type: "
- << typeName(preCondition["ns"].type())};
- }
- const NamespaceString nss(preCondition["ns"].valueStringData());
- if (!nss.isValid()) {
- return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()};
- }
+bool _hasPrecondition(const BSONObj& applyOpCmd) {
+ return applyOpCmd[kPreconditionFieldName].type() == Array;
+}
- DBDirectClient db(txn);
- BSONObj realres = db.findOne(nss.ns(), preCondition["q"].Obj());
+Status _checkPrecondition(OperationContext* opCtx,
+ const BSONObj& applyOpCmd,
+ BSONObjBuilder* result) {
+ invariant(opCtx->lockState()->isW());
+ invariant(_hasPrecondition(applyOpCmd));
+
+ for (auto elem : applyOpCmd[kPreconditionFieldName].Obj()) {
+ auto preCondition = elem.Obj();
+ if (preCondition["ns"].type() != BSONType::String) {
+ return {ErrorCodes::InvalidNamespace,
+ str::stream() << "ns in preCondition must be a string, but found type: "
+ << typeName(preCondition["ns"].type())};
+ }
+ const NamespaceString nss(preCondition["ns"].valueStringData());
+ if (!nss.isValid()) {
+ return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()};
+ }
- // Get collection default collation.
- Database* database = dbHolder().get(txn, nss.db());
- if (!database) {
- return {ErrorCodes::NamespaceNotFound,
- "database in ns does not exist: " + nss.ns()};
- }
- Collection* collection = database->getCollection(nss.ns());
- if (!collection) {
- return {ErrorCodes::NamespaceNotFound,
- "collection in ns does not exist: " + nss.ns()};
- }
- const CollatorInterface* collator = collection->getDefaultCollator();
-
- // Apply-ops would never have a $where/$text matcher. Using the "DisallowExtensions"
- // callback ensures that parsing will throw an error if $where or $text are found.
- Matcher matcher(
- preCondition["res"].Obj(), ExtensionsCallbackDisallowExtensions(), collator);
- if (!matcher.matches(realres)) {
- result->append("got", realres);
- result->append("whatFailed", preCondition);
- return {ErrorCodes::BadValue, "preCondition failed"};
- }
+ DBDirectClient db(opCtx);
+ BSONObj realres = db.findOne(nss.ns(), preCondition["q"].Obj());
+
+ // Get collection default collation.
+ Database* database = dbHolder().get(opCtx, nss.db());
+ if (!database) {
+ return {ErrorCodes::NamespaceNotFound, "database in ns does not exist: " + nss.ns()};
+ }
+ Collection* collection = database->getCollection(nss.ns());
+ if (!collection) {
+ return {ErrorCodes::NamespaceNotFound, "collection in ns does not exist: " + nss.ns()};
+ }
+ const CollatorInterface* collator = collection->getDefaultCollator();
+
+ // Apply-ops would never have a $where/$text matcher. Using the "DisallowExtensions"
+ // callback ensures that parsing will throw an error if $where or $text are found.
+ Matcher matcher(
+ preCondition["res"].Obj(), ExtensionsCallbackDisallowExtensions(), collator);
+ if (!matcher.matches(realres)) {
+ result->append("got", realres);
+ result->append("whatFailed", preCondition);
+ return {ErrorCodes::BadValue, "preCondition failed"};
}
}
+
return Status::OK();
}
} // namespace
-Status applyOps(OperationContext* txn,
+Status applyOps(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& applyOpCmd,
BSONObjBuilder* result) {
- ScopedTransaction scopedXact(txn, MODE_X);
- Lock::GlobalWrite globalWriteLock(txn->lockState());
+ bool allowAtomic = false;
+ uassertStatusOK(
+ bsonExtractBooleanFieldWithDefault(applyOpCmd, "allowAtomic", true, &allowAtomic));
+ auto areOpsCrudOnly = _areOpsCrudOnly(applyOpCmd);
+ auto isAtomic = allowAtomic && areOpsCrudOnly;
+ auto hasPrecondition = _hasPrecondition(applyOpCmd);
+
+ ScopedTransaction scopedXact(opCtx, MODE_X);
+ boost::optional<Lock::GlobalWrite> globalWriteLock;
+ boost::optional<Lock::DBLock> dbWriteLock;
+
+ // There's only one case where we are allowed to take the database lock instead of the global
+ // lock - no preconditions; only CRUD ops; and non-atomic mode.
+ if (!hasPrecondition && areOpsCrudOnly && !allowAtomic) {
+ dbWriteLock.emplace(opCtx->lockState(), dbName, MODE_IX);
+ } else {
+ globalWriteLock.emplace(opCtx->lockState());
+ }
- bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() &&
+ bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() &&
!repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(dbName);
if (userInitiatedWritesAndNotPrimary)
return Status(ErrorCodes::NotMaster,
str::stream() << "Not primary while applying ops to database " << dbName);
- Status preconditionStatus = preconditionOK(txn, applyOpCmd, result);
- if (!preconditionStatus.isOK()) {
- return preconditionStatus;
+ if (hasPrecondition) {
+ auto status = _checkPrecondition(opCtx, applyOpCmd, result);
+ if (!status.isOK()) {
+ return status;
+ }
}
int numApplied = 0;
- if (!canBeAtomic(applyOpCmd))
- return _applyOps(txn, dbName, applyOpCmd, result, &numApplied);
+ if (!isAtomic)
+ return _applyOps(opCtx, dbName, applyOpCmd, result, &numApplied);
// Perform write ops atomically
+ invariant(globalWriteLock);
try {
MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
BSONObjBuilder intermediateResult;
- WriteUnitOfWork wunit(txn);
+ WriteUnitOfWork wunit(opCtx);
numApplied = 0;
- uassertStatusOK(_applyOps(txn, dbName, applyOpCmd, &intermediateResult, &numApplied));
+ {
+ // Suppress replication for atomic operations until end of applyOps.
+ repl::UnreplicatedWritesBlock uwb(opCtx);
+ uassertStatusOK(
+ _applyOps(opCtx, dbName, applyOpCmd, &intermediateResult, &numApplied));
+ }
+ // Generate oplog entry for all atomic ops collectively.
+ if (opCtx->writesAreReplicated()) {
+ // We want this applied atomically on slaves so we rewrite the oplog entry without
+ // the pre-condition for speed.
+
+ std::string tempNS = str::stream() << dbName << ".$cmd";
+
+ BSONObjBuilder cmdBuilder;
+
+ for (auto elem : applyOpCmd) {
+ auto name = elem.fieldNameStringData();
+ if (name == kPreconditionFieldName)
+ continue;
+ if (name == "bypassDocumentValidation")
+ continue;
+ cmdBuilder.append(elem);
+ }
+
+ const BSONObj cmdRewritten = cmdBuilder.done();
+
+ auto opObserver = getGlobalServiceContext()->getOpObserver();
+ invariant(opObserver);
+ opObserver->onApplyOps(opCtx, tempNS, cmdRewritten);
+ }
wunit.commit();
result->appendElements(intermediateResult.obj());
}
- MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "applyOps", dbName);
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_END(opCtx, "applyOps", dbName);
} catch (const DBException& ex) {
if (ex.getCode() == ErrorCodes::NamespaceNotFound) {
// Retry in non-atomic mode, since MMAP cannot implicitly create a new database
// within an active WriteUnitOfWork.
- return _applyOps(txn, dbName, applyOpCmd, result, &numApplied);
+ return _applyOps(opCtx, dbName, applyOpCmd, result, &numApplied);
}
BSONArrayBuilder ab;
++numApplied;
diff --git a/src/mongo/db/catalog/apply_ops.h b/src/mongo/db/catalog/apply_ops.h
index 588d3bb370b..3a742891573 100644
--- a/src/mongo/db/catalog/apply_ops.h
+++ b/src/mongo/db/catalog/apply_ops.h
@@ -37,7 +37,7 @@ class OperationContext;
* Applies ops contained in "applyOpCmd" and populates fields in "result" to be returned to the
* user.
*/
-Status applyOps(OperationContext* txn,
+Status applyOps(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& applyOpCmd,
BSONObjBuilder* result);
diff --git a/src/mongo/db/catalog/database.cpp b/src/mongo/db/catalog/database.cpp
index 9e24afd5ffb..23fdfb84e95 100644
--- a/src/mongo/db/catalog/database.cpp
+++ b/src/mongo/db/catalog/database.cpp
@@ -475,8 +475,8 @@ Status Database::renameCollection(OperationContext* txn,
Top::get(txn->getClient()->getServiceContext()).collectionDropped(fromNS.toString());
}
- txn->recoveryUnit()->registerChange(new AddCollectionChange(txn, this, toNS));
Status s = _dbEntry->renameCollection(txn, fromNS, toNS, stayTemp);
+ txn->recoveryUnit()->registerChange(new AddCollectionChange(txn, this, toNS));
_collections[toNS] = _getOrCreateCollectionInstance(txn, toNS);
return s;
}
diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp
index d9d25a951dc..f5f62ce9636 100644
--- a/src/mongo/db/catalog/rename_collection.cpp
+++ b/src/mongo/db/catalog/rename_collection.cpp
@@ -71,7 +71,15 @@ Status renameCollection(OperationContext* txn,
DisableDocumentValidation validationDisabler(txn);
ScopedTransaction transaction(txn, MODE_X);
- Lock::GlobalWrite globalWriteLock(txn->lockState());
+ boost::optional<Lock::GlobalWrite> globalWriteLock;
+ boost::optional<Lock::DBLock> dbWriteLock;
+
+ // If the rename is known not to be a cross-database rename, just a database lock suffices.
+ if (source.db() == target.db())
+ dbWriteLock.emplace(txn->lockState(), source.db(), MODE_X);
+ else
+ globalWriteLock.emplace(txn->lockState());
+
// We stay in source context the whole time. This is mostly to set the CurOp namespace.
OldClientContext ctx(txn, source.ns());
diff --git a/src/mongo/db/commands/apply_ops_cmd.cpp b/src/mongo/db/commands/apply_ops_cmd.cpp
index c252b8fd073..262e8ef93d3 100644
--- a/src/mongo/db/commands/apply_ops_cmd.cpp
+++ b/src/mongo/db/commands/apply_ops_cmd.cpp
@@ -116,22 +116,17 @@ public:
}
}
- auto client = txn->getClient();
- auto lastOpAtOperationStart = repl::ReplClientInfo::forClient(client).getLastOp();
- ScopeGuard lastOpSetterGuard =
- MakeObjGuard(repl::ReplClientInfo::forClient(client),
- &repl::ReplClientInfo::setLastOpToSystemLastOpTime,
- txn);
-
+ // TODO (SERVER-30217): When a write concern is provided to the applyOps command, we
+ // normally wait on the OpTime of whichever operation successfully completed last. This is
+ // erroneous, however, if the last operation in the array happens to be a write no-op and
+ // thus isn’t assigned an OpTime. Let the second to last operation in the applyOps be write
+ // A, the last operation in applyOps be write B. Let B do a no-op write and let the
+ // operation that caused B to be a no-op be C. If C has an OpTime after A but before B,
+ // then we won’t wait for C to be replicated and it could be rolled back, even though B
+ // was acknowledged. To fix this, we should wait for replication of the node’s last applied
+ // OpTime if the last write operation was a no-op write.
auto applyOpsStatus = appendCommandStatus(result, applyOps(txn, dbname, cmdObj, &result));
- if (repl::ReplClientInfo::forClient(client).getLastOp() != lastOpAtOperationStart) {
- // If this operation has already generated a new lastOp, don't bother setting it
- // here. No-op applyOps will not generate a new lastOp, so we still need the guard to
- // fire in that case.
- lastOpSetterGuard.Dismiss();
- }
-
return applyOpsStatus;
}
diff --git a/src/mongo/db/commands/create_indexes.cpp b/src/mongo/db/commands/create_indexes.cpp
index 489e080f7a6..ee0133da46b 100644
--- a/src/mongo/db/commands/create_indexes.cpp
+++ b/src/mongo/db/commands/create_indexes.cpp
@@ -290,12 +290,6 @@ public:
const int numIndexesBefore = collection->getIndexCatalog()->numIndexesTotal(txn);
result.append("numIndexesBefore", numIndexesBefore);
- auto client = txn->getClient();
- ScopeGuard lastOpSetterGuard =
- MakeObjGuard(repl::ReplClientInfo::forClient(client),
- &repl::ReplClientInfo::setLastOpToSystemLastOpTime,
- txn);
-
MultiIndexBlock indexer(txn, collection);
indexer.allowBackgroundBuilding();
indexer.allowInterruption();
@@ -403,8 +397,6 @@ public:
result.append("numIndexesAfter", collection->getIndexCatalog()->numIndexesTotal(txn));
- lastOpSetterGuard.Dismiss();
-
return true;
}
diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp
index 37bcf783d47..38eae01ce9d 100644
--- a/src/mongo/db/commands/dbcommands.cpp
+++ b/src/mongo/db/commands/dbcommands.cpp
@@ -61,6 +61,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/commands/shutdown.h"
+#include "mongo/db/concurrency/global_lock_acquisition_tracker.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
@@ -1256,13 +1257,21 @@ namespace {
void _waitForWriteConcernAndAddToCommandResponse(OperationContext* opCtx,
const std::string& commandName,
+ const repl::OpTime& lastOpBeforeRun,
BSONObjBuilder* commandResponseBuilder) {
+ auto lastOpAfterRun = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp();
+
+ // Ensures that if we tried to do a write, we wait for write concern, even if that write was
+ // a noop.
+ if ((lastOpAfterRun == lastOpBeforeRun) &&
+ GlobalLockAcquisitionTracker::get(opCtx).getGlobalExclusiveLockTaken()) {
+ repl::ReplClientInfo::forClient(opCtx->getClient()).setLastOpToSystemLastOpTime(opCtx);
+ lastOpAfterRun = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp();
+ }
+
WriteConcernResult res;
auto waitForWCStatus =
- waitForWriteConcern(opCtx,
- repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(),
- opCtx->getWriteConcern(),
- &res);
+ waitForWriteConcern(opCtx, lastOpAfterRun, opCtx->getWriteConcern(), &res);
Command::appendCommandWCStatus(*commandResponseBuilder, waitForWCStatus, res);
// SERVER-22421: This code is to ensure error response backwards compatibility with the
@@ -1558,13 +1567,17 @@ bool Command::run(OperationContext* txn,
return result;
}
+ auto lastOpBeforeRun = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
+
// Change the write concern while running the command.
const auto oldWC = txn->getWriteConcern();
ON_BLOCK_EXIT([&] { txn->setWriteConcern(oldWC); });
txn->setWriteConcern(wcResult.getValue());
- ON_BLOCK_EXIT(
- [&] { _waitForWriteConcernAndAddToCommandResponse(txn, getName(), &inPlaceReplyBob); });
+ ON_BLOCK_EXIT([&] {
+ _waitForWriteConcernAndAddToCommandResponse(
+ txn, getName(), lastOpBeforeRun, &inPlaceReplyBob);
+ });
result = run(txn, db, cmd, 0, errmsg, inPlaceReplyBob);
diff --git a/src/mongo/db/commands/feature_compatibility_version.cpp b/src/mongo/db/commands/feature_compatibility_version.cpp
index f354efa88d9..00c6de52a5c 100644
--- a/src/mongo/db/commands/feature_compatibility_version.cpp
+++ b/src/mongo/db/commands/feature_compatibility_version.cpp
@@ -248,12 +248,8 @@ void FeatureCompatibilityVersion::set(OperationContext* txn, StringData version)
client.runCommand(nss.db().toString(),
makeUpdateCommand(version, WriteConcernOptions::Majority),
updateResult);
- uassertStatusOK(getStatusFromCommandResult(updateResult));
- uassertStatusOK(getWriteConcernStatusFromCommandResult(updateResult));
+ uassertStatusOK(getStatusFromWriteCommandReply(updateResult));
- // We then update the value of the featureCompatibilityVersion server parameter.
- serverGlobalParams.featureCompatibility.version.store(
- ServerGlobalParams::FeatureCompatibility::Version::k34);
} else if (version == FeatureCompatibilityVersionCommandParser::kVersion32) {
// We update the featureCompatibilityVersion document stored in the "admin.system.version"
// collection. We do this before dropping the v=2 index in order to maintain the invariant
@@ -262,7 +258,7 @@ void FeatureCompatibilityVersion::set(OperationContext* txn, StringData version)
// concern to this update because we're going to do so anyway for the "dropIndexes" command.
BSONObj updateResult;
client.runCommand(nss.db().toString(), makeUpdateCommand(version, BSONObj()), updateResult);
- uassertStatusOK(getStatusFromCommandResult(updateResult));
+ uassertStatusOK(getStatusFromWriteCommandReply(updateResult));
// We then drop the v=2 index on the "admin.system.version" collection to enable 3.2
// secondaries to sync from this mongod.
@@ -278,10 +274,6 @@ void FeatureCompatibilityVersion::set(OperationContext* txn, StringData version)
uassertStatusOK(status);
}
uassertStatusOK(getWriteConcernStatusFromCommandResult(dropIndexesResult));
-
- // We then update the value of the featureCompatibilityVersion server parameter.
- serverGlobalParams.featureCompatibility.version.store(
- ServerGlobalParams::FeatureCompatibility::Version::k32);
}
}
@@ -323,7 +315,8 @@ void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* txn,
}
// We then insert the featureCompatibilityVersion document into the "admin.system.version"
- // collection. We do this after creating the v=2 index in order to maintain the invariant
+ // collection. The server parameter will be updated on commit by the op observer.
+ // We do this after creating the v=2 index in order to maintain the invariant
// that if the featureCompatibilityVersion is 3.4, then 'k32IncompatibleIndexSpec' index
// exists on the "admin.system.version" collection. If we happened to fail to insert the
// document when starting up, then on a subsequent start-up we'd no longer consider the data
@@ -334,14 +327,10 @@ void FeatureCompatibilityVersion::setIfCleanStartup(OperationContext* txn,
BSON("_id" << FeatureCompatibilityVersion::kParameterName
<< FeatureCompatibilityVersion::kVersionField
<< FeatureCompatibilityVersionCommandParser::kVersion34)));
-
- // We then update the value of the featureCompatibilityVersion server parameter.
- serverGlobalParams.featureCompatibility.version.store(
- ServerGlobalParams::FeatureCompatibility::Version::k34);
}
}
-void FeatureCompatibilityVersion::onInsertOrUpdate(const BSONObj& doc) {
+void FeatureCompatibilityVersion::onInsertOrUpdate(OperationContext* opCtx, const BSONObj& doc) {
auto idElement = doc["_id"];
if (idElement.type() != BSONType::String ||
idElement.String() != FeatureCompatibilityVersion::kParameterName) {
@@ -350,10 +339,11 @@ void FeatureCompatibilityVersion::onInsertOrUpdate(const BSONObj& doc) {
auto newVersion = uassertStatusOK(FeatureCompatibilityVersion::parse(doc));
log() << "setting featureCompatibilityVersion to "
<< getFeatureCompatibilityVersionString(newVersion);
- serverGlobalParams.featureCompatibility.version.store(newVersion);
+ opCtx->recoveryUnit()->onCommit(
+ [newVersion]() { serverGlobalParams.featureCompatibility.version.store(newVersion); });
}
-void FeatureCompatibilityVersion::onDelete(const BSONObj& doc) {
+void FeatureCompatibilityVersion::onDelete(OperationContext* opCtx, const BSONObj& doc) {
auto idElement = doc["_id"];
if (idElement.type() != BSONType::String ||
idElement.String() != FeatureCompatibilityVersion::kParameterName) {
@@ -361,15 +351,19 @@ void FeatureCompatibilityVersion::onDelete(const BSONObj& doc) {
}
log() << "setting featureCompatibilityVersion to "
<< FeatureCompatibilityVersionCommandParser::kVersion32;
- serverGlobalParams.featureCompatibility.version.store(
- ServerGlobalParams::FeatureCompatibility::Version::k32);
+ opCtx->recoveryUnit()->onCommit([]() {
+ serverGlobalParams.featureCompatibility.version.store(
+ ServerGlobalParams::FeatureCompatibility::Version::k32);
+ });
}
-void FeatureCompatibilityVersion::onDropCollection() {
+void FeatureCompatibilityVersion::onDropCollection(OperationContext* opCtx) {
log() << "setting featureCompatibilityVersion to "
<< FeatureCompatibilityVersionCommandParser::kVersion32;
- serverGlobalParams.featureCompatibility.version.store(
- ServerGlobalParams::FeatureCompatibility::Version::k32);
+ opCtx->recoveryUnit()->onCommit([]() {
+ serverGlobalParams.featureCompatibility.version.store(
+ ServerGlobalParams::FeatureCompatibility::Version::k32);
+ });
}
/**
diff --git a/src/mongo/db/commands/feature_compatibility_version.h b/src/mongo/db/commands/feature_compatibility_version.h
index 4bcb4b56e55..8e7969c2e70 100644
--- a/src/mongo/db/commands/feature_compatibility_version.h
+++ b/src/mongo/db/commands/feature_compatibility_version.h
@@ -76,21 +76,22 @@ public:
/**
* Examines a document inserted or updated in admin.system.version. If it is the
- * featureCompatibilityVersion document, validates the document and updates the server
- * parameter.
+ * featureCompatibilityVersion document, validates the document and on commit, updates
+ * the server parameter.
*/
- static void onInsertOrUpdate(const BSONObj& doc);
+ static void onInsertOrUpdate(OperationContext* opCtx, const BSONObj& doc);
/**
* Examines the _id of a document removed from admin.system.version. If it is the
* featureCompatibilityVersion document, resets the server parameter to its default value (3.2).
+ * on commit.
*/
- static void onDelete(const BSONObj& doc);
+ static void onDelete(OperationContext* opCtx, const BSONObj& doc);
/**
- * Resets the server parameter to its default value (3.2).
+ * Resets the server parameter to its default value (3.2) on commit.
*/
- static void onDropCollection();
+ static void onDropCollection(OperationContext* opCtx);
};
} // namespace mongo
diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp
index 79ee34203ce..61dbc4f6509 100644
--- a/src/mongo/db/commands/find_and_modify.cpp
+++ b/src/mongo/db/commands/find_and_modify.cpp
@@ -364,18 +364,6 @@ public:
if (shouldBypassDocumentValidationForCommand(cmdObj))
maybeDisableValidation.emplace(txn);
- auto client = txn->getClient();
- auto lastOpAtOperationStart = repl::ReplClientInfo::forClient(client).getLastOp();
- ScopeGuard lastOpSetterGuard =
- MakeObjGuard(repl::ReplClientInfo::forClient(client),
- &repl::ReplClientInfo::setLastOpToSystemLastOpTime,
- txn);
-
- // If this is the local database, don't set last op.
- if (dbName == "local") {
- lastOpSetterGuard.Dismiss();
- }
-
auto curOp = CurOp::get(txn);
OpDebug* opDebug = &curOp->debug();
@@ -567,13 +555,6 @@ public:
}
MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "findAndModify", nsString.ns());
- if (repl::ReplClientInfo::forClient(client).getLastOp() != lastOpAtOperationStart) {
- // If this operation has already generated a new lastOp, don't bother setting it here.
- // No-op updates will not generate a new lastOp, so we still need the guard to fire in
- // that case.
- lastOpSetterGuard.Dismiss();
- }
-
return true;
}
diff --git a/src/mongo/db/commands/mr.cpp b/src/mongo/db/commands/mr.cpp
index 4aeed6d5978..136656b8dd2 100644
--- a/src/mongo/db/commands/mr.cpp
+++ b/src/mongo/db/commands/mr.cpp
@@ -314,7 +314,7 @@ Config::Config(const string& _dbname, const BSONObj& cmdObj) {
// scope and code
if (cmdObj["scope"].type() == Object)
- scopeSetup = cmdObj["scope"].embeddedObjectUserCheck();
+ scopeSetup = cmdObj["scope"].embeddedObjectUserCheck().getOwned();
mapper.reset(new JSMapper(cmdObj["map"]));
reducer.reset(new JSReducer(cmdObj["reduce"]));
@@ -322,7 +322,7 @@ Config::Config(const string& _dbname, const BSONObj& cmdObj) {
finalizer.reset(new JSFinalizer(cmdObj["finalize"]));
if (cmdObj["mapparams"].type() == Array) {
- mapParams = cmdObj["mapparams"].embeddedObjectUserCheck();
+ mapParams = cmdObj["mapparams"].embeddedObjectUserCheck().getOwned();
}
}
@@ -836,6 +836,7 @@ void State::init() {
AuthorizationSession::get(Client::getCurrent())->getAuthenticatedUserNamesToken();
_scope.reset(getGlobalScriptEngine()->newScopeForCurrentThread());
_scope->registerOperation(_txn);
+ _scope->requireOwnedObjects();
_scope->setLocalDB(_config.dbname);
_scope->loadStored(_txn, true);
@@ -1522,6 +1523,7 @@ public:
BSONObj o;
PlanExecutor::ExecState execState;
while (PlanExecutor::ADVANCED == (execState = exec->getNext(&o, NULL))) {
+ o = o.getOwned(); // we will be accessing outside of the lock
// check to see if this is a new object we don't own yet
// because of a chunk migration
if (collMetadata) {
diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp
index 6fe6de34381..03f8ec87e30 100644
--- a/src/mongo/db/commands/pipeline_command.cpp
+++ b/src/mongo/db/commands/pipeline_command.cpp
@@ -407,10 +407,6 @@ public:
return appendCommandStatus(result, resolvedView.getStatus());
}
- auto collationSpec = ctx.getView()->defaultCollator()
- ? ctx.getView()->defaultCollator()->getSpec().toBSON().getOwned()
- : CollationSpec::kSimpleSpec;
-
// With the view & collation resolved, we can relinquish locks.
ctx.releaseLocksForView();
@@ -424,7 +420,6 @@ public:
if (!newRequest.isOK()) {
return appendCommandStatus(result, newRequest.getStatus());
}
- newRequest.getValue().setCollation(collationSpec);
bool status = runParsed(
txn, origNss, newRequest.getValue(), newCmd.getValue(), errmsg, result);
diff --git a/src/mongo/db/concurrency/SConscript b/src/mongo/db/concurrency/SConscript
index 942b7a4f7f1..023d3b6b09e 100644
--- a/src/mongo/db/concurrency/SConscript
+++ b/src/mongo/db/concurrency/SConscript
@@ -13,6 +13,17 @@ env.Library(
)
env.Library(
+ target='global_lock_acquisition_tracker',
+ source=[
+ 'global_lock_acquisition_tracker.cpp'
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/util/decorable',
+ ]
+)
+
+
+env.Library(
target='lock_manager',
source=[
'd_concurrency.cpp',
@@ -21,6 +32,7 @@ env.Library(
'lock_stats.cpp',
],
LIBDEPS=[
+ 'global_lock_acquisition_tracker',
'$BUILD_DIR/mongo/util/background_job',
'$BUILD_DIR/mongo/base',
# Temporary crutch since the ssl cleanup is hard coded in background.cpp
@@ -44,6 +56,7 @@ env.CppUnitTest(
LIBDEPS=[
'$BUILD_DIR/mongo/db/service_context_noop_init',
'$BUILD_DIR/mongo/util/progress_meter',
+ 'global_lock_acquisition_tracker',
'lock_manager',
]
)
diff --git a/src/mongo/db/concurrency/d_concurrency.cpp b/src/mongo/db/concurrency/d_concurrency.cpp
index f7dabcc2248..6e7bd1ee7d6 100644
--- a/src/mongo/db/concurrency/d_concurrency.cpp
+++ b/src/mongo/db/concurrency/d_concurrency.cpp
@@ -34,6 +34,7 @@
#include <string>
+#include "mongo/db/concurrency/global_lock_acquisition_tracker.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/server_parameters.h"
#include "mongo/db/service_context.h"
@@ -91,6 +92,13 @@ void Lock::GlobalLock::waitForLock(unsigned timeoutMs) {
if (_result != LOCK_OK && _locker->shouldConflictWithSecondaryBatchApplication()) {
_pbwm.unlock();
}
+
+ if (_locker->isWriteLocked() && haveClient()) {
+ auto opCtx = cc().getOperationContext();
+ if (opCtx) {
+ GlobalLockAcquisitionTracker::get(opCtx).setGlobalExclusiveLockTaken();
+ }
+ }
}
void Lock::GlobalLock::_unlock() {
diff --git a/src/mongo/db/concurrency/d_concurrency.h b/src/mongo/db/concurrency/d_concurrency.h
index 6274814ca1c..b8df869089e 100644
--- a/src/mongo/db/concurrency/d_concurrency.h
+++ b/src/mongo/db/concurrency/d_concurrency.h
@@ -163,6 +163,9 @@ public:
/**
* Enqueues lock but does not block on lock acquisition.
* Call waitForLock() to complete locking process.
+ *
+ * Does not set that the global lock was taken on the GlobalLockAcquisitionTracker. Call
+ * waitForLock to do so.
*/
GlobalLock(Locker* locker, LockMode lockMode, unsigned timeoutMs, EnqueueOnly enqueueOnly);
@@ -171,7 +174,8 @@ public:
}
/**
- * Waits for lock to be granted.
+ * Waits for lock to be granted. Sets that the global lock was taken on the
+ * GlobalLockAcquisitionTracker.
*/
void waitForLock(unsigned timeoutMs);
diff --git a/src/mongo/db/concurrency/d_concurrency_test.cpp b/src/mongo/db/concurrency/d_concurrency_test.cpp
index 22aae759650..799aae8a7e3 100644
--- a/src/mongo/db/concurrency/d_concurrency_test.cpp
+++ b/src/mongo/db/concurrency/d_concurrency_test.cpp
@@ -34,6 +34,7 @@
#include <vector>
#include "mongo/db/concurrency/d_concurrency.h"
+#include "mongo/db/concurrency/global_lock_acquisition_tracker.h"
#include "mongo/db/concurrency/lock_manager_test_help.h"
#include "mongo/db/operation_context.h"
#include "mongo/stdx/functional.h"
@@ -80,12 +81,13 @@ private:
class UseGlobalThrottling {
public:
explicit UseGlobalThrottling(OperationContext* opCtx, int numTickets)
- : _opCtx(opCtx), _holder(1) {
+ : _opCtx(opCtx), _holder(numTickets) {
_opCtx->lockState()->setGlobalThrottling(&_holder, &_holder);
}
- ~UseGlobalThrottling() {
+ ~UseGlobalThrottling() noexcept(false) {
// Reset the global setting as we're about to destroy the ticket holder.
_opCtx->lockState()->setGlobalThrottling(nullptr, nullptr);
+ ASSERT_EQ(_holder.used(), 0);
}
private:
@@ -115,6 +117,16 @@ makeKClientsWithLockers(int k) {
}
/**
+ * Returns an operation context that has an MMAPV1 locker attached to it.
+ */
+ServiceContext::UniqueOperationContext makeMMAPOperationContext() {
+ auto opCtx = cc().makeOperationContext();
+ opCtx->releaseLockState();
+ opCtx->setLockState(stdx::make_unique<MMAPV1LockerImpl>());
+ return opCtx;
+}
+
+/**
* Calls fn the given number of iterations, spread out over up to maxThreads threads.
* The threadNr passed is an integer between 0 and maxThreads exclusive. Logs timing
* statistics for for all power-of-two thread counts from 1 up to maxThreds.
@@ -292,6 +304,86 @@ TEST(DConcurrency, GlobalLockX_Timeout) {
}
}
+TEST(DConcurrency, GlobalLockXSetsGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+
+ {
+ Lock::GlobalLock globalWrite(opCtx->lockState(), MODE_X, 0);
+ ASSERT(globalWrite.isLocked());
+ }
+ ASSERT_TRUE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, GlobalLockIXSetsGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+ {
+ Lock::GlobalLock globalWrite(opCtx->lockState(), MODE_IX, 0);
+ ASSERT(globalWrite.isLocked());
+ }
+ ASSERT_TRUE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, GlobalLockSDoesNotSetGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+ {
+ Lock::GlobalLock globalRead(opCtx->lockState(), MODE_S, 0);
+ ASSERT(globalRead.isLocked());
+ }
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, GlobalLockISDoesNotSetGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+ {
+ Lock::GlobalLock globalRead(opCtx->lockState(), MODE_IS, 0);
+ ASSERT(globalRead.isLocked());
+ }
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, DBLockXSetsGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+
+ { Lock::DBLock dbWrite(opCtx->lockState(), "db", MODE_X); }
+ ASSERT_TRUE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, DBLockSDoesNotSetGlobalLockTakenOnOperationContext) {
+ Client::initThreadIfNotAlready();
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+
+ { Lock::DBLock dbRead(opCtx->lockState(), "db", MODE_S); }
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
+TEST(DConcurrency, GlobalLockXDoesNotSetGlobalLockTakenWhenLockAcquisitionTimesOut) {
+ Client::initThreadIfNotAlready();
+ auto clients = makeKClientsWithLockers<MMAPV1LockerImpl>(1);
+
+ // Take a global lock so that the next one times out.
+ Lock::GlobalLock globalWrite0(clients[0].second.get()->lockState(), MODE_X, 0);
+ ASSERT(globalWrite0.isLocked());
+
+ auto opCtx = makeMMAPOperationContext();
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+ {
+ Lock::GlobalLock globalWrite1(opCtx->lockState(), MODE_X, 1);
+ ASSERT_FALSE(globalWrite1.isLocked());
+ }
+ ASSERT_FALSE(GlobalLockAcquisitionTracker::get(opCtx.get()).getGlobalExclusiveLockTaken());
+}
+
TEST(DConcurrency, GlobalLockS_NoTimeoutDueToGlobalLockS) {
MMAPV1LockerImpl ls;
Lock::GlobalRead globalRead(&ls);
@@ -720,6 +812,231 @@ TEST(DConcurrency, Throttling) {
ASSERT(!overlongWait);
}
+TEST(DConcurrency, NoThrottlingWhenNotAcquiringTickets) {
+ auto clientOpctxPairs = makeKClientsWithLockers<DefaultLockerImpl>(2);
+ auto opctx1 = clientOpctxPairs[0].second.get();
+ auto opctx2 = clientOpctxPairs[1].second.get();
+ // Limit the locker to 1 ticket at a time.
+ UseGlobalThrottling throttle(opctx1, 1);
+
+ // Prevent the enforcement of ticket throttling.
+ opctx1->lockState()->setShouldAcquireTicket(false);
+
+ // Both locks should be acquired immediately because there is no throttling.
+ Lock::GlobalRead R1(opctx1->lockState(), 0);
+ ASSERT(R1.isLocked());
+
+ Lock::GlobalRead R2(opctx2->lockState(), 0);
+ ASSERT(R2.isLocked());
+}
+
+TEST(DConcurrency, CompatibleFirstWithSXIS) {
+ auto clientOpctxPairs = makeKClientsWithLockers<DefaultLockerImpl>(3);
+ auto opctx1 = clientOpctxPairs[0].second.get();
+ auto opctx2 = clientOpctxPairs[1].second.get();
+ auto opctx3 = clientOpctxPairs[2].second.get();
+
+ // Build a queue of MODE_S <- MODE_X <- MODE_IS, with MODE_S granted.
+ Lock::GlobalRead lockS(opctx1->lockState());
+ ASSERT(lockS.isLocked());
+ Lock::GlobalLock lockX(opctx2->lockState(), MODE_X, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockX.isLocked());
+
+ // A MODE_IS should be granted due to compatibleFirst policy.
+ Lock::GlobalLock lockIS(opctx3->lockState(), MODE_IS, 0);
+ ASSERT(lockIS.isLocked());
+
+ lockX.waitForLock(0);
+ ASSERT(!lockX.isLocked());
+}
+
+
+TEST(DConcurrency, CompatibleFirstWithXSIXIS) {
+ auto clientOpctxPairs = makeKClientsWithLockers<DefaultLockerImpl>(4);
+ auto opctx1 = clientOpctxPairs[0].second.get();
+ auto opctx2 = clientOpctxPairs[1].second.get();
+ auto opctx3 = clientOpctxPairs[2].second.get();
+ auto opctx4 = clientOpctxPairs[3].second.get();
+
+ // Build a queue of MODE_X <- MODE_S <- MODE_IX <- MODE_IS, with MODE_X granted.
+ boost::optional<Lock::GlobalWrite> lockX;
+ lockX.emplace(opctx1->lockState());
+ ASSERT(lockX->isLocked());
+ boost::optional<Lock::GlobalLock> lockS;
+ lockS.emplace(opctx2->lockState(), MODE_S, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockS->isLocked());
+ Lock::GlobalLock lockIX(
+ opctx3->lockState(), MODE_IX, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockIX.isLocked());
+ Lock::GlobalLock lockIS(
+ opctx4->lockState(), MODE_IS, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockIS.isLocked());
+
+
+ // Now release the MODE_X and ensure that MODE_S will switch policy to compatibleFirst
+ lockX.reset();
+ lockS->waitForLock(0);
+ ASSERT(lockS->isLocked());
+ ASSERT(!lockIX.isLocked());
+ lockIS.waitForLock(0);
+ ASSERT(lockIS.isLocked());
+
+ // Now release the MODE_S and ensure that MODE_IX gets locked.
+ lockS.reset();
+ lockIX.waitForLock(0);
+ ASSERT(lockIX.isLocked());
+}
+
+TEST(DConcurrency, CompatibleFirstWithXSXIXIS) {
+ auto clientOpctxPairs = makeKClientsWithLockers<DefaultLockerImpl>(5);
+ auto opctx1 = clientOpctxPairs[0].second.get();
+ auto opctx2 = clientOpctxPairs[1].second.get();
+ auto opctx3 = clientOpctxPairs[2].second.get();
+ auto opctx4 = clientOpctxPairs[3].second.get();
+ auto opctx5 = clientOpctxPairs[4].second.get();
+
+ // Build a queue of MODE_X <- MODE_S <- MODE_X <- MODE_IX <- MODE_IS, with the first MODE_X
+ // granted and check that releasing it will result in the MODE_IS being granted.
+ boost::optional<Lock::GlobalWrite> lockXgranted;
+ lockXgranted.emplace(opctx1->lockState());
+ ASSERT(lockXgranted->isLocked());
+
+ boost::optional<Lock::GlobalLock> lockX;
+ lockX.emplace(opctx3->lockState(), MODE_X, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockX->isLocked());
+
+ // Now request MODE_S: it will be first in the pending list due to EnqueueAtFront policy.
+ boost::optional<Lock::GlobalLock> lockS;
+ lockS.emplace(opctx2->lockState(), MODE_S, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockS->isLocked());
+
+ Lock::GlobalLock lockIX(
+ opctx4->lockState(), MODE_IX, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockIX.isLocked());
+ Lock::GlobalLock lockIS(
+ opctx5->lockState(), MODE_IS, UINT_MAX, Lock::GlobalLock::EnqueueOnly());
+ ASSERT(!lockIS.isLocked());
+
+
+ // Now release the granted MODE_X and ensure that MODE_S will switch policy to compatibleFirst,
+ // not locking the MODE_X or MODE_IX, but instead granting the final MODE_IS.
+ lockXgranted.reset();
+ lockS->waitForLock(0);
+ ASSERT(lockS->isLocked());
+
+ lockX->waitForLock(0);
+ ASSERT(!lockX->isLocked());
+ lockIX.waitForLock(0);
+ ASSERT(!lockIX.isLocked());
+
+ lockIS.waitForLock(0);
+ ASSERT(lockIS.isLocked());
+}
+
+TEST(DConcurrency, CompatibleFirstStress) {
+ int numThreads = 8;
+ int testMicros = 500000;
+ AtomicUInt64 readOnlyInterval{0};
+ AtomicBool done{false};
+ std::vector<uint64_t> acquisitionCount(numThreads);
+ std::vector<uint64_t> timeoutCount(numThreads);
+ std::vector<uint64_t> busyWaitCount(numThreads);
+ auto clientOpctxPairs = makeKClientsWithLockers<DefaultLockerImpl>(numThreads);
+
+ // Do some busy waiting to trigger different timings. The atomic load prevents compilers
+ // from optimizing the loop away.
+ auto busyWait = [&done, &busyWaitCount](int threadId, long long iters) {
+ while (iters-- > 0) {
+ for (int i = 0; i < 100 && !done.load(); i++) {
+ busyWaitCount[threadId]++;
+ }
+ }
+ };
+
+ std::vector<stdx::thread> threads;
+
+ // Thread putting state in/out of read-only CompatibleFirst mode.
+ threads.emplace_back([&]() {
+ Timer t;
+ auto endTime = t.micros() + testMicros;
+ uint64_t readOnlyIntervalCount = 0;
+ OperationContext* opCtx = clientOpctxPairs[0].second.get();
+ for (int iters = 0; (t.micros() < endTime); iters++) {
+ busyWait(0, iters % 20);
+ Lock::GlobalRead readLock(opCtx->lockState(), iters % 2);
+ if (!readLock.isLocked()) {
+ timeoutCount[0]++;
+ continue;
+ }
+ acquisitionCount[0]++;
+ readOnlyInterval.store(++readOnlyIntervalCount);
+ busyWait(0, iters % 200);
+ readOnlyInterval.store(0);
+ };
+ done.store(true);
+ });
+
+ for (int threadId = 1; threadId < numThreads; threadId++) {
+ threads.emplace_back([&, threadId]() {
+ Timer t;
+ for (int iters = 0; !done.load(); iters++) {
+ OperationContext* opCtx = clientOpctxPairs[threadId].second.get();
+ boost::optional<Lock::GlobalLock> lock;
+ switch (threadId) {
+ case 1:
+ case 2:
+ case 3:
+ case 4: {
+ // Here, actually try to acquire a lock without waiting, and check whether
+ // we should have gotten the lock or not. Use MODE_IS in 95% of the cases,
+ // and MODE_S in only 5, as that stressing the partitioning scheme and
+ // policy changes more as thread 0 acquires/releases its MODE_S lock.
+ busyWait(threadId, iters % 100);
+ auto interval = readOnlyInterval.load();
+ lock.emplace(opCtx->lockState(),
+ iters % 20 ? MODE_IS : MODE_S,
+ 0,
+ Lock::GlobalLock::EnqueueOnly());
+ // If thread 0 is holding the MODE_S lock while we tried to acquire a
+ // MODE_IS or MODE_S lock, the CompatibleFirst policy guarantees success.
+ auto newInterval = readOnlyInterval.load();
+ invariant(!interval || interval != newInterval || lock->isLocked());
+ lock->waitForLock(0);
+ break;
+ }
+ case 5:
+ busyWait(threadId, iters % 150);
+ lock.emplace(opCtx->lockState(), MODE_X, iters % 2);
+ busyWait(threadId, iters % 10);
+ break;
+ case 6:
+ lock.emplace(opCtx->lockState(), iters % 25 ? MODE_IX : MODE_S, iters % 2);
+ busyWait(threadId, iters % 100);
+ break;
+ case 7:
+ busyWait(threadId, iters % 100);
+ lock.emplace(opCtx->lockState(), iters % 20 ? MODE_IS : MODE_X, 0);
+ break;
+ default:
+ MONGO_UNREACHABLE;
+ }
+ if (lock->isLocked())
+ acquisitionCount[threadId]++;
+ else
+ timeoutCount[threadId]++;
+ };
+ });
+ }
+
+ for (auto& thread : threads)
+ thread.join();
+ for (int threadId = 0; threadId < numThreads; threadId++) {
+ log() << "thread " << threadId << " stats: " << acquisitionCount[threadId]
+ << " acquisitions, " << timeoutCount[threadId] << " timeouts, "
+ << busyWaitCount[threadId] / 1000000 << "M busy waits";
+ }
+}
+
// These tests exercise single- and multi-threaded performance of uncontended lock acquisition. It
// is neither practical nor useful to run them on debug builds.
diff --git a/src/mongo/db/concurrency/global_lock_acquisition_tracker.cpp b/src/mongo/db/concurrency/global_lock_acquisition_tracker.cpp
new file mode 100644
index 00000000000..21ac9272c93
--- /dev/null
+++ b/src/mongo/db/concurrency/global_lock_acquisition_tracker.cpp
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/platform/basic.h"
+
+#include "mongo/db/concurrency/global_lock_acquisition_tracker.h"
+
+namespace mongo {
+
+const OperationContext::Decoration<GlobalLockAcquisitionTracker> GlobalLockAcquisitionTracker::get =
+ OperationContext::declareDecoration<GlobalLockAcquisitionTracker>();
+
+bool GlobalLockAcquisitionTracker::getGlobalExclusiveLockTaken() const {
+ return _globalExclusiveLockTaken;
+}
+
+void GlobalLockAcquisitionTracker::setGlobalExclusiveLockTaken() {
+ _globalExclusiveLockTaken = true;
+}
+} // namespace mongo
diff --git a/src/mongo/db/s/collection_range_deleter.h b/src/mongo/db/concurrency/global_lock_acquisition_tracker.h
index 9d2e1ae8cb4..5bd68453f57 100644
--- a/src/mongo/db/s/collection_range_deleter.h
+++ b/src/mongo/db/concurrency/global_lock_acquisition_tracker.h
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2016 MongoDB Inc.
+ * Copyright (C) 2017 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
@@ -25,53 +25,38 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
+
#pragma once
-#include "mongo/base/disallow_copying.h"
-#include "mongo/db/namespace_string.h"
-#include "mongo/s/catalog/type_chunk.h"
+#include "mongo/db/operation_context.h"
namespace mongo {
-class BSONObj;
-class Collection;
-class OperationContext;
-
-class CollectionRangeDeleter {
- MONGO_DISALLOW_COPYING(CollectionRangeDeleter);
-
+/**
+ * The GlobalLockAcquisitionTracker keeps track of if the global lock has ever been taken in X or
+ * IX mode. This class is used to track if we ever did a transaction with the intent to do a
+ * write, so that we can enforce write concern on noop writes.
+ */
+class GlobalLockAcquisitionTracker {
public:
- CollectionRangeDeleter(NamespaceString nss);
+ static const OperationContext::Decoration<GlobalLockAcquisitionTracker> get;
- /**
- * Starts deleting ranges and cleans up this object when it is finished.
- */
- void run();
+ // Decoration requires a default constructor.
+ GlobalLockAcquisitionTracker() = default;
/**
- * Acquires the collection IX lock and checks whether there are new entries for the collection's
- * rangesToClean structure. If there are, deletes some small amount of entries and yields using
- * the standard query yielding logic.
- *
- * Returns true if there are more entries in rangesToClean, false if there is no more progress
- * to be made.
+ * Returns whether we have ever taken a global lock in X or IX mode in this operation.
*/
- bool cleanupNextRange(OperationContext* txn);
+ bool getGlobalExclusiveLockTaken() const;
-private:
/**
- * Performs the deletion of a small amount of entries within the range in progress.
- * This function will invariant if called while _rangeInProgress is not set.
- *
- * Returns the number of documents deleted (0 if deletion is finished), or -1 for error.
+ * Sets that we have ever taken a global lock in X or IX mode in this operation.
*/
- int _doDeletion(OperationContext* txn, Collection* collection, const BSONObj& keyPattern);
+ void setGlobalExclusiveLockTaken();
- NamespaceString _nss;
-
- // Holds a range for which deletion has begun. If empty, then a new range
- // must be requested from rangesToClean
- boost::optional<ChunkRange> _rangeInProgress;
+private:
+ // Set to true when the global lock is first taken in X or IX mode. Never set back to false.
+ bool _globalExclusiveLockTaken = false;
};
} // namespace mongo
diff --git a/src/mongo/db/concurrency/lock_manager.cpp b/src/mongo/db/concurrency/lock_manager.cpp
index f90141e79be..8ae7919012e 100644
--- a/src/mongo/db/concurrency/lock_manager.cpp
+++ b/src/mongo/db/concurrency/lock_manager.cpp
@@ -592,7 +592,9 @@ bool LockManager::unlock(LockRequest* request) {
lock->decGrantedModeCount(request->mode);
if (request->compatibleFirst) {
+ invariant(lock->compatibleFirstCount > 0);
lock->compatibleFirstCount--;
+ invariant(lock->compatibleFirstCount == 0 || !lock->grantedList.empty());
}
_onLockModeChanged(lock, lock->grantedCounts[request->mode] == 0);
diff --git a/src/mongo/db/concurrency/lock_state.cpp b/src/mongo/db/concurrency/lock_state.cpp
index 45ea279c137..3625cef24f7 100644
--- a/src/mongo/db/concurrency/lock_state.cpp
+++ b/src/mongo/db/concurrency/lock_state.cpp
@@ -311,7 +311,7 @@ LockResult LockerImpl<IsForMMAPV1>::_lockGlobalBegin(LockMode mode, Milliseconds
dassert(isLocked() == (_modeForTicket != MODE_NONE));
if (_modeForTicket == MODE_NONE) {
const bool reader = isSharedLockMode(mode);
- auto holder = ticketHolders[mode];
+ auto holder = shouldAcquireTicket() ? ticketHolders[mode] : nullptr;
if (holder) {
_clientState.store(reader ? kQueuedReader : kQueuedWriter);
if (timeout == Milliseconds::max()) {
@@ -787,7 +787,10 @@ LockResult LockerImpl<IsForMMAPV1>::lockComplete(ResourceId resId,
}
}
- // Cleanup the state, since this is an unused lock now
+ // Cleanup the state, since this is an unused lock now.
+ // Note: in case of the _notify object returning LOCK_TIMEOUT, it is possible to find that the
+ // lock was still granted after all, but we don't try to take advantage of that and will return
+ // a timeout.
if (result != LOCK_OK) {
LockRequestsMap::Iterator it = _requests.find(resId);
_unlockImpl(&it);
@@ -807,7 +810,7 @@ bool LockerImpl<IsForMMAPV1>::_unlockImpl(LockRequestsMap::Iterator* it) {
if (globalLockManager.unlock(it->objAddr())) {
if (it->key() == resourceIdGlobal) {
invariant(_modeForTicket != MODE_NONE);
- auto holder = ticketHolders[_modeForTicket];
+ auto holder = shouldAcquireTicket() ? ticketHolders[_modeForTicket] : nullptr;
_modeForTicket = MODE_NONE;
if (holder) {
holder->release();
diff --git a/src/mongo/db/concurrency/locker.h b/src/mongo/db/concurrency/locker.h
index 37af569124f..c1443865326 100644
--- a/src/mongo/db/concurrency/locker.h
+++ b/src/mongo/db/concurrency/locker.h
@@ -334,11 +334,25 @@ public:
return _shouldConflictWithSecondaryBatchApplication;
}
+ /**
+ * If set to false, this opts out of the ticket mechanism. This should be used sparingly
+ * for special purpose threads, such as FTDC.
+ */
+ void setShouldAcquireTicket(bool newValue) {
+ invariant(!isLocked());
+ _shouldAcquireTicket = newValue;
+ }
+ bool shouldAcquireTicket() const {
+ return _shouldAcquireTicket;
+ }
+
+
protected:
Locker() {}
private:
bool _shouldConflictWithSecondaryBatchApplication = true;
+ bool _shouldAcquireTicket = true;
};
} // namespace mongo
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index 2dbb2b1065e..c6272425bf5 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -103,10 +103,10 @@
#include "mongo/db/startup_warnings_mongod.h"
#include "mongo/db/stats/counters.h"
#include "mongo/db/stats/snapshots.h"
+#include "mongo/db/storage/encryption_hooks.h"
#include "mongo/db/storage/mmap_v1/mmap_v1_options.h"
#include "mongo/db/storage/storage_engine.h"
#include "mongo/db/storage/storage_options.h"
-#include "mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h"
#include "mongo/db/ttl.h"
#include "mongo/db/wire_version.h"
#include "mongo/executor/network_interface_factory.h"
@@ -596,7 +596,7 @@ ExitCode _initAndListen(int listenPort) {
getGlobalServiceContext()->initializeGlobalStorageEngine();
#ifdef MONGO_CONFIG_WIREDTIGER_ENABLED
- if (WiredTigerCustomizationHooks::get(getGlobalServiceContext())->restartRequired()) {
+ if (EncryptionHooks::get(getGlobalServiceContext())->restartRequired()) {
exitCleanly(EXIT_CLEAN);
}
#endif
@@ -704,7 +704,11 @@ ExitCode _initAndListen(int listenPort) {
Status status = authindex::verifySystemIndexes(startupOpCtx.get());
if (!status.isOK()) {
log() << redact(status);
- exitCleanly(EXIT_NEED_UPGRADE);
+ if (status.code() == ErrorCodes::AuthSchemaIncompatible) {
+ exitCleanly(EXIT_NEED_UPGRADE);
+ } else {
+ quickExit(EXIT_FAILURE);
+ }
}
// SERVER-14090: Verify that auth schema version is schemaVersion26Final.
@@ -752,7 +756,7 @@ ExitCode _initAndListen(int listenPort) {
if (!storageGlobalParams.readOnly) {
logStartup(startupOpCtx.get());
- startFTDC();
+ startMongoDFTDC();
getDeleter()->startWorkers();
@@ -1108,7 +1112,7 @@ static void shutdownTask() {
#endif
// Shutdown Full-Time Data Capture
- stopFTDC();
+ stopMongoDFTDC();
if (txn) {
ShardingState::get(txn)->shutDown(txn);
diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp
index ebed6807c7e..96eb011018d 100644
--- a/src/mongo/db/dbhelpers.cpp
+++ b/src/mongo/db/dbhelpers.cpp
@@ -62,8 +62,8 @@
#include "mongo/db/s/sharding_state.h"
#include "mongo/db/service_context.h"
#include "mongo/db/storage/data_protector.h"
+#include "mongo/db/storage/encryption_hooks.h"
#include "mongo/db/storage/storage_options.h"
-#include "mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h"
#include "mongo/db/write_concern.h"
#include "mongo/db/write_concern_options.h"
#include "mongo/s/shard_key_pattern.h"
@@ -270,6 +270,7 @@ long long Helpers::removeRange(OperationContext* txn,
const KeyRange& range,
BoundInclusion boundInclusion,
const WriteConcernOptions& writeConcern,
+ Milliseconds& replWaitDuration,
RemoveSaver* callback,
bool fromMigrate,
bool onlyRemoveOrphanedDocs) {
@@ -283,6 +284,7 @@ long long Helpers::removeRange(OperationContext* txn,
BSONObj max;
{
+ ScopedTransaction scopedXact(txn, MODE_IS);
AutoGetCollectionForRead ctx(txn, ns);
Collection* collection = ctx.getCollection();
if (!collection) {
@@ -326,11 +328,12 @@ long long Helpers::removeRange(OperationContext* txn,
long long numDeleted = 0;
- Milliseconds millisWaitingForReplication{0};
+ replWaitDuration = Milliseconds::zero();
while (1) {
// Scoping for write lock.
{
+ ScopedTransaction scopedXact(txn, MODE_IX);
AutoGetCollection ctx(txn, NamespaceString(ns), MODE_IX, MODE_IX);
Collection* collection = ctx.getCollection();
if (!collection)
@@ -438,20 +441,21 @@ long long Helpers::removeRange(OperationContext* txn,
txn,
repl::ReplClientInfo::forClient(txn->getClient()).getLastOp(),
writeConcern);
- if (replStatus.status.code() == ErrorCodes::ExceededTimeLimit) {
+ if (replStatus.status.code() == ErrorCodes::ExceededTimeLimit ||
+ replStatus.status.code() == ErrorCodes::WriteConcernFailed) {
warning(LogComponent::kSharding) << "replication to secondaries for removeRange at "
"least 60 seconds behind";
} else {
uassertStatusOK(replStatus.status);
}
- millisWaitingForReplication += replStatus.duration;
+ replWaitDuration += replStatus.duration;
}
}
if (writeConcern.shouldWaitForOtherNodes())
log(LogComponent::kSharding)
<< "Helpers::removeRangeUnlocked time spent waiting for replication: "
- << durationCount<Milliseconds>(millisWaitingForReplication) << "ms" << endl;
+ << durationCount<Milliseconds>(replWaitDuration) << "ms" << endl;
MONGO_LOG_COMPONENT(1, LogComponent::kSharding) << "end removal of " << min << " to " << max
<< " in " << ns << " (took "
@@ -485,19 +489,19 @@ Helpers::RemoveSaver::RemoveSaver(const string& a, const string& b, const string
ss << why << "." << terseCurrentTime(false) << "." << NUM++ << ".bson";
_file /= ss.str();
- auto hooks = WiredTigerCustomizationHooks::get(getGlobalServiceContext());
- if (hooks->enabled()) {
- _protector = hooks->getDataProtector();
- _file += hooks->getProtectedPathSuffix();
+ auto encryptionHooks = EncryptionHooks::get(getGlobalServiceContext());
+ if (encryptionHooks->enabled()) {
+ _protector = encryptionHooks->getDataProtector();
+ _file += encryptionHooks->getProtectedPathSuffix();
}
}
Helpers::RemoveSaver::~RemoveSaver() {
if (_protector && _out) {
- auto hooks = WiredTigerCustomizationHooks::get(getGlobalServiceContext());
- invariant(hooks->enabled());
+ auto encryptionHooks = EncryptionHooks::get(getGlobalServiceContext());
+ invariant(encryptionHooks->enabled());
- size_t protectedSizeMax = hooks->additionalBytesForProtectedBuffer();
+ size_t protectedSizeMax = encryptionHooks->additionalBytesForProtectedBuffer();
std::unique_ptr<uint8_t[]> protectedBuffer(new uint8_t[protectedSizeMax]);
size_t resultLen;
@@ -560,10 +564,10 @@ Status Helpers::RemoveSaver::goingToDelete(const BSONObj& o) {
std::unique_ptr<uint8_t[]> protectedBuffer;
if (_protector) {
- auto hooks = WiredTigerCustomizationHooks::get(getGlobalServiceContext());
- invariant(hooks->enabled());
+ auto encryptionHooks = EncryptionHooks::get(getGlobalServiceContext());
+ invariant(encryptionHooks->enabled());
- size_t protectedSizeMax = dataSize + hooks->additionalBytesForProtectedBuffer();
+ size_t protectedSizeMax = dataSize + encryptionHooks->additionalBytesForProtectedBuffer();
protectedBuffer.reset(new uint8_t[protectedSizeMax]);
size_t resultLen;
diff --git a/src/mongo/db/dbhelpers.h b/src/mongo/db/dbhelpers.h
index 650f265265d..59a4a317db7 100644
--- a/src/mongo/db/dbhelpers.h
+++ b/src/mongo/db/dbhelpers.h
@@ -141,11 +141,13 @@ struct Helpers {
/**
* Takes a namespace range, specified by a min and max and qualified by an index pattern,
* and removes all the documents in that range found by iterating
- * over the given index. Caller is responsible for insuring that min/max are
+ * over the given index. Caller is responsible for ensuring that min/max are
* compatible with the given keyPattern (e.g min={a:100} is compatible with
* keyPattern={a:1,b:1} since it can be extended to {a:100,b:minKey}, but
* min={b:100} is not compatible).
*
+ * Returns time spent waiting for majority replication in replWaitDuration.
+ *
* Caller must hold a write lock on 'ns'
*
* Returns -1 when no usable index exists
@@ -157,6 +159,7 @@ struct Helpers {
const KeyRange& range,
BoundInclusion boundInclusion,
const WriteConcernOptions& secondaryThrottle,
+ Milliseconds& replWaitDuration,
RemoveSaver* callback = NULL,
bool fromMigrate = false,
bool onlyRemoveOrphanedDocs = false);
diff --git a/src/mongo/db/dbwebserver.cpp b/src/mongo/db/dbwebserver.cpp
index b202e0500b9..50fa68a8176 100644
--- a/src/mongo/db/dbwebserver.cpp
+++ b/src/mongo/db/dbwebserver.cpp
@@ -65,6 +65,7 @@
namespace mongo {
using std::map;
+using std::string;
using std::stringstream;
using std::vector;
diff --git a/src/mongo/db/ftdc/SConscript b/src/mongo/db/ftdc/SConscript
index 0d16aec93f2..81df9ad0f4d 100644
--- a/src/mongo/db/ftdc/SConscript
+++ b/src/mongo/db/ftdc/SConscript
@@ -21,7 +21,6 @@ ftdcEnv.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/bson/util/bson_extract',
- '$BUILD_DIR/mongo/db/server_options_core',
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/third_party/s2/s2', # For VarInt
'$BUILD_DIR/third_party/shim_zlib',
@@ -40,24 +39,44 @@ elif env.TargetOSIs('windows'):
]
env.Library(
- target='ftdc_mongod',
+ target='ftdc_server',
source=[
- 'ftdc_commands.cpp',
- 'ftdc_mongod.cpp',
+ 'ftdc_server.cpp',
'ftdc_system_stats.cpp',
'ftdc_system_stats_${TARGET_OS}.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/db/commands',
- '$BUILD_DIR/mongo/db/repl/repl_coordinator_global',
'$BUILD_DIR/mongo/db/server_parameters',
- '$BUILD_DIR/mongo/db/storage/storage_options',
'$BUILD_DIR/mongo/util/processinfo',
'ftdc'
] + platform_libs,
)
+env.Library(
+ target='ftdc_mongod',
+ source=[
+ 'ftdc_commands.cpp',
+ 'ftdc_mongod.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/db/repl/repl_coordinator_global',
+ '$BUILD_DIR/mongo/db/storage/storage_options',
+ 'ftdc_server'
+ ],
+)
+
+env.Library(
+ target='ftdc_mongos',
+ source=[
+ 'ftdc_mongos.cpp',
+ ],
+ LIBDEPS=[
+ 'ftdc_server'
+ ],
+)
+
env.CppUnitTest(
target='ftdc_test',
source=[
diff --git a/src/mongo/db/ftdc/collector.cpp b/src/mongo/db/ftdc/collector.cpp
index 611f12dff5a..7c35fb12bb6 100644
--- a/src/mongo/db/ftdc/collector.cpp
+++ b/src/mongo/db/ftdc/collector.cpp
@@ -67,6 +67,7 @@ std::tuple<BSONObj, Date_t> FTDCCollectorCollection::collect(Client* client) {
// batches that are taking a long time.
auto txn = client->makeOperationContext();
txn->lockState()->setShouldConflictWithSecondaryBatchApplication(false);
+ txn->lockState()->setShouldAcquireTicket(false);
for (auto& collector : _collectors) {
BSONObjBuilder subObjBuilder(builder.subobjStart(collector->name()));
diff --git a/src/mongo/db/ftdc/compressor_test.cpp b/src/mongo/db/ftdc/compressor_test.cpp
index 688197a392a..0c01bd58040 100644
--- a/src/mongo/db/ftdc/compressor_test.cpp
+++ b/src/mongo/db/ftdc/compressor_test.cpp
@@ -368,7 +368,7 @@ TEST(FTDCCompressor, TestNumbersCompat) {
}
// Test various date time types
-TEST(AFTDCCompressor, TestDateTimeTypes) {
+TEST(FTDCCompressor, TestDateTimeTypes) {
TestTie c;
for (int i = 0; i < 10; i++) {
BSONObjBuilder builder1;
diff --git a/src/mongo/db/ftdc/constants.h b/src/mongo/db/ftdc/constants.h
index a989fd35b64..3fe5191ff44 100644
--- a/src/mongo/db/ftdc/constants.h
+++ b/src/mongo/db/ftdc/constants.h
@@ -26,6 +26,10 @@
* then also delete it in the license file.
*/
+#pragma once
+
+#include "mongo/base/string_data.h"
+
namespace mongo {
extern const char kFTDCInterimFile[];
@@ -42,4 +46,6 @@ extern const char kFTDCDocsField[];
extern const char kFTDCCollectStartField[];
extern const char kFTDCCollectEndField[];
+constexpr StringData kFTDCDefaultDirectory = "diagnostic.data"_sd;
+
} // namespace mongo
diff --git a/src/mongo/db/ftdc/controller.cpp b/src/mongo/db/ftdc/controller.cpp
index 9e2987db0f9..bc3d4df444c 100644
--- a/src/mongo/db/ftdc/controller.cpp
+++ b/src/mongo/db/ftdc/controller.cpp
@@ -47,9 +47,19 @@
namespace mongo {
-void FTDCController::setEnabled(bool enabled) {
+Status FTDCController::setEnabled(bool enabled) {
stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ if (_path.empty()) {
+ return Status(ErrorCodes::FTDCPathNotSet,
+ str::stream() << "FTDC cannot be enabled without setting the set parameter "
+ "'diagnosticDataCollectionDirectoryPath' first.");
+ }
+
_configTemp.enabled = enabled;
+ _condvar.notify_one();
+
+ return Status::OK();
}
void FTDCController::setPeriod(Milliseconds millis) {
@@ -82,6 +92,23 @@ void FTDCController::setMaxSamplesPerInterimMetricChunk(size_t size) {
_condvar.notify_one();
}
+Status FTDCController::setDirectory(const boost::filesystem::path& path) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ if (!_path.empty()) {
+ return Status(ErrorCodes::FTDCPathAlreadySet,
+ str::stream() << "FTDC path has already been set to '" << _path.string()
+ << "'. It cannot be changed.");
+ }
+
+ _path = path;
+
+ // Do not notify for the change since it has to be enabled via setEnabled.
+
+ return Status::OK();
+}
+
+
void FTDCController::addPeriodicCollector(std::unique_ptr<FTDCCollectorInterface> collector) {
{
stdx::lock_guard<stdx::mutex> lock(_mutex);
@@ -203,7 +230,7 @@ void FTDCController::doLoop() {
}
// TODO: consider only running this thread if we are enabled
- // for now, we just keep an idle thread as it is simplier
+ // for now, we just keep an idle thread as it is simpler
if (_config.enabled) {
// Delay initialization of FTDCFileManager until we are sure the user has enabled
// FTDC
diff --git a/src/mongo/db/ftdc/controller.h b/src/mongo/db/ftdc/controller.h
index 44b5e838506..e73d570598d 100644
--- a/src/mongo/db/ftdc/controller.h
+++ b/src/mongo/db/ftdc/controller.h
@@ -62,8 +62,12 @@ public:
/*
* Set whether the controller is enabled, and collects data.
+ *
+ * Returns ErrorCodes::FTDCPathNotSet if no log path has been specified for FTDC. This occurs
+ * in MongoS in some situations since MongoS is not required to have a storage directory like
+ * MongoD does.
*/
- void setEnabled(bool enabled);
+ Status setEnabled(bool enabled);
/**
* Set the period for data collection.
@@ -94,6 +98,13 @@ public:
*/
void setMaxSamplesPerInterimMetricChunk(size_t size);
+ /*
+ * Set the path to store FTDC files if not already set.
+ *
+ * Returns ErrorCodes::FTDCPathAlreadySet if the path has already been set.
+ */
+ Status setDirectory(const boost::filesystem::path& path);
+
/**
* Add a metric collector to collect periodically. i.e., serverStatus
*/
@@ -172,7 +183,7 @@ private:
State _state{State::kNotStarted};
// Directory to store files
- const boost::filesystem::path _path;
+ boost::filesystem::path _path;
// Mutex to protect the condvar, configuration changes, and most recent periodic document.
stdx::mutex _mutex;
diff --git a/src/mongo/db/ftdc/controller_test.cpp b/src/mongo/db/ftdc/controller_test.cpp
index 365f06580cd..c75a3fe44d6 100644
--- a/src/mongo/db/ftdc/controller_test.cpp
+++ b/src/mongo/db/ftdc/controller_test.cpp
@@ -254,7 +254,7 @@ TEST(FTDCControllerTest, TestStartAsDisabled) {
ASSERT_EQUALS(files0.size(), 0UL);
- c.setEnabled(true);
+ ASSERT_OK(c.setEnabled(true));
c1Ptr->setSignalOnCount(50);
diff --git a/src/mongo/db/ftdc/ftdc_mongod.cpp b/src/mongo/db/ftdc/ftdc_mongod.cpp
index 094a2b05d06..1f53ec345ea 100644
--- a/src/mongo/db/ftdc/ftdc_mongod.cpp
+++ b/src/mongo/db/ftdc/ftdc_mongod.cpp
@@ -31,295 +31,18 @@
#include "mongo/db/ftdc/ftdc_mongod.h"
#include <boost/filesystem.hpp>
-#include <fstream>
-#include <memory>
-#include "mongo/base/init.h"
-#include "mongo/base/status.h"
-#include "mongo/bson/bsonobjbuilder.h"
-#include "mongo/db/commands.h"
-#include "mongo/db/ftdc/collector.h"
-#include "mongo/db/ftdc/config.h"
+#include "mongo/db/ftdc/constants.h"
#include "mongo/db/ftdc/controller.h"
-#include "mongo/db/ftdc/ftdc_system_stats.h"
-#include "mongo/db/jsobj.h"
+#include "mongo/db/ftdc/ftdc_server.h"
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/repl/replication_coordinator_global.h"
-#include "mongo/db/server_parameters.h"
-#include "mongo/db/service_context.h"
#include "mongo/db/storage/storage_options.h"
namespace mongo {
namespace {
-
-const auto getFTDCController = ServiceContext::declareDecoration<std::unique_ptr<FTDCController>>();
-
-FTDCController* getGlobalFTDCController() {
- if (!hasGlobalServiceContext()) {
- return nullptr;
- }
-
- return getFTDCController(getGlobalServiceContext()).get();
-}
-
-std::atomic<bool> localEnabledFlag(FTDCConfig::kEnabledDefault); // NOLINT
-
-class ExportedFTDCEnabledParameter
- : public ExportedServerParameter<bool, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCEnabledParameter()
- : ExportedServerParameter<bool, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionEnabled",
- &localEnabledFlag) {}
-
- virtual Status validate(const bool& potentialNewValue) {
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setEnabled(potentialNewValue);
- }
-
- return Status::OK();
- }
-
-} exportedFTDCEnabledParameter;
-
-std::atomic<std::int32_t> localPeriodMillis(FTDCConfig::kPeriodMillisDefault); // NOLINT
-
-class ExportedFTDCPeriodParameter
- : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCPeriodParameter()
- : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionPeriodMillis",
- &localPeriodMillis) {}
-
- virtual Status validate(const std::int32_t& potentialNewValue) {
- if (potentialNewValue < 100) {
- return Status(
- ErrorCodes::BadValue,
- "diagnosticDataCollectionPeriodMillis must be greater than or equal to 100ms");
- }
-
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setPeriod(Milliseconds(potentialNewValue));
- }
-
- return Status::OK();
- }
-
-} exportedFTDCPeriodParameter;
-
-// Scale the values down since are defaults are in bytes, but the user interface is MB
-std::atomic<std::int32_t> localMaxDirectorySizeMB( // NOLINT
- FTDCConfig::kMaxDirectorySizeBytesDefault / (1024 * 1024));
-
-std::atomic<std::int32_t> localMaxFileSizeMB(FTDCConfig::kMaxFileSizeBytesDefault / // NOLINT
- (1024 * 1024));
-
-class ExportedFTDCDirectorySizeParameter
- : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCDirectorySizeParameter()
- : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionDirectorySizeMB",
- &localMaxDirectorySizeMB) {}
-
- virtual Status validate(const std::int32_t& potentialNewValue) {
- if (potentialNewValue < 10) {
- return Status(
- ErrorCodes::BadValue,
- "diagnosticDataCollectionDirectorySizeMB must be greater than or equal to 10");
- }
-
- if (potentialNewValue < localMaxFileSizeMB) {
- return Status(
- ErrorCodes::BadValue,
- str::stream()
- << "diagnosticDataCollectionDirectorySizeMB must be greater than or equal to '"
- << localMaxFileSizeMB
- << "' which is the current value of diagnosticDataCollectionFileSizeMB.");
- }
-
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setMaxDirectorySizeBytes(potentialNewValue * 1024 * 1024);
- }
-
- return Status::OK();
- }
-
-} exportedFTDCDirectorySizeParameter;
-
-class ExportedFTDCFileSizeParameter
- : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCFileSizeParameter()
- : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionFileSizeMB",
- &localMaxFileSizeMB) {}
-
- virtual Status validate(const std::int32_t& potentialNewValue) {
- if (potentialNewValue < 1) {
- return Status(ErrorCodes::BadValue,
- "diagnosticDataCollectionFileSizeMB must be greater than or equal to 1");
- }
-
- if (potentialNewValue > localMaxDirectorySizeMB) {
- return Status(
- ErrorCodes::BadValue,
- str::stream()
- << "diagnosticDataCollectionFileSizeMB must be less than or equal to '"
- << localMaxDirectorySizeMB
- << "' which is the current value of diagnosticDataCollectionDirectorySizeMB.");
- }
-
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setMaxFileSizeBytes(potentialNewValue * 1024 * 1024);
- }
-
- return Status::OK();
- }
-
-} exportedFTDCFileSizeParameter;
-
-std::atomic<std::int32_t> localMaxSamplesPerArchiveMetricChunk( // NOLINT
- FTDCConfig::kMaxSamplesPerArchiveMetricChunkDefault);
-
-class ExportedFTDCArchiveChunkSizeParameter
- : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCArchiveChunkSizeParameter()
- : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionSamplesPerChunk",
- &localMaxSamplesPerArchiveMetricChunk) {}
-
- virtual Status validate(const std::int32_t& potentialNewValue) {
- if (potentialNewValue < 2) {
- return Status(
- ErrorCodes::BadValue,
- "diagnosticDataCollectionSamplesPerChunk must be greater than or equal to 2");
- }
-
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setMaxSamplesPerArchiveMetricChunk(potentialNewValue);
- }
-
- return Status::OK();
- }
-
-} exportedFTDCArchiveChunkSizeParameter;
-
-std::atomic<std::int32_t> localMaxSamplesPerInterimMetricChunk( // NOLINT
- FTDCConfig::kMaxSamplesPerInterimMetricChunkDefault);
-
-class ExportedFTDCInterimChunkSizeParameter
- : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
-public:
- ExportedFTDCInterimChunkSizeParameter()
- : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
- ServerParameterSet::getGlobal(),
- "diagnosticDataCollectionSamplesPerInterimUpdate",
- &localMaxSamplesPerInterimMetricChunk) {}
-
- virtual Status validate(const std::int32_t& potentialNewValue) {
- if (potentialNewValue < 2) {
- return Status(ErrorCodes::BadValue,
- "diagnosticDataCollectionSamplesPerInterimUpdate must be greater than or "
- "equal to 2");
- }
-
- auto controller = getGlobalFTDCController();
- if (controller) {
- controller->setMaxSamplesPerInterimMetricChunk(potentialNewValue);
- }
-
- return Status::OK();
- }
-
-} exportedFTDCInterimChunkSizeParameter;
-
-class FTDCSimpleInternalCommandCollector final : public FTDCCollectorInterface {
-public:
- FTDCSimpleInternalCommandCollector(StringData command,
- StringData name,
- StringData ns,
- BSONObj cmdObj)
- : _name(name.toString()), _ns(ns.toString()), _cmdObj(std::move(cmdObj)) {
- _command = Command::findCommand(command);
- invariant(_command);
- }
-
- void collect(OperationContext* txn, BSONObjBuilder& builder) override {
- std::string errmsg;
-
- bool ret = _command->run(txn, _ns, _cmdObj, 0, errmsg, builder);
-
- // Some commands return errmsgs when they return false (collstats)
- // Some commands return bson objs when they return false (replGetStatus)
- // We append the status as needed to ensure readers of the collected data can check the
- // status of any individual command.
- _command->appendCommandStatus(builder, ret, errmsg);
- }
-
- std::string name() const override {
- return _name;
- }
-
-private:
- std::string _name;
- std::string _ns;
- BSONObj _cmdObj;
-
- // Not owned
- Command* _command;
-};
-
-} // namespace
-
-// Register the FTDC system
-// Note: This must be run before the server parameters are parsed during startup
-// so that the FTDCController is initialized.
-//
-void startFTDC() {
- boost::filesystem::path dir(storageGlobalParams.dbpath);
- dir /= "diagnostic.data";
-
-
- FTDCConfig config;
- config.period = Milliseconds(localPeriodMillis.load());
- config.enabled = localEnabledFlag;
- config.maxFileSizeBytes = localMaxFileSizeMB * 1024 * 1024;
- config.maxDirectorySizeBytes = localMaxDirectorySizeMB * 1024 * 1024;
- config.maxSamplesPerArchiveMetricChunk = localMaxSamplesPerArchiveMetricChunk;
- config.maxSamplesPerInterimMetricChunk = localMaxSamplesPerInterimMetricChunk;
-
- auto controller = stdx::make_unique<FTDCController>(dir, config);
-
- // Install periodic collectors
- // These are collected on the period interval in FTDCConfig.
- // NOTE: For each command here, there must be an equivalent privilege check in
- // GetDiagnosticDataCommand
-
- // CmdServerStatus
- // The "sharding" section is filtered out because at this time it only consists of strings in
- // migration status. This section triggers too many schema changes in the serverStatus which
- // hurt ftdc compression efficiency, because its output varies depending on the list of active
- // migrations.
- controller->addPeriodicCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
- "serverStatus",
- "serverStatus",
- "",
- BSON("serverStatus" << 1 << "tcMalloc" << true << "sharding" << false)));
-
+void registerMongoDCollectors(FTDCController* controller) {
// These metrics are only collected if replication is enabled
if (repl::getGlobalReplicationCoordinator()->getReplicationMode() !=
repl::ReplicationCoordinator::modeNone) {
@@ -335,43 +58,19 @@ void startFTDC() {
BSON("collStats"
<< "oplog.rs")));
}
-
- // Install System Metric Collector as a periodic collector
- installSystemMetricsCollector(controller.get());
-
- // Install file rotation collectors
- // These are collected on each file rotation.
-
- // CmdBuildInfo
- controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
- "buildInfo", "buildInfo", "", BSON("buildInfo" << 1)));
-
- // CmdGetCmdLineOpts
- controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
- "getCmdLineOpts", "getCmdLineOpts", "", BSON("getCmdLineOpts" << 1)));
-
- // HostInfoCmd
- controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
- "hostInfo", "hostInfo", "", BSON("hostInfo" << 1)));
-
- // Install the new controller
- auto& staticFTDC = getFTDCController(getGlobalServiceContext());
-
- staticFTDC = std::move(controller);
-
- staticFTDC->start();
}
-void stopFTDC() {
- auto controller = getGlobalFTDCController();
+} // namespace
- if (controller) {
- controller->stop();
- }
+void startMongoDFTDC() {
+ boost::filesystem::path dir(storageGlobalParams.dbpath);
+ dir /= kFTDCDefaultDirectory.toString();
+
+ startFTDC(dir, FTDCStartMode::kStart, registerMongoDCollectors);
}
-FTDCController* FTDCController::get(ServiceContext* serviceContext) {
- return getFTDCController(serviceContext).get();
+void stopMongoDFTDC() {
+ stopFTDC();
}
} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_mongod.h b/src/mongo/db/ftdc/ftdc_mongod.h
index 1e4f20b8b17..b4409dde902 100644
--- a/src/mongo/db/ftdc/ftdc_mongod.h
+++ b/src/mongo/db/ftdc/ftdc_mongod.h
@@ -1,30 +1,30 @@
/**
-* Copyright (C) 2015 MongoDB Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* 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
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* 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 GNU Affero General 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.
-*/
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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
@@ -34,11 +34,11 @@ namespace mongo {
* Start Full Time Data Capture
* Starts 1 thread.
*/
-void startFTDC();
+void startMongoDFTDC();
/**
* Stop Full Time Data Capture
*/
-void stopFTDC();
+void stopMongoDFTDC();
} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_mongos.cpp b/src/mongo/db/ftdc/ftdc_mongos.cpp
new file mode 100644
index 00000000000..bdacaa7b6e1
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_mongos.cpp
@@ -0,0 +1,153 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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.
+ */
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/ftdc/ftdc_mongos.h"
+
+#include <boost/filesystem.hpp>
+
+#include "mongo/db/ftdc/controller.h"
+#include "mongo/db/ftdc/ftdc_server.h"
+#include "mongo/db/server_parameters.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+
+/**
+ * Expose diagnosticDataCollectionDirectoryPath set parameter to specify the MongoS FTDC path.
+ */
+class ExportedFTDCDirectoryPathParameter : public ServerParameter {
+public:
+ ExportedFTDCDirectoryPathParameter()
+ : ServerParameter(ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionDirectoryPath",
+ true,
+ true) {}
+
+
+ void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) final {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ b.append(name, _path.generic_string());
+ }
+
+ Status set(const BSONElement& newValueElement) {
+ if (newValueElement.type() != String) {
+ return Status(ErrorCodes::BadValue,
+ "diagnosticDataCollectionDirectoryPath only supports type string");
+ }
+
+ std::string str = newValueElement.str();
+ return setFromString(str);
+ }
+
+ Status setFromString(const std::string& str) final {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+
+ FTDCController* controller = nullptr;
+
+ if (hasGlobalServiceContext()) {
+ controller = FTDCController::get(getGlobalServiceContext());
+ }
+
+ if (controller) {
+ Status s = controller->setDirectory(str);
+ if (!s.isOK()) {
+ return s;
+ }
+ }
+
+ _path = str;
+
+ return Status::OK();
+ }
+
+ boost::filesystem::path getDirectory() {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ return _path;
+ }
+
+ void setDirectory(boost::filesystem::path& path) {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ _path = path;
+ }
+
+private:
+ // Lock to guard _path
+ stdx::mutex _lock;
+
+ // Directory location of ftdc files, guarded by _lock
+ boost::filesystem::path _path;
+} exportedFTDCDirectoryPathParameter;
+
+void registerMongoSCollectors(FTDCController* controller) {
+ // PoolStats
+ controller->addPeriodicCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "connPoolStats", "connPoolStats", "", BSON("connPoolStats" << 1)));
+}
+
+} // namespace
+
+void startMongoSFTDC() {
+ // Get the path to use for FTDC:
+ // 1. Check if the user set one.
+ // 2. If not, check if the user has a logpath and derive one.
+ // 3. Otherwise, tell the user FTDC cannot run.
+
+ // Only attempt to enable FTDC if we have a path to log files to.
+ FTDCStartMode startMode = FTDCStartMode::kStart;
+ auto directory = exportedFTDCDirectoryPathParameter.getDirectory();
+
+ if (directory.empty()) {
+ if (serverGlobalParams.logpath.empty()) {
+ warning() << "FTDC is disabled because neither '--logpath' nor set parameter "
+ "'diagnosticDataCollectionDirectoryPath' are specified.";
+ startMode = FTDCStartMode::kSkipStart;
+ } else {
+ directory = FTDCUtil::getMongoSPath(serverGlobalParams.logpath);
+
+ // Update the server parameter with the computed path.
+ // Note: If the computed FTDC directory conflicts with an existing file, then FTDC will
+ // warn about the conflict, and not startup. It will not terminate MongoS in this
+ // situation.
+ exportedFTDCDirectoryPathParameter.setDirectory(directory);
+ }
+ }
+
+ startFTDC(directory, startMode, registerMongoSCollectors);
+}
+
+void stopMongoSFTDC() {
+ stopFTDC();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_mongos.h b/src/mongo/db/ftdc/ftdc_mongos.h
new file mode 100644
index 00000000000..4d8ff8fc08b
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_mongos.h
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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
+
+namespace mongo {
+
+/**
+ * Start Full Time Data Capture
+ */
+void startMongoSFTDC();
+
+/**
+ * Stop Full Time Data Capture
+ */
+void stopMongoSFTDC();
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_server.cpp b/src/mongo/db/ftdc/ftdc_server.cpp
new file mode 100644
index 00000000000..167587a8722
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_server.cpp
@@ -0,0 +1,350 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/platform/basic.h"
+
+#include "mongo/db/ftdc/ftdc_server.h"
+
+#include <boost/filesystem.hpp>
+#include <fstream>
+#include <memory>
+
+#include "mongo/base/status.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/ftdc/collector.h"
+#include "mongo/db/ftdc/config.h"
+#include "mongo/db/ftdc/controller.h"
+#include "mongo/db/ftdc/ftdc_system_stats.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/server_parameters.h"
+#include "mongo/db/service_context.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+namespace {
+
+const auto getFTDCController = ServiceContext::declareDecoration<std::unique_ptr<FTDCController>>();
+
+FTDCController* getGlobalFTDCController() {
+ if (!hasGlobalServiceContext()) {
+ return nullptr;
+ }
+
+ return getFTDCController(getGlobalServiceContext()).get();
+}
+
+std::atomic<bool> localEnabledFlag(FTDCConfig::kEnabledDefault); // NOLINT
+
+class ExportedFTDCEnabledParameter
+ : public ExportedServerParameter<bool, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCEnabledParameter()
+ : ExportedServerParameter<bool, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionEnabled",
+ &localEnabledFlag) {}
+
+ virtual Status validate(const bool& potentialNewValue) {
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ return controller->setEnabled(potentialNewValue);
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCEnabledParameter;
+
+std::atomic<std::int32_t> localPeriodMillis(FTDCConfig::kPeriodMillisDefault); // NOLINT
+
+class ExportedFTDCPeriodParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCPeriodParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionPeriodMillis",
+ &localPeriodMillis) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 100) {
+ return Status(
+ ErrorCodes::BadValue,
+ "diagnosticDataCollectionPeriodMillis must be greater than or equal to 100ms");
+ }
+
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ controller->setPeriod(Milliseconds(potentialNewValue));
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCPeriodParameter;
+
+// Scale the values down since are defaults are in bytes, but the user interface is MB
+std::atomic<std::int32_t> localMaxDirectorySizeMB(FTDCConfig::kMaxDirectorySizeBytesDefault /
+ (1024 * 1024)); // NOLINT
+
+std::atomic<std::int32_t> localMaxFileSizeMB(FTDCConfig::kMaxFileSizeBytesDefault /
+ (1024 * 1024)); // NOLINT
+
+class ExportedFTDCDirectorySizeParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCDirectorySizeParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionDirectorySizeMB",
+ &localMaxDirectorySizeMB) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 10) {
+ return Status(
+ ErrorCodes::BadValue,
+ "diagnosticDataCollectionDirectorySizeMB must be greater than or equal to 10");
+ }
+
+ if (potentialNewValue < localMaxFileSizeMB.load()) {
+ return Status(
+ ErrorCodes::BadValue,
+ str::stream()
+ << "diagnosticDataCollectionDirectorySizeMB must be greater than or equal to '"
+ << localMaxFileSizeMB.load()
+ << "' which is the current value of diagnosticDataCollectionFileSizeMB.");
+ }
+
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ controller->setMaxDirectorySizeBytes(potentialNewValue * 1024 * 1024);
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCDirectorySizeParameter;
+
+class ExportedFTDCFileSizeParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCFileSizeParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionFileSizeMB",
+ &localMaxFileSizeMB) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 1) {
+ return Status(ErrorCodes::BadValue,
+ "diagnosticDataCollectionFileSizeMB must be greater than or equal to 1");
+ }
+
+ if (potentialNewValue > localMaxDirectorySizeMB.load()) {
+ return Status(
+ ErrorCodes::BadValue,
+ str::stream()
+ << "diagnosticDataCollectionFileSizeMB must be less than or equal to '"
+ << localMaxDirectorySizeMB.load()
+ << "' which is the current value of diagnosticDataCollectionDirectorySizeMB.");
+ }
+
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ controller->setMaxFileSizeBytes(potentialNewValue * 1024 * 1024);
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCFileSizeParameter;
+
+std::atomic<std::int32_t> localMaxSamplesPerArchiveMetricChunk( // NOLINT
+ FTDCConfig::kMaxSamplesPerArchiveMetricChunkDefault);
+
+class ExportedFTDCArchiveChunkSizeParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCArchiveChunkSizeParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionSamplesPerChunk",
+ &localMaxSamplesPerArchiveMetricChunk) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 2) {
+ return Status(
+ ErrorCodes::BadValue,
+ "diagnosticDataCollectionSamplesPerChunk must be greater than or equal to 2");
+ }
+
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ controller->setMaxSamplesPerArchiveMetricChunk(potentialNewValue);
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCArchiveChunkSizeParameter;
+
+std::atomic<std::int32_t> localMaxSamplesPerInterimMetricChunk( // NOLINT
+ FTDCConfig::kMaxSamplesPerInterimMetricChunkDefault);
+
+class ExportedFTDCInterimChunkSizeParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ ExportedFTDCInterimChunkSizeParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionSamplesPerInterimUpdate",
+ &localMaxSamplesPerInterimMetricChunk) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 2) {
+ return Status(ErrorCodes::BadValue,
+ "diagnosticDataCollectionSamplesPerInterimUpdate must be greater than or "
+ "equal to 2");
+ }
+
+ auto controller = getGlobalFTDCController();
+ if (controller) {
+ controller->setMaxSamplesPerInterimMetricChunk(potentialNewValue);
+ }
+
+ return Status::OK();
+ }
+
+} exportedFTDCInterimChunkSizeParameter;
+} // namespace
+
+FTDCSimpleInternalCommandCollector::FTDCSimpleInternalCommandCollector(StringData command,
+ StringData name,
+ StringData ns,
+ BSONObj cmdObj)
+ : _name(name.toString()), _ns(ns.toString()), _cmdObj(std::move(cmdObj)) {
+ _command = Command::findCommand(command);
+ invariant(_command);
+}
+
+void FTDCSimpleInternalCommandCollector::collect(OperationContext* opCtx, BSONObjBuilder& builder) {
+ std::string errmsg;
+
+ bool ret = _command->run(opCtx, _ns, _cmdObj, 0, errmsg, builder);
+
+ // Some commands return errmsgs when they return false (collstats)
+ // Some commands return bson objs when they return false (replGetStatus)
+ // We append the status as needed to ensure readers of the collected data can check the
+ // status of any individual command.
+ _command->appendCommandStatus(builder, ret, errmsg);
+}
+
+std::string FTDCSimpleInternalCommandCollector::name() const {
+ return _name;
+}
+
+// Register the FTDC system
+// Note: This must be run before the server parameters are parsed during startup
+// so that the FTDCController is initialized.
+//
+void startFTDC(boost::filesystem::path& path,
+ FTDCStartMode startupMode,
+ RegisterCollectorsFunction registerCollectors) {
+ FTDCConfig config;
+ config.period = Milliseconds(localPeriodMillis.load());
+ // Only enable FTDC if our caller says to enable FTDC, MongoS may not have a valid path to write
+ // files to so update the diagnosticDataCollectionEnabled set parameter to reflect that.
+ localEnabledFlag.store(startupMode == FTDCStartMode::kStart && localEnabledFlag.load());
+ config.enabled = localEnabledFlag.load();
+ config.maxFileSizeBytes = localMaxFileSizeMB.load() * 1024 * 1024;
+ config.maxDirectorySizeBytes = localMaxDirectorySizeMB.load() * 1024 * 1024;
+ config.maxSamplesPerArchiveMetricChunk = localMaxSamplesPerArchiveMetricChunk.load();
+ config.maxSamplesPerInterimMetricChunk = localMaxSamplesPerInterimMetricChunk.load();
+
+ auto controller = stdx::make_unique<FTDCController>(path, config);
+
+ // Install periodic collectors
+ // These are collected on the period interval in FTDCConfig.
+ // NOTE: For each command here, there must be an equivalent privilege check in
+ // GetDiagnosticDataCommand
+
+ // CmdServerStatus
+ // The "sharding" section is filtered out because at this time it only consists of strings in
+ // migration status. This section triggers too many schema changes in the serverStatus which
+ // hurt ftdc compression efficiency, because its output varies depending on the list of active
+ // migrations.
+ // TODO: do we need to enable "sharding" on MongoS?
+ controller->addPeriodicCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "serverStatus",
+ "serverStatus",
+ "",
+ BSON("serverStatus" << 1 << "tcMalloc" << true << "sharding" << false)));
+
+ registerCollectors(controller.get());
+
+ // Install System Metric Collector as a periodic collector
+ installSystemMetricsCollector(controller.get());
+
+ // Install file rotation collectors
+ // These are collected on each file rotation.
+
+ // CmdBuildInfo
+ controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "buildInfo", "buildInfo", "", BSON("buildInfo" << 1)));
+
+ // CmdGetCmdLineOpts
+ controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "getCmdLineOpts", "getCmdLineOpts", "", BSON("getCmdLineOpts" << 1)));
+
+ // HostInfoCmd
+ controller->addOnRotateCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "hostInfo", "hostInfo", "", BSON("hostInfo" << 1)));
+
+ // Install the new controller
+ auto& staticFTDC = getFTDCController(getGlobalServiceContext());
+
+ staticFTDC = std::move(controller);
+
+ staticFTDC->start();
+}
+
+void stopFTDC() {
+ auto controller = getGlobalFTDCController();
+
+ if (controller) {
+ controller->stop();
+ }
+}
+
+FTDCController* FTDCController::get(ServiceContext* serviceContext) {
+ return getFTDCController(serviceContext).get();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_server.h b/src/mongo/db/ftdc/ftdc_server.h
new file mode 100644
index 00000000000..4b4583ae153
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_server.h
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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 <string>
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/ftdc/collector.h"
+#include "mongo/db/ftdc/controller.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/stdx/functional.h"
+
+namespace mongo {
+
+/**
+ * Function that allows FTDC server components to register their own collectors as needed.
+ */
+using RegisterCollectorsFunction = stdx::function<void(FTDCController*)>;
+
+/**
+ * An enum that decides whether FTDC will startup as part of startup or if its deferred to later.
+ */
+enum class FTDCStartMode {
+
+ /**
+ * Skip starting FTDC since it missing a file storage location.
+ */
+ kSkipStart,
+
+ /**
+ * Start FTDC because it has a path to store files.
+ */
+ kStart,
+};
+
+/**
+ * Start Full Time Data Capture
+ * Starts 1 thread.
+ *
+ * See MongoD and MongoS specific functions.
+ */
+void startFTDC(boost::filesystem::path& path,
+ FTDCStartMode startupMode,
+ RegisterCollectorsFunction registerCollectors);
+
+/**
+ * Stop Full Time Data Capture
+ *
+ * See MongoD and MongoS specific functions.
+ */
+void stopFTDC();
+
+/**
+ * A simple FTDC Collector that runs Commands.
+ */
+class FTDCSimpleInternalCommandCollector final : public FTDCCollectorInterface {
+public:
+ FTDCSimpleInternalCommandCollector(StringData command,
+ StringData name,
+ StringData ns,
+ BSONObj cmdObj);
+
+ void collect(OperationContext* opCtx, BSONObjBuilder& builder) override;
+ std::string name() const override;
+
+private:
+ std::string _name;
+ std::string _ns;
+ BSONObj _cmdObj;
+
+ // Not owned
+ Command* _command;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_system_stats.h b/src/mongo/db/ftdc/ftdc_system_stats.h
index 20d21ef4f39..97bc0a67cf5 100644
--- a/src/mongo/db/ftdc/ftdc_system_stats.h
+++ b/src/mongo/db/ftdc/ftdc_system_stats.h
@@ -25,6 +25,7 @@
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
+#pragma once
#include <string>
diff --git a/src/mongo/db/ftdc/util.cpp b/src/mongo/db/ftdc/util.cpp
index d56eb8ca380..243ba90b666 100644
--- a/src/mongo/db/ftdc/util.cpp
+++ b/src/mongo/db/ftdc/util.cpp
@@ -103,6 +103,19 @@ Date_t roundTime(Date_t now, Milliseconds period) {
return Date_t::fromMillisSinceEpoch(next_time);
}
+boost::filesystem::path getMongoSPath(const boost::filesystem::path& logFile) {
+ auto base = logFile;
+
+ // Keep stripping file extensions until we are only left with the file name
+ while (base.has_extension()) {
+ auto full_path = base.generic_string();
+ base = full_path.substr(0, full_path.size() - base.extension().size());
+ }
+
+ base += "." + kFTDCDefaultDirectory.toString();
+ return base;
+}
+
} // namespace FTDCUtil
diff --git a/src/mongo/db/ftdc/util.h b/src/mongo/db/ftdc/util.h
index 0cb4d19ce7f..4816c534f96 100644
--- a/src/mongo/db/ftdc/util.h
+++ b/src/mongo/db/ftdc/util.h
@@ -190,6 +190,11 @@ boost::filesystem::path getInterimTempFile(const boost::filesystem::path& file);
*/
Date_t roundTime(Date_t now, Milliseconds period);
+/**
+ * Get the storage path for MongoS from the log file path.
+ */
+boost::filesystem::path getMongoSPath(const boost::filesystem::path& logFile);
+
} // namespace FTDCUtil
} // namespace mongo
diff --git a/src/mongo/db/ftdc/util_test.cpp b/src/mongo/db/ftdc/util_test.cpp
index aa99006c73a..0adf0f73c15 100644
--- a/src/mongo/db/ftdc/util_test.cpp
+++ b/src/mongo/db/ftdc/util_test.cpp
@@ -46,4 +46,25 @@ TEST(FTDCUtilTest, TestRoundTime) {
checkTime(14, 13, 7);
}
+// Validate the MongoS FTDC path is computed correctly from a log file path.
+TEST(FTDCUtilTest, TestMongoSPath) {
+
+ std::vector<std::pair<std::string, std::string>> testCases = {
+ {"/var/log/mongos.log", "/var/log/mongos.diagnostic.data"},
+ {"/var/log/mongos.foo.log", "/var/log/mongos.diagnostic.data"},
+ {"/var/log/log_file", "/var/log/log_file.diagnostic.data"},
+ {"./mongos.log", "./mongos.diagnostic.data"},
+ {"../mongos.log", "../mongos.diagnostic.data"},
+ {"c:\\var\\log\\mongos.log", "c:\\var\\log\\mongos.diagnostic.data"},
+ {"c:\\var\\log\\mongos.foo.log", "c:\\var\\log\\mongos.diagnostic.data"},
+ {"c:\\var\\log\\log_file", "c:\\var\\log\\log_file.diagnostic.data"},
+ {"/var/some.log/mongos.log", "/var/some.log/mongos.diagnostic.data"},
+ {"/var/some.log/log_file", "/var/some.log/log_file.diagnostic.data"},
+ };
+
+ for (const auto& p : testCases) {
+ ASSERT_EQUALS(FTDCUtil::getMongoSPath(p.first), p.second);
+ }
+}
+
} // namespace mongo
diff --git a/src/mongo/db/fts/unicode/string.cpp b/src/mongo/db/fts/unicode/string.cpp
index 10737acc3ed..8c1890f3669 100644
--- a/src/mongo/db/fts/unicode/string.cpp
+++ b/src/mongo/db/fts/unicode/string.cpp
@@ -30,6 +30,7 @@
#include <algorithm>
#include <boost/algorithm/searching/boyer_moore.hpp>
+#include <boost/version.hpp>
#include "mongo/db/fts/unicode/byte_vector.h"
#include "mongo/platform/bits.h"
@@ -272,9 +273,15 @@ bool String::substrMatch(const std::string& str,
auto haystack = caseFoldAndStripDiacritics(&haystackBuf, str, options, cfMode);
auto needle = caseFoldAndStripDiacritics(&needleBuf, find, options, cfMode);
- // Case sensitive and diacritic sensitive.
+// Case sensitive and diacritic sensitive.
+#if BOOST_VERSION < 106200
return boost::algorithm::boyer_moore_search(
haystack.begin(), haystack.end(), needle.begin(), needle.end()) != haystack.end();
+#else
+ return boost::algorithm::boyer_moore_search(
+ haystack.begin(), haystack.end(), needle.begin(), needle.end()) !=
+ std::make_pair(haystack.end(), haystack.end());
+#endif
}
} // namespace unicode
diff --git a/src/mongo/db/index/expression_keys_private.cpp b/src/mongo/db/index/expression_keys_private.cpp
index d58c5ce1dc1..9fe3365f393 100644
--- a/src/mongo/db/index/expression_keys_private.cpp
+++ b/src/mongo/db/index/expression_keys_private.cpp
@@ -32,6 +32,7 @@
#include <utility>
+#include "mongo/bson/bsonelement_comparator_interface.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/bson/dotted_path_support.h"
#include "mongo/db/field_ref.h"
@@ -247,7 +248,7 @@ void ExpressionKeysPrivate::get2DKeys(const BSONObj& obj,
const TwoDIndexingParams& params,
BSONObjSet* keys,
std::vector<BSONObj>* locs) {
- BSONElementMSet bSet;
+ BSONElementMultiSet bSet;
// Get all the nested location fields, but don't return individual elements from
// the last array, if it exists.
@@ -256,7 +257,7 @@ void ExpressionKeysPrivate::get2DKeys(const BSONObj& obj,
if (bSet.empty())
return;
- for (BSONElementMSet::iterator setI = bSet.begin(); setI != bSet.end(); ++setI) {
+ for (BSONElementMultiSet::iterator setI = bSet.begin(); setI != bSet.end(); ++setI) {
BSONElement geo = *setI;
if (geo.eoo() || !geo.isABSONObj())
diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp
index c10d0a3174b..5bbf3339a37 100644
--- a/src/mongo/db/instance.cpp
+++ b/src/mongo/db/instance.cpp
@@ -206,14 +206,15 @@ void receivedCommand(OperationContext* txn,
const int32_t responseToMsgId = message.header().getId();
- DbMessage dbMessage(message);
- QueryMessage queryMessage(dbMessage);
-
CurOp* op = CurOp::get(txn);
-
rpc::LegacyReplyBuilder builder{};
try {
+ DbMessage dbMessage(message);
+
+ // Can throw, so make sure it's under the try statement.
+ QueryMessage queryMessage(dbMessage);
+
// This will throw if the request is on an invalid namespace.
rpc::LegacyRequest request{&message};
// Auth checking for Commands happens later.
diff --git a/src/mongo/db/log_process_details.cpp b/src/mongo/db/log_process_details.cpp
index 125106fd331..ea6e9d945a8 100644
--- a/src/mongo/db/log_process_details.cpp
+++ b/src/mongo/db/log_process_details.cpp
@@ -34,6 +34,9 @@
#include "mongo/db/log_process_details.h"
+#include "mongo/db/repl/repl_set_config.h"
+#include "mongo/db/repl/replication_coordinator.h"
+#include "mongo/db/repl/replication_coordinator_global.h"
#include "mongo/db/server_options.h"
#include "mongo/db/server_options_helpers.h"
#include "mongo/util/log.h"
@@ -51,6 +54,7 @@ void logProcessDetails() {
auto&& vii = VersionInfoInterface::instance();
log() << mongodVersion(vii);
vii.logBuildInfo();
+
printCommandLineOpts();
}
@@ -59,6 +63,19 @@ void logProcessDetailsForLogRotate() {
<< (is32bit() ? " 32" : " 64") << "-bit "
<< "host=" << getHostNameCached();
+ auto replCoord = repl::getGlobalReplicationCoordinator();
+ if (replCoord != nullptr &&
+ replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet) {
+ auto rsConfig = replCoord->getConfig();
+
+ if (rsConfig.isInitialized()) {
+ log() << "Replica Set Config: " << rsConfig.toBSON();
+ log() << "Replica Set Member State: " << (replCoord->getMemberState()).toString();
+ } else {
+ log() << "Node currently has no Replica Set Config.";
+ }
+ }
+
logProcessDetails();
}
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index d562bff141e..0bf8d8685bc 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -202,7 +202,7 @@ void ComparisonMatchExpression::debugString(StringBuilder& debug, int level) con
}
void ComparisonMatchExpression::serialize(BSONObjBuilder* out) const {
- string opString = "";
+ std::string opString = "";
switch (matchType()) {
case LT:
opString = "$lt";
@@ -656,29 +656,31 @@ bool InMatchExpression::equivalent(const MatchExpression* other) const {
void InMatchExpression::_doSetCollator(const CollatorInterface* collator) {
_collator = collator;
+ _eltCmp = BSONElementComparator(BSONElementComparator::FieldNamesMode::kIgnore, _collator);
// We need to re-compute '_equalitySet', since our set comparator has changed.
- BSONElementSet equalitiesWithNewComparator(
- _originalEqualityVector.begin(), _originalEqualityVector.end(), collator);
- _equalitySet = std::move(equalitiesWithNewComparator);
+ _equalitySet = _eltCmp.makeBSONEltFlatSet(_originalEqualityVector);
}
-Status InMatchExpression::addEquality(const BSONElement& elt) {
- if (elt.type() == BSONType::RegEx) {
- return Status(ErrorCodes::BadValue, "InMatchExpression equality cannot be a regex");
- }
- if (elt.type() == BSONType::Undefined) {
- return Status(ErrorCodes::BadValue, "InMatchExpression equality cannot be undefined");
- }
+Status InMatchExpression::setEqualities(std::vector<BSONElement> equalities) {
+ for (auto&& equality : equalities) {
+ if (equality.type() == BSONType::RegEx) {
+ return Status(ErrorCodes::BadValue, "InMatchExpression equality cannot be a regex");
+ }
+ if (equality.type() == BSONType::Undefined) {
+ return Status(ErrorCodes::BadValue, "InMatchExpression equality cannot be undefined");
+ }
- if (elt.type() == BSONType::jstNULL) {
- _hasNull = true;
- }
- if (elt.type() == BSONType::Array && elt.Obj().isEmpty()) {
- _hasEmptyArray = true;
+ if (equality.type() == BSONType::jstNULL) {
+ _hasNull = true;
+ } else if (equality.type() == BSONType::Array && equality.Obj().isEmpty()) {
+ _hasEmptyArray = true;
+ }
}
- _equalitySet.insert(elt);
- _originalEqualityVector.push_back(elt);
+ _originalEqualityVector = std::move(equalities);
+
+ _equalitySet = _eltCmp.makeBSONEltFlatSet(_originalEqualityVector);
+
return Status::OK();
}
@@ -884,7 +886,7 @@ void BitTestMatchExpression::debugString(StringBuilder& debug, int level) const
}
void BitTestMatchExpression::serialize(BSONObjBuilder* out) const {
- string opString = "";
+ std::string opString = "";
switch (matchType()) {
case BITS_ALL_SET:
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index 128d3070737..5761f2ca2a2 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -30,9 +30,11 @@
#pragma once
+#include "mongo/bson/bsonelement_comparator.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/matcher/expression.h"
+#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/stdx/memory.h"
#include "mongo/stdx/unordered_map.h"
@@ -330,7 +332,10 @@ public:
*/
class InMatchExpression : public LeafMatchExpression {
public:
- InMatchExpression() : LeafMatchExpression(MATCH_IN) {}
+ InMatchExpression()
+ : LeafMatchExpression(MATCH_IN),
+ _eltCmp(BSONElementComparator::FieldNamesMode::kIgnore, _collator),
+ _equalitySet(_eltCmp.makeBSONEltFlatSet(_originalEqualityVector)) {}
Status init(StringData path);
@@ -349,11 +354,11 @@ public:
*/
virtual void _doSetCollator(const CollatorInterface* collator);
- Status addEquality(const BSONElement& elt);
+ Status setEqualities(std::vector<BSONElement> equalities);
Status addRegex(std::unique_ptr<RegexMatchExpression> expr);
- const BSONElementSet& getEqualities() const {
+ const BSONEltFlatSet& getEqualities() const {
return _equalitySet;
}
@@ -380,17 +385,20 @@ private:
// Whether or not '_equalities' has an empty array element in it.
bool _hasEmptyArray = false;
- // Collator used to compare elements. By default, simple binary comparison will be used.
+ // Collator used to construct '_eltCmp';
const CollatorInterface* _collator = nullptr;
- // Set of equality elements associated with this expression. '_collator' is used as a comparator
- // for this set.
- BSONElementSet _equalitySet;
+ // Comparator used to compare elements. By default, simple binary comparison will be used.
+ BSONElementComparator _eltCmp;
// Original container of equality elements, including duplicates. Needed for re-computing
// '_equalitySet' in case '_collator' changes after elements have been added.
std::vector<BSONElement> _originalEqualityVector;
+ // Set of equality elements associated with this expression. '_eltCmp' is used as a comparator
+ // for this set.
+ BSONEltFlatSet _equalitySet;
+
// Container of regex elements this object owns.
std::vector<std::unique_ptr<RegexMatchExpression>> _regexes;
};
diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp
index 293ed59c85c..1c3f7795a95 100644
--- a/src/mongo/db/matcher/expression_leaf_test.cpp
+++ b/src/mongo/db/matcher/expression_leaf_test.cpp
@@ -1501,7 +1501,8 @@ TEST(InMatchExpression, MatchesElementSingle) {
BSONObj match = BSON("a" << 1);
BSONObj notMatch = BSON("a" << 2);
InMatchExpression in;
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesSingleElement(match["a"]));
ASSERT(!in.matchesSingleElement(notMatch["a"]));
}
@@ -1519,10 +1520,8 @@ TEST(InMatchExpression, MatchesEmpty) {
TEST(InMatchExpression, MatchesElementMultiple) {
BSONObj operand = BSON_ARRAY(1 << "r" << true << 1);
InMatchExpression in;
- in.addEquality(operand[0]);
- in.addEquality(operand[1]);
- in.addEquality(operand[2]);
- in.addEquality(operand[3]);
+ std::vector<BSONElement> equalities{operand[0], operand[1], operand[2], operand[3]};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
BSONObj matchFirst = BSON("a" << 1);
BSONObj matchSecond = BSON("a"
@@ -1540,7 +1539,8 @@ TEST(InMatchExpression, MatchesScalar) {
BSONObj operand = BSON_ARRAY(5);
InMatchExpression in;
in.init("a");
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSON("a" << 5.0), NULL));
ASSERT(!in.matchesBSON(BSON("a" << 4), NULL));
@@ -1550,7 +1550,8 @@ TEST(InMatchExpression, MatchesArrayValue) {
BSONObj operand = BSON_ARRAY(5);
InMatchExpression in;
in.init("a");
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSON("a" << BSON_ARRAY(5.0 << 6)), NULL));
ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(6 << 7)), NULL));
@@ -1562,7 +1563,8 @@ TEST(InMatchExpression, MatchesNull) {
InMatchExpression in;
in.init("a");
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSONObj(), NULL));
ASSERT(in.matchesBSON(BSON("a" << BSONNULL), NULL));
@@ -1576,15 +1578,16 @@ TEST(InMatchExpression, MatchesUndefined) {
InMatchExpression in;
in.init("a");
- Status s = in.addEquality(operand.firstElement());
- ASSERT_NOT_OK(s);
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_NOT_OK(in.setEqualities(std::move(equalities)));
}
TEST(InMatchExpression, MatchesMinKey) {
BSONObj operand = BSON_ARRAY(MinKey);
InMatchExpression in;
in.init("a");
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSON("a" << MinKey), NULL));
ASSERT(!in.matchesBSON(BSON("a" << MaxKey), NULL));
@@ -1595,7 +1598,8 @@ TEST(InMatchExpression, MatchesMaxKey) {
BSONObj operand = BSON_ARRAY(MaxKey);
InMatchExpression in;
in.init("a");
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSON("a" << MaxKey), NULL));
ASSERT(!in.matchesBSON(BSON("a" << MinKey), NULL));
@@ -1606,9 +1610,8 @@ TEST(InMatchExpression, MatchesFullArray) {
BSONObj operand = BSON_ARRAY(BSON_ARRAY(1 << 2) << 4 << 5);
InMatchExpression in;
in.init("a");
- in.addEquality(operand[0]);
- in.addEquality(operand[1]);
- in.addEquality(operand[2]);
+ std::vector<BSONElement> equalities{operand[0], operand[1], operand[2]};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2)), NULL));
ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3)), NULL));
@@ -1620,8 +1623,8 @@ TEST(InMatchExpression, ElemMatchKey) {
BSONObj operand = BSON_ARRAY(5 << 2);
InMatchExpression in;
in.init("a");
- in.addEquality(operand[0]);
- in.addEquality(operand[1]);
+ std::vector<BSONElement> equalities{operand[0], operand[1]};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
MatchDetails details;
details.requestElemMatchKey();
@@ -1639,7 +1642,8 @@ TEST(InMatchExpression, InMatchExpressionsWithDifferentNumbersOfElementsAreUnequ
<< "string");
InMatchExpression eq1;
InMatchExpression eq2;
- eq1.addEquality(obj.firstElement());
+ std::vector<BSONElement> equalities{obj.firstElement()};
+ ASSERT_OK(eq1.setEqualities(std::move(equalities)));
ASSERT(!eq1.equivalent(&eq2));
}
@@ -1675,8 +1679,12 @@ TEST(InMatchExpression, InMatchExpressionsWithCollationEquivalentElementsAreEqua
InMatchExpression eq2;
eq2.setCollator(&collator2);
- eq1.addEquality(obj1.firstElement());
- eq2.addEquality(obj2.firstElement());
+ std::vector<BSONElement> equalities1{obj1.firstElement()};
+ ASSERT_OK(eq1.setEqualities(std::move(equalities1)));
+
+ std::vector<BSONElement> equalities2{obj2.firstElement()};
+ ASSERT_OK(eq2.setEqualities(std::move(equalities2)));
+
ASSERT(eq1.equivalent(&eq2));
}
@@ -1692,8 +1700,12 @@ TEST(InMatchExpression, InMatchExpressionsWithCollationNonEquivalentElementsAreU
InMatchExpression eq2;
eq2.setCollator(&collator2);
- eq1.addEquality(obj1.firstElement());
- eq2.addEquality(obj2.firstElement());
+ std::vector<BSONElement> equalities1{obj1.firstElement()};
+ ASSERT_OK(eq1.setEqualities(std::move(equalities1)));
+
+ std::vector<BSONElement> equalities2{obj2.firstElement()};
+ ASSERT_OK(eq2.setEqualities(std::move(equalities2)));
+
ASSERT(!eq1.equivalent(&eq2));
}
@@ -1702,7 +1714,8 @@ TEST(InMatchExpression, StringMatchingWithNullCollatorUsesBinaryComparison) {
BSONObj notMatch = BSON("a"
<< "string2");
InMatchExpression in;
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(!in.matchesSingleElement(notMatch["a"]));
}
@@ -1713,7 +1726,8 @@ TEST(InMatchExpression, StringMatchingRespectsCollation) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
InMatchExpression in;
in.setCollator(&collator);
- in.addEquality(operand.firstElement());
+ std::vector<BSONElement> equalities{operand.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.matchesSingleElement(match["a"]));
}
@@ -1726,8 +1740,8 @@ TEST(InMatchExpression, ChangingCollationAfterAddingEqualitiesPreservesEqualitie
CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString);
InMatchExpression in;
in.setCollator(&collatorAlwaysEqual);
- in.addEquality(obj1.firstElement());
- in.addEquality(obj2.firstElement());
+ std::vector<BSONElement> equalities{obj1.firstElement(), obj2.firstElement()};
+ ASSERT_OK(in.setEqualities(std::move(equalities)));
ASSERT(in.getEqualities().size() == 1);
in.setCollator(&collatorReverseString);
ASSERT(in.getEqualities().size() == 2);
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 2d1399edcf9..9bfc2d75f3e 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -588,6 +588,7 @@ Status MatchExpressionParser::_parseInExpression(InMatchExpression* inExpression
const BSONObj& theArray,
const CollatorInterface* collator) {
inExpression->setCollator(collator);
+ std::vector<BSONElement> equalities;
BSONObjIterator i(theArray);
while (i.more()) {
BSONElement e = i.next();
@@ -606,12 +607,10 @@ Status MatchExpressionParser::_parseInExpression(InMatchExpression* inExpression
if (!s.isOK())
return s;
} else {
- Status s = inExpression->addEquality(e);
- if (!s.isOK())
- return s;
+ equalities.push_back(e);
}
}
- return Status::OK();
+ return inExpression->setEqualities(std::move(equalities));
}
StatusWithMatchExpression MatchExpressionParser::_parseType(const char* name,
diff --git a/src/mongo/db/mongod_options.cpp b/src/mongo/db/mongod_options.cpp
index 307d6ace41a..c4b2283ef40 100644
--- a/src/mongo/db/mongod_options.cpp
+++ b/src/mongo/db/mongod_options.cpp
@@ -58,6 +58,7 @@ using std::cout;
using std::endl;
using std::string;
+
MongodGlobalParams mongodGlobalParams;
extern DiagLog _diaglog;
@@ -685,14 +686,13 @@ Status validateMongodOptions(const moe::Environment& params) {
}
}
- bool isClusterRoleShard = false;
+ bool isClusterRoleShard = params.count("shardsvr");
if (params.count("sharding.clusterRole")) {
auto clusterRole = params["sharding.clusterRole"].as<std::string>();
- isClusterRoleShard = (clusterRole == "shardsvr");
+ isClusterRoleShard = isClusterRoleShard || (clusterRole == "shardsvr");
}
- if ((isClusterRoleShard || params.count("shardsvr")) &&
- !params.count("sharding._overrideShardIdentity")) {
+ if (isClusterRoleShard && !params.count("sharding._overrideShardIdentity")) {
return Status(
ErrorCodes::BadValue,
"shardsvr cluster role with queryableBackupMode requires _overrideShardIdentity");
@@ -1328,6 +1328,21 @@ Status storeMongodOptions(const moe::Environment& params) {
log() << endl;
}
+ bool isClusterRoleShard = params.count("shardsvr");
+ bool isClusterRoleConfig = params.count("configsvr");
+ if (params.count("sharding.clusterRole")) {
+ auto clusterRole = params["sharding.clusterRole"].as<std::string>();
+ isClusterRoleShard = isClusterRoleShard || (clusterRole == "shardsvr");
+ isClusterRoleConfig = isClusterRoleConfig || (clusterRole == "configsvr");
+ }
+
+ if ((isClusterRoleShard || isClusterRoleConfig) && skipShardingConfigurationChecks) {
+ auto clusterRoleStr = isClusterRoleConfig ? "--configsvr" : "--shardsvr";
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Can not specify " << clusterRoleStr
+ << " and set skipShardingConfigurationChecks=true");
+ }
+
setGlobalReplSettings(replSettings);
return Status::OK();
}
diff --git a/src/mongo/db/mongod_options.h b/src/mongo/db/mongod_options.h
index 27fb778cfdf..481edacc96f 100644
--- a/src/mongo/db/mongod_options.h
+++ b/src/mongo/db/mongod_options.h
@@ -50,6 +50,8 @@ struct MongodGlobalParams {
MongodGlobalParams() : scriptingEnabled(true) {}
};
+extern bool skipShardingConfigurationChecks;
+
extern MongodGlobalParams mongodGlobalParams;
Status addMongodOptions(moe::OptionSection* options);
diff --git a/src/mongo/db/namespace_string-inl.h b/src/mongo/db/namespace_string-inl.h
index 0ff45fca39e..7ad830f2481 100644
--- a/src/mongo/db/namespace_string-inl.h
+++ b/src/mongo/db/namespace_string-inl.h
@@ -118,7 +118,7 @@ inline bool NamespaceString::validCollectionName(StringData coll) {
return true;
}
-inline NamespaceString::NamespaceString() : _ns(), _dotIndex(0) {}
+inline NamespaceString::NamespaceString() : _ns(), _dotIndex(std::string::npos) {}
inline NamespaceString::NamespaceString(StringData nsIn) {
_ns = nsIn.toString(); // copy to our buffer
_dotIndex = _ns.find('.');
diff --git a/src/mongo/db/namespace_string_test.cpp b/src/mongo/db/namespace_string_test.cpp
index 69884bd5ca9..69742c797bf 100644
--- a/src/mongo/db/namespace_string_test.cpp
+++ b/src/mongo/db/namespace_string_test.cpp
@@ -272,4 +272,16 @@ TEST(NamespaceStringTest, makeListIndexesNSIsCorrect) {
ASSERT(ns.isListIndexesCursorNS());
ASSERT_EQUALS(NamespaceString("DB.COLL"), ns.getTargetNSForListIndexes());
}
+
+TEST(NamespaceStringTest, EmptyNSStringReturnsEmptyColl) {
+ NamespaceString nss{};
+ ASSERT_TRUE(nss.toString().empty());
+ ASSERT_EQ(nss.coll(), StringData{});
+}
+
+TEST(NamespaceStringTest, EmptyNSStringReturnsEmptyDb) {
+ NamespaceString nss{};
+ ASSERT_TRUE(nss.toString().empty());
+ ASSERT_EQ(nss.db(), StringData{});
+}
}
diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp
index 00560851a7a..91bc3317d48 100644
--- a/src/mongo/db/op_observer_impl.cpp
+++ b/src/mongo/db/op_observer_impl.cpp
@@ -42,9 +42,12 @@
#include "mongo/db/server_options.h"
#include "mongo/db/views/durable_view_catalog.h"
#include "mongo/scripting/engine.h"
+#include "mongo/util/fail_point_service.h"
namespace mongo {
+MONGO_FP_DECLARE(failCollectionUpdates);
+
void OpObserverImpl::onCreateIndex(OperationContext* txn,
const std::string& ns,
BSONObj indexDoc,
@@ -80,7 +83,7 @@ void OpObserverImpl::onInserts(OperationContext* txn,
if (nss.ns() == FeatureCompatibilityVersion::kCollection) {
for (auto it = begin; it != end; it++) {
- FeatureCompatibilityVersion::onInsertOrUpdate(*it);
+ FeatureCompatibilityVersion::onInsertOrUpdate(txn, *it);
}
}
@@ -94,6 +97,20 @@ void OpObserverImpl::onInserts(OperationContext* txn,
}
void OpObserverImpl::onUpdate(OperationContext* txn, const OplogUpdateEntryArgs& args) {
+ MONGO_FAIL_POINT_BLOCK(failCollectionUpdates, extraData) {
+ auto collElem = extraData.getData()["collectionNS"];
+ // If the failpoint specifies no collection or matches the existing one, fail.
+ if (!collElem || args.ns == collElem.String()) {
+ uasserted(40654,
+ str::stream() << "failCollectionUpdates failpoint enabled, namespace: "
+ << args.ns
+ << ", update: "
+ << args.update
+ << " on document with "
+ << args.criteria);
+ }
+ }
+
// Do not log a no-op operation; see SERVER-21738
if (args.update.isEmpty()) {
return;
@@ -119,7 +136,7 @@ void OpObserverImpl::onUpdate(OperationContext* txn, const OplogUpdateEntryArgs&
}
if (args.ns == FeatureCompatibilityVersion::kCollection) {
- FeatureCompatibilityVersion::onInsertOrUpdate(args.updatedDoc);
+ FeatureCompatibilityVersion::onInsertOrUpdate(txn, args.updatedDoc);
}
}
@@ -162,7 +179,7 @@ void OpObserverImpl::onDelete(OperationContext* txn,
DurableViewCatalog::onExternalChange(txn, ns);
}
if (ns.ns() == FeatureCompatibilityVersion::kCollection) {
- FeatureCompatibilityVersion::onDelete(deleteState.idDoc);
+ FeatureCompatibilityVersion::onDelete(txn, deleteState.idDoc);
}
}
@@ -221,7 +238,7 @@ void OpObserverImpl::onDropDatabase(OperationContext* txn, const std::string& db
repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false);
if (NamespaceString(dbName).db() == FeatureCompatibilityVersion::kDatabase) {
- FeatureCompatibilityVersion::onDropCollection();
+ FeatureCompatibilityVersion::onDropCollection(txn);
}
getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr);
@@ -243,7 +260,7 @@ void OpObserverImpl::onDropCollection(OperationContext* txn,
}
if (collectionName.ns() == FeatureCompatibilityVersion::kCollection) {
- FeatureCompatibilityVersion::onDropCollection();
+ FeatureCompatibilityVersion::onDropCollection(txn);
}
getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr);
@@ -276,11 +293,10 @@ void OpObserverImpl::onRenameCollection(OperationContext* txn,
<< dropTarget);
repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false);
- if (fromCollection.coll() == DurableViewCatalog::viewsCollectionName() ||
- toCollection.coll() == DurableViewCatalog::viewsCollectionName()) {
- DurableViewCatalog::onExternalChange(
- txn, NamespaceString(DurableViewCatalog::viewsCollectionName()));
- }
+ if (fromCollection.isSystemDotViews())
+ DurableViewCatalog::onExternalChange(txn, fromCollection);
+ if (toCollection.isSystemDotViews())
+ DurableViewCatalog::onExternalChange(txn, toCollection);
getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr);
logOpForDbHash(txn, dbName.c_str());
diff --git a/src/mongo/db/operation_context.cpp b/src/mongo/db/operation_context.cpp
index 8151fe97dfa..41b54eb6611 100644
--- a/src/mongo/db/operation_context.cpp
+++ b/src/mongo/db/operation_context.cpp
@@ -325,7 +325,7 @@ StatusWith<stdx::cv_status> OperationContext::waitForConditionOrInterruptNoAsser
deadline = std::min(deadline, getDeadline());
}
- const auto waitStatus = [&] {
+ const auto waitStatus = [&]() -> stdx::cv_status {
if (Date_t::max() == deadline) {
cv.wait(m);
return stdx::cv_status::no_timeout;
diff --git a/src/mongo/db/operation_context.h b/src/mongo/db/operation_context.h
index 2c8be360a77..79455e47309 100644
--- a/src/mongo/db/operation_context.h
+++ b/src/mongo/db/operation_context.h
@@ -202,6 +202,40 @@ public:
}
/**
+ * Waits on condition "cv" for "pred" until "pred" returns true, or the given "deadline"
+ * expires, or this operation is interrupted, or this operation's own deadline expires.
+ *
+ *
+ * If the operation deadline expires or the operation is interrupted, throws a DBException. If
+ * the given "deadline" expires, returns pred. Otherwise, returns true.
+ */
+ template <typename Pred>
+ bool waitForConditionOrInterruptUntilPred(stdx::condition_variable& cv,
+ stdx::unique_lock<stdx::mutex>& m,
+ Date_t deadline,
+ Pred pred) {
+ while (!pred()) {
+ if (stdx::cv_status::timeout == waitForConditionOrInterruptUntil(cv, m, deadline)) {
+ return pred();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Same as the predicate form of waitForConditionOrInterruptUntilPred, but takes a relative
+ * amount of time to wait instead of an absolute time point.
+ */
+ template <typename Pred>
+ bool waitForConditionOrInterruptFor(stdx::condition_variable& cv,
+ stdx::unique_lock<stdx::mutex>& m,
+ Milliseconds duration,
+ Pred pred) {
+ return waitForConditionOrInterruptUntilPred(
+ cv, m, getServiceContext()->getPreciseClockSource()->now() + duration, pred);
+ }
+
+ /**
* Same as waitForConditionOrInterruptUntil, except returns StatusWith<stdx::cv_status> and
* non-ok status indicates the error instead of a DBException.
*/
diff --git a/src/mongo/db/operation_context_test.cpp b/src/mongo/db/operation_context_test.cpp
index 06c6575208a..ca77b8c316b 100644
--- a/src/mongo/db/operation_context_test.cpp
+++ b/src/mongo/db/operation_context_test.cpp
@@ -231,7 +231,7 @@ public:
Date_t maxTime) {
auto barrier = std::make_shared<unittest::Barrier>(2);
- auto task = stdx::packaged_task<stdx::cv_status()>([=] {
+ auto task = stdx::packaged_task<stdx::cv_status()>([=]() -> stdx::cv_status {
if (maxTime < Date_t::max()) {
txn->setDeadlineByDate(maxTime);
}
diff --git a/src/mongo/db/ops/delete.cpp b/src/mongo/db/ops/delete.cpp
index 7f509308ad4..6a7da15ee74 100644
--- a/src/mongo/db/ops/delete.cpp
+++ b/src/mongo/db/ops/delete.cpp
@@ -63,19 +63,11 @@ long long deleteObjects(OperationContext* txn,
ParsedDelete parsedDelete(txn, &request);
uassertStatusOK(parsedDelete.parseRequest());
- auto client = txn->getClient();
- auto lastOpAtOperationStart = repl::ReplClientInfo::forClient(client).getLastOp();
-
std::unique_ptr<PlanExecutor> exec = uassertStatusOK(
getExecutorDelete(txn, &CurOp::get(txn)->debug(), collection, &parsedDelete));
uassertStatusOK(exec->executePlan());
- // No-ops need to reset lastOp in the client, for write concern.
- if (repl::ReplClientInfo::forClient(client).getLastOp() == lastOpAtOperationStart) {
- repl::ReplClientInfo::forClient(client).setLastOpToSystemLastOpTime(txn);
- }
-
return DeleteStage::getNumDeleted(*exec);
}
diff --git a/src/mongo/db/ops/modifier_add_to_set.cpp b/src/mongo/db/ops/modifier_add_to_set.cpp
index a565800d749..416d487e0f4 100644
--- a/src/mongo/db/ops/modifier_add_to_set.cpp
+++ b/src/mongo/db/ops/modifier_add_to_set.cpp
@@ -225,6 +225,8 @@ Status ModifierAddToSet::prepare(mb::Element root, StringData matchedField, Exec
// Locate the field name in 'root'.
Status status = pathsupport::findLongestPrefix(
_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -248,6 +250,12 @@ Status ModifierAddToSet::prepare(mb::Element root, StringData matchedField, Exec
if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
// If no target element exists, we will simply be creating a new array.
_preparedState->addAll = true;
+
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_add_to_set_test.cpp b/src/mongo/db/ops/modifier_add_to_set_test.cpp
index 235a65239e6..63d5a7136d4 100644
--- a/src/mongo/db/ops/modifier_add_to_set_test.cpp
+++ b/src/mongo/db/ops/modifier_add_to_set_test.cpp
@@ -459,4 +459,41 @@ TEST(Collation, AddToSetWithEachRespectsCollation) {
ASSERT_EQUALS(doc, fromjson("{ a : ['abc', 'bdc'] }"));
}
+
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$addToSet: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$addToSet: {'a.0.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$addToSet: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
} // namespace
diff --git a/src/mongo/db/ops/modifier_bit.cpp b/src/mongo/db/ops/modifier_bit.cpp
index fb7ae3f45eb..99296911e95 100644
--- a/src/mongo/db/ops/modifier_bit.cpp
+++ b/src/mongo/db/ops/modifier_bit.cpp
@@ -164,6 +164,8 @@ Status ModifierBit::prepare(mutablebson::Element root,
// Locate the field name in 'root'.
Status status = pathsupport::findLongestPrefix(
_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
@@ -189,6 +191,12 @@ Status ModifierBit::prepare(mutablebson::Element root,
// If no target element exists, the value we will write is the result of applying
// the operation to a zero-initialized integer element.
_preparedState->newValue = apply(SafeNum(static_cast<int32_t>(0)));
+
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_bit_test.cpp b/src/mongo/db/ops/modifier_bit_test.cpp
index dbc4d7ba0b2..5b8b0131ea7 100644
--- a/src/mongo/db/ops/modifier_bit_test.cpp
+++ b/src/mongo/db/ops/modifier_bit_test.cpp
@@ -755,4 +755,41 @@ TEST(DbUpdateTests, Bit1_4_Combined) {
ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 | 2) & 8))), logDoc);
}
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$bit: {'a.1.c': {and: NumberInt(1)}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: NumberInt(0)}]}"));
+ Mod mod(fromjson("{$bit: {'a.0.c': {or: NumberInt(1)}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$bit: {'a.1.c': {and: NumberInt(1)}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
} // namespace
diff --git a/src/mongo/db/ops/modifier_compare.cpp b/src/mongo/db/ops/modifier_compare.cpp
index 4e366f90ee2..7db7abd09ca 100644
--- a/src/mongo/db/ops/modifier_compare.cpp
+++ b/src/mongo/db/ops/modifier_compare.cpp
@@ -110,6 +110,8 @@ Status ModifierCompare::prepare(mutablebson::Element root,
// be created during the apply.
Status status = pathsupport::findLongestPrefix(
_updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -128,6 +130,11 @@ Status ModifierCompare::prepare(mutablebson::Element root,
_preparedState->idxFound == (_updatePath.numParts() - 1));
if (!destExists) {
execInfo->noOp = false;
+
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
} else {
const int compareVal =
_preparedState->elemFound.compareWithBSONElement(_val, _collator, false);
diff --git a/src/mongo/db/ops/modifier_compare_test.cpp b/src/mongo/db/ops/modifier_compare_test.cpp
index 9bc036b776b..edaba6a2a68 100644
--- a/src/mongo/db/ops/modifier_compare_test.cpp
+++ b/src/mongo/db/ops/modifier_compare_test.cpp
@@ -341,4 +341,41 @@ TEST(Collation, MaxRespectsCollationFromSetCollator) {
ASSERT_OK(mod.apply());
ASSERT_EQUALS(fromjson("{a : 'abd'}"), doc);
}
+
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$min: {'a.0.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
} // namespace
diff --git a/src/mongo/db/ops/modifier_current_date.cpp b/src/mongo/db/ops/modifier_current_date.cpp
index cd328f5fe94..4496e0cd2cd 100644
--- a/src/mongo/db/ops/modifier_current_date.cpp
+++ b/src/mongo/db/ops/modifier_current_date.cpp
@@ -161,6 +161,8 @@ Status ModifierCurrentDate::prepare(mutablebson::Element root,
// be created during the apply.
Status status = pathsupport::findLongestPrefix(
_updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -175,6 +177,14 @@ Status ModifierCurrentDate::prepare(mutablebson::Element root,
// there is any conflict among mods.
execInfo->fieldRef[0] = &_updatePath;
+ if (!_preparedState->elemFound.ok() ||
+ _preparedState->idxFound < (_updatePath.numParts() - 1)) {
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_current_date_test.cpp b/src/mongo/db/ops/modifier_current_date_test.cpp
index 81bcd4d04e0..a3746e445da 100644
--- a/src/mongo/db/ops/modifier_current_date_test.cpp
+++ b/src/mongo/db/ops/modifier_current_date_test.cpp
@@ -359,4 +359,41 @@ TEST(DottedTimestampInput, EmptyStartDoc) {
validateOplogEntry(oplogFormat, logDoc);
}
+TEST(BoolInput, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$currentDate: {'a.1.c': true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(BoolInput, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$currentDate: {'a.0.c': true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(BoolInput, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$currentDate: {'a.1.c': true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
} // namespace
diff --git a/src/mongo/db/ops/modifier_inc.cpp b/src/mongo/db/ops/modifier_inc.cpp
index 314ac6a5024..f2d51b390da 100644
--- a/src/mongo/db/ops/modifier_inc.cpp
+++ b/src/mongo/db/ops/modifier_inc.cpp
@@ -135,6 +135,8 @@ Status ModifierInc::prepare(mutablebson::Element root,
// be created during the apply.
Status status = pathsupport::findLongestPrefix(
_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -166,6 +168,11 @@ Status ModifierInc::prepare(mutablebson::Element root,
if (_mode == MODE_MUL)
_preparedState->newValue *= SafeNum(static_cast<int32_t>(0));
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_inc_test.cpp b/src/mongo/db/ops/modifier_inc_test.cpp
index 02fbbd4474a..3784abbcdff 100644
--- a/src/mongo/db/ops/modifier_inc_test.cpp
+++ b/src/mongo/db/ops/modifier_inc_test.cpp
@@ -630,4 +630,41 @@ TEST(Multiplication, ApplyMissingElementDouble) {
ASSERT_EQUALS(mongo::NumberDouble, doc.root().rightChild().getType());
}
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$inc: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$inc: {'a.0.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$inc: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
} // namespace
diff --git a/src/mongo/db/ops/modifier_interface.h b/src/mongo/db/ops/modifier_interface.h
index d833dcf07a1..2294802e18d 100644
--- a/src/mongo/db/ops/modifier_interface.h
+++ b/src/mongo/db/ops/modifier_interface.h
@@ -205,11 +205,17 @@ struct ModifierInterface::ExecInfo {
ExecInfo() : noOp(false), context(ANY_CONTEXT) {
for (int i = 0; i < MAX_NUM_FIELDS; i++) {
fieldRef[i] = NULL;
+ indexOfArrayWithNewElement[i] = boost::none;
}
}
// The fields of concern to the driver: no other op may modify the fields listed here.
FieldRef* fieldRef[MAX_NUM_FIELDS]; // not owned here
+
+ // For each modified field ref, the index of the path component representing an existing array
+ // that gained a new element.
+ boost::optional<size_t> indexOfArrayWithNewElement[MAX_NUM_FIELDS];
+
bool noOp;
UpdateContext context;
};
diff --git a/src/mongo/db/ops/modifier_push.cpp b/src/mongo/db/ops/modifier_push.cpp
index 8bc671ecf46..ef19e45c8a2 100644
--- a/src/mongo/db/ops/modifier_push.cpp
+++ b/src/mongo/db/ops/modifier_push.cpp
@@ -434,6 +434,8 @@ Status ModifierPush::prepare(mutablebson::Element root,
// be created during the apply.
Status status = pathsupport::findLongestPrefix(
_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -463,6 +465,13 @@ Status ModifierPush::prepare(mutablebson::Element root,
// there is any conflict among mods.
execInfo->fieldRef[0] = &_fieldRef;
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_push_test.cpp b/src/mongo/db/ops/modifier_push_test.cpp
index 49b9d01b002..a7de5d9f0f0 100644
--- a/src/mongo/db/ops/modifier_push_test.cpp
+++ b/src/mongo/db/ops/modifier_push_test.cpp
@@ -1473,4 +1473,87 @@ TEST(ToPosition, Back) {
ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
}
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$push: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareReportCreatedArrayElementPushAll) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ auto modObj = fromjson("{$pushAll: {'a.1.c': [2]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod mod(fromjson("{$push: {'a.0.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElementPushAll) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ auto modObj = fromjson("{$pushAll: {'a.0.c': [2]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod mod(fromjson("{$push: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectFieldPushAll) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ auto modObj = fromjson("{$pushAll: {'a.1.c': [2]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_set.cpp b/src/mongo/db/ops/modifier_set.cpp
index 59f59c555ce..03b703b860c 100644
--- a/src/mongo/db/ops/modifier_set.cpp
+++ b/src/mongo/db/ops/modifier_set.cpp
@@ -127,6 +127,8 @@ Status ModifierSet::prepare(mutablebson::Element root,
// be created during the apply.
Status status = pathsupport::findLongestPrefix(
_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ const auto elemFoundIsArray =
+ _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
// FindLongestPrefix may say the path does not exist at all, which is fine here, or
// that the path was not viable or otherwise wrong, in which case, the mod cannot
@@ -155,6 +157,10 @@ Status ModifierSet::prepare(mutablebson::Element root,
// If the field path is not fully present, then this mod cannot be in place, nor is it a noOp.
if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ if (elemFoundIsArray) {
+ // Report that an existing array will gain a new element as a result of this mod.
+ execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
+ }
return Status::OK();
}
diff --git a/src/mongo/db/ops/modifier_set_test.cpp b/src/mongo/db/ops/modifier_set_test.cpp
index 1579b096722..b83270da957 100644
--- a/src/mongo/db/ops/modifier_set_test.cpp
+++ b/src/mongo/db/ops/modifier_set_test.cpp
@@ -429,6 +429,43 @@ TEST(IndexedMod, PrepareNonViablePath) {
ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
}
+TEST(IndexedMod, PrepareReportCreatedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod setMod(fromjson("{$set: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ Mod setMod(fromjson("{$set: {'a.0.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
+ Document doc(fromjson("{a: {'0': {b: 0}}}"));
+ Mod setMod(fromjson("{$set: {'a.1.c': 2}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
+ ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
+ ASSERT_FALSE(execInfo.noOp);
+}
+
TEST(IndexedMod, PrepareApplyInPlace) {
Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp
index 5e0763f9eac..bc3c595dc6c 100644
--- a/src/mongo/db/ops/update.cpp
+++ b/src/mongo/db/ops/update.cpp
@@ -63,20 +63,9 @@ UpdateResult update(OperationContext* txn, Database* db, const UpdateRequest& re
// Explain should never use this helper.
invariant(!request.isExplain());
- auto client = txn->getClient();
- auto lastOpAtOperationStart = repl::ReplClientInfo::forClient(client).getLastOp();
- ScopeGuard lastOpSetterGuard = MakeObjGuard(repl::ReplClientInfo::forClient(client),
- &repl::ReplClientInfo::setLastOpToSystemLastOpTime,
- txn);
-
const NamespaceString& nsString = request.getNamespaceString();
Collection* collection = db->getCollection(nsString.ns());
- // If this is the local database, don't set last op.
- if (db->name() == "local") {
- lastOpSetterGuard.Dismiss();
- }
-
// The update stage does not create its own collection. As such, if the update is
// an upsert, create the collection that the update stage inserts into beforehand.
if (!collection && request.isUpsert()) {
@@ -116,12 +105,6 @@ UpdateResult update(OperationContext* txn, Database* db, const UpdateRequest& re
uassertStatusOK(getExecutorUpdate(txn, nullOpDebug, collection, &parsedUpdate));
uassertStatusOK(exec->executePlan());
- if (repl::ReplClientInfo::forClient(client).getLastOp() != lastOpAtOperationStart) {
- // If this operation has already generated a new lastOp, don't bother setting it here.
- // No-op updates will not generate a new lastOp, so we still need the guard to fire in that
- // case.
- lastOpSetterGuard.Dismiss();
- }
const UpdateStats* updateStats = UpdateStage::getUpdateStats(exec.get());
diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp
index e5a63d64ccb..dc47ca4175f 100644
--- a/src/mongo/db/ops/update_driver.cpp
+++ b/src/mongo/db/ops/update_driver.cpp
@@ -291,11 +291,21 @@ Status UpdateDriver::update(StringData matchedField,
// is not a no-op and it is in a valid context -- then we switch back to a
// non-in-place mode.
//
- // TODO: make mightBeIndexed and fieldRef like each other.
- if (!_affectIndices && !execInfo.noOp && _indexedFields &&
- _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
- _affectIndices = true;
- doc->disableInPlaceUpdates();
+ // To determine if indexes are affected: If we did not create a new element in an array,
+ // check whether the full path affects indexes. If we did create a new element in an
+ // array, check whether the array itself might affect any indexes. This is necessary
+ // because if there is an index {"a.b": 1}, and we set "a.1.c" and implicitly create an
+ // array element in "a", then we may need to add a null key to the index {"a.b": 1},
+ // even though "a.1.c" does not appear to affect the index.
+ if (!_affectIndices && !execInfo.noOp && _indexedFields) {
+ auto pathLengthForIndexCheck = execInfo.indexOfArrayWithNewElement[i]
+ ? *execInfo.indexOfArrayWithNewElement[i] + 1
+ : execInfo.fieldRef[i]->numParts();
+ if (_indexedFields->mightBeIndexed(
+ execInfo.fieldRef[i]->dottedSubstring(0, pathLengthForIndexCheck))) {
+ _affectIndices = true;
+ doc->disableInPlaceUpdates();
+ }
}
}
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 98a66a02f48..ca8617f3d3d 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -252,8 +252,8 @@ docSourceEnv.Library(
'$BUILD_DIR/mongo/db/matcher/expression_algo',
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/mongo/db/stats/top',
+ '$BUILD_DIR/mongo/db/storage/encryption_hooks',
'$BUILD_DIR/mongo/db/storage/storage_options',
- '$BUILD_DIR/mongo/db/storage/wiredtiger/storage_wiredtiger_customization_hooks',
'$BUILD_DIR/third_party/shim_snappy',
],
LIBDEPS_TAGS=[
diff --git a/src/mongo/db/pipeline/document_source_geo_near.cpp b/src/mongo/db/pipeline/document_source_geo_near.cpp
index 17e9997bbf5..a385eeeb07e 100644
--- a/src/mongo/db/pipeline/document_source_geo_near.cpp
+++ b/src/mongo/db/pipeline/document_source_geo_near.cpp
@@ -150,10 +150,12 @@ BSONObj DocumentSourceGeoNear::buildGeoNearCmd() const {
geoNear.append("minDistance", minDistance);
geoNear.append("query", query);
- if (pExpCtx->getCollator()) {
- geoNear.append("collation", pExpCtx->getCollator()->getSpec().toBSON());
- } else {
- geoNear.append("collation", CollationSpec::kSimpleSpec);
+ if (!pExpCtx->collation.isEmpty()) {
+ if (pExpCtx->getCollator()) {
+ geoNear.append("collation", pExpCtx->getCollator()->getSpec().toBSON());
+ } else {
+ geoNear.append("collation", CollationSpec::kSimpleSpec);
+ }
}
geoNear.append("spherical", spherical);
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
index 69b196faab3..c8d7bf06d7f 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
@@ -46,6 +46,8 @@
namespace mongo {
namespace parsed_aggregation_projection {
+using expression::isPathPrefixOf;
+
//
// ProjectionSpecValidator
//
@@ -54,22 +56,36 @@ Status ProjectionSpecValidator::validate(const BSONObj& spec) {
return ProjectionSpecValidator(spec).validate();
}
-Status ProjectionSpecValidator::ensurePathDoesNotConflictOrThrow(StringData path) {
- for (auto&& seenPath : _seenPaths) {
- if ((path == seenPath) || (expression::isPathPrefixOf(path, seenPath)) ||
- (expression::isPathPrefixOf(seenPath, path))) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "specification contains two conflicting paths. "
- "Cannot specify both '"
- << path
- << "' and '"
- << seenPath
- << "': "
- << _rawObj.toString(),
- 40176);
- }
+Status ProjectionSpecValidator::ensurePathDoesNotConflictOrThrow(const std::string& path) {
+ auto result = _seenPaths.emplace(path);
+ auto pos = result.first;
+
+ // Check whether the path was a duplicate of an existing path.
+ auto conflictingPath = boost::make_optional(!result.second, *pos);
+
+ // Check whether the preceding path prefixes this path.
+ if (!conflictingPath && pos != _seenPaths.begin()) {
+ conflictingPath =
+ boost::make_optional(isPathPrefixOf(*std::prev(pos), path), *std::prev(pos));
+ }
+
+ // Check whether this path prefixes the subsequent path.
+ if (!conflictingPath && std::next(pos) != _seenPaths.end()) {
+ conflictingPath =
+ boost::make_optional(isPathPrefixOf(path, *std::next(pos)), *std::next(pos));
+ }
+
+ if (conflictingPath) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "specification contains two conflicting paths. "
+ "Cannot specify both '"
+ << path
+ << "' and '"
+ << *conflictingPath
+ << "': "
+ << _rawObj.toString(),
+ 40176);
}
- _seenPaths.emplace_back(path.toString());
return Status::OK();
}
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.h b/src/mongo/db/pipeline/parsed_aggregation_projection.h
index 8d92e4ab768..4fd9731774e 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.h
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection.h
@@ -69,7 +69,7 @@ private:
* For example, a user is not allowed to specify {'a': 1, 'a.b': 1}, or some similar conflicting
* paths.
*/
- Status ensurePathDoesNotConflictOrThrow(StringData path);
+ Status ensurePathDoesNotConflictOrThrow(const std::string& path);
/**
* Returns the relevant error if an invalid projection specification is detected.
@@ -99,9 +99,40 @@ private:
// The original object. Used to generate more helpful error messages.
const BSONObj& _rawObj;
+ // Custom comparator that orders fieldpath strings by path prefix first, then by field.
+ struct PathPrefixComparator {
+ static constexpr char dot = '.';
+
+ // Returns true if the lhs value should sort before the rhs, false otherwise.
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ for (size_t pos = 0, len = std::min(lhs.size(), rhs.size()); pos < len; ++pos) {
+ auto &lchar = lhs[pos], &rchar = rhs[pos];
+ if (lchar == rchar) {
+ continue;
+ }
+
+ // Consider the path delimiter '.' as being less than all other characters, so that
+ // paths sort directly before any paths they prefix and directly after any paths
+ // which prefix them.
+ if (lchar == dot) {
+ return true;
+ } else if (rchar == dot) {
+ return false;
+ }
+
+ // Otherwise, default to normal character comparison.
+ return lchar < rchar;
+ }
+
+ // If we get here, then we have reached the end of lhs and/or rhs and all of their path
+ // segments up to this point match. If lhs is shorter than rhs, then lhs prefixes rhs
+ // and should sort before it.
+ return lhs.size() < rhs.size();
+ }
+ };
+
// Tracks which paths we've seen to ensure no two paths conflict with each other.
- // Can be a vector since we iterate through it.
- std::vector<std::string> _seenPaths;
+ std::set<std::string, PathPrefixComparator> _seenPaths;
};
/**
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
index 71dd1c2378f..cb5d50b2771 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
@@ -126,6 +126,75 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectFieldsWithSharedPrefix) {
UserException);
}
+TEST(ParsedAggregationProjectionErrors, ShouldRejectPathConflictsWithNonAlphaNumericCharacters) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ // Include/exclude non-alphanumeric fields with a shared prefix. First assert that the non-
+ // alphanumeric fields are accepted when no prefixes are present.
+ ASSERT(ParsedAggregationProjection::create(
+ expCtx, BSON("a.b-c" << true << "a.b" << true << "a.b?c" << true << "a.b c" << true)));
+ ASSERT(ParsedAggregationProjection::create(
+ expCtx, BSON("a.b c" << false << "a.b?c" << false << "a.b" << false << "a.b-c" << false)));
+
+ // Then assert that we throw when we introduce a prefixed field.
+ ASSERT_THROWS(
+ ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b-c" << true << "a.b" << true << "a.b?c" << true << "a.b c" << true << "a.b.d"
+ << true)),
+ AssertionException);
+ ASSERT_THROWS(
+ ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b.d" << false << "a.b c" << false << "a.b?c" << false << "a.b" << false
+ << "a.b-c"
+ << false)),
+ AssertionException);
+
+ // Adding the same field twice.
+ ASSERT_THROWS(ParsedAggregationProjection::create(
+ expCtx, BSON("a.b?c" << wrapInLiteral(1) << "a.b?c" << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(ParsedAggregationProjection::create(
+ expCtx, BSON("a.b c" << wrapInLiteral(0) << "a.b c" << wrapInLiteral(1))),
+ AssertionException);
+
+ // Mix of include/exclude and adding a shared prefix.
+ ASSERT_THROWS(
+ ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b-c" << true << "a.b" << wrapInLiteral(1) << "a.b?c" << true << "a.b c" << true
+ << "a.b.d"
+ << true)),
+ AssertionException);
+ ASSERT_THROWS(ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b.d" << false << "a.b c" << false << "a.b?c" << false << "a.b"
+ << wrapInLiteral(0)
+ << "a.b-c"
+ << false)),
+ AssertionException);
+
+ // Adding a shared prefix twice.
+ ASSERT_THROWS(ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b-c" << wrapInLiteral(1) << "a.b" << wrapInLiteral(1) << "a.b?c"
+ << wrapInLiteral(1)
+ << "a.b c"
+ << wrapInLiteral(1)
+ << "a.b.d"
+ << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(ParsedAggregationProjection::create(
+ expCtx,
+ BSON("a.b.d" << wrapInLiteral(1) << "a.b c" << wrapInLiteral(1) << "a.b?c"
+ << wrapInLiteral(1)
+ << "a.b"
+ << wrapInLiteral(0)
+ << "a.b-c"
+ << wrapInLiteral(1))),
+ AssertionException);
+}
+
TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfIdAndSubFieldsOfId) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude _id twice.
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index a6c14a5f7dc..521f44cecb8 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -364,47 +364,11 @@ env.CppUnitTest(
env.CppUnitTest(
target="query_planner_test",
source=[
- "query_planner_test.cpp"
- ],
- LIBDEPS=[
- "query_planner_test_fixture",
- ],
-)
-
-env.CppUnitTest(
- target="query_planner_array_test",
- source=[
- "query_planner_array_test.cpp"
- ],
- LIBDEPS=[
- "query_planner_test_fixture",
- ],
-)
-
-env.CppUnitTest(
- target="query_planner_geo_test",
- source=[
- "query_planner_geo_test.cpp"
- ],
- LIBDEPS=[
- "query_planner_test_fixture",
- ],
-)
-
-env.CppUnitTest(
- target="query_planner_partialidx_test",
- source=[
- "query_planner_partialidx_test.cpp"
- ],
- LIBDEPS=[
- "query_planner_test_fixture",
- ],
-)
-
-env.CppUnitTest(
- target="query_planner_collation_test",
- source=[
+ "query_planner_array_test.cpp",
"query_planner_collation_test.cpp",
+ "query_planner_geo_test.cpp",
+ "query_planner_partialidx_test.cpp",
+ "query_planner_test.cpp",
],
LIBDEPS=[
"collation/collator_interface_mock",
diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp
index c7a1f2a05a2..31ba2737ee9 100644
--- a/src/mongo/db/query/canonical_query_test.cpp
+++ b/src/mongo/db/query/canonical_query_test.cpp
@@ -548,7 +548,8 @@ TEST(CanonicalQueryTest, NormalizeWithInPreservesCollator) {
BSONObj obj = fromjson("{'': 'string'}");
auto inMatchExpression = stdx::make_unique<InMatchExpression>();
inMatchExpression->setCollator(&collator);
- inMatchExpression->addEquality(obj.firstElement());
+ std::vector<BSONElement> equalities{obj.firstElement()};
+ ASSERT_OK(inMatchExpression->setEqualities(std::move(equalities)));
unique_ptr<MatchExpression> matchExpression(
CanonicalQuery::normalizeTree(inMatchExpression.release()));
ASSERT(matchExpression->matchType() == MatchExpression::MatchType::EQ);
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index b16ad43dc5e..eeb1c2f1114 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -249,6 +249,14 @@ bool typeMatch(const BSONObj& obj) {
return first.canonicalType() == second.canonicalType();
}
+bool IndexBoundsBuilder::canUseCoveredMatching(const MatchExpression* expr,
+ const IndexEntry& index) {
+ IndexBoundsBuilder::BoundsTightness tightness;
+ OrderedIntervalList oil;
+ translate(expr, BSONElement{}, index, &oil, &tightness);
+ return tightness >= IndexBoundsBuilder::INEXACT_COVERED;
+}
+
// static
void IndexBoundsBuilder::translate(const MatchExpression* expr,
const BSONElement& elt,
diff --git a/src/mongo/db/query/index_bounds_builder.h b/src/mongo/db/query/index_bounds_builder.h
index b397f81b295..311b3ee61f6 100644
--- a/src/mongo/db/query/index_bounds_builder.h
+++ b/src/mongo/db/query/index_bounds_builder.h
@@ -70,6 +70,17 @@ public:
static void allValuesForField(const BSONElement& elt, OrderedIntervalList* out);
/**
+ * Returns true if 'expr' can correctly be assigned as an INEXACT_COVERED predicate to an index
+ * scan over 'index'.
+ *
+ * The result of this function is not meaningful when the predicate applies to special fields
+ * such as "hashed", "2d", or "2dsphere". That is, the caller is responsible for ensuring that
+ * 'expr' is a candidate for covered matching over a regular ascending/descending field of the
+ * index.
+ */
+ static bool canUseCoveredMatching(const MatchExpression* expr, const IndexEntry& index);
+
+ /**
* Turn the MatchExpression in 'expr' into a set of index bounds. The field that 'expr' is
* concerned with is indexed according to the keypattern element 'elt' from index 'index'.
*
diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp
index 45f11a2cc1d..a8ae3651d32 100644
--- a/src/mongo/db/query/index_bounds_builder_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_test.cpp
@@ -2137,4 +2137,54 @@ TEST(IndexBoundsBuilderTest, InWithStringAgainstHashedIndexWithCollatorUsesHashO
ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
}
+TEST(IndexBoundsBuilderTest, CanUseCoveredMatchingForEqualityPredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$eq: 3}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_TRUE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToArrayPredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$eq: [1, 2, 3]}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToNullPredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: null}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForTypeArrayPredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$type: 'array'}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsTruePredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$exists: true}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsFalsePredicate) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$exists: false}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
+TEST(IndexBoundsBuilderTest, CanUseCoveredMatchingForExistsTrueWithSparseIndex) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ testIndex.sparse = true;
+ BSONObj obj = fromjson("{a: {$exists: true}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ ASSERT_TRUE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex));
+}
+
} // namespace
diff --git a/src/mongo/db/query/plan_enumerator.cpp b/src/mongo/db/query/plan_enumerator.cpp
index 789cd405607..de80606df8d 100644
--- a/src/mongo/db/query/plan_enumerator.cpp
+++ b/src/mongo/db/query/plan_enumerator.cpp
@@ -579,6 +579,32 @@ bool PlanEnumerator::prepMemo(MatchExpression* node, PrepMemoContext context) {
return false;
}
+void PlanEnumerator::assignToNonMultikeyMandatoryIndex(
+ const IndexEntry& index,
+ const std::vector<MatchExpression*>& predsOverLeadingField,
+ const IndexToPredMap& idxToNotFirst,
+ OneIndexAssignment* indexAssign) {
+ // Text indexes are typically multikey because there is an index key for each token in the
+ // source text. However, the leading and trailing non-text fields of the index cannot be
+ // multikey. As a result, we should use non-multikey predicate assignment rules for such
+ // indexes.
+ invariant(!index.multikey || index.type == IndexType::INDEX_TEXT);
+
+ // Since the index is not multikey, all predicates over the leading field can be assigned.
+ indexAssign->preds = predsOverLeadingField;
+
+ // Since everything in assign.preds prefixes the index, they all go at position '0' in the
+ // index, the first position.
+ indexAssign->positions.resize(indexAssign->preds.size(), 0);
+
+ // And now we begin compound analysis. Find everything that could use assign.index but isn't a
+ // pred over the first field of that index.
+ auto compIt = idxToNotFirst.find(indexAssign->index);
+ if (compIt != idxToNotFirst.end()) {
+ compound(compIt->second, index, indexAssign);
+ }
+}
+
bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
const IndexToPredMap& idxToNotFirst,
MatchExpression* mandatoryPred,
@@ -613,7 +639,12 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
const vector<MatchExpression*>& predsOverLeadingField = it->second;
- if (thisIndex.multikey && !thisIndex.multikeyPaths.empty()) {
+ // Text indexes should be treated like non-multikey indexes, since the non-text fields are
+ // prohibited from containing arrays.
+ if (thisIndex.type == IndexType::INDEX_TEXT) {
+ assignToNonMultikeyMandatoryIndex(
+ thisIndex, predsOverLeadingField, idxToNotFirst, &indexAssign);
+ } else if (thisIndex.multikey && !thisIndex.multikeyPaths.empty()) {
// 2dsphere indexes are the only special index type that should ever have path-level
// multikey information.
invariant(INDEX_2DSPHERE == thisIndex.type);
@@ -719,22 +750,9 @@ bool PlanEnumerator::enumerateMandatoryIndex(const IndexToPredMap& idxToFirst,
}
}
} else {
- // For non-multikey, we don't have to do anything too special.
- // Just assign all "first" predicates and try to compound like usual.
- indexAssign.preds = it->second;
-
- // Since everything in assign.preds prefixes the index, they all go
- // at position '0' in the index, the first position.
- indexAssign.positions.resize(indexAssign.preds.size(), 0);
-
- // And now we begin compound analysis.
-
- // Find everything that could use assign.index but isn't a pred over
- // the first field of that index.
- IndexToPredMap::const_iterator compIt = idxToNotFirst.find(indexAssign.index);
- if (compIt != idxToNotFirst.end()) {
- compound(compIt->second, thisIndex, &indexAssign);
- }
+ // The index is not multikey.
+ assignToNonMultikeyMandatoryIndex(
+ thisIndex, predsOverLeadingField, idxToNotFirst, &indexAssign);
}
// The mandatory predicate must be assigned.
diff --git a/src/mongo/db/query/plan_enumerator.h b/src/mongo/db/query/plan_enumerator.h
index 5543bc68ca9..a29c1dd4e76 100644
--- a/src/mongo/db/query/plan_enumerator.h
+++ b/src/mongo/db/query/plan_enumerator.h
@@ -434,6 +434,17 @@ private:
AndAssignment* andAssignment);
/**
+ * Assigns predicates in 'predsOverLeadingField' and 'idxToNotFirst' to 'indexAssign'. Assumes
+ * that the index is not multikey. Also assumes that that the index is of a type used to answer
+ * "mandatory predicates" such as text or geoNear.
+ */
+ void assignToNonMultikeyMandatoryIndex(
+ const IndexEntry& index,
+ const std::vector<MatchExpression*>& predsOverLeadingField,
+ const IndexToPredMap& idxToNotFirst,
+ OneIndexAssignment* indexAssign);
+
+ /**
* Try to assign predicates in 'tryCompound' to 'thisIndex' as compound assignments.
* Output the assignments in 'assign'.
*/
diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp
index 9a6f52e18d0..90c4873ffbb 100644
--- a/src/mongo/db/query/planner_access.cpp
+++ b/src/mongo/db/query/planner_access.cpp
@@ -224,6 +224,17 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode(
TextMatchExpressionBase* textExpr = static_cast<TextMatchExpressionBase*>(expr);
TextNode* ret = new TextNode(index);
ret->ftsQuery = textExpr->getFTSQuery().clone();
+
+ // Count the number of prefix fields before the "text" field.
+ for (auto&& keyPatternElt : ret->index.keyPattern) {
+ // We know that the only key pattern with a type of String is the _fts field
+ // which is immediately after all prefix fields.
+ if (BSONType::String == keyPatternElt.type()) {
+ break;
+ }
+ ++(ret->numPrefixFields);
+ }
+
return ret;
} else {
// Note that indexKeyPattern.firstElement().fieldName() may not equal expr->path()
@@ -335,10 +346,24 @@ void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, ScanBuildingSt
const StageType type = node->getType();
- // Text data is covered, but not exactly. Text covering is unlike any other covering
- // so we deal with it in addFilterToSolutionNode.
if (STAGE_TEXT == type) {
- scanState->tightness = IndexBoundsBuilder::INEXACT_COVERED;
+ auto textNode = static_cast<TextNode*>(node);
+
+ if (pos < textNode->numPrefixFields) {
+ // This predicate is assigned to one of the prefix fields of the text index. Such
+ // predicates must always be equalities and must always be attached to the TEXT node. In
+ // order to ensure this happens, we assign INEXACT_COVERED tightness.
+ scanState->tightness = IndexBoundsBuilder::INEXACT_COVERED;
+ } else {
+ // The predicate is assigned to one of the trailing fields of the text index. We
+ // currently don't generate bounds for predicates assigned to trailing fields of a text
+ // index, but rather attempt to attach a covered filter. However, certain predicates can
+ // never be correctly covered (e.g. $exists), so we assign the tightness accordingly.
+ scanState->tightness = IndexBoundsBuilder::canUseCoveredMatching(expr, index)
+ ? IndexBoundsBuilder::INEXACT_COVERED
+ : IndexBoundsBuilder::INEXACT_FETCH;
+ }
+
return;
}
@@ -347,20 +372,23 @@ void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, ScanBuildingSt
if (STAGE_GEO_NEAR_2D == type) {
invariant(INDEX_2D == index.type);
- // 2D indexes are weird - the "2d" field stores a normally-indexed BinData field, but
- // additional array fields are *not* exploded into multi-keys - they are stored directly
- // as arrays in the index. Also, no matter what the index expression, the "2d" field is
- // always first.
- // This means that we can only generically accumulate bounds for 2D indexes over the
- // first "2d" field (pos == 0) - MatchExpressions over other fields in the 2D index may
- // be covered (can be evaluated using only the 2D index key). The additional fields
- // must not affect the index scan bounds, since they are not stored in an
- // IndexScan-compatible format.
+ // 2D indexes have a special format - the "2d" field stores a normally-indexed BinData
+ // field, but additional array fields are *not* exploded into multi-keys - they are stored
+ // directly as arrays in the index. Also, no matter what the index expression, the "2d"
+ // field is always first.
+ //
+ // This means that we can only generically accumulate bounds for 2D indexes over the first
+ // "2d" field (pos == 0) - MatchExpressions over other fields in the 2D index may be covered
+ // (can be evaluated using only the 2D index key). The additional fields must not affect
+ // the index scan bounds, since they are not stored in an IndexScan-compatible format.
if (pos > 0) {
- // Marking this field as covered allows the planner to accumulate a MatchExpression
- // over the returned 2D index keys instead of adding to the index bounds.
- scanState->tightness = IndexBoundsBuilder::INEXACT_COVERED;
+ // The predicate is over a trailing field of the "2d" index. If possible, we assign it
+ // as a covered filter (the INEXACT_COVERED case). Otherwise, the filter must be
+ // evaluated after fetching the full documents.
+ scanState->tightness = IndexBoundsBuilder::canUseCoveredMatching(expr, index)
+ ? IndexBoundsBuilder::INEXACT_COVERED
+ : IndexBoundsBuilder::INEXACT_FETCH;
return;
}
@@ -374,10 +402,15 @@ void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, ScanBuildingSt
verify(type == STAGE_IXSCAN);
IndexScanNode* scan = static_cast<IndexScanNode*>(node);
- // See STAGE_GEO_NEAR_2D above - 2D indexes can only accumulate scan bounds over the
- // first "2d" field (pos == 0)
+ // See STAGE_GEO_NEAR_2D above - 2D indexes can only accumulate scan bounds over the first
+ // "2d" field (pos == 0).
if (INDEX_2D == index.type && pos > 0) {
- scanState->tightness = IndexBoundsBuilder::INEXACT_COVERED;
+ // The predicate is over a trailing field of the "2d" index. If possible, we assign it
+ // as a covered filter (the INEXACT_COVERED case). Otherwise, the filter must be
+ // evaluated after fetching the full documents.
+ scanState->tightness = IndexBoundsBuilder::canUseCoveredMatching(expr, index)
+ ? IndexBoundsBuilder::INEXACT_COVERED
+ : IndexBoundsBuilder::INEXACT_FETCH;
return;
}
@@ -416,25 +449,9 @@ void QueryPlannerAccess::mergeWithLeafNode(MatchExpression* expr, ScanBuildingSt
void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntry& index) {
TextNode* tn = static_cast<TextNode*>(node);
- // Figure out what positions are prefix positions. We build an index key prefix from
- // the predicates over the text index prefix keys.
- // For example, say keyPattern = { a: 1, _fts: "text", _ftsx: 1, b: 1 }
- // prefixEnd should be 1.
- size_t prefixEnd = 0;
- BSONObjIterator it(tn->index.keyPattern);
- // Count how many prefix terms we have.
- while (it.more()) {
- // We know that the only key pattern with a type of String is the _fts field
- // which is immediately after all prefix fields.
- if (String == it.next().type()) {
- break;
- }
- ++prefixEnd;
- }
-
// If there's no prefix, the filter is already on the node and the index prefix is null.
// We can just return.
- if (!prefixEnd) {
+ if (!tn->numPrefixFields) {
return;
}
@@ -448,7 +465,7 @@ void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntr
if (MatchExpression::AND != textFilterMe->matchType()) {
// Only one prefix term.
- invariant(1 == prefixEnd);
+ invariant(1u == tn->numPrefixFields);
// Sanity check: must be an EQ.
invariant(MatchExpression::EQ == textFilterMe->matchType());
@@ -460,10 +477,10 @@ void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntr
// Indexed by the keyPattern position index assignment. We want to add
// prefixes in order but we must order them first.
- vector<MatchExpression*> prefixExprs(prefixEnd, NULL);
+ vector<MatchExpression*> prefixExprs(tn->numPrefixFields, nullptr);
AndMatchExpression* amExpr = static_cast<AndMatchExpression*>(textFilterMe);
- invariant(amExpr->numChildren() >= prefixEnd);
+ invariant(amExpr->numChildren() >= tn->numPrefixFields);
// Look through the AND children. The prefix children we want to
// stash in prefixExprs.
@@ -474,7 +491,7 @@ void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntr
invariant(NULL != ixtag);
// Skip this child if it's not part of a prefix, or if we've already assigned a
// predicate to this prefix position.
- if (ixtag->pos >= prefixEnd || prefixExprs[ixtag->pos] != NULL) {
+ if (ixtag->pos >= tn->numPrefixFields || prefixExprs[ixtag->pos] != NULL) {
++curChild;
continue;
}
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index 321e856d1b7..2bf2eedc25e 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -48,6 +48,28 @@
namespace mongo {
+namespace {
+
+/**
+ * Checks whether the given index is compatible with each child of the given $elemMatch expression.
+ * Assumes that the match expression is of type ELEM_MATCH_VALUE.
+ */
+bool elemMatchValueCompatible(const BSONElement& elt,
+ const IndexEntry& index,
+ MatchExpression* elemMatch,
+ const CollatorInterface* collator) {
+ invariant(elemMatch->matchType() == MatchExpression::ELEM_MATCH_VALUE);
+ for (size_t child = 0; child < elemMatch->numChildren(); ++child) {
+ if (!QueryPlannerIXSelect::compatible(
+ elt, index, elemMatch->getChild(child), collator, true)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
static double fieldWithDefault(const BSONObj& infoObj, const string& name, double def) {
BSONElement e = infoObj[name];
if (e.isNumber()) {
@@ -168,7 +190,8 @@ void QueryPlannerIXSelect::findRelevantIndices(const unordered_set<string>& fiel
bool QueryPlannerIXSelect::compatible(const BSONElement& elt,
const IndexEntry& index,
MatchExpression* node,
- const CollatorInterface* collator) {
+ const CollatorInterface* collator,
+ bool elemMatchChild) {
if ((boundsGeneratingNodeContainsComparisonToType(node, BSONType::String) ||
boundsGeneratingNodeContainsComparisonToType(node, BSONType::Array) ||
boundsGeneratingNodeContainsComparisonToType(node, BSONType::Object)) &&
@@ -195,16 +218,18 @@ bool QueryPlannerIXSelect::compatible(const BSONElement& elt,
MatchExpression::MatchType exprtype = node->matchType();
if (indexedFieldType.empty()) {
- // Can't check for null w/a sparse index.
- if (exprtype == MatchExpression::EQ && index.sparse) {
+ // Can't use a sparse index for $eq with a null element, unless the equality is within a
+ // $elemMatch expression since the latter implies a match on the literal element 'null'.
+ if (exprtype == MatchExpression::EQ && index.sparse && !elemMatchChild) {
const EqualityMatchExpression* expr = static_cast<const EqualityMatchExpression*>(node);
if (expr->getData().isNull()) {
return false;
}
}
- // Can't check for $in w/ null element w/a sparse index.
- if (exprtype == MatchExpression::MATCH_IN && index.sparse) {
+ // Can't use a sparse index for $in with a null element, unless the $eq is within a
+ // $elemMatch expression since the latter implies a match on the literal element 'null'.
+ if (exprtype == MatchExpression::MATCH_IN && index.sparse && !elemMatchChild) {
const InMatchExpression* expr = static_cast<const InMatchExpression*>(node);
if (expr->hasNull()) {
return false;
@@ -220,7 +245,10 @@ bool QueryPlannerIXSelect::compatible(const BSONElement& elt,
// the expression is a NOT.
if (exprtype == MatchExpression::NOT) {
// Don't allow indexed NOT on special index types such as geo or text indices.
- if (INDEX_BTREE != index.type) {
+ // TODO: SERVER-30994 should remove this check entirely and allow $not on the
+ // 'non-special' fields of non-btree indices.
+ // (e.g. {a: 1, geo: "2dsphere"})
+ if (INDEX_BTREE != index.type && !elemMatchChild) {
return false;
}
@@ -386,12 +414,19 @@ void QueryPlannerIXSelect::rateIndices(MatchExpression* node,
BSONObjIterator it(indices[i].keyPattern);
BSONElement elt = it.next();
if (elt.fieldName() == fullPath && compatible(elt, indices[i], node, collator)) {
- rt->first.push_back(i);
+ if (node->matchType() != MatchExpression::ELEM_MATCH_VALUE ||
+ elemMatchValueCompatible(elt, indices[i], node, collator)) {
+ rt->first.push_back(i);
+ }
}
while (it.more()) {
elt = it.next();
- if (elt.fieldName() == fullPath && compatible(elt, indices[i], node, collator)) {
- rt->notFirst.push_back(i);
+ if (elt.fieldName() == fullPath &&
+ compatible(elt, indices[i], node, collator, false)) {
+ if (node->matchType() != MatchExpression::ELEM_MATCH_VALUE ||
+ elemMatchValueCompatible(elt, indices[i], node, collator)) {
+ rt->notFirst.push_back(i);
+ }
}
}
}
diff --git a/src/mongo/db/query/planner_ixselect.h b/src/mongo/db/query/planner_ixselect.h
index 914e9265d9b..7cb34973810 100644
--- a/src/mongo/db/query/planner_ixselect.h
+++ b/src/mongo/db/query/planner_ixselect.h
@@ -72,7 +72,8 @@ public:
static bool compatible(const BSONElement& elt,
const IndexEntry& index,
MatchExpression* node,
- const CollatorInterface* collator);
+ const CollatorInterface* collator,
+ bool elemMatchChild = false);
/**
* Determine how useful all of our relevant 'indices' are to all predicates in the subtree
diff --git a/src/mongo/db/query/planner_ixselect_test.cpp b/src/mongo/db/query/planner_ixselect_test.cpp
index 1c273db8716..0db164a68be 100644
--- a/src/mongo/db/query/planner_ixselect_test.cpp
+++ b/src/mongo/db/query/planner_ixselect_test.cpp
@@ -45,6 +45,8 @@ using namespace mongo;
namespace {
+constexpr CollatorInterface* kSimpleCollator = nullptr;
+
using std::unique_ptr;
using std::string;
using std::vector;
@@ -304,6 +306,83 @@ TEST(QueryPlannerIXSelectTest, RateIndicesTaggedNodePathArrayNegation) {
}
/**
+ * $not within $elemMatch should not attempt to use a sparse index for $exists:false.
+ */
+TEST(QueryPlannerIXSelectTest, ElemMatchNotExistsShouldNotUseSparseIndex) {
+ std::vector<IndexEntry> indices;
+ auto idxEntry = IndexEntry(BSON("a" << 1));
+ idxEntry.sparse = true;
+ indices.push_back(idxEntry);
+ std::set<size_t> expectedIndices;
+ testRateIndices("{a: {$elemMatch: {$not: {$exists: true}}}}",
+ "",
+ kSimpleCollator,
+ indices,
+ "",
+ expectedIndices);
+}
+
+/**
+ * $in with a null value within $elemMatch can use a sparse index.
+ */
+TEST(QueryPlannerIXSelectTest, ElemMatchInNullValueShouldUseSparseIndex) {
+ std::vector<IndexEntry> indices;
+ auto idxEntry = IndexEntry(BSON("a" << 1));
+ idxEntry.sparse = true;
+ indices.push_back(idxEntry);
+ std::set<size_t> expectedIndices = {0};
+ testRateIndices(
+ "{a: {$elemMatch: {$in: [null]}}}", "", kSimpleCollator, indices, "a", expectedIndices);
+}
+
+/**
+ * $geo queries within $elemMatch should not use a normal B-tree index.
+ */
+TEST(QueryPlannerIXSelectTest, ElemMatchGeoShouldNotUseBtreeIndex) {
+ std::vector<IndexEntry> indices;
+ auto idxEntry = IndexEntry(BSON("a" << 1));
+ indices.push_back(idxEntry);
+ std::set<size_t> expectedIndices;
+ testRateIndices(R"({a: {$elemMatch: {$geoWithin: {$geometry: {type: 'Polygon',
+ coordinates: [[[0,0],[0,1],[1,0],[0,0]]]}}}}})",
+ "",
+ kSimpleCollator,
+ indices,
+ "a",
+ expectedIndices);
+}
+
+/**
+ * $eq with a null value within $elemMatch can use a sparse index.
+ */
+TEST(QueryPlannerIXSelectTest, ElemMatchEqNullValueShouldUseSparseIndex) {
+ std::vector<IndexEntry> indices;
+ auto idxEntry = IndexEntry(BSON("a" << 1));
+ idxEntry.sparse = true;
+ indices.push_back(idxEntry);
+ std::set<size_t> expectedIndices = {0};
+ testRateIndices(
+ "{a: {$elemMatch: {$eq: null}}}", "", kSimpleCollator, indices, "a", expectedIndices);
+}
+
+/**
+ * $elemMatch with multiple children will not use an index if any child is incompatible.
+ */
+TEST(QueryPlannerIXSelectTest, ElemMatchMultipleChildrenShouldRequireAllToBeCompatible) {
+ std::vector<IndexEntry> indices;
+ auto idxEntry = IndexEntry(BSON("a" << 1));
+ idxEntry.sparse = true;
+ indices.push_back(idxEntry);
+ std::set<size_t> expectedIndices;
+ testRateIndices("{a: {$elemMatch: {$eq: null, $not: {$exists: true}}}}",
+ "",
+ kSimpleCollator,
+ indices,
+ "",
+ expectedIndices);
+}
+
+/**
* If the collator is null, we select the relevant index with a null collator.
*/
TEST(QueryPlannerIXSelectTest, NullCollatorsMatch) {
diff --git a/src/mongo/db/query/query_planner_geo_test.cpp b/src/mongo/db/query/query_planner_geo_test.cpp
index 9e087444079..0421546a60d 100644
--- a/src/mongo/db/query/query_planner_geo_test.cpp
+++ b/src/mongo/db/query/query_planner_geo_test.cpp
@@ -1531,4 +1531,66 @@ TEST_F(QueryPlanner2dsphereVersionTest, NegationWithoutGeoPredCannotUseGeoIndex)
testMultiple2dsphereIndexVersions(versions, keyPatterns, predicate, solutions);
}
+TEST_F(QueryPlannerTest, 2dInexactFetchPredicateOverTrailingFieldHandledCorrectly) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN;
+
+ addIndex(BSON("a"
+ << "2d"
+ << "b"
+ << 1));
+
+ runQuery(fromjson("{a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: true}}"));
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: true}}, node: "
+ "{ixscan: {filter: null, pattern: {a: '2d', b: 1}}}}}");
+}
+
+TEST_F(QueryPlannerTest, 2dInexactFetchPredicateOverTrailingFieldHandledCorrectlyMultikey) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN;
+
+ const bool multikey = true;
+ addIndex(BSON("a"
+ << "2d"
+ << "b"
+ << 1),
+ multikey);
+
+ runQuery(fromjson("{a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: true}}"));
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$exists: true}}, node: "
+ "{ixscan: {filter: null, pattern: {a: '2d', b: 1}}}}}");
+}
+
+TEST_F(QueryPlannerTest, 2dNearInexactFetchPredicateOverTrailingFieldHandledCorrectly) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN;
+
+ addIndex(BSON("a"
+ << "2d"
+ << "b"
+ << 1));
+
+ runQuery(fromjson("{a: {$near: [0, 0]}, b: {$exists: true}}"));
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: {$exists: true}}, node: {geoNear2d: {a: '2d', b: 1}}}}");
+}
+
+TEST_F(QueryPlannerTest, 2dNearInexactFetchPredicateOverTrailingFieldMultikey) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN;
+
+ const bool multikey = true;
+ addIndex(BSON("a"
+ << "2d"
+ << "b"
+ << 1),
+ multikey);
+
+ runQuery(fromjson("{a: {$near: [0, 0]}, b: {$exists: true}}"));
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: {$exists: true}}, node: {geoNear2d: {a: '2d', b: 1}}}}");
+}
+
} // namespace
diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp
index 5050653292a..cbb7fcdbd39 100644
--- a/src/mongo/db/query/query_planner_text_test.cpp
+++ b/src/mongo/db/query/query_planner_text_test.cpp
@@ -461,4 +461,77 @@ TEST_F(QueryPlannerTest, SortKeyMetaProjectionWithTextScoreMetaSort) {
"{sortKeyGen: {node: {text: {search: 'foo'}}}}}}}}");
}
+TEST_F(QueryPlannerTest, PredicatesOverLeadingFieldsWithSharedPathPrefixHandledCorrectly) {
+ const bool multikey = true;
+ addIndex(BSON("a.x" << 1 << "a.y" << 1 << "b.x" << 1 << "b.y" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1),
+ multikey);
+
+ runQuery(fromjson("{'a.x': 1, 'a.y': 2, 'b.x': 3, 'b.y': 4, $text: {$search: 'foo'}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{text: {search: 'foo', prefix: {'a.x': 1, 'a.y': 2, 'b.x': 3, 'b.y': 4}}}");
+}
+
+TEST_F(QueryPlannerTest, EqualityToArrayOverLeadingFieldHandledCorrectly) {
+ addIndex(BSON("a" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1));
+
+ runQuery(fromjson("{a: [1, 2, 3], $text: {$search: 'foo'}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists("{text: {search: 'foo', prefix: {a: [1, 2, 3]}}}");
+}
+
+TEST_F(QueryPlannerTest, EqualityToArrayOverLeadingFieldHandledCorrectlyWithMultikeyTrue) {
+ const bool multikey = true;
+ addIndex(BSON("a" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1),
+ multikey);
+
+ runQuery(fromjson("{a: [1, 2, 3], $text: {$search: 'foo'}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists("{text: {search: 'foo', prefix: {a: [1, 2, 3]}}}");
+}
+
+TEST_F(QueryPlannerTest, InexactFetchPredicateOverTrailingFieldHandledCorrectly) {
+ addIndex(BSON("a" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1
+ << "b"
+ << 1));
+
+ runQuery(fromjson("{a: 3, $text: {$search: 'foo'}, b: {$exists: true}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: {$exists: true}}, node: {text: {search: 'foo', prefix: {a: 3}}}}}");
+}
+
+TEST_F(QueryPlannerTest, InexactFetchPredicateOverTrailingFieldHandledCorrectlyMultikeyTrue) {
+ const bool multikey = true;
+ addIndex(BSON("a" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1
+ << "b"
+ << 1),
+ multikey);
+
+ runQuery(fromjson("{a: 3, $text: {$search: 'foo'}, b: {$exists: true}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: {$exists: true}}, node: {text: {search: 'foo', prefix: {a: 3}}}}}");
+}
+
} // namespace
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index 7e68378042d..ff02e0df3a5 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -250,6 +250,13 @@ struct TextNode : public QuerySolutionNode {
IndexEntry index;
std::unique_ptr<fts::FTSQuery> ftsQuery;
+ // The number of fields in the prefix of the text index. For example, if the key pattern is
+ //
+ // { a: 1, b: 1, _fts: "text", _ftsx: 1, c: 1 }
+ //
+ // then the number of prefix fields is 2, because of "a" and "b".
+ size_t numPrefixFields = 0u;
+
// "Prefix" fields of a text index can handle equality predicates. We group them with the
// text node while creating the text leaf node and convert them into a BSONObj index prefix
// when we finish the text leaf node.
diff --git a/src/mongo/db/range_deleter.cpp b/src/mongo/db/range_deleter.cpp
index b97d9fae216..e0d1d5c5aa0 100644
--- a/src/mongo/db/range_deleter.cpp
+++ b/src/mongo/db/range_deleter.cpp
@@ -354,16 +354,13 @@ bool RangeDeleter::deleteNow(OperationContext* txn,
taskDetails.stats.queueEndTS = jsTime();
taskDetails.stats.deleteStartTS = jsTime();
- bool result = _env->deleteRange(txn, taskDetails, &taskDetails.stats.deletedDocCount, errMsg);
-
+ bool result = _env->deleteRange(txn,
+ taskDetails,
+ &taskDetails.stats.deletedDocCount,
+ taskDetails.stats.waitForReplDurationMs,
+ errMsg);
taskDetails.stats.deleteEndTS = jsTime();
- if (result) {
- taskDetails.stats.waitForReplStartTS = jsTime();
- result = _waitForMajority(txn, errMsg);
- taskDetails.stats.waitForReplEndTS = jsTime();
- }
-
{
stdx::lock_guard<stdx::mutex> sl(_queueMutex);
_deleteSet.erase(&deleteRange);
@@ -485,8 +482,11 @@ void RangeDeleter::doWork() {
{
auto txn = client->makeOperationContext();
nextTask->stats.deleteStartTS = jsTime();
- bool delResult =
- _env->deleteRange(txn.get(), *nextTask, &nextTask->stats.deletedDocCount, &errMsg);
+ bool delResult = _env->deleteRange(txn.get(),
+ *nextTask,
+ &nextTask->stats.deletedDocCount,
+ nextTask->stats.waitForReplDurationMs,
+ &errMsg);
nextTask->stats.deleteEndTS = jsTime();
if (delResult) {
diff --git a/src/mongo/db/range_deleter.h b/src/mongo/db/range_deleter.h
index 776f8a825fc..b6acafe3112 100644
--- a/src/mongo/db/range_deleter.h
+++ b/src/mongo/db/range_deleter.h
@@ -254,6 +254,7 @@ struct DeleteJobStats {
Date_t deleteEndTS;
Date_t waitForReplStartTS;
Date_t waitForReplEndTS;
+ Milliseconds waitForReplDurationMs;
long long int deletedDocCount;
@@ -314,6 +315,7 @@ struct RangeDeleterEnv {
virtual bool deleteRange(OperationContext* txn,
const RangeDeleteEntry& taskDetails,
long long int* deletedDocs,
+ Milliseconds& replWaitDuration,
std::string* errMsg) = 0;
/**
diff --git a/src/mongo/db/range_deleter_db_env.cpp b/src/mongo/db/range_deleter_db_env.cpp
index 2f692111c0a..421cafc9692 100644
--- a/src/mongo/db/range_deleter_db_env.cpp
+++ b/src/mongo/db/range_deleter_db_env.cpp
@@ -61,6 +61,7 @@ using std::string;
bool RangeDeleterDBEnv::deleteRange(OperationContext* txn,
const RangeDeleteEntry& taskDetails,
long long int* deletedDocs,
+ Milliseconds& replWaitDuration,
std::string* errMsg) {
const string ns(taskDetails.options.range.ns);
const BSONObj inclusiveLower(taskDetails.options.range.minKey);
@@ -92,6 +93,7 @@ bool RangeDeleterDBEnv::deleteRange(OperationContext* txn,
KeyRange(ns, inclusiveLower, exclusiveUpper, keyPattern),
BoundInclusion::kIncludeStartKeyOnly,
writeConcern,
+ replWaitDuration,
removeSaverPtr,
fromMigrate,
onlyRemoveOrphans);
diff --git a/src/mongo/db/range_deleter_db_env.h b/src/mongo/db/range_deleter_db_env.h
index 0bca8c6618c..b0e5a11d0cc 100644
--- a/src/mongo/db/range_deleter_db_env.h
+++ b/src/mongo/db/range_deleter_db_env.h
@@ -46,13 +46,16 @@ struct RangeDeleterDBEnv : public RangeDeleterEnv {
* Note that secondaryThrottle will be ignored if current process is not part
* of a replica set.
*
- * docsDeleted would contain the number of docs deleted if the deletion was successful.
+ * deletedDocs would contain the number of docs deleted if the deletion was successful.
+ *
+ * Returns time spent waiting for majority replication in replWaitDuration.
*
* Does not throw Exceptions.
*/
virtual bool deleteRange(OperationContext* txn,
const RangeDeleteEntry& taskDetails,
long long int* deletedDocs,
+ Milliseconds& replWaitDuration,
std::string* errMsg);
/**
diff --git a/src/mongo/db/range_deleter_mock_env.cpp b/src/mongo/db/range_deleter_mock_env.cpp
index 1ee01d6a19a..9c50185ba07 100644
--- a/src/mongo/db/range_deleter_mock_env.cpp
+++ b/src/mongo/db/range_deleter_mock_env.cpp
@@ -104,6 +104,7 @@ DeletedRange RangeDeleterMockEnv::getLastDelete() const {
bool RangeDeleterMockEnv::deleteRange(OperationContext* txn,
const RangeDeleteEntry& taskDetails,
long long int* deletedDocs,
+ Milliseconds& replWaitDuration,
string* errMsg) {
{
stdx::unique_lock<stdx::mutex> sl(_pauseDeleteMutex);
diff --git a/src/mongo/db/range_deleter_mock_env.h b/src/mongo/db/range_deleter_mock_env.h
index 307dbe2bd53..5792b24499c 100644
--- a/src/mongo/db/range_deleter_mock_env.h
+++ b/src/mongo/db/range_deleter_mock_env.h
@@ -127,6 +127,7 @@ public:
bool deleteRange(OperationContext* txn,
const RangeDeleteEntry& taskDetails,
long long int* deletedDocs,
+ Milliseconds& replWaitDuration,
std::string* errMsg);
/**
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index 05dbeff18a0..31f35d384f8 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -631,12 +631,12 @@ env.Library('replica_set_messages',
'is_master_response.cpp',
'member_config.cpp',
'old_update_position_args.cpp',
+ 'repl_set_config.cpp',
'repl_set_heartbeat_args.cpp',
'repl_set_heartbeat_args_v1.cpp',
'repl_set_heartbeat_response.cpp',
'repl_set_html_summary.cpp',
'repl_set_request_votes_args.cpp',
- 'repl_set_config.cpp',
'repl_set_tag.cpp',
'update_position_args.cpp',
'last_vote.cpp',
diff --git a/src/mongo/db/repl/bgsync.cpp b/src/mongo/db/repl/bgsync.cpp
index 2c04c40bf55..fbbf1c56efd 100644
--- a/src/mongo/db/repl/bgsync.cpp
+++ b/src/mongo/db/repl/bgsync.cpp
@@ -225,15 +225,16 @@ void BackgroundSync::_runProducer() {
}
// we want to start when we're no longer primary
// start() also loads _lastOpTimeFetched, which we know is set from the "if"
- auto txn = cc().makeOperationContext();
- if (getState() == ProducerState::Starting) {
- start(txn.get());
+ {
+ auto opCtx = cc().makeOperationContext();
+ if (getState() == ProducerState::Starting) {
+ start(opCtx.get());
+ }
}
-
- _produce(txn.get());
+ _produce();
}
-void BackgroundSync::_produce(OperationContext* opCtx) {
+void BackgroundSync::_produce() {
if (MONGO_FAIL_POINT(stopReplProducer)) {
// This log output is used in js tests so please leave it.
log() << "bgsync - stopReplProducer fail point "
@@ -266,15 +267,18 @@ void BackgroundSync::_produce(OperationContext* opCtx) {
}
}
- auto storageInterface = StorageInterface::get(opCtx);
// find a target to sync from the last optime fetched
OpTime lastOpTimeFetched;
HostAndPort source;
HostAndPort oldSource = _syncSourceHost;
SyncSourceResolverResponse syncSourceResp;
{
- const OpTime minValidSaved = storageInterface->getMinValid(opCtx);
-
+ OpTime minValidSaved;
+ {
+ auto opCtx = cc().makeOperationContext();
+ auto storageInterface = StorageInterface::get(opCtx.get());
+ minValidSaved = storageInterface->getMinValid(opCtx.get());
+ }
stdx::lock_guard<stdx::mutex> lock(_mutex);
if (_state != ProducerState::Running) {
return;
@@ -397,8 +401,12 @@ void BackgroundSync::_produce(OperationContext* opCtx) {
// Set the applied point if unset. This is most likely the first time we've established a sync
// source since stepping down or otherwise clearing the applied point. We need to set this here,
// before the OplogWriter gets a chance to append to the oplog.
- if (storageInterface->getAppliedThrough(opCtx).isNull()) {
- storageInterface->setAppliedThrough(opCtx, _replCoord->getMyLastAppliedOpTime());
+ {
+ auto opCtx = cc().makeOperationContext();
+ auto storageInterface = StorageInterface::get(opCtx.get());
+ if (storageInterface->getAppliedThrough(opCtx.get()).isNull()) {
+ storageInterface->setAppliedThrough(opCtx.get(), _replCoord->getMyLastAppliedOpTime());
+ }
}
// "lastFetched" not used. Already set in _enqueueDocuments.
@@ -521,10 +529,18 @@ void BackgroundSync::_produce(OperationContext* opCtx) {
}
}
- OplogInterfaceLocal localOplog(opCtx, rsOplogName);
- RollbackSourceImpl rollbackSource(getConnection, source, rsOplogName);
- rollback(
- opCtx, localOplog, rollbackSource, syncSourceResp.rbid, _replCoord, storageInterface);
+ {
+ auto opCtx = cc().makeOperationContext();
+ OplogInterfaceLocal localOplog(opCtx.get(), rsOplogName);
+ RollbackSourceImpl rollbackSource(getConnection, source, rsOplogName);
+ auto storageInterface = StorageInterface::get(opCtx.get());
+ rollback(opCtx.get(),
+ localOplog,
+ rollbackSource,
+ syncSourceResp.rbid,
+ _replCoord,
+ storageInterface);
+ }
// Reset the producer to clear the sync source and the last optime fetched.
stop(true);
diff --git a/src/mongo/db/repl/bgsync.h b/src/mongo/db/repl/bgsync.h
index 6d068967723..00f23e59b1d 100644
--- a/src/mongo/db/repl/bgsync.h
+++ b/src/mongo/db/repl/bgsync.h
@@ -148,7 +148,7 @@ private:
void _run();
// Production thread inner loop.
void _runProducer();
- void _produce(OperationContext* txn);
+ void _produce();
/**
* Checks current background sync state before pushing operations into blocking queue and
diff --git a/src/mongo/db/repl/collection_cloner.cpp b/src/mongo/db/repl/collection_cloner.cpp
index 9dc3674efa0..bbaf04ba3dc 100644
--- a/src/mongo/db/repl/collection_cloner.cpp
+++ b/src/mongo/db/repl/collection_cloner.cpp
@@ -114,7 +114,8 @@ CollectionCloner::CollectionCloner(executor::TaskExecutor* executor,
stdx::placeholders::_2,
stdx::placeholders::_3),
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- RemoteCommandRequest::kNoTimeout,
+ RemoteCommandRequest::kNoTimeout /* find network timeout */,
+ RemoteCommandRequest::kNoTimeout /* getMore network timeout */,
RemoteCommandRetryScheduler::makeRetryPolicy(
numInitialSyncListIndexesAttempts,
executor::RemoteCommandRequest::kNoTimeout,
@@ -518,7 +519,8 @@ void CollectionCloner::_beginCollectionCallback(const executor::TaskExecutor::Ca
stdx::placeholders::_3,
onCompletionGuard),
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- RemoteCommandRequest::kNoTimeout,
+ RemoteCommandRequest::kNoTimeout /* find network timeout */,
+ RemoteCommandRequest::kNoTimeout /* getMore network timeout */,
RemoteCommandRetryScheduler::makeRetryPolicy(
numInitialSyncCollectionFindAttempts.load(),
executor::RemoteCommandRequest::kNoTimeout,
diff --git a/src/mongo/db/repl/database_cloner.cpp b/src/mongo/db/repl/database_cloner.cpp
index 461c4dfe1b7..d0211fdf058 100644
--- a/src/mongo/db/repl/database_cloner.cpp
+++ b/src/mongo/db/repl/database_cloner.cpp
@@ -114,7 +114,8 @@ DatabaseCloner::DatabaseCloner(executor::TaskExecutor* executor,
stdx::placeholders::_2,
stdx::placeholders::_3),
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- RemoteCommandRequest::kNoTimeout,
+ RemoteCommandRequest::kNoTimeout /* find network timeout */,
+ RemoteCommandRequest::kNoTimeout /* getMore network timeout */,
RemoteCommandRetryScheduler::makeRetryPolicy(
numInitialSyncListCollectionsAttempts,
executor::RemoteCommandRequest::kNoTimeout,
diff --git a/src/mongo/db/repl/databases_cloner.cpp b/src/mongo/db/repl/databases_cloner.cpp
index 4a18e1be1b8..c9016c322f4 100644
--- a/src/mongo/db/repl/databases_cloner.cpp
+++ b/src/mongo/db/repl/databases_cloner.cpp
@@ -231,6 +231,44 @@ void DatabasesCloner::setScheduleDbWorkFn_forTest(const CollectionCloner::Schedu
_scheduleDbWorkFn = work;
}
+StatusWith<std::vector<BSONElement>> DatabasesCloner::parseListDatabasesResponse_forTest(
+ BSONObj dbResponse) {
+ return _parseListDatabasesResponse(dbResponse);
+}
+
+void DatabasesCloner::setAdminAsFirst_forTest(std::vector<BSONElement>& dbsArray) {
+ _setAdminAsFirst(dbsArray);
+}
+
+StatusWith<std::vector<BSONElement>> DatabasesCloner::_parseListDatabasesResponse(
+ BSONObj dbResponse) {
+ if (!dbResponse.hasField("databases")) {
+ return Status(ErrorCodes::BadValue,
+ "The 'listDatabases' response does not contain a 'databases' field.");
+ }
+ BSONElement response = dbResponse["databases"];
+ try {
+ return response.Array();
+ } catch (const MsgAssertionException& e) {
+ return Status(ErrorCodes::BadValue,
+ "The 'listDatabases' response is unable to be transformed into an array.");
+ }
+}
+
+void DatabasesCloner::_setAdminAsFirst(std::vector<BSONElement>& dbsArray) {
+ auto adminIter = std::find_if(dbsArray.begin(), dbsArray.end(), [](BSONElement elem) {
+ if (!elem.isABSONObj()) {
+ return false;
+ }
+ auto bsonObj = elem.Obj();
+ std::string databaseName = bsonObj.getStringField("name");
+ return (databaseName == "admin");
+ });
+ if (adminIter != dbsArray.end()) {
+ std::iter_swap(adminIter, dbsArray.begin());
+ }
+}
+
void DatabasesCloner::_onListDatabaseFinish(const CommandCallbackArgs& cbd) {
Status respStatus = cbd.response.status;
if (respStatus.isOK()) {
@@ -239,24 +277,44 @@ void DatabasesCloner::_onListDatabaseFinish(const CommandCallbackArgs& cbd) {
UniqueLock lk(_mutex);
if (!respStatus.isOK()) {
- LOG(1) << "listDatabases failed: " << respStatus;
+ LOG(1) << "'listDatabases' failed: " << respStatus;
_fail_inlock(&lk, respStatus);
return;
}
- const auto respBSON = cbd.response.data;
- // There should not be any cloners yet
+ // There should not be any cloners yet.
invariant(_databaseCloners.size() == 0);
- const auto dbsElem = respBSON["databases"].Obj();
- BSONForEach(arrayElement, dbsElem) {
+ const auto respBSON = cbd.response.data;
+
+ auto databasesArray = _parseListDatabasesResponse(respBSON);
+ if (!databasesArray.isOK()) {
+ LOG(1) << "'listDatabases' returned a malformed response: "
+ << databasesArray.getStatus().toString();
+ _fail_inlock(&lk, databasesArray.getStatus());
+ return;
+ }
+
+ auto dbsArray = databasesArray.getValue();
+ // Ensure that the 'admin' database is the first element in the array of databases so that it
+ // will be the first to be cloned. This allows users to authenticate against a database while
+ // initial sync is occurring.
+ _setAdminAsFirst(dbsArray);
+
+ for (BSONElement arrayElement : dbsArray) {
const BSONObj dbBSON = arrayElement.Obj();
// Check to see if we want to exclude this db from the clone.
if (!_includeDbFn(dbBSON)) {
- LOG(1) << "excluding db: " << dbBSON;
+ LOG(1) << "Excluding database from the 'listDatabases' response: " << dbBSON;
continue;
}
+ if (!dbBSON.hasField("name")) {
+ LOG(1) << "Excluding database due to the 'listDatabases' response not containing a "
+ "'name' field for this entry: "
+ << dbBSON;
+ }
+
const std::string dbName = dbBSON["name"].str();
std::shared_ptr<DatabaseCloner> dbCloner{nullptr};
@@ -321,7 +379,6 @@ void DatabasesCloner::_onListDatabaseFinish(const CommandCallbackArgs& cbd) {
// add cloner to list.
_databaseCloners.push_back(dbCloner);
}
-
if (_databaseCloners.size() == 0) {
if (_status.isOK()) {
_succeed_inlock(&lk);
diff --git a/src/mongo/db/repl/databases_cloner.h b/src/mongo/db/repl/databases_cloner.h
index 53cced9ee4f..13f11f1e2a1 100644
--- a/src/mongo/db/repl/databases_cloner.h
+++ b/src/mongo/db/repl/databases_cloner.h
@@ -34,6 +34,7 @@
#include "mongo/base/disallow_copying.h"
#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/client/fetcher.h"
#include "mongo/db/namespace_string.h"
@@ -104,6 +105,18 @@ public:
*/
void setScheduleDbWorkFn_forTest(const CollectionCloner::ScheduleDbWorkFn& scheduleDbWorkFn);
+ /**
+ * Calls DatabasesCloner::_setAdminAsFirst.
+ * For testing only.
+ */
+ void setAdminAsFirst_forTest(std::vector<BSONElement>& dbsArray);
+
+ /**
+ * Calls DatabasesCloner::_parseListDatabasesResponse.
+ * For testing only.
+ */
+ StatusWith<std::vector<BSONElement>> parseListDatabasesResponse_forTest(BSONObj dbResponse);
+
private:
bool _isActive_inlock() const;
@@ -135,6 +148,23 @@ private:
void _onListDatabaseFinish(const CommandCallbackArgs& cbd);
+ /**
+ * Takes a vector of BSONElements and scans for an element that contains a 'name' field with the
+ * value 'admin'. If found, the element is swapped with the first element in the vector.
+ * Otherwise, return.
+ *
+ * Used to parse the BSONResponse returned by listDatabases.
+ */
+ void _setAdminAsFirst(std::vector<BSONElement>& dbsArray);
+
+ /**
+ * Takes a 'listDatabases' command response and parses the response into a
+ * vector of BSON elements.
+ *
+ * If the input response is malformed, Status ErrorCodes::BadValue will be returned.
+ */
+ StatusWith<std::vector<BSONElement>> _parseListDatabasesResponse(BSONObj dbResponse);
+
//
// All member variables are labeled with one of the following codes indicating the
// synchronization rules for accessing them.
diff --git a/src/mongo/db/repl/databases_cloner_test.cpp b/src/mongo/db/repl/databases_cloner_test.cpp
index 81552201485..e44114e5d58 100644
--- a/src/mongo/db/repl/databases_cloner_test.cpp
+++ b/src/mongo/db/repl/databases_cloner_test.cpp
@@ -314,6 +314,15 @@ protected:
ASSERT_OK(result);
};
+ std::unique_ptr<DatabasesCloner> makeDummyDatabasesCloner() {
+ return stdx::make_unique<DatabasesCloner>(&getStorage(),
+ &getExecutor(),
+ &getDbWorkThreadPool(),
+ HostAndPort{"local:1234"},
+ [](const BSONObj&) { return true; },
+ [](const Status&) {});
+ }
+
private:
executor::ThreadPoolMock::Options makeThreadPoolMockOptions() const override;
@@ -450,6 +459,87 @@ TEST_F(DBsClonerTest, StartupReturnsInternalErrorAfterSuccessfulStartup) {
ASSERT_TRUE(cloner.isActive());
}
+TEST_F(DBsClonerTest, ParseAndSetAdminFirstWhenAdminInListDatabasesResponse) {
+ const Responses responsesWithAdmin = {
+ {"listDatabases", fromjson("{ok:1, databases:[{name:'a'}, {name:'aab'}, {name:'admin'}]}")},
+ {"listDatabases", fromjson("{ok:1, databases:[{name:'admin'}, {name:'a'}, {name:'b'}]}")},
+ };
+ std::unique_ptr<DatabasesCloner> cloner = makeDummyDatabasesCloner();
+
+ for (auto&& resp : responsesWithAdmin) {
+ auto parseResponseStatus = cloner->parseListDatabasesResponse_forTest(resp.second);
+ ASSERT_TRUE(parseResponseStatus.isOK());
+ std::vector<BSONElement> dbNamesArray = parseResponseStatus.getValue();
+ cloner->setAdminAsFirst_forTest(dbNamesArray);
+ ASSERT_EQUALS("admin", dbNamesArray[0].Obj().firstElement().str());
+ }
+}
+
+TEST_F(DBsClonerTest, ParseAndAttemptSetAdminFirstWhenAdminNotInListDatabasesResponse) {
+ const Responses responsesWithoutAdmin = {
+ {"listDatabases", fromjson("{ok:1, databases:[{name:'a'}, {name:'aab'}, {name:'abc'}]}")},
+ {"listDatabases", fromjson("{ok:1, databases:[{name:'foo'}, {name:'a'}, {name:'b'}]}")},
+ {"listDatabases", fromjson("{ok:1, databases:[{name:1}, {name:2}, {name:3}]}")},
+ };
+ std::unique_ptr<DatabasesCloner> cloner = makeDummyDatabasesCloner();
+
+ for (auto&& resp : responsesWithoutAdmin) {
+ auto parseResponseStatus = cloner->parseListDatabasesResponse_forTest(resp.second);
+ ASSERT_TRUE(parseResponseStatus.isOK());
+ std::vector<BSONElement> dbNamesArray = parseResponseStatus.getValue();
+ std::string expectedResult = dbNamesArray[0].Obj().firstElement().str();
+ cloner->setAdminAsFirst_forTest(dbNamesArray);
+ ASSERT_EQUALS(expectedResult, dbNamesArray[0].Obj().firstElement().str());
+ }
+}
+
+
+TEST_F(DBsClonerTest, ParseListDatabasesResponseWithMalformedResponses) {
+ Status expectedResultForNoDatabasesField{
+ ErrorCodes::BadValue,
+ "The 'listDatabases' command response does not contain a databases field."};
+ Status expectedResultForNoArrayOfDatabases{
+ ErrorCodes::BadValue,
+ "The 'listDatabases' command response is unable to be transformed into an array."};
+
+ const Responses responsesWithoutDatabasesField = {
+ {"listDatabases", fromjson("{ok:1, fake:[{name:'a'}, {name:'aab'}, {name:'foo'}]}")},
+ {"listDatabases", fromjson("{ok:1, fake:[{name:'admin'}, {name:'a'}, {name:'b'}]}")},
+ };
+
+ const Responses responsesWithoutArrayOfDatabases = {
+ {"listDatabases", fromjson("{ok:1, databases:1}")},
+ {"listDatabases", fromjson("{ok:1, databases:'abc'}")},
+ };
+
+ const Responses responsesWithInvalidAdminNameField = {
+ {"listDatabases", fromjson("{ok:1, databases:[{name:'a'}, {name:'aab'}, {fake:'admin'}]}")},
+ {"listDatabases", fromjson("{ok:1, databases:[{fake:'admin'}, {name:'a'}, {name:'b'}]}")},
+ };
+
+ std::unique_ptr<DatabasesCloner> cloner = makeDummyDatabasesCloner();
+
+ for (auto&& resp : responsesWithoutDatabasesField) {
+ auto parseResponseStatus = cloner->parseListDatabasesResponse_forTest(resp.second);
+ ASSERT_EQ(parseResponseStatus.getStatus(), expectedResultForNoDatabasesField);
+ }
+
+ for (auto&& resp : responsesWithoutArrayOfDatabases) {
+ auto parseResponseStatus = cloner->parseListDatabasesResponse_forTest(resp.second);
+ ASSERT_EQ(parseResponseStatus.getStatus(), expectedResultForNoArrayOfDatabases);
+ }
+
+ for (auto&& resp : responsesWithInvalidAdminNameField) {
+ auto parseResponseStatus = cloner->parseListDatabasesResponse_forTest(resp.second);
+ ASSERT_TRUE(parseResponseStatus.isOK());
+ // We expect no elements to be swapped.
+ std::vector<BSONElement> dbNamesArray = parseResponseStatus.getValue();
+ std::string expectedResult = dbNamesArray[0].Obj().firstElement().str();
+ cloner->setAdminAsFirst_forTest(dbNamesArray);
+ ASSERT_EQUALS(expectedResult, dbNamesArray[0].Obj().firstElement().str());
+ }
+}
+
TEST_F(DBsClonerTest, FailsOnListDatabases) {
Status result{Status::OK()};
Status expectedResult{ErrorCodes::BadValue, "foo"};
diff --git a/src/mongo/db/repl/initial_syncer.cpp b/src/mongo/db/repl/initial_syncer.cpp
index e9a920bb37b..f3bc1a8acd3 100644
--- a/src/mongo/db/repl/initial_syncer.cpp
+++ b/src/mongo/db/repl/initial_syncer.cpp
@@ -1189,7 +1189,8 @@ Status InitialSyncer::_scheduleLastOplogEntryFetcher_inlock(Fetcher::CallbackFn
query,
callback,
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- RemoteCommandRequest::kNoTimeout,
+ RemoteCommandRequest::kNoTimeout /* find network timeout */,
+ RemoteCommandRequest::kNoTimeout /* getMore network timeout */,
RemoteCommandRetryScheduler::makeRetryPolicy(
numInitialSyncOplogFindAttempts,
executor::RemoteCommandRequest::kNoTimeout,
diff --git a/src/mongo/db/repl/master_slave.cpp b/src/mongo/db/repl/master_slave.cpp
index 96cfe58efdc..6929d5be297 100644
--- a/src/mongo/db/repl/master_slave.cpp
+++ b/src/mongo/db/repl/master_slave.cpp
@@ -168,7 +168,7 @@ BSONObj ReplSource::jsobj() {
BSONObjBuilder dbsNextPassBuilder;
int n = 0;
- for (set<string>::iterator i = addDbNextPass.begin(); i != addDbNextPass.end(); i++) {
+ for (set<std::string>::iterator i = addDbNextPass.begin(); i != addDbNextPass.end(); i++) {
n++;
dbsNextPassBuilder.appendBool(*i, 1);
}
@@ -177,7 +177,8 @@ BSONObj ReplSource::jsobj() {
BSONObjBuilder incompleteCloneDbsBuilder;
n = 0;
- for (set<string>::iterator i = incompleteCloneDbs.begin(); i != incompleteCloneDbs.end(); i++) {
+ for (set<std::string>::iterator i = incompleteCloneDbs.begin(); i != incompleteCloneDbs.end();
+ i++) {
n++;
incompleteCloneDbsBuilder.appendBool(*i, 1);
}
@@ -188,7 +189,7 @@ BSONObj ReplSource::jsobj() {
}
void ReplSource::ensureMe(OperationContext* txn) {
- string myname = getHostName();
+ std::string myname = getHostName();
// local.me is an identifier for a server for getLastError w:2+
bool exists = Helpers::getSingleton(txn, "local.me", _me);
@@ -378,10 +379,10 @@ public:
}
virtual bool run(OperationContext* txn,
- const string& ns,
+ const std::string& ns,
BSONObj& cmdObj,
int options,
- string& errmsg,
+ std::string& errmsg,
BSONObjBuilder& result) {
HandshakeArgs handshake;
Status status = handshake.initialize(cmdObj);
@@ -398,7 +399,7 @@ public:
} handshakeCmd;
bool replHandshake(DBClientConnection* conn, const OID& myRID) {
- string myname = getHostName();
+ std::string myname = getHostName();
BSONObjBuilder cmd;
cmd.append("handshake", myRID);
@@ -450,7 +451,7 @@ void ReplSource::forceResync(OperationContext* txn, const char* requester) {
BSONElement e = i.next();
if (e.eoo())
break;
- string name = e.embeddedObject().getField("name").valuestr();
+ std::string name = e.embeddedObject().getField("name").valuestr();
if (!e.embeddedObject().getBoolField("empty")) {
if (name != "local") {
if (only.empty() || only == name) {
@@ -481,7 +482,7 @@ Status ReplSource::_updateIfDoneWithInitialSync() {
return Status::OK();
}
-void ReplSource::resyncDrop(OperationContext* txn, const string& dbName) {
+void ReplSource::resyncDrop(OperationContext* txn, const std::string& dbName) {
log() << "resync: dropping database " << dbName;
invariant(txn->lockState()->isW());
@@ -531,13 +532,13 @@ void ReplSource::resync(OperationContext* txn, const std::string& dbName) {
static DatabaseIgnorer ___databaseIgnorer;
-void DatabaseIgnorer::doIgnoreUntilAfter(const string& db, const Timestamp& futureOplogTime) {
+void DatabaseIgnorer::doIgnoreUntilAfter(const std::string& db, const Timestamp& futureOplogTime) {
if (futureOplogTime > _ignores[db]) {
_ignores[db] = futureOplogTime;
}
}
-bool DatabaseIgnorer::ignoreAt(const string& db, const Timestamp& currentOplogTime) {
+bool DatabaseIgnorer::ignoreAt(const std::string& db, const Timestamp& currentOplogTime) {
if (_ignores[db].isNull()) {
return false;
}
@@ -627,7 +628,7 @@ bool ReplSource::handleDuplicateDbName(OperationContext* txn,
// The database is present on the master and no conflicting databases
// are present on the master. Drop any local conflicts.
- for (set<string>::const_iterator i = duplicates.begin(); i != duplicates.end(); ++i) {
+ for (set<std::string>::const_iterator i = duplicates.begin(); i != duplicates.end(); ++i) {
___databaseIgnorer.doIgnoreUntilAfter(*i, lastTime);
incompleteCloneDbs.erase(*i);
addDbNextPass.erase(*i);
@@ -822,10 +823,10 @@ void ReplSource::_sync_pullOpLog_applyOperation(OperationContext* txn,
}
void ReplSource::syncToTailOfRemoteLog() {
- string _ns = ns();
+ std::string _ns = ns();
BSONObjBuilder b;
if (!only.empty()) {
- b.appendRegex("ns", string("^") + pcrecpp::RE::QuoteMeta(only));
+ b.appendRegex("ns", std::string("^") + pcrecpp::RE::QuoteMeta(only));
}
BSONObj last = oplogReader.findOne(_ns.c_str(), Query(b.done()).sort(BSON("$natural" << -1)));
if (!last.isEmpty()) {
@@ -873,7 +874,7 @@ public:
*/
int ReplSource::_sync_pullOpLog(OperationContext* txn, int& nApplied) {
int okResultCode = restartSyncAfterSleep;
- string ns = string("local.oplog.$") + sourceName();
+ std::string ns = std::string("local.oplog.$") + sourceName();
LOG(2) << "sync_pullOpLog " << ns << " syncedTo:" << syncedTo.toStringLong() << '\n';
bool tailing = true;
@@ -893,7 +894,7 @@ int ReplSource::_sync_pullOpLog(OperationContext* txn, int& nApplied) {
BSONElement e = i.next();
if (e.eoo())
break;
- string name = e.embeddedObject().getField("name").valuestr();
+ std::string name = e.embeddedObject().getField("name").valuestr();
if (!e.embeddedObject().getBoolField("empty")) {
if (name != "local") {
if (only.empty() || only == name) {
@@ -917,7 +918,7 @@ int ReplSource::_sync_pullOpLog(OperationContext* txn, int& nApplied) {
if (!only.empty()) {
// note we may here skip a LOT of data table scanning, a lot of work for the master.
// maybe append "\\." here?
- query.appendRegex("ns", string("^") + pcrecpp::RE::QuoteMeta(only));
+ query.appendRegex("ns", std::string("^") + pcrecpp::RE::QuoteMeta(only));
}
BSONObj queryObj = query.done();
// e.g. queryObj = { ts: { $gte: syncedTo } }
@@ -936,7 +937,7 @@ int ReplSource::_sync_pullOpLog(OperationContext* txn, int& nApplied) {
// show any deferred database creates from a previous pass
{
- set<string>::iterator i = addDbNextPass.begin();
+ set<std::string>::iterator i = addDbNextPass.begin();
if (i != addDbNextPass.end()) {
BSONObjBuilder b;
b.append("ns", *i + '.');
@@ -980,7 +981,7 @@ int ReplSource::_sync_pullOpLog(OperationContext* txn, int& nApplied) {
BSONObj op = oplogReader.nextSafe();
BSONElement ts = op.getField("ts");
if (ts.type() != Date && ts.type() != bsonTimestamp) {
- string err = op.getStringField("$err");
+ std::string err = op.getStringField("$err");
if (!err.empty()) {
// 13051 is "tailable cursor requested on non capped collection"
if (op.getIntField("code") == 13051) {
@@ -1148,7 +1149,7 @@ int ReplSource::sync(OperationContext* txn, int& nApplied) {
// FIXME Handle cases where this db isn't on default port, or default port is spec'd in
// hostName.
- if ((string("localhost") == hostName || string("127.0.0.1") == hostName) &&
+ if ((std::string("localhost") == hostName || std::string("127.0.0.1") == hostName) &&
serverGlobalParams.port == ServerGlobalParams::DefaultDBPort) {
log() << "can't sync from self (localhost). sources configuration may be wrong." << endl;
sleepsecs(5);
@@ -1293,7 +1294,7 @@ static void replMain(OperationContext* txn) {
if (s) {
stringstream ss;
ss << "sleep " << s << " sec before next pass";
- string msg = ss.str();
+ std::string msg = ss.str();
if (!serverGlobalParams.quiet)
log() << msg << endl;
ReplInfo r(msg.c_str());
diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp
index 43a1eba5042..76df271ed78 100644
--- a/src/mongo/db/repl/oplog.cpp
+++ b/src/mongo/db/repl/oplog.cpp
@@ -116,6 +116,11 @@ namespace {
// cached copy...so don't rename, drop, etc.!!!
Collection* _localOplogCollection = nullptr;
+// Specifies whether we abort initial sync when attempting to apply a renameCollection operation.
+// If set to true, users risk corrupting their data. This should only be enabled by expert users
+// of the server who understand the risks this poses.
+MONGO_EXPORT_SERVER_PARAMETER(allowUnsafeRenamesDuringInitialSync, bool, false);
+
PseudoRandom hashGenerator(std::unique_ptr<SecureRandom>(SecureRandom::create())->nextInt64());
// Synchronizes the section where a new Timestamp is generated and when it actually
@@ -663,6 +668,45 @@ std::map<std::string, ApplyOpMetadata> opsMap = {
} // namespace
+std::pair<BSONObj, NamespaceString> prepForApplyOpsIndexInsert(const BSONElement& fieldO,
+ const BSONObj& op,
+ const NamespaceString& requestNss) {
+ uassert(ErrorCodes::NoSuchKey,
+ str::stream() << "Missing expected index spec in field 'o': " << op,
+ !fieldO.eoo());
+ uassert(ErrorCodes::TypeMismatch,
+ str::stream() << "Expected object for index spec in field 'o': " << op,
+ fieldO.isABSONObj());
+ BSONObj indexSpec = fieldO.embeddedObject();
+
+ std::string indexNs;
+ uassertStatusOK(bsonExtractStringField(indexSpec, "ns", &indexNs));
+ const NamespaceString indexNss(indexNs);
+ uassert(ErrorCodes::InvalidNamespace,
+ str::stream() << "Invalid namespace in index spec: " << op,
+ indexNss.isValid());
+ uassert(ErrorCodes::InvalidNamespace,
+ str::stream() << "Database name mismatch for database (" << requestNss.db()
+ << ") while creating index: "
+ << op,
+ requestNss.db() == indexNss.db());
+
+ if (!indexSpec["v"]) {
+ // If the "v" field isn't present in the index specification, then we assume it is a
+ // v=1 index from an older version of MongoDB. This is because
+ // (1) we haven't built v=0 indexes as the default for a long time, and
+ // (2) the index version has been included in the corresponding oplog entry since
+ // v=2 indexes were introduced.
+ BSONObjBuilder bob;
+
+ bob.append("v", static_cast<int>(IndexVersion::kV1));
+ bob.appendElements(indexSpec);
+
+ indexSpec = bob.obj();
+ }
+
+ return std::make_pair(indexSpec, indexNss);
+}
// @return failure status if an update should have happened and the document DNE.
// See replset initial sync code.
Status applyOperation_inlock(OperationContext* txn,
@@ -688,6 +732,7 @@ Status applyOperation_inlock(OperationContext* txn,
o = fieldO.embeddedObject();
const StringData ns = fieldNs.valueStringData();
+ NamespaceString requestNss{ns};
BSONObj o2;
if (fieldO2.isABSONObj())
@@ -718,27 +763,11 @@ Status applyOperation_inlock(OperationContext* txn,
invariant(*opType != 'c'); // commands are processed in applyCommand_inlock()
if (*opType == 'i') {
- if (nsToCollectionSubstring(ns) == "system.indexes") {
- uassert(ErrorCodes::NoSuchKey,
- str::stream() << "Missing expected index spec in field 'o': " << op,
- !fieldO.eoo());
- uassert(ErrorCodes::TypeMismatch,
- str::stream() << "Expected object for index spec in field 'o': " << op,
- fieldO.isABSONObj());
- BSONObj indexSpec = fieldO.embeddedObject();
-
- std::string indexNs;
- uassertStatusOK(bsonExtractStringField(indexSpec, "ns", &indexNs));
- const NamespaceString indexNss(indexNs);
- uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "Invalid namespace in index spec: " << op,
- indexNss.isValid());
- uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "Database name mismatch for database ("
- << nsToDatabaseSubstring(ns)
- << ") while creating index: "
- << op,
- nsToDatabaseSubstring(ns) == indexNss.db());
+ if (requestNss.isSystemDotIndexes()) {
+ BSONObj indexSpec;
+ NamespaceString indexNss;
+ std::tie(indexSpec, indexNss) =
+ repl::prepForApplyOpsIndexInsert(fieldO, op, requestNss);
// Check if collection exists.
auto indexCollection = db->getCollection(indexNss);
@@ -749,20 +778,6 @@ Status applyOperation_inlock(OperationContext* txn,
opCounters->gotInsert();
- if (!indexSpec["v"]) {
- // If the "v" field isn't present in the index specification, then we assume it is a
- // v=1 index from an older version of MongoDB. This is because
- // (1) we haven't built v=0 indexes as the default for a long time, and
- // (2) the index version has been included in the corresponding oplog entry since
- // v=2 indexes were introduced.
- BSONObjBuilder bob;
-
- bob.append("v", static_cast<int>(IndexVersion::kV1));
- bob.appendElements(indexSpec);
-
- indexSpec = bob.obj();
- }
-
bool relaxIndexConstraints =
ReplicationCoordinator::get(txn)->shouldRelaxIndexConstraints(indexNss);
if (indexSpec["background"].trueValue()) {
@@ -863,13 +878,12 @@ Status applyOperation_inlock(OperationContext* txn,
BSONObjBuilder b;
b.append(o.getField("_id"));
- const NamespaceString requestNs(ns);
- UpdateRequest request(requestNs);
+ UpdateRequest request(requestNss);
request.setQuery(b.done());
request.setUpdates(o);
request.setUpsert();
- UpdateLifecycleImpl updateLifecycle(requestNs);
+ UpdateLifecycleImpl updateLifecycle(requestNss);
request.setLifecycle(&updateLifecycle);
UpdateResult res = update(txn, db, request);
@@ -894,13 +908,12 @@ Status applyOperation_inlock(OperationContext* txn,
str::stream() << "Failed to apply update due to missing _id: " << op.toString(),
updateCriteria.hasField("_id"));
- const NamespaceString requestNs(ns);
- UpdateRequest request(requestNs);
+ UpdateRequest request(requestNss);
request.setQuery(updateCriteria);
request.setUpdates(o);
request.setUpsert(upsert);
- UpdateLifecycleImpl updateLifecycle(requestNs);
+ UpdateLifecycleImpl updateLifecycle(requestNss);
request.setLifecycle(&updateLifecycle);
UpdateResult ur = update(txn, db, request);
@@ -954,7 +967,12 @@ Status applyOperation_inlock(OperationContext* txn,
o.hasField("_id"));
if (opType[1] == 0) {
- deleteObjects(txn, collection, ns, o, PlanExecutor::YIELD_MANUAL, /*justOne*/ valueB);
+ deleteObjects(txn,
+ collection,
+ requestNss.ns().c_str(),
+ o,
+ PlanExecutor::YIELD_MANUAL,
+ /*justOne*/ valueB);
} else
verify(opType[1] == 'b'); // "db" advertisement
if (incrementOpsAppliedStats) {
@@ -970,16 +988,6 @@ Status applyOperation_inlock(OperationContext* txn,
14825, str::stream() << "error in applyOperation : unknown opType " << *opType);
}
- // AuthorizationManager's logOp method registers a RecoveryUnit::Change and to do so we need
- // to a new WriteUnitOfWork, if we dont have a wrapping unit of work already. If we already
- // have a wrapping WUOW, the extra nexting is harmless. The logOp really should have been
- // done in the WUOW that did the write, but this won't happen because applyOps turns off
- // observers.
- WriteUnitOfWork wuow(txn);
- getGlobalAuthorizationManager()->logOp(
- txn, opType, ns.toString().c_str(), o, fieldO2.isABSONObj() ? &o2 : NULL);
- wuow.commit();
-
return Status::OK();
}
@@ -1021,9 +1029,15 @@ Status applyCommand_inlock(OperationContext* txn,
// Applying renameCollection during initial sync might lead to data corruption, so we restart
// the initial sync.
if (!inSteadyStateReplication && o.firstElementFieldName() == std::string("renameCollection")) {
- return Status(ErrorCodes::OplogOperationUnsupported,
- str::stream() << "Applying renameCollection not supported in initial sync: "
- << redact(op));
+ if (!allowUnsafeRenamesDuringInitialSync.load()) {
+ return Status(ErrorCodes::OplogOperationUnsupported,
+ str::stream()
+ << "Applying renameCollection not supported in initial sync: "
+ << redact(op));
+ }
+ warning() << "allowUnsafeRenamesDuringInitialSync set to true. Applying renameCollection "
+ "operation during initial sync even though it may lead to data corruption: "
+ << redact(op);
}
// Applying commands in repl is done under Global W-lock, so it is safe to not
@@ -1071,11 +1085,8 @@ Status applyCommand_inlock(OperationContext* txn,
break;
}
default:
- if (_oplogCollectionName == masterSlaveOplogName) {
- error() << "Failed command " << redact(o) << " on " << nss.db()
- << " with status " << status << " during oplog application";
- } else if (curOpToApply.acceptableErrors.find(status.code()) ==
- curOpToApply.acceptableErrors.end()) {
+ if (curOpToApply.acceptableErrors.find(status.code()) ==
+ curOpToApply.acceptableErrors.end()) {
error() << "Failed command " << redact(o) << " on " << nss.db()
<< " with status " << status << " during oplog application";
return status;
diff --git a/src/mongo/db/repl/oplog.h b/src/mongo/db/repl/oplog.h
index b2078e93d28..f51aadb8dcf 100644
--- a/src/mongo/db/repl/oplog.h
+++ b/src/mongo/db/repl/oplog.h
@@ -104,6 +104,15 @@ void oplogCheckCloseDatabase(OperationContext* txn, Database* db);
using IncrementOpsAppliedStatsFn = stdx::function<void()>;
/**
+ * Take the object field of a BSONObj, the BSONObj, and the namespace of
+ * the operation and perform necessary validation to ensure the BSONObj is a
+ * properly-formed command to insert into system.indexes. This is only to
+ * be used for insert operations into system.indexes. It is called via applyOps.
+ */
+std::pair<BSONObj, NamespaceString> prepForApplyOpsIndexInsert(const BSONElement& fieldO,
+ const BSONObj& op,
+ const NamespaceString& requestNss);
+/**
* Take a non-command op and apply it locally
* Used for applying from an oplog
* @param inSteadyStateReplication convert some updates to upserts for idempotency reasons
diff --git a/src/mongo/db/repl/oplog_fetcher.cpp b/src/mongo/db/repl/oplog_fetcher.cpp
index ece0ef266fd..c9b96ab77f7 100644
--- a/src/mongo/db/repl/oplog_fetcher.cpp
+++ b/src/mongo/db/repl/oplog_fetcher.cpp
@@ -36,6 +36,7 @@
#include "mongo/db/commands/server_status_metric.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/repl/replication_coordinator.h"
+#include "mongo/db/server_parameters.h"
#include "mongo/db/stats/timer_stats.h"
#include "mongo/rpc/metadata/oplog_query_metadata.h"
#include "mongo/rpc/metadata/server_selection_metadata.h"
@@ -56,8 +57,12 @@ MONGO_FP_DECLARE(stopReplProducer);
namespace {
-Seconds kOplogInitialFindMaxTime{60};
-Seconds kOplogQueryNetworkTimeout{65}; // 5 seconds past the find command's 1 minute maxTimeMs
+// Number of seconds for the `maxTimeMS` on the initial `find` command.
+MONGO_EXPORT_SERVER_PARAMETER(oplogInitialFindMaxSeconds, int, 60);
+
+// Number of milliseconds to add to the `find` and `getMore` timeouts to calculate the network
+// timeout for the requests.
+const Milliseconds kNetworkTimeoutBufferMS{5000};
Counter64 readersCreatedStats;
ServerStatusMetricField<Counter64> displayReadersCreated("repl.network.readersCreated",
@@ -91,17 +96,22 @@ Milliseconds calculateAwaitDataTimeout(const ReplSetConfig& config) {
*/
BSONObj makeFindCommandObject(const NamespaceString& nss,
long long currentTerm,
- OpTime lastOpTimeFetched) {
+ OpTime lastOpTimeFetched,
+ Milliseconds fetcherMaxTimeMS) {
BSONObjBuilder cmdBob;
cmdBob.append("find", nss.coll());
cmdBob.append("filter", BSON("ts" << BSON("$gte" << lastOpTimeFetched.getTimestamp())));
cmdBob.append("tailable", true);
cmdBob.append("oplogReplay", true);
cmdBob.append("awaitData", true);
- cmdBob.append("maxTimeMS", durationCount<Milliseconds>(kOplogInitialFindMaxTime));
+ cmdBob.append("maxTimeMS", durationCount<Milliseconds>(fetcherMaxTimeMS));
if (currentTerm != OpTime::kUninitializedTerm) {
cmdBob.append("term", currentTerm);
}
+ if (serverGlobalParams.featureCompatibility.version.load() ==
+ ServerGlobalParams::FeatureCompatibility::Version::k34) {
+ cmdBob.append("readConcern", BSON("afterOpTime" << lastOpTimeFetched.toBSON()));
+ }
return cmdBob.obj();
}
@@ -450,6 +460,14 @@ BSONObj OplogFetcher::getMetadataObject_forTest() const {
}
Milliseconds OplogFetcher::getAwaitDataTimeout_forTest() const {
+ return _getGetMoreMaxTime();
+}
+
+Milliseconds OplogFetcher::_getFindMaxTime() const {
+ return Milliseconds(oplogInitialFindMaxSeconds.load() * 1000);
+}
+
+Milliseconds OplogFetcher::_getGetMoreMaxTime() const {
return _awaitDataTimeout;
}
@@ -660,7 +678,7 @@ void OplogFetcher::_callback(const Fetcher::QueryResponseStatus& result,
getMoreBob->appendElements(makeGetMoreCommandObject(queryResponse.nss,
queryResponse.cursorId,
lastCommittedWithCurrentTerm,
- _awaitDataTimeout));
+ _getGetMoreMaxTime()));
}
void OplogFetcher::_finishCallback(Status status) {
@@ -691,10 +709,11 @@ std::unique_ptr<Fetcher> OplogFetcher::_makeFetcher(long long currentTerm,
_executor,
_source,
_nss.db().toString(),
- makeFindCommandObject(_nss, currentTerm, lastFetchedOpTime),
+ makeFindCommandObject(_nss, currentTerm, lastFetchedOpTime, _getFindMaxTime()),
stdx::bind(&OplogFetcher::_callback, this, stdx::placeholders::_1, stdx::placeholders::_3),
_metadataObject,
- kOplogQueryNetworkTimeout);
+ _getFindMaxTime() + kNetworkTimeoutBufferMS,
+ _getGetMoreMaxTime() + kNetworkTimeoutBufferMS);
}
bool OplogFetcher::_isShuttingDown() const {
diff --git a/src/mongo/db/repl/oplog_fetcher.h b/src/mongo/db/repl/oplog_fetcher.h
index ad4fde296b1..54bfbabbf8d 100644
--- a/src/mongo/db/repl/oplog_fetcher.h
+++ b/src/mongo/db/repl/oplog_fetcher.h
@@ -235,6 +235,16 @@ private:
void _finishCallback(Status status, OpTimeWithHash opTimeWithHash);
/**
+ * Returns how long the `find` command should wait before timing out.
+ */
+ virtual Milliseconds _getFindMaxTime() const;
+
+ /**
+ * Returns how long the `getMore` command should wait before timing out.
+ */
+ virtual Milliseconds _getGetMoreMaxTime() const;
+
+ /**
* Creates a new instance of the fetcher to tail the remote oplog starting at the given optime.
*/
std::unique_ptr<Fetcher> _makeFetcher(long long currentTerm, OpTime lastFetchedOpTime);
diff --git a/src/mongo/db/repl/repl_client_info.cpp b/src/mongo/db/repl/repl_client_info.cpp
index 3e98a0cb9d3..5b77e3aa6f3 100644
--- a/src/mongo/db/repl/repl_client_info.cpp
+++ b/src/mongo/db/repl/repl_client_info.cpp
@@ -26,6 +26,8 @@
* it in the license file.
*/
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kReplication
+
#include "mongo/platform/basic.h"
#include "mongo/db/repl/repl_client_info.h"
@@ -36,6 +38,7 @@
#include "mongo/db/operation_context.h"
#include "mongo/db/repl/replication_coordinator_global.h"
#include "mongo/util/decorable.h"
+#include "mongo/util/log.h"
namespace mongo {
namespace repl {
@@ -48,10 +51,23 @@ void ReplClientInfo::setLastOp(const OpTime& ot) {
_lastOp = ot;
}
+
void ReplClientInfo::setLastOpToSystemLastOpTime(OperationContext* txn) {
ReplicationCoordinator* replCoord = repl::ReplicationCoordinator::get(txn->getServiceContext());
if (replCoord->isReplEnabled() && txn->writesAreReplicated()) {
- setLastOp(replCoord->getMyLastAppliedOpTime());
+ auto systemOpTime = replCoord->getMyLastAppliedOpTime();
+
+ // If the system optime has gone backwards, that must mean that there was a rollback.
+ // This is safe, but the last op for a Client should never go backwards, so just leave
+ // the last op for this Client as it was.
+ if (systemOpTime >= _lastOp) {
+ _lastOp = systemOpTime;
+ } else {
+ log() << "Not setting the last OpTime for this Client from " << _lastOp
+ << " to the current system time of " << systemOpTime
+ << " as that would be moving the OpTime backwards. This should only happen if "
+ "there was a rollback recently";
+ }
}
}
diff --git a/src/mongo/db/repl/repl_set_config.cpp b/src/mongo/db/repl/repl_set_config.cpp
index 1ad2d245ac6..024c3ff04a5 100644
--- a/src/mongo/db/repl/repl_set_config.cpp
+++ b/src/mongo/db/repl/repl_set_config.cpp
@@ -35,11 +35,19 @@
#include "mongo/bson/util/bson_check.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/mongod_options.h"
#include "mongo/db/server_options.h"
+#include "mongo/db/server_parameters.h"
#include "mongo/stdx/functional.h"
#include "mongo/util/stringutils.h"
namespace mongo {
+/**
+ * Dont run any sharding validations. Can not be combined with --configsvr or shardvr. Intended to
+ * allow restarting config server or shard as an independent replica set.
+ */
+MONGO_EXPORT_STARTUP_SERVER_PARAMETER(skipShardingConfigurationChecks, bool, false);
+
namespace repl {
const size_t ReplSetConfig::kMaxMembers;
@@ -552,7 +560,8 @@ Status ReplSetConfig::validate() const {
"servers cannot have a non-zero slaveDelay");
}
}
- if (serverGlobalParams.clusterRole != ClusterRole::ConfigServer) {
+ if (serverGlobalParams.clusterRole != ClusterRole::ConfigServer &&
+ !skipShardingConfigurationChecks) {
return Status(ErrorCodes::BadValue,
"Nodes being used for config servers must be started with the "
"--configsvr flag");
diff --git a/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp b/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp
index cfc6dbb55e1..05d070c08fe 100644
--- a/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp
+++ b/src/mongo/db/repl/replication_coordinator_external_state_impl.cpp
@@ -240,8 +240,11 @@ void ReplicationCoordinatorExternalStateImpl::startSteadyStateReplication(
_applierThread->startup();
log() << "Starting replication reporter thread";
invariant(!_syncSourceFeedbackThread);
- _syncSourceFeedbackThread.reset(new stdx::thread(stdx::bind(
- &SyncSourceFeedback::run, &_syncSourceFeedback, _taskExecutor.get(), _bgSync.get())));
+ _syncSourceFeedbackThread.reset(new stdx::thread(stdx::bind(&SyncSourceFeedback::run,
+ &_syncSourceFeedback,
+ _taskExecutor.get(),
+ _bgSync.get(),
+ replCoord)));
}
void ReplicationCoordinatorExternalStateImpl::stopDataReplication(OperationContext* txn) {
diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp
index 3de3d35bb61..ad1a9fd93ba 100644
--- a/src/mongo/db/repl/replication_coordinator_impl.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl.cpp
@@ -2922,6 +2922,26 @@ ReplicationCoordinatorImpl::_setCurrentRSConfig_inlock(const ReplSetConfig& newC
const ReplSetConfig oldConfig = _rsConfig;
_rsConfig = newConfig;
_protVersion.store(_rsConfig.getProtocolVersion());
+
+ // Warn if running --nojournal and writeConcernMajorityJournalDefault = false
+ StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine();
+ if (storageEngine && !storageEngine->isDurable() &&
+ (newConfig.getWriteConcernMajorityShouldJournal() &&
+ (!oldConfig.isInitialized() || !oldConfig.getWriteConcernMajorityShouldJournal()))) {
+ log() << startupWarningsLog;
+ log() << "** WARNING: This replica set is running without journaling enabled but the "
+ << startupWarningsLog;
+ log() << "** writeConcernMajorityJournalDefault option to the replica set config "
+ << startupWarningsLog;
+ log() << "** is set to true. The writeConcernMajorityJournalDefault "
+ << startupWarningsLog;
+ log() << "** option to the replica set config must be set to false "
+ << startupWarningsLog;
+ log() << "** or w:majority write concerns will never complete."
+ << startupWarningsLog;
+ log() << startupWarningsLog;
+ }
+
log() << "New replica set config in use: " << _rsConfig.toBSON() << rsLog;
_selfIndex = myIndex;
if (_selfIndex >= 0) {
diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
index fbd976ec6b7..1dd9bf5b7c3 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
@@ -133,7 +133,9 @@ void ReplicationCoordinatorImpl::_startElectSelfV1() {
return;
}
- log() << "conducting a dry run election to see if we could be elected";
+ long long term = _topCoord->getTerm();
+
+ log() << "conducting a dry run election to see if we could be elected. current term: " << term;
_voteRequester.reset(new VoteRequester);
// This is necessary because the voteRequester may call directly into winning an
@@ -141,12 +143,11 @@ void ReplicationCoordinatorImpl::_startElectSelfV1() {
// _mutex again.
lk.unlock();
- long long term = _topCoord->getTerm();
StatusWith<ReplicationExecutor::EventHandle> nextPhaseEvh =
_voteRequester->start(&_replExecutor,
_rsConfig,
_selfIndex,
- _topCoord->getTerm(),
+ term,
true, // dry run
lastOpTime);
if (nextPhaseEvh.getStatus() == ErrorCodes::ShutdownInProgress) {
@@ -165,7 +166,8 @@ void ReplicationCoordinatorImpl::_onDryRunComplete(long long originalTerm) {
LockGuard lk(_topoMutex);
if (_topCoord->getTerm() != originalTerm) {
- log() << "not running for primary, we have been superceded already";
+ log() << "not running for primary, we have been superseded already during dry run. "
+ << "original term: " << originalTerm << ", current term: " << _topCoord->getTerm();
return;
}
@@ -175,23 +177,24 @@ void ReplicationCoordinatorImpl::_onDryRunComplete(long long originalTerm) {
log() << "not running for primary, we received insufficient votes";
return;
} else if (endResult == VoteRequester::Result::kStaleTerm) {
- log() << "not running for primary, we have been superceded already";
+ log() << "not running for primary, we have been superseded already";
return;
} else if (endResult != VoteRequester::Result::kSuccessfullyElected) {
log() << "not running for primary, we received an unexpected problem";
return;
}
- log() << "dry election run succeeded, running for election";
+ long long newTerm = originalTerm + 1;
+ log() << "dry election run succeeded, running for election in term " << newTerm;
// Stepdown is impossible from this term update.
TopologyCoordinator::UpdateTermResult updateTermResult;
- _updateTerm_incallback(originalTerm + 1, &updateTermResult);
+ _updateTerm_incallback(newTerm, &updateTermResult);
invariant(updateTermResult == TopologyCoordinator::UpdateTermResult::kUpdatedTerm);
// Secure our vote for ourself first
_topCoord->voteForMyselfV1();
// Store the vote in persistent storage.
- LastVote lastVote{originalTerm + 1, _selfIndex};
+ LastVote lastVote{newTerm, _selfIndex};
auto cbStatus = _replExecutor.scheduleDBWork(
[this, lastVote](const ReplicationExecutor::CallbackArgs& cbData) {
@@ -232,12 +235,19 @@ void ReplicationCoordinatorImpl::_startVoteRequester(long long newTerm) {
LockGuard lk(_topoMutex);
+ if (_topCoord->getTerm() != newTerm) {
+ log() << "not running for primary, we have been superseded already while writing our last "
+ "vote. election term: "
+ << newTerm << ", current term: " << _topCoord->getTerm();
+ return;
+ }
+
const auto lastOpTime =
_isDurableStorageEngine() ? getMyLastDurableOpTime() : getMyLastAppliedOpTime();
_voteRequester.reset(new VoteRequester);
- StatusWith<ReplicationExecutor::EventHandle> nextPhaseEvh = _voteRequester->start(
- &_replExecutor, _rsConfig, _selfIndex, _topCoord->getTerm(), false, lastOpTime);
+ StatusWith<ReplicationExecutor::EventHandle> nextPhaseEvh =
+ _voteRequester->start(&_replExecutor, _rsConfig, _selfIndex, newTerm, false, lastOpTime);
if (nextPhaseEvh.getStatus() == ErrorCodes::ShutdownInProgress) {
return;
}
@@ -249,14 +259,15 @@ void ReplicationCoordinatorImpl::_startVoteRequester(long long newTerm) {
lossGuard.dismiss();
}
-void ReplicationCoordinatorImpl::_onVoteRequestComplete(long long originalTerm) {
+void ReplicationCoordinatorImpl::_onVoteRequestComplete(long long newTerm) {
invariant(_voteRequester);
LoseElectionGuardV1 lossGuard(this);
LockGuard lk(_topoMutex);
- if (_topCoord->getTerm() != originalTerm) {
- log() << "not becoming primary, we have been superceded already";
+ if (_topCoord->getTerm() != newTerm) {
+ log() << "not becoming primary, we have been superseded already during election. "
+ << "election term: " << newTerm << ", current term: " << _topCoord->getTerm();
return;
}
@@ -267,7 +278,7 @@ void ReplicationCoordinatorImpl::_onVoteRequestComplete(long long originalTerm)
log() << "not becoming primary, we received insufficient votes";
return;
case VoteRequester::Result::kStaleTerm:
- log() << "not becoming primary, we have been superceded already";
+ log() << "not becoming primary, we have been superseded already";
return;
case VoteRequester::Result::kSuccessfullyElected:
log() << "election succeeded, assuming primary role in term " << _topCoord->getTerm();
diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
index 6e87f882b48..0f550869546 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
@@ -431,7 +431,7 @@ TEST_F(ReplCoordTest, ElectionFailsWhenDryRunResponseContainsANewerTerm) {
getReplCoord()->waitForElectionFinish_forTest();
stopCapturingLogMessages();
ASSERT_EQUALS(
- 1, countLogLinesContaining("not running for primary, we have been superceded already"));
+ 1, countLogLinesContaining("not running for primary, we have been superseded already"));
}
TEST_F(ReplCoordTest, NodeWillNotStandForElectionDuringHeartbeatReconfig) {
@@ -689,7 +689,7 @@ TEST_F(ReplCoordTest, ElectionFailsWhenVoteRequestResponseContainsANewerTerm) {
getReplCoord()->waitForElectionFinish_forTest();
stopCapturingLogMessages();
ASSERT_EQUALS(1,
- countLogLinesContaining("not becoming primary, we have been superceded already"));
+ countLogLinesContaining("not becoming primary, we have been superseded already"));
}
TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringDryRun) {
@@ -728,8 +728,9 @@ TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringDryRun) {
simulateSuccessfulDryRun(onDryRunRequest);
stopCapturingLogMessages();
- ASSERT_EQUALS(
- 1, countLogLinesContaining("not running for primary, we have been superceded already"));
+ ASSERT_EQUALS(1,
+ countLogLinesContaining(
+ "not running for primary, we have been superseded already during dry run"));
}
TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringActualElection) {
@@ -784,7 +785,7 @@ TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringActualElection) {
getReplCoord()->waitForElectionFinish_forTest();
stopCapturingLogMessages();
ASSERT_EQUALS(1,
- countLogLinesContaining("not becoming primary, we have been superceded already"));
+ countLogLinesContaining("not becoming primary, we have been superseded already"));
}
class PriorityTakeoverTest : public ReplCoordTest {
diff --git a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
index b72d7e7dbcc..66c9799029d 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
@@ -778,18 +778,24 @@ void ReplicationCoordinatorImpl::_scheduleNextLivenessUpdate_inlock() {
}
auto nextTimeout = earliestDate + _rsConfig.getElectionTimeoutPeriod();
- if (nextTimeout > _replExecutor.now()) {
- LOG(3) << "scheduling next check at " << nextTimeout;
- auto cbh = _scheduleWorkAt(nextTimeout,
- stdx::bind(&ReplicationCoordinatorImpl::_handleLivenessTimeout,
- this,
- stdx::placeholders::_1));
- if (!cbh) {
- return;
- }
- _handleLivenessTimeoutCbh = cbh;
- _earliestMemberId = earliestMemberId;
+ LOG(3) << "scheduling next check at " << nextTimeout;
+
+ // It is possible we will schedule the next timeout in the past.
+ // ReplicationExecutor::_scheduleWorkAt() schedules its work immediately if it's given a
+ // time <= now().
+ // If we missed the timeout, it means that on our last check the earliest live member was
+ // just barely fresh and it has become stale since then. We must schedule another liveness
+ // check to continue conducting liveness checks and be able to step down from primary if we
+ // lose contact with a majority of nodes.
+ auto cbh = _scheduleWorkAt(nextTimeout,
+ stdx::bind(&ReplicationCoordinatorImpl::_handleLivenessTimeout,
+ this,
+ stdx::placeholders::_1));
+ if (!cbh) {
+ return;
}
+ _handleLivenessTimeoutCbh = cbh;
+ _earliestMemberId = earliestMemberId;
}
void ReplicationCoordinatorImpl::_cancelAndRescheduleLivenessUpdate_inlock(int updatedMemberId) {
diff --git a/src/mongo/db/repl/replication_executor_test.cpp b/src/mongo/db/repl/replication_executor_test.cpp
index ba41df46769..2e8a66465de 100644
--- a/src/mongo/db/repl/replication_executor_test.cpp
+++ b/src/mongo/db/repl/replication_executor_test.cpp
@@ -249,6 +249,21 @@ TEST_F(ReplicationExecutorTest, ScheduleCallbackAtNow) {
executor.waitForEvent(finishEvent);
}
+TEST_F(ReplicationExecutorTest, ScheduleCallbackInPast) {
+ launchExecutorThread();
+ getNet()->exitNetwork();
+
+ ReplicationExecutor& executor = getReplExecutor();
+ auto finishEvent = assertGet(executor.makeEvent());
+ auto fn = [&executor, finishEvent](const ReplicationExecutor::CallbackArgs& cbData) {
+ ASSERT_OK(cbData.status);
+ executor.signalEvent(finishEvent);
+ };
+
+ auto cb = executor.scheduleWorkAt(getNet()->now() - Milliseconds(1000), fn);
+ executor.waitForEvent(finishEvent);
+}
+
TEST_F(ReplicationExecutorTest, ScheduleCallbackAtAFutureTime) {
launchExecutorThread();
getNet()->exitNetwork();
diff --git a/src/mongo/db/repl/sync_source_feedback.cpp b/src/mongo/db/repl/sync_source_feedback.cpp
index 4d395e376e6..a82b1553212 100644
--- a/src/mongo/db/repl/sync_source_feedback.cpp
+++ b/src/mongo/db/repl/sync_source_feedback.cpp
@@ -50,27 +50,23 @@ namespace repl {
namespace {
/**
- * Calculates the keep alive interval based on the current configuration in the replication
- * coordinator.
+ * Calculates the keep alive interval based on the given ReplSetConfig.
*/
-Milliseconds calculateKeepAliveInterval(OperationContext* txn, stdx::mutex& mtx) {
- stdx::lock_guard<stdx::mutex> lock(mtx);
- auto replCoord = repl::ReplicationCoordinator::get(txn);
- auto rsConfig = replCoord->getConfig();
- auto keepAliveInterval = rsConfig.getElectionTimeoutPeriod() / 2;
- return keepAliveInterval;
+Milliseconds calculateKeepAliveInterval(const ReplSetConfig& rsConfig) {
+ return rsConfig.getElectionTimeoutPeriod() / 2;
}
/**
* Returns function to prepare update command
*/
Reporter::PrepareReplSetUpdatePositionCommandFn makePrepareReplSetUpdatePositionCommandFn(
- OperationContext* txn,
+ ReplicationCoordinator* replCoord,
stdx::mutex& mtx,
const HostAndPort& syncTarget,
BackgroundSync* bgsync) {
- return [&mtx, syncTarget, txn, bgsync](ReplicationCoordinator::ReplSetUpdatePositionCommandStyle
- commandStyle) -> StatusWith<BSONObj> {
+ return [&mtx, syncTarget, replCoord, bgsync](
+ ReplicationCoordinator::ReplSetUpdatePositionCommandStyle
+ commandStyle) -> StatusWith<BSONObj> {
auto currentSyncTarget = bgsync->getSyncTarget();
if (currentSyncTarget != syncTarget) {
if (currentSyncTarget.empty()) {
@@ -86,7 +82,6 @@ Reporter::PrepareReplSetUpdatePositionCommandFn makePrepareReplSetUpdatePosition
}
}
- auto replCoord = repl::ReplicationCoordinator::get(txn);
if (replCoord->getMemberState().primary()) {
// Primary has no one to send updates to.
return Status(ErrorCodes::InvalidSyncSource,
@@ -114,7 +109,7 @@ void SyncSourceFeedback::forwardSlaveProgress() {
}
}
-Status SyncSourceFeedback::_updateUpstream(OperationContext* txn,
+Status SyncSourceFeedback::_updateUpstream(ReplicationCoordinator* replCoord,
BackgroundSync* bgsync,
Reporter* reporter) {
auto syncTarget = reporter->getTarget();
@@ -139,7 +134,6 @@ Status SyncSourceFeedback::_updateUpstream(OperationContext* txn,
} else {
// Blacklist sync target for .5 seconds and find a new one.
stdx::lock_guard<stdx::mutex> lock(_mtx);
- auto replCoord = repl::ReplicationCoordinator::get(txn);
const auto blacklistDuration = Milliseconds{500};
const auto until = Date_t::now() + blacklistDuration;
log() << "Blacklisting " << syncTarget << " due to error: '" << status << "' for "
@@ -161,7 +155,9 @@ void SyncSourceFeedback::shutdown() {
_cond.notify_all();
}
-void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* bgsync) {
+void SyncSourceFeedback::run(executor::TaskExecutor* executor,
+ BackgroundSync* bgsync,
+ ReplicationCoordinator* replCoord) {
Client::initThread("SyncSourceFeedback");
HostAndPort syncTarget;
@@ -170,10 +166,9 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
Milliseconds keepAliveInterval(0);
while (true) { // breaks once _shutdownSignaled is true
- auto txn = cc().makeOperationContext();
if (keepAliveInterval == Milliseconds(0)) {
- keepAliveInterval = calculateKeepAliveInterval(txn.get(), _mtx);
+ keepAliveInterval = calculateKeepAliveInterval(replCoord->getConfig());
}
{
@@ -189,7 +184,7 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
continue;
}
}
- MemberState state = ReplicationCoordinator::get(txn.get())->getMemberState();
+ MemberState state = replCoord->getMemberState();
if (!(state.primary() || state.startup())) {
break;
}
@@ -204,7 +199,7 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
{
stdx::lock_guard<stdx::mutex> lock(_mtx);
- MemberState state = ReplicationCoordinator::get(txn.get())->getMemberState();
+ MemberState state = replCoord->getMemberState();
if (state.primary() || state.startup()) {
continue;
}
@@ -226,7 +221,7 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
// Update keepalive value from config.
auto oldKeepAliveInterval = keepAliveInterval;
- keepAliveInterval = calculateKeepAliveInterval(txn.get(), _mtx);
+ keepAliveInterval = calculateKeepAliveInterval(replCoord->getConfig());
if (oldKeepAliveInterval != keepAliveInterval) {
LOG(1) << "new syncSourceFeedback keep alive duration = " << keepAliveInterval
<< " (previously " << oldKeepAliveInterval << ")";
@@ -235,7 +230,7 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
Reporter reporter(
executor,
- makePrepareReplSetUpdatePositionCommandFn(txn.get(), _mtx, syncTarget, bgsync),
+ makePrepareReplSetUpdatePositionCommandFn(replCoord, _mtx, syncTarget, bgsync),
syncTarget,
keepAliveInterval);
{
@@ -250,7 +245,7 @@ void SyncSourceFeedback::run(executor::TaskExecutor* executor, BackgroundSync* b
_reporter = nullptr;
});
- auto status = _updateUpstream(txn.get(), bgsync, &reporter);
+ auto status = _updateUpstream(replCoord, bgsync, &reporter);
if (!status.isOK()) {
LOG(1) << "The replication progress command (replSetUpdatePosition) failed and will be "
"retried: "
diff --git a/src/mongo/db/repl/sync_source_feedback.h b/src/mongo/db/repl/sync_source_feedback.h
index 40c29dc172a..35ed470fc26 100644
--- a/src/mongo/db/repl/sync_source_feedback.h
+++ b/src/mongo/db/repl/sync_source_feedback.h
@@ -31,6 +31,7 @@
#include "mongo/base/disallow_copying.h"
#include "mongo/base/status.h"
+#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/stdx/condition_variable.h"
#include "mongo/stdx/mutex.h"
@@ -63,7 +64,9 @@ public:
*
* Task executor is used to run replSetUpdatePosition command on sync source.
*/
- void run(executor::TaskExecutor* executor, BackgroundSync* bgsync);
+ void run(executor::TaskExecutor* executor,
+ BackgroundSync* bgsync,
+ ReplicationCoordinator* replCoord);
/// Signals the run() method to terminate.
void shutdown();
@@ -72,7 +75,9 @@ private:
/* Inform the sync target of our current position in the oplog, as well as the positions
* of all secondaries chained through us.
*/
- Status _updateUpstream(OperationContext* txn, BackgroundSync* bgsync, Reporter* reporter);
+ Status _updateUpstream(ReplicationCoordinator* replCoord,
+ BackgroundSync* bgsync,
+ Reporter* reporter);
// protects cond, _shutdownSignaled, _keepAliveInterval, and _positionChanged.
stdx::mutex _mtx;
diff --git a/src/mongo/db/repl/sync_source_resolver.cpp b/src/mongo/db/repl/sync_source_resolver.cpp
index 948a471b245..17a706f1b61 100644
--- a/src/mongo/db/repl/sync_source_resolver.cpp
+++ b/src/mongo/db/repl/sync_source_resolver.cpp
@@ -54,6 +54,7 @@ const Seconds SyncSourceResolver::kFirstOplogEntryEmptyBlacklistDuration(10);
const Seconds SyncSourceResolver::kFirstOplogEntryNullTimestampBlacklistDuration(10);
const Minutes SyncSourceResolver::kTooStaleBlacklistDuration(1);
const Seconds SyncSourceResolver::kNoRequiredOpTimeBlacklistDuration(60);
+const int SyncSourceResolver::kUninitializedRollbackId(-1);
SyncSourceResolver::SyncSourceResolver(executor::TaskExecutor* taskExecutor,
SyncSourceSelector* syncSourceSelector,
@@ -175,11 +176,13 @@ std::unique_ptr<Fetcher> SyncSourceResolver::_makeFirstOplogEntryFetcher(
candidate,
earliestOpTimeSeen),
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- kFetcherTimeout);
+ kFetcherTimeout /* find network timeout */,
+ kFetcherTimeout /* getMore network timeout */);
}
std::unique_ptr<Fetcher> SyncSourceResolver::_makeRequiredOpTimeFetcher(HostAndPort candidate,
- OpTime earliestOpTimeSeen) {
+ OpTime earliestOpTimeSeen,
+ int rbid) {
// This query is structured so that it is executed on the sync source using the oplog
// start hack (oplogReplay=true and $gt/$gte predicate over "ts").
return stdx::make_unique<Fetcher>(
@@ -193,9 +196,11 @@ std::unique_ptr<Fetcher> SyncSourceResolver::_makeRequiredOpTimeFetcher(HostAndP
this,
stdx::placeholders::_1,
candidate,
- earliestOpTimeSeen),
+ earliestOpTimeSeen,
+ rbid),
rpc::ServerSelectionMetadata(true, boost::none).toBSON(),
- kFetcherTimeout);
+ kFetcherTimeout /* find network timeout */,
+ kFetcherTimeout /* getMore network timeout */);
}
Status SyncSourceResolver::_scheduleFetcher(std::unique_ptr<Fetcher> fetcher) {
@@ -205,6 +210,9 @@ Status SyncSourceResolver::_scheduleFetcher(std::unique_ptr<Fetcher> fetcher) {
// executor.
auto status = fetcher->schedule();
if (status.isOK()) {
+ // Fetcher destruction blocks on all outstanding callbacks. If we are currently in a
+ // Fetcher-related callback, we can't destroy the Fetcher just yet, so we assign it to a
+ // temporary unique pointer to allow the destruction to run to completion.
_shuttingDownFetcher = std::move(_fetcher);
_fetcher = std::move(fetcher);
} else {
@@ -311,10 +319,26 @@ void SyncSourceResolver::_firstOplogEntryFetcherCallback(
return;
}
- _scheduleRBIDRequest(candidate, earliestOpTimeSeen);
+ auto status = _scheduleRBIDRequest(candidate, earliestOpTimeSeen);
+ if (!status.isOK()) {
+ _finishCallback(status);
+ }
}
-void SyncSourceResolver::_scheduleRBIDRequest(HostAndPort candidate, OpTime earliestOpTimeSeen) {
+Status SyncSourceResolver::_scheduleRBIDRequest(HostAndPort candidate, OpTime earliestOpTimeSeen) {
+ // Once a work is scheduled, nothing prevents it finishing. We need the mutex to protect the
+ // access of member variables after scheduling, because otherwise the scheduled callback could
+ // finish and allow the destructor to fire before we access the member variables.
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ if (_state == State::kShuttingDown) {
+ return Status(
+ ErrorCodes::CallbackCanceled,
+ str::stream()
+ << "sync source resolver shut down while checking rollbackId on candidate: "
+ << candidate);
+ }
+
+ invariant(_state == State::kRunning);
auto handle = _taskExecutor->scheduleRemoteCommand(
{candidate, "admin", BSON("replSetGetRBID" << 1), nullptr, kFetcherTimeout},
stdx::bind(&SyncSourceResolver::_rbidRequestCallback,
@@ -324,15 +348,11 @@ void SyncSourceResolver::_scheduleRBIDRequest(HostAndPort candidate, OpTime earl
stdx::placeholders::_1));
if (!handle.isOK()) {
- _finishCallback(handle.getStatus());
- return;
+ return handle.getStatus();
}
- stdx::lock_guard<stdx::mutex> lk(_mutex);
_rbidCommandHandle = std::move(handle.getValue());
- if (_state == State::kShuttingDown) {
- _taskExecutor->cancel(_rbidCommandHandle);
- }
+ return Status::OK();
}
void SyncSourceResolver::_rbidRequestCallback(
@@ -344,10 +364,11 @@ void SyncSourceResolver::_rbidRequestCallback(
return;
}
+ int rbid = kUninitializedRollbackId;
try {
uassertStatusOK(rbidReply.response.status);
uassertStatusOK(getStatusFromCommandResult(rbidReply.response.data));
- _rbid = rbidReply.response.data["rbid"].Int();
+ rbid = rbidReply.response.data["rbid"].Int();
} catch (const DBException& ex) {
const auto until = _taskExecutor->now() + kFetcherErrorBlacklistDuration;
log() << "Blacklisting " << candidate << " due to error: '" << ex << "' for "
@@ -360,13 +381,15 @@ void SyncSourceResolver::_rbidRequestCallback(
if (!_requiredOpTime.isNull()) {
// Schedule fetcher to look for '_requiredOpTime' in the remote oplog.
// Unittest requires that this kind of failure be handled specially.
- auto status = _scheduleFetcher(_makeRequiredOpTimeFetcher(candidate, earliestOpTimeSeen));
+ auto status =
+ _scheduleFetcher(_makeRequiredOpTimeFetcher(candidate, earliestOpTimeSeen, rbid));
if (!status.isOK()) {
_finishCallback(status);
}
return;
}
- _finishCallback(candidate);
+
+ _finishCallback(candidate, rbid);
}
Status SyncSourceResolver::_compareRequiredOpTimeWithQueryResponse(
@@ -399,7 +422,8 @@ Status SyncSourceResolver::_compareRequiredOpTimeWithQueryResponse(
void SyncSourceResolver::_requiredOpTimeFetcherCallback(
const StatusWith<Fetcher::QueryResponse>& queryResult,
HostAndPort candidate,
- OpTime earliestOpTimeSeen) {
+ OpTime earliestOpTimeSeen,
+ int rbid) {
if (_isShuttingDown()) {
_finishCallback(Status(ErrorCodes::CallbackCanceled,
str::stream() << "sync source resolver shut down while looking for "
@@ -444,18 +468,18 @@ void SyncSourceResolver::_requiredOpTimeFetcherCallback(
return;
}
- _finishCallback(candidate);
+ _finishCallback(candidate, rbid);
}
Status SyncSourceResolver::_chooseAndProbeNextSyncSource(OpTime earliestOpTimeSeen) {
auto candidateResult = _chooseNewSyncSource();
if (!candidateResult.isOK()) {
- return _finishCallback(candidateResult);
+ return _finishCallback(candidateResult.getStatus());
}
if (candidateResult.getValue().empty()) {
if (earliestOpTimeSeen.isNull()) {
- return _finishCallback(candidateResult);
+ return _finishCallback(candidateResult.getValue(), kUninitializedRollbackId);
}
SyncSourceResolverResponse response;
@@ -473,16 +497,22 @@ Status SyncSourceResolver::_chooseAndProbeNextSyncSource(OpTime earliestOpTimeSe
return Status::OK();
}
-Status SyncSourceResolver::_finishCallback(StatusWith<HostAndPort> result) {
+Status SyncSourceResolver::_finishCallback(HostAndPort hostAndPort, int rbid) {
SyncSourceResolverResponse response;
- response.syncSourceStatus = std::move(result);
- if (response.isOK() && !response.getSyncSource().empty()) {
- invariant(_requiredOpTime.isNull() || _rbid);
- response.rbid = _rbid;
+ response.syncSourceStatus = std::move(hostAndPort);
+ if (rbid != kUninitializedRollbackId) {
+ response.rbid = rbid;
}
return _finishCallback(response);
}
+Status SyncSourceResolver::_finishCallback(Status status) {
+ invariant(!status.isOK());
+ SyncSourceResolverResponse response;
+ response.syncSourceStatus = std::move(status);
+ return _finishCallback(response);
+}
+
Status SyncSourceResolver::_finishCallback(const SyncSourceResolverResponse& response) {
try {
_onCompletion(response);
diff --git a/src/mongo/db/repl/sync_source_resolver.h b/src/mongo/db/repl/sync_source_resolver.h
index 201658caacf..0f9343cfcdb 100644
--- a/src/mongo/db/repl/sync_source_resolver.h
+++ b/src/mongo/db/repl/sync_source_resolver.h
@@ -102,6 +102,7 @@ public:
static const Seconds kFirstOplogEntryNullTimestampBlacklistDuration;
static const Minutes kTooStaleBlacklistDuration;
static const Seconds kNoRequiredOpTimeBlacklistDuration;
+ static const int kUninitializedRollbackId;
/**
* Callback function to report final status of resolving sync source.
@@ -154,7 +155,8 @@ private:
* Creates fetcher to check the remote oplog for '_requiredOpTime'.
*/
std::unique_ptr<Fetcher> _makeRequiredOpTimeFetcher(HostAndPort candidate,
- OpTime earliestOpTimeSeen);
+ OpTime earliestOpTimeSeen,
+ int rbid);
/**
* Schedules fetcher to read oplog on sync source.
@@ -179,7 +181,7 @@ private:
/**
* Schedules a replSetGetRBID command against the candidate to fetch its current rollback id.
*/
- void _scheduleRBIDRequest(HostAndPort candidate, OpTime earliestOpTimeSeen);
+ Status _scheduleRBIDRequest(HostAndPort candidate, OpTime earliestOpTimeSeen);
void _rbidRequestCallback(HostAndPort candidate,
OpTime earliestOpTimeSeen,
const executor::TaskExecutor::RemoteCommandCallbackArgs& rbidReply);
@@ -194,7 +196,8 @@ private:
*/
void _requiredOpTimeFetcherCallback(const StatusWith<Fetcher::QueryResponse>& queryResult,
HostAndPort candidate,
- OpTime earliestOpTimeSeen);
+ OpTime earliestOpTimeSeen,
+ int rbid);
/**
* Obtains new sync source candidate and schedules remote command to fetcher first oplog entry.
@@ -207,7 +210,8 @@ private:
* Invokes completion callback and transitions state to State::kComplete.
* Returns result.getStatus().
*/
- Status _finishCallback(StatusWith<HostAndPort> result);
+ Status _finishCallback(HostAndPort hostAndPort, int rbid);
+ Status _finishCallback(Status status);
Status _finishCallback(const SyncSourceResolverResponse& response);
// Executor used to send remote commands to sync source candidates.
@@ -229,9 +233,6 @@ private:
// resolver via this callback in a SyncSourceResolverResponse struct when the resolver finishes.
const OnCompletionFn _onCompletion;
- // The rbid we will return to our caller.
- int _rbid;
-
// Protects members of this sync source resolver defined below.
mutable stdx::mutex _mutex;
mutable stdx::condition_variable _condition;
diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp
index 30f1e28b619..3f65e3fc6b7 100644
--- a/src/mongo/db/repl/sync_tail.cpp
+++ b/src/mongo/db/repl/sync_tail.cpp
@@ -448,7 +448,6 @@ void applyOps(std::vector<MultiApplier::OperationPtrs>& writerVectors,
const MultiApplier::ApplyOperationFn& func,
std::vector<Status>* statusVector) {
invariant(writerVectors.size() == statusVector->size());
- TimerHolder timer(&applyBatchStats);
for (size_t i = 0; i < writerVectors.size(); i++) {
if (!writerVectors[i].empty()) {
writerPool->schedule([&func, &writerVectors, statusVector, i] {
@@ -695,32 +694,53 @@ public:
}
private:
+ /**
+ * Calculates batch limit size (in bytes) using the maximum capped collection size of the oplog
+ * size.
+ * Batches are limited to 10% of the oplog.
+ */
+ std::size_t _calculateBatchLimitBytes() {
+ auto opCtx = cc().makeOperationContext();
+ auto storageInterface = StorageInterface::get(opCtx.get());
+ auto oplogMaxSizeResult =
+ storageInterface->getOplogMaxSize(opCtx.get(), NamespaceString(rsOplogName));
+ auto oplogMaxSize = fassertStatusOK(40301, oplogMaxSizeResult);
+ return std::min(oplogMaxSize / 10, std::size_t(replBatchLimitBytes));
+ }
+
+ /**
+ * If slaveDelay is enabled, this function calculates the most recent timestamp of any oplog
+ * entries that can be be returned in a batch.
+ */
+ boost::optional<Date_t> _calculateSlaveDelayLatestTimestamp() {
+ auto service = cc().getServiceContext();
+ auto replCoord = ReplicationCoordinator::get(service);
+ auto slaveDelay = replCoord->getSlaveDelaySecs();
+ if (slaveDelay <= Seconds(0)) {
+ return {};
+ }
+ auto fastClockSource = service->getFastClockSource();
+ return fastClockSource->now() - slaveDelay;
+ }
+
void run() {
Client::initThread("ReplBatcher");
- const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
- OperationContext& txn = *txnPtr;
- const auto replCoord = ReplicationCoordinator::get(&txn);
- const auto fastClockSource = txn.getServiceContext()->getFastClockSource();
- const auto oplogMaxSize = fassertStatusOK(
- 40301,
- StorageInterface::get(&txn)->getOplogMaxSize(&txn, NamespaceString(rsOplogName)));
- // Batches are limited to 10% of the oplog.
BatchLimits batchLimits;
- batchLimits.bytes = std::min(oplogMaxSize / 10, size_t(replBatchLimitBytes));
+ batchLimits.bytes = _calculateBatchLimitBytes();
while (true) {
- const auto slaveDelay = replCoord->getSlaveDelaySecs();
- batchLimits.slaveDelayLatestTimestamp = (slaveDelay > Seconds(0))
- ? (fastClockSource->now() - slaveDelay)
- : boost::optional<Date_t>();
+ batchLimits.slaveDelayLatestTimestamp = _calculateSlaveDelayLatestTimestamp();
// Check this once per batch since users can change it at runtime.
batchLimits.ops = replBatchLimitOperations.load();
OpQueue ops;
// tryPopAndWaitForMore adds to ops and returns true when we need to end a batch early.
- while (!_syncTail->tryPopAndWaitForMore(&txn, &ops, batchLimits)) {
+ {
+ auto opCtx = cc().makeOperationContext();
+ while (!_syncTail->tryPopAndWaitForMore(opCtx.get(), &ops, batchLimits)) {
+ }
}
if (ops.empty() && !ops.mustShutdown()) {
@@ -755,14 +775,15 @@ private:
void SyncTail::oplogApplication(ReplicationCoordinator* replCoord) {
OpQueueBatcher batcher(this);
- const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
- OperationContext& txn = *txnPtr;
std::unique_ptr<ApplyBatchFinalizer> finalizer{
getGlobalServiceContext()->getGlobalStorageEngine()->isDurable()
? new ApplyBatchFinalizerForJournal(replCoord)
: new ApplyBatchFinalizer(replCoord)};
while (true) { // Exits on message from OpQueueBatcher.
+ const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
+ OperationContext& txn = *txnPtr;
+
// For pausing replication in tests.
while (MONGO_FAIL_POINT(rsSyncApplyStop)) {
// Tests should not trigger clean shutdown while that failpoint is active. If we
@@ -1301,6 +1322,9 @@ StatusWith<OpTime> multiApply(OperationContext* txn,
std::vector<Status> statusVector(workerPool->getNumThreads(), Status::OK());
{
+ // Each node records cumulative batch application stats for itself using this timer.
+ TimerHolder timer(&applyBatchStats);
+
// We must wait for the all work we've dispatched to complete before leaving this block
// because the spawned threads refer to objects on our stack, including writerVectors.
std::vector<MultiApplier::OperationPtrs> writerVectors(workerPool->getNumThreads());
diff --git a/src/mongo/db/repl/topology_coordinator_impl.cpp b/src/mongo/db/repl/topology_coordinator_impl.cpp
index 88337b6a500..f5302008206 100644
--- a/src/mongo/db/repl/topology_coordinator_impl.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl.cpp
@@ -36,6 +36,7 @@
#include "mongo/db/audit.h"
#include "mongo/db/client.h"
+#include "mongo/db/mongod_options.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/repl/heartbeat_response_action.h"
#include "mongo/db/repl/is_master_response.h"
@@ -1411,7 +1412,7 @@ bool TopologyCoordinatorImpl::_aMajoritySeemsToBeUp() const {
return vUp * 2 > _rsConfig.getTotalVotingMembers();
}
-bool TopologyCoordinatorImpl::_canSeeHealthyPrimaryOfEqualOrGreaterPriority(
+int TopologyCoordinatorImpl::_findHealthyPrimaryOfEqualOrGreaterPriority(
const int candidateIndex) const {
const double candidatePriority = _rsConfig.getMemberAt(candidateIndex).getPriority();
for (auto it = _hbdata.begin(); it != _hbdata.end(); ++it) {
@@ -1421,11 +1422,11 @@ bool TopologyCoordinatorImpl::_canSeeHealthyPrimaryOfEqualOrGreaterPriority(
const int itIndex = indexOfIterator(_hbdata, it);
const double priority = _rsConfig.getMemberAt(itIndex).getPriority();
if (itIndex != candidateIndex && priority >= candidatePriority) {
- return true;
+ return itIndex;
}
}
- return false;
+ return -1;
}
bool TopologyCoordinatorImpl::_isOpTimeCloseEnoughToLatestToElect(
@@ -2232,7 +2233,7 @@ MemberState TopologyCoordinatorImpl::getMemberState() const {
}
if (_rsConfig.isConfigServer()) {
- if (_options.clusterRole != ClusterRole::ConfigServer) {
+ if (_options.clusterRole != ClusterRole::ConfigServer && !skipShardingConfigurationChecks) {
return MemberState::RS_REMOVED;
} else {
invariant(_storageEngineSupportsReadCommitted != ReadCommittedSupport::kUnknown);
@@ -2241,7 +2242,7 @@ MemberState TopologyCoordinatorImpl::getMemberState() const {
}
}
} else {
- if (_options.clusterRole == ClusterRole::ConfigServer) {
+ if (_options.clusterRole == ClusterRole::ConfigServer && !skipShardingConfigurationChecks) {
return MemberState::RS_REMOVED;
}
}
@@ -2571,29 +2572,54 @@ void TopologyCoordinatorImpl::processReplSetRequestVotes(const ReplSetRequestVot
if (args.getTerm() < _term) {
response->setVoteGranted(false);
- response->setReason("candidate's term is lower than mine");
+ response->setReason(str::stream() << "candidate's term (" << args.getTerm()
+ << ") is lower than mine ("
+ << _term
+ << ")");
} else if (args.getConfigVersion() != _rsConfig.getConfigVersion()) {
response->setVoteGranted(false);
- response->setReason("candidate's config version differs from mine");
+ response->setReason(str::stream() << "candidate's config version ("
+ << args.getConfigVersion()
+ << ") differs from mine ("
+ << _rsConfig.getConfigVersion()
+ << ")");
} else if (args.getSetName() != _rsConfig.getReplSetName()) {
response->setVoteGranted(false);
- response->setReason("candidate's set name differs from mine");
+ response->setReason(str::stream() << "candidate's set name (" << args.getSetName()
+ << ") differs from mine ("
+ << _rsConfig.getReplSetName()
+ << ")");
} else if (args.getLastDurableOpTime() < lastAppliedOpTime) {
response->setVoteGranted(false);
- response->setReason("candidate's data is staler than mine");
+ response
+ ->setReason(str::stream()
+ << "candidate's data is staler than mine. candidate's last applied OpTime: "
+ << args.getLastDurableOpTime().toString()
+ << ", my last applied OpTime: "
+ << lastAppliedOpTime.toString());
} else if (!args.isADryRun() && _lastVote.getTerm() == args.getTerm()) {
response->setVoteGranted(false);
- response->setReason("already voted for another candidate this term");
- } else if (_selfConfig().isArbiter() &&
- _canSeeHealthyPrimaryOfEqualOrGreaterPriority(args.getCandidateIndex())) {
- response->setVoteGranted(false);
- response->setReason("can see a healthy primary of equal or greater priority");
+ response->setReason(str::stream()
+ << "already voted for another candidate ("
+ << _rsConfig.getMemberAt(_lastVote.getCandidateIndex()).getHostAndPort()
+ << ") this term ("
+ << _lastVote.getTerm()
+ << ")");
} else {
- if (!args.isADryRun()) {
- _lastVote.setTerm(args.getTerm());
- _lastVote.setCandidateIndex(args.getCandidateIndex());
+ int betterPrimary = _findHealthyPrimaryOfEqualOrGreaterPriority(args.getCandidateIndex());
+ if (_selfConfig().isArbiter() && betterPrimary >= 0) {
+ response->setVoteGranted(false);
+ response->setReason(str::stream()
+ << "can see a healthy primary ("
+ << _rsConfig.getMemberAt(betterPrimary).getHostAndPort()
+ << ") of equal or greater priority");
+ } else {
+ if (!args.isADryRun()) {
+ _lastVote.setTerm(args.getTerm());
+ _lastVote.setCandidateIndex(args.getCandidateIndex());
+ }
+ response->setVoteGranted(true);
}
- response->setVoteGranted(true);
}
}
diff --git a/src/mongo/db/repl/topology_coordinator_impl.h b/src/mongo/db/repl/topology_coordinator_impl.h
index d4b0476cde1..1a03338e3c4 100644
--- a/src/mongo/db/repl/topology_coordinator_impl.h
+++ b/src/mongo/db/repl/topology_coordinator_impl.h
@@ -310,9 +310,9 @@ private:
// Sees if a majority number of votes are held by members who are currently "up"
bool _aMajoritySeemsToBeUp() const;
- // Returns true if the node can see a healthy primary of equal or greater priority to the
- // candidate.
- bool _canSeeHealthyPrimaryOfEqualOrGreaterPriority(const int candidateIndex) const;
+ // Checks if the node can see a healthy primary of equal or greater priority to the
+ // candidate. If so, returns the index of that node. Otherwise returns -1.
+ int _findHealthyPrimaryOfEqualOrGreaterPriority(const int candidateIndex) const;
// Is otherOpTime close enough (within 10 seconds) to the latest known optime to qualify
// for an election
diff --git a/src/mongo/db/repl/topology_coordinator_impl_test.cpp b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
index e3708a9bf54..1f8d4b56c40 100644
--- a/src/mongo/db/repl/topology_coordinator_impl_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
@@ -5912,7 +5912,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVotesToTwoDifferentNodesInTheSameTerm) {
// different candidate same term, should be a problem
getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
- ASSERT_EQUALS("already voted for another candidate this term", response2.getReason());
+ ASSERT_EQUALS("already voted for another candidate (hself:27017) this term (1)",
+ response2.getReason());
ASSERT_FALSE(response2.getVoteGranted());
}
@@ -6028,7 +6029,8 @@ TEST_F(TopoCoordTest, VoteRequestShouldNotPreventDryRunsForThatTerm) {
ReplSetRequestVotesResponse response2;
getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
- ASSERT_EQUALS("already voted for another candidate this term", response2.getReason());
+ ASSERT_EQUALS("already voted for another candidate (hself:27017) this term (1)",
+ response2.getReason());
ASSERT_FALSE(response2.getVoteGranted());
}
@@ -6063,7 +6065,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenReplSetNameDoesNotMatch) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's set name differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's set name (wrongName) differs from mine (rs0)", response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6098,7 +6100,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigVersionDoesNotMatch) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's config version differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6145,7 +6147,8 @@ TEST_F(TopoCoordTest, ArbiterDoesNotGrantVoteWhenItCanSeeAHealthyPrimaryOfEqualO
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("can see a healthy primary of equal or greater priority", response.getReason());
+ ASSERT_EQUALS("can see a healthy primary (h2:27017) of equal or greater priority",
+ response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6184,7 +6187,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenTermIsStale) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's term is lower than mine", response.getReason());
+ ASSERT_EQUALS("candidate's term (1) is lower than mine (2)", response.getReason());
ASSERT_EQUALS(2, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6221,7 +6224,12 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenOpTimeIsStale) {
OpTime lastAppliedOpTime2 = {Timestamp(20, 0), 0};
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime2);
- ASSERT_EQUALS("candidate's data is staler than mine", response.getReason());
+ ASSERT_EQUALS(
+ str::stream() << "candidate's data is staler than mine. candidate's last applied OpTime: "
+ << OpTime().toString()
+ << ", my last applied OpTime: "
+ << OpTime(Timestamp(20, 0), 0).toString(),
+ response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6281,7 +6289,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenReplSetNameDoesNotMatch) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's set name differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's set name (wrongName) differs from mine (rs0)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6342,7 +6350,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionDoesNotMatch) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's config version differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6402,7 +6410,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenTermIsStale) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's term is lower than mine", response.getReason());
+ ASSERT_EQUALS("candidate's term (0) is lower than mine (1)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -6525,7 +6533,12 @@ TEST_F(TopoCoordTest, DoNotGrantDryRunVoteWhenOpTimeIsStale) {
OpTime lastAppliedOpTime2 = {Timestamp(20, 0), 0};
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime2);
- ASSERT_EQUALS("candidate's data is staler than mine", response.getReason());
+ ASSERT_EQUALS(
+ str::stream() << "candidate's data is staler than mine. candidate's last applied OpTime: "
+ << OpTime().toString()
+ << ", my last applied OpTime: "
+ << OpTime(Timestamp(20, 0), 0).toString(),
+ response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
diff --git a/src/mongo/db/repl/topology_coordinator_impl_v1_test.cpp b/src/mongo/db/repl/topology_coordinator_impl_v1_test.cpp
index 8062208949b..8f60269cd71 100644
--- a/src/mongo/db/repl/topology_coordinator_impl_v1_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl_v1_test.cpp
@@ -2574,7 +2574,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVotesToTwoDifferentNodesInTheSameTerm) {
// different candidate same term, should be a problem
getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
- ASSERT_EQUALS("already voted for another candidate this term", response2.getReason());
+ ASSERT_EQUALS("already voted for another candidate (hself:27017) this term (1)",
+ response2.getReason());
ASSERT_FALSE(response2.getVoteGranted());
}
@@ -2674,7 +2675,8 @@ TEST_F(TopoCoordTest, DryRunVoteRequestShouldNotPreventSubsequentDryRunsForThatT
ReplSetRequestVotesResponse response4;
getTopoCoord().processReplSetRequestVotes(args4, &response4, lastAppliedOpTime);
- ASSERT_EQUALS("already voted for another candidate this term", response4.getReason());
+ ASSERT_EQUALS("already voted for another candidate (hself:27017) this term (1)",
+ response4.getReason());
ASSERT_FALSE(response4.getVoteGranted());
}
@@ -2732,7 +2734,8 @@ TEST_F(TopoCoordTest, VoteRequestShouldNotPreventDryRunsForThatTerm) {
ReplSetRequestVotesResponse response2;
getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
- ASSERT_EQUALS("already voted for another candidate this term", response2.getReason());
+ ASSERT_EQUALS("already voted for another candidate (hself:27017) this term (1)",
+ response2.getReason());
ASSERT_FALSE(response2.getVoteGranted());
}
@@ -2767,7 +2770,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenReplSetNameDoesNotMatch) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's set name differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's set name (wrongName) differs from mine (rs0)", response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2802,7 +2805,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigVersionDoesNotMatch) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's config version differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2841,7 +2844,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenTermIsStale) {
OpTime lastAppliedOpTime;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's term is lower than mine", response.getReason());
+ ASSERT_EQUALS("candidate's term (1) is lower than mine (2)", response.getReason());
ASSERT_EQUALS(2, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2878,7 +2881,12 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenOpTimeIsStale) {
OpTime lastAppliedOpTime2 = {Timestamp(20, 0), 0};
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime2);
- ASSERT_EQUALS("candidate's data is staler than mine", response.getReason());
+ ASSERT_EQUALS(
+ str::stream() << "candidate's data is staler than mine. candidate's last applied OpTime: "
+ << OpTime().toString()
+ << ", my last applied OpTime: "
+ << OpTime(Timestamp(20, 0), 0).toString(),
+ response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2938,7 +2946,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenReplSetNameDoesNotMatch) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's set name differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's set name (wrongName) differs from mine (rs0)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2999,7 +3007,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionDoesNotMatch) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's config version differs from mine", response.getReason());
+ ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -3059,7 +3067,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenTermIsStale) {
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
- ASSERT_EQUALS("candidate's term is lower than mine", response.getReason());
+ ASSERT_EQUALS("candidate's term (0) is lower than mine (1)", response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -3182,7 +3190,12 @@ TEST_F(TopoCoordTest, DoNotGrantDryRunVoteWhenOpTimeIsStale) {
OpTime lastAppliedOpTime2 = {Timestamp(20, 0), 0};
getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime2);
- ASSERT_EQUALS("candidate's data is staler than mine", response.getReason());
+ ASSERT_EQUALS(
+ str::stream() << "candidate's data is staler than mine. candidate's last applied OpTime: "
+ << OpTime().toString()
+ << ", my last applied OpTime: "
+ << OpTime(Timestamp(20, 0), 0).toString(),
+ response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript
index b2abb86c056..b4c8b12a3d2 100644
--- a/src/mongo/db/s/SConscript
+++ b/src/mongo/db/s/SConscript
@@ -47,7 +47,6 @@ env.Library(
source=[
'active_migrations_registry.cpp',
'chunk_move_write_concern_options.cpp',
- 'collection_range_deleter.cpp',
'collection_sharding_state.cpp',
'metadata_manager.cpp',
'migration_chunk_cloner_source.cpp',
diff --git a/src/mongo/db/s/balancer/balancer.cpp b/src/mongo/db/s/balancer/balancer.cpp
index 36987fb81ec..7e334b8b86e 100644
--- a/src/mongo/db/s/balancer/balancer.cpp
+++ b/src/mongo/db/s/balancer/balancer.cpp
@@ -243,8 +243,9 @@ void Balancer::waitForBalancerToStop() {
void Balancer::joinCurrentRound(OperationContext* opCtx) {
stdx::unique_lock<stdx::mutex> scopedLock(_mutex);
const auto numRoundsAtStart = _numBalancerRounds;
- _condVar.wait(scopedLock,
- [&] { return !_inBalancerRound || _numBalancerRounds != numRoundsAtStart; });
+ opCtx->waitForConditionOrInterrupt(_condVar, scopedLock, [&] {
+ return !_inBalancerRound || _numBalancerRounds != numRoundsAtStart;
+ });
}
Status Balancer::rebalanceSingleChunk(OperationContext* opCtx, const ChunkType& chunk) {
diff --git a/src/mongo/db/s/balancer/balancer.h b/src/mongo/db/s/balancer/balancer.h
index d8847a4ee90..7f601a849e2 100644
--- a/src/mongo/db/s/balancer/balancer.h
+++ b/src/mongo/db/s/balancer/balancer.h
@@ -110,7 +110,8 @@ public:
/**
* Potentially blocking method, which will return immediately if the balancer is not running a
- * balancer round and will block until the current round completes otherwise.
+ * balancer round and will block until the current round completes otherwise. If the operation
+ * context's deadline is exceeded, it will throw an ExceededTimeLimit exception.
*/
void joinCurrentRound(OperationContext* txn);
diff --git a/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.cpp b/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.cpp
index 5d96be7004b..2f7c6feb7ec 100644
--- a/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.cpp
+++ b/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.cpp
@@ -32,7 +32,6 @@
#include "mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h"
-#include <set>
#include <vector>
#include "mongo/base/status_with.h"
@@ -59,7 +58,7 @@ namespace {
/**
* Does a linear pass over the information cached in the specified chunk manager and extracts chunk
- * distrubution and chunk placement information which is needed by the balancer policy.
+ * distribution and chunk placement information which is needed by the balancer policy.
*/
StatusWith<DistributionStatus> createCollectionDistributionStatus(
OperationContext* opCtx, const ShardStatisticsVector& allShards, ChunkManager* chunkMgr) {
@@ -258,6 +257,7 @@ StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::selectChunksToMo
}
MigrateInfoVector candidateChunks;
+ std::set<ShardId> usedShards;
for (const auto& coll : collections) {
if (coll.getDropped()) {
@@ -271,8 +271,8 @@ StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::selectChunksToMo
continue;
}
- auto candidatesStatus =
- _getMigrateCandidatesForCollection(opCtx, nss, shardStats, aggressiveBalanceHint);
+ auto candidatesStatus = _getMigrateCandidatesForCollection(
+ opCtx, nss, shardStats, aggressiveBalanceHint, &usedShards);
if (candidatesStatus == ErrorCodes::NamespaceNotFound) {
// Namespace got dropped before we managed to get to it, so just skip it
continue;
@@ -416,7 +416,8 @@ StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::_getMigrateCandi
OperationContext* opCtx,
const NamespaceString& nss,
const ShardStatisticsVector& shardStats,
- bool aggressiveBalanceHint) {
+ bool aggressiveBalanceHint,
+ std::set<ShardId>* usedShards) {
auto routingInfoStatus =
Grid::get(opCtx)->catalogCache()->getShardedCollectionRoutingInfoWithRefresh(opCtx, nss);
if (!routingInfoStatus.isOK()) {
@@ -475,7 +476,7 @@ StatusWith<MigrateInfoVector> BalancerChunkSelectionPolicyImpl::_getMigrateCandi
}
}
- return BalancerPolicy::balance(shardStats, distribution, aggressiveBalanceHint);
+ return BalancerPolicy::balance(shardStats, distribution, aggressiveBalanceHint, usedShards);
}
} // namespace mongo
diff --git a/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h b/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h
index f010d8c723b..5a1277cd5ea 100644
--- a/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h
+++ b/src/mongo/db/s/balancer/balancer_chunk_selection_policy_impl.h
@@ -67,7 +67,8 @@ private:
OperationContext* txn,
const NamespaceString& nss,
const ShardStatisticsVector& shardStats,
- bool aggressiveBalanceHint);
+ bool aggressiveBalanceHint,
+ std::set<ShardId>* usedShards);
// Source for obtaining cluster statistics. Not owned and must not be destroyed before the
// policy object is destroyed.
diff --git a/src/mongo/db/s/balancer/balancer_policy.cpp b/src/mongo/db/s/balancer/balancer_policy.cpp
index 348d8cec603..f5a0473d93e 100644
--- a/src/mongo/db/s/balancer/balancer_policy.cpp
+++ b/src/mongo/db/s/balancer/balancer_policy.cpp
@@ -1,30 +1,30 @@
/**
-* Copyright (C) 2010 10gen Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* 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
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* 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 GNU Affero General 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.
-*/
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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.
+ */
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
@@ -32,7 +32,6 @@
#include "mongo/db/s/balancer/balancer_policy.h"
-#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/s/catalog/type_shard.h"
#include "mongo/s/catalog/type_tags.h"
#include "mongo/util/log.h"
@@ -291,20 +290,17 @@ ShardId BalancerPolicy::_getMostOverloadedShard(const ShardStatisticsVector& sha
vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardStats,
const DistributionStatus& distribution,
- bool shouldAggressivelyBalance) {
+ bool shouldAggressivelyBalance,
+ std::set<ShardId>* usedShards) {
vector<MigrateInfo> migrations;
- // Set of shards, which have already been used for migrations. Used so we don't return multiple
- // migrations for the same shard.
- set<ShardId> usedShards;
-
// 1) Check for shards, which are in draining mode
{
for (const auto& stat : shardStats) {
if (!stat.isDraining)
continue;
- if (usedShards.count(stat.shardId))
+ if (usedShards->count(stat.shardId))
continue;
const vector<ChunkType>& chunks = distribution.getChunks(stat.shardId);
@@ -326,7 +322,7 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
const string tag = distribution.getTagForChunk(chunk);
const ShardId to =
- _getLeastLoadedReceiverShard(shardStats, distribution, tag, usedShards);
+ _getLeastLoadedReceiverShard(shardStats, distribution, tag, *usedShards);
if (!to.isValid()) {
if (migrations.empty()) {
warning() << "Chunk " << redact(chunk.toString())
@@ -337,8 +333,8 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
invariant(to != stat.shardId);
migrations.emplace_back(to, chunk);
- invariant(usedShards.insert(stat.shardId).second);
- invariant(usedShards.insert(to).second);
+ invariant(usedShards->insert(stat.shardId).second);
+ invariant(usedShards->insert(to).second);
break;
}
@@ -352,7 +348,7 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
// 2) Check for chunks, which are on the wrong shard and must be moved off of it
if (!distribution.tags().empty()) {
for (const auto& stat : shardStats) {
- if (usedShards.count(stat.shardId))
+ if (usedShards->count(stat.shardId))
continue;
const vector<ChunkType>& chunks = distribution.getChunks(stat.shardId);
@@ -373,7 +369,7 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
}
const ShardId to =
- _getLeastLoadedReceiverShard(shardStats, distribution, tag, usedShards);
+ _getLeastLoadedReceiverShard(shardStats, distribution, tag, *usedShards);
if (!to.isValid()) {
if (migrations.empty()) {
warning() << "Chunk " << redact(chunk.toString()) << " violates zone "
@@ -384,8 +380,8 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
invariant(to != stat.shardId);
migrations.emplace_back(to, chunk);
- invariant(usedShards.insert(stat.shardId).second);
- invariant(usedShards.insert(to).second);
+ invariant(usedShards->insert(stat.shardId).second);
+ invariant(usedShards->insert(to).second);
break;
}
}
@@ -434,7 +430,7 @@ vector<MigrateInfo> BalancerPolicy::balance(const ShardStatisticsVector& shardSt
idealNumberOfChunksPerShardForTag,
imbalanceThreshold,
&migrations,
- &usedShards))
+ usedShards))
;
}
diff --git a/src/mongo/db/s/balancer/balancer_policy.h b/src/mongo/db/s/balancer/balancer_policy.h
index 63f44c3ca68..afba1db4915 100644
--- a/src/mongo/db/s/balancer/balancer_policy.h
+++ b/src/mongo/db/s/balancer/balancer_policy.h
@@ -1,39 +1,42 @@
/**
-* Copyright (C) 2010 10gen Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* 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
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* 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 GNU Affero General 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.
-*/
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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 <set>
+#include <vector>
+
#include "mongo/base/disallow_copying.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/s/balancer/cluster_statistics.h"
#include "mongo/s/catalog/type_chunk.h"
-#include "mongo/s/client/shard.h"
+#include "mongo/s/shard_id.h"
namespace mongo {
@@ -179,10 +182,15 @@ public:
*
* The shouldAggressivelyBalance parameter causes the threshold for chunk could disparity
* between shards to be lowered.
+ *
+ * The usedShards parameter is in/out and it contains the set of shards, which have already been
+ * used for migrations. Used so we don't return multiple conflicting migrations for the same
+ * shard.
*/
static std::vector<MigrateInfo> balance(const ShardStatisticsVector& shardStats,
const DistributionStatus& distribution,
- bool shouldAggressivelyBalance);
+ bool shouldAggressivelyBalance,
+ std::set<ShardId>* usedShards);
/**
* Using the specified distribution information, returns a suggested better location for the
diff --git a/src/mongo/db/s/balancer/balancer_policy_test.cpp b/src/mongo/db/s/balancer/balancer_policy_test.cpp
index de5ceafaa83..f6abd7c8acf 100644
--- a/src/mongo/db/s/balancer/balancer_policy_test.cpp
+++ b/src/mongo/db/s/balancer/balancer_policy_test.cpp
@@ -110,14 +110,22 @@ std::pair<ShardStatisticsVector, ShardToChunksMap> generateCluster(
return std::make_pair(std::move(shardStats), std::move(chunkMap));
}
+std::vector<MigrateInfo> balanceChunks(const ShardStatisticsVector& shardStats,
+ const DistributionStatus& distribution,
+ bool shouldAggressivelyBalance) {
+ std::set<ShardId> usedShards;
+ return BalancerPolicy::balance(
+ shardStats, distribution, shouldAggressivelyBalance, &usedShards);
+}
+
TEST(BalancerPolicy, Basic) {
auto cluster = generateCluster(
{{ShardStatistics(kShardId0, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
{ShardStatistics(kShardId1, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
{ShardStatistics(kShardId2, kNoMaxSize, 3, false, emptyTagSet, emptyShardVersion), 3}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -131,8 +139,8 @@ TEST(BalancerPolicy, SmallClusterShouldBePerfectlyBalanced) {
{ShardStatistics(kShardId1, kNoMaxSize, 2, false, emptyTagSet, emptyShardVersion), 2},
{ShardStatistics(kShardId2, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId1, migrations[0].from);
ASSERT_EQ(kShardId2, migrations[0].to);
@@ -146,10 +154,8 @@ TEST(BalancerPolicy, SingleChunkShouldNotMove) {
{ShardStatistics(kShardId1, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
ASSERT(
- BalancerPolicy::balance(cluster.first, DistributionStatus(kNamespace, cluster.second), true)
- .empty());
- ASSERT(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false)
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), true).empty());
+ ASSERT(balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false)
.empty());
}
@@ -161,10 +167,8 @@ TEST(BalancerPolicy, BalanceThresholdObeyed) {
{ShardStatistics(kShardId3, kNoMaxSize, 1, false, emptyTagSet, emptyShardVersion), 1}});
ASSERT(
- BalancerPolicy::balance(cluster.first, DistributionStatus(kNamespace, cluster.second), true)
- .empty());
- ASSERT(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false)
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), true).empty());
+ ASSERT(balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false)
.empty());
}
@@ -175,8 +179,8 @@ TEST(BalancerPolicy, ParallelBalancing) {
{ShardStatistics(kShardId2, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
{ShardStatistics(kShardId3, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(2U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
@@ -199,8 +203,8 @@ TEST(BalancerPolicy, ParallelBalancingDoesNotPutChunksOnShardsAboveTheOptimal) {
{ShardStatistics(kShardId4, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
{ShardStatistics(kShardId5, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(2U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
@@ -221,8 +225,60 @@ TEST(BalancerPolicy, ParallelBalancingDoesNotMoveChunksFromShardsBelowOptimal) {
{ShardStatistics(kShardId2, kNoMaxSize, 5, false, emptyTagSet, emptyShardVersion), 5},
{ShardStatistics(kShardId3, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ ASSERT_EQ(1U, migrations.size());
+
+ ASSERT_EQ(kShardId0, migrations[0].from);
+ ASSERT_EQ(kShardId3, migrations[0].to);
+ ASSERT_BSONOBJ_EQ(cluster.second[kShardId0][0].getMin(), migrations[0].minKey);
+ ASSERT_BSONOBJ_EQ(cluster.second[kShardId0][0].getMax(), migrations[0].maxKey);
+}
+
+TEST(BalancerPolicy, ParallelBalancingNotSchedulingOnInUseSourceShardsWithMoveNecessary) {
+ auto cluster = generateCluster(
+ {{ShardStatistics(kShardId0, kNoMaxSize, 8, false, emptyTagSet, emptyShardVersion), 8},
+ {ShardStatistics(kShardId1, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
+ {ShardStatistics(kShardId2, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
+ {ShardStatistics(kShardId3, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
+
+ // Here kShardId0 would have been selected as a donor
+ std::set<ShardId> usedShards{kShardId0};
+ const auto migrations(BalancerPolicy::balance(
+ cluster.first, DistributionStatus(kNamespace, cluster.second), false, &usedShards));
+ ASSERT_EQ(1U, migrations.size());
+
+ ASSERT_EQ(kShardId1, migrations[0].from);
+ ASSERT_EQ(kShardId2, migrations[0].to);
+ ASSERT_BSONOBJ_EQ(cluster.second[kShardId1][0].getMin(), migrations[0].minKey);
+ ASSERT_BSONOBJ_EQ(cluster.second[kShardId1][0].getMax(), migrations[0].maxKey);
+}
+
+TEST(BalancerPolicy, ParallelBalancingNotSchedulingOnInUseSourceShardsWithMoveNotNecessary) {
+ auto cluster = generateCluster(
+ {{ShardStatistics(kShardId0, kNoMaxSize, 12, false, emptyTagSet, emptyShardVersion), 12},
+ {ShardStatistics(kShardId1, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
+ {ShardStatistics(kShardId2, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
+ {ShardStatistics(kShardId3, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0}});
+
+ // Here kShardId0 would have been selected as a donor
+ std::set<ShardId> usedShards{kShardId0};
+ const auto migrations(BalancerPolicy::balance(
+ cluster.first, DistributionStatus(kNamespace, cluster.second), false, &usedShards));
+ ASSERT_EQ(0U, migrations.size());
+}
+
+TEST(BalancerPolicy, ParallelBalancingNotSchedulingOnInUseDestinationShards) {
+ auto cluster = generateCluster(
+ {{ShardStatistics(kShardId0, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
+ {ShardStatistics(kShardId1, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
+ {ShardStatistics(kShardId2, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 0},
+ {ShardStatistics(kShardId3, kNoMaxSize, 1, false, emptyTagSet, emptyShardVersion), 1}});
+
+ // Here kShardId2 would have been selected as a recipient
+ std::set<ShardId> usedShards{kShardId2};
const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ cluster.first, DistributionStatus(kNamespace, cluster.second), false, &usedShards));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
@@ -241,8 +297,8 @@ TEST(BalancerPolicy, JumboChunksNotMoved) {
cluster.second[kShardId0][2].setJumbo(true);
cluster.second[kShardId0][3].setJumbo(true);
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -267,8 +323,8 @@ TEST(BalancerPolicy, JumboChunksNotMovedParallel) {
cluster.second[kShardId2][2].setJumbo(false); // Only chunk 1 is not jumbo
cluster.second[kShardId2][3].setJumbo(true);
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(2U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
@@ -288,8 +344,8 @@ TEST(BalancerPolicy, DrainingSingleChunk) {
{{ShardStatistics(kShardId0, kNoMaxSize, 2, true, emptyTagSet, emptyShardVersion), 1},
{ShardStatistics(kShardId1, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 5}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -305,8 +361,8 @@ TEST(BalancerPolicy, DrainingSingleChunkPerShard) {
{ShardStatistics(kShardId2, kNoMaxSize, 2, true, emptyTagSet, emptyShardVersion), 1},
{ShardStatistics(kShardId3, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 5}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(2U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
@@ -326,8 +382,8 @@ TEST(BalancerPolicy, DrainingWithTwoChunksFirstOneSelected) {
{{ShardStatistics(kShardId0, kNoMaxSize, 2, true, emptyTagSet, emptyShardVersion), 2},
{ShardStatistics(kShardId1, kNoMaxSize, 0, false, emptyTagSet, emptyShardVersion), 5}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -343,8 +399,8 @@ TEST(BalancerPolicy, DrainingMultipleShardsFirstOneSelected) {
{ShardStatistics(kShardId1, kNoMaxSize, 5, true, emptyTagSet, emptyShardVersion), 2},
{ShardStatistics(kShardId2, kNoMaxSize, 5, false, emptyTagSet, emptyShardVersion), 16}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId2, migrations[0].to);
@@ -359,8 +415,8 @@ TEST(BalancerPolicy, DrainingMultipleShardsWontAcceptChunks) {
{ShardStatistics(kShardId1, kNoMaxSize, 0, true, emptyTagSet, emptyShardVersion), 0},
{ShardStatistics(kShardId2, kNoMaxSize, 0, true, emptyTagSet, emptyShardVersion), 0}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT(migrations.empty());
}
@@ -374,7 +430,7 @@ TEST(BalancerPolicy, DrainingSingleAppropriateShardFoundDueToTag) {
ASSERT_OK(distribution.addRangeToZone(ZoneRange(
cluster.second[kShardId2][0].getMin(), cluster.second[kShardId2][0].getMax(), "LAX")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId2, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -392,7 +448,7 @@ TEST(BalancerPolicy, DrainingNoAppropriateShardsFoundDueToTag) {
ASSERT_OK(distribution.addRangeToZone(ZoneRange(
cluster.second[kShardId2][0].getMin(), cluster.second[kShardId2][0].getMax(), "SEA")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT(migrations.empty());
}
@@ -403,8 +459,8 @@ TEST(BalancerPolicy, NoBalancingDueToAllNodesEitherDrainingOrMaxedOut) {
{ShardStatistics(kShardId1, 1, 1, false, emptyTagSet, emptyShardVersion), 6},
{ShardStatistics(kShardId2, kNoMaxSize, 1, true, emptyTagSet, emptyShardVersion), 1}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT(migrations.empty());
}
@@ -417,8 +473,8 @@ TEST(BalancerPolicy, BalancerRespectsMaxShardSizeOnlyBalanceToNonMaxed) {
{ShardStatistics(kShardId1, kNoMaxSize, 5, false, emptyTagSet, emptyShardVersion), 5},
{ShardStatistics(kShardId2, kNoMaxSize, 10, false, emptyTagSet, emptyShardVersion), 10}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId2, migrations[0].from);
ASSERT_EQ(kShardId1, migrations[0].to);
@@ -435,8 +491,8 @@ TEST(BalancerPolicy, BalancerRespectsMaxShardSizeWhenAllBalanced) {
{ShardStatistics(kShardId1, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4},
{ShardStatistics(kShardId2, kNoMaxSize, 4, false, emptyTagSet, emptyShardVersion), 4}});
- const auto migrations(BalancerPolicy::balance(
- cluster.first, DistributionStatus(kNamespace, cluster.second), false));
+ const auto migrations(
+ balanceChunks(cluster.first, DistributionStatus(kNamespace, cluster.second), false));
ASSERT(migrations.empty());
}
@@ -451,7 +507,7 @@ TEST(BalancerPolicy, BalancerRespectsTagsWhenDraining) {
ASSERT_OK(distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 7), "a")));
ASSERT_OK(distribution.addRangeToZone(ZoneRange(BSON("x" << 8), kMaxBSONKey, "b")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId1, migrations[0].from);
ASSERT_EQ(kShardId0, migrations[0].to);
@@ -470,7 +526,7 @@ TEST(BalancerPolicy, BalancerRespectsTagPolicyBeforeImbalance) {
DistributionStatus distribution(kNamespace, cluster.second);
ASSERT_OK(distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 100), "a")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId2, migrations[0].from);
ASSERT_EQ(kShardId0, migrations[0].to);
@@ -490,7 +546,7 @@ TEST(BalancerPolicy, BalancerFixesIncorrectTagsWithCrossShardViolationOfTags) {
ASSERT_OK(distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 1), "b")));
ASSERT_OK(distribution.addRangeToZone(ZoneRange(BSON("x" << 8), kMaxBSONKey, "a")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId0, migrations[0].from);
ASSERT_EQ(kShardId2, migrations[0].to);
@@ -508,7 +564,7 @@ TEST(BalancerPolicy, BalancerFixesIncorrectTagsInOtherwiseBalancedCluster) {
DistributionStatus distribution(kNamespace, cluster.second);
ASSERT_OK(distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 10), "a")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(1U, migrations.size());
ASSERT_EQ(kShardId2, migrations[0].from);
ASSERT_EQ(kShardId0, migrations[0].to);
@@ -527,7 +583,7 @@ TEST(BalancerPolicy, BalancerFixesIncorrectTagsInOtherwiseBalancedClusterParalle
DistributionStatus distribution(kNamespace, cluster.second);
ASSERT_OK(distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 20), "a")));
- const auto migrations(BalancerPolicy::balance(cluster.first, distribution, false));
+ const auto migrations(balanceChunks(cluster.first, distribution, false));
ASSERT_EQ(2U, migrations.size());
ASSERT_EQ(kShardId2, migrations[0].from);
@@ -550,7 +606,7 @@ TEST(BalancerPolicy, BalancerHandlesNoShardsWithTag) {
ASSERT_OK(
distribution.addRangeToZone(ZoneRange(kMinBSONKey, BSON("x" << 7), "NonExistentZone")));
- ASSERT(BalancerPolicy::balance(cluster.first, distribution, false).empty());
+ ASSERT(balanceChunks(cluster.first, distribution, false).empty());
}
TEST(DistributionStatus, AddTagRangeOverlap) {
diff --git a/src/mongo/db/s/collection_range_deleter.cpp b/src/mongo/db/s/collection_range_deleter.cpp
deleted file mode 100644
index 803541d16f9..00000000000
--- a/src/mongo/db/s/collection_range_deleter.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-/**
- * Copyright (C) 2016 MongoDB Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * 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 GNU Affero General 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.
- */
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/s/collection_range_deleter.h"
-
-#include <algorithm>
-
-#include "mongo/db/catalog/collection.h"
-#include "mongo/db/client.h"
-#include "mongo/db/db_raii.h"
-#include "mongo/db/dbhelpers.h"
-#include "mongo/db/exec/working_set_common.h"
-#include "mongo/db/index/index_descriptor.h"
-#include "mongo/db/keypattern.h"
-#include "mongo/db/query/internal_plans.h"
-#include "mongo/db/query/query_knobs.h"
-#include "mongo/db/query/query_planner.h"
-#include "mongo/db/repl/repl_client_info.h"
-#include "mongo/db/repl/replication_coordinator_global.h"
-#include "mongo/db/s/collection_sharding_state.h"
-#include "mongo/db/s/sharding_state.h"
-#include "mongo/db/write_concern.h"
-#include "mongo/executor/task_executor.h"
-#include "mongo/util/log.h"
-#include "mongo/util/mongoutils/str.h"
-#include "mongo/util/scopeguard.h"
-
-namespace mongo {
-
-class ChunkRange;
-class OldClientWriteContext;
-
-using CallbackArgs = executor::TaskExecutor::CallbackArgs;
-using logger::LogComponent;
-
-namespace {
-
-const WriteConcernOptions kMajorityWriteConcern(WriteConcernOptions::kMajority,
- WriteConcernOptions::SyncMode::UNSET,
- Seconds(60));
-
-} // unnamed namespace
-
-CollectionRangeDeleter::CollectionRangeDeleter(NamespaceString nss) : _nss(std::move(nss)) {}
-
-void CollectionRangeDeleter::run() {
- Client::initThread(getThreadName());
- ON_BLOCK_EXIT([&] { Client::destroy(); });
- auto txn = cc().makeOperationContext().get();
- bool hasNextRangeToClean = cleanupNextRange(txn);
-
- // If there are more ranges to run, we add <this> back onto the task executor to run again.
- if (hasNextRangeToClean) {
- auto executor = ShardingState::get(txn)->getRangeDeleterTaskExecutor();
- executor->scheduleWork([this](const CallbackArgs& cbArgs) { run(); });
- } else {
- delete this;
- }
-}
-
-bool CollectionRangeDeleter::cleanupNextRange(OperationContext* txn) {
- int numDocumentsDeleted;
-
- {
- AutoGetCollection autoColl(txn, _nss, MODE_IX);
- Collection* collection = autoColl.getCollection();
- if (!collection) {
- return false;
- }
-
- CollectionShardingState* shardingState = CollectionShardingState::get(txn, _nss);
- MetadataManager& metadataManager = *shardingState->_metadataManager;
-
- if (!_rangeInProgress && !metadataManager.hasRangesToClean()) {
- // Nothing left to do
- return false;
- }
-
- if (!_rangeInProgress || !metadataManager.isInRangesToClean(_rangeInProgress.get())) {
- // No valid chunk in progress, get a new one
- _rangeInProgress = metadataManager.getNextRangeToClean();
- }
-
- auto metadata = shardingState->getMetadata();
- if (!metadata) {
- return false;
- }
-
- numDocumentsDeleted = _doDeletion(txn, collection, metadata->getKeyPattern());
- if (numDocumentsDeleted <= 0) {
- metadataManager.removeRangeToClean(_rangeInProgress.get());
- _rangeInProgress = boost::none;
- return true;
- }
- }
-
- // wait for replication
- WriteConcernResult wcResult;
- auto currentClientOpTime = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
- Status status = waitForWriteConcern(txn, currentClientOpTime, kMajorityWriteConcern, &wcResult);
- if (!status.isOK()) {
- warning() << "Error when waiting for write concern after removing chunks in " << _nss
- << " : " << status.reason();
- }
-
- return true;
-}
-
-int CollectionRangeDeleter::_doDeletion(OperationContext* txn,
- Collection* collection,
- const BSONObj& keyPattern) {
- invariant(_rangeInProgress);
- invariant(collection);
-
- // The IndexChunk has a keyPattern that may apply to more than one index - we need to
- // select the index and get the full index keyPattern here.
- const IndexDescriptor* idx =
- collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn, keyPattern, false);
- if (idx == NULL) {
- warning() << "Unable to find shard key index for " << keyPattern.toString() << " in "
- << _nss;
- return -1;
- }
-
- KeyPattern indexKeyPattern(idx->keyPattern().getOwned());
-
- // Extend bounds to match the index we found
- const BSONObj& min =
- Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(_rangeInProgress->getMin(), false));
- const BSONObj& max =
- Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(_rangeInProgress->getMax(), false));
-
- LOG(1) << "begin removal of " << min << " to " << max << " in " << _nss;
-
- auto indexName = idx->indexName();
- IndexDescriptor* desc = collection->getIndexCatalog()->findIndexByName(txn, indexName);
- if (!desc) {
- warning() << "shard key index with name " << indexName << " on '" << _nss
- << "' was dropped";
- return -1;
- }
-
- std::unique_ptr<PlanExecutor> exec(
- InternalPlanner::indexScan(txn,
- collection,
- desc,
- min,
- max,
- BoundInclusion::kIncludeStartKeyOnly,
- PlanExecutor::YIELD_MANUAL,
- InternalPlanner::FORWARD,
- InternalPlanner::IXSCAN_FETCH));
- int numDeleted = 0;
- const int maxItersBeforeYield = std::max(static_cast<int>(internalQueryExecYieldIterations), 1);
-
- while (numDeleted < maxItersBeforeYield) {
- RecordId rloc;
- BSONObj obj;
- PlanExecutor::ExecState state;
- state = exec->getNext(&obj, &rloc);
- if (PlanExecutor::IS_EOF == state) {
- break;
- }
-
- if (PlanExecutor::FAILURE == state || PlanExecutor::DEAD == state) {
- warning(LogComponent::kSharding)
- << PlanExecutor::statestr(state) << " - cursor error while trying to delete " << min
- << " to " << max << " in " << _nss << ": " << WorkingSetCommon::toStatusString(obj)
- << ", stats: " << Explain::getWinningPlanStats(exec.get());
- break;
- }
-
- invariant(PlanExecutor::ADVANCED == state);
-
- WriteUnitOfWork wuow(txn);
-
- NamespaceString nss(_nss);
- if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nss)) {
- warning() << "stepped down from primary while deleting chunk; "
- << "orphaning data in " << _nss << " in range [" << min << ", " << max << ")";
- return numDeleted;
- }
-
- OpDebug* const nullOpDebug = nullptr;
- collection->deleteDocument(txn, rloc, nullOpDebug, true);
- wuow.commit();
- numDeleted++;
- }
-
- return numDeleted;
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/s/collection_sharding_state.h b/src/mongo/db/s/collection_sharding_state.h
index 2a01bec5567..1abababcc1b 100644
--- a/src/mongo/db/s/collection_sharding_state.h
+++ b/src/mongo/db/s/collection_sharding_state.h
@@ -168,8 +168,6 @@ public:
void onDropCollection(OperationContext* txn, const NamespaceString& collectionName);
private:
- friend class CollectionRangeDeleter;
-
/**
* Checks whether the shard version of the operation matches that of the collection.
*
diff --git a/src/mongo/db/s/metadata_manager.cpp b/src/mongo/db/s/metadata_manager.cpp
index 28ca20e2f6f..0bba1ff478e 100644
--- a/src/mongo/db/s/metadata_manager.cpp
+++ b/src/mongo/db/s/metadata_manager.cpp
@@ -34,7 +34,6 @@
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/range_arithmetic.h"
-#include "mongo/db/s/collection_range_deleter.h"
#include "mongo/db/s/sharding_state.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/log.h"
@@ -47,9 +46,8 @@ MetadataManager::MetadataManager(ServiceContext* sc, NamespaceString nss)
: _nss(std::move(nss)),
_serviceContext(sc),
_activeMetadataTracker(stdx::make_unique<CollectionMetadataTracker>(nullptr)),
- _receivingChunks(SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<CachedChunkInfo>()),
- _rangesToClean(
- SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<RangeToCleanDescriptor>()) {}
+ _receivingChunks(
+ SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<CachedChunkInfo>()) {}
MetadataManager::~MetadataManager() {
stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
@@ -74,7 +72,6 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata>
// collection sharding information regardless of whether the node is sharded or not.
if (!remoteMetadata && !_activeMetadataTracker->metadata) {
invariant(_receivingChunks.empty());
- invariant(_rangesToClean.empty());
return;
}
@@ -84,8 +81,6 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata>
<< _activeMetadataTracker->metadata->toStringBasic() << " as no longer sharded";
_receivingChunks.clear();
- _rangesToClean.clear();
-
_setActiveMetadata_inlock(nullptr);
return;
}
@@ -100,8 +95,6 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata>
<< remoteMetadata->toStringBasic();
invariant(_receivingChunks.empty());
- invariant(_rangesToClean.empty());
-
_setActiveMetadata_inlock(std::move(remoteMetadata));
return;
}
@@ -115,8 +108,6 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata>
<< remoteMetadata->toStringBasic() << " due to epoch change";
_receivingChunks.clear();
- _rangesToClean.clear();
-
_setActiveMetadata_inlock(std::move(remoteMetadata));
return;
}
@@ -168,9 +159,6 @@ void MetadataManager::refreshActiveMetadata(std::unique_ptr<CollectionMetadata>
const ChunkRange receivingRange(itRecv->first, itRecv->second.getMaxKey());
_receivingChunks.erase(itRecv);
-
- // Make sure any potentially partially copied chunks are scheduled to be cleaned up
- _addRangeToClean_inlock(receivingRange);
}
// Need to reset the iterator
@@ -207,14 +195,10 @@ void MetadataManager::beginReceive(const ChunkRange& range) {
const ChunkRange receivingRange(itRecv->first, itRecv->second.getMaxKey());
_receivingChunks.erase(itRecv);
-
- // Make sure any potentially partially copied chunks are scheduled to be cleaned up
- _addRangeToClean_inlock(receivingRange);
}
// Need to ensure that the background range deleter task won't delete the range we are about to
// receive
- _removeRangeToClean_inlock(range, Status::OK());
_receivingChunks.insert(
std::make_pair(range.getMin().getOwned(),
CachedChunkInfo(range.getMax().getOwned(), ChunkVersion::IGNORED())));
@@ -241,9 +225,6 @@ void MetadataManager::forgetReceive(const ChunkRange& range) {
_receivingChunks.erase(it);
}
- // This is potentially a partially received data, which needs to be cleaned up
- _addRangeToClean_inlock(range);
-
// For compatibility with the current range deleter, update the pending chunks on the collection
// metadata to exclude the chunk being received, which was added in beginReceive
ChunkType chunk;
@@ -343,91 +324,9 @@ ScopedCollectionMetadata::operator bool() const {
return _tracker && _tracker->metadata.get();
}
-RangeMap MetadataManager::getCopyOfRangesToClean() {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- return _getCopyOfRangesToClean_inlock();
-}
-
-RangeMap MetadataManager::_getCopyOfRangesToClean_inlock() {
- RangeMap ranges = SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<CachedChunkInfo>();
- for (auto it = _rangesToClean.begin(); it != _rangesToClean.end(); ++it) {
- ranges.insert(std::make_pair(
- it->first, CachedChunkInfo(it->second.getMax(), ChunkVersion::IGNORED())));
- }
- return ranges;
-}
-
-std::shared_ptr<Notification<Status>> MetadataManager::addRangeToClean(const ChunkRange& range) {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- return _addRangeToClean_inlock(range);
-}
-
-std::shared_ptr<Notification<Status>> MetadataManager::_addRangeToClean_inlock(
- const ChunkRange& range) {
- // This first invariant currently makes an unnecessary copy, to reuse the
- // rangeMapOverlaps helper function.
- invariant(!rangeMapOverlaps(_getCopyOfRangesToClean_inlock(), range.getMin(), range.getMax()));
- invariant(!rangeMapOverlaps(_receivingChunks, range.getMin(), range.getMax()));
-
- RangeToCleanDescriptor descriptor(range.getMax().getOwned());
- _rangesToClean.insert(std::make_pair(range.getMin().getOwned(), descriptor));
-
- // If _rangesToClean was previously empty, we need to start the collection range deleter
- if (_rangesToClean.size() == 1UL) {
- ShardingState::get(_serviceContext)->scheduleCleanup(_nss);
- }
-
- return descriptor.getNotification();
-}
-
-void MetadataManager::removeRangeToClean(const ChunkRange& range, Status deletionStatus) {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- _removeRangeToClean_inlock(range, deletionStatus);
-}
-
-void MetadataManager::_removeRangeToClean_inlock(const ChunkRange& range, Status deletionStatus) {
- auto it = _rangesToClean.upper_bound(range.getMin());
- // We want our iterator to point at the greatest value
- // that is still less than or equal to range.
- if (it != _rangesToClean.begin()) {
- --it;
- }
-
- for (; it != _rangesToClean.end() &&
- SimpleBSONObjComparator::kInstance.evaluate(it->first < range.getMax());) {
- if (SimpleBSONObjComparator::kInstance.evaluate(it->second.getMax() <= range.getMin())) {
- ++it;
- continue;
- }
-
- // There's overlap between *it and range so we remove *it
- // and then replace with new ranges.
- BSONObj oldMin = it->first;
- BSONObj oldMax = it->second.getMax();
- it->second.complete(deletionStatus);
- _rangesToClean.erase(it++);
- if (SimpleBSONObjComparator::kInstance.evaluate(oldMin < range.getMin())) {
- _addRangeToClean_inlock(ChunkRange(oldMin, range.getMin()));
- }
-
- if (SimpleBSONObjComparator::kInstance.evaluate(oldMax > range.getMax())) {
- _addRangeToClean_inlock(ChunkRange(range.getMax(), oldMax));
- }
- }
-}
-
void MetadataManager::append(BSONObjBuilder* builder) {
stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- BSONArrayBuilder rtcArr(builder->subarrayStart("rangesToClean"));
- for (const auto& entry : _rangesToClean) {
- BSONObjBuilder obj;
- ChunkRange r = ChunkRange(entry.first, entry.second.getMax());
- r.append(&obj);
- rtcArr.append(obj.done());
- }
- rtcArr.done();
-
BSONArrayBuilder pcArr(builder->subarrayStart("pendingChunks"));
for (const auto& entry : _receivingChunks) {
BSONObjBuilder obj;
@@ -447,23 +346,4 @@ void MetadataManager::append(BSONObjBuilder* builder) {
amrArr.done();
}
-bool MetadataManager::hasRangesToClean() {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- return !_rangesToClean.empty();
-}
-
-bool MetadataManager::isInRangesToClean(const ChunkRange& range) {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- // For convenience, this line makes an unnecessary copy, to reuse the
- // rangeMapContains helper function.
- return rangeMapContains(_getCopyOfRangesToClean_inlock(), range.getMin(), range.getMax());
-}
-
-ChunkRange MetadataManager::getNextRangeToClean() {
- stdx::lock_guard<stdx::mutex> scopedLock(_managerLock);
- invariant(!_rangesToClean.empty());
- auto it = _rangesToClean.begin();
- return ChunkRange(it->first, it->second.getMax());
-}
-
} // namespace mongo
diff --git a/src/mongo/db/s/metadata_manager.h b/src/mongo/db/s/metadata_manager.h
index fa61e9ef2ea..b2e712d7f9f 100644
--- a/src/mongo/db/s/metadata_manager.h
+++ b/src/mongo/db/s/metadata_manager.h
@@ -85,52 +85,10 @@ public:
RangeMap getCopyOfReceivingChunks();
/**
- * Adds a new range to be cleaned up.
- * The newly introduced range must not overlap with the existing ranges.
- */
- std::shared_ptr<Notification<Status>> addRangeToClean(const ChunkRange& range);
-
- /**
- * Calls removeRangeToClean with Status::OK.
- */
- void removeRangeToClean(const ChunkRange& range) {
- removeRangeToClean(range, Status::OK());
- }
-
- /**
- * Removes the specified range from the ranges to be cleaned up.
- * The specified deletionStatus will be returned to callers waiting
- * on whether the deletion succeeded or failed.
- */
- void removeRangeToClean(const ChunkRange& range, Status deletionStatus);
-
- /**
- * Gets copy of the set of chunk ranges which are scheduled for cleanup.
- * Converts RangeToCleanMap to RangeMap.
- */
- RangeMap getCopyOfRangesToClean();
-
- /**
* Appends information on all the chunk ranges in rangesToClean to builder.
*/
void append(BSONObjBuilder* builder);
- /**
- * Returns true if _rangesToClean is not empty.
- */
- bool hasRangesToClean();
-
- /**
- * Returns true if the exact range is in _rangesToClean.
- */
- bool isInRangesToClean(const ChunkRange& range);
-
- /**
- * Gets and returns, but does not remove, a single ChunkRange from _rangesToClean.
- * Should not be called if _rangesToClean is empty: it will hit an invariant.
- */
- ChunkRange getNextRangeToClean();
-
private:
friend class ScopedCollectionMetadata;
@@ -193,12 +151,6 @@ private:
*/
void _removeMetadata_inlock(CollectionMetadataTracker* metadataTracker);
- std::shared_ptr<Notification<Status>> _addRangeToClean_inlock(const ChunkRange& range);
-
- void _removeRangeToClean_inlock(const ChunkRange& range, Status deletionStatus);
-
- RangeMap _getCopyOfRangesToClean_inlock();
-
void _setActiveMetadata_inlock(std::unique_ptr<CollectionMetadata> newMetadata);
const NamespaceString _nss;
@@ -219,10 +171,6 @@ private:
// Chunk ranges which are currently assumed to be transferred to the shard. Indexed by the min
// key of the range.
RangeMap _receivingChunks;
-
- // Set of ranges to be deleted. Indexed by the min key of the range.
- typedef BSONObjIndexedMap<RangeToCleanDescriptor> RangeToCleanMap;
- RangeToCleanMap _rangesToClean;
};
class ScopedCollectionMetadata {
diff --git a/src/mongo/db/s/metadata_manager_test.cpp b/src/mongo/db/s/metadata_manager_test.cpp
index 19f4fd405ab..207eb4fd37b 100644
--- a/src/mongo/db/s/metadata_manager_test.cpp
+++ b/src/mongo/db/s/metadata_manager_test.cpp
@@ -50,12 +50,6 @@ using unittest::assertGet;
class MetadataManagerTest : public ServiceContextMongoDTest {
protected:
- void setUp() override {
- ServiceContextMongoDTest::setUp();
- ShardingState::get(getServiceContext())
- ->setScheduleCleanupFunctionForTest([](const NamespaceString& nss) {});
- }
-
static std::unique_ptr<CollectionMetadata> makeEmptyMetadata() {
const OID epoch = OID::gen();
@@ -106,8 +100,7 @@ TEST_F(MetadataManagerTest, SetAndGetActiveMetadata) {
ScopedCollectionMetadata scopedMetadata = manager.getActiveMetadata(manager_ptr);
ASSERT_EQ(cmPtr, scopedMetadata.getMetadata());
-};
-
+}
TEST_F(MetadataManagerTest, ResetActiveMetadata) {
manager.refreshActiveMetadata(makeEmptyMetadata());
@@ -124,167 +117,8 @@ TEST_F(MetadataManagerTest, ResetActiveMetadata) {
ScopedCollectionMetadata scopedMetadata2 = manager.getActiveMetadata(manager_ptr);
ASSERT_EQ(cm2Ptr, scopedMetadata2.getMetadata());
-};
-
-TEST_F(MetadataManagerTest, AddAndRemoveRangesToClean) {
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
- ChunkRange cr2 = ChunkRange(BSON("key" << 10), BSON("key" << 20));
-
- manager.addRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- manager.removeRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-
- manager.addRangeToClean(cr1);
- manager.addRangeToClean(cr2);
- manager.removeRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- auto ranges = manager.getCopyOfRangesToClean();
- auto it = ranges.find(cr2.getMin());
- ChunkRange remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- ASSERT_EQ(remainingChunk.toString(), cr2.toString());
- manager.removeRangeToClean(cr2);
-}
-
-// Tests that a removal in the middle of an existing ChunkRange results in
-// two correct chunk ranges.
-TEST_F(MetadataManagerTest, RemoveRangeInMiddleOfRange) {
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
-
- manager.addRangeToClean(cr1);
- manager.removeRangeToClean(ChunkRange(BSON("key" << 4), BSON("key" << 6)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 2UL);
-
- auto ranges = manager.getCopyOfRangesToClean();
- auto it = ranges.find(BSON("key" << 0));
- ChunkRange expectedChunk = ChunkRange(BSON("key" << 0), BSON("key" << 4));
- ChunkRange remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- it++;
- expectedChunk = ChunkRange(BSON("key" << 6), BSON("key" << 10));
- remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- manager.removeRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-}
-
-// Tests removals that overlap with just one ChunkRange.
-TEST_F(MetadataManagerTest, RemoveRangeWithSingleRangeOverlap) {
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
-
- manager.addRangeToClean(cr1);
- manager.removeRangeToClean(ChunkRange(BSON("key" << 0), BSON("key" << 5)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- auto ranges = manager.getCopyOfRangesToClean();
- auto it = ranges.find(BSON("key" << 5));
- ChunkRange remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- ChunkRange expectedChunk = ChunkRange(BSON("key" << 5), BSON("key" << 10));
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- manager.removeRangeToClean(ChunkRange(BSON("key" << 4), BSON("key" << 6)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- ranges = manager.getCopyOfRangesToClean();
- it = ranges.find(BSON("key" << 6));
- remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- expectedChunk = ChunkRange(BSON("key" << 6), BSON("key" << 10));
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- manager.removeRangeToClean(ChunkRange(BSON("key" << 9), BSON("key" << 13)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- ranges = manager.getCopyOfRangesToClean();
- it = ranges.find(BSON("key" << 6));
- remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- expectedChunk = ChunkRange(BSON("key" << 6), BSON("key" << 9));
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- manager.removeRangeToClean(ChunkRange(BSON("key" << 0), BSON("key" << 10)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-}
-
-// Tests removals that overlap with more than one ChunkRange.
-TEST_F(MetadataManagerTest, RemoveRangeWithMultipleRangeOverlaps) {
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
- ChunkRange cr2 = ChunkRange(BSON("key" << 10), BSON("key" << 20));
- ChunkRange cr3 = ChunkRange(BSON("key" << 20), BSON("key" << 30));
-
- manager.addRangeToClean(cr1);
- manager.addRangeToClean(cr2);
- manager.addRangeToClean(cr3);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 3UL);
-
- manager.removeRangeToClean(ChunkRange(BSON("key" << 8), BSON("key" << 22)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 2UL);
- auto ranges = manager.getCopyOfRangesToClean();
- auto it = ranges.find(BSON("key" << 0));
- ChunkRange remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- ChunkRange expectedChunk = ChunkRange(BSON("key" << 0), BSON("key" << 8));
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
- it++;
- remainingChunk = ChunkRange(it->first, it->second.getMaxKey());
- expectedChunk = ChunkRange(BSON("key" << 22), BSON("key" << 30));
- ASSERT_EQ(remainingChunk.toString(), expectedChunk.toString());
-
- manager.removeRangeToClean(ChunkRange(BSON("key" << 0), BSON("key" << 30)));
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-}
-
-TEST_F(MetadataManagerTest, AddAndRemoveRangeNotificationsBlockAndYield) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ChunkRange cr1(BSON("key" << 0), BSON("key" << 10));
- auto notification = manager.addRangeToClean(cr1);
- manager.removeRangeToClean(cr1, Status::OK());
- ASSERT_OK(notification->get());
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-}
-
-TEST_F(MetadataManagerTest, RemoveRangeToCleanCorrectlySetsBadStatus) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ChunkRange cr1(BSON("key" << 0), BSON("key" << 10));
- auto notification = manager.addRangeToClean(cr1);
- manager.removeRangeToClean(cr1, Status(ErrorCodes::InternalError, "test error"));
- ASSERT_NOT_OK(notification->get());
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-}
-
-TEST_F(MetadataManagerTest, RemovingSubrangeStillSetsNotificationStatus) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ChunkRange cr1(BSON("key" << 0), BSON("key" << 10));
- auto notification = manager.addRangeToClean(cr1);
- manager.removeRangeToClean(ChunkRange(BSON("key" << 3), BSON("key" << 7)));
- ASSERT_OK(notification->get());
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 2UL);
- manager.removeRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
-
- notification = manager.addRangeToClean(cr1);
- manager.removeRangeToClean(ChunkRange(BSON("key" << 7), BSON("key" << 15)));
- ASSERT_OK(notification->get());
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 1UL);
- manager.removeRangeToClean(cr1);
- ASSERT_EQ(manager.getCopyOfRangesToClean().size(), 0UL);
}
-TEST_F(MetadataManagerTest, NotificationBlocksUntilDeletion) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ChunkRange cr1(BSON("key" << 0), BSON("key" << 10));
- auto notification = manager.addRangeToClean(cr1);
- auto txn = cc().makeOperationContext().get();
- // Once the new range deleter is set up, this might fail if the range deleter
- // deleted cr1 before we got here...
- ASSERT_FALSE(notification->waitFor(txn, Milliseconds(0)));
-
- manager.removeRangeToClean(cr1);
- ASSERT_TRUE(notification->waitFor(txn, Milliseconds(0)));
- ASSERT_OK(notification->get());
-}
-
-
TEST_F(MetadataManagerTest, RefreshAfterSuccessfulMigrationSinglePending) {
manager.refreshActiveMetadata(makeEmptyMetadata());
@@ -417,29 +251,5 @@ TEST_F(MetadataManagerTest, RefreshMetadataAfterDropAndRecreate) {
ASSERT_EQ(newVersion, chunkEntry->second.getVersion());
}
-// Tests membership functions for _rangesToClean
-TEST_F(MetadataManagerTest, RangesToCleanMembership) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ASSERT(!manager.hasRangesToClean());
-
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
- manager.addRangeToClean(cr1);
-
- ASSERT(manager.hasRangesToClean());
- ASSERT(manager.isInRangesToClean(cr1));
-}
-
-// Tests that getNextRangeToClean successfully pulls a stored ChunkRange
-TEST_F(MetadataManagerTest, GetNextRangeToClean) {
- manager.refreshActiveMetadata(makeEmptyMetadata());
-
- ChunkRange cr1 = ChunkRange(BSON("key" << 0), BSON("key" << 10));
- manager.addRangeToClean(cr1);
-
- ChunkRange cr2 = manager.getNextRangeToClean();
- ASSERT_EQ(cr1.toString(), cr2.toString());
-}
-
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/s/migration_chunk_cloner_source.h b/src/mongo/db/s/migration_chunk_cloner_source.h
index 04cf9e36df2..cb3b10a8ef8 100644
--- a/src/mongo/db/s/migration_chunk_cloner_source.h
+++ b/src/mongo/db/s/migration_chunk_cloner_source.h
@@ -88,9 +88,13 @@ public:
* This must only be called once and no more methods on the cloner must be used afterwards
* regardless of whether it succeeds or not.
*
+ * Returns statistics about the move. These are informational only and should not be
+ * interpreted by the caller for any means other than reporting.
+ *
* NOTE: Must be called without any locks.
*/
- virtual Status commitClone(OperationContext* txn) = 0;
+
+ virtual StatusWith<BSONObj> commitClone(OperationContext* opCtx) = 0;
/**
* Tells the recipient to abort the clone and cleanup any unused data. This method's
diff --git a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp
index a11d74ef587..ea83fa83d0d 100644
--- a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp
+++ b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp
@@ -75,9 +75,11 @@ bool isInRange(const BSONObj& obj,
BSONObj createRequestWithSessionId(StringData commandName,
const NamespaceString& nss,
- const MigrationSessionId& sessionId) {
+ const MigrationSessionId& sessionId,
+ bool waitForSteadyOrDone = false) {
BSONObjBuilder builder;
builder.append(commandName, nss.ns());
+ builder.append("waitForSteadyOrDone", waitForSteadyOrDone);
sessionId.append(&builder);
return builder.obj();
}
@@ -231,13 +233,8 @@ Status MigrationChunkClonerSourceLegacy::awaitUntilCriticalSectionIsAppropriate(
int iteration = 0;
while ((Date_t::now() - startTime) < maxTimeToWait) {
- // Exponential sleep backoff, up to 1024ms. Don't sleep much on the first few iterations,
- // since we want empty chunk migrations to be fast.
- sleepmillis(1LL << std::min(iteration, 10));
- iteration++;
-
auto responseStatus = _callRecipient(
- createRequestWithSessionId(kRecvChunkStatus, _args.getNss(), _sessionId));
+ createRequestWithSessionId(kRecvChunkStatus, _args.getNss(), _sessionId, true));
if (!responseStatus.isOK()) {
return {responseStatus.getStatus().code(),
str::stream()
@@ -247,6 +244,11 @@ Status MigrationChunkClonerSourceLegacy::awaitUntilCriticalSectionIsAppropriate(
const BSONObj& res = responseStatus.getValue();
+ if (!res["waited"].boolean()) {
+ sleepmillis(1LL << std::min(iteration, 10));
+ }
+ iteration++;
+
stdx::lock_guard<stdx::mutex> sl(_mutex);
const std::size_t cloneLocsRemaining = _cloneLocs.size();
@@ -306,7 +308,7 @@ Status MigrationChunkClonerSourceLegacy::awaitUntilCriticalSectionIsAppropriate(
return {ErrorCodes::ExceededTimeLimit, "Timed out waiting for the cloner to catch up"};
}
-Status MigrationChunkClonerSourceLegacy::commitClone(OperationContext* txn) {
+StatusWith<BSONObj> MigrationChunkClonerSourceLegacy::commitClone(OperationContext* txn) {
invariant(_state == kCloning);
invariant(!txn->lockState()->isLocked());
@@ -314,7 +316,7 @@ Status MigrationChunkClonerSourceLegacy::commitClone(OperationContext* txn) {
_callRecipient(createRequestWithSessionId(kRecvChunkCommit, _args.getNss(), _sessionId));
if (responseStatus.isOK()) {
_cleanup(txn);
- return Status::OK();
+ return responseStatus;
}
cancelClone(txn);
@@ -576,8 +578,7 @@ Status MigrationChunkClonerSourceLegacy::_storeCurrentLocs(OperationContext* txn
if (totalRecs > 0) {
avgRecSize = collection->dataSize(txn) / totalRecs;
maxRecsWhenFull = _args.getMaxChunkSizeBytes() / avgRecSize;
- maxRecsWhenFull = std::min((unsigned long long)(kMaxObjectPerChunk + 1),
- 130 * maxRecsWhenFull / 100 /* slack */);
+ maxRecsWhenFull = 130 * maxRecsWhenFull / 100; // pad some slack
} else {
avgRecSize = 0;
maxRecsWhenFull = kMaxObjectPerChunk + 1;
diff --git a/src/mongo/db/s/migration_chunk_cloner_source_legacy.h b/src/mongo/db/s/migration_chunk_cloner_source_legacy.h
index c683df2be29..860282a16d0 100644
--- a/src/mongo/db/s/migration_chunk_cloner_source_legacy.h
+++ b/src/mongo/db/s/migration_chunk_cloner_source_legacy.h
@@ -66,7 +66,7 @@ public:
Status awaitUntilCriticalSectionIsAppropriate(OperationContext* txn,
Milliseconds maxTimeToWait) override;
- Status commitClone(OperationContext* txn) override;
+ StatusWith<BSONObj> commitClone(OperationContext* txn) override;
void cancelClone(OperationContext* txn) override;
diff --git a/src/mongo/db/s/migration_destination_manager.cpp b/src/mongo/db/s/migration_destination_manager.cpp
index 2e748cf3ca9..1c02208ee27 100644
--- a/src/mongo/db/s/migration_destination_manager.cpp
+++ b/src/mongo/db/s/migration_destination_manager.cpp
@@ -148,10 +148,14 @@ bool opReplicatedEnough(OperationContext* txn,
const repl::OpTime& lastOpApplied,
const WriteConcernOptions& writeConcern) {
WriteConcernResult writeConcernResult;
+ writeConcernResult.wTimedOut = false;
- Status waitForMajorityWriteConcernStatus =
+ Status majorityStatus =
waitForWriteConcern(txn, lastOpApplied, kMajorityWriteConcern, &writeConcernResult);
- if (!waitForMajorityWriteConcernStatus.isOK()) {
+ if (!majorityStatus.isOK()) {
+ if (!writeConcernResult.wTimedOut) {
+ uassertStatusOK(majorityStatus);
+ }
return false;
}
@@ -159,13 +163,16 @@ bool opReplicatedEnough(OperationContext* txn,
// write concerns in case the user's write concern is stronger than majority
WriteConcernOptions userWriteConcern(writeConcern);
userWriteConcern.wTimeout = -1;
+ writeConcernResult.wTimedOut = false;
- Status waitForUserWriteConcernStatus =
+ Status userStatus =
waitForWriteConcern(txn, lastOpApplied, userWriteConcern, &writeConcernResult);
- if (!waitForUserWriteConcernStatus.isOK()) {
+ if (!userStatus.isOK()) {
+ if (!writeConcernResult.wTimedOut) {
+ uassertStatusOK(userStatus);
+ }
return false;
}
-
return true;
}
@@ -220,6 +227,7 @@ MigrationDestinationManager::State MigrationDestinationManager::getState() const
void MigrationDestinationManager::setState(State newState) {
stdx::lock_guard<stdx::mutex> sl(_mutex);
_state = newState;
+ _stateChangedCV.notify_all();
}
bool MigrationDestinationManager::isActive() const {
@@ -231,7 +239,21 @@ bool MigrationDestinationManager::_isActive_inlock() const {
return _sessionId.is_initialized();
}
-void MigrationDestinationManager::report(BSONObjBuilder& b) {
+void MigrationDestinationManager::report(BSONObjBuilder& b,
+ OperationContext* opCtx,
+ bool waitForSteadyOrDone) {
+ if (waitForSteadyOrDone) {
+ stdx::unique_lock<stdx::mutex> lock(_mutex);
+ try {
+ opCtx->waitForConditionOrInterruptFor(_stateChangedCV, lock, Seconds(1), [&]() -> bool {
+ return _state != READY && _state != CLONE && _state != CATCHUP;
+ });
+ } catch (...) {
+ // Ignoring this error because this is an optional parameter and we catch timeout
+ // exceptions later.
+ }
+ b.append("waited", true);
+ }
stdx::lock_guard<stdx::mutex> sl(_mutex);
b.appendBool("active", _sessionId.is_initialized());
@@ -286,6 +308,7 @@ Status MigrationDestinationManager::start(const NamespaceString& nss,
invariant(!_scopedRegisterReceiveChunk);
_state = READY;
+ _stateChangedCV.notify_all();
_errmsg = "";
_nss = nss;
@@ -336,6 +359,7 @@ bool MigrationDestinationManager::abort(const MigrationSessionId& sessionId) {
}
_state = ABORT;
+ _stateChangedCV.notify_all();
_errmsg = "aborted";
return true;
@@ -344,6 +368,7 @@ bool MigrationDestinationManager::abort(const MigrationSessionId& sessionId) {
void MigrationDestinationManager::abortWithoutSessionIdCheck() {
stdx::lock_guard<stdx::mutex> sl(_mutex);
_state = ABORT;
+ _stateChangedCV.notify_all();
_errmsg = "aborted without session id check";
}
@@ -368,6 +393,7 @@ bool MigrationDestinationManager::startCommit(const MigrationSessionId& sessionI
}
_state = COMMIT_START;
+ _stateChangedCV.notify_all();
const auto deadline = Date_t::now() + Seconds(30);
@@ -375,6 +401,8 @@ bool MigrationDestinationManager::startCommit(const MigrationSessionId& sessionI
if (stdx::cv_status::timeout ==
_isActiveCV.wait_until(lock, deadline.toSystemTimePoint())) {
_state = FAIL;
+ _stateChangedCV.notify_all();
+
log() << "startCommit never finished!" << migrateLog;
return false;
}
diff --git a/src/mongo/db/s/migration_destination_manager.h b/src/mongo/db/s/migration_destination_manager.h
index 0b16202d55e..4bd269f37fd 100644
--- a/src/mongo/db/s/migration_destination_manager.h
+++ b/src/mongo/db/s/migration_destination_manager.h
@@ -77,7 +77,7 @@ public:
/**
* Reports the state of the migration manager as a BSON document.
*/
- void report(BSONObjBuilder& b);
+ void report(BSONObjBuilder& b, OperationContext* opCtx, bool waitForSteadyOrDone);
/**
* Returns a report on the active migration, if the migration is active. Otherwise return an
@@ -222,6 +222,9 @@ private:
State _state{READY};
std::string _errmsg;
+
+ // Condition variable, which is signalled every time the state of the migration changes.
+ stdx::condition_variable _stateChangedCV;
};
} // namespace mongo
diff --git a/src/mongo/db/s/migration_destination_manager_legacy_commands.cpp b/src/mongo/db/s/migration_destination_manager_legacy_commands.cpp
index a20db670259..ac9752fd96d 100644
--- a/src/mongo/db/s/migration_destination_manager_legacy_commands.cpp
+++ b/src/mongo/db/s/migration_destination_manager_legacy_commands.cpp
@@ -220,7 +220,9 @@ public:
int,
string& errmsg,
BSONObjBuilder& result) {
- ShardingState::get(txn)->migrationDestinationManager()->report(result);
+ bool waitForSteadyOrDone = cmdObj["waitForSteadyOrDone"].boolean();
+ ShardingState::get(txn)->migrationDestinationManager()->report(
+ result, txn, waitForSteadyOrDone);
return true;
}
@@ -261,12 +263,13 @@ public:
int,
string& errmsg,
BSONObjBuilder& result) {
+
const MigrationSessionId migrationSessionid(
uassertStatusOK(MigrationSessionId::extractFromBSON(cmdObj)));
const bool ok =
ShardingState::get(txn)->migrationDestinationManager()->startCommit(migrationSessionid);
- ShardingState::get(txn)->migrationDestinationManager()->report(result);
+ ShardingState::get(txn)->migrationDestinationManager()->report(result, txn, false);
return ok;
}
@@ -313,11 +316,11 @@ public:
if (migrationSessionIdStatus.isOK()) {
const bool ok = mdm->abort(migrationSessionIdStatus.getValue());
- mdm->report(result);
+ mdm->report(result, txn, false);
return ok;
} else if (migrationSessionIdStatus == ErrorCodes::NoSuchKey) {
mdm->abortWithoutSessionIdCheck();
- mdm->report(result);
+ mdm->report(result, txn, false);
return true;
}
diff --git a/src/mongo/db/s/migration_source_manager.cpp b/src/mongo/db/s/migration_source_manager.cpp
index 0ad841ea56f..b70617deee3 100644
--- a/src/mongo/db/s/migration_source_manager.cpp
+++ b/src/mongo/db/s/migration_source_manager.cpp
@@ -268,18 +268,18 @@ Status MigrationSourceManager::commitChunkOnRecipient(OperationContext* txn) {
auto scopedGuard = MakeGuard([&] { cleanupOnError(txn); });
// Tell the recipient shard to fetch the latest changes.
- Status commitCloneStatus = _cloneDriver->commitClone(txn);
+ auto commitCloneStatus = _cloneDriver->commitClone(txn);
if (MONGO_FAIL_POINT(failMigrationCommit) && commitCloneStatus.isOK()) {
commitCloneStatus = {ErrorCodes::InternalError,
"Failing _recvChunkCommit due to failpoint."};
}
-
if (!commitCloneStatus.isOK()) {
- return {commitCloneStatus.code(),
- str::stream() << "commit clone failed due to " << commitCloneStatus.toString()};
+ return commitCloneStatus.getStatus();
}
+ _recipientCloneCounts = commitCloneStatus.getValue()["counts"].Obj().getOwned();
+
_state = kCloneCompleted;
scopedGuard.Dismiss();
return Status::OK();
@@ -332,9 +332,13 @@ Status MigrationSourceManager::commitChunkMetadataOnConfig(OperationContext* txn
ErrorCodes::InternalError, "Failpoint 'migrationCommitNetworkError' generated error");
}
- const Status migrationCommitStatus =
- (commitChunkMigrationResponse.isOK() ? commitChunkMigrationResponse.getValue().commandStatus
- : commitChunkMigrationResponse.getStatus());
+ Status migrationCommitStatus = commitChunkMigrationResponse.getStatus();
+ if (migrationCommitStatus.isOK()) {
+ migrationCommitStatus = commitChunkMigrationResponse.getValue().commandStatus;
+ if (migrationCommitStatus.isOK()) {
+ migrationCommitStatus = commitChunkMigrationResponse.getValue().writeConcernStatus;
+ }
+ }
if (!migrationCommitStatus.isOK()) {
// Need to get the latest optime in case the refresh request goes to a secondary --
@@ -431,7 +435,9 @@ Status MigrationSourceManager::commitChunkMetadataOnConfig(OperationContext* txn
<< "from"
<< _args.getFromShardId()
<< "to"
- << _args.getToShardId()),
+ << _args.getToShardId()
+ << "counts"
+ << _recipientCloneCounts),
ShardingCatalogClient::kMajorityWriteConcern);
return Status::OK();
diff --git a/src/mongo/db/s/migration_source_manager.h b/src/mongo/db/s/migration_source_manager.h
index cb5ce4be792..27d4480a665 100644
--- a/src/mongo/db/s/migration_source_manager.h
+++ b/src/mongo/db/s/migration_source_manager.h
@@ -233,6 +233,9 @@ private:
// callers don't have to hold collection lock in order to wait on it. Available after the
// critical section stage has completed.
std::shared_ptr<Notification<void>> _critSecSignal;
+
+ // The statistics about a chunk migration to be included in moveChunk.commit
+ BSONObj _recipientCloneCounts;
};
} // namespace mongo
diff --git a/src/mongo/db/s/sharding_state.cpp b/src/mongo/db/s/sharding_state.cpp
index e264c82b57f..02c75e287fe 100644
--- a/src/mongo/db/s/sharding_state.cpp
+++ b/src/mongo/db/s/sharding_state.cpp
@@ -117,8 +117,7 @@ const std::set<std::string> ShardingState::_commandsThatInitializeShardingAwaren
ShardingState::ShardingState()
: _initializationState(static_cast<uint32_t>(InitializationState::kNew)),
_initializationStatus(Status(ErrorCodes::InternalError, "Uninitialized value")),
- _globalInit(&initializeGlobalShardingStateForMongod),
- _scheduleWorkFn([](NamespaceString nss) {}) {}
+ _globalInit(&initializeGlobalShardingStateForMongod) {}
ShardingState::~ShardingState() = default;
@@ -222,14 +221,6 @@ void ShardingState::setGlobalInitMethodForTest(GlobalInitFunc func) {
_globalInit = func;
}
-void ShardingState::setScheduleCleanupFunctionForTest(RangeDeleterCleanupNotificationFunc fn) {
- _scheduleWorkFn = fn;
-}
-
-void ShardingState::scheduleCleanup(const NamespaceString& nss) {
- _scheduleWorkFn(nss);
-}
-
Status ShardingState::onStaleShardVersion(OperationContext* txn,
const NamespaceString& nss,
const ChunkVersion& expectedVersion) {
@@ -416,8 +407,6 @@ Status ShardingState::initializeFromShardIdentity(OperationContext* txn,
_shardName = shardIdentity.getShardName();
_clusterId = shardIdentity.getClusterId();
- _initializeRangeDeleterTaskExecutor();
-
return status;
} catch (const DBException& ex) {
auto errorStatus = ex.toStatus();
@@ -446,8 +435,6 @@ void ShardingState::_initializeImpl(ConnectionString configSvr, string shardName
&ShardRegistry::replicaSetChangeShardRegistryUpdateHook);
ReplicaSetMonitor::setAsynchronousConfigChangeHook(&updateShardIdentityConfigStringCB);
- _initializeRangeDeleterTaskExecutor();
-
_shardName = shardName;
}
@@ -626,47 +613,76 @@ ChunkVersion ShardingState::_refreshMetadata(OperationContext* txn, const Namesp
<< " before shard name has been set",
shardId.isValid());
- auto newCollectionMetadata = [&]() -> std::unique_ptr<CollectionMetadata> {
- auto const catalogCache = Grid::get(txn)->catalogCache();
- catalogCache->invalidateShardedCollection(nss);
+ auto const catalogCache = Grid::get(txn)->catalogCache();
+ catalogCache->invalidateShardedCollection(nss);
- const auto routingInfo = uassertStatusOK(catalogCache->getCollectionRoutingInfo(txn, nss));
- const auto cm = routingInfo.cm();
- if (!cm) {
- return nullptr;
- }
+ auto routingInfo = uassertStatusOK(catalogCache->getCollectionRoutingInfo(txn, nss));
+ const auto cm = routingInfo.cm();
- RangeMap shardChunksMap =
- SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<CachedChunkInfo>();
+ if (!cm) {
+ // No chunk manager, so unsharded.
- for (const auto& chunkMapEntry : cm->chunkMap()) {
- const auto& chunk = chunkMapEntry.second;
+ // Exclusive collection lock needed since we're now changing the metadata
+ ScopedTransaction transaction(txn, MODE_IX);
+ AutoGetCollection autoColl(txn, nss, MODE_IX, MODE_X);
- if (chunk->getShardId() != shardId)
- continue;
+ auto css = CollectionShardingState::get(txn, nss);
+ css->refreshMetadata(txn, nullptr);
- shardChunksMap.emplace_hint(shardChunksMap.end(),
- chunk->getMin(),
- CachedChunkInfo(chunk->getMax(), chunk->getLastmod()));
- }
+ return ChunkVersion::UNSHARDED();
+ }
- return stdx::make_unique<CollectionMetadata>(cm->getShardKeyPattern().toBSON(),
- cm->getVersion(),
- cm->getVersion(shardId),
- std::move(shardChunksMap));
- }();
+ {
+ AutoGetCollection autoColl(txn, nss, MODE_IS);
+ auto css = CollectionShardingState::get(txn, nss);
+
+ // We already have newer version
+ if (css->getMetadata() &&
+ css->getMetadata()->getCollVersion().epoch() == cm->getVersion().epoch() &&
+ css->getMetadata()->getCollVersion() >= cm->getVersion()) {
+ LOG(1) << "Skipping refresh of metadata for " << nss << " "
+ << css->getMetadata()->getCollVersion() << " with an older " << cm->getVersion();
+ return css->getMetadata()->getShardVersion();
+ }
+ }
// Exclusive collection lock needed since we're now changing the metadata
ScopedTransaction transaction(txn, MODE_IX);
AutoGetCollection autoColl(txn, nss, MODE_IX, MODE_X);
auto css = CollectionShardingState::get(txn, nss);
- css->refreshMetadata(txn, std::move(newCollectionMetadata));
- if (!css->getMetadata()) {
- return ChunkVersion::UNSHARDED();
+ // We already have newer version
+ if (css->getMetadata() &&
+ css->getMetadata()->getCollVersion().epoch() == cm->getVersion().epoch() &&
+ css->getMetadata()->getCollVersion() >= cm->getVersion()) {
+ LOG(1) << "Skipping refresh of metadata for " << nss << " "
+ << css->getMetadata()->getCollVersion() << " with an older " << cm->getVersion();
+ return css->getMetadata()->getShardVersion();
+ }
+
+ RangeMap shardChunksMap =
+ SimpleBSONObjComparator::kInstance.makeBSONObjIndexedMap<CachedChunkInfo>();
+
+ for (const auto& chunkMapEntry : cm->chunkMap()) {
+ const auto& chunk = chunkMapEntry.second;
+
+ if (chunk->getShardId() != shardId)
+ continue;
+
+ shardChunksMap.emplace_hint(shardChunksMap.end(),
+ chunk->getMin(),
+ CachedChunkInfo(chunk->getMax(), chunk->getLastmod()));
}
+ std::unique_ptr<CollectionMetadata> newCollectionMetadata =
+ stdx::make_unique<CollectionMetadata>(cm->getShardKeyPattern().toBSON(),
+ cm->getVersion(),
+ cm->getVersion(shardId),
+ std::move(shardChunksMap));
+
+ css->refreshMetadata(txn, std::move(newCollectionMetadata));
+
return css->getMetadata()->getShardVersion();
}
@@ -756,19 +772,6 @@ Status ShardingState::updateShardIdentityConfigString(OperationContext* txn,
return Status::OK();
}
-void ShardingState::_initializeRangeDeleterTaskExecutor() {
- invariant(!_rangeDeleterTaskExecutor);
- auto net =
- executor::makeNetworkInterface("NetworkInterfaceCollectionRangeDeleter-TaskExecutor");
- auto netPtr = net.get();
- _rangeDeleterTaskExecutor = stdx::make_unique<executor::ThreadPoolTaskExecutor>(
- stdx::make_unique<executor::NetworkInterfaceThreadPool>(netPtr), std::move(net));
-}
-
-executor::ThreadPoolTaskExecutor* ShardingState::getRangeDeleterTaskExecutor() {
- return _rangeDeleterTaskExecutor.get();
-}
-
/**
* Global free function.
*/
diff --git a/src/mongo/db/s/sharding_state.h b/src/mongo/db/s/sharding_state.h
index e53fee63515..f9e5a507446 100644
--- a/src/mongo/db/s/sharding_state.h
+++ b/src/mongo/db/s/sharding_state.h
@@ -35,7 +35,6 @@
#include "mongo/bson/oid.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/s/active_migrations_registry.h"
-#include "mongo/db/s/collection_range_deleter.h"
#include "mongo/db/s/migration_destination_manager.h"
#include "mongo/executor/task_executor.h"
#include "mongo/executor/thread_pool_task_executor.h"
@@ -236,24 +235,6 @@ public:
void setGlobalInitMethodForTest(GlobalInitFunc func);
/**
- * Schedules for the range to clean of the given namespace to be deleted.
- * Behavior can be modified through setScheduleCleanupFunctionForTest.
- */
- void scheduleCleanup(const NamespaceString& nss);
-
- /**
- * Returns a pointer to the collection range deleter task executor.
- */
- executor::ThreadPoolTaskExecutor* getRangeDeleterTaskExecutor();
-
- /**
- * Sets the function used by scheduleWorkOnRangeDeleterTaskExecutor to
- * schedule work. Used for mocking the executor for testing. See the ShardingState
- * for the default implementation of _scheduleWorkFn.
- */
- void setScheduleCleanupFunctionForTest(RangeDeleterCleanupNotificationFunc fn);
-
- /**
* If started with --shardsvr, initializes sharding awareness from the shardIdentity document
* on disk, if there is one.
* If started with --shardsvr in queryableBackupMode, initializes sharding awareness from the
@@ -348,9 +329,6 @@ private:
*/
ChunkVersion _refreshMetadata(OperationContext* opCtx, const NamespaceString& nss);
- // Initializes a TaskExecutor for cleaning up orphaned ranges
- void _initializeRangeDeleterTaskExecutor();
-
// Manages the state of the migration recipient shard
MigrationDestinationManager _migrationDestManager;
@@ -386,13 +364,6 @@ private:
// Function for initializing the external sharding state components not owned here.
GlobalInitFunc _globalInit;
-
- // Function for scheduling work on the _rangeDeleterTaskExecutor.
- // Used in call to scheduleCleanup(NamespaceString).
- RangeDeleterCleanupNotificationFunc _scheduleWorkFn;
-
- // Task executor for the collection range deleter.
- std::unique_ptr<executor::ThreadPoolTaskExecutor> _rangeDeleterTaskExecutor;
};
} // namespace mongo
diff --git a/src/mongo/db/sorter/SConscript b/src/mongo/db/sorter/SConscript
index 959f6cdb919..1ceb05088af 100644
--- a/src/mongo/db/sorter/SConscript
+++ b/src/mongo/db/sorter/SConscript
@@ -5,6 +5,6 @@ sorterEnv.InjectThirdPartyIncludePaths(libraries=['snappy'])
sorterEnv.CppUnitTest('sorter_test',
'sorter_test.cpp',
LIBDEPS=['$BUILD_DIR/mongo/db/service_context',
- '$BUILD_DIR/mongo/db/storage/wiredtiger/storage_wiredtiger_customization_hooks',
+ '$BUILD_DIR/mongo/db/storage/encryption_hooks',
'$BUILD_DIR/mongo/db/storage/storage_options',
'$BUILD_DIR/third_party/shim_snappy'])
diff --git a/src/mongo/db/sorter/sorter.cpp b/src/mongo/db/sorter/sorter.cpp
index 7f6e8866562..0165e0adccc 100644
--- a/src/mongo/db/sorter/sorter.cpp
+++ b/src/mongo/db/sorter/sorter.cpp
@@ -55,8 +55,8 @@
#include "mongo/config.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/service_context.h"
+#include "mongo/db/storage/encryption_hooks.h"
#include "mongo/db/storage/storage_options.h"
-#include "mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/s/mongos_options.h"
#include "mongo/util/assert_util.h"
@@ -226,15 +226,16 @@ private:
read(_buffer.get(), blockSize);
massert(16816, "file too short?", !_done);
- auto hooks = WiredTigerCustomizationHooks::get(getGlobalServiceContext());
- if (hooks->enabled()) {
+ auto encryptionHooks = EncryptionHooks::get(getGlobalServiceContext());
+ if (encryptionHooks->enabled()) {
std::unique_ptr<char[]> out(new char[blockSize]);
size_t outLen;
- Status status = hooks->unprotectTmpData(reinterpret_cast<uint8_t*>(_buffer.get()),
- blockSize,
- reinterpret_cast<uint8_t*>(out.get()),
- blockSize,
- &outLen);
+ Status status =
+ encryptionHooks->unprotectTmpData(reinterpret_cast<uint8_t*>(_buffer.get()),
+ blockSize,
+ reinterpret_cast<uint8_t*>(out.get()),
+ blockSize,
+ &outLen);
massert(28841,
str::stream() << "Failed to unprotect data: " << status.toString(),
status.isOK());
@@ -884,16 +885,16 @@ void SortedFileWriter<Key, Value>::spill() {
}
std::unique_ptr<char[]> out;
- auto hooks = WiredTigerCustomizationHooks::get(getGlobalServiceContext());
- if (hooks->enabled()) {
- size_t protectedSizeMax = size + hooks->additionalBytesForProtectedBuffer();
+ auto encryptionHooks = EncryptionHooks::get(getGlobalServiceContext());
+ if (encryptionHooks->enabled()) {
+ size_t protectedSizeMax = size + encryptionHooks->additionalBytesForProtectedBuffer();
out.reset(new char[protectedSizeMax]);
size_t resultLen;
- Status status = hooks->protectTmpData(reinterpret_cast<const uint8_t*>(outBuffer),
- size,
- reinterpret_cast<uint8_t*>(out.get()),
- protectedSizeMax,
- &resultLen);
+ Status status = encryptionHooks->protectTmpData(reinterpret_cast<const uint8_t*>(outBuffer),
+ size,
+ reinterpret_cast<uint8_t*>(out.get()),
+ protectedSizeMax,
+ &resultLen);
massert(28842,
str::stream() << "Failed to compress data: " << status.toString(),
status.isOK());
diff --git a/src/mongo/db/stats/range_deleter_server_status.cpp b/src/mongo/db/stats/range_deleter_server_status.cpp
index 817ffa444e4..e37143342f0 100644
--- a/src/mongo/db/stats/range_deleter_server_status.cpp
+++ b/src/mongo/db/stats/range_deleter_server_status.cpp
@@ -84,6 +84,11 @@ public:
entryBuilder.append("deleteStart", (*it)->deleteStartTS);
entryBuilder.append("deleteEnd", (*it)->deleteEndTS);
+ const auto waitForReplDurationMs =
+ durationCount<Milliseconds>((*it)->waitForReplDurationMs);
+ if (waitForReplDurationMs > 0) {
+ entryBuilder.append("waitForReplDurationMs", waitForReplDurationMs);
+ }
if ((*it)->waitForReplEndTS > Date_t()) {
entryBuilder.append("waitForReplStart", (*it)->waitForReplStartTS);
entryBuilder.append("waitForReplEnd", (*it)->waitForReplEndTS);
diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript
index d65046ed6fa..e887882df87 100644
--- a/src/mongo/db/storage/SConscript
+++ b/src/mongo/db/storage/SConscript
@@ -11,7 +11,6 @@ env.SConscript(
],
)
-
env.Library(
target='journal_listener',
source=[
@@ -41,7 +40,6 @@ env.Library(
],
)
-
env.Library(
target='bson_collection_catalog_entry',
source=[
@@ -75,6 +73,19 @@ env.Library(
)
env.Library(
+ target='encryption_hooks',
+ source= [
+ 'encryption_hooks.cpp',
+ ],
+ LIBDEPS= ['$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/service_context'],
+ PROGDEPS_DEPENDENTS=[
+ '$BUILD_DIR/mongo/mongod',
+ '$BUILD_DIR/mongo/mongos',
+ ],
+ )
+
+env.Library(
target='storage_options',
source=[
'storage_options.cpp',
diff --git a/src/mongo/db/storage/encryption_hooks.cpp b/src/mongo/db/storage/encryption_hooks.cpp
new file mode 100644
index 00000000000..9ee9a317dbe
--- /dev/null
+++ b/src/mongo/db/storage/encryption_hooks.cpp
@@ -0,0 +1,96 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/platform/basic.h"
+
+#include "mongo/db/storage/encryption_hooks.h"
+
+#include <boost/filesystem/path.hpp>
+
+#include "mongo/base/init.h"
+#include "mongo/db/service_context.h"
+#include "mongo/db/storage/data_protector.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+/* Make a EncryptionHooks pointer a decoration on the global ServiceContext */
+MONGO_INITIALIZER_WITH_PREREQUISITES(SetEncryptionHooks, ("SetGlobalEnvironment"))
+(InitializerContext* context) {
+ auto encryptionHooks = stdx::make_unique<EncryptionHooks>();
+ EncryptionHooks::set(getGlobalServiceContext(), std::move(encryptionHooks));
+
+ return Status::OK();
+}
+
+namespace {
+const auto getEncryptionHooks =
+ ServiceContext::declareDecoration<std::unique_ptr<EncryptionHooks>>();
+} // namespace
+
+void EncryptionHooks::set(ServiceContext* service, std::unique_ptr<EncryptionHooks> custHooks) {
+ auto& hooks = getEncryptionHooks(service);
+ invariant(custHooks);
+ hooks = std::move(custHooks);
+}
+
+EncryptionHooks* EncryptionHooks::get(ServiceContext* service) {
+ return getEncryptionHooks(service).get();
+}
+
+EncryptionHooks::~EncryptionHooks() {}
+
+bool EncryptionHooks::enabled() const {
+ return false;
+}
+
+bool EncryptionHooks::restartRequired() {
+ return false;
+}
+
+std::unique_ptr<DataProtector> EncryptionHooks::getDataProtector() {
+ return std::unique_ptr<DataProtector>();
+}
+
+boost::filesystem::path EncryptionHooks::getProtectedPathSuffix() {
+ return "";
+}
+
+Status EncryptionHooks::protectTmpData(
+ const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) {
+ return Status(ErrorCodes::InternalError,
+ "Encryption hooks must be enabled to use preprocessTmpData.");
+}
+
+Status EncryptionHooks::unprotectTmpData(
+ const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) {
+ return Status(ErrorCodes::InternalError,
+ "Encryption hooks must be enabled to use postprocessTmpData.");
+}
+} // namespace mongo
diff --git a/src/mongo/db/storage/encryption_hooks.h b/src/mongo/db/storage/encryption_hooks.h
new file mode 100644
index 00000000000..e1c9d553a10
--- /dev/null
+++ b/src/mongo/db/storage/encryption_hooks.h
@@ -0,0 +1,99 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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 <memory>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/db/jsobj.h"
+
+namespace boost {
+namespace filesystem {
+class path;
+} // namespace filesystem
+} // namespace boost
+
+namespace mongo {
+class DataProtector;
+class ServiceContext;
+
+class EncryptionHooks {
+public:
+ static void set(ServiceContext* service, std::unique_ptr<EncryptionHooks> custHooks);
+
+ static EncryptionHooks* get(ServiceContext* service);
+
+ virtual ~EncryptionHooks();
+
+ /**
+ * Returns true if the encryption hooks are enabled.
+ */
+ virtual bool enabled() const;
+
+ /**
+ * Perform any encryption engine initialization/sanity checking that needs to happen after
+ * storage engine initialization but before the server starts accepting incoming connections.
+ *
+ * Returns true if the server needs to be rebooted because of configuration changes.
+ */
+ virtual bool restartRequired();
+
+ /**
+ * Returns the maximum size addition when doing transforming temp data.
+ */
+ size_t additionalBytesForProtectedBuffer() {
+ return 33;
+ }
+
+ /**
+ * Get the data protector object
+ */
+ virtual std::unique_ptr<DataProtector> getDataProtector();
+
+ /**
+ * Get an implementation specific path suffix to tag files with
+ */
+ virtual boost::filesystem::path getProtectedPathSuffix();
+
+ /**
+ * Transform temp data to non-readable form before writing it to disk.
+ */
+ virtual Status protectTmpData(
+ const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen);
+
+ /**
+ * Tranforms temp data back to readable form, after reading from disk.
+ */
+ virtual Status unprotectTmpData(
+ const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen);
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
index a537a9c63c6..8c816d39f31 100644
--- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
+++ b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
@@ -556,8 +556,15 @@ void EphemeralForTestRecordStore::temp_cappedTruncateAfter(OperationContext* txn
Records::iterator it =
inclusive ? _data->records.lower_bound(end) : _data->records.upper_bound(end);
while (it != _data->records.end()) {
- txn->recoveryUnit()->registerChange(new RemoveChange(txn, _data, it->first, it->second));
- _data->dataSize -= it->second.size;
+ RecordId id = it->first;
+ EphemeralForTestRecord record = it->second;
+
+ if (_cappedCallback) {
+ uassertStatusOK(_cappedCallback->aboutToDeleteCapped(txn, id, record.toRecordData()));
+ }
+
+ txn->recoveryUnit()->registerChange(new RemoveChange(txn, _data, id, record));
+ _data->dataSize -= record.size;
_data->records.erase(it++);
}
}
diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp
index 624c87a2404..fc580ca111d 100644
--- a/src/mongo/db/storage/key_string.cpp
+++ b/src/mongo/db/storage/key_string.cpp
@@ -1366,7 +1366,8 @@ void toBsonValue(uint8_t ctype,
Decimal128 dec(Decimal128::Value{lowbits, highbits});
if (isNegative)
dec = dec.negate();
- dec = adjustDecimalExponent(typeBits, dec);
+ if (dec.isFinite())
+ dec = adjustDecimalExponent(typeBits, dec);
*stream << dec;
break;
}
@@ -1385,7 +1386,8 @@ void toBsonValue(uint8_t ctype,
Decimal128 dec(bin, Decimal128::kRoundTo34Digits, roundAwayFromZero);
if (hasDecimalContinuation)
dec = readDecimalContinuation(reader, inverted, dec);
- dec = adjustDecimalExponent(typeBits, dec);
+ if (dec.isFinite())
+ dec = adjustDecimalExponent(typeBits, dec);
*stream << dec;
}
break;
diff --git a/src/mongo/db/storage/key_string_test.cpp b/src/mongo/db/storage/key_string_test.cpp
index b29d1b01da8..46a11c2592a 100644
--- a/src/mongo/db/storage/key_string_test.cpp
+++ b/src/mongo/db/storage/key_string_test.cpp
@@ -596,6 +596,15 @@ const std::vector<BSONObj>& getInterestingElements(KeyString::Version version) {
// Something that needs multiple bytes of typeBits
elements.push_back(BSON("" << BSON_ARRAY("" << BSONSymbol("") << 0 << 0ll << 0.0 << -0.0)));
+ if (version != KeyString::Version::V0) {
+ // Something with exceptional typeBits for Decimal
+ elements.push_back(
+ BSON("" << BSON_ARRAY("" << BSONSymbol("") << Decimal128::kNegativeInfinity
+ << Decimal128::kPositiveInfinity
+ << Decimal128::kPositiveNaN
+ << Decimal128("0.0000000")
+ << Decimal128("-0E1000"))));
+ }
//
// Interesting numeric cases
@@ -605,6 +614,11 @@ const std::vector<BSONObj>& getInterestingElements(KeyString::Version version) {
elements.push_back(BSON("" << 0ll));
elements.push_back(BSON("" << 0.0));
elements.push_back(BSON("" << -0.0));
+ if (version != KeyString::Version::V0) {
+ Decimal128("0.0.0000000");
+ Decimal128("-0E1000");
+ }
+
elements.push_back(BSON("" << std::numeric_limits<double>::quiet_NaN()));
elements.push_back(BSON("" << std::numeric_limits<double>::infinity()));
elements.push_back(BSON("" << -std::numeric_limits<double>::infinity()));
@@ -740,6 +754,11 @@ const std::vector<BSONObj>& getInterestingElements(KeyString::Version version) {
elements.push_back(BSON("" << Decimal128("4.940656458412465441765687928682214E-324")));
elements.push_back(BSON("" << Decimal128("-4.940656458412465441765687928682214E-324")));
elements.push_back(BSON("" << Decimal128("-4.940656458412465441765687928682213E-324")));
+
+ // Non-finite values. Note: can't roundtrip negative NaNs, so not testing here.
+ elements.push_back(BSON("" << Decimal128::kPositiveNaN));
+ elements.push_back(BSON("" << Decimal128::kNegativeInfinity));
+ elements.push_back(BSON("" << Decimal128::kPositiveInfinity));
}
// Tricky double precision number for binary/decimal conversion: very close to a decimal
@@ -823,8 +842,36 @@ void testPermutation(KeyString::Version version,
}
}
+namespace {
+std::random_device rd;
+std::mt19937_64 seedGen(rd());
+
+// To be used by perf test for seeding, so that the entire test is repeatable in case of error.
+unsigned newSeed() {
+ unsigned int seed = seedGen(); // Replace by the reported number to repeat test execution.
+ log() << "Initializing random number generator using seed " << seed;
+ return seed;
+};
+
+std::vector<BSONObj> thinElements(std::vector<BSONObj> elements,
+ unsigned seed,
+ size_t maxElements) {
+ std::mt19937_64 gen(seed);
+
+ if (elements.size() <= maxElements)
+ return elements;
+
+ log() << "only keeping " << maxElements << " of " << elements.size()
+ << " elements using random selection";
+ std::shuffle(elements.begin(), elements.end(), gen);
+ elements.resize(maxElements);
+ return elements;
+}
+} // namespace
+
+
TEST_F(KeyStringTest, AllPermCompare) {
- const std::vector<BSONObj>& elements = getInterestingElements(version);
+ std::vector<BSONObj> elements = getInterestingElements(version);
for (size_t i = 0; i < elements.size(); i++) {
const BSONObj& o = elements[i];
@@ -839,19 +886,23 @@ TEST_F(KeyStringTest, AllPermCompare) {
}
TEST_F(KeyStringTest, AllPerm2Compare) {
-#if !defined(MONGO_CONFIG_OPTIMIZED_BUILD)
- log() << "\t\t\tskipping permutation testing on non-optimized build";
- return;
-#endif
+ std::vector<BSONObj> baseElements = getInterestingElements(version);
+ auto seed = newSeed();
- const std::vector<BSONObj>& baseElements = getInterestingElements(version);
+ // Select only a small subset of elements, as the combination is quadratic.
+ // We want to select two subsets independently, so all combinations will get tested eventually.
+ // kMaxPermElements is the desired number of elements to pass to testPermutation.
+ const size_t kMaxPermElements = kDebugBuild ? 100000 : 500000;
+ size_t maxElements = sqrt(kMaxPermElements);
+ auto firstElements = thinElements(baseElements, seed, maxElements);
+ auto secondElements = thinElements(baseElements, seed + 1, maxElements);
std::vector<BSONObj> elements;
- for (size_t i = 0; i < baseElements.size(); i++) {
- for (size_t j = 0; j < baseElements.size(); j++) {
+ for (size_t i = 0; i < firstElements.size(); i++) {
+ for (size_t j = 0; j < secondElements.size(); j++) {
BSONObjBuilder b;
- b.appendElements(baseElements[i]);
- b.appendElements(baseElements[j]);
+ b.appendElements(firstElements[i]);
+ b.appendElements(secondElements[j]);
BSONObj o = b.obj();
elements.push_back(o);
}
@@ -927,6 +978,27 @@ TEST_F(KeyStringTest, NaNs) {
ASSERT(std::isnan(toBson(ks2a, ONE_ASCENDING)[""].Double()));
ASSERT(std::isnan(toBson(ks1d, ONE_DESCENDING)[""].Double()));
ASSERT(std::isnan(toBson(ks2d, ONE_DESCENDING)[""].Double()));
+
+ if (version == KeyString::Version::V0)
+ return;
+
+ const auto nan3 = Decimal128::kPositiveNaN;
+ const auto nan4 = Decimal128::kNegativeNaN;
+ // Since we only output a single NaN, we can only do ROUNDTRIP testing for nan1.
+ ROUNDTRIP(version, BSON("" << nan3));
+ const KeyString ks3a(version, BSON("" << nan3), ONE_ASCENDING);
+ const KeyString ks3d(version, BSON("" << nan3), ONE_DESCENDING);
+
+ const KeyString ks4a(version, BSON("" << nan4), ONE_ASCENDING);
+ const KeyString ks4d(version, BSON("" << nan4), ONE_DESCENDING);
+
+ ASSERT_EQ(ks1a, ks4a);
+ ASSERT_EQ(ks1d, ks4d);
+
+ ASSERT(toBson(ks3a, ONE_ASCENDING)[""].Decimal().isNaN());
+ ASSERT(toBson(ks4a, ONE_ASCENDING)[""].Decimal().isNaN());
+ ASSERT(toBson(ks3d, ONE_DESCENDING)[""].Decimal().isNaN());
+ ASSERT(toBson(ks4d, ONE_DESCENDING)[""].Decimal().isNaN());
}
TEST_F(KeyStringTest, NumberOrderLots) {
std::vector<BSONObj> numbers;
@@ -1074,16 +1146,6 @@ const uint64_t kMinPerfMicros = 20 * 1000;
const uint64_t kMinPerfSamples = 50 * 1000;
typedef std::vector<BSONObj> Numbers;
-std::random_device rd;
-std::mt19937 seedGen(rd());
-
-// To be used by perf test for seeding, so that the entire test is repeatable in case of error.
-unsigned newSeed() {
- unsigned int seed = seedGen(); // Replace by the reported number to repeat test execution.
- log() << "Initializing random number generator using seed " << seed;
- return seed;
-};
-
/**
* Evaluates ROUNDTRIP on all items in Numbers a sufficient number of times to take at least
* kMinPerfMicros microseconds. Logs the elapsed time per ROUNDTRIP evaluation.
diff --git a/src/mongo/db/storage/mmap_v1/mmap_v1_engine.cpp b/src/mongo/db/storage/mmap_v1/mmap_v1_engine.cpp
index e185afe03c7..71b3ada3ed4 100644
--- a/src/mongo/db/storage/mmap_v1/mmap_v1_engine.cpp
+++ b/src/mongo/db/storage/mmap_v1/mmap_v1_engine.cpp
@@ -36,6 +36,10 @@
#include <boost/filesystem/path.hpp>
#include <fstream>
+#ifdef __linux__
+#include <sys/sysmacros.h>
+#endif
+
#include "mongo/db/mongod_options.h"
#include "mongo/db/storage/mmap_v1/data_file_sync.h"
#include "mongo/db/storage/mmap_v1/dur.h"
diff --git a/src/mongo/db/storage/wiredtiger/SConscript b/src/mongo/db/storage/wiredtiger/SConscript
index 9d2a8fc8035..42eb3b4d41d 100644
--- a/src/mongo/db/storage/wiredtiger/SConscript
+++ b/src/mongo/db/storage/wiredtiger/SConscript
@@ -82,6 +82,7 @@ if wiredtiger:
],
LIBDEPS=['storage_wiredtiger_core',
'storage_wiredtiger_customization_hooks',
+ '$BUILD_DIR/mongo/db/concurrency/lock_manager',
'$BUILD_DIR/mongo/db/storage/kv/kv_engine',
'$BUILD_DIR/mongo/db/storage/storage_engine_lock_file',
'$BUILD_DIR/mongo/db/storage/storage_engine_metadata',
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp
index d14b33de5f7..e40d3a58edb 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.cpp
@@ -31,12 +31,9 @@
#include "mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h"
-#include <boost/filesystem/path.hpp>
-
#include "mongo/base/init.h"
#include "mongo/base/string_data.h"
#include "mongo/db/service_context.h"
-#include "mongo/db/storage/data_protector.h"
#include "mongo/stdx/memory.h"
namespace mongo {
@@ -44,7 +41,7 @@ namespace mongo {
/* Make a WiredTigerCustomizationHooks pointer a decoration on the global ServiceContext */
MONGO_INITIALIZER_WITH_PREREQUISITES(SetWiredTigerCustomizationHooks, ("SetGlobalEnvironment"))
(InitializerContext* context) {
- auto customizationHooks = stdx::make_unique<EmptyWiredTigerCustomizationHooks>();
+ auto customizationHooks = stdx::make_unique<WiredTigerCustomizationHooks>();
WiredTigerCustomizationHooks::set(getGlobalServiceContext(), std::move(customizationHooks));
return Status::OK();
@@ -66,37 +63,14 @@ WiredTigerCustomizationHooks* WiredTigerCustomizationHooks::get(ServiceContext*
return getCustomizationHooks(service).get();
}
-EmptyWiredTigerCustomizationHooks::~EmptyWiredTigerCustomizationHooks() {}
-
-bool EmptyWiredTigerCustomizationHooks::enabled() const {
- return false;
-}
+WiredTigerCustomizationHooks::~WiredTigerCustomizationHooks() {}
-bool EmptyWiredTigerCustomizationHooks::restartRequired() {
+bool WiredTigerCustomizationHooks::enabled() const {
return false;
}
-std::string EmptyWiredTigerCustomizationHooks::getTableCreateConfig(StringData tableName) {
- return "";
-}
-
-std::unique_ptr<DataProtector> EmptyWiredTigerCustomizationHooks::getDataProtector() {
- return std::unique_ptr<DataProtector>();
-}
-
-boost::filesystem::path EmptyWiredTigerCustomizationHooks::getProtectedPathSuffix() {
+std::string WiredTigerCustomizationHooks::getTableCreateConfig(StringData tableName) {
return "";
}
-Status EmptyWiredTigerCustomizationHooks::protectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) {
- return Status(ErrorCodes::InternalError,
- "Customization hooks must be enabled to use preprocessTmpData.");
-}
-
-Status EmptyWiredTigerCustomizationHooks::unprotectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) {
- return Status(ErrorCodes::InternalError,
- "Customization hooks must be enabled to use postprocessTmpData.");
-}
} // namespace mongo
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h
index e78995481bd..1ff86a8799e 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_customization_hooks.h
@@ -32,20 +32,11 @@
#include <memory>
#include <string>
-#include "mongo/base/disallow_copying.h"
-#include "mongo/db/jsobj.h"
-
-namespace boost {
-namespace filesystem {
-class path;
-} // namespace filesystem
-} // namespace boost
-
namespace mongo {
-class DataProtector;
class StringData;
class ServiceContext;
+// Interface and default implementation for WiredTiger customization hooks
class WiredTigerCustomizationHooks {
public:
static void set(ServiceContext* service,
@@ -53,76 +44,18 @@ public:
static WiredTigerCustomizationHooks* get(ServiceContext* service);
- virtual ~WiredTigerCustomizationHooks() = default;
+ virtual ~WiredTigerCustomizationHooks();
/**
* Returns true if the customization hooks are enabled.
*/
- virtual bool enabled() const = 0;
-
- /**
- * Perform any encryption engine initialization/sanity checking that needs to happen after
- * storage engine initialization but before the server starts accepting incoming connections.
- *
- * Returns true if the server needs to be rebooted because of configuration changes.
- */
- virtual bool restartRequired() = 0;
+ virtual bool enabled() const;
/**
* Gets an additional configuration string for the provided table name on a
* `WT_SESSION::create` call.
*/
- virtual std::string getTableCreateConfig(StringData tableName) = 0;
-
- /**
- * Returns the maximum size addition when doing transforming temp data.
- */
- size_t additionalBytesForProtectedBuffer() {
- return 33;
- }
-
- /**
- * Get the data protector object
- */
- virtual std::unique_ptr<DataProtector> getDataProtector() = 0;
-
- /**
- * Get an implementation specific path suffix to tag files with
- */
- virtual boost::filesystem::path getProtectedPathSuffix() = 0;
-
- /**
- * Transform temp data to non-readable form before writing it to disk.
- */
- virtual Status protectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) = 0;
-
- /**
- * Tranforms temp data back to readable form, after reading from disk.
- */
- virtual Status unprotectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) = 0;
+ virtual std::string getTableCreateConfig(StringData tableName);
};
-// Empty default implementation of the abstract class WiredTigerCustomizationHooks
-class EmptyWiredTigerCustomizationHooks : public WiredTigerCustomizationHooks {
-public:
- ~EmptyWiredTigerCustomizationHooks() override;
-
- bool enabled() const override;
-
- bool restartRequired() override;
-
- std::string getTableCreateConfig(StringData tableName) override;
-
- std::unique_ptr<DataProtector> getDataProtector() override;
-
- boost::filesystem::path getProtectedPathSuffix() override;
-
- Status protectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) override;
-
- Status unprotectTmpData(
- const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen, size_t* resultLen) override;
-};
} // namespace mongo
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
index 6331594a57c..ecf3c183996 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
@@ -1102,6 +1102,7 @@ void WiredTigerIndexUnique::_unindex(WT_CURSOR* c,
triggerWriteConflictAtPoint(c);
return;
}
+ invariantWTOK(ret);
WT_ITEM value;
invariantWTOK(c->get_value(c, &value));
BufReader br(value.data, value.size);
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp
index f48fff37b0b..aafb52b30c0 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp
@@ -228,6 +228,7 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName,
ss << "checkpoint=(wait=" << wiredTigerGlobalOptions.checkpointDelaySecs;
ss << ",log_size=2GB),";
ss << "statistics_log=(wait=" << wiredTigerGlobalOptions.statisticsLogDelaySecs << "),";
+ ss << "verbose=(recovery_progress),";
}
ss << WiredTigerCustomizationHooks::get(getGlobalServiceContext())
->getTableCreateConfig("system");
@@ -253,6 +254,13 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName,
msgassertedNoTrace(28718, s.reason());
}
invariantWTOK(_conn->close(_conn, NULL));
+ // After successful recovery, remove the journal directory.
+ try {
+ boost::filesystem::remove_all(journalPath);
+ } catch (std::exception& e) {
+ error() << "error removing journal dir " << journalPath.string() << ' ' << e.what();
+ throw;
+ }
}
// This setting overrides the earlier setting because it is later in the config string.
ss << ",log=(enabled=false),";
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
index 0d3cf93a307..7761857bf90 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
@@ -909,7 +909,7 @@ int64_t WiredTigerRecordStore::storageSize(OperationContext* txn,
if (_isEphemeral) {
return dataSize(txn);
}
- WiredTigerSession* session = WiredTigerRecoveryUnit::get(txn)->getSession(txn);
+ WiredTigerSession* session = WiredTigerRecoveryUnit::get(txn)->getSessionNoTxn(txn);
StatusWith<int64_t> result =
WiredTigerUtil::getStatisticsValueAs<int64_t>(session->getSession(),
"statistics:" + getURI(),
@@ -1194,7 +1194,9 @@ bool WiredTigerRecordStore::yieldAndAwaitOplogDeletionRequest(OperationContext*
// The top-level locks were freed, so also release any potential low-level (storage engine)
// locks that might be held.
- txn->recoveryUnit()->abandonSnapshot();
+ WiredTigerRecoveryUnit* recoveryUnit = (WiredTigerRecoveryUnit*)txn->recoveryUnit();
+ recoveryUnit->abandonSnapshot();
+ recoveryUnit->beginIdle();
// Wait for an oplog deletion request, or for this record store to have been destroyed.
oplogStones->awaitHasExcessStonesOrDead();
@@ -1219,15 +1221,22 @@ void WiredTigerRecordStore::reclaimOplog(OperationContext* txn) {
try {
WriteUnitOfWork wuow(txn);
- WiredTigerCursor startwrap(_uri, _tableId, true, txn);
- WT_CURSOR* start = startwrap.get();
- start->set_key(start, _makeKey(_oplogStones->firstRecord));
+ WiredTigerCursor cwrap(_uri, _tableId, true, txn);
+ WT_CURSOR* cursor = cwrap.get();
- WiredTigerCursor endwrap(_uri, _tableId, true, txn);
- WT_CURSOR* end = endwrap.get();
- end->set_key(end, _makeKey(stone->lastRecord));
+ // The first record in the oplog should be within the truncate range.
+ int ret = WT_READ_CHECK(cursor->next(cursor));
+ invariantWTOK(ret);
+ int64_t key;
+ invariantWTOK(cursor->get_key(cursor, &key));
+ RecordId firstRecord = _fromKey(key);
+ if (firstRecord < _oplogStones->firstRecord || firstRecord > stone->lastRecord) {
+ warning() << "First oplog record " << firstRecord << " is not in truncation range ("
+ << _oplogStones->firstRecord << ", " << stone->lastRecord << ")";
+ }
- invariantWTOK(session->truncate(session, nullptr, start, end, nullptr));
+ cursor->set_key(cursor, _makeKey(stone->lastRecord));
+ invariantWTOK(session->truncate(session, nullptr, nullptr, cursor, nullptr));
_changeNumRecords(txn, -stone->records);
_increaseDataSize(txn, -stone->bytes);
@@ -1619,7 +1628,7 @@ void WiredTigerRecordStore::appendCustomStats(OperationContext* txn,
result->appendIntOrLL("sleepCount", _cappedSleep.load());
result->appendIntOrLL("sleepMS", _cappedSleepMS.load());
}
- WiredTigerSession* session = WiredTigerRecoveryUnit::get(txn)->getSession(txn);
+ WiredTigerSession* session = WiredTigerRecoveryUnit::get(txn)->getSessionNoTxn(txn);
WT_SESSION* s = session->getSession();
BSONObjBuilder bob(result->subobjStart(_engineName));
{
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp
index 66fd5e0ddfa..fe35bd07651 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp
@@ -43,6 +43,7 @@
#include "mongo/util/concurrency/ticketholder.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/scopeguard.h"
#include "mongo/util/stacktrace.h"
namespace mongo {
@@ -172,7 +173,12 @@ WiredTigerSession* WiredTigerRecoveryUnit::getSession(OperationContext* opCtx) {
WiredTigerSession* WiredTigerRecoveryUnit::getSessionNoTxn(OperationContext* opCtx) {
_ensureSession();
- return _session.get();
+ WiredTigerSession* session = _session.get();
+
+ // Dropping the queued idents might block session, which is not desired for fastpath workflow
+ // like FTDC thread. Disable dropping of queued idents for such sessions.
+ session->dropQueuedIdentsAtSessionEndAllowed(false);
+ return session;
}
void WiredTigerRecoveryUnit::abandonSnapshot() {
@@ -249,6 +255,13 @@ void WiredTigerRecoveryUnit::_txnOpen(OperationContext* opCtx) {
_active = true;
}
+void WiredTigerRecoveryUnit::beginIdle() {
+ // Close all cursors, we don't want to keep any old cached cursors around.
+ if (_session) {
+ _session->closeAllCursors("");
+ }
+}
+
// ---------------------
WiredTigerCursor::WiredTigerCursor(const std::string& uri,
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h
index 86d9eece13d..695ad331240 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h
@@ -84,6 +84,12 @@ public:
WiredTigerSession* getSession(OperationContext* opCtx);
/**
+ * Enter a period of wait or computation during which there are no WT calls.
+ * Any non-relevant cached handles can be closed.
+ */
+ void beginIdle();
+
+ /**
* Returns a session without starting a new WT txn on the session. Will not close any already
* running session.
*/
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_server_status.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_server_status.cpp
index 4967dfc2f86..3cb87349fdb 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_server_status.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_server_status.cpp
@@ -35,6 +35,7 @@
#include "mongo/base/checked_cast.h"
#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/concurrency/d_concurrency.h"
#include "mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h"
#include "mongo/db/storage/wiredtiger/wiredtiger_record_store.h"
#include "mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h"
@@ -56,6 +57,8 @@ bool WiredTigerServerStatusSection::includeByDefault() const {
BSONObj WiredTigerServerStatusSection::generateSection(OperationContext* txn,
const BSONElement& configElement) const {
+ Lock::GlobalLock lk(txn->lockState(), LockMode::MODE_IS, UINT_MAX);
+
// The session does not open a transaction here as one is not needed and opening one would
// mean that execution could become blocked when a new transaction cannot be allocated
// immediately.
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp
index 4fccd06ed6d..9df6ba94651 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp
@@ -36,6 +36,7 @@
#include "mongo/db/storage/wiredtiger/wiredtiger_session_cache.h"
#include "mongo/base/error_codes.h"
+#include "mongo/db/server_parameters.h"
#include "mongo/db/storage/journal_listener.h"
#include "mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h"
#include "mongo/db/storage/wiredtiger/wiredtiger_util.h"
@@ -46,13 +47,32 @@
namespace mongo {
+std::atomic<std::int32_t> kWiredTigerCursorCacheSize(10000); // NOLINT
+
+class WiredTigerCursorCacheSize
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime> {
+public:
+ WiredTigerCursorCacheSize()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupAndRuntime>(
+ ServerParameterSet::getGlobal(),
+ "wiredTigerCursorCacheSize",
+ &kWiredTigerCursorCacheSize) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "wiredTigerCursorCacheSize must be greater than or equal "
+ << "to 0, but attempted to set to: "
+ << potentialNewValue);
+ }
+
+ return Status::OK();
+ }
+} WiredTigerCursorCacheSizeSetting;
+
WiredTigerSession::WiredTigerSession(WT_CONNECTION* conn, uint64_t epoch, uint64_t cursorEpoch)
- : _epoch(epoch),
- _cursorEpoch(cursorEpoch),
- _session(NULL),
- _cursorGen(0),
- _cursorsCached(0),
- _cursorsOut(0) {
+ : _epoch(epoch), _cursorEpoch(cursorEpoch), _session(NULL), _cursorGen(0), _cursorsOut(0) {
invariantWTOK(conn->open_session(conn, NULL, "isolation=snapshot", &_session));
}
@@ -65,7 +85,6 @@ WiredTigerSession::WiredTigerSession(WT_CONNECTION* conn,
_cache(cache),
_session(NULL),
_cursorGen(0),
- _cursorsCached(0),
_cursorsOut(0) {
invariantWTOK(conn->open_session(conn, NULL, "isolation=snapshot", &_session));
}
@@ -83,7 +102,6 @@ WT_CURSOR* WiredTigerSession::getCursor(const std::string& uri, uint64_t id, boo
WT_CURSOR* c = i->_cursor;
_cursors.erase(i);
_cursorsOut++;
- _cursorsCached--;
return c;
}
}
@@ -107,17 +125,11 @@ void WiredTigerSession::releaseCursor(uint64_t id, WT_CURSOR* cursor) {
// Cursors are pushed to the front of the list and removed from the back
_cursors.push_front(WiredTigerCachedCursor(id, _cursorGen++, cursor));
- _cursorsCached++;
-
- // "Old" is defined as not used in the last N**2 operations, if we have N cursors cached.
- // The reasoning here is to imagine a workload with N tables performing operations randomly
- // across all of them (i.e., each cursor has 1/N chance of used for each operation). We
- // would like to cache N cursors in that case, so any given cursor could go N**2 operations
- // in between use.
- while (_cursorGen - _cursors.back()._gen > 10000) {
+
+ std::uint64_t cursorCacheSize = static_cast<std::uint64_t>(kWiredTigerCursorCacheSize.load());
+ while (!_cursors.empty() && _cursorGen - _cursors.back()._gen > cursorCacheSize) {
cursor = _cursors.back()._cursor;
_cursors.pop_back();
- _cursorsCached--;
invariantWTOK(cursor->close(cursor));
}
}
@@ -125,9 +137,10 @@ void WiredTigerSession::releaseCursor(uint64_t id, WT_CURSOR* cursor) {
void WiredTigerSession::closeAllCursors(const std::string& uri) {
invariant(_session);
+ bool all = (uri == "");
for (auto i = _cursors.begin(); i != _cursors.end();) {
WT_CURSOR* cursor = i->_cursor;
- if (cursor && uri == cursor->uri) {
+ if (cursor && (all || uri == cursor->uri)) {
invariantWTOK(cursor->close(cursor));
i = _cursors.erase(i);
} else
@@ -344,6 +357,11 @@ void WiredTigerSessionCache::releaseSession(WiredTigerSession* session) {
bool returnedToCache = false;
uint64_t currentEpoch = _epoch.load();
+ bool dropQueuedIdentsAtSessionEnd = session->isDropQueuedIdentsAtSessionEndAllowed();
+
+ // Reset this session's flag for dropping queued idents to default, before returning it to
+ // session cache.
+ session->dropQueuedIdentsAtSessionEndAllowed(true);
if (session->_getEpoch() == currentEpoch) { // check outside of lock to reduce contention
stdx::lock_guard<stdx::mutex> lock(_cacheLock);
@@ -357,7 +375,7 @@ void WiredTigerSessionCache::releaseSession(WiredTigerSession* session) {
if (!returnedToCache)
delete session;
- if (_engine && _engine->haveDropsQueued())
+ if (dropQueuedIdentsAtSessionEnd && _engine && _engine->haveDropsQueued())
_engine->dropSomeQueuedIdents();
}
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.h b/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.h
index e8fa9811796..34c05a3304b 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.h
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.h
@@ -99,12 +99,24 @@ public:
void closeCursorsForQueuedDrops(WiredTigerKVEngine* engine);
+ /**
+ * Closes all cached cursors matching the uri. If the uri is empty,
+ * all cached cursors are closed.
+ */
void closeAllCursors(const std::string& uri);
int cursorsOut() const {
return _cursorsOut;
}
+ bool isDropQueuedIdentsAtSessionEndAllowed() const {
+ return _dropQueuedIdentsAtSessionEnd;
+ }
+
+ void dropQueuedIdentsAtSessionEndAllowed(bool dropQueuedIdentsAtSessionEnd) {
+ _dropQueuedIdentsAtSessionEnd = dropQueuedIdentsAtSessionEnd;
+ }
+
static uint64_t genTableId();
/**
@@ -134,7 +146,8 @@ private:
WT_SESSION* _session; // owned
CursorCache _cursors; // owned
uint64_t _cursorGen;
- int _cursorsCached, _cursorsOut;
+ int _cursorsOut;
+ bool _dropQueuedIdentsAtSessionEnd = true;
};
/**
@@ -174,8 +187,8 @@ public:
void closeCursorsForQueuedDrops();
/**
- * Closes all cached cursors and ensures that previously opened cursors will be closed on
- * release.
+ * Closes all cached cursors matching the uri. If the uri is empty,
+ * all cached cursors are closed.
*/
void closeAllCursors(const std::string& uri);
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_size_storer.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_size_storer.cpp
index 7783cea4cbf..bab41079a9b 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_size_storer.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_size_storer.cpp
@@ -202,7 +202,7 @@ void WiredTigerSizeStorer::syncCache(bool syncToDisk) {
WT_SESSION* session = _session.getSession();
invariantWTOK(session->begin_transaction(session, syncToDisk ? "sync=true" : ""));
- ScopeGuard rollbacker = MakeGuard(session->rollback_transaction, session, "");
+ auto rollbacker = MakeGuard(session->rollback_transaction, session, "");
for (Map::iterator it = myMap.begin(); it != myMap.end(); ++it) {
string uriKey = it->first;
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.cpp
index da0f618f1bd..e6000e62604 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.cpp
@@ -38,6 +38,7 @@
#include "mongo/db/storage/wiredtiger/wiredtiger_snapshot_manager.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/scopeguard.h"
namespace mongo {
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp
index f00b4a33804..c0f0907f8da 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp
@@ -103,9 +103,13 @@ void WiredTigerUtil::fetchTypeAndSourceURI(OperationContext* opCtx,
StatusWith<std::string> WiredTigerUtil::getMetadata(OperationContext* opCtx, StringData uri) {
invariant(opCtx);
- WiredTigerCursor curwrap("metadata:create", WiredTigerSession::kMetadataTableId, false, opCtx);
- WT_CURSOR* cursor = curwrap.get();
+
+ auto session = WiredTigerRecoveryUnit::get(opCtx)->getSessionNoTxn(opCtx);
+ WT_CURSOR* cursor =
+ session->getCursor("metadata:create", WiredTigerSession::kMetadataTableId, false);
invariant(cursor);
+ ON_BLOCK_EXIT([&] { session->releaseCursor(WiredTigerSession::kMetadataTableId, cursor); });
+
std::string strUri = uri.toString();
cursor->set_key(cursor, strUri.c_str());
int ret = cursor->search(cursor);
diff --git a/src/mongo/db/views/resolved_view.cpp b/src/mongo/db/views/resolved_view.cpp
index d5f4faa7f68..f497cbbb9bd 100644
--- a/src/mongo/db/views/resolved_view.cpp
+++ b/src/mongo/db/views/resolved_view.cpp
@@ -62,7 +62,27 @@ ResolvedView ResolvedView::fromBSON(BSONObj commandResponseObj) {
pipeline.push_back(item.Obj().getOwned());
}
- return {ResolvedView(NamespaceString(viewDef["ns"].valueStringData()), pipeline)};
+ BSONObj collationSpec;
+ if (auto collationElt = viewDef["collation"]) {
+ uassert(40639,
+ "View definition 'collation' field must be an object",
+ collationElt.type() == BSONType::Object);
+ collationSpec = collationElt.embeddedObject().getOwned();
+ }
+
+ return {NamespaceString(viewDef["ns"].valueStringData()),
+ std::move(pipeline),
+ std::move(collationSpec)};
+}
+
+BSONObj ResolvedView::toBSON() const {
+ BSONObjBuilder builder;
+ builder.append("ns", _namespace.ns());
+ builder.append("pipeline", _pipeline);
+ if (!_defaultCollation.isEmpty()) {
+ builder.append("collation", _defaultCollation);
+ }
+ return builder.obj();
}
StatusWith<BSONObj> ResolvedView::asExpandedViewAggregation(
@@ -104,6 +124,13 @@ StatusWith<BSONObj> ResolvedView::asExpandedViewAggregation(
aggregationBuilder.append("allowDiskUse", true);
}
+ // Operations on a view must always use the default collation of the view. We must have already
+ // checked that if the user's request specifies a collation, it matches the collation of the
+ // view.
+ if (!_defaultCollation.isEmpty()) {
+ aggregationBuilder.append("collation", _defaultCollation);
+ }
+
return aggregationBuilder.obj();
}
diff --git a/src/mongo/db/views/resolved_view.h b/src/mongo/db/views/resolved_view.h
index a5016ffc4b3..789576c737f 100644
--- a/src/mongo/db/views/resolved_view.h
+++ b/src/mongo/db/views/resolved_view.h
@@ -44,8 +44,12 @@ class AggregationRequest;
*/
class ResolvedView {
public:
- ResolvedView(const NamespaceString& collectionNs, const std::vector<BSONObj>& pipeline)
- : _namespace(collectionNs), _pipeline(pipeline) {}
+ ResolvedView(const NamespaceString& collectionNs,
+ std::vector<BSONObj> pipeline,
+ BSONObj defaultCollation)
+ : _namespace(collectionNs),
+ _pipeline(std::move(pipeline)),
+ _defaultCollation(std::move(defaultCollation)) {}
/**
* Returns whether 'commandResponseObj' contains a CommandOnShardedViewNotSupportedOnMongod
@@ -55,6 +59,8 @@ public:
static ResolvedView fromBSON(BSONObj commandResponseObj);
+ BSONObj toBSON() const;
+
/**
* Convert an aggregation command on a view to the equivalent command against the views
* underlying collection.
@@ -70,9 +76,21 @@ public:
return _pipeline;
}
+ const BSONObj& getDefaultCollation() const {
+ return _defaultCollation;
+ }
+
private:
NamespaceString _namespace;
std::vector<BSONObj> _pipeline;
+
+ // The default collation associated with this view. An empty object means that the default is
+ // the simple collation.
+ //
+ // Currently all operations which run over a view must use the default collation. This means
+ // that operations on the view which do not specify a collation inherit the default. Operations
+ // on the view which specify any other collation fail with a user error.
+ BSONObj _defaultCollation;
};
} // namespace mongo
diff --git a/src/mongo/db/views/resolved_view_test.cpp b/src/mongo/db/views/resolved_view_test.cpp
index eebcda01db5..d8b95308d45 100644
--- a/src/mongo/db/views/resolved_view_test.cpp
+++ b/src/mongo/db/views/resolved_view_test.cpp
@@ -33,6 +33,7 @@
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/aggregation_request.h"
#include "mongo/db/views/resolved_view.h"
@@ -50,9 +51,10 @@ namespace {
const NamespaceString viewNss("testdb.testview");
const NamespaceString backingNss("testdb.testcoll");
const std::vector<BSONObj> emptyPipeline;
+const BSONObj kSimpleCollation;
TEST(ResolvedViewTest, ExpandingCmdObjWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
BSONObj cmdObj = BSON("aggregate" << viewNss.coll() << "pipeline" << BSONArray());
auto result = resolvedView.asExpandedViewAggregation(cmdObj);
@@ -65,7 +67,7 @@ TEST(ResolvedViewTest, ExpandingCmdObjWithEmptyPipelineOnNoOpViewYieldsEmptyPipe
TEST(ResolvedViewTest, ExpandingCmdObjWithNonemptyPipelineAppendsToViewPipeline) {
std::vector<BSONObj> viewPipeline{BSON("skip" << 7)};
- const ResolvedView resolvedView{backingNss, viewPipeline};
+ const ResolvedView resolvedView{backingNss, viewPipeline, kSimpleCollation};
BSONObj cmdObj =
BSON("aggregate" << viewNss.coll() << "pipeline" << BSON_ARRAY(BSON("limit" << 3)));
@@ -80,13 +82,13 @@ TEST(ResolvedViewTest, ExpandingCmdObjWithNonemptyPipelineAppendsToViewPipeline)
}
TEST(ResolvedViewTest, ExpandingCmdObjFailsIfCmdObjIsNotAValidAggregationCommand) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
BSONObj badCmdObj = BSON("invalid" << 0);
ASSERT_NOT_OK(resolvedView.asExpandedViewAggregation(badCmdObj).getStatus());
}
TEST(ResolvedViewTest, ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
AggregationRequest aggRequest(viewNss, {});
auto result = resolvedView.asExpandedViewAggregation(aggRequest);
@@ -99,7 +101,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmpty
TEST(ResolvedViewTest, ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipeline) {
std::vector<BSONObj> viewPipeline{BSON("skip" << 7)};
- const ResolvedView resolvedView{backingNss, viewPipeline};
+ const ResolvedView resolvedView{backingNss, viewPipeline, kSimpleCollation};
std::vector<BSONObj> userAggregationPipeline = {BSON("limit" << 3)};
AggregationRequest aggRequest(viewNss, userAggregationPipeline);
@@ -115,7 +117,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipel
}
TEST(ResolvedViewTest, ExpandingAggRequestPreservesExplain) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
AggregationRequest aggRequest(viewNss, {});
aggRequest.setExplain(true);
@@ -130,7 +132,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesExplain) {
}
TEST(ResolvedViewTest, ExpandingAggRequestPreservesBypassDocumentValidation) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
AggregationRequest aggRequest(viewNss, {});
aggRequest.setBypassDocumentValidation(true);
@@ -145,7 +147,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesBypassDocumentValidation) {
}
TEST(ResolvedViewTest, ExpandingAggRequestPreservesAllowDiskUse) {
- const ResolvedView resolvedView{backingNss, emptyPipeline};
+ const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
AggregationRequest aggRequest(viewNss, {});
aggRequest.setAllowDiskUse(true);
@@ -159,6 +161,27 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesAllowDiskUse) {
ASSERT_BSONOBJ_EQ(result.getValue(), expected);
}
+TEST(ResolvedViewTest, ExpandingAggRequestPreservesDefaultCollationOfView) {
+ const ResolvedView resolvedView{backingNss,
+ emptyPipeline,
+ BSON("locale"
+ << "fr_CA")};
+ ASSERT_BSONOBJ_EQ(resolvedView.getDefaultCollation(),
+ BSON("locale"
+ << "fr_CA"));
+ AggregationRequest aggRequest(viewNss, {});
+
+ auto result = resolvedView.asExpandedViewAggregation(aggRequest);
+ ASSERT_OK(result.getStatus());
+
+ BSONObj expected =
+ BSON("aggregate" << backingNss.coll() << "pipeline" << BSONArray() << "cursor" << BSONObj()
+ << "collation"
+ << BSON("locale"
+ << "fr_CA"));
+ ASSERT_BSONOBJ_EQ(result.getValue(), expected);
+}
+
TEST(ResolvedViewTest, FromBSONFailsIfMissingResolvedView) {
BSONObj badCmdResponse = BSON("x" << 1);
ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), UserException, 40248);
@@ -190,6 +213,13 @@ TEST(ResolvedViewTest, FromBSONFailsOnInvalidPipelineType) {
ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), UserException, 40251);
}
+TEST(ResolvedViewTest, FromBSONFailsOnInvalidCollationType) {
+ BSONObj badCmdResponse =
+ BSON("resolvedView" << BSON(
+ "ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation" << 1));
+ ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40639);
+}
+
TEST(ResolvedViewTest, FromBSONSuccessfullyParsesEmptyBSONArrayIntoEmptyVector) {
BSONObj cmdResponse =
BSON("resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray()));
@@ -222,6 +252,22 @@ TEST(ResolvedViewTest, FromBSONSuccessfullyParsesPopulatedBSONArrayIntoVector) {
SimpleBSONObjComparator::kInstance.makeEqualTo()));
}
+TEST(ResolvedViewTest, FromBSONSuccessfullyParsesCollation) {
+ BSONObj cmdResponse = BSON(
+ "resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation"
+ << BSON("locale"
+ << "fil")));
+ const ResolvedView result = ResolvedView::fromBSON(cmdResponse);
+ ASSERT_EQ(result.getNamespace(), backingNss);
+ ASSERT(std::equal(emptyPipeline.begin(),
+ emptyPipeline.end(),
+ result.getPipeline().begin(),
+ SimpleBSONObjComparator::kInstance.makeEqualTo()));
+ ASSERT_BSONOBJ_EQ(result.getDefaultCollation(),
+ BSON("locale"
+ << "fil"));
+}
+
TEST(ResolvedViewTest, IsResolvedViewErrorResponseDetectsKickbackErrorCodeSuccessfully) {
BSONObj errorResponse =
BSON("ok" << 0 << "code" << ErrorCodes::CommandOnShardedViewNotSupportedOnMongod << "errmsg"
@@ -235,5 +281,34 @@ TEST(ResolvedViewTest, IsResolvedViewErrorResponseReportsFalseOnNonKickbackError
<< "View nesting too deep or view cycle detected");
ASSERT_FALSE(ResolvedView::isResolvedViewErrorResponse(errorResponse));
}
+
+TEST(ResolvedViewTest, ToBSONSerializesCorrectly) {
+ const ResolvedView resolvedView{backingNss,
+ std::vector<BSONObj>{BSON("$match" << BSON("x" << 1))},
+ BSON("locale"
+ << "fr_CA")};
+ auto serialized = resolvedView.toBSON();
+ ASSERT_BSONOBJ_EQ(
+ serialized,
+ fromjson(
+ "{ns: 'testdb.testcoll', pipeline: [{$match: {x: 1}}], collation: {locale: 'fr_CA'}}"));
+}
+
+TEST(ResolvedViewTest, ToBSONOutputCanBeReparsed) {
+ const ResolvedView resolvedView{backingNss,
+ emptyPipeline,
+ BSON("locale"
+ << "fr_CA")};
+ auto serialized = resolvedView.toBSON();
+ auto reparsedResolvedView = ResolvedView::fromBSON(BSON("resolvedView" << serialized));
+ ASSERT_EQ(reparsedResolvedView.getNamespace(), backingNss);
+ ASSERT(std::equal(emptyPipeline.begin(),
+ emptyPipeline.end(),
+ reparsedResolvedView.getPipeline().begin(),
+ SimpleBSONObjComparator::kInstance.makeEqualTo()));
+ ASSERT_BSONOBJ_EQ(reparsedResolvedView.getDefaultCollation(),
+ BSON("locale"
+ << "fr_CA"));
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp
index 30cabf24935..d590c509a7e 100644
--- a/src/mongo/db/views/view_catalog.cpp
+++ b/src/mongo/db/views/view_catalog.cpp
@@ -394,6 +394,7 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn,
stdx::lock_guard<stdx::mutex> lk(_mutex);
const NamespaceString* resolvedNss = &nss;
std::vector<BSONObj> resolvedPipeline;
+ BSONObj collation;
for (int i = 0; i < ViewGraph::kMaxViewDepth; i++) {
auto view = _lookup_inlock(txn, resolvedNss->ns());
@@ -408,10 +409,13 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn,
str::stream() << "View pipeline exceeds maximum size; maximum size is "
<< ViewGraph::kMaxViewPipelineSizeBytes};
}
- return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline});
+ return StatusWith<ResolvedView>(
+ {*resolvedNss, std::move(resolvedPipeline), std::move(collation)});
}
resolvedNss = &(view->viewOn());
+ collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON()
+ : CollationSpec::kSimpleSpec;
// Prepend the underlying view's pipeline to the current working pipeline.
const std::vector<BSONObj>& toPrepend = view->pipeline();
@@ -419,7 +423,8 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn,
// If the first stage is a $collStats, then we return early with the viewOn namespace.
if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) {
- return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline});
+ return StatusWith<ResolvedView>(
+ {*resolvedNss, std::move(resolvedPipeline), std::move(collation)});
}
}
diff --git a/src/mongo/db/views/view_catalog_test.cpp b/src/mongo/db/views/view_catalog_test.cpp
index 1ca54f809df..367b4f73b0c 100644
--- a/src/mongo/db/views/view_catalog_test.cpp
+++ b/src/mongo/db/views/view_catalog_test.cpp
@@ -38,6 +38,8 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/query/query_test_service_context.h"
#include "mongo/db/server_options.h"
#include "mongo/db/service_context_noop.h"
@@ -417,6 +419,42 @@ TEST_F(ViewCatalogFixture, ResolveViewCorrectPipeline) {
}
}
+TEST_F(ViewCatalogFixture, ResolveViewCorrectlyExtractsDefaultCollation) {
+ const NamespaceString view1("db.view1");
+ const NamespaceString view2("db.view2");
+ const NamespaceString viewOn("db.coll");
+ BSONArrayBuilder pipeline1;
+ BSONArrayBuilder pipeline2;
+
+ pipeline1 << BSON("$match" << BSON("foo" << 1));
+ pipeline2 << BSON("$match" << BSON("foo" << 2));
+
+ BSONObj collation = BSON("locale"
+ << "mock_reverse_string");
+
+ ASSERT_OK(viewCatalog.createView(opCtx.get(), view1, viewOn, pipeline1.arr(), collation));
+ ASSERT_OK(viewCatalog.createView(opCtx.get(), view2, view1, pipeline2.arr(), collation));
+
+ auto resolvedView = viewCatalog.resolveView(opCtx.get(), view2);
+ ASSERT(resolvedView.isOK());
+
+ ASSERT_EQ(resolvedView.getValue().getNamespace(), viewOn);
+
+ std::vector<BSONObj> expected = {BSON("$match" << BSON("foo" << 1)),
+ BSON("$match" << BSON("foo" << 2))};
+ std::vector<BSONObj> result = resolvedView.getValue().getPipeline();
+ ASSERT_EQ(expected.size(), result.size());
+ for (uint32_t i = 0; i < expected.size(); i++) {
+ ASSERT(SimpleBSONObjComparator::kInstance.evaluate(expected[i] == result[i]));
+ }
+
+ auto expectedCollation =
+ CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation);
+ ASSERT_OK(expectedCollation.getStatus());
+ ASSERT_BSONOBJ_EQ(resolvedView.getValue().getDefaultCollation(),
+ expectedCollation.getValue()->getSpec().toBSON());
+}
+
TEST_F(ViewCatalogFixture, InvalidateThenReload) {
const NamespaceString viewName("db.view");
const NamespaceString viewOn("db.coll");
diff --git a/src/mongo/db/views/view_sharding_check.cpp b/src/mongo/db/views/view_sharding_check.cpp
index cc662ff2549..90835e03a07 100644
--- a/src/mongo/db/views/view_sharding_check.cpp
+++ b/src/mongo/db/views/view_sharding_check.cpp
@@ -69,11 +69,7 @@ StatusWith<BSONObj> ViewShardingCheck::getResolvedViewIfSharded(OperationContext
return BSONObj();
}
- BSONObjBuilder viewDetailBob;
- viewDetailBob.append("ns", sourceNss.ns());
- viewDetailBob.append("pipeline", resolvedView.getValue().getPipeline());
-
- return viewDetailBob.obj();
+ return resolvedView.getValue().toBSON();
}
void ViewShardingCheck::appendShardedViewStatus(const BSONObj& resolvedView, BSONObjBuilder* out) {
diff --git a/src/mongo/dbtests/dbhelper_tests.cpp b/src/mongo/dbtests/dbhelper_tests.cpp
index 1ed948092c7..7a4d745e764 100644
--- a/src/mongo/dbtests/dbhelper_tests.cpp
+++ b/src/mongo/dbtests/dbhelper_tests.cpp
@@ -74,8 +74,12 @@ public:
KeyRange range(ns, BSON("_id" << _min), BSON("_id" << _max), BSON("_id" << 1));
mongo::WriteConcernOptions dummyWriteConcern;
- Helpers::removeRange(
- &txn, range, BoundInclusion::kIncludeStartKeyOnly, dummyWriteConcern);
+ Milliseconds dummyReplWaitDuration;
+ Helpers::removeRange(&txn,
+ range,
+ BoundInclusion::kIncludeStartKeyOnly,
+ dummyWriteConcern,
+ dummyReplWaitDuration);
}
// Check that the expected documents remain.
diff --git a/src/mongo/dbtests/jstests.cpp b/src/mongo/dbtests/jstests.cpp
index 13e71a0a74a..c0421910c2f 100644
--- a/src/mongo/dbtests/jstests.cpp
+++ b/src/mongo/dbtests/jstests.cpp
@@ -2388,6 +2388,49 @@ public:
}
};
+class RequiresOwnedObjects {
+public:
+ void run() {
+ char buf[] = {5, 0, 0, 0, 0};
+ BSONObj unowned(buf);
+ BSONObj owned = unowned.getOwned();
+
+ ASSERT(!unowned.isOwned());
+ ASSERT(owned.isOwned());
+
+ // Ensure that by default we can bind owned and unowned
+ {
+ unique_ptr<Scope> s(getGlobalScriptEngine()->newScope());
+ s->setObject("unowned", unowned, true);
+ s->setObject("owned", owned, true);
+ }
+
+ // After we set the flag, we should only be able to set owned
+ {
+ unique_ptr<Scope> s(getGlobalScriptEngine()->newScope());
+ s->requireOwnedObjects();
+ s->setObject("owned", owned, true);
+
+ bool threwException = false;
+ try {
+ s->setObject("unowned", unowned, true);
+ } catch (...) {
+ threwException = true;
+
+ auto status = exceptionToStatus();
+
+ ASSERT_EQUALS(status.code(), ErrorCodes::BadValue);
+ }
+
+ ASSERT(threwException);
+
+ // after resetting, we can set unowned's again
+ s->reset();
+ s->setObject("unowned", unowned, true);
+ }
+ }
+};
+
class All : public Suite {
public:
All() : Suite("js") {}
@@ -2444,6 +2487,7 @@ public:
add<RecursiveInvoke>();
add<ErrorCodeFromInvoke>();
+ add<RequiresOwnedObjects>();
add<RoundTripTests::DBRefTest>();
add<RoundTripTests::DBPointerTest>();
diff --git a/src/mongo/dbtests/rollbacktests.cpp b/src/mongo/dbtests/rollbacktests.cpp
index b842eb5a9bc..300d3f46aed 100644
--- a/src/mongo/dbtests/rollbacktests.cpp
+++ b/src/mongo/dbtests/rollbacktests.cpp
@@ -216,7 +216,7 @@ public:
}
};
-template <bool rollback, bool defaultIndexes>
+template <bool rollback, bool defaultIndexes, bool capped>
class RenameCollection {
public:
void run() {
@@ -236,7 +236,8 @@ public:
WriteUnitOfWork uow(&txn);
ASSERT(!collectionExists(&ctx, source.ns()));
ASSERT(!collectionExists(&ctx, target.ns()));
- ASSERT_OK(userCreateNS(&txn, ctx.db(), source.ns(), BSONObj(), defaultIndexes));
+ auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj();
+ ASSERT_OK(userCreateNS(&txn, ctx.db(), source.ns(), options, defaultIndexes));
uow.commit();
}
ASSERT(collectionExists(&ctx, source.ns()));
@@ -263,7 +264,7 @@ public:
}
};
-template <bool rollback, bool defaultIndexes>
+template <bool rollback, bool defaultIndexes, bool capped>
class RenameDropTargetCollection {
public:
void run() {
@@ -288,8 +289,9 @@ public:
WriteUnitOfWork uow(&txn);
ASSERT(!collectionExists(&ctx, source.ns()));
ASSERT(!collectionExists(&ctx, target.ns()));
- ASSERT_OK(userCreateNS(&txn, ctx.db(), source.ns(), BSONObj(), defaultIndexes));
- ASSERT_OK(userCreateNS(&txn, ctx.db(), target.ns(), BSONObj(), defaultIndexes));
+ auto options = capped ? BSON("capped" << true << "size" << 1000) : BSONObj();
+ ASSERT_OK(userCreateNS(&txn, ctx.db(), source.ns(), options, defaultIndexes));
+ ASSERT_OK(userCreateNS(&txn, ctx.db(), target.ns(), options, defaultIndexes));
insertRecord(&txn, source, sourceDoc);
insertRecord(&txn, target, targetDoc);
diff --git a/src/mongo/executor/connection_pool.cpp b/src/mongo/executor/connection_pool.cpp
index cf8aadd4d3a..9bbf528725e 100644
--- a/src/mongo/executor/connection_pool.cpp
+++ b/src/mongo/executor/connection_pool.cpp
@@ -60,6 +60,45 @@ namespace executor {
*/
class ConnectionPool::SpecificPool {
public:
+ /**
+ * These active client methods must be used whenever entering a specific pool outside of the
+ * shutdown background task. The presence of an active client will bump a counter on the
+ * specific pool which will prevent the shutdown thread from deleting it.
+ *
+ * The complexity comes from the need to hold a lock when writing to the
+ * _activeClients param on the specific pool. Because the code beneath the client needs to lock
+ * and unlock the parent mutex (and can leave unlocked), we want to start the client with the
+ * lock acquired, move it into the client, then re-acquire to decrement the counter on the way
+ * out.
+ *
+ * It's used like:
+ *
+ * pool.runWithActiveClient([](stdx::unique_lock<stdx::mutex> lk){ codeToBeProtected(); });
+ */
+ template <typename Callback>
+ void runWithActiveClient(Callback&& cb) {
+ runWithActiveClient(stdx::unique_lock<stdx::mutex>(_parent->_mutex),
+ std::forward<Callback>(cb));
+ }
+
+ template <typename Callback>
+ void runWithActiveClient(stdx::unique_lock<stdx::mutex> lk, Callback&& cb) {
+ invariant(lk.owns_lock());
+
+ _activeClients++;
+
+ const auto guard = MakeGuard([&] {
+ invariant(!lk.owns_lock());
+ stdx::lock_guard<stdx::mutex> lk(_parent->_mutex);
+ _activeClients--;
+ });
+
+ {
+ decltype(lk) localLk(std::move(lk));
+ cb(std::move(localLk));
+ }
+ }
+
SpecificPool(ConnectionPool* parent, const HostAndPort& hostAndPort);
~SpecificPool();
@@ -149,6 +188,7 @@ private:
std::unique_ptr<TimerInterface> _requestTimer;
Date_t _requestTimerExpiration;
+ size_t _activeClients;
size_t _generation;
bool _inFulfillRequests;
bool _inSpawnConnections;
@@ -206,8 +246,11 @@ void ConnectionPool::dropConnections(const HostAndPort& hostAndPort) {
if (iter == _pools.end())
return;
- iter->second.get()->processFailure(
- Status(ErrorCodes::PooledConnectionsDropped, "Pooled connections dropped"), std::move(lk));
+ iter->second->runWithActiveClient(std::move(lk), [&](decltype(lk) lk) {
+ iter->second->processFailure(
+ Status(ErrorCodes::PooledConnectionsDropped, "Pooled connections dropped"),
+ std::move(lk));
+ });
}
void ConnectionPool::get(const HostAndPort& hostAndPort,
@@ -229,7 +272,9 @@ void ConnectionPool::get(const HostAndPort& hostAndPort,
invariant(pool);
- pool->getConnection(hostAndPort, timeout, std::move(lk), std::move(cb));
+ pool->runWithActiveClient(std::move(lk), [&](decltype(lk) lk) {
+ pool->getConnection(hostAndPort, timeout, std::move(lk), std::move(cb));
+ });
}
void ConnectionPool::appendConnectionStats(ConnectionPoolStats* stats) const {
@@ -264,13 +309,16 @@ void ConnectionPool::returnConnection(ConnectionInterface* conn) {
invariant(iter != _pools.end());
- iter->second.get()->returnConnection(conn, std::move(lk));
+ iter->second->runWithActiveClient(std::move(lk), [&](decltype(lk) lk) {
+ iter->second->returnConnection(conn, std::move(lk));
+ });
}
ConnectionPool::SpecificPool::SpecificPool(ConnectionPool* parent, const HostAndPort& hostAndPort)
: _parent(parent),
_hostAndPort(hostAndPort),
_requestTimer(parent->_factory->makeTimer()),
+ _activeClients(0),
_generation(0),
_inFulfillRequests(false),
_inSpawnConnections(false),
@@ -362,47 +410,48 @@ void ConnectionPool::SpecificPool::returnConnection(ConnectionInterface* connPtr
// Unlock in case refresh can occur immediately
lk.unlock();
- connPtr->refresh(_parent->_options.refreshTimeout,
- [this](ConnectionInterface* connPtr, Status status) {
- connPtr->indicateUsed();
-
- stdx::unique_lock<stdx::mutex> lk(_parent->_mutex);
-
- auto conn = takeFromProcessingPool(connPtr);
-
- // If the host and port were dropped, let this lapse
- if (conn->getGeneration() != _generation) {
- spawnConnections(lk);
- return;
- }
-
- // If we're in shutdown, we don't need refreshed connections
- if (_state == State::kInShutdown)
- return;
-
- // If the connection refreshed successfully, throw it back in the ready
- // pool
- if (status.isOK()) {
- addToReady(lk, std::move(conn));
- spawnConnections(lk);
- return;
- }
-
- // If we've exceeded the time limit, start a new connect, rather than
- // failing all operations. We do this because the various callers have
- // their own time limit which is unrelated to our internal one.
- if (status.code() == ErrorCodes::NetworkInterfaceExceededTimeLimit) {
- log() << "Pending connection to host " << _hostAndPort
- << " did not complete within the connection timeout,"
- << " retrying with a new connection;" << openConnections(lk)
- << " connections to that host remain open";
- spawnConnections(lk);
- return;
- }
-
- // Otherwise pass the failure on through
- processFailure(status, std::move(lk));
- });
+ connPtr->refresh(
+ _parent->_options.refreshTimeout, [this](ConnectionInterface* connPtr, Status status) {
+ connPtr->indicateUsed();
+
+ runWithActiveClient([&](stdx::unique_lock<stdx::mutex> lk) {
+ auto conn = takeFromProcessingPool(connPtr);
+
+ // If the host and port were dropped, let this lapse
+ if (conn->getGeneration() != _generation) {
+ spawnConnections(lk);
+ return;
+ }
+
+ // If we're in shutdown, we don't need refreshed connections
+ if (_state == State::kInShutdown)
+ return;
+
+ // If the connection refreshed successfully, throw it back in
+ // the ready pool
+ if (status.isOK()) {
+ addToReady(lk, std::move(conn));
+ spawnConnections(lk);
+ return;
+ }
+
+ // If we've exceeded the time limit, start a new connect,
+ // rather than failing all operations. We do this because the
+ // various callers have their own time limit which is unrelated
+ // to our internal one.
+ if (status.code() == ErrorCodes::NetworkInterfaceExceededTimeLimit) {
+ log() << "Pending connection to host " << _hostAndPort
+ << " did not complete within the connection timeout,"
+ << " retrying with a new connection;" << openConnections(lk)
+ << " connections to that host remain open";
+ spawnConnections(lk);
+ return;
+ }
+
+ // Otherwise pass the failure on through
+ processFailure(status, std::move(lk));
+ });
+ });
lk.lock();
} else {
// If it's fine as it is, just put it in the ready queue
@@ -425,25 +474,25 @@ void ConnectionPool::SpecificPool::addToReady(stdx::unique_lock<stdx::mutex>& lk
connPtr->setTimeout(_parent->_options.refreshRequirement, [this, connPtr]() {
OwnedConnection conn;
- stdx::unique_lock<stdx::mutex> lk(_parent->_mutex);
-
- if (!_readyPool.count(connPtr)) {
- // We've already been checked out. We don't need to refresh
- // ourselves.
- return;
- }
+ runWithActiveClient([&](stdx::unique_lock<stdx::mutex> lk) {
+ if (!_readyPool.count(connPtr)) {
+ // We've already been checked out. We don't need to refresh
+ // ourselves.
+ return;
+ }
- conn = takeFromPool(_readyPool, connPtr);
+ conn = takeFromPool(_readyPool, connPtr);
- // If we're in shutdown, we don't need to refresh connections
- if (_state == State::kInShutdown)
- return;
+ // If we're in shutdown, we don't need to refresh connections
+ if (_state == State::kInShutdown)
+ return;
- _checkedOutPool[connPtr] = std::move(conn);
+ _checkedOutPool[connPtr] = std::move(conn);
- connPtr->indicateSuccess();
+ connPtr->indicateSuccess();
- returnConnection(connPtr, std::move(lk));
+ returnConnection(connPtr, std::move(lk));
+ });
});
fulfillRequests(lk);
@@ -586,26 +635,26 @@ void ConnectionPool::SpecificPool::spawnConnections(stdx::unique_lock<stdx::mute
_parent->_options.refreshTimeout, [this](ConnectionInterface* connPtr, Status status) {
connPtr->indicateUsed();
- stdx::unique_lock<stdx::mutex> lk(_parent->_mutex);
-
- auto conn = takeFromProcessingPool(connPtr);
-
- if (conn->getGeneration() != _generation) {
- // If the host and port was dropped, let the
- // connection lapse
- spawnConnections(lk);
- } else if (status.isOK()) {
- addToReady(lk, std::move(conn));
- spawnConnections(lk);
- } else if (status.code() == ErrorCodes::NetworkInterfaceExceededTimeLimit) {
- // If we've exceeded the time limit, restart the connect, rather than
- // failing all operations. We do this because the various callers
- // have their own time limit which is unrelated to our internal one.
- spawnConnections(lk);
- } else {
- // If the setup failed, cascade the failure edge
- processFailure(status, std::move(lk));
- }
+ runWithActiveClient([&](stdx::unique_lock<stdx::mutex> lk) {
+ auto conn = takeFromProcessingPool(connPtr);
+
+ if (conn->getGeneration() != _generation) {
+ // If the host and port was dropped, let the
+ // connection lapse
+ spawnConnections(lk);
+ } else if (status.isOK()) {
+ addToReady(lk, std::move(conn));
+ spawnConnections(lk);
+ } else if (status.code() == ErrorCodes::NetworkInterfaceExceededTimeLimit) {
+ // If we've exceeded the time limit, restart the connect, rather than
+ // failing all operations. We do this because the various callers
+ // have their own time limit which is unrelated to our internal one.
+ spawnConnections(lk);
+ } else {
+ // If the setup failed, cascade the failure edge
+ processFailure(status, std::move(lk));
+ }
+ });
});
// Note that this assumes that the refreshTimeout is sound for the
// setupTimeout
@@ -641,7 +690,7 @@ void ConnectionPool::SpecificPool::shutdown() {
// If we have processing connections, wait for them to finish or timeout
// before shutdown
- if (_processingPool.size() || _droppedProcessingPool.size()) {
+ if (_processingPool.size() || _droppedProcessingPool.size() || _activeClients) {
_requestTimer->setTimeout(Seconds(1), [this]() { shutdown(); });
return;
@@ -693,27 +742,27 @@ void ConnectionPool::SpecificPool::updateStateInLock() {
// We set a timer for the most recent request, then invoke each timed
// out request we couldn't service
_requestTimer->setTimeout(timeout, [this]() {
- stdx::unique_lock<stdx::mutex> lk(_parent->_mutex);
-
- auto now = _parent->_factory->now();
-
- while (_requests.size()) {
- auto& x = _requests.top();
-
- if (x.first <= now) {
- auto cb = std::move(x.second);
- _requests.pop();
-
- lk.unlock();
- cb(Status(ErrorCodes::NetworkInterfaceExceededTimeLimit,
- "Couldn't get a connection within the time limit"));
- lk.lock();
- } else {
- break;
+ runWithActiveClient([&](stdx::unique_lock<stdx::mutex> lk) {
+ auto now = _parent->_factory->now();
+
+ while (_requests.size()) {
+ auto& x = _requests.top();
+
+ if (x.first <= now) {
+ auto cb = std::move(x.second);
+ _requests.pop();
+
+ lk.unlock();
+ cb(Status(ErrorCodes::NetworkInterfaceExceededTimeLimit,
+ "Couldn't get a connection within the time limit"));
+ lk.lock();
+ } else {
+ break;
+ }
}
- }
- updateStateInLock();
+ updateStateInLock();
+ });
});
} else if (_checkedOutPool.size()) {
// If we have no requests, but someone's using a connection, we just
diff --git a/src/mongo/executor/network_interface_asio_auth.cpp b/src/mongo/executor/network_interface_asio_auth.cpp
index 1f2039dbd1e..571a1be1f81 100644
--- a/src/mongo/executor/network_interface_asio_auth.cpp
+++ b/src/mongo/executor/network_interface_asio_auth.cpp
@@ -38,6 +38,7 @@
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/auth/internal_user_auth.h"
#include "mongo/db/commands.h"
+#include "mongo/db/commands/feature_compatibility_version_command_parser.h"
#include "mongo/db/server_options.h"
#include "mongo/db/wire_version.h"
#include "mongo/rpc/factory.h"
diff --git a/src/mongo/executor/task_executor.h b/src/mongo/executor/task_executor.h
index 2d558512f91..ebf7c447ef0 100644
--- a/src/mongo/executor/task_executor.h
+++ b/src/mongo/executor/task_executor.h
@@ -196,6 +196,8 @@ public:
/**
* Schedules "work" to be run by the executor no sooner than "when".
*
+ * If "when" is <= now(), then it schedules the "work" to be run ASAP.
+ *
* Returns a handle for waiting on or canceling the callback, or
* ErrorCodes::ShutdownInProgress.
*
diff --git a/src/mongo/executor/task_executor_test_common.cpp b/src/mongo/executor/task_executor_test_common.cpp
index 57c12813250..d7a384231b8 100644
--- a/src/mongo/executor/task_executor_test_common.cpp
+++ b/src/mongo/executor/task_executor_test_common.cpp
@@ -328,14 +328,22 @@ COMMON_EXECUTOR_TEST(ScheduleWorkAt) {
Status status1 = getDetectableErrorStatus();
Status status2 = getDetectableErrorStatus();
Status status3 = getDetectableErrorStatus();
+ Status status4 = getDetectableErrorStatus();
+
const Date_t now = net->now();
const TaskExecutor::CallbackHandle cb1 = unittest::assertGet(executor.scheduleWorkAt(
now + Milliseconds(100), stdx::bind(setStatus, stdx::placeholders::_1, &status1)));
+ const TaskExecutor::CallbackHandle cb4 = unittest::assertGet(executor.scheduleWorkAt(
+ now - Milliseconds(50), stdx::bind(setStatus, stdx::placeholders::_1, &status4)));
unittest::assertGet(executor.scheduleWorkAt(
now + Milliseconds(5000), stdx::bind(setStatus, stdx::placeholders::_1, &status3)));
const TaskExecutor::CallbackHandle cb2 = unittest::assertGet(executor.scheduleWorkAt(
now + Milliseconds(200),
stdx::bind(setStatusAndShutdown, stdx::placeholders::_1, &status2)));
+
+ executor.wait(cb4);
+ ASSERT_OK(status4);
+
const Date_t startTime = net->now();
net->enterNetwork();
net->runUntil(startTime + Milliseconds(200));
diff --git a/src/mongo/rpc/get_status_from_command_result.cpp b/src/mongo/rpc/get_status_from_command_result.cpp
index 2b4adf94aae..13c176f44df 100644
--- a/src/mongo/rpc/get_status_from_command_result.cpp
+++ b/src/mongo/rpc/get_status_from_command_result.cpp
@@ -30,6 +30,7 @@
#include "mongo/rpc/get_status_from_command_result.h"
+#include "mongo/base/error_codes.h"
#include "mongo/base/status.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/jsobj.h"
@@ -40,6 +41,7 @@ namespace mongo {
namespace {
const std::string kCmdResponseWriteConcernField = "writeConcernError";
+const std::string kCmdResponseWriteErrorsField = "writeErrors";
} // namespace
Status getStatusFromCommandResult(const BSONObj& result) {
@@ -107,4 +109,45 @@ Status getWriteConcernStatusFromCommandResult(const BSONObj& obj) {
return wcError.toStatus();
}
+Status getFirstWriteErrorStatusFromCommandResult(const BSONObj& cmdResponse) {
+ BSONElement writeErrorElem;
+ auto status = bsonExtractTypedField(
+ cmdResponse, kCmdResponseWriteErrorsField, BSONType::Array, &writeErrorElem);
+ if (!status.isOK()) {
+ if (status == ErrorCodes::NoSuchKey) {
+ return Status::OK();
+ } else {
+ return status;
+ }
+ }
+
+ auto firstWriteErrorElem = writeErrorElem.Obj().firstElement();
+ if (!firstWriteErrorElem) {
+ return Status::OK();
+ }
+
+ if (firstWriteErrorElem.type() != BSONType::Object) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ str::stream() << "writeErrors should be an array of objects, found "
+ << typeName(firstWriteErrorElem.type()));
+ }
+
+ auto firstWriteErrorObj = firstWriteErrorElem.Obj();
+
+ return Status(ErrorCodes::fromInt(firstWriteErrorObj["code"].Int()),
+ firstWriteErrorObj["errmsg"].String());
+}
+
+Status getStatusFromWriteCommandReply(const BSONObj& cmdResponse) {
+ auto status = getStatusFromCommandResult(cmdResponse);
+ if (!status.isOK()) {
+ return status;
+ }
+ status = getFirstWriteErrorStatusFromCommandResult(cmdResponse);
+ if (!status.isOK()) {
+ return status;
+ }
+ return getWriteConcernStatusFromCommandResult(cmdResponse);
+}
+
} // namespace mongo
diff --git a/src/mongo/rpc/get_status_from_command_result.h b/src/mongo/rpc/get_status_from_command_result.h
index 971805ee088..423b656a0ed 100644
--- a/src/mongo/rpc/get_status_from_command_result.h
+++ b/src/mongo/rpc/get_status_from_command_result.h
@@ -51,4 +51,17 @@ Status getStatusFromCommandResult(const BSONObj& result);
*/
Status getWriteConcernStatusFromCommandResult(const BSONObj& cmdResponse);
+
+/**
+ * Extracts the first write error from a command response and converts it into a status. This
+ * ignores all errors after the first and does not preserve the write error index, so it should not
+ * be used with bulk writes.
+ */
+Status getFirstWriteErrorStatusFromCommandResult(const BSONObj& cmdResponse);
+
+/**
+ * Extracts any type of error from a write command response.
+ */
+Status getStatusFromWriteCommandReply(const BSONObj& cmdResponse);
+
} // namespace mongo
diff --git a/src/mongo/s/catalog/type_tags.cpp b/src/mongo/s/catalog/type_tags.cpp
index 59d28cc1cc4..b0c72053cca 100644
--- a/src/mongo/s/catalog/type_tags.cpp
+++ b/src/mongo/s/catalog/type_tags.cpp
@@ -34,6 +34,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/util/bson_extract.h"
+#include "mongo/s/catalog/type_chunk.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/mongoutils/str.h"
@@ -55,8 +56,9 @@ StatusWith<TagsType> TagsType::fromBSON(const BSONObj& source) {
{
std::string tagsNs;
Status status = bsonExtractStringField(source, ns.name(), &tagsNs);
- if (!status.isOK())
+ if (!status.isOK()) {
return status;
+ }
tags._ns = tagsNs;
}
@@ -64,28 +66,21 @@ StatusWith<TagsType> TagsType::fromBSON(const BSONObj& source) {
{
std::string tagsTag;
Status status = bsonExtractStringField(source, tag.name(), &tagsTag);
- if (!status.isOK())
+ if (!status.isOK()) {
return status;
+ }
tags._tag = tagsTag;
}
{
- BSONElement tagsMinKey;
- Status status = bsonExtractTypedField(source, min.name(), Object, &tagsMinKey);
- if (!status.isOK())
- return status;
-
- tags._minKey = tagsMinKey.Obj().getOwned();
- }
-
- {
- BSONElement tagsMaxKey;
- Status status = bsonExtractTypedField(source, max.name(), Object, &tagsMaxKey);
- if (!status.isOK())
- return status;
+ auto tagRangeStatus = ChunkRange::fromBSON(source);
+ if (!tagRangeStatus.isOK())
+ return tagRangeStatus.getStatus();
- tags._maxKey = tagsMaxKey.Obj().getOwned();
+ const auto tagRange = std::move(tagRangeStatus.getValue());
+ tags._minKey = tagRange.getMin().getOwned();
+ tags._maxKey = tagRange.getMax().getOwned();
}
return tags;
diff --git a/src/mongo/s/catalog/type_tags_test.cpp b/src/mongo/s/catalog/type_tags_test.cpp
index 78af4d5fe03..16a7c6dc2ee 100644
--- a/src/mongo/s/catalog/type_tags_test.cpp
+++ b/src/mongo/s/catalog/type_tags_test.cpp
@@ -117,9 +117,8 @@ TEST(TagsType, KeysNotAscending) {
BSON(TagsType::tag("tag") << TagsType::ns("test.mycol") << TagsType::min(BSON("a" << 20))
<< TagsType::max(BSON("a" << 10)));
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- const TagsType& tag = status.getValue();
- ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
+ StatusWith<TagsType> tagStatus = TagsType::fromBSON(obj);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, tagStatus.getStatus());
}
TEST(TagsType, BadType) {
diff --git a/src/mongo/s/client/shard_remote.cpp b/src/mongo/s/client/shard_remote.cpp
index 85e0bc7678b..1e0ea51ed4b 100644
--- a/src/mongo/s/client/shard_remote.cpp
+++ b/src/mongo/s/client/shard_remote.cpp
@@ -340,7 +340,8 @@ StatusWith<Shard::QueryResponse> ShardRemote::_exhaustiveFindOnConfig(
findCmdBuilder.done(),
fetcherCallback,
_appendMetadataForCommand(txn, readPrefWithMinOpTime),
- maxTimeMS);
+ maxTimeMS /* find network timeout */,
+ maxTimeMS /* getMore network timeout */);
Status scheduleStatus = fetcher.schedule();
if (!scheduleStatus.isOK()) {
return scheduleStatus;
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 800f8fe6005..b70eb831ac4 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -83,6 +83,7 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/db/commands/apply_ops_cmd_common',
'$BUILD_DIR/mongo/db/commands/killcursors_common',
+ '$BUILD_DIR/mongo/db/ftdc/ftdc_server',
'$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/views/views',
'$BUILD_DIR/mongo/rpc/client_metadata',
diff --git a/src/mongo/s/commands/cluster_ftdc_commands.cpp b/src/mongo/s/commands/cluster_ftdc_commands.cpp
index 23903e92984..12021e98f6d 100644
--- a/src/mongo/s/commands/cluster_ftdc_commands.cpp
+++ b/src/mongo/s/commands/cluster_ftdc_commands.cpp
@@ -67,6 +67,28 @@ public:
Status checkAuthForCommand(Client* client,
const std::string& dbname,
const BSONObj& cmdObj) override {
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::serverStatus)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::replSetGetStatus)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::connPoolStats)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString("local", "oplog.rs")),
+ ActionType::collStats)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
return Status::OK();
}
@@ -77,9 +99,10 @@ public:
std::string& errmsg,
BSONObjBuilder& result) override {
- errmsg = "getDiagnosticData not allowed through mongos";
+ result.append(
+ "data", FTDCController::get(txn->getServiceContext())->getMostRecentPeriodicDocument());
- return false;
+ return true;
}
};
diff --git a/src/mongo/s/commands/cluster_shard_collection_cmd.cpp b/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
index f1ba15396a9..eac3e2a24e0 100644
--- a/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
+++ b/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
@@ -52,6 +52,7 @@
#include "mongo/db/write_concern_options.h"
#include "mongo/s/balancer_configuration.h"
#include "mongo/s/catalog/sharding_catalog_client.h"
+#include "mongo/s/catalog/type_database.h"
#include "mongo/s/catalog_cache.h"
#include "mongo/s/client/shard_registry.h"
#include "mongo/s/commands/cluster_write.h"
@@ -186,12 +187,17 @@ public:
auto const shardRegistry = Grid::get(opCtx)->shardRegistry();
auto const catalogCache = Grid::get(opCtx)->catalogCache();
- auto dbInfo = uassertStatusOK(catalogCache->getDatabase(opCtx, nss.db()));
-
- // Ensure sharding is allowed on the database
- uassert(ErrorCodes::IllegalOperation,
- str::stream() << "sharding not enabled for db " << nss.db(),
- dbInfo.shardingEnabled());
+ // Ensure sharding is allowed on the database by reading directly from the config server,
+ // because reading through the cache might produce a stale entry if the "enableSharding" was
+ // called through a different mongos
+ {
+ const auto opTimeWithDbt =
+ uassertStatusOK(catalogClient->getDatabase(opCtx, nss.db().toString()));
+ const auto& dbt = opTimeWithDbt.value;
+ uassert(ErrorCodes::IllegalOperation,
+ str::stream() << "sharding not enabled for db " << nss.db(),
+ dbt.getSharded());
+ }
auto routingInfo = uassertStatusOK(catalogCache->getCollectionRoutingInfo(opCtx, nss));
diff --git a/src/mongo/s/commands/cluster_write.cpp b/src/mongo/s/commands/cluster_write.cpp
index b17035993d9..565a1ddc4d7 100644
--- a/src/mongo/s/commands/cluster_write.cpp
+++ b/src/mongo/s/commands/cluster_write.cpp
@@ -66,28 +66,6 @@ void toBatchError(const Status& status, BatchedCommandResponse* response) {
}
/**
- * Given a maxChunkSize configuration and the number of chunks in a particular sharded collection,
- * returns an optimal chunk size to use in order to achieve a good ratio between number of chunks
- * and their size.
- */
-uint64_t calculateDesiredChunkSize(uint64_t maxChunkSizeBytes, uint64_t numChunks) {
- // Splitting faster in early chunks helps spread out an initial load better
- const uint64_t minChunkSize = 1 << 20; // 1 MBytes
-
- if (numChunks <= 1) {
- return 1024;
- } else if (numChunks < 3) {
- return minChunkSize / 2;
- } else if (numChunks < 10) {
- return std::max(maxChunkSizeBytes / 4, minChunkSize);
- } else if (numChunks < 20) {
- return std::max(maxChunkSizeBytes / 2, minChunkSize);
- } else {
- return maxChunkSizeBytes;
- }
-}
-
-/**
* Returns the split point that will result in one of the chunk having exactly one document. Also
* returns an empty document if the split point cannot be determined.
*
@@ -341,8 +319,7 @@ void updateChunkWriteStatsAndSplitIfNeeded(OperationContext* opCtx,
const uint64_t chunkBytesWritten = chunk->addBytesWritten(dataWritten);
- const uint64_t desiredChunkSize =
- calculateDesiredChunkSize(balancerConfig->getMaxChunkSizeBytes(), manager->numChunks());
+ const uint64_t desiredChunkSize = balancerConfig->getMaxChunkSizeBytes();
// If this chunk is at either end of the range, trigger auto-split at 10% less data written in
// order to trigger the top-chunk optimization.
diff --git a/src/mongo/s/move_chunk_request.cpp b/src/mongo/s/move_chunk_request.cpp
index 4622f26fd29..104587ef751 100644
--- a/src/mongo/s/move_chunk_request.cpp
+++ b/src/mongo/s/move_chunk_request.cpp
@@ -195,7 +195,8 @@ bool MoveChunkRequest::operator==(const MoveChunkRequest& other) const {
return false;
if (_range != other._range)
return false;
-
+ if (_waitForDelete != other._waitForDelete)
+ return false;
return true;
}
diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp
index 9177cccdb73..0b8a9a17651 100644
--- a/src/mongo/s/server.cpp
+++ b/src/mongo/s/server.cpp
@@ -51,6 +51,7 @@
#include "mongo/db/auth/user_cache_invalidator_job.h"
#include "mongo/db/client.h"
#include "mongo/db/dbwebserver.h"
+#include "mongo/db/ftdc/ftdc_mongos.h"
#include "mongo/db/initialize_server_global_state.h"
#include "mongo/db/instance.h"
#include "mongo/db/lasterror.h"
@@ -152,6 +153,9 @@ static void cleanupTask() {
if (auto catalog = Grid::get(txn)->catalogClient(txn)) {
catalog->shutDown(txn);
}
+
+ // Shutdown Full-Time Data Capture
+ stopMongoSFTDC();
}
audit::logShutdown(Client::getCurrent());
@@ -293,6 +297,8 @@ static ExitCode runMongosServer() {
web.detach();
}
+ startMongoSFTDC();
+
Status status = getGlobalAuthorizationManager()->initialize(NULL);
if (!status.isOK()) {
error() << "Initializing authorization data failed: " << status;
diff --git a/src/mongo/s/sharding_initialization.cpp b/src/mongo/s/sharding_initialization.cpp
index 2bf84b02d18..1c28829a696 100644
--- a/src/mongo/s/sharding_initialization.cpp
+++ b/src/mongo/s/sharding_initialization.cpp
@@ -182,6 +182,30 @@ Status initializeGlobalShardingState(OperationContext* txn,
connPoolOptions.refreshRequirement = Milliseconds(ShardingTaskExecutorPoolRefreshRequirementMS);
connPoolOptions.refreshTimeout = Milliseconds(ShardingTaskExecutorPoolRefreshTimeoutMS);
+ if (connPoolOptions.refreshRequirement <= connPoolOptions.refreshTimeout) {
+ auto newRefreshTimeout = connPoolOptions.refreshRequirement - Milliseconds(1);
+ warning() << "ShardingTaskExecutorPoolRefreshRequirementMS ("
+ << connPoolOptions.refreshRequirement
+ << ") set below ShardingTaskExecutorPoolRefreshTimeoutMS ("
+ << connPoolOptions.refreshTimeout
+ << "). Adjusting ShardingTaskExecutorPoolRefreshTimeoutMS to "
+ << newRefreshTimeout;
+ connPoolOptions.refreshTimeout = newRefreshTimeout;
+ }
+
+ if (connPoolOptions.hostTimeout <=
+ connPoolOptions.refreshRequirement + connPoolOptions.refreshTimeout) {
+ auto newHostTimeout =
+ connPoolOptions.refreshRequirement + connPoolOptions.refreshTimeout + Milliseconds(1);
+ warning() << "ShardingTaskExecutorPoolHostTimeoutMS (" << connPoolOptions.hostTimeout
+ << ") set below ShardingTaskExecutorPoolRefreshRequirementMS ("
+ << connPoolOptions.refreshRequirement
+ << ") + ShardingTaskExecutorPoolRefreshTimeoutMS ("
+ << connPoolOptions.refreshTimeout
+ << "). Adjusting ShardingTaskExecutorPoolHostTimeoutMS to " << newHostTimeout;
+ connPoolOptions.hostTimeout = newHostTimeout;
+ }
+
auto network =
executor::makeNetworkInterface("NetworkInterfaceASIO-ShardRegistry",
stdx::make_unique<ShardingNetworkConnectionHook>(),
diff --git a/src/mongo/scripting/deadline_monitor.cpp b/src/mongo/scripting/deadline_monitor.cpp
index f75c34a0cc2..5eb0f52e5de 100644
--- a/src/mongo/scripting/deadline_monitor.cpp
+++ b/src/mongo/scripting/deadline_monitor.cpp
@@ -34,7 +34,7 @@
namespace mongo {
-MONGO_EXPORT_SERVER_PARAMETER(scriptingEngineInterruptIntervalMS, int, 0);
+MONGO_EXPORT_SERVER_PARAMETER(scriptingEngineInterruptIntervalMS, int, 1000);
int getScriptingEngineInterruptInterval() {
return scriptingEngineInterruptIntervalMS.load();
diff --git a/src/mongo/scripting/deadline_monitor.h b/src/mongo/scripting/deadline_monitor.h
index b3c5007c72d..9081d7366e6 100644
--- a/src/mongo/scripting/deadline_monitor.h
+++ b/src/mongo/scripting/deadline_monitor.h
@@ -141,10 +141,10 @@ private:
const Date_t now = Date_t::now();
const auto interruptInterval = Milliseconds{getScriptingEngineInterruptInterval()};
- if ((interruptInterval.count() > 0) && (now - lastInterruptCycle > interruptInterval)) {
+ if (now - lastInterruptCycle > interruptInterval) {
for (const auto& task : _tasks) {
- if (task.second > now)
- task.first->interrupt();
+ if (task.first->isKillPending())
+ task.first->kill();
}
lastInterruptCycle = now;
}
diff --git a/src/mongo/scripting/deadline_monitor_test.cpp b/src/mongo/scripting/deadline_monitor_test.cpp
index 71daefbce8f..d802673bd3d 100644
--- a/src/mongo/scripting/deadline_monitor_test.cpp
+++ b/src/mongo/scripting/deadline_monitor_test.cpp
@@ -73,8 +73,12 @@ public:
_group->noteKill();
}
void interrupt() {}
+ const bool isKillPending() {
+ return killPending;
+ }
TaskGroup* _group;
uint64_t _killed;
+ bool killPending = false;
};
// single task expires before stopping the deadline
@@ -175,4 +179,13 @@ TEST(DeadlineMonitor, MultipleTasksExpireOrComplete) {
}
}
+TEST(DeadlineMonitor, IsKillPendingKills) {
+ DeadlineMonitor<Task> dm;
+ TaskGroup group;
+ Task task(&group);
+ dm.startDeadline(&task, -1);
+ task.killPending = true;
+ group.waitForKillCount(1);
+ ASSERT(task._killed);
+}
} // namespace mongo
diff --git a/src/mongo/scripting/engine.cpp b/src/mongo/scripting/engine.cpp
index 3e9a5c542cf..1848bccfba2 100644
--- a/src/mongo/scripting/engine.cpp
+++ b/src/mongo/scripting/engine.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/service_context.h"
#include "mongo/platform/unordered_set.h"
#include "mongo/scripting/dbdirectclient_factory.h"
+#include "mongo/util/fail_point_service.h"
#include "mongo/util/file.h"
#include "mongo/util/log.h"
#include "mongo/util/text.h"
@@ -55,7 +56,10 @@ using std::unique_ptr;
AtomicInt64 Scope::_lastVersion(1);
+
namespace {
+
+MONGO_FP_DECLARE(mr_killop_test_fp);
// 2 GB is the largest support Javascript file size.
const fileofs kMaxJsFileLength = fileofs(2) * 1024 * 1024 * 1024;
@@ -231,6 +235,15 @@ void Scope::loadStored(OperationContext* txn, bool ignoreNotConnected) {
uassert(10209, str::stream() << "name has to be a string: " << n, n.type() == String);
uassert(10210, "value has to be set", v.type() != EOO);
+ if (MONGO_FAIL_POINT(mr_killop_test_fp)) {
+
+ /* This thread sleep makes the interrupts in the test come in at a time
+ * where the js misses the interrupt and throw an exception instead of
+ * being interrupted
+ */
+ stdx::this_thread::sleep_for(stdx::chrono::seconds(1));
+ }
+
try {
setElement(n.valuestr(), v, o);
thisTime.insert(n.valuestr());
@@ -427,6 +440,9 @@ public:
void advanceGeneration() {
_real->advanceGeneration();
}
+ void requireOwnedObjects() override {
+ _real->requireOwnedObjects();
+ }
bool isKillPending() const {
return _real->isKillPending();
}
diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h
index 2b2ab0ff14d..e773dec5f60 100644
--- a/src/mongo/scripting/engine.h
+++ b/src/mongo/scripting/engine.h
@@ -44,7 +44,7 @@ class OperationContext;
struct JSFile {
const char* name;
- const StringData& source;
+ const StringData source;
};
class Scope {
@@ -103,6 +103,8 @@ public:
virtual void advanceGeneration() = 0;
+ virtual void requireOwnedObjects() = 0;
+
virtual ScriptingFunction createFunction(const char* code);
/**
diff --git a/src/mongo/scripting/mozjs/PosixNSPR.cpp b/src/mongo/scripting/mozjs/PosixNSPR.cpp
index ed1a3d5a49d..9054e930443 100644
--- a/src/mongo/scripting/mozjs/PosixNSPR.cpp
+++ b/src/mongo/scripting/mozjs/PosixNSPR.cpp
@@ -24,6 +24,7 @@
#include "mongo/stdx/chrono.h"
#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/memory.h"
#include "mongo/stdx/mutex.h"
#include "mongo/stdx/thread.h"
#include "mongo/util/concurrency/thread_name.h"
@@ -98,9 +99,13 @@ PRThread* PR_CreateThread(PRThreadType type,
MOZ_ASSERT(priority == PR_PRIORITY_NORMAL);
try {
- std::unique_ptr<nspr::Thread, void (*)(nspr::Thread*)> t(
- js_new<nspr::Thread>(start, arg, state != PR_UNJOINABLE_THREAD),
- js_delete_nonconst<nspr::Thread>);
+ // We can't use the nspr allocator to allocate this thread, because under asan we
+ // instrument the allocator so that asan can track the pointers correctly. This
+ // instrumentation
+ // requires that pointers be deleted in the same thread that they were allocated in.
+ // The threads created in PR_CreateThread are not always freed in the same thread
+ // that they were created in. So, we use the standard allocator here.
+ auto t = mongo::stdx::make_unique<nspr::Thread>(start, arg, state != PR_UNJOINABLE_THREAD);
t->thread() = mongo::stdx::thread(&nspr::Thread::ThreadRoutine, t.get());
@@ -118,7 +123,7 @@ PRStatus PR_JoinThread(PRThread* thread) {
try {
thread->thread().join();
- js_delete(thread);
+ delete thread;
return PR_SUCCESS;
} catch (...) {
diff --git a/src/mongo/scripting/mozjs/bson.cpp b/src/mongo/scripting/mozjs/bson.cpp
index 5a2ebd0dfed..a2881c44664 100644
--- a/src/mongo/scripting/mozjs/bson.cpp
+++ b/src/mongo/scripting/mozjs/bson.cpp
@@ -59,13 +59,17 @@ namespace {
* the appearance of mutable state on the read/write versions.
*/
struct BSONHolder {
- BSONHolder(const BSONObj& obj, const BSONObj* parent, std::size_t generation, bool ro)
+ BSONHolder(const BSONObj& obj, const BSONObj* parent, const MozJSImplScope* scope, bool ro)
: _obj(obj),
- _generation(generation),
+ _generation(scope->getGeneration()),
_isOwned(obj.isOwned() || (parent && parent->isOwned())),
_resolved(false),
_readOnly(ro),
_altered(false) {
+ uassert(
+ ErrorCodes::BadValue,
+ "Attempt to bind an unowned BSON Object to a JS scope marked as requiring ownership",
+ _isOwned || (!scope->requiresOwnedObjects()));
if (parent) {
_parent.emplace(*parent);
}
@@ -107,7 +111,7 @@ void BSONInfo::make(
auto scope = getScope(cx);
scope->getProto<BSONInfo>().newObject(obj);
- JS_SetPrivate(obj, scope->trackedNew<BSONHolder>(bson, parent, scope->getGeneration(), ro));
+ JS_SetPrivate(obj, scope->trackedNew<BSONHolder>(bson, parent, scope, ro));
}
void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) {
diff --git a/src/mongo/scripting/mozjs/db.cpp b/src/mongo/scripting/mozjs/db.cpp
index 7fa2f179241..7aa73337237 100644
--- a/src/mongo/scripting/mozjs/db.cpp
+++ b/src/mongo/scripting/mozjs/db.cpp
@@ -45,38 +45,17 @@ namespace mozjs {
const char* const DBInfo::className = "DB";
-void DBInfo::getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp) {
- // 2nd look into real values, may be cached collection object
- if (!vp.isUndefined()) {
- auto scope = getScope(cx);
- auto opContext = scope->getOpContext();
-
- if (opContext && vp.isObject()) {
- ObjectWrapper o(cx, vp);
-
- if (o.hasOwnField(InternedString::_fullName)) {
- // need to check every time that the collection did not get sharded
- if (haveLocalShardingInfo(opContext, o.getString(InternedString::_fullName)))
- uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval");
- }
- }
+void DBInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
+ *resolvedp = false;
- return;
- }
+ JS::RootedValue coll(cx);
JS::RootedObject parent(cx);
if (!JS_GetPrototype(cx, obj, &parent))
uasserted(ErrorCodes::JSInterpreterFailure, "Couldn't get prototype");
ObjectWrapper parentWrapper(cx, parent);
-
- if (parentWrapper.hasOwnField(id)) {
- parentWrapper.getValue(id, vp);
- return;
- }
+ ObjectWrapper o(cx, obj);
IdWrapper idw(cx, id);
@@ -87,21 +66,39 @@ void DBInfo::getProperty(JSContext* cx,
if (sname.size() == 0 || sname[0] == '_') {
return;
}
+
+ // SpiderMonkey will call resolve even for __proto__ so acknowledge it exists.
+ if (sname == "__proto__"_sd) {
+ *resolvedp = true;
+ return;
+ }
+ }
+
+ // Check if this exists on the parent, ie. DBCollection::resolve case
+ if (parentWrapper.hasOwnField(id)) {
+ parentWrapper.getValue(id, &coll);
+
+ o.defineProperty(id, coll, 0);
+
+ *resolvedp = true;
+ return;
}
// no hit, create new collection
JS::RootedValue getCollection(cx);
parentWrapper.getValue(InternedString::getCollection, &getCollection);
+ // Check if getCollection has been installed yet
+ // It is undefined if the user has a db name the same as one of methods/properties of the DB
+ // object.
if (!(getCollection.isObject() && JS_ObjectIsFunction(cx, getCollection.toObjectOrNull()))) {
- uasserted(ErrorCodes::BadValue, "getCollection is not a function");
+ return;
}
JS::AutoValueArray<1> args(cx);
idw.toValue(args[0]);
- JS::RootedValue coll(cx);
ObjectWrapper(cx, obj).callMethod(getCollection, args, &coll);
uassert(16861,
@@ -111,7 +108,7 @@ void DBInfo::getProperty(JSContext* cx,
// cache collection for reuse, don't enumerate
ObjectWrapper(cx, obj).defineProperty(id, coll, 0);
- vp.set(coll);
+ *resolvedp = true;
}
void DBInfo::construct(JSContext* cx, JS::CallArgs args) {
diff --git a/src/mongo/scripting/mozjs/db.h b/src/mongo/scripting/mozjs/db.h
index c752953236f..8f080265ef0 100644
--- a/src/mongo/scripting/mozjs/db.h
+++ b/src/mongo/scripting/mozjs/db.h
@@ -44,10 +44,7 @@ namespace mozjs {
*/
struct DBInfo : public BaseInfo {
static void construct(JSContext* cx, JS::CallArgs args);
- static void getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp);
+ static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp);
static const char* const className;
};
diff --git a/src/mongo/scripting/mozjs/dbcollection.cpp b/src/mongo/scripting/mozjs/dbcollection.cpp
index e0f903dd1fc..b1983bd367c 100644
--- a/src/mongo/scripting/mozjs/dbcollection.cpp
+++ b/src/mongo/scripting/mozjs/dbcollection.cpp
@@ -44,11 +44,11 @@ namespace mozjs {
const char* const DBCollectionInfo::className = "DBCollection";
-void DBCollectionInfo::getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp) {
- DBInfo::getProperty(cx, obj, id, vp);
+void DBCollectionInfo::resolve(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ bool* resolvedp) {
+ DBInfo::resolve(cx, obj, id, resolvedp);
}
void DBCollectionInfo::construct(JSContext* cx, JS::CallArgs args) {
diff --git a/src/mongo/scripting/mozjs/dbcollection.h b/src/mongo/scripting/mozjs/dbcollection.h
index 34be422dfd4..ed3c39ff0f2 100644
--- a/src/mongo/scripting/mozjs/dbcollection.h
+++ b/src/mongo/scripting/mozjs/dbcollection.h
@@ -44,10 +44,7 @@ namespace mozjs {
*/
struct DBCollectionInfo : public BaseInfo {
static void construct(JSContext* cx, JS::CallArgs args);
- static void getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp);
+ static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp);
static const char* const className;
};
diff --git a/src/mongo/scripting/mozjs/dbquery.cpp b/src/mongo/scripting/mozjs/dbquery.cpp
index c2543814505..15bd45bde3c 100644
--- a/src/mongo/scripting/mozjs/dbquery.cpp
+++ b/src/mongo/scripting/mozjs/dbquery.cpp
@@ -105,13 +105,8 @@ void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) {
args.rval().setObjectOrNull(thisv);
}
-void DBQueryInfo::getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp) {
- if (!vp.isUndefined()) {
- return;
- }
+void DBQueryInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
+ *resolvedp = false;
IdWrapper wid(cx, id);
@@ -134,9 +129,19 @@ void DBQueryInfo::getProperty(JSContext* cx,
args[0].setInt32(wid.toInt32());
- ObjectWrapper(cx, obj).callMethod(arrayAccess, args, vp);
- } else {
- uasserted(ErrorCodes::BadValue, "arrayAccess is not a function");
+ JS::RootedValue vp(cx);
+
+ ObjectWrapper(cx, obj).callMethod(arrayAccess, args, &vp);
+
+ if (!vp.isNullOrUndefined()) {
+ ObjectWrapper o(cx, obj);
+
+ // Assumes the user won't modify the contents of what DBQuery::arrayAccess returns
+ // otherwise we need to install a getter.
+ o.defineProperty(id, vp, 0);
+ }
+
+ *resolvedp = true;
}
}
diff --git a/src/mongo/scripting/mozjs/dbquery.h b/src/mongo/scripting/mozjs/dbquery.h
index dc844c3084c..590185c55ee 100644
--- a/src/mongo/scripting/mozjs/dbquery.h
+++ b/src/mongo/scripting/mozjs/dbquery.h
@@ -41,10 +41,8 @@ namespace mozjs {
*/
struct DBQueryInfo : public BaseInfo {
static void construct(JSContext* cx, JS::CallArgs args);
- static void getProperty(JSContext* cx,
- JS::HandleObject obj,
- JS::HandleId id,
- JS::MutableHandleValue vp);
+ static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp);
+
static const char* const className;
};
diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp
index c37ebc0cb2a..c31d5b940a4 100644
--- a/src/mongo/scripting/mozjs/implscope.cpp
+++ b/src/mongo/scripting/mozjs/implscope.cpp
@@ -258,7 +258,6 @@ MozJSImplScope::ASANHandles::ASANHandles() {
}
MozJSImplScope::ASANHandles::~ASANHandles() {
- invariant(_handles.empty());
invariant(kCurrentASANHandles == this);
kCurrentASANHandles = nullptr;
}
@@ -342,6 +341,14 @@ MozJSImplScope::MozRuntime::MozRuntime(const MozJSScriptEngine* engine) {
.setIon(true)
.setAsyncStack(false)
.setNativeRegExp(true);
+ } else {
+ JS::RuntimeOptionsRef(_runtime.get())
+ .setAsmJS(false)
+ .setThrowOnAsmJSValidationFailure(false)
+ .setBaseline(false)
+ .setIon(false)
+ .setAsyncStack(false)
+ .setNativeRegExp(false);
}
const StackLocator locator;
@@ -403,6 +410,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine)
_connectState(ConnectState::Not),
_status(Status::OK()),
_generation(0),
+ _requireOwnedObjects(false),
_hasOutOfMemoryException(false),
_binDataProto(_context),
_bsonProto(_context),
@@ -491,82 +499,81 @@ void MozJSImplScope::init(const BSONObj* data) {
}
}
-void MozJSImplScope::setNumber(const char* field, double val) {
- MozJSEntry entry(this);
+template <typename ImplScopeFunction>
+auto MozJSImplScope::_runSafely(ImplScopeFunction&& functionToRun) -> decltype(functionToRun()) {
+ try {
+ MozJSEntry entry(this);
+ return functionToRun();
+ } catch (...) {
+ _error = _status.reason();
- ObjectWrapper(_context, _global).setNumber(field, val);
+ // Clear the status state
+ auto status = std::move(_status);
+ uassertStatusOK(status);
+ throw;
+ }
}
-void MozJSImplScope::setString(const char* field, StringData val) {
- MozJSEntry entry(this);
+void MozJSImplScope::setNumber(const char* field, double val) {
+ _runSafely([this, &field, &val] { ObjectWrapper(_context, _global).setNumber(field, val); });
+}
- ObjectWrapper(_context, _global).setString(field, val);
+void MozJSImplScope::setString(const char* field, StringData val) {
+ _runSafely([this, &field, &val] { ObjectWrapper(_context, _global).setString(field, val); });
}
void MozJSImplScope::setBoolean(const char* field, bool val) {
- MozJSEntry entry(this);
-
- ObjectWrapper(_context, _global).setBoolean(field, val);
+ _runSafely([this, &field, &val] { ObjectWrapper(_context, _global).setBoolean(field, val); });
}
void MozJSImplScope::setElement(const char* field, const BSONElement& e, const BSONObj& parent) {
- MozJSEntry entry(this);
+ _runSafely([this, &field, &e, &parent] {
- ObjectWrapper(_context, _global).setBSONElement(field, e, parent, false);
+ ObjectWrapper(_context, _global).setBSONElement(field, e, parent, false);
+ });
}
void MozJSImplScope::setObject(const char* field, const BSONObj& obj, bool readOnly) {
- MozJSEntry entry(this);
+ _runSafely([this, &field, &obj, &readOnly] {
- ObjectWrapper(_context, _global).setBSON(field, obj, readOnly);
+ ObjectWrapper(_context, _global).setBSON(field, obj, readOnly);
+ });
}
int MozJSImplScope::type(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).type(field);
+ return _runSafely([this, &field] { return ObjectWrapper(_context, _global).type(field); });
}
double MozJSImplScope::getNumber(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getNumber(field);
+ return _runSafely([this, &field] { return ObjectWrapper(_context, _global).getNumber(field); });
}
int MozJSImplScope::getNumberInt(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getNumberInt(field);
+ return _runSafely(
+ [this, &field] { return ObjectWrapper(_context, _global).getNumberInt(field); });
}
long long MozJSImplScope::getNumberLongLong(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getNumberLongLong(field);
+ return _runSafely(
+ [this, &field] { return ObjectWrapper(_context, _global).getNumberLongLong(field); });
}
Decimal128 MozJSImplScope::getNumberDecimal(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getNumberDecimal(field);
+ return _runSafely(
+ [this, &field] { return ObjectWrapper(_context, _global).getNumberDecimal(field); });
}
std::string MozJSImplScope::getString(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getString(field);
+ return _runSafely([this, &field] { return ObjectWrapper(_context, _global).getString(field); });
}
bool MozJSImplScope::getBoolean(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getBoolean(field);
+ return _runSafely(
+ [this, &field] { return ObjectWrapper(_context, _global).getBoolean(field); });
}
BSONObj MozJSImplScope::getObject(const char* field) {
- MozJSEntry entry(this);
-
- return ObjectWrapper(_context, _global).getObject(field);
+ return _runSafely([this, &field] { return ObjectWrapper(_context, _global).getObject(field); });
}
void MozJSImplScope::newFunction(StringData raw, JS::MutableHandleValue out) {
@@ -642,17 +649,15 @@ ScriptingFunction MozJSImplScope::_createFunction(const char* raw) {
}
void MozJSImplScope::setFunction(const char* field, const char* code) {
- MozJSEntry entry(this);
-
- JS::RootedValue fun(_context);
- _MozJSCreateFunction(code, &fun);
- ObjectWrapper(_context, _global).setValue(field, fun);
+ _runSafely([this, &field, &code] {
+ JS::RootedValue fun(_context);
+ _MozJSCreateFunction(code, &fun);
+ ObjectWrapper(_context, _global).setValue(field, fun);
+ });
}
void MozJSImplScope::rename(const char* from, const char* to) {
- MozJSEntry entry(this);
-
- ObjectWrapper(_context, _global).rename(from, to);
+ _runSafely([this, &from, &to] { ObjectWrapper(_context, _global).rename(from, to); });
}
int MozJSImplScope::invoke(ScriptingFunction func,
@@ -764,15 +769,15 @@ bool MozJSImplScope::exec(StringData code,
}
void MozJSImplScope::injectNative(const char* field, NativeFunction func, void* data) {
- MozJSEntry entry(this);
-
- JS::RootedObject obj(_context);
+ _runSafely([this, &field, &func, &data] {
+ JS::RootedObject obj(_context);
- NativeFunctionInfo::make(_context, &obj, func, data);
+ NativeFunctionInfo::make(_context, &obj, func, data);
- JS::RootedValue value(_context);
- value.setObjectOrNull(obj);
- ObjectWrapper(_context, _global).setValue(field, value);
+ JS::RootedValue value(_context);
+ value.setObjectOrNull(obj);
+ ObjectWrapper(_context, _global).setValue(field, value);
+ });
}
void MozJSImplScope::gc() {
@@ -781,63 +786,65 @@ void MozJSImplScope::gc() {
}
void MozJSImplScope::localConnectForDbEval(OperationContext* txn, const char* dbName) {
- MozJSEntry entry(this);
-
- if (_connectState == ConnectState::External)
- uasserted(12510, "externalSetup already called, can't call localConnect");
- if (_connectState == ConnectState::Local) {
- if (_localDBName == dbName)
- return;
- uasserted(12511,
- str::stream() << "localConnect previously called with name " << _localDBName);
- }
+ _runSafely([this, &txn, &dbName] {
+ if (_connectState == ConnectState::External)
+ uasserted(12510, "externalSetup already called, can't call localConnect");
+ if (_connectState == ConnectState::Local) {
+ if (_localDBName == dbName)
+ return;
+ uasserted(12511,
+ str::stream() << "localConnect previously called with name " << _localDBName);
+ }
- // NOTE: order is important here. the following methods must be called after
- // the above conditional statements.
+ // NOTE: order is important here. the following methods must be called after
+ // the above conditional statements.
- _connectState = ConnectState::Local;
- _localDBName = dbName;
+ _connectState = ConnectState::Local;
+ _localDBName = dbName;
- loadStored(txn);
+ loadStored(txn);
- // install db access functions in the global object
- installDBAccess();
+ // install db access functions in the global object
+ installDBAccess();
- // install the Mongo function object and instantiate the 'db' global
- _mongoLocalProto.install(_global);
- execCoreFiles();
+ // install the Mongo function object and instantiate the 'db' global
+ _mongoLocalProto.install(_global);
+ execCoreFiles();
- const char* const makeMongo = "const _mongo = new Mongo()";
- exec(makeMongo, "local connect 2", false, true, true, 0);
+ const char* const makeMongo = "const _mongo = new Mongo()";
+ exec(makeMongo, "local connect 2", false, true, true, 0);
- std::string makeDB = str::stream() << "const db = _mongo.getDB(\"" << dbName << "\");";
- exec(makeDB, "local connect 3", false, true, true, 0);
+ std::string makeDB = str::stream() << "const db = _mongo.getDB(\"" << dbName << "\");";
+ exec(makeDB, "local connect 3", false, true, true, 0);
+ });
}
void MozJSImplScope::externalSetup() {
- MozJSEntry entry(this);
- if (_connectState == ConnectState::External)
- return;
- if (_connectState == ConnectState::Local)
- uasserted(12512, "localConnect already called, can't call externalSetup");
+ _runSafely([&] {
+ if (_connectState == ConnectState::External)
+ return;
+ if (_connectState == ConnectState::Local)
+ uasserted(12512, "localConnect already called, can't call externalSetup");
- // install db access functions in the global object
- installDBAccess();
+ // install db access functions in the global object
+ installDBAccess();
- // install thread-related functions (e.g. _threadInject)
- installFork();
+ // install thread-related functions (e.g. _threadInject)
+ installFork();
- // install the Mongo function object
- _mongoExternalProto.install(_global);
- execCoreFiles();
- _connectState = ConnectState::External;
+ // install the Mongo function object
+ _mongoExternalProto.install(_global);
+ execCoreFiles();
+ _connectState = ConnectState::External;
+ });
}
void MozJSImplScope::reset() {
unregisterOperation();
_pendingKill.store(false);
_pendingGC.store(false);
+ _requireOwnedObjects = false;
advanceGeneration();
}
@@ -951,6 +958,14 @@ void MozJSImplScope::advanceGeneration() {
_generation++;
}
+void MozJSImplScope::requireOwnedObjects() {
+ _requireOwnedObjects = true;
+}
+
+bool MozJSImplScope::requiresOwnedObjects() const {
+ return _requireOwnedObjects;
+}
+
const std::string& MozJSImplScope::getParentStack() const {
return _parentStack;
}
diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h
index df37553e181..2de309e87ab 100644
--- a/src/mongo/scripting/mozjs/implscope.h
+++ b/src/mongo/scripting/mozjs/implscope.h
@@ -309,6 +309,10 @@ public:
void advanceGeneration() override;
+ void requireOwnedObjects() override;
+
+ bool requiresOwnedObjects() const;
+
JS::HandleId getInternedStringId(InternedString name) {
return _internedStrings.getInternedString(name);
}
@@ -341,6 +345,9 @@ public:
};
private:
+ template <typename ImplScopeFunction>
+ auto _runSafely(ImplScopeFunction&& functionToRun) -> decltype(functionToRun());
+
void _MozJSCreateFunction(StringData raw, JS::MutableHandleValue fun);
/**
@@ -402,6 +409,7 @@ private:
Status _status;
std::string _parentStack;
std::size_t _generation;
+ bool _requireOwnedObjects;
bool _hasOutOfMemoryException;
WrapType<BinDataInfo> _binDataProto;
diff --git a/src/mongo/scripting/mozjs/mongohelpers.js b/src/mongo/scripting/mozjs/mongohelpers.js
index b0b35bb2fe8..d3f743623a3 100644
--- a/src/mongo/scripting/mozjs/mongohelpers.js
+++ b/src/mongo/scripting/mozjs/mongohelpers.js
@@ -33,6 +33,14 @@
exportToMongoHelpers = {
// This function accepts an expression or function body and returns a function definition
'functionExpressionParser': function functionExpressionParser(fnSrc) {
+
+ // Ensure that a provided expression or function body is not terminated with a ';'.
+ // This ensures we interpret the input as a single expression, rather than a sequence
+ // of expressions, and can wrap it in parentheses.
+ while (fnSrc.endsWith(";") || fnSrc != fnSrc.trimRight()) {
+ fnSrc = fnSrc.slice(0, -1).trimRight();
+ }
+
var parseTree;
try {
parseTree = this.Reflect.parse(fnSrc);
diff --git a/src/mongo/scripting/mozjs/proxyscope.cpp b/src/mongo/scripting/mozjs/proxyscope.cpp
index 6a6d20f4448..05697468f07 100644
--- a/src/mongo/scripting/mozjs/proxyscope.cpp
+++ b/src/mongo/scripting/mozjs/proxyscope.cpp
@@ -122,6 +122,10 @@ void MozJSProxyScope::advanceGeneration() {
run([&] { _implScope->advanceGeneration(); });
}
+void MozJSProxyScope::requireOwnedObjects() {
+ run([&] { _implScope->requireOwnedObjects(); });
+}
+
double MozJSProxyScope::getNumber(const char* field) {
double out;
run([&] { out = _implScope->getNumber(field); });
diff --git a/src/mongo/scripting/mozjs/proxyscope.h b/src/mongo/scripting/mozjs/proxyscope.h
index 451981330a1..4dd69a3ebe9 100644
--- a/src/mongo/scripting/mozjs/proxyscope.h
+++ b/src/mongo/scripting/mozjs/proxyscope.h
@@ -129,6 +129,8 @@ public:
void advanceGeneration() override;
+ void requireOwnedObjects() override;
+
double getNumber(const char* field) override;
int getNumberInt(const char* field) override;
long long getNumberLongLong(const char* field) override;
diff --git a/src/mongo/shell/bench.cpp b/src/mongo/shell/bench.cpp
index 040002f5c6e..338477ebe56 100644
--- a/src/mongo/shell/bench.cpp
+++ b/src/mongo/shell/bench.cpp
@@ -674,7 +674,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) {
invariant(bsonTemplateEvaluator.setId(_id) == BsonTemplateEvaluator::StatusSuccess);
if (_config->username != "") {
- string errmsg;
+ std::string errmsg;
if (!conn->auth("admin", _config->username, _config->password, errmsg)) {
uasserted(15931, "Authenticating to connection for _benchThread failed: " + errmsg);
}
@@ -918,7 +918,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) {
if (!result["err"].eoo() && result["err"].type() == String &&
(_config->throwGLE || op.throwGLE))
- throw DBException((string) "From benchRun GLE" +
+ throw DBException((std::string) "From benchRun GLE" +
causedBy(result["err"].String()),
result["code"].eoo() ? 0 : result["code"].Int());
}
@@ -984,7 +984,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) {
if (!result["err"].eoo() && result["err"].type() == String &&
(_config->throwGLE || op.throwGLE))
- throw DBException((string) "From benchRun GLE" +
+ throw DBException((std::string) "From benchRun GLE" +
causedBy(result["err"].String()),
result["code"].eoo() ? 0 : result["code"].Int());
}
@@ -1031,7 +1031,7 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) {
if (!result["err"].eoo() && result["err"].type() == String &&
(_config->throwGLE || op.throwGLE))
- throw DBException((string) "From benchRun GLE " +
+ throw DBException((std::string) "From benchRun GLE " +
causedBy(result["err"].String()),
result["code"].eoo() ? 0 : result["code"].Int());
}
@@ -1133,7 +1133,7 @@ void BenchRunWorker::run() {
try {
std::unique_ptr<DBClientBase> conn(_config->createConnection());
if (!_config->username.empty()) {
- string errmsg;
+ std::string errmsg;
if (!conn->auth("admin", _config->username, _config->password, errmsg)) {
uasserted(15932, "Authenticating to connection for benchThread failed: " + errmsg);
}
@@ -1165,7 +1165,7 @@ void BenchRunner::start() {
std::unique_ptr<DBClientBase> conn(_config->createConnection());
// Must authenticate to admin db in order to run serverStatus command
if (_config->username != "") {
- string errmsg;
+ std::string errmsg;
if (!conn->auth("admin", _config->username, _config->password, errmsg)) {
uasserted(
16704,
@@ -1201,7 +1201,7 @@ void BenchRunner::stop() {
{
std::unique_ptr<DBClientBase> conn(_config->createConnection());
if (_config->username != "") {
- string errmsg;
+ std::string errmsg;
// this can only fail if admin access was revoked since start of run
if (!conn->auth("admin", _config->username, _config->password, errmsg)) {
uasserted(
diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp
index 801130d07c5..90c59f0ee55 100644
--- a/src/mongo/shell/dbshell.cpp
+++ b/src/mongo/shell/dbshell.cpp
@@ -43,6 +43,7 @@
#include "mongo/base/initializer.h"
#include "mongo/base/status.h"
#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/mongo_uri.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/client.h"
#include "mongo/db/log_process_details.h"
@@ -215,57 +216,151 @@ void setupSignals() {
signal(SIGINT, quitNicely);
}
-string getURIFromArgs(const std::string& url, const std::string& host, const std::string& port) {
- if (host.size() == 0 && port.size() == 0) {
- return url.size() == 0 ? kDefaultMongoURL.toString() : url;
+string getURIFromArgs(const std::string& arg, const std::string& host, const std::string& port) {
+ if (host.empty() && arg.empty() && port.empty()) {
+ // Nothing provided, just play the default.
+ return kDefaultMongoURL.toString();
}
- // The name URL is misleading; really it's just a positional argument that wasn't a file. The
- // check for "/" means "this 'URL' is probably a real URL and not the db name (e.g.)".
- if (url.find("/") != string::npos) {
- cerr << "if a full URI is provided, you cannot also specify host or port" << endl;
+ if (str::startsWith(arg, "mongodb://") && host.empty() && port.empty()) {
+ // mongo mongodb://blah
+ return arg;
+ }
+ if (str::startsWith(host, "mongodb://") && arg.empty() && port.empty()) {
+ // mongo --host mongodb://blah
+ return host;
+ }
+
+ // We expect a positional arg to be a plain dbname or plain hostname at this point
+ // since we have separate host/port args.
+ if ((arg.find('/') != string::npos) && (host.size() || port.size())) {
+ cerr << "If a full URI is provided, you cannot also specify --host or --port" << endl;
quickExit(-1);
}
- bool hostEndsInSock = str::endsWith(host, ".sock");
+ const auto parseDbHost = [port](const std::string& db, const std::string& host) -> std::string {
+ // Parse --host as a connection string.
+ // e.g. rs0/host0:27000,host1:27001
+ const auto slashPos = host.find('/');
+ const auto hasReplSet = (slashPos > 0) && (slashPos != std::string::npos);
+
+ std::ostringstream ss;
+ ss << "mongodb://";
+
+ // Handle each sub-element of the connection string individually.
+ // Comma separated list of host elements.
+ // Each host element may be:
+ // * /unix/domain.sock
+ // * hostname
+ // * hostname:port
+ // If --port is specified and port is included in connection string,
+ // then they must match exactly.
+ auto start = hasReplSet ? slashPos + 1 : 0;
+ while (start < host.size()) {
+ // Encode each host component.
+ auto end = host.find(',', start);
+ if (end == std::string::npos) {
+ end = host.size();
+ }
+ if ((end - start) == 0) {
+ // Ignore empty components.
+ start = end + 1;
+ continue;
+ }
+
+ const auto hostElem = host.substr(start, end - start);
+ if ((hostElem.find('/') != std::string::npos) && str::endsWith(hostElem, ".sock")) {
+ // Unix domain socket, ignore --port.
+ ss << uriEncode(hostElem);
+
+ } else {
+ auto colon = hostElem.find(':');
+ if ((colon != std::string::npos) &&
+ (hostElem.find(':', colon + 1) != std::string::npos)) {
+ // Looks like an IPv6 numeric address.
+ const auto close = hostElem.find(']');
+ if ((hostElem[0] == '[') && (close != std::string::npos)) {
+ // Encapsulated already.
+ ss << '[' << uriEncode(hostElem.substr(1, close - 1), ":") << ']';
+ colon = hostElem.find(':', close + 1);
+ } else {
+ // Not encapsulated yet.
+ ss << '[' << uriEncode(hostElem, ":") << ']';
+ colon = std::string::npos;
+ }
+ } else if (colon != std::string::npos) {
+ // Not IPv6 numeric, but does have a port.
+ ss << uriEncode(hostElem.substr(0, colon));
+ } else {
+ // Raw hostname/IPv4 without port.
+ ss << uriEncode(hostElem);
+ }
+
+ if (colon != std::string::npos) {
+ // Have a port in our host element, verify it.
+ const auto myport = hostElem.substr(colon + 1);
+ if (port.size() && (port != myport)) {
+ cerr << "connection string bears different port than provided by --port"
+ << endl;
+ quickExit(-1);
+ }
+ ss << ':' << uriEncode(myport);
+ } else if (port.size()) {
+ ss << ':' << uriEncode(port);
+ } else {
+ ss << ":27017";
+ }
+ }
+ start = end + 1;
+ if (start < host.size()) {
+ ss << ',';
+ }
+ }
+
+ ss << '/' << uriEncode(db);
- // If host looks like a full URI (i.e. has a slash and isn't a unix socket) and the other fields
- // are empty, then just return host.
- std::string::size_type slashPos;
- if (url.size() == 0 && port.size() == 0 &&
- (!hostEndsInSock && ((slashPos = host.find("/")) != string::npos))) {
- if (str::startsWith(host, "mongodb://")) {
- return host;
+ if (hasReplSet) {
+ // Remap included replica set name to URI option
+ ss << "?replicaSet=" << uriEncode(host.substr(0, slashPos));
}
- // If there's a slash in the host field, then it's the replica set name, not a database name
- stringstream ss;
- ss << "mongodb://" << host.substr(slashPos + 1)
- << "/?replicaSet=" << host.substr(0, slashPos);
+
return ss.str();
- }
+ };
+
+ if (host.size()) {
+ // --host provided, treat it as the connect string and get db from positional arg.
+ return parseDbHost(arg, host);
+ } else if (arg.size()) {
+ // --host missing, but we have a potential host/db positional arg.
+ const auto slashPos = arg.find('/');
+ if (slashPos != std::string::npos) {
+ // host/db pair.
+ return parseDbHost(arg.substr(slashPos + 1), arg.substr(0, slashPos));
+ }
- stringstream ss;
- if (host.size() == 0) {
- ss << "mongodb://127.0.0.1";
- } else {
- if (!str::startsWith(host, "mongodb://")) {
- ss << "mongodb://";
+ // Compatability formats.
+ // * Any arg with a dot is assumed to be a hostname or IPv4 numeric address.
+ // * Any arg with a colon followed by a digit assumed to be host or IP followed by port.
+ // * Anything else is assumed to be a db.
+
+ if (arg.find('.') != std::string::npos) {
+ // Assume IPv4 or hostnameish.
+ return parseDbHost("test", arg);
}
- ss << host;
- }
- if (!hostEndsInSock) {
- if (port.size() > 0) {
- ss << ":" << port;
- } else if (host.find(':') == string::npos || str::endsWith(host, "]")) {
- // Default the port to 27017 if the host did not provide one (i.e. the host has no
- // colons or ends in ']' like an IPv6 address).
- ss << ":27017";
+ const auto colonPos = arg.find(':');
+ if ((colonPos != std::string::npos) && ((colonPos + 1) < arg.size()) &&
+ isdigit(arg[colonPos + 1])) {
+ // Assume IPv4 or hostname with port.
+ return parseDbHost("test", arg);
}
+
+ // db, assume localhost.
+ return parseDbHost(arg, "127.0.0.1");
}
- ss << "/" << url;
- return ss.str();
+ // --host empty, position arg empty, fallback on localhost without a dbname.
+ return parseDbHost("", "127.0.0.1");
}
static string OpSymbols = "~!%^&*-+=|:,<>/?.";
diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js
index 69971609bb8..92fc83b38b9 100644
--- a/src/mongo/shell/mongo.js
+++ b/src/mongo/shell/mongo.js
@@ -223,6 +223,9 @@ connect = function(url, user, pass) {
if (!url.startsWith("mongodb://")) {
const colon = url.lastIndexOf(":");
const slash = url.lastIndexOf("/");
+ if (url.split("/").length > 1) {
+ url = url.substring(0, slash).replace(/\//g, "%2F") + url.substring(slash);
+ }
if (slash == 0) {
throw Error("Failed to parse mongodb:// URL: " + url);
}
@@ -235,7 +238,7 @@ connect = function(url, user, pass) {
chatty("connecting to: " + url);
var m = new Mongo(url);
- db = m.getDB(m.defaultDB);
+ var db = m.getDB(m.defaultDB);
if (user && pass) {
if (!db.auth(user, pass)) {
diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js
index 0e05a1bfa4a..9d6a4378009 100644
--- a/src/mongo/shell/replsettest.js
+++ b/src/mongo/shell/replsettest.js
@@ -427,8 +427,10 @@ var ReplSetTest = function(opts) {
var member = {};
member._id = i;
- var port = this.ports[i];
- member.host = this.host + ":" + port;
+ member.host = this.host;
+ if (!member.host.contains('/')) {
+ member.host += ":" + this.ports[i];
+ }
var nodeOpts = this.nodeOptions["n" + i];
if (nodeOpts) {
@@ -470,7 +472,7 @@ var ReplSetTest = function(opts) {
*
* @param options - The options passed to {@link MongoRunner.runMongod}
*/
- this.startSet = function(options) {
+ this.startSet = function(options, restart) {
print("ReplSetTest starting set");
if (options && options.keyFile) {
@@ -482,7 +484,7 @@ var ReplSetTest = function(opts) {
var nodes = [];
for (var n = 0; n < this.ports.length; n++) {
- nodes.push(this.start(n, options));
+ nodes.push(this.start(n, options, restart));
}
this.nodes = nodes;
@@ -1883,7 +1885,11 @@ var ReplSetTest = function(opts) {
* Constructor, which instantiates the ReplSetTest object from an existing set.
*/
function _constructFromExistingSeedNode(seedNode) {
- var conf = _replSetGetConfig(new Mongo(seedNode));
+ const conn = new Mongo(seedNode);
+ if (jsTest.options().keyFile) {
+ self.keyFile = jsTest.options().keyFile;
+ }
+ var conf = asCluster(conn, () => _replSetGetConfig(conn));
print('Recreating replica set from config ' + tojson(conf));
var existingNodes = conf.members.map(member => member.host);
diff --git a/src/mongo/shell/servers_misc.js b/src/mongo/shell/servers_misc.js
index fdb49deb76a..f419428e2d0 100644
--- a/src/mongo/shell/servers_misc.js
+++ b/src/mongo/shell/servers_misc.js
@@ -214,11 +214,21 @@ function startParallelShell(jsCode, port, noConnect) {
var args = ["mongo"];
if (typeof db == "object") {
- var hostAndPort = db.getMongo().host.split(':');
- var host = hostAndPort[0];
- args.push("--host", host);
- if (!port && hostAndPort.length >= 2) {
- var port = hostAndPort[1];
+ if (!port) {
+ // If no port override specified, just passthrough connect string.
+ args.push("--host", db.getMongo().host);
+ } else {
+ // Strip port numbers from connect string.
+ const uri = new MongoURI(db.getMongo().host);
+ var connString = uri.servers
+ .map(function(server) {
+ return server.host;
+ })
+ .join(',');
+ if (uri.setName.length > 0) {
+ connString = uri.setName + '/' + connString;
+ }
+ args.push("--host", connString);
}
}
if (port) {
diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js
index 9b06dcd89ce..7a0fdae70f3 100644
--- a/src/mongo/shell/shardingtest.js
+++ b/src/mongo/shell/shardingtest.js
@@ -370,10 +370,14 @@ var ShardingTest = function(params) {
}
};
- this.stop = function(opts) {
+ this.stopAllMongos = function(opts) {
for (var i = 0; i < this._mongos.length; i++) {
this.stopMongos(i, opts);
}
+ };
+
+ this.stop = function(opts) {
+ this.stopAllMongos(opts);
for (var i = 0; i < this._connections.length; i++) {
if (this._rs[i]) {
diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js
index 9794a60df4e..148eacc1c89 100644
--- a/src/mongo/shell/utils.js
+++ b/src/mongo/shell/utils.js
@@ -158,6 +158,18 @@ print.captureAllOutput = function(fn, args) {
return res;
};
+var indentStr = function(indent, s) {
+ if (typeof(s) === "undefined") {
+ s = indent;
+ indent = 0;
+ }
+ if (indent > 0) {
+ indent = (new Array(indent + 1)).join(" ");
+ s = indent + s.replace(/\n/g, "\n" + indent);
+ }
+ return s;
+};
+
if (typeof TestData == "undefined") {
TestData = undefined;
}
@@ -230,7 +242,8 @@ jsTestOptions = function() {
networkMessageCompressors: TestData.networkMessageCompressors,
skipValidationOnInvalidViewDefinitions: TestData.skipValidationOnInvalidViewDefinitions,
forceValidationWithFeatureCompatibilityVersion:
- TestData.forceValidationWithFeatureCompatibilityVersion
+ TestData.forceValidationWithFeatureCompatibilityVersion,
+ skipValidationNamespaces: TestData.skipValidationNamespaces || [],
});
}
return _jsTestOptions;
diff --git a/src/mongo/shell/utils_sh.js b/src/mongo/shell/utils_sh.js
index ef8ae2dfa36..6e9c61a32e5 100644
--- a/src/mongo/shell/utils_sh.js
+++ b/src/mongo/shell/utils_sh.js
@@ -632,6 +632,18 @@ sh.getRecentMigrations = function(configDB) {
return result;
};
+sh._shardingStatusStr = function(indent, s) {
+ // convert from logical indentation to actual num of chars
+ if (indent == 0) {
+ indent = 0;
+ } else if (indent == 1) {
+ indent = 2;
+ } else {
+ indent = (indent - 1) * 8;
+ }
+ return indentStr(indent, s) + "\n";
+};
+
function printShardingStatus(configDB, verbose) {
// configDB is a DB object that contains the sharding metadata of interest.
// Defaults to the db named "config" on the current connection.
@@ -646,15 +658,15 @@ function printShardingStatus(configDB, verbose) {
}
var raw = "";
- var output = function(s) {
- raw += s + "\n";
+ var output = function(indent, s) {
+ raw += sh._shardingStatusStr(indent, s);
};
- output("--- Sharding Status --- ");
- output(" sharding version: " + tojson(configDB.getCollection("version").findOne()));
+ output(0, "--- Sharding Status --- ");
+ output(1, "sharding version: " + tojson(configDB.getCollection("version").findOne()));
- output(" shards:");
+ output(1, "shards:");
configDB.shards.find().sort({_id: 1}).forEach(function(z) {
- output("\t" + tojsononeline(z));
+ output(2, tojsononeline(z));
});
// (most recently) active mongoses
@@ -672,9 +684,9 @@ function printShardingStatus(configDB, verbose) {
}
}
- output(" " + mongosAdjective + " mongoses:");
+ output(1, mongosAdjective + " mongoses:");
if (mostRecentMongosTime === null) {
- output("\tnone");
+ output(2, "none");
} else {
var recentMongosQuery = {
ping: {
@@ -688,7 +700,7 @@ function printShardingStatus(configDB, verbose) {
if (verbose) {
configDB.mongos.find(recentMongosQuery).sort({ping: -1}).forEach(function(z) {
- output("\t" + tojsononeline(z));
+ output(2, tojsononeline(z));
});
} else {
configDB.mongos
@@ -698,23 +710,23 @@ function printShardingStatus(configDB, verbose) {
{$sort: {num: -1}}
])
.forEach(function(z) {
- output("\t" + tojson(z._id) + " : " + z.num);
+ output(2, tojson(z._id) + " : " + z.num);
});
}
}
- output(" autosplit:");
+ output(1, "autosplit:");
// Is autosplit currently enabled
- output("\tCurrently enabled: " + (sh.getShouldAutoSplit(configDB) ? "yes" : "no"));
+ output(2, "Currently enabled: " + (sh.getShouldAutoSplit(configDB) ? "yes" : "no"));
- output(" balancer:");
+ output(1, "balancer:");
// Is the balancer currently enabled
- output("\tCurrently enabled: " + (sh.getBalancerState(configDB) ? "yes" : "no"));
+ output(2, "Currently enabled: " + (sh.getBalancerState(configDB) ? "yes" : "no"));
// Is the balancer currently active
- output("\tCurrently running: " + (sh.isBalancerRunning(configDB) ? "yes" : "no"));
+ output(2, "Currently running: " + (sh.isBalancerRunning(configDB) ? "yes" : "no"));
// Output details of the current balancer round
var balLock = sh.getBalancerLockDetails(configDB);
@@ -725,16 +737,17 @@ function printShardingStatus(configDB, verbose) {
// Output the balancer window
var balSettings = sh.getBalancerWindow(configDB);
if (balSettings) {
- output("\t\tBalancer active window is set between " + balSettings.start + " and " +
- balSettings.stop + " server local time");
+ output(3,
+ "Balancer active window is set between " + balSettings.start + " and " +
+ balSettings.stop + " server local time");
}
// Output the list of active migrations
var activeMigrations = sh.getActiveMigrations(configDB);
if (activeMigrations.length > 0) {
- output("\tCollections with active migrations: ");
+ output(2, "Collections with active migrations: ");
activeMigrations.forEach(function(migration) {
- output("\t\t" + migration._id + " started at " + migration.when);
+ output(3, migration._id + " started at " + migration.when);
});
}
@@ -755,31 +768,32 @@ function printShardingStatus(configDB, verbose) {
// Review config.actionlog for errors
var actionReport = sh.getRecentFailedRounds(configDB);
// Always print the number of failed rounds
- output("\tFailed balancer rounds in last 5 attempts: " + actionReport.count);
+ output(2, "Failed balancer rounds in last 5 attempts: " + actionReport.count);
// Only print the errors if there are any
if (actionReport.count > 0) {
- output("\tLast reported error: " + actionReport.lastErr);
- output("\tTime of Reported error: " + actionReport.lastTime);
+ output(2, "Last reported error: " + actionReport.lastErr);
+ output(2, "Time of Reported error: " + actionReport.lastTime);
}
- output("\tMigration Results for the last 24 hours: ");
+ output(2, "Migration Results for the last 24 hours: ");
var migrations = sh.getRecentMigrations(configDB);
if (migrations.length > 0) {
migrations.forEach(function(x) {
if (x._id === "Success") {
- output("\t\t" + x.count + " : " + x._id);
+ output(3, x.count + " : " + x._id);
} else {
- output("\t\t" + x.count + " : Failed with error '" + x._id + "', from " +
- x.from + " to " + x.to);
+ output(3,
+ x.count + " : Failed with error '" + x._id + "', from " + x.from +
+ " to " + x.to);
}
});
} else {
- output("\t\tNo recent migrations");
+ output(3, "No recent migrations");
}
}
- output(" databases:");
+ output(1, "databases:");
configDB.databases.find().sort({name: 1}).forEach(function(db) {
var truthy = function(value) {
return !!value;
@@ -795,20 +809,22 @@ function printShardingStatus(configDB, verbose) {
return s;
};
- output("\t" + tojsononeline(db, "", true));
+ output(2, tojsononeline(db, "", true));
if (db.partitioned) {
configDB.collections.find({_id: new RegExp("^" + RegExp.escape(db._id) + "\\.")})
.sort({_id: 1})
.forEach(function(coll) {
if (!coll.dropped) {
- output("\t\t" + coll._id);
- output("\t\t\tshard key: " + tojson(coll.key));
- output("\t\t\tunique: " + truthy(coll.unique) +
- nonBooleanNote("unique", coll.unique));
- output("\t\t\tbalancing: " + !truthy(coll.noBalance) +
- nonBooleanNote("noBalance", coll.noBalance));
- output("\t\t\tchunks:");
+ output(3, coll._id);
+ output(4, "shard key: " + tojson(coll.key));
+ output(4,
+ "unique: " + truthy(coll.unique) +
+ nonBooleanNote("unique", coll.unique));
+ output(4,
+ "balancing: " + !truthy(coll.noBalance) +
+ nonBooleanNote("noBalance", coll.noBalance));
+ output(4, "chunks:");
res = configDB.chunks
.aggregate({$match: {ns: coll._id}},
@@ -819,26 +835,29 @@ function printShardingStatus(configDB, verbose) {
var totalChunks = 0;
res.forEach(function(z) {
totalChunks += z.nChunks;
- output("\t\t\t\t" + z.shard + "\t" + z.nChunks);
+ output(5, z.shard + "\t" + z.nChunks);
});
if (totalChunks < 20 || verbose) {
configDB.chunks.find({"ns": coll._id})
.sort({min: 1})
.forEach(function(chunk) {
- output("\t\t\t" + tojson(chunk.min) + " -->> " +
- tojson(chunk.max) + " on : " + chunk.shard + " " +
- tojson(chunk.lastmod) + " " +
- (chunk.jumbo ? "jumbo " : ""));
+ output(4,
+ tojson(chunk.min) + " -->> " + tojson(chunk.max) +
+ " on : " + chunk.shard + " " +
+ tojson(chunk.lastmod) + " " +
+ (chunk.jumbo ? "jumbo " : ""));
});
} else {
output(
- "\t\t\ttoo many chunks to print, use verbose if you want to force print");
+ 4,
+ "too many chunks to print, use verbose if you want to force print");
}
configDB.tags.find({ns: coll._id}).sort({min: 1}).forEach(function(tag) {
- output("\t\t\t tag: " + tag.tag + " " + tojson(tag.min) + " -->> " +
- tojson(tag.max));
+ output(4,
+ " tag: " + tag.tag + " " + tojson(tag.min) + " -->> " +
+ tojson(tag.max));
});
}
});
@@ -861,29 +880,29 @@ function printShardingSizes(configDB) {
}
var raw = "";
- var output = function(s) {
- raw += s + "\n";
+ var output = function(indent, s) {
+ raw += sh._shardingStatusStr(indent, s);
};
- output("--- Sharding Status --- ");
- output(" sharding version: " + tojson(configDB.getCollection("version").findOne()));
+ output(0, "--- Sharding Sizes --- ");
+ output(1, "sharding version: " + tojson(configDB.getCollection("version").findOne()));
- output(" shards:");
+ output(1, "shards:");
var shards = {};
configDB.shards.find().forEach(function(z) {
shards[z._id] = new Mongo(z.host);
- output(" " + tojson(z));
+ output(2, tojson(z));
});
var saveDB = db;
- output(" databases:");
+ output(1, "databases:");
configDB.databases.find().sort({name: 1}).forEach(function(db) {
- output("\t" + tojson(db, "", true));
+ output(2, tojson(db, "", true));
if (db.partitioned) {
configDB.collections.find({_id: new RegExp("^" + RegExp.escape(db._id) + "\.")})
.sort({_id: 1})
.forEach(function(coll) {
- output("\t\t" + coll._id + " chunks:");
+ output(3, coll._id + " chunks:");
configDB.chunks.find({"ns": coll._id}).sort({min: 1}).forEach(function(chunk) {
var mydb = shards[chunk.shard].getDB(db._id);
var out = mydb.runCommand({
@@ -895,8 +914,9 @@ function printShardingSizes(configDB) {
delete out.millis;
delete out.ok;
- output("\t\t\t" + tojson(chunk.min) + " -->> " + tojson(chunk.max) +
- " on : " + chunk.shard + " " + tojson(out));
+ output(4,
+ tojson(chunk.min) + " -->> " + tojson(chunk.max) + " on : " +
+ chunk.shard + " " + tojson(out));
});
});
diff --git a/src/mongo/transport/SConscript b/src/mongo/transport/SConscript
index 8e414610b2c..e82b6bd28fa 100644
--- a/src/mongo/transport/SConscript
+++ b/src/mongo/transport/SConscript
@@ -100,7 +100,9 @@ env.CppUnitTest(
],
)
-env.Library(
+messageCompressorEnv = env.Clone()
+messageCompressorEnv.InjectThirdPartyIncludePaths(libraries=['snappy'])
+messageCompressorEnv.Library(
target='message_compressor',
source=[
'message_compressor_manager.cpp',
diff --git a/src/mongo/transport/message_compressor_manager.cpp b/src/mongo/transport/message_compressor_manager.cpp
index 669b091f422..9a5b5ca81a9 100644
--- a/src/mongo/transport/message_compressor_manager.cpp
+++ b/src/mongo/transport/message_compressor_manager.cpp
@@ -51,23 +51,31 @@ struct CompressionHeader {
uint8_t compressorId;
void serialize(DataRangeCursor* cursor) {
- cursor->writeAndAdvance<LittleEndian<int32_t>>(originalOpCode);
- cursor->writeAndAdvance<LittleEndian<int32_t>>(uncompressedSize);
- cursor->writeAndAdvance<LittleEndian<uint8_t>>(compressorId);
+ uassertStatusOK(cursor->writeAndAdvance<LittleEndian<int32_t>>(originalOpCode));
+ uassertStatusOK(cursor->writeAndAdvance<LittleEndian<int32_t>>(uncompressedSize));
+ uassertStatusOK(cursor->writeAndAdvance<LittleEndian<uint8_t>>(compressorId));
}
CompressionHeader(int32_t _opcode, int32_t _size, uint8_t _id)
: originalOpCode{_opcode}, uncompressedSize{_size}, compressorId{_id} {}
CompressionHeader(ConstDataRangeCursor* cursor) {
- originalOpCode = cursor->readAndAdvance<LittleEndian<std::int32_t>>().getValue();
- uncompressedSize = cursor->readAndAdvance<LittleEndian<std::int32_t>>().getValue();
- compressorId = cursor->readAndAdvance<LittleEndian<uint8_t>>().getValue();
+ originalOpCode = _readWithChecking<LittleEndian<std::int32_t>>(cursor);
+ uncompressedSize = _readWithChecking<LittleEndian<std::int32_t>>(cursor);
+ compressorId = _readWithChecking<LittleEndian<uint8_t>>(cursor);
}
static size_t size() {
return sizeof(originalOpCode) + sizeof(uncompressedSize) + sizeof(compressorId);
}
+
+private:
+ template <typename T>
+ T _readWithChecking(ConstDataRangeCursor* cursor) {
+ auto sw = cursor->readAndAdvance<T>();
+ uassertStatusOK(sw.getStatus());
+ return sw.getValue();
+ }
};
} // namespace
@@ -124,6 +132,9 @@ StatusWith<Message> MessageCompressorManager::compressMessage(const Message& msg
StatusWith<Message> MessageCompressorManager::decompressMessage(const Message& msg) {
auto inputHeader = msg.header();
ConstDataRangeCursor input(inputHeader.data(), inputHeader.data() + inputHeader.dataLen());
+ if (input.length() < CompressionHeader::size()) {
+ return {ErrorCodes::BadValue, "Invalid compressed message header"};
+ }
CompressionHeader compressionHeader(&input);
auto compressor = _registry->getCompressor(compressionHeader.compressorId);
@@ -132,7 +143,12 @@ StatusWith<Message> MessageCompressorManager::decompressMessage(const Message& m
"Compression algorithm specified in message is not available"};
}
- auto bufferSize = compressionHeader.uncompressedSize + MsgData::MsgDataHeaderSize;
+ size_t bufferSize = compressionHeader.uncompressedSize + MsgData::MsgDataHeaderSize;
+ if (bufferSize > MaxMessageSizeBytes) {
+ return {ErrorCodes::BadValue,
+ "Decompressed message would be larger than maximum message size"};
+ }
+
auto outputMessageBuffer = SharedBuffer::allocate(bufferSize);
MsgData::View outMessage(outputMessageBuffer.get());
outMessage.setId(inputHeader.getId());
diff --git a/src/mongo/transport/message_compressor_manager_test.cpp b/src/mongo/transport/message_compressor_manager_test.cpp
index 383bb1e3260..5c533e45e75 100644
--- a/src/mongo/transport/message_compressor_manager_test.cpp
+++ b/src/mongo/transport/message_compressor_manager_test.cpp
@@ -26,6 +26,8 @@
* it in the license file.
*/
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork
+
#include "mongo/platform/basic.h"
#include "mongo/bson/bsonobjbuilder.h"
@@ -33,7 +35,9 @@
#include "mongo/transport/message_compressor_manager.h"
#include "mongo/transport/message_compressor_noop.h"
#include "mongo/transport/message_compressor_registry.h"
+#include "mongo/transport/message_compressor_snappy.h"
#include "mongo/unittest/unittest.h"
+#include "mongo/util/log.h"
#include "mongo/util/net/message.h"
#include <string>
@@ -120,6 +124,41 @@ void checkFidelity(const Message& msg, std::unique_ptr<MessageCompressorBase> co
ASSERT_EQ(memcmp(decompressedMsgView.data(), originalView.data(), originalView.dataLen()), 0);
}
+void checkOverflow(std::unique_ptr<MessageCompressorBase> compressor) {
+ // This is our test data that we're going to try to compress/decompress into a buffer that's
+ // way too small.
+ const std::string data =
+ "We embrace reality. We apply high-quality thinking and rigor."
+ "We have courage in our convictions but work hard to ensure biases "
+ "or personal beliefs do not get in the way of finding the best solution.";
+ ConstDataRange input(data.data(), data.size());
+
+ // This is our tiny buffer that should cause an error.
+ std::array<char, 16> smallBuffer;
+ DataRange smallOutput(smallBuffer.data(), smallBuffer.size());
+
+ // This is a normal sized buffer that we can store a compressed version of our test data safely
+ std::vector<char> normalBuffer;
+ normalBuffer.resize(compressor->getMaxCompressedSize(data.size()));
+ auto sws = compressor->compressData(input, DataRange(normalBuffer.data(), normalBuffer.size()));
+ ASSERT_OK(sws);
+ DataRange normalRange = DataRange(normalBuffer.data(), sws.getValue());
+
+ // Check that compressing the test data into a small buffer fails
+ ASSERT_NOT_OK(compressor->compressData(input, smallOutput));
+
+ // Check that decompressing compressed test data into a small buffer fails
+ ASSERT_NOT_OK(compressor->decompressData(normalRange, smallOutput));
+
+ // Check that decompressing a valid buffer that's missing data doesn't overflow the
+ // source buffer.
+ std::vector<char> scratch;
+ scratch.resize(data.size());
+ ConstDataRange tooSmallRange(normalBuffer.data(), normalBuffer.size() / 2);
+ ASSERT_NOT_OK(
+ compressor->decompressData(tooSmallRange, DataRange(scratch.data(), scratch.size())));
+}
+
Message buildMessage() {
const auto data = std::string{"Hello, world!"};
const auto bufferSize = MsgData::MsgDataHeaderSize + data.size();
@@ -179,8 +218,54 @@ TEST(NoopMessageCompressor, Fidelity) {
TEST(SnappyMessageCompressor, Fidelity) {
auto testMessage = buildMessage();
- checkFidelity(testMessage, stdx::make_unique<NoopMessageCompressor>());
+ checkFidelity(testMessage, stdx::make_unique<SnappyMessageCompressor>());
+}
+
+TEST(SnappyMessageCompressor, Overflow) {
+ checkOverflow(stdx::make_unique<SnappyMessageCompressor>());
+}
+
+TEST(MessageCompressorManager, MessageSizeTooLarge) {
+ auto registry = buildRegistry();
+ MessageCompressorManager compManager(&registry);
+
+ auto badMessageBuffer = SharedBuffer::allocate(128);
+ MsgData::View badMessage(badMessageBuffer.get());
+ badMessage.setId(1);
+ badMessage.setResponseToMsgId(0);
+ badMessage.setOperation(dbCompressed);
+ badMessage.setLen(128);
+
+ DataRangeCursor cursor(badMessage.data(), badMessage.data() + badMessage.dataLen());
+ uassertStatusOK(cursor.writeAndAdvance<LittleEndian<int32_t>>(dbQuery));
+ uassertStatusOK(cursor.writeAndAdvance<LittleEndian<int32_t>>(MaxMessageSizeBytes + 1));
+ uassertStatusOK(
+ cursor.writeAndAdvance<LittleEndian<uint8_t>>(registry.getCompressor("noop")->getId()));
+
+ auto status = compManager.decompressMessage(Message(badMessageBuffer)).getStatus();
+ ASSERT_NOT_OK(status);
+}
+
+TEST(MessageCompressorManager, RuntMessage) {
+ auto registry = buildRegistry();
+ MessageCompressorManager compManager(&registry);
+
+ auto badMessageBuffer = SharedBuffer::allocate(128);
+ MsgData::View badMessage(badMessageBuffer.get());
+ badMessage.setId(1);
+ badMessage.setResponseToMsgId(0);
+ badMessage.setOperation(dbCompressed);
+ badMessage.setLen(MsgData::MsgDataHeaderSize + 8);
+
+ // This is a totally bogus compression header of just the orginal opcode + 0 byte uncompressed
+ // size
+ DataRangeCursor cursor(badMessage.data(), badMessage.data() + badMessage.dataLen());
+ uassertStatusOK(cursor.writeAndAdvance<LittleEndian<int32_t>>(dbQuery));
+ uassertStatusOK(cursor.writeAndAdvance<LittleEndian<int32_t>>(0));
+
+ auto status = compManager.decompressMessage(Message(badMessageBuffer)).getStatus();
+ ASSERT_NOT_OK(status);
}
-} // namespace mongo
} // namespace
+} // namespace mongo
diff --git a/src/mongo/transport/message_compressor_snappy.cpp b/src/mongo/transport/message_compressor_snappy.cpp
index db1e0c9dfca..9d523fca661 100644
--- a/src/mongo/transport/message_compressor_snappy.cpp
+++ b/src/mongo/transport/message_compressor_snappy.cpp
@@ -30,12 +30,13 @@
#include "mongo/platform/basic.h"
+#include "mongo/base/data_range_cursor.h"
#include "mongo/base/init.h"
#include "mongo/stdx/memory.h"
#include "mongo/transport/message_compressor_registry.h"
#include "mongo/transport/message_compressor_snappy.h"
-#include "third_party/snappy-1.1.3/snappy.h"
+#include <snappy.h>
namespace mongo {
@@ -48,7 +49,10 @@ std::size_t SnappyMessageCompressor::getMaxCompressedSize(size_t inputSize) {
StatusWith<std::size_t> SnappyMessageCompressor::compressData(ConstDataRange input,
DataRange output) {
- size_t outLength;
+ size_t outLength = output.length();
+ if (output.length() < getMaxCompressedSize(input.length())) {
+ return {ErrorCodes::BadValue, "Output too small for max size of compressed input"};
+ }
snappy::RawCompress(input.data(), input.length(), const_cast<char*>(output.data()), &outLength);
counterHitCompress(input.length(), outLength);
@@ -57,10 +61,13 @@ StatusWith<std::size_t> SnappyMessageCompressor::compressData(ConstDataRange inp
StatusWith<std::size_t> SnappyMessageCompressor::decompressData(ConstDataRange input,
DataRange output) {
- bool ret =
- snappy::RawUncompress(input.data(), input.length(), const_cast<char*>(output.data()));
+ size_t expectedLength = 0;
+ if (!snappy::GetUncompressedLength(input.data(), input.length(), &expectedLength) ||
+ expectedLength != output.length()) {
+ return {ErrorCodes::BadValue, "Compressed message was invalid or corrupted"};
+ }
- if (!ret) {
+ if (!snappy::RawUncompress(input.data(), input.length(), const_cast<char*>(output.data()))) {
return Status{ErrorCodes::BadValue, "Compressed message was invalid or corrupted"};
}
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript
index 3346a596763..fe0380c1345 100644
--- a/src/mongo/util/SConscript
+++ b/src/mongo/util/SConscript
@@ -500,6 +500,17 @@ env.CppUnitTest(
)
env.CppUnitTest(
+ target='producer_consumer_queue_test',
+ source=[
+ 'producer_consumer_queue_test.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/service_context',
+ ]
+)
+
+env.CppUnitTest(
target='duration_test',
source=[
'duration_test.cpp',
diff --git a/src/mongo/util/assert_util.cpp b/src/mongo/util/assert_util.cpp
index 9d40b1f5d62..500a8cbb5eb 100644
--- a/src/mongo/util/assert_util.cpp
+++ b/src/mongo/util/assert_util.cpp
@@ -232,10 +232,9 @@ NOINLINE_DECL void msgassertedWithLocation(int msgid,
const char* msg,
const char* file,
unsigned line) {
- assertionCount.condrollover(++assertionCount.warning);
+ assertionCount.condrollover(++assertionCount.msg);
log() << "Assertion: " << msgid << ":" << redact(msg) << ' ' << file << ' ' << dec << line
<< endl;
- logContext();
throw MsgAssertionException(msgid, msg);
}
@@ -243,7 +242,7 @@ NOINLINE_DECL void msgassertedNoTraceWithLocation(int msgid,
const char* msg,
const char* file,
unsigned line) {
- assertionCount.condrollover(++assertionCount.warning);
+ assertionCount.condrollover(++assertionCount.msg);
log() << "Assertion: " << msgid << ":" << redact(msg) << ' ' << file << ' ' << dec << line
<< endl;
throw MsgAssertionException(msgid, msg);
diff --git a/src/mongo/util/cmdline_utils/censor_cmdline.cpp b/src/mongo/util/cmdline_utils/censor_cmdline.cpp
index 044de375e56..e903242ac75 100644
--- a/src/mongo/util/cmdline_utils/censor_cmdline.cpp
+++ b/src/mongo/util/cmdline_utils/censor_cmdline.cpp
@@ -45,6 +45,7 @@ static bool _isPasswordArgument(const char* argumentName) {
"net.ssl.clusterPassword",
"processManagement.windowsService.servicePassword",
"security.kmip.clientCertificatePassword",
+ "security.ldap.bind.queryPassword",
NULL // Last entry sentinel.
};
for (const char* const* current = passwordArguments; *current; ++current) {
@@ -60,6 +61,7 @@ static bool _isPasswordSwitch(const char* switchName) {
"sslClusterPassword",
"servicePassword",
"kmipClientCertificatePassword",
+ "ldapQueryPassword",
NULL // Last entry sentinel.
};
diff --git a/src/mongo/util/concurrency/with_lock.h b/src/mongo/util/concurrency/with_lock.h
new file mode 100644
index 00000000000..e1607e361cf
--- /dev/null
+++ b/src/mongo/util/concurrency/with_lock.h
@@ -0,0 +1,111 @@
+/** Copyright 2017 MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/stdx/mutex.h"
+#include "mongo/util/assert_util.h"
+
+#include <utility>
+
+namespace mongo {
+
+/**
+ * WithLock is an attestation to pass as an argument to functions that must be called only while
+ * holding a lock, as a rigorous alternative to an unchecked naming convention and/or stern
+ * comments. It helps prevent a common usage error.
+ *
+ * It may be used to modernize code from (something like) this
+ *
+ * // Member _mutex MUST be held when calling this:
+ * void _clobber_inlock(OperationContext* opCtx) {
+ * _stuff = makeStuff(opCtx);
+ * }
+ *
+ * into
+ *
+ * void _clobber(WithLock, OperationContext* opCtx) {
+ * _stuff = makeStuff(opCtx);
+ * }
+ *
+ * A call to such a function looks like this:
+ *
+ * stdx::lock_guard<stdx::mutex> lk(_mutex);
+ * _clobber(lk, opCtx); // instead of _clobber_inlock(opCtx)
+ *
+ * Note that the formal argument need not (and should not) be named unless it is needed to pass
+ * the attestation along to another function:
+ *
+ * void _clobber(WithLock lock, OperationContext* opCtx) {
+ * _really_clobber(lock, opCtx);
+ * }
+ *
+ */
+struct WithLock {
+ template <typename Mutex>
+ WithLock(stdx::lock_guard<Mutex> const&) noexcept {}
+
+ template <typename Mutex>
+ WithLock(stdx::unique_lock<Mutex> const& lock) noexcept {
+ invariant(lock.owns_lock());
+ }
+
+ // Add constructors from any other lock types here.
+
+ // Pass by value is OK.
+ WithLock(WithLock const&) noexcept {}
+ WithLock(WithLock&&) noexcept {}
+
+ // No assigning WithLocks.
+ void operator=(WithLock const&) = delete;
+ void operator=(WithLock&&) = delete;
+
+ // No moving a lock_guard<> or unique_lock<> in.
+ template <typename Mutex>
+ WithLock(stdx::lock_guard<Mutex>&&) = delete;
+ template <typename Mutex>
+ WithLock(stdx::unique_lock<Mutex>&&) = delete;
+
+ /*
+ * Produces a WithLock without benefit of any actual lock, for use in cases where a lock is not
+ * really needed, such as in many (but not all!) constructors.
+ */
+ static WithLock withoutLock() noexcept {
+ return {};
+ }
+
+private:
+ WithLock() noexcept = default;
+};
+
+} // namespace mongo
+
+namespace std {
+// No moving a WithLock:
+template <>
+mongo::WithLock&& move<mongo::WithLock>(mongo::WithLock&&) noexcept = delete;
+} // namespace std
diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp
index abd8f2d6ade..4b46938fbdb 100644
--- a/src/mongo/util/net/hostandport.cpp
+++ b/src/mongo/util/net/hostandport.cpp
@@ -95,7 +95,9 @@ void HostAndPort::append(StringBuilder& ss) const {
} else {
ss << host();
}
- ss << ':' << port();
+ if (host().find('/') == std::string::npos) {
+ ss << ':' << port();
+ }
}
bool HostAndPort::empty() const {
diff --git a/src/mongo/util/net/miniwebserver.cpp b/src/mongo/util/net/miniwebserver.cpp
index 5f4165d42f4..9fe847168ee 100644
--- a/src/mongo/util/net/miniwebserver.cpp
+++ b/src/mongo/util/net/miniwebserver.cpp
@@ -45,6 +45,7 @@
namespace mongo {
using std::shared_ptr;
+using std::string;
using std::stringstream;
using std::vector;
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index 187c0c105e2..4bb0befb4b0 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -37,6 +37,7 @@
#include <boost/thread/thread.hpp>
#include <iostream>
#include <sstream>
+#include <stack>
#include <string>
#include <vector>
@@ -171,21 +172,28 @@ const STACK_OF(X509_EXTENSION) * X509_get0_extensions(const X509* peerCert) {
* OpenSSL before version 1.1.0 requires applications provide a callback which emits a thread
* identifier. This ID is used to store thread specific ERR information. When a thread is
* terminated, it must call ERR_remove_state or ERR_remove_thread_state. These functions may
- * themselves invoke the application provided callback.
+ * themselves invoke the application provided callback. These IDs are stored in a hashtable with
+ * a questionable hash function. They must be uniformly distributed to prevent collisions.
*/
class SSLThreadInfo {
public:
static unsigned long getID() {
- enforceCleanupOnShutdown();
+ struct ThreadLocalState {
+ bool firstCall;
+ unsigned long id;
+ };
+ static MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL ThreadLocalState state{true, 0};
+ if (state.firstCall) {
+ state.id = _idManager.reserveID();
+ unsigned long id = state.id;
+ boost::this_thread::at_thread_exit([id] {
+ ERR_remove_state(0);
+ _idManager.releaseID(id);
+ });
+ state.firstCall = false;
+ }
-#ifdef _WIN32
- return GetCurrentThreadId();
-#else
- static_assert(sizeof(void*) == sizeof(unsigned long),
- "OpenSSL needs the address of a thread-unique object to be castable to"
- "unsigned long");
- return reinterpret_cast<unsigned long>(&errno);
-#endif
+ return state.id;
}
static void lockingCallback(int mode, int type, const char* file, int line) {
@@ -208,21 +216,39 @@ public:
private:
SSLThreadInfo() = delete;
- // When called, ensures that this thread will, on termination, call ERR_remove_state.
- static void enforceCleanupOnShutdown() {
- static MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL bool firstCall = true;
- if (firstCall) {
- boost::this_thread::at_thread_exit([] { ERR_remove_state(0); });
- firstCall = false;
- }
- }
-
// Note: see SERVER-8734 for why we are using a recursive mutex here.
// Once the deadlock fix in OpenSSL is incorporated into most distros of
// Linux, this can be changed back to a nonrecursive mutex.
static std::vector<std::unique_ptr<stdx::recursive_mutex>> _mutex;
+
+ class ThreadIDManager {
+ public:
+ unsigned long reserveID() {
+ stdx::unique_lock<stdx::mutex> lock(_idMutex);
+ if (!_idLast.empty()) {
+ unsigned long ret = _idLast.top();
+ _idLast.pop();
+ return ret;
+ }
+ return ++_idNext;
+ }
+
+ void releaseID(unsigned long id) {
+ stdx::unique_lock<stdx::mutex> lock(_idMutex);
+ _idLast.push(id);
+ }
+
+ private:
+ // Machinery for producing IDs that are unique for the life of a thread.
+ stdx::mutex _idMutex; // Protects _idNext and _idLast.
+ unsigned long _idNext = 0; // Stores the next thread ID to use, if none already allocated.
+ std::stack<unsigned long, std::vector<unsigned long>>
+ _idLast; // Stores old thread IDs, for reuse.
+ };
+ static ThreadIDManager _idManager;
};
std::vector<std::unique_ptr<stdx::recursive_mutex>> SSLThreadInfo::_mutex;
+SSLThreadInfo::ThreadIDManager SSLThreadInfo::_idManager;
// We only want to free SSL_CTX objects if they have been populated. OpenSSL seems to perform this
// check before freeing them, but because it does not document this, we should protect ourselves.
diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp
index 5d03910a390..63bf184ec85 100644
--- a/src/mongo/util/options_parser/options_parser.cpp
+++ b/src/mongo/util/options_parser/options_parser.cpp
@@ -341,6 +341,11 @@ Status YAMLNodeToValue(const YAML::Node& YAMLNode,
return Status::OK();
}
+ if (!YAMLNode.IsScalar() && !YAMLNode.IsNull()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Scalar option '" << key << "' must be a single value");
+ }
+
// Our YAML parser reads everything as a string, so we need to parse it ourselves.
std::string stringVal = YAMLNode.Scalar();
return stringToValue(stringVal, type, key, value);
diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp
index a0d97c512bb..aeb4a875914 100644
--- a/src/mongo/util/options_parser/options_parser_test.cpp
+++ b/src/mongo/util/options_parser/options_parser_test.cpp
@@ -1834,6 +1834,82 @@ TEST(Parsing, BadConfigFileOption) {
ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
}
+TEST(Parsing, MapForScalarMismatch) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+ moe::OptionSection testOpts;
+
+ testOpts.addOptionChaining("config", "config", moe::Int, "Config file to parse");
+ testOpts.addOptionChaining("str", "str", moe::String, "");
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", R"cfg({ str: { elem: "val" } })cfg");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+}
+
+TEST(Parsing, ScalarForMapMismatch) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+ moe::OptionSection testOpts;
+
+ testOpts.addOptionChaining("config", "config", moe::Int, "Config file to parse");
+ testOpts.addOptionChaining("strmap", "strmap", moe::StringMap, "");
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", R"cfg({ str: "val" })cfg");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+}
+
+TEST(Parsing, ListForScalarMismatch) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+ moe::OptionSection testOpts;
+
+ testOpts.addOptionChaining("config", "config", moe::Int, "Config file to parse");
+ testOpts.addOptionChaining("str", "str", moe::String, "");
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", R"cfg({ str: ["val"] })cfg");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+}
+
+TEST(Parsing, ScalarForListMismatch) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+ moe::OptionSection testOpts;
+
+ testOpts.addOptionChaining("config", "config", moe::Int, "Config file to parse");
+ testOpts.addOptionChaining("strlist", "strlist", moe::StringVector, "");
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", R"cfg({ str: "val" })cfg");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+}
+
TEST(ConfigFromFilesystem, JSONGood) {
moe::OptionsParser parser;
moe::Environment environment;
diff --git a/src/mongo/util/password.cpp b/src/mongo/util/password.cpp
index 11c936bac45..4814cbb3b12 100644
--- a/src/mongo/util/password.cpp
+++ b/src/mongo/util/password.cpp
@@ -46,7 +46,7 @@ namespace mongo {
string askPassword() {
std::string password;
- cout << "Enter password: ";
+ cerr << "Enter password: ";
#ifndef _WIN32
const int stdinfd = 0;
termios termio;
@@ -102,7 +102,7 @@ string askPassword() {
return string();
}
#endif
- cout << "\n";
+ cerr << "\n";
return password;
}
}
diff --git a/src/mongo/util/producer_consumer_queue.h b/src/mongo/util/producer_consumer_queue.h
new file mode 100644
index 00000000000..f0e942a7d94
--- /dev/null
+++ b/src/mongo/util/producer_consumer_queue.h
@@ -0,0 +1,559 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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 <boost/optional.hpp>
+#include <deque>
+#include <list>
+#include <queue>
+#include <stack>
+
+#include "mongo/db/operation_context.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/type_traits.h"
+#include "mongo/util/concurrency/with_lock.h"
+#include "mongo/util/scopeguard.h"
+
+namespace mongo {
+
+namespace producer_consumer_queue_detail {
+
+/**
+ * The default cost function for the producer consumer queue.
+ *
+ * By default, all items in the queue have equal weight.
+ */
+struct DefaultCostFunction {
+ template <typename T>
+ size_t operator()(const T&) const {
+ return 1;
+ }
+};
+
+// Various helpers to tighten down whether the args getting passed are valid interruption args.
+//
+// Whatever the caller passes in the interruption args, they need to be invocable on one of
+// these helpers. std::is_invocable would do the job in C++17
+std::false_type areInterruptionArgsHelper(...) {
+ return {};
+}
+
+std::true_type areInterruptionArgsHelper(OperationContext*) {
+ return {};
+}
+
+std::true_type areInterruptionArgsHelper(OperationContext*, Date_t) {
+ return {};
+}
+
+std::true_type areInterruptionArgsHelper(Milliseconds) {
+ return {};
+}
+
+std::true_type areInterruptionArgsHelper(Date_t) {
+ return {};
+}
+
+template <typename U, typename... InterruptionArgs>
+auto areInterruptionArgs(U&& u, InterruptionArgs&&... args)
+ -> decltype(areInterruptionArgsHelper(std::forward<U>(u),
+ std::forward<InterruptionArgs>(args)...)) {
+ return areInterruptionArgsHelper(std::forward<U>(u), std::forward<InterruptionArgs>(args)...);
+}
+
+std::true_type areInterruptionArgs() {
+ return {};
+}
+
+} // namespace producer_consumer_queue_detail
+
+/**
+ * A bounded, blocking, thread safe, cost parametrizable, single producer, multi-consumer queue.
+ *
+ * Properties:
+ * bounded - the queue can be limited in the number of items it can hold
+ * blocking - when the queue is full, or has no entries, callers block
+ * thread safe - the queue can be accessed safely from multiple threads at the same time
+ * cost parametrizable - the cost of items in the queue need not be equal. I.e. your items could
+ * be discrete byte buffers and the queue depth measured in bytes, so that
+ * the queue could hold one large buffer, or many smaller ones
+ * single producer - Only one thread may push work into the queue
+ * multi-consumer - Any number of threads may pop work out of the queue
+ *
+ * Interruptibility:
+ * All of the blocking methods on this type allow for 6 kinds of interruptibility. The matrix is
+ * parameterized by (void|OperationContext*)|(void|Milliseconds|Date_t). These provide different
+ * kinds of waiting based on whether the method should be interruptible via opCtx, and then
+ * whether they should timeout via deadline or duration
+ *
+ * A contrived example: pcq.pop(opCtx, Minutes(1)) would be warranted if:
+ * - The caller is blocking on a client thread. (opCtx)
+ * - The caller needs to act periodically on inactivity. (the duration)
+ *
+ * Exceptions include:
+ * timeouts
+ * ErrorCodes::ExceededTimeLimit exceptions
+ * opCtx interrupts
+ * ErrorCodes::Interrupted exceptions
+ * closure of queue endpoints
+ * ErrorCodes::ProducerConsumerQueueEndClosed
+ * pushes with batches that exceed the max queue size
+ * ErrorCodes::ProducerConsumerQueueBatchTooLarge
+ *
+ * Cost Function:
+ * The cost function must have a call operator which takes a const T& and returns the cost in
+ * size_t units. It must be pure across moves for a given T and never return zero. The intent of
+ * the cost function is to express the kind of bounds the queue provides, rather than to
+ * specialize behavior for a type. I.e. you should not specialize the default cost function and
+ * the cost function should always be explicit in the type.
+ */
+template <typename T, typename CostFunc = producer_consumer_queue_detail::DefaultCostFunction>
+class ProducerConsumerQueue {
+
+public:
+ // By default the queue depth is unlimited
+ ProducerConsumerQueue()
+ : ProducerConsumerQueue(std::numeric_limits<size_t>::max(), CostFunc{}) {}
+
+ // Or it can be measured in whatever units your size function returns
+ explicit ProducerConsumerQueue(size_t size) : ProducerConsumerQueue(size, CostFunc{}) {}
+
+ // If your cost function has meaningful state, you may also pass a non-default constructed
+ // instance
+ explicit ProducerConsumerQueue(size_t size, CostFunc costFunc)
+ : _max(size), _costFunc(std::move(costFunc)) {}
+
+ ProducerConsumerQueue(const ProducerConsumerQueue&) = delete;
+ ProducerConsumerQueue& operator=(const ProducerConsumerQueue&) = delete;
+
+ ProducerConsumerQueue(ProducerConsumerQueue&&) = delete;
+ ProducerConsumerQueue& operator=(ProducerConsumerQueue&&) = delete;
+
+ ~ProducerConsumerQueue() {
+ invariant(!_producerWants);
+ invariant(!_consumers);
+ }
+
+ // Pushes the passed T into the queue
+ //
+ // Leaves T unchanged if an interrupt exception is thrown while waiting for space
+ template <
+ typename... InterruptionArgs,
+ typename = stdx::enable_if_t<decltype(producer_consumer_queue_detail::areInterruptionArgs(
+ std::declval<InterruptionArgs>()...))::value>>
+ void push(T&& t, InterruptionArgs&&... interruptionArgs) {
+ _pushRunner([&](stdx::unique_lock<stdx::mutex>& lk) {
+ auto cost = _invokeCostFunc(t, lk);
+ uassert(ErrorCodes::ProducerConsumerQueueBatchTooLarge,
+ str::stream() << "cost of item (" << cost
+ << ") larger than maximum queue size ("
+ << _max
+ << ")",
+ cost <= _max);
+
+ _waitForSpace(lk, cost, std::forward<InterruptionArgs>(interruptionArgs)...);
+ _push(lk, std::move(t));
+ });
+ }
+
+ // Pushes all Ts into the queue
+ //
+ // Blocks until all of the Ts can be pushed at once
+ //
+ // StartIterator must be ForwardIterator
+ //
+ // Leaves the values underneath the iterators unchanged if an interrupt exception is thrown
+ // while waiting for space
+ //
+ // Lifecycle methods of T must not throw if you want to use this method, as there's no obvious
+ // mechanism to see what was and was not pushed if those do throw
+ template <
+ typename StartIterator,
+ typename EndIterator,
+ typename... InterruptionArgs,
+ typename = stdx::enable_if_t<decltype(producer_consumer_queue_detail::areInterruptionArgs(
+ std::declval<InterruptionArgs>()...))::value>>
+ void pushMany(StartIterator start, EndIterator last, InterruptionArgs&&... interruptionArgs) {
+ return _pushRunner([&](stdx::unique_lock<stdx::mutex>& lk) {
+ size_t cost = 0;
+ for (auto iter = start; iter != last; ++iter) {
+ cost += _invokeCostFunc(*iter, lk);
+ }
+
+ uassert(ErrorCodes::ProducerConsumerQueueBatchTooLarge,
+ str::stream() << "cost of items in batch (" << cost
+ << ") larger than maximum queue size ("
+ << _max
+ << ")",
+ cost <= _max);
+
+ _waitForSpace(lk, cost, std::forward<InterruptionArgs>(interruptionArgs)...);
+
+ for (auto iter = start; iter != last; ++iter) {
+ _push(lk, std::move(*iter));
+ }
+ });
+ }
+
+ // Attempts a non-blocking push of a value
+ //
+ // Leaves T unchanged if it fails
+ bool tryPush(T&& t) {
+ return _pushRunner(
+ [&](stdx::unique_lock<stdx::mutex>& lk) { return _tryPush(lk, std::move(t)); });
+ }
+
+ // Pops one T out of the queue
+ template <
+ typename... InterruptionArgs,
+ typename = stdx::enable_if_t<decltype(producer_consumer_queue_detail::areInterruptionArgs(
+ std::declval<InterruptionArgs>()...))::value>>
+ T pop(InterruptionArgs&&... interruptionArgs) {
+ return _popRunner([&](stdx::unique_lock<stdx::mutex>& lk) {
+ _waitForNonEmpty(lk, std::forward<InterruptionArgs>(interruptionArgs)...);
+ return _pop(lk);
+ });
+ }
+
+ // Waits for at least one item in the queue, then pops items out of the queue until it would
+ // block
+ //
+ // OutputIterator must not throw on move assignment to *iter or popped values may be lost
+ // TODO: add sfinae to check to enforce
+ //
+ // Returns the cost value of the items extracted, along with the updated output iterator
+ template <
+ typename OutputIterator,
+ typename... InterruptionArgs,
+ typename = stdx::enable_if_t<decltype(producer_consumer_queue_detail::areInterruptionArgs(
+ std::declval<InterruptionArgs>()...))::value>>
+ std::pair<size_t, OutputIterator> popMany(OutputIterator iterator,
+ InterruptionArgs&&... interruptionArgs) {
+ return popManyUpTo(_max, iterator, std::forward<InterruptionArgs>(interruptionArgs)...);
+ }
+
+ // Waits for at least one item in the queue, then pops items out of the queue until it would
+ // block, or we've exceeded our budget
+ //
+ // OutputIterator must not throw on move assignment to *iter or popped values may be lost
+ // TODO: add sfinae to check to enforce
+ //
+ // Returns the cost value of the items extracted, along with the updated output iterator
+ template <
+ typename OutputIterator,
+ typename... InterruptionArgs,
+ typename = stdx::enable_if_t<decltype(producer_consumer_queue_detail::areInterruptionArgs(
+ std::declval<InterruptionArgs>()...))::value>>
+ std::pair<size_t, OutputIterator> popManyUpTo(size_t budget,
+ OutputIterator iterator,
+ InterruptionArgs&&... interruptionArgs) {
+ return _popRunner([&](stdx::unique_lock<stdx::mutex>& lk) {
+ size_t cost = 0;
+
+ _waitForNonEmpty(lk, std::forward<InterruptionArgs>(interruptionArgs)...);
+
+ while (auto out = _tryPop(lk)) {
+ cost += _invokeCostFunc(*out, lk);
+ *iterator = std::move(*out);
+ ++iterator;
+
+ if (cost >= budget) {
+ break;
+ }
+ }
+
+ return std::make_pair(cost, iterator);
+ });
+ }
+
+ // Attempts a non-blocking pop of a value
+ boost::optional<T> tryPop() {
+ return _popRunner([&](stdx::unique_lock<stdx::mutex>& lk) { return _tryPop(lk); });
+ }
+
+ // Closes the producer end. Consumers will continue to consume until the queue is exhausted, at
+ // which time they will begin to throw with an interruption dbexception
+ void closeProducerEnd() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ _producerEndClosed = true;
+
+ _notifyIfNecessary(lk);
+ }
+
+ // Closes the consumer end. This causes all callers to throw with an interruption dbexception
+ void closeConsumerEnd() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ _consumerEndClosed = true;
+ _producerEndClosed = true;
+
+ _notifyIfNecessary(lk);
+ }
+
+ // TEST ONLY FUNCTIONS
+
+ // Returns the current depth of the queue in CostFunction units
+ size_t sizeForTest() const {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ return _current;
+ }
+
+ // Returns true if the queue is empty
+ bool emptyForTest() const {
+ return sizeForTest() == 0;
+ }
+
+private:
+ size_t _invokeCostFunc(const T& t, WithLock) {
+ auto cost = _costFunc(t);
+ invariant(cost);
+ return cost;
+ }
+
+ void _checkProducerClosed(WithLock) {
+ uassert(
+ ErrorCodes::ProducerConsumerQueueEndClosed, "Producer end closed", !_producerEndClosed);
+ uassert(
+ ErrorCodes::ProducerConsumerQueueEndClosed, "Consumer end closed", !_consumerEndClosed);
+ }
+
+ void _checkConsumerClosed(WithLock) {
+ uassert(
+ ErrorCodes::ProducerConsumerQueueEndClosed, "Consumer end closed", !_consumerEndClosed);
+ uassert(ErrorCodes::ProducerConsumerQueueEndClosed,
+ "Producer end closed and values exhausted",
+ !(_producerEndClosed && _queue.empty()));
+ }
+
+ void _notifyIfNecessary(WithLock) {
+ // If we've closed the consumer end, or if the production end is closed and we've exhausted
+ // the queue, wake everyone up and get out of here
+ if (_consumerEndClosed || (_queue.empty() && _producerEndClosed)) {
+ if (_consumers) {
+ _condvarConsumer.notify_all();
+ }
+
+ if (_producerWants) {
+ _condvarProducer.notify_one();
+ }
+
+ return;
+ }
+
+ // If a producer is queued, and we have enough space for it to push its work
+ if (_producerWants && _current + _producerWants <= _max) {
+ _condvarProducer.notify_one();
+
+ return;
+ }
+
+ // If we have consumers and anything in the queue, notify consumers
+ if (_consumers && _queue.size()) {
+ _condvarConsumer.notify_one();
+
+ return;
+ }
+ }
+
+ template <typename Callback>
+ auto _pushRunner(Callback&& cb) -> decltype(
+ cb(std::declval<std::add_lvalue_reference<stdx::unique_lock<stdx::mutex>>::type>())) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+
+ _checkProducerClosed(lk);
+
+ const auto guard = MakeGuard([&] { _notifyIfNecessary(lk); });
+
+ return cb(lk);
+ }
+
+ template <typename Callback>
+ auto _popRunner(Callback&& cb) -> decltype(
+ cb(std::declval<std::add_lvalue_reference<stdx::unique_lock<stdx::mutex>>::type>())) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+
+ _checkConsumerClosed(lk);
+
+ const auto guard = MakeGuard([&] { _notifyIfNecessary(lk); });
+
+ return cb(lk);
+ }
+
+ bool _tryPush(WithLock wl, T&& t) {
+ size_t cost = _invokeCostFunc(t, wl);
+ if (_current + cost <= _max) {
+ _queue.emplace(std::move(t));
+ _current += cost;
+ return true;
+ }
+
+ return false;
+ }
+
+ void _push(WithLock wl, T&& t) {
+ size_t cost = _invokeCostFunc(t, wl);
+ invariant(_current + cost <= _max);
+
+ _queue.emplace(std::move(t));
+ _current += cost;
+ }
+
+ boost::optional<T> _tryPop(WithLock wl) {
+ boost::optional<T> out;
+
+ if (!_queue.empty()) {
+ out.emplace(std::move(_queue.front()));
+ _queue.pop();
+ _current -= _invokeCostFunc(*out, wl);
+ }
+
+ return out;
+ }
+
+ T _pop(WithLock wl) {
+ invariant(_queue.size());
+
+ auto t = std::move(_queue.front());
+ _queue.pop();
+
+ _current -= _invokeCostFunc(t, wl);
+
+ return t;
+ }
+
+ template <typename... InterruptionArgs>
+ void _waitForSpace(stdx::unique_lock<stdx::mutex>& lk,
+ size_t cost,
+ InterruptionArgs&&... interruptionArgs) {
+ invariant(!_producerWants);
+
+ _producerWants = cost;
+ const auto guard = MakeGuard([&] { _producerWants = 0; });
+
+ _waitFor(lk,
+ _condvarProducer,
+ [&] {
+ _checkProducerClosed(lk);
+ return _current + cost <= _max;
+ },
+ std::forward<InterruptionArgs>(interruptionArgs)...);
+ }
+
+ template <typename... InterruptionArgs>
+ void _waitForNonEmpty(stdx::unique_lock<stdx::mutex>& lk,
+ InterruptionArgs&&... interruptionArgs) {
+
+ _consumers++;
+ const auto guard = MakeGuard([&] { _consumers--; });
+
+ _waitFor(lk,
+ _condvarConsumer,
+ [&] {
+ _checkConsumerClosed(lk);
+ return _queue.size();
+ },
+ std::forward<InterruptionArgs>(interruptionArgs)...);
+ }
+
+ template <typename Callback>
+ void _waitFor(stdx::unique_lock<stdx::mutex>& lk,
+ stdx::condition_variable& condvar,
+ Callback&& pred,
+ OperationContext* opCtx) {
+ opCtx->waitForConditionOrInterrupt(condvar, lk, pred);
+ }
+
+ template <typename Callback>
+ void _waitFor(stdx::unique_lock<stdx::mutex>& lk,
+ stdx::condition_variable& condvar,
+ Callback&& pred) {
+ condvar.wait(lk, pred);
+ }
+
+ template <typename Callback>
+ void _waitFor(stdx::unique_lock<stdx::mutex>& lk,
+ stdx::condition_variable& condvar,
+ Callback&& pred,
+ OperationContext* opCtx,
+ Date_t deadline) {
+ uassert(ErrorCodes::ExceededTimeLimit,
+ "exceeded timeout",
+ opCtx->waitForConditionOrInterruptUntil(condvar, lk, deadline, pred));
+ }
+
+ template <typename Callback>
+ void _waitFor(stdx::unique_lock<stdx::mutex>& lk,
+ stdx::condition_variable& condvar,
+ Callback&& pred,
+ Date_t deadline) {
+ uassert(ErrorCodes::ExceededTimeLimit,
+ "exceeded timeout",
+ condvar.wait_until(lk, deadline.toSystemTimePoint(), pred));
+ }
+
+ template <typename Callback>
+ void _waitFor(stdx::unique_lock<stdx::mutex>& lk,
+ stdx::condition_variable& condvar,
+ Callback&& pred,
+ Milliseconds duration) {
+ uassert(ErrorCodes::ExceededTimeLimit,
+ "exceeded timeout",
+ condvar.wait_for(lk, duration.toSystemDuration(), pred));
+ }
+
+ mutable stdx::mutex _mutex;
+ stdx::condition_variable _condvarConsumer;
+ stdx::condition_variable _condvarProducer;
+
+ // Max size of the queue
+ const size_t _max;
+
+ // User's cost function
+ CostFunc _costFunc;
+
+ // Current size of the queue
+ size_t _current = 0;
+
+ std::queue<T> _queue;
+
+ // Counter for consumers in the queue
+ size_t _consumers = 0;
+
+ // Size of batch the blocking producer wants to insert
+ size_t _producerWants = 0;
+
+ // Flags that we're shutting down the queue
+ bool _consumerEndClosed = false;
+ bool _producerEndClosed = false;
+};
+
+} // namespace mongo
diff --git a/src/mongo/util/producer_consumer_queue_test.cpp b/src/mongo/util/producer_consumer_queue_test.cpp
new file mode 100644
index 00000000000..f824b5a63de
--- /dev/null
+++ b/src/mongo/util/producer_consumer_queue_test.cpp
@@ -0,0 +1,531 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/platform/basic.h"
+
+#include "mongo/unittest/unittest.h"
+
+#include "mongo/util/producer_consumer_queue.h"
+
+#include "mongo/db/service_context_noop.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/memory.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+
+namespace {
+
+class ProducerConsumerQueueTest : public unittest::Test {
+public:
+ ProducerConsumerQueueTest() : _serviceCtx(stdx::make_unique<ServiceContextNoop>()) {}
+
+ template <typename Callback>
+ stdx::thread runThread(StringData name, Callback&& cb) {
+ return stdx::thread([this, name, cb] { cb(); });
+ }
+
+private:
+ std::unique_ptr<ServiceContext> _serviceCtx;
+};
+
+class MoveOnly {
+public:
+ struct CostFunc {
+ CostFunc() = default;
+ explicit CostFunc(size_t val) : val(val) {}
+
+ size_t operator()(const MoveOnly& mo) const {
+ return val + *mo._val;
+ }
+
+ const size_t val = 0;
+ };
+
+ explicit MoveOnly(int i) : _val(i) {}
+
+ MoveOnly(const MoveOnly&) = delete;
+ MoveOnly& operator=(const MoveOnly&) = delete;
+
+ MoveOnly(MoveOnly&& other) : _val(other._val) {
+ other._val.reset();
+ }
+
+ MoveOnly& operator=(MoveOnly&& other) {
+ if (&other == this) {
+ return *this;
+ }
+
+ _val = other._val;
+ other._val.reset();
+
+ return *this;
+ }
+
+ bool movedFrom() const {
+ return !_val;
+ }
+
+ friend bool operator==(const MoveOnly& lhs, const MoveOnly& rhs) {
+ return *lhs._val == *rhs._val;
+ }
+
+ friend bool operator!=(const MoveOnly& lhs, const MoveOnly& rhs) {
+ return !(lhs == rhs);
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const MoveOnly& mo) {
+ return (os << "MoveOnly(" << *mo._val << ")");
+ }
+
+private:
+ boost::optional<int> _val;
+};
+
+TEST_F(ProducerConsumerQueueTest, basicPushPop) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ runThread("Producer", [&]() { pcq.push(MoveOnly(1)); }).join();
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+
+ runThread("Consumer", [&]() { ASSERT_EQUALS(pcq.pop(), MoveOnly(1)); }).join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, closeConsumerEnd) {
+ ProducerConsumerQueue<MoveOnly> pcq{1};
+
+ pcq.push(MoveOnly(1));
+
+ auto producer = runThread("Producer", [&]() {
+ ASSERT_THROWS_CODE(
+ pcq.push(MoveOnly(2)), DBException, ErrorCodes::ProducerConsumerQueueEndClosed);
+ });
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+
+ pcq.closeConsumerEnd();
+
+ ASSERT_THROWS_CODE(pcq.pop(), DBException, ErrorCodes::ProducerConsumerQueueEndClosed);
+
+ producer.join();
+}
+
+TEST_F(ProducerConsumerQueueTest, closeProducerEndImmediate) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ pcq.push(MoveOnly(1));
+ pcq.closeProducerEnd();
+
+ runThread("Consumer", [&]() {
+ ASSERT_EQUALS(pcq.pop(), MoveOnly(1));
+
+ ASSERT_THROWS_CODE(pcq.pop(), DBException, ErrorCodes::ProducerConsumerQueueEndClosed);
+ }).join();
+}
+
+TEST_F(ProducerConsumerQueueTest, closeProducerEndBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ auto consumer = runThread("Consumer", [&]() {
+ ASSERT_THROWS_CODE(pcq.pop(), DBException, ErrorCodes::ProducerConsumerQueueEndClosed);
+ });
+
+ pcq.closeProducerEnd();
+
+ consumer.join();
+}
+
+TEST_F(ProducerConsumerQueueTest, popsWithTimeout) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ runThread("Consumer", [&]() {
+ ASSERT_THROWS_CODE(pcq.pop(Milliseconds(100)), DBException, ErrorCodes::ExceededTimeLimit);
+
+ std::vector<MoveOnly> vec;
+ ASSERT_THROWS_CODE(pcq.popMany(std::back_inserter(vec), Milliseconds(100)),
+ DBException,
+ ErrorCodes::ExceededTimeLimit);
+
+ ASSERT_THROWS_CODE(pcq.popManyUpTo(1000, std::back_inserter(vec), Milliseconds(100)),
+ DBException,
+ ErrorCodes::ExceededTimeLimit);
+ }).join();
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 0ul);
+}
+
+TEST_F(ProducerConsumerQueueTest, pushesWithTimeout) {
+ ProducerConsumerQueue<MoveOnly> pcq{1};
+
+ {
+ MoveOnly mo(1);
+ pcq.push(std::move(mo));
+ ASSERT(mo.movedFrom());
+ }
+
+ runThread("Consumer", [&]() {
+ {
+ MoveOnly mo(2);
+ ASSERT_THROWS_CODE(pcq.push(std::move(mo), Milliseconds(100)),
+ DBException,
+ ErrorCodes::ExceededTimeLimit);
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+ ASSERT(!mo.movedFrom());
+ ASSERT_EQUALS(mo, MoveOnly(2));
+ }
+
+ {
+ std::vector<MoveOnly> vec;
+ vec.emplace_back(MoveOnly(2));
+
+ auto iter = begin(vec);
+ ASSERT_THROWS_CODE(pcq.pushMany(iter, end(vec), Milliseconds(100)),
+ DBException,
+ ErrorCodes::ExceededTimeLimit);
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+ ASSERT(!vec[0].movedFrom());
+ ASSERT_EQUALS(vec[0], MoveOnly(2));
+ }
+ }).join();
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+}
+
+TEST_F(ProducerConsumerQueueTest, basicPushPopWithBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ auto consumer = runThread("Consumer", [&]() { ASSERT_EQUALS(pcq.pop(), MoveOnly(1)); });
+
+ auto producer = runThread("Producer", [&]() { pcq.push(MoveOnly(1)); });
+
+ consumer.join();
+ producer.join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, multipleStepPushPopWithBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{1};
+
+ auto consumer = runThread("Consumer", [&]() {
+ for (int i = 0; i < 10; ++i) {
+ ASSERT_EQUALS(pcq.pop(), MoveOnly(i));
+ }
+ });
+
+ auto producer = runThread("Producer", [&]() {
+ for (int i = 0; i < 10; ++i) {
+ pcq.push(MoveOnly(i));
+ }
+ });
+
+ consumer.join();
+ producer.join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+
+TEST_F(ProducerConsumerQueueTest, pushTooLarge) {
+ {
+ ProducerConsumerQueue<MoveOnly, MoveOnly::CostFunc> pcq{1};
+
+ runThread("Producer", [&]() {
+ ASSERT_THROWS_CODE(
+ pcq.push(MoveOnly(2)), DBException, ErrorCodes::ProducerConsumerQueueBatchTooLarge);
+ }).join();
+ }
+
+ {
+ ProducerConsumerQueue<MoveOnly, MoveOnly::CostFunc> pcq{4};
+
+ std::vector<MoveOnly> vec;
+ vec.push_back(MoveOnly(3));
+ vec.push_back(MoveOnly(3));
+
+ runThread("Producer", [&]() {
+ ASSERT_THROWS_CODE(pcq.pushMany(begin(vec), end(vec)),
+ DBException,
+ ErrorCodes::ProducerConsumerQueueBatchTooLarge);
+ }).join();
+ }
+}
+
+TEST_F(ProducerConsumerQueueTest, pushManyPopWithoutBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ runThread("Producer", [&]() {
+ std::vector<MoveOnly> vec;
+ for (int i = 0; i < 10; ++i) {
+ vec.emplace_back(MoveOnly(i));
+ }
+
+ pcq.pushMany(begin(vec), end(vec));
+ }).join();
+
+ runThread("Consumer", [&]() {
+ for (int i = 0; i < 10; ++i) {
+ ASSERT_EQUALS(pcq.pop(), MoveOnly(i));
+ }
+ }).join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, popManyPopWithBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{2};
+
+ auto consumer = runThread("Consumer", [&]() {
+ for (int i = 0; i < 10; i = i + 2) {
+ std::vector<MoveOnly> out;
+
+ pcq.popMany(std::back_inserter(out));
+
+ ASSERT_EQUALS(out.size(), 2ul);
+ ASSERT_EQUALS(out[0], MoveOnly(i));
+ ASSERT_EQUALS(out[1], MoveOnly(i + 1));
+ }
+ });
+
+ auto producer = runThread("Producer", [&]() {
+ std::vector<MoveOnly> vec;
+ for (int i = 0; i < 10; ++i) {
+ vec.emplace_back(MoveOnly(i));
+ }
+
+ for (auto iter = begin(vec); iter != end(vec); iter += 2) {
+ pcq.pushMany(iter, iter + 2);
+ }
+ });
+
+ consumer.join();
+ producer.join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, popManyUpToPopWithBlocking) {
+ ProducerConsumerQueue<MoveOnly> pcq{4};
+
+ auto consumer = runThread("Consumer", [&]() {
+ for (int i = 0; i < 10; i = i + 2) {
+ std::vector<MoveOnly> out;
+
+ size_t spent;
+ std::tie(spent, std::ignore) = pcq.popManyUpTo(2, std::back_inserter(out));
+
+ ASSERT_EQUALS(spent, 2ul);
+ ASSERT_EQUALS(out.size(), 2ul);
+ ASSERT_EQUALS(out[0], MoveOnly(i));
+ ASSERT_EQUALS(out[1], MoveOnly(i + 1));
+ }
+ });
+
+ auto producer = runThread("Producer", [&]() {
+ std::vector<MoveOnly> vec;
+ for (int i = 0; i < 10; ++i) {
+ vec.emplace_back(MoveOnly(i));
+ }
+
+ for (auto iter = begin(vec); iter != end(vec); iter += 2) {
+ pcq.pushMany(iter, iter + 2);
+ }
+ });
+
+ consumer.join();
+ producer.join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, popManyUpToPopWithBlockingWithSpecialCost) {
+ ProducerConsumerQueue<MoveOnly, MoveOnly::CostFunc> pcq{};
+
+ auto consumer = runThread("Consumer", [&]() {
+ {
+ std::vector<MoveOnly> out;
+ size_t spent;
+ std::tie(spent, std::ignore) = pcq.popManyUpTo(5, std::back_inserter(out));
+
+ ASSERT_EQUALS(spent, 6ul);
+ ASSERT_EQUALS(out.size(), 3ul);
+ ASSERT_EQUALS(out[0], MoveOnly(1));
+ ASSERT_EQUALS(out[1], MoveOnly(2));
+ ASSERT_EQUALS(out[2], MoveOnly(3));
+ }
+
+ {
+ std::vector<MoveOnly> out;
+ size_t spent;
+ std::tie(spent, std::ignore) = pcq.popManyUpTo(15, std::back_inserter(out));
+
+ ASSERT_EQUALS(spent, 9ul);
+ ASSERT_EQUALS(out.size(), 2ul);
+ ASSERT_EQUALS(out[0], MoveOnly(4));
+ ASSERT_EQUALS(out[1], MoveOnly(5));
+ }
+ });
+
+ auto producer = runThread("Producer", [&]() {
+ std::vector<MoveOnly> vec;
+ for (int i = 1; i < 6; ++i) {
+ vec.emplace_back(MoveOnly(i));
+ }
+
+ pcq.pushMany(begin(vec), end(vec));
+ });
+
+ consumer.join();
+ producer.join();
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, singleProducerMultiConsumer) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ stdx::mutex mutex;
+ size_t success = 0;
+ size_t failure = 0;
+
+ std::array<stdx::thread, 3> threads;
+ for (auto& thread : threads) {
+ thread = runThread("Consumer", [&]() {
+ {
+ try {
+ pcq.pop();
+ stdx::lock_guard<stdx::mutex> lk(mutex);
+ success++;
+ } catch (const DBException& exception) {
+ ASSERT_EQUALS(exception.getCode(), ErrorCodes::ProducerConsumerQueueEndClosed);
+ stdx::lock_guard<stdx::mutex> lk(mutex);
+ failure++;
+ }
+ }
+ });
+ }
+
+ pcq.push(MoveOnly(1));
+ pcq.push(MoveOnly(2));
+
+ pcq.closeProducerEnd();
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ ASSERT_EQUALS(success, 2ul);
+ ASSERT_EQUALS(failure, 1ul);
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, basicTryPop) {
+ ProducerConsumerQueue<MoveOnly> pcq{};
+
+ ASSERT_FALSE(pcq.tryPop());
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(1)));
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+
+ auto val = pcq.tryPop();
+
+ ASSERT_FALSE(pcq.tryPop());
+ ASSERT_TRUE(val);
+ ASSERT_EQUALS(*val, MoveOnly(1));
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, basicTryPush) {
+ ProducerConsumerQueue<MoveOnly> pcq{1};
+
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(1)));
+ ASSERT_FALSE(pcq.tryPush(MoveOnly(2)));
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 1ul);
+
+ auto val = pcq.tryPop();
+ ASSERT_FALSE(pcq.tryPop());
+ ASSERT_TRUE(val);
+ ASSERT_EQUALS(*val, MoveOnly(1));
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, tryPushWithSpecialCost) {
+ ProducerConsumerQueue<MoveOnly, MoveOnly::CostFunc> pcq{5};
+
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(1)));
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(2)));
+ ASSERT_FALSE(pcq.tryPush(MoveOnly(3)));
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 3ul);
+
+ auto val1 = pcq.tryPop();
+ ASSERT_EQUALS(pcq.sizeForTest(), 2ul);
+ auto val2 = pcq.tryPop();
+ ASSERT_EQUALS(pcq.sizeForTest(), 0ul);
+ ASSERT_FALSE(pcq.tryPop());
+ ASSERT_TRUE(val1);
+ ASSERT_TRUE(val2);
+ ASSERT_EQUALS(*val1, MoveOnly(1));
+ ASSERT_EQUALS(*val2, MoveOnly(2));
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+TEST_F(ProducerConsumerQueueTest, tryPushWithSpecialStatefulCost) {
+ ProducerConsumerQueue<MoveOnly, MoveOnly::CostFunc> pcq{5, MoveOnly::CostFunc(1)};
+
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(1)));
+ ASSERT_TRUE(pcq.tryPush(MoveOnly(2)));
+ ASSERT_FALSE(pcq.tryPush(MoveOnly(3)));
+
+ ASSERT_EQUALS(pcq.sizeForTest(), 5ul);
+
+ auto val1 = pcq.tryPop();
+ ASSERT_EQUALS(pcq.sizeForTest(), 3ul);
+ auto val2 = pcq.tryPop();
+ ASSERT_EQUALS(pcq.sizeForTest(), 0ul);
+ ASSERT_FALSE(pcq.tryPop());
+ ASSERT_TRUE(val1);
+ ASSERT_TRUE(val2);
+ ASSERT_EQUALS(*val1, MoveOnly(1));
+ ASSERT_EQUALS(*val2, MoveOnly(2));
+
+ ASSERT_TRUE(pcq.emptyForTest());
+}
+
+} // namespace
+
+} // namespace mongo
diff --git a/src/mongo/util/tcmalloc_server_status_section.cpp b/src/mongo/util/tcmalloc_server_status_section.cpp
index 303076ba784..784f74e8cf8 100644
--- a/src/mongo/util/tcmalloc_server_status_section.cpp
+++ b/src/mongo/util/tcmalloc_server_status_section.cpp
@@ -38,6 +38,7 @@
#include "mongo/base/init.h"
#include "mongo/db/commands/server_status.h"
+#include "mongo/db/server_parameters.h"
#include "mongo/db/service_context.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/log.h"
@@ -55,11 +56,17 @@ const int kManyClients = 40;
stdx::mutex tcmallocCleanupLock;
+MONGO_EXPORT_SERVER_PARAMETER(tcmallocEnableMarkThreadIdle, bool, true);
+
/**
* Callback to allow TCMalloc to release freed memory to the central list at
* favorable times. Ideally would do some milder cleanup or scavenge...
*/
void threadStateChange() {
+ if (!tcmallocEnableMarkThreadIdle.load()) {
+ return;
+ }
+
if (getGlobalServiceContext()->getTransportLayer()->sessionStats().numOpenSessions <=
kManyClients)
return;
@@ -156,6 +163,25 @@ public:
appendNumericPropertyIfAvailable(
sub, "aggressive_memory_decommit", "tcmalloc.aggressive_memory_decommit");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_committed_bytes", "tcmalloc.pageheap_committed_bytes");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_scavenge_count", "tcmalloc.pageheap_scavenge_count");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_commit_count", "tcmalloc.pageheap_commit_count");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_total_commit_bytes", "tcmalloc.pageheap_total_commit_bytes");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_decommit_count", "tcmalloc.pageheap_decommit_count");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_total_decommit_bytes", "tcmalloc.pageheap_total_decommit_bytes");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_reserve_count", "tcmalloc.pageheap_reserve_count");
+ appendNumericPropertyIfAvailable(
+ sub, "pageheap_total_reserve_bytes", "tcmalloc.pageheap_total_reserve_bytes");
+ appendNumericPropertyIfAvailable(
+ sub, "spinlock_total_delay_ns", "tcmalloc.spinlock_total_delay_ns");
+
#if MONGO_HAVE_GPERFTOOLS_SIZE_CLASS_STATS
if (verbosity >= 2) {
// Size class information
diff --git a/src/third_party/SConscript b/src/third_party/SConscript
index 3a820399677..add20816b83 100644
--- a/src/third_party/SConscript
+++ b/src/third_party/SConscript
@@ -6,7 +6,7 @@ Import("wiredtiger")
boostSuffix = "-1.60.0"
snappySuffix = '-1.1.3'
zlibSuffix = '-1.2.8'
-pcreSuffix = "-8.39"
+pcreSuffix = "-8.41"
mozjsSuffix = '-45'
yamlSuffix = '-0.5.3'
icuSuffix = '-57.1'
diff --git a/src/third_party/gperftools-2.5/src/base/spinlock.cc b/src/third_party/gperftools-2.5/src/base/spinlock.cc
index 85ff21ed404..acb01c8537a 100644
--- a/src/third_party/gperftools-2.5/src/base/spinlock.cc
+++ b/src/third_party/gperftools-2.5/src/base/spinlock.cc
@@ -49,6 +49,47 @@ const base::LinkerInitialized SpinLock::LINKER_INITIALIZED =
base::LINKER_INITIALIZED;
namespace {
+
+const int64_t kNanoPerSec = uint64_t(1000) * 1000 * 1000;
+
+#ifdef _WIN32
+int64_t frequency;
+
+void InitTimer() {
+ LARGE_INTEGER large_freq;
+ QueryPerformanceFrequency(&large_freq);
+ frequency = large_freq.QuadPart;
+}
+
+int64_t NowMonotonic() {
+ LARGE_INTEGER time_value;
+ QueryPerformanceCounter(&time_value);
+ return time_value.QuadPart;
+}
+
+int64_t TicksToNanos(int64_t timer_value) {
+ return timer_value * kNanoPerSec / frequency;
+}
+
+#else // _WIN32
+void InitTimer() {}
+
+int64_t NowMonotonic() {
+ struct timespec t;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &t)) {
+ return 0;
+ }
+
+ return (static_cast<double>(t.tv_sec) * kNanoPerSec) + t.tv_nsec;
+}
+
+int64_t TicksToNanos(int64_t timer_value) {
+ return timer_value;
+}
+
+#endif // _WIN32
+
struct SpinLock_InitHelper {
SpinLock_InitHelper() {
// On multi-cpu machines, spin for longer before yielding
@@ -56,6 +97,8 @@ struct SpinLock_InitHelper {
if (GetSystemCPUsCount() > 1) {
adaptive_spin_count = 1000;
}
+
+ InitTimer();
}
};
@@ -69,6 +112,31 @@ inline void SpinlockPause(void) {
__asm__ __volatile__("rep; nop" : : );
#endif
}
+
+// The current version of atomic_ops.h lacks base::subtle::Barrier_AtomicIncrement
+// so a CAS loop is used instead.
+void NoBarrier_AtomicAdd(volatile base::subtle::Atomic64* ptr,
+ base::subtle::Atomic64 increment) {
+ base::subtle::Atomic64 base_value = base::subtle::NoBarrier_Load(ptr);
+ while (true) {
+ base::subtle::Atomic64 new_value;
+ base::subtle::NoBarrier_Store(&new_value, base::subtle::NoBarrier_Load(&base_value)
+ + base::subtle::NoBarrier_Load(&increment));
+
+ // Swap in the new incremented value.
+ base::subtle::Atomic64 cas_result = base::subtle::Acquire_CompareAndSwap(
+ ptr, base_value, new_value);
+
+ // Check if the increment succeeded.
+ if (cas_result == base_value) {
+ return;
+ }
+
+ // If the increment failed, just use the previous value as the value to
+ // add our increment to.
+ base_value = cas_result;
+ };
+}
} // unnamed namespace
@@ -84,10 +152,13 @@ Atomic32 SpinLock::SpinLoop() {
kSpinLockSleeper);
}
+base::subtle::Atomic64 SpinLock::totalDelayNanos_ = 0;
+
void SpinLock::SlowLock() {
Atomic32 lock_value = SpinLoop();
int lock_wait_call_count = 0;
+ int64_t start = 0;
while (lock_value != kSpinLockFree) {
// If the lock is currently held, but not marked as having a sleeper, mark
// it as having a sleeper.
@@ -115,12 +186,18 @@ void SpinLock::SlowLock() {
}
// Wait for an OS specific delay.
+ start = NowMonotonic();
base::internal::SpinLockDelay(&lockword_, lock_value,
++lock_wait_call_count);
// Spin again after returning from the wait routine to give this thread
// some chance of obtaining the lock.
lock_value = SpinLoop();
}
+
+ if (start) {
+ NoBarrier_AtomicAdd(&totalDelayNanos_, static_cast<base::subtle::Atomic64>(
+ TicksToNanos(NowMonotonic() - start)));
+ }
}
void SpinLock::SlowUnlock() {
diff --git a/src/third_party/gperftools-2.5/src/base/spinlock.h b/src/third_party/gperftools-2.5/src/base/spinlock.h
index 7243aeaaefd..c5ad8f048f6 100644
--- a/src/third_party/gperftools-2.5/src/base/spinlock.h
+++ b/src/third_party/gperftools-2.5/src/base/spinlock.h
@@ -107,6 +107,10 @@ class LOCKABLE SpinLock {
return base::subtle::NoBarrier_Load(&lockword_) != kSpinLockFree;
}
+ static base::subtle::Atomic64 GetTotalDelayNanos() {
+ return base::subtle::NoBarrier_Load(&totalDelayNanos_);
+ }
+
static const base::LinkerInitialized LINKER_INITIALIZED; // backwards compat
private:
enum { kSpinLockFree = 0 };
@@ -115,6 +119,8 @@ class LOCKABLE SpinLock {
volatile Atomic32 lockword_;
+ static base::subtle::Atomic64 totalDelayNanos_;
+
void SlowLock();
void SlowUnlock();
Atomic32 SpinLoop();
diff --git a/src/third_party/gperftools-2.5/src/page_heap.cc b/src/third_party/gperftools-2.5/src/page_heap.cc
index f52ae2af029..eaa33255f2c 100644
--- a/src/third_party/gperftools-2.5/src/page_heap.cc
+++ b/src/third_party/gperftools-2.5/src/page_heap.cc
@@ -245,16 +245,22 @@ Span* PageHeap::Split(Span* span, Length n) {
}
void PageHeap::CommitSpan(Span* span) {
+ ++stats_.commit_count;
+
TCMalloc_SystemCommit(reinterpret_cast<void*>(span->start << kPageShift),
static_cast<size_t>(span->length << kPageShift));
stats_.committed_bytes += span->length << kPageShift;
+ stats_.total_commit_bytes += (span->length << kPageShift);
}
bool PageHeap::DecommitSpan(Span* span) {
+ ++stats_.decommit_count;
+
bool rv = TCMalloc_SystemRelease(reinterpret_cast<void*>(span->start << kPageShift),
static_cast<size_t>(span->length << kPageShift));
if (rv) {
stats_.committed_bytes -= span->length << kPageShift;
+ stats_.total_decommit_bytes += (span->length << kPageShift);
}
return rv;
@@ -441,6 +447,8 @@ void PageHeap::IncrementalScavenge(Length n) {
return;
}
+ ++stats_.scavenge_count;
+
Length released_pages = ReleaseAtLeastNPages(1);
if (released_pages == 0) {
@@ -616,9 +624,16 @@ bool PageHeap::GrowHeap(Length n) {
ask = actual_size >> kPageShift;
RecordGrowth(ask << kPageShift);
+ ++stats_.reserve_count;
+ ++stats_.commit_count;
+
uint64_t old_system_bytes = stats_.system_bytes;
stats_.system_bytes += (ask << kPageShift);
stats_.committed_bytes += (ask << kPageShift);
+
+ stats_.total_commit_bytes += (ask << kPageShift);
+ stats_.total_reserve_bytes += (ask << kPageShift);
+
const PageID p = reinterpret_cast<uintptr_t>(ptr) >> kPageShift;
ASSERT(p > 0);
diff --git a/src/third_party/gperftools-2.5/src/page_heap.h b/src/third_party/gperftools-2.5/src/page_heap.h
index 18abed1974a..5c6607bbf16 100644
--- a/src/third_party/gperftools-2.5/src/page_heap.h
+++ b/src/third_party/gperftools-2.5/src/page_heap.h
@@ -143,12 +143,24 @@ class PERFTOOLS_DLL_DECL PageHeap {
// Page heap statistics
struct Stats {
- Stats() : system_bytes(0), free_bytes(0), unmapped_bytes(0), committed_bytes(0) {}
+ Stats() : system_bytes(0), free_bytes(0), unmapped_bytes(0), committed_bytes(0),
+ scavenge_count(0), commit_count(0), decommit_count(0),
+ total_commit_bytes(0), total_decommit_bytes(0),
+ reserve_count(0), total_reserve_bytes(0) {}
uint64_t system_bytes; // Total bytes allocated from system
uint64_t free_bytes; // Total bytes on normal freelists
uint64_t unmapped_bytes; // Total bytes on returned freelists
uint64_t committed_bytes; // Bytes committed, always <= system_bytes_.
+ uint64_t scavenge_count; // Number of times scavagened flush pages
+
+ uint64_t commit_count; // Number of virtual memory commits
+ uint64_t total_commit_bytes; // Bytes committed in lifetime of process
+ uint64_t decommit_count; // Number of virtual memory decommits
+ uint64_t total_decommit_bytes; // Bytes decommitted in lifetime of process
+
+ uint64_t reserve_count; // Number of virtual memory reserves
+ uint64_t total_reserve_bytes; // Bytes reserved in lifetime of process
};
inline Stats stats() const { return stats_; }
diff --git a/src/third_party/gperftools-2.5/src/tcmalloc.cc b/src/third_party/gperftools-2.5/src/tcmalloc.cc
index ba6c06e9d89..b573b4a9821 100644
--- a/src/third_party/gperftools-2.5/src/tcmalloc.cc
+++ b/src/third_party/gperftools-2.5/src/tcmalloc.cc
@@ -703,6 +703,54 @@ class TCMallocImplementation : public MallocExtension {
return true;
}
+ if (strcmp(name, "tcmalloc.pageheap_committed_bytes") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().committed_bytes;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_scavenge_count") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().scavenge_count;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_commit_count") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().commit_count;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_total_commit_bytes") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().total_commit_bytes;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_decommit_count") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().decommit_count;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_total_decommit_bytes") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().total_decommit_bytes;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_reserve_count") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().reserve_count;
+ return true;
+ }
+
+ if (strcmp(name, "tcmalloc.pageheap_total_reserve_bytes") == 0) {
+ SpinLockHolder l(Static::pageheap_lock());
+ *value = Static::pageheap()->stats().total_reserve_bytes;
+ return true;
+ }
+
if (strcmp(name, "tcmalloc.max_total_thread_cache_bytes") == 0) {
SpinLockHolder l(Static::pageheap_lock());
*value = ThreadCache::overall_thread_cache_size();
@@ -721,6 +769,11 @@ class TCMallocImplementation : public MallocExtension {
return true;
}
+ if (strcmp(name, "tcmalloc.spinlock_total_delay_ns") == 0) {
+ *value = SpinLock::GetTotalDelayNanos();
+ return true;
+ }
+
return false;
}
diff --git a/src/third_party/pcre-8.39/132html b/src/third_party/pcre-8.41/132html
index e00024970a5..e00024970a5 100755
--- a/src/third_party/pcre-8.39/132html
+++ b/src/third_party/pcre-8.41/132html
diff --git a/src/third_party/pcre-8.39/AUTHORS b/src/third_party/pcre-8.41/AUTHORS
index 342417a8a19..291657caef1 100644
--- a/src/third_party/pcre-8.39/AUTHORS
+++ b/src/third_party/pcre-8.41/AUTHORS
@@ -8,7 +8,7 @@ Email domain: cam.ac.uk
University of Cambridge Computing Service,
Cambridge, England.
-Copyright (c) 1997-2016 University of Cambridge
+Copyright (c) 1997-2017 University of Cambridge
All rights reserved
@@ -19,7 +19,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Emain domain: freemail.hu
-Copyright(c) 2010-2016 Zoltan Herczeg
+Copyright(c) 2010-2017 Zoltan Herczeg
All rights reserved.
@@ -30,7 +30,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Emain domain: freemail.hu
-Copyright(c) 2009-2016 Zoltan Herczeg
+Copyright(c) 2009-2017 Zoltan Herczeg
All rights reserved.
diff --git a/src/third_party/pcre-8.39/COPYING b/src/third_party/pcre-8.41/COPYING
index 58eed01b61d..58eed01b61d 100644
--- a/src/third_party/pcre-8.39/COPYING
+++ b/src/third_party/pcre-8.41/COPYING
diff --git a/src/third_party/pcre-8.39/ChangeLog b/src/third_party/pcre-8.41/ChangeLog
index a34f845f8a1..590a7542885 100644
--- a/src/third_party/pcre-8.39/ChangeLog
+++ b/src/third_party/pcre-8.41/ChangeLog
@@ -4,6 +4,100 @@ ChangeLog for PCRE
Note that the PCRE 8.xx series (PCRE1) is now in a bugfix-only state. All
development is happening in the PCRE2 10.xx series.
+Version 8.41 05-July-2017
+-------------------------
+
+1. Fixed typo in CMakeLists.txt (wrong number of arguments for
+PCRE_STATIC_RUNTIME (affects MSVC only).
+
+2. Issue 1 for 8.40 below was not correctly fixed. If pcregrep in multiline
+mode with --only-matching matched several lines, it restarted scanning at the
+next line instead of moving on to the end of the matched string, which can be
+several lines after the start.
+
+3. Fix a missing else in the JIT compiler reported by 'idaifish'.
+
+4. A (?# style comment is now ignored between a basic quantifier and a
+following '+' or '?' (example: /X+(?#comment)?Y/.
+
+5. Avoid use of a potentially overflowing buffer in pcregrep (patch by Petr
+Pisar).
+
+6. Fuzzers have reported issues in pcretest. These are NOT serious (it is,
+after all, just a test program). However, to stop the reports, some easy ones
+are fixed:
+
+ (a) Check for values < 256 when calling isprint() in pcretest.
+ (b) Give an error for too big a number after \O.
+
+7. In the 32-bit library in non-UTF mode, an attempt to find a Unicode
+property for a character with a code point greater than 0x10ffff (the Unicode
+maximum) caused a crash.
+
+8. The alternative matching function, pcre_dfa_exec() misbehaved if it
+encountered a character class with a possessive repeat, for example [a-f]{3}+.
+
+9. When pcretest called pcre_copy_substring() in 32-bit mode, it set the buffer
+length incorrectly, which could result in buffer overflow.
+
+10. Remove redundant line of code (accidentally left in ages ago).
+
+11. Applied C++ patch from Irfan Adilovic to guard 'using std::' directives
+with namespace pcrecpp (Bugzilla #2084).
+
+12. Remove a duplication typo in pcre_tables.c.
+
+13. Fix returned offsets from regexec() when REG_STARTEND is used with a
+starting offset greater than zero.
+
+
+Version 8.40 11-January-2017
+----------------------------
+
+1. Using -o with -M in pcregrep could cause unnecessary repeated output when
+ the match extended over a line boundary.
+
+2. Applied Chris Wilson's second patch (Bugzilla #1681) to CMakeLists.txt for
+ MSVC static compilation, putting the first patch under a new option.
+
+3. Fix register overwite in JIT when SSE2 acceleration is enabled.
+
+4. Ignore "show all captures" (/=) for DFA matching.
+
+5. Fix JIT unaligned accesses on x86. Patch by Marc Mutz.
+
+6. In any wide-character mode (8-bit UTF or any 16-bit or 32-bit mode),
+ without PCRE_UCP set, a negative character type such as \D in a positive
+ class should cause all characters greater than 255 to match, whatever else
+ is in the class. There was a bug that caused this not to happen if a
+ Unicode property item was added to such a class, for example [\D\P{Nd}] or
+ [\W\pL].
+
+7. When pcretest was outputing information from a callout, the caret indicator
+ for the current position in the subject line was incorrect if it was after
+ an escape sequence for a character whose code point was greater than
+ \x{ff}.
+
+8. A pattern such as (?<RA>abc)(?(R)xyz) was incorrectly compiled such that
+ the conditional was interpreted as a reference to capturing group 1 instead
+ of a test for recursion. Any group whose name began with R was
+ misinterpreted in this way. (The reference interpretation should only
+ happen if the group's name is precisely "R".)
+
+9. A number of bugs have been mended relating to match start-up optimizations
+ when the first thing in a pattern is a positive lookahead. These all
+ applied only when PCRE_NO_START_OPTIMIZE was *not* set:
+
+ (a) A pattern such as (?=.*X)X$ was incorrectly optimized as if it needed
+ both an initial 'X' and a following 'X'.
+ (b) Some patterns starting with an assertion that started with .* were
+ incorrectly optimized as having to match at the start of the subject or
+ after a newline. There are cases where this is not true, for example,
+ (?=.*[A-Z])(?=.{8,16})(?!.*[\s]) matches after the start in lines that
+ start with spaces. Starting .* in an assertion is no longer taken as an
+ indication of matching at the start (or after a newline).
+
+
Version 8.39 14-June-2016
-------------------------
diff --git a/src/third_party/pcre-8.39/CheckMan b/src/third_party/pcre-8.41/CheckMan
index 480d735481e..480d735481e 100755
--- a/src/third_party/pcre-8.39/CheckMan
+++ b/src/third_party/pcre-8.41/CheckMan
diff --git a/src/third_party/pcre-8.39/CleanTxt b/src/third_party/pcre-8.41/CleanTxt
index 1f42519c8d7..1f42519c8d7 100755
--- a/src/third_party/pcre-8.39/CleanTxt
+++ b/src/third_party/pcre-8.41/CleanTxt
diff --git a/src/third_party/pcre-8.39/Detrail b/src/third_party/pcre-8.41/Detrail
index 1c5c7e9cae1..1c5c7e9cae1 100755
--- a/src/third_party/pcre-8.39/Detrail
+++ b/src/third_party/pcre-8.41/Detrail
diff --git a/src/third_party/pcre-8.39/HACKING b/src/third_party/pcre-8.41/HACKING
index 691b7a14e50..691b7a14e50 100644
--- a/src/third_party/pcre-8.39/HACKING
+++ b/src/third_party/pcre-8.41/HACKING
diff --git a/src/third_party/pcre-8.39/INSTALL b/src/third_party/pcre-8.41/INSTALL
index 2099840756e..2099840756e 100644
--- a/src/third_party/pcre-8.39/INSTALL
+++ b/src/third_party/pcre-8.41/INSTALL
diff --git a/src/third_party/pcre-8.39/LICENCE b/src/third_party/pcre-8.41/LICENCE
index dd977af971b..dd9071a8dd8 100644
--- a/src/third_party/pcre-8.39/LICENCE
+++ b/src/third_party/pcre-8.41/LICENCE
@@ -25,7 +25,7 @@ Email domain: cam.ac.uk
University of Cambridge Computing Service,
Cambridge, England.
-Copyright (c) 1997-2016 University of Cambridge
+Copyright (c) 1997-2017 University of Cambridge
All rights reserved.
@@ -36,7 +36,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Emain domain: freemail.hu
-Copyright(c) 2010-2016 Zoltan Herczeg
+Copyright(c) 2010-2017 Zoltan Herczeg
All rights reserved.
@@ -47,7 +47,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Emain domain: freemail.hu
-Copyright(c) 2009-2016 Zoltan Herczeg
+Copyright(c) 2009-2017 Zoltan Herczeg
All rights reserved.
diff --git a/src/third_party/pcre-8.39/NEWS b/src/third_party/pcre-8.41/NEWS
index 0ca1bab2a4c..36be07cb880 100644
--- a/src/third_party/pcre-8.39/NEWS
+++ b/src/third_party/pcre-8.41/NEWS
@@ -1,6 +1,18 @@
News about PCRE releases
------------------------
+Release 8.41 13-June-2017
+-------------------------
+
+This is a bug-fix release.
+
+
+Release 8.40 11-January-2017
+----------------------------
+
+This is a bug-fix release.
+
+
Release 8.39 14-June-2016
-------------------------
diff --git a/src/third_party/pcre-8.39/NON-AUTOTOOLS-BUILD b/src/third_party/pcre-8.41/NON-AUTOTOOLS-BUILD
index 3910059106b..3910059106b 100644
--- a/src/third_party/pcre-8.39/NON-AUTOTOOLS-BUILD
+++ b/src/third_party/pcre-8.41/NON-AUTOTOOLS-BUILD
diff --git a/src/third_party/pcre-8.39/NON-UNIX-USE b/src/third_party/pcre-8.41/NON-UNIX-USE
index a25546b6ff5..a25546b6ff5 100644
--- a/src/third_party/pcre-8.39/NON-UNIX-USE
+++ b/src/third_party/pcre-8.41/NON-UNIX-USE
diff --git a/src/third_party/pcre-8.39/PrepareRelease b/src/third_party/pcre-8.41/PrepareRelease
index 9891e08d67f..9891e08d67f 100755
--- a/src/third_party/pcre-8.39/PrepareRelease
+++ b/src/third_party/pcre-8.41/PrepareRelease
diff --git a/src/third_party/pcre-8.39/README b/src/third_party/pcre-8.41/README
index 4887ebf350e..4887ebf350e 100644
--- a/src/third_party/pcre-8.39/README
+++ b/src/third_party/pcre-8.41/README
diff --git a/src/third_party/pcre-8.39/RunGrepTest b/src/third_party/pcre-8.41/RunGrepTest
index 721ec5184ab..721ec5184ab 100755
--- a/src/third_party/pcre-8.39/RunGrepTest
+++ b/src/third_party/pcre-8.41/RunGrepTest
diff --git a/src/third_party/pcre-8.39/RunTest b/src/third_party/pcre-8.41/RunTest
index 357a739b75f..357a739b75f 100755
--- a/src/third_party/pcre-8.39/RunTest
+++ b/src/third_party/pcre-8.41/RunTest
diff --git a/src/third_party/pcre-8.39/RunTest.bat b/src/third_party/pcre-8.41/RunTest.bat
index 35d7f71f9e8..35d7f71f9e8 100644
--- a/src/third_party/pcre-8.39/RunTest.bat
+++ b/src/third_party/pcre-8.41/RunTest.bat
diff --git a/src/third_party/pcre-8.39/SConscript b/src/third_party/pcre-8.41/SConscript
index e7b9a59a550..e7b9a59a550 100644
--- a/src/third_party/pcre-8.39/SConscript
+++ b/src/third_party/pcre-8.41/SConscript
diff --git a/src/third_party/pcre-8.39/ar-lib b/src/third_party/pcre-8.41/ar-lib
index 463b9ec0206..463b9ec0206 100755
--- a/src/third_party/pcre-8.39/ar-lib
+++ b/src/third_party/pcre-8.41/ar-lib
diff --git a/src/third_party/pcre-8.39/build_posix/config.h b/src/third_party/pcre-8.41/build_posix/config.h
index 9cfa5a4d70f..8af8c7add1e 100644
--- a/src/third_party/pcre-8.39/build_posix/config.h
+++ b/src/third_party/pcre-8.41/build_posix/config.h
@@ -221,7 +221,7 @@ sure both macros are undefined; an emulation function will then be used. */
#define PACKAGE_NAME "PCRE"
/* Define to the full name and version of this package. */
-#define PACKAGE_STRING "PCRE 8.39"
+#define PACKAGE_STRING "PCRE 8.41"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "pcre"
@@ -230,7 +230,7 @@ sure both macros are undefined; an emulation function will then be used. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
-#define PACKAGE_VERSION "8.39"
+#define PACKAGE_VERSION "8.41"
/* The value of PARENS_NEST_LIMIT specifies the maximum depth of nested
parentheses (of any kind) in a pattern. This limits the amount of system
@@ -335,7 +335,7 @@ sure both macros are undefined; an emulation function will then be used. */
/* #undef SUPPORT_VALGRIND */
/* Version number of package */
-#define VERSION "8.39"
+#define VERSION "8.41"
/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */
diff --git a/src/third_party/pcre-8.39/build_solaris/config.h b/src/third_party/pcre-8.41/build_solaris/config.h
index a391fd1cee5..164bd8a2e39 100644
--- a/src/third_party/pcre-8.39/build_solaris/config.h
+++ b/src/third_party/pcre-8.41/build_solaris/config.h
@@ -221,7 +221,7 @@ sure both macros are undefined; an emulation function will then be used. */
#define PACKAGE_NAME "PCRE"
/* Define to the full name and version of this package. */
-#define PACKAGE_STRING "PCRE 8.39"
+#define PACKAGE_STRING "PCRE 8.41"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "pcre"
@@ -230,7 +230,7 @@ sure both macros are undefined; an emulation function will then be used. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
-#define PACKAGE_VERSION "8.39"
+#define PACKAGE_VERSION "8.41"
/* The value of PARENS_NEST_LIMIT specifies the maximum depth of nested
parentheses (of any kind) in a pattern. This limits the amount of system
@@ -335,7 +335,7 @@ sure both macros are undefined; an emulation function will then be used. */
/* #undef SUPPORT_VALGRIND */
/* Version number of package */
-#define VERSION "8.39"
+#define VERSION "8.41"
/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */
diff --git a/src/third_party/pcre-8.39/build_windows/config.h b/src/third_party/pcre-8.41/build_windows/config.h
index 2e8d4ec7b6a..2e8d4ec7b6a 100755
--- a/src/third_party/pcre-8.39/build_windows/config.h
+++ b/src/third_party/pcre-8.41/build_windows/config.h
diff --git a/src/third_party/pcre-8.39/dftables.c b/src/third_party/pcre-8.41/dftables.c
index 1fdc8e0f231..1fdc8e0f231 100644
--- a/src/third_party/pcre-8.39/dftables.c
+++ b/src/third_party/pcre-8.41/dftables.c
diff --git a/src/third_party/pcre-8.39/libpcre.pc.in b/src/third_party/pcre-8.41/libpcre.pc.in
index 0a35da87f85..0a35da87f85 100644
--- a/src/third_party/pcre-8.39/libpcre.pc.in
+++ b/src/third_party/pcre-8.41/libpcre.pc.in
diff --git a/src/third_party/pcre-8.39/libpcre16.pc.in b/src/third_party/pcre-8.41/libpcre16.pc.in
index 080c9dcfe8a..080c9dcfe8a 100644
--- a/src/third_party/pcre-8.39/libpcre16.pc.in
+++ b/src/third_party/pcre-8.41/libpcre16.pc.in
diff --git a/src/third_party/pcre-8.39/libpcre32.pc.in b/src/third_party/pcre-8.41/libpcre32.pc.in
index a3ae0e11fcf..a3ae0e11fcf 100644
--- a/src/third_party/pcre-8.39/libpcre32.pc.in
+++ b/src/third_party/pcre-8.41/libpcre32.pc.in
diff --git a/src/third_party/pcre-8.39/libpcrecpp.pc.in b/src/third_party/pcre-8.41/libpcrecpp.pc.in
index ef006fe47aa..ef006fe47aa 100644
--- a/src/third_party/pcre-8.39/libpcrecpp.pc.in
+++ b/src/third_party/pcre-8.41/libpcrecpp.pc.in
diff --git a/src/third_party/pcre-8.39/libpcreposix.pc.in b/src/third_party/pcre-8.41/libpcreposix.pc.in
index c6c0b0c6c66..c6c0b0c6c66 100644
--- a/src/third_party/pcre-8.39/libpcreposix.pc.in
+++ b/src/third_party/pcre-8.41/libpcreposix.pc.in
diff --git a/src/third_party/pcre-8.39/makevp.bat b/src/third_party/pcre-8.41/makevp.bat
index 5f795487eb9..5f795487eb9 100644
--- a/src/third_party/pcre-8.39/makevp.bat
+++ b/src/third_party/pcre-8.41/makevp.bat
diff --git a/src/third_party/pcre-8.39/makevp_c.txt b/src/third_party/pcre-8.41/makevp_c.txt
index 5648115405d..5648115405d 100644
--- a/src/third_party/pcre-8.39/makevp_c.txt
+++ b/src/third_party/pcre-8.41/makevp_c.txt
diff --git a/src/third_party/pcre-8.39/makevp_l.txt b/src/third_party/pcre-8.41/makevp_l.txt
index 9b071e0f90a..9b071e0f90a 100644
--- a/src/third_party/pcre-8.39/makevp_l.txt
+++ b/src/third_party/pcre-8.41/makevp_l.txt
diff --git a/src/third_party/pcre-8.39/pcre-config.in b/src/third_party/pcre-8.41/pcre-config.in
index ac06a3325bc..ac06a3325bc 100644
--- a/src/third_party/pcre-8.39/pcre-config.in
+++ b/src/third_party/pcre-8.41/pcre-config.in
diff --git a/src/third_party/pcre-8.39/pcre.h b/src/third_party/pcre-8.41/pcre.h
index 70559700654..442c6bdb5fe 100644
--- a/src/third_party/pcre-8.39/pcre.h
+++ b/src/third_party/pcre-8.41/pcre.h
@@ -42,9 +42,9 @@ POSSIBILITY OF SUCH DAMAGE.
/* The current PCRE version information. */
#define PCRE_MAJOR 8
-#define PCRE_MINOR 39
+#define PCRE_MINOR 41
#define PCRE_PRERELEASE
-#define PCRE_DATE 2016-06-14
+#define PCRE_DATE 2017-07-05
/* When an application links to a PCRE DLL in Windows, the symbols that are
imported have to be identified as such. When building PCRE, the appropriate
diff --git a/src/third_party/pcre-8.39/pcre.h.generic b/src/third_party/pcre-8.41/pcre.h.generic
index 70559700654..442c6bdb5fe 100644
--- a/src/third_party/pcre-8.39/pcre.h.generic
+++ b/src/third_party/pcre-8.41/pcre.h.generic
@@ -42,9 +42,9 @@ POSSIBILITY OF SUCH DAMAGE.
/* The current PCRE version information. */
#define PCRE_MAJOR 8
-#define PCRE_MINOR 39
+#define PCRE_MINOR 41
#define PCRE_PRERELEASE
-#define PCRE_DATE 2016-06-14
+#define PCRE_DATE 2017-07-05
/* When an application links to a PCRE DLL in Windows, the symbols that are
imported have to be identified as such. When building PCRE, the appropriate
diff --git a/src/third_party/pcre-8.39/pcre.h.in b/src/third_party/pcre-8.41/pcre.h.in
index 667a45ed575..667a45ed575 100644
--- a/src/third_party/pcre-8.39/pcre.h.in
+++ b/src/third_party/pcre-8.41/pcre.h.in
diff --git a/src/third_party/pcre-8.39/pcre16_byte_order.c b/src/third_party/pcre-8.41/pcre16_byte_order.c
index 11d2973a3db..11d2973a3db 100644
--- a/src/third_party/pcre-8.39/pcre16_byte_order.c
+++ b/src/third_party/pcre-8.41/pcre16_byte_order.c
diff --git a/src/third_party/pcre-8.39/pcre16_chartables.c b/src/third_party/pcre-8.41/pcre16_chartables.c
index 7c0ff35f5e0..7c0ff35f5e0 100644
--- a/src/third_party/pcre-8.39/pcre16_chartables.c
+++ b/src/third_party/pcre-8.41/pcre16_chartables.c
diff --git a/src/third_party/pcre-8.39/pcre16_compile.c b/src/third_party/pcre-8.41/pcre16_compile.c
index e499b670877..e499b670877 100644
--- a/src/third_party/pcre-8.39/pcre16_compile.c
+++ b/src/third_party/pcre-8.41/pcre16_compile.c
diff --git a/src/third_party/pcre-8.39/pcre16_config.c b/src/third_party/pcre-8.41/pcre16_config.c
index b52138764f6..b52138764f6 100644
--- a/src/third_party/pcre-8.39/pcre16_config.c
+++ b/src/third_party/pcre-8.41/pcre16_config.c
diff --git a/src/third_party/pcre-8.39/pcre16_dfa_exec.c b/src/third_party/pcre-8.41/pcre16_dfa_exec.c
index 2ba740e972b..2ba740e972b 100644
--- a/src/third_party/pcre-8.39/pcre16_dfa_exec.c
+++ b/src/third_party/pcre-8.41/pcre16_dfa_exec.c
diff --git a/src/third_party/pcre-8.39/pcre16_exec.c b/src/third_party/pcre-8.41/pcre16_exec.c
index 7417b1770c6..7417b1770c6 100644
--- a/src/third_party/pcre-8.39/pcre16_exec.c
+++ b/src/third_party/pcre-8.41/pcre16_exec.c
diff --git a/src/third_party/pcre-8.39/pcre16_fullinfo.c b/src/third_party/pcre-8.41/pcre16_fullinfo.c
index 544dca6ed5c..544dca6ed5c 100644
--- a/src/third_party/pcre-8.39/pcre16_fullinfo.c
+++ b/src/third_party/pcre-8.41/pcre16_fullinfo.c
diff --git a/src/third_party/pcre-8.39/pcre16_get.c b/src/third_party/pcre-8.41/pcre16_get.c
index 3ded08c622c..3ded08c622c 100644
--- a/src/third_party/pcre-8.39/pcre16_get.c
+++ b/src/third_party/pcre-8.41/pcre16_get.c
diff --git a/src/third_party/pcre-8.39/pcre16_globals.c b/src/third_party/pcre-8.41/pcre16_globals.c
index a136b3d8c22..a136b3d8c22 100644
--- a/src/third_party/pcre-8.39/pcre16_globals.c
+++ b/src/third_party/pcre-8.41/pcre16_globals.c
diff --git a/src/third_party/pcre-8.39/pcre16_jit_compile.c b/src/third_party/pcre-8.41/pcre16_jit_compile.c
index ab0cacd7646..ab0cacd7646 100644
--- a/src/third_party/pcre-8.39/pcre16_jit_compile.c
+++ b/src/third_party/pcre-8.41/pcre16_jit_compile.c
diff --git a/src/third_party/pcre-8.39/pcre16_maketables.c b/src/third_party/pcre-8.41/pcre16_maketables.c
index b1cd1c579d6..b1cd1c579d6 100644
--- a/src/third_party/pcre-8.39/pcre16_maketables.c
+++ b/src/third_party/pcre-8.41/pcre16_maketables.c
diff --git a/src/third_party/pcre-8.39/pcre16_newline.c b/src/third_party/pcre-8.41/pcre16_newline.c
index 7fe201400f5..7fe201400f5 100644
--- a/src/third_party/pcre-8.39/pcre16_newline.c
+++ b/src/third_party/pcre-8.41/pcre16_newline.c
diff --git a/src/third_party/pcre-8.39/pcre16_ord2utf16.c b/src/third_party/pcre-8.41/pcre16_ord2utf16.c
index 8e2ce5ea6c5..8e2ce5ea6c5 100644
--- a/src/third_party/pcre-8.39/pcre16_ord2utf16.c
+++ b/src/third_party/pcre-8.41/pcre16_ord2utf16.c
diff --git a/src/third_party/pcre-8.39/pcre16_printint.c b/src/third_party/pcre-8.41/pcre16_printint.c
index 33d8c340200..33d8c340200 100644
--- a/src/third_party/pcre-8.39/pcre16_printint.c
+++ b/src/third_party/pcre-8.41/pcre16_printint.c
diff --git a/src/third_party/pcre-8.39/pcre16_refcount.c b/src/third_party/pcre-8.41/pcre16_refcount.c
index d3d15439737..d3d15439737 100644
--- a/src/third_party/pcre-8.39/pcre16_refcount.c
+++ b/src/third_party/pcre-8.41/pcre16_refcount.c
diff --git a/src/third_party/pcre-8.39/pcre16_string_utils.c b/src/third_party/pcre-8.41/pcre16_string_utils.c
index 382c40799fb..382c40799fb 100644
--- a/src/third_party/pcre-8.39/pcre16_string_utils.c
+++ b/src/third_party/pcre-8.41/pcre16_string_utils.c
diff --git a/src/third_party/pcre-8.39/pcre16_study.c b/src/third_party/pcre-8.41/pcre16_study.c
index f87de081fc4..f87de081fc4 100644
--- a/src/third_party/pcre-8.39/pcre16_study.c
+++ b/src/third_party/pcre-8.41/pcre16_study.c
diff --git a/src/third_party/pcre-8.39/pcre16_tables.c b/src/third_party/pcre-8.41/pcre16_tables.c
index d84297093a4..d84297093a4 100644
--- a/src/third_party/pcre-8.39/pcre16_tables.c
+++ b/src/third_party/pcre-8.41/pcre16_tables.c
diff --git a/src/third_party/pcre-8.39/pcre16_ucd.c b/src/third_party/pcre-8.41/pcre16_ucd.c
index ee23439a013..ee23439a013 100644
--- a/src/third_party/pcre-8.39/pcre16_ucd.c
+++ b/src/third_party/pcre-8.41/pcre16_ucd.c
diff --git a/src/third_party/pcre-8.39/pcre16_utf16_utils.c b/src/third_party/pcre-8.41/pcre16_utf16_utils.c
index 49ced0c0b1c..49ced0c0b1c 100644
--- a/src/third_party/pcre-8.39/pcre16_utf16_utils.c
+++ b/src/third_party/pcre-8.41/pcre16_utf16_utils.c
diff --git a/src/third_party/pcre-8.39/pcre16_valid_utf16.c b/src/third_party/pcre-8.41/pcre16_valid_utf16.c
index 09076539d09..09076539d09 100644
--- a/src/third_party/pcre-8.39/pcre16_valid_utf16.c
+++ b/src/third_party/pcre-8.41/pcre16_valid_utf16.c
diff --git a/src/third_party/pcre-8.39/pcre16_version.c b/src/third_party/pcre-8.41/pcre16_version.c
index e991b1a8cfd..e991b1a8cfd 100644
--- a/src/third_party/pcre-8.39/pcre16_version.c
+++ b/src/third_party/pcre-8.41/pcre16_version.c
diff --git a/src/third_party/pcre-8.39/pcre16_xclass.c b/src/third_party/pcre-8.41/pcre16_xclass.c
index 5aac2a36c68..5aac2a36c68 100644
--- a/src/third_party/pcre-8.39/pcre16_xclass.c
+++ b/src/third_party/pcre-8.41/pcre16_xclass.c
diff --git a/src/third_party/pcre-8.39/pcre32_byte_order.c b/src/third_party/pcre-8.41/pcre32_byte_order.c
index 9cf5362730a..9cf5362730a 100644
--- a/src/third_party/pcre-8.39/pcre32_byte_order.c
+++ b/src/third_party/pcre-8.41/pcre32_byte_order.c
diff --git a/src/third_party/pcre-8.39/pcre32_chartables.c b/src/third_party/pcre-8.41/pcre32_chartables.c
index b5d8c23dbf1..b5d8c23dbf1 100644
--- a/src/third_party/pcre-8.39/pcre32_chartables.c
+++ b/src/third_party/pcre-8.41/pcre32_chartables.c
diff --git a/src/third_party/pcre-8.39/pcre32_compile.c b/src/third_party/pcre-8.41/pcre32_compile.c
index d781eb377e0..d781eb377e0 100644
--- a/src/third_party/pcre-8.39/pcre32_compile.c
+++ b/src/third_party/pcre-8.41/pcre32_compile.c
diff --git a/src/third_party/pcre-8.39/pcre32_config.c b/src/third_party/pcre-8.41/pcre32_config.c
index d63f3e9ea23..d63f3e9ea23 100644
--- a/src/third_party/pcre-8.39/pcre32_config.c
+++ b/src/third_party/pcre-8.41/pcre32_config.c
diff --git a/src/third_party/pcre-8.39/pcre32_dfa_exec.c b/src/third_party/pcre-8.41/pcre32_dfa_exec.c
index b0bfd34f04d..b0bfd34f04d 100644
--- a/src/third_party/pcre-8.39/pcre32_dfa_exec.c
+++ b/src/third_party/pcre-8.41/pcre32_dfa_exec.c
diff --git a/src/third_party/pcre-8.39/pcre32_exec.c b/src/third_party/pcre-8.41/pcre32_exec.c
index 8170ed77d35..8170ed77d35 100644
--- a/src/third_party/pcre-8.39/pcre32_exec.c
+++ b/src/third_party/pcre-8.41/pcre32_exec.c
diff --git a/src/third_party/pcre-8.39/pcre32_fullinfo.c b/src/third_party/pcre-8.41/pcre32_fullinfo.c
index 6ecc5209a08..6ecc5209a08 100644
--- a/src/third_party/pcre-8.39/pcre32_fullinfo.c
+++ b/src/third_party/pcre-8.41/pcre32_fullinfo.c
diff --git a/src/third_party/pcre-8.39/pcre32_get.c b/src/third_party/pcre-8.41/pcre32_get.c
index d35deee0cd4..d35deee0cd4 100644
--- a/src/third_party/pcre-8.39/pcre32_get.c
+++ b/src/third_party/pcre-8.41/pcre32_get.c
diff --git a/src/third_party/pcre-8.39/pcre32_globals.c b/src/third_party/pcre-8.41/pcre32_globals.c
index 32e0914ca6d..32e0914ca6d 100644
--- a/src/third_party/pcre-8.39/pcre32_globals.c
+++ b/src/third_party/pcre-8.41/pcre32_globals.c
diff --git a/src/third_party/pcre-8.39/pcre32_jit_compile.c b/src/third_party/pcre-8.41/pcre32_jit_compile.c
index 2e7c6f97c96..2e7c6f97c96 100644
--- a/src/third_party/pcre-8.39/pcre32_jit_compile.c
+++ b/src/third_party/pcre-8.41/pcre32_jit_compile.c
diff --git a/src/third_party/pcre-8.39/pcre32_maketables.c b/src/third_party/pcre-8.41/pcre32_maketables.c
index 5d1b1c64c96..5d1b1c64c96 100644
--- a/src/third_party/pcre-8.39/pcre32_maketables.c
+++ b/src/third_party/pcre-8.41/pcre32_maketables.c
diff --git a/src/third_party/pcre-8.39/pcre32_newline.c b/src/third_party/pcre-8.41/pcre32_newline.c
index 7f8d5360cdc..7f8d5360cdc 100644
--- a/src/third_party/pcre-8.39/pcre32_newline.c
+++ b/src/third_party/pcre-8.41/pcre32_newline.c
diff --git a/src/third_party/pcre-8.39/pcre32_ord2utf32.c b/src/third_party/pcre-8.41/pcre32_ord2utf32.c
index 606bcb3d7ba..606bcb3d7ba 100644
--- a/src/third_party/pcre-8.39/pcre32_ord2utf32.c
+++ b/src/third_party/pcre-8.41/pcre32_ord2utf32.c
diff --git a/src/third_party/pcre-8.39/pcre32_printint.c b/src/third_party/pcre-8.41/pcre32_printint.c
index f3fd7b25e2c..f3fd7b25e2c 100644
--- a/src/third_party/pcre-8.39/pcre32_printint.c
+++ b/src/third_party/pcre-8.41/pcre32_printint.c
diff --git a/src/third_party/pcre-8.39/pcre32_refcount.c b/src/third_party/pcre-8.41/pcre32_refcount.c
index dbdf432d82a..dbdf432d82a 100644
--- a/src/third_party/pcre-8.39/pcre32_refcount.c
+++ b/src/third_party/pcre-8.41/pcre32_refcount.c
diff --git a/src/third_party/pcre-8.39/pcre32_string_utils.c b/src/third_party/pcre-8.41/pcre32_string_utils.c
index e37b3d4805f..e37b3d4805f 100644
--- a/src/third_party/pcre-8.39/pcre32_string_utils.c
+++ b/src/third_party/pcre-8.41/pcre32_string_utils.c
diff --git a/src/third_party/pcre-8.39/pcre32_study.c b/src/third_party/pcre-8.41/pcre32_study.c
index d3a3afed791..d3a3afed791 100644
--- a/src/third_party/pcre-8.39/pcre32_study.c
+++ b/src/third_party/pcre-8.41/pcre32_study.c
diff --git a/src/third_party/pcre-8.39/pcre32_tables.c b/src/third_party/pcre-8.41/pcre32_tables.c
index 3d94cca33a1..3d94cca33a1 100644
--- a/src/third_party/pcre-8.39/pcre32_tables.c
+++ b/src/third_party/pcre-8.41/pcre32_tables.c
diff --git a/src/third_party/pcre-8.39/pcre32_ucd.c b/src/third_party/pcre-8.41/pcre32_ucd.c
index befe22d3435..befe22d3435 100644
--- a/src/third_party/pcre-8.39/pcre32_ucd.c
+++ b/src/third_party/pcre-8.41/pcre32_ucd.c
diff --git a/src/third_party/pcre-8.39/pcre32_utf32_utils.c b/src/third_party/pcre-8.41/pcre32_utf32_utils.c
index f844e237165..f844e237165 100644
--- a/src/third_party/pcre-8.39/pcre32_utf32_utils.c
+++ b/src/third_party/pcre-8.41/pcre32_utf32_utils.c
diff --git a/src/third_party/pcre-8.39/pcre32_valid_utf32.c b/src/third_party/pcre-8.41/pcre32_valid_utf32.c
index 94cda1a2c4c..94cda1a2c4c 100644
--- a/src/third_party/pcre-8.39/pcre32_valid_utf32.c
+++ b/src/third_party/pcre-8.41/pcre32_valid_utf32.c
diff --git a/src/third_party/pcre-8.39/pcre32_version.c b/src/third_party/pcre-8.41/pcre32_version.c
index fdaad9b0859..fdaad9b0859 100644
--- a/src/third_party/pcre-8.39/pcre32_version.c
+++ b/src/third_party/pcre-8.41/pcre32_version.c
diff --git a/src/third_party/pcre-8.39/pcre32_xclass.c b/src/third_party/pcre-8.41/pcre32_xclass.c
index 5662408ad5f..5662408ad5f 100644
--- a/src/third_party/pcre-8.39/pcre32_xclass.c
+++ b/src/third_party/pcre-8.41/pcre32_xclass.c
diff --git a/src/third_party/pcre-8.39/pcre_byte_order.c b/src/third_party/pcre-8.41/pcre_byte_order.c
index cf5f12b04ea..cf5f12b04ea 100644
--- a/src/third_party/pcre-8.39/pcre_byte_order.c
+++ b/src/third_party/pcre-8.41/pcre_byte_order.c
diff --git a/src/third_party/pcre-8.39/pcre_chartables.c b/src/third_party/pcre-8.41/pcre_chartables.c
index 1e20ec29d05..1e20ec29d05 100644
--- a/src/third_party/pcre-8.39/pcre_chartables.c
+++ b/src/third_party/pcre-8.41/pcre_chartables.c
diff --git a/src/third_party/pcre-8.39/pcre_chartables.c.dist b/src/third_party/pcre-8.41/pcre_chartables.c.dist
index 1e20ec29d05..1e20ec29d05 100644
--- a/src/third_party/pcre-8.39/pcre_chartables.c.dist
+++ b/src/third_party/pcre-8.41/pcre_chartables.c.dist
diff --git a/src/third_party/pcre-8.39/pcre_compile.c b/src/third_party/pcre-8.41/pcre_compile.c
index 7cd39501230..42f204cdfff 100644
--- a/src/third_party/pcre-8.39/pcre_compile.c
+++ b/src/third_party/pcre-8.41/pcre_compile.c
@@ -5579,6 +5579,34 @@ for (;; ptr++)
#endif
#if defined SUPPORT_UTF || !defined COMPILE_PCRE8
{
+ /* For non-UCP wide characters, in a non-negative class containing \S or
+ similar (should_flip_negation is set), all characters greater than 255
+ must be in the class. */
+
+ if (
+#if defined COMPILE_PCRE8
+ utf &&
+#endif
+ should_flip_negation && !negate_class && (options & PCRE_UCP) == 0)
+ {
+ *class_uchardata++ = XCL_RANGE;
+ if (utf) /* Will always be utf in the 8-bit library */
+ {
+ class_uchardata += PRIV(ord2utf)(0x100, class_uchardata);
+ class_uchardata += PRIV(ord2utf)(0x10ffff, class_uchardata);
+ }
+ else /* Can only happen for the 16-bit & 32-bit libraries */
+ {
+#if defined COMPILE_PCRE16
+ *class_uchardata++ = 0x100;
+ *class_uchardata++ = 0xffffu;
+#elif defined COMPILE_PCRE32
+ *class_uchardata++ = 0x100;
+ *class_uchardata++ = 0xffffffffu;
+#endif
+ }
+ }
+
*class_uchardata++ = XCL_END; /* Marks the end of extra data */
*code++ = OP_XCLASS;
code += LINK_SIZE;
@@ -5711,6 +5739,21 @@ for (;; ptr++)
ptr = p - 1; /* Character before the next significant one. */
}
+ /* We also need to skip over (?# comments, which are not dependent on
+ extended mode. */
+
+ if (ptr[1] == CHAR_LEFT_PARENTHESIS && ptr[2] == CHAR_QUESTION_MARK &&
+ ptr[3] == CHAR_NUMBER_SIGN)
+ {
+ ptr += 4;
+ while (*ptr != CHAR_NULL && *ptr != CHAR_RIGHT_PARENTHESIS) ptr++;
+ if (*ptr == CHAR_NULL)
+ {
+ *errorcodeptr = ERR18;
+ goto FAILED;
+ }
+ }
+
/* If the next character is '+', we have a possessive quantifier. This
implies greediness, whatever the setting of the PCRE_UNGREEDY option.
If the next character is '?' this is a minimizing repeat, by default,
@@ -6923,7 +6966,8 @@ for (;; ptr++)
slot = cd->name_table;
for (i = 0; i < cd->names_found; i++)
{
- if (STRNCMP_UC_UC(name, slot+IMM2_SIZE, namelen) == 0) break;
+ if (STRNCMP_UC_UC(name, slot+IMM2_SIZE, namelen) == 0 &&
+ slot[IMM2_SIZE+namelen] == 0) break;
slot += cd->name_entry_size;
}
@@ -7889,15 +7933,17 @@ for (;; ptr++)
}
}
- /* For a forward assertion, we take the reqchar, if set. This can be
- helpful if the pattern that follows the assertion doesn't set a different
- char. For example, it's useful for /(?=abcde).+/. We can't set firstchar
- for an assertion, however because it leads to incorrect effect for patterns
- such as /(?=a)a.+/ when the "real" "a" would then become a reqchar instead
- of a firstchar. This is overcome by a scan at the end if there's no
- firstchar, looking for an asserted first char. */
-
- else if (bravalue == OP_ASSERT && subreqcharflags >= 0)
+ /* For a forward assertion, we take the reqchar, if set, provided that the
+ group has also set a first char. This can be helpful if the pattern that
+ follows the assertion doesn't set a different char. For example, it's
+ useful for /(?=abcde).+/. We can't set firstchar for an assertion, however
+ because it leads to incorrect effect for patterns such as /(?=a)a.+/ when
+ the "real" "a" would then become a reqchar instead of a firstchar. This is
+ overcome by a scan at the end if there's no firstchar, looking for an
+ asserted first char. */
+
+ else if (bravalue == OP_ASSERT && subreqcharflags >= 0 &&
+ subfirstcharflags >= 0)
{
reqchar = subreqchar;
reqcharflags = subreqcharflags;
@@ -8179,7 +8225,6 @@ for (;; ptr++)
if (mclength == 1 || req_caseopt == 0)
{
- firstchar = mcbuffer[0] | req_caseopt;
firstchar = mcbuffer[0];
firstcharflags = req_caseopt;
@@ -8686,8 +8731,8 @@ matching and for non-DOTALL patterns that start with .* (which must start at
the beginning or after \n). As in the case of is_anchored() (see above), we
have to take account of back references to capturing brackets that contain .*
because in that case we can't make the assumption. Also, the appearance of .*
-inside atomic brackets or in a pattern that contains *PRUNE or *SKIP does not
-count, because once again the assumption no longer holds.
+inside atomic brackets or in an assertion, or in a pattern that contains *PRUNE
+or *SKIP does not count, because once again the assumption no longer holds.
Arguments:
code points to start of expression (the bracket)
@@ -8696,13 +8741,14 @@ Arguments:
the less precise approach
cd points to the compile data
atomcount atomic group level
+ inassert TRUE if in an assertion
Returns: TRUE or FALSE
*/
static BOOL
is_startline(const pcre_uchar *code, unsigned int bracket_map,
- compile_data *cd, int atomcount)
+ compile_data *cd, int atomcount, BOOL inassert)
{
do {
const pcre_uchar *scode = first_significant_code(
@@ -8729,7 +8775,7 @@ do {
return FALSE;
default: /* Assertion */
- if (!is_startline(scode, bracket_map, cd, atomcount)) return FALSE;
+ if (!is_startline(scode, bracket_map, cd, atomcount, TRUE)) return FALSE;
do scode += GET(scode, 1); while (*scode == OP_ALT);
scode += 1 + LINK_SIZE;
break;
@@ -8743,7 +8789,7 @@ do {
if (op == OP_BRA || op == OP_BRAPOS ||
op == OP_SBRA || op == OP_SBRAPOS)
{
- if (!is_startline(scode, bracket_map, cd, atomcount)) return FALSE;
+ if (!is_startline(scode, bracket_map, cd, atomcount, inassert)) return FALSE;
}
/* Capturing brackets */
@@ -8753,33 +8799,33 @@ do {
{
int n = GET2(scode, 1+LINK_SIZE);
int new_map = bracket_map | ((n < 32)? (1 << n) : 1);
- if (!is_startline(scode, new_map, cd, atomcount)) return FALSE;
+ if (!is_startline(scode, new_map, cd, atomcount, inassert)) return FALSE;
}
/* Positive forward assertions */
else if (op == OP_ASSERT)
{
- if (!is_startline(scode, bracket_map, cd, atomcount)) return FALSE;
+ if (!is_startline(scode, bracket_map, cd, atomcount, TRUE)) return FALSE;
}
/* Atomic brackets */
else if (op == OP_ONCE || op == OP_ONCE_NC)
{
- if (!is_startline(scode, bracket_map, cd, atomcount + 1)) return FALSE;
+ if (!is_startline(scode, bracket_map, cd, atomcount + 1, inassert)) return FALSE;
}
/* .* means "start at start or after \n" if it isn't in atomic brackets or
- brackets that may be referenced, as long as the pattern does not contain
- *PRUNE or *SKIP, because these break the feature. Consider, for example,
- /.*?a(*PRUNE)b/ with the subject "aab", which matches "ab", i.e. not at the
- start of a line. */
+ brackets that may be referenced or an assertion, as long as the pattern does
+ not contain *PRUNE or *SKIP, because these break the feature. Consider, for
+ example, /.*?a(*PRUNE)b/ with the subject "aab", which matches "ab", i.e.
+ not at the start of a line. */
else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR || op == OP_TYPEPOSSTAR)
{
if (scode[1] != OP_ANY || (bracket_map & cd->backref_map) != 0 ||
- atomcount > 0 || cd->had_pruneorskip)
+ atomcount > 0 || cd->had_pruneorskip || inassert)
return FALSE;
}
@@ -9634,7 +9680,7 @@ if ((re->options & PCRE_ANCHORED) == 0)
re->flags |= PCRE_FIRSTSET;
}
- else if (is_startline(codestart, 0, cd, 0)) re->flags |= PCRE_STARTLINE;
+ else if (is_startline(codestart, 0, cd, 0, FALSE)) re->flags |= PCRE_STARTLINE;
}
}
diff --git a/src/third_party/pcre-8.39/pcre_config.c b/src/third_party/pcre-8.41/pcre_config.c
index 1cbdd9c960c..1cbdd9c960c 100644
--- a/src/third_party/pcre-8.39/pcre_config.c
+++ b/src/third_party/pcre-8.41/pcre_config.c
diff --git a/src/third_party/pcre-8.39/pcre_dfa_exec.c b/src/third_party/pcre-8.41/pcre_dfa_exec.c
index 170ce6a0016..bc09ced3a7c 100644
--- a/src/third_party/pcre-8.39/pcre_dfa_exec.c
+++ b/src/third_party/pcre-8.41/pcre_dfa_exec.c
@@ -7,7 +7,7 @@ and semantics are as close as possible to those of the Perl 5 language (but see
below for why this module is different).
Written by Philip Hazel
- Copyright (c) 1997-2014 University of Cambridge
+ Copyright (c) 1997-2017 University of Cambridge
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
@@ -2625,7 +2625,7 @@ for (;;)
if (isinclass)
{
int max = (int)GET2(ecode, 1 + IMM2_SIZE);
- if (*ecode == OP_CRPOSRANGE)
+ if (*ecode == OP_CRPOSRANGE && count >= (int)GET2(ecode, 1))
{
active_count--; /* Remove non-match possibility */
next_active_state--;
diff --git a/src/third_party/pcre-8.39/pcre_exec.c b/src/third_party/pcre-8.41/pcre_exec.c
index 24b23ca2864..1a9bdd546ee 100644
--- a/src/third_party/pcre-8.39/pcre_exec.c
+++ b/src/third_party/pcre-8.41/pcre_exec.c
@@ -669,7 +669,7 @@ if (ecode == NULL)
return match((PCRE_PUCHAR)&rdepth, NULL, NULL, 0, NULL, NULL, 1);
else
{
- int len = (char *)&rdepth - (char *)eptr;
+ int len = (int)((char *)&rdepth - (char *)eptr);
return (len > 0)? -len : len;
}
}
diff --git a/src/third_party/pcre-8.39/pcre_fullinfo.c b/src/third_party/pcre-8.41/pcre_fullinfo.c
index a6c2ece6ca5..a6c2ece6ca5 100644
--- a/src/third_party/pcre-8.39/pcre_fullinfo.c
+++ b/src/third_party/pcre-8.41/pcre_fullinfo.c
diff --git a/src/third_party/pcre-8.39/pcre_get.c b/src/third_party/pcre-8.41/pcre_get.c
index 9475d5e88cd..9475d5e88cd 100644
--- a/src/third_party/pcre-8.39/pcre_get.c
+++ b/src/third_party/pcre-8.41/pcre_get.c
diff --git a/src/third_party/pcre-8.39/pcre_globals.c b/src/third_party/pcre-8.41/pcre_globals.c
index 0f106aa9013..0f106aa9013 100644
--- a/src/third_party/pcre-8.39/pcre_globals.c
+++ b/src/third_party/pcre-8.41/pcre_globals.c
diff --git a/src/third_party/pcre-8.39/pcre_internal.h b/src/third_party/pcre-8.41/pcre_internal.h
index 2923b29f82d..97ff55d03b3 100644
--- a/src/third_party/pcre-8.39/pcre_internal.h
+++ b/src/third_party/pcre-8.41/pcre_internal.h
@@ -2772,6 +2772,9 @@ extern const pcre_uint8 PRIV(ucd_stage1)[];
extern const pcre_uint16 PRIV(ucd_stage2)[];
extern const pcre_uint32 PRIV(ucp_gentype)[];
extern const pcre_uint32 PRIV(ucp_gbtable)[];
+#ifdef COMPILE_PCRE32
+extern const ucd_record PRIV(dummy_ucd_record)[];
+#endif
#ifdef SUPPORT_JIT
extern const int PRIV(ucp_typerange)[];
#endif
@@ -2780,10 +2783,16 @@ extern const int PRIV(ucp_typerange)[];
/* UCD access macros */
#define UCD_BLOCK_SIZE 128
-#define GET_UCD(ch) (PRIV(ucd_records) + \
+#define REAL_GET_UCD(ch) (PRIV(ucd_records) + \
PRIV(ucd_stage2)[PRIV(ucd_stage1)[(int)(ch) / UCD_BLOCK_SIZE] * \
UCD_BLOCK_SIZE + (int)(ch) % UCD_BLOCK_SIZE])
+#ifdef COMPILE_PCRE32
+#define GET_UCD(ch) ((ch > 0x10ffff)? PRIV(dummy_ucd_record) : REAL_GET_UCD(ch))
+#else
+#define GET_UCD(ch) REAL_GET_UCD(ch)
+#endif
+
#define UCD_CHARTYPE(ch) GET_UCD(ch)->chartype
#define UCD_SCRIPT(ch) GET_UCD(ch)->script
#define UCD_CATEGORY(ch) PRIV(ucp_gentype)[UCD_CHARTYPE(ch)]
diff --git a/src/third_party/pcre-8.39/pcre_jit_compile.c b/src/third_party/pcre-8.41/pcre_jit_compile.c
index 4f15a27ac28..249edbe8e7f 100644
--- a/src/third_party/pcre-8.39/pcre_jit_compile.c
+++ b/src/third_party/pcre-8.41/pcre_jit_compile.c
@@ -487,7 +487,7 @@ typedef struct compare_context {
#undef CMP
/* Used for accessing the elements of the stack. */
-#define STACK(i) ((-(i) - 1) * (int)sizeof(sljit_sw))
+#define STACK(i) ((i) * (int)sizeof(sljit_sw))
#define TMP1 SLJIT_R0
#define TMP2 SLJIT_R2
@@ -552,13 +552,15 @@ the start pointers when the end of the capturing group has not yet reached. */
sljit_emit_cmp(compiler, (type), (src1), (src1w), (src2), (src2w))
#define CMPTO(type, src1, src1w, src2, src2w, label) \
sljit_set_label(sljit_emit_cmp(compiler, (type), (src1), (src1w), (src2), (src2w)), (label))
-#define OP_FLAGS(op, dst, dstw, src, srcw, type) \
- sljit_emit_op_flags(compiler, (op), (dst), (dstw), (src), (srcw), (type))
+#define OP_FLAGS(op, dst, dstw, type) \
+ sljit_emit_op_flags(compiler, (op), (dst), (dstw), (type))
#define GET_LOCAL_BASE(dst, dstw, offset) \
sljit_get_local_base(compiler, (dst), (dstw), (offset))
#define READ_CHAR_MAX 0x7fffffff
+#define INVALID_UTF_CHAR 888
+
static pcre_uchar *bracketend(pcre_uchar *cc)
{
SLJIT_ASSERT((*cc >= OP_ASSERT && *cc <= OP_ASSERTBACK_NOT) || (*cc >= OP_ONCE && *cc <= OP_SCOND));
@@ -784,7 +786,7 @@ switch(*cc)
default:
/* All opcodes are supported now! */
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
return NULL;
}
}
@@ -1660,9 +1662,9 @@ while (cc < ccend)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -OVECTOR(0));
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
setsom_found = TRUE;
}
cc += 1;
@@ -1676,9 +1678,9 @@ while (cc < ccend)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->mark_ptr);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
setmark_found = TRUE;
}
cc += 1 + 2 + cc[1];
@@ -1689,27 +1691,27 @@ while (cc < ccend)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -OVECTOR(0));
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
setsom_found = TRUE;
}
if (common->mark_ptr != 0 && !setmark_found)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->mark_ptr);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
setmark_found = TRUE;
}
if (common->capture_last_ptr != 0 && !capture_last_found)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->capture_last_ptr);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
capture_last_found = TRUE;
}
cc += 1 + LINK_SIZE;
@@ -1723,20 +1725,20 @@ while (cc < ccend)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->capture_last_ptr);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
capture_last_found = TRUE;
}
offset = (GET2(cc, 1 + LINK_SIZE)) << 1;
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, OVECTOR(offset));
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset));
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP2, 0);
- stackpos += (int)sizeof(sljit_sw);
+ stackpos -= (int)sizeof(sljit_sw);
cc += 1 + LINK_SIZE + IMM2_SIZE;
break;
@@ -1887,18 +1889,17 @@ BOOL tmp1empty = TRUE;
BOOL tmp2empty = TRUE;
pcre_uchar *alternative;
enum {
- start,
loop,
end
} status;
-status = save ? start : loop;
-stackptr = STACK(stackptr - 2);
+status = loop;
+stackptr = STACK(stackptr);
stacktop = STACK(stacktop - 1);
if (!save)
{
- stackptr += (needs_control_head ? 2 : 1) * sizeof(sljit_sw);
+ stacktop -= (needs_control_head ? 2 : 1) * sizeof(sljit_sw);
if (stackptr < stacktop)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), stackptr);
@@ -1914,196 +1915,186 @@ if (!save)
/* The tmp1next must be TRUE in either way. */
}
+SLJIT_ASSERT(common->recursive_head_ptr != 0);
+
do
{
count = 0;
- switch(status)
+ if (cc >= ccend)
{
- case start:
- SLJIT_ASSERT(save && common->recursive_head_ptr != 0);
+ if (!save)
+ break;
+
count = 1;
srcw[0] = common->recursive_head_ptr;
if (needs_control_head)
{
SLJIT_ASSERT(common->control_head_ptr != 0);
count = 2;
- srcw[1] = common->control_head_ptr;
+ srcw[0] = common->control_head_ptr;
+ srcw[1] = common->recursive_head_ptr;
+ }
+ status = end;
+ }
+ else switch(*cc)
+ {
+ case OP_KET:
+ if (PRIVATE_DATA(cc) != 0)
+ {
+ count = 1;
+ srcw[0] = PRIVATE_DATA(cc);
+ SLJIT_ASSERT(PRIVATE_DATA(cc + 1) != 0);
+ cc += PRIVATE_DATA(cc + 1);
}
- status = loop;
+ cc += 1 + LINK_SIZE;
+ break;
+
+ case OP_ASSERT:
+ case OP_ASSERT_NOT:
+ case OP_ASSERTBACK:
+ case OP_ASSERTBACK_NOT:
+ case OP_ONCE:
+ case OP_ONCE_NC:
+ case OP_BRAPOS:
+ case OP_SBRA:
+ case OP_SBRAPOS:
+ case OP_SCOND:
+ count = 1;
+ srcw[0] = PRIVATE_DATA(cc);
+ SLJIT_ASSERT(srcw[0] != 0);
+ cc += 1 + LINK_SIZE;
break;
- case loop:
- if (cc >= ccend)
+ case OP_CBRA:
+ case OP_SCBRA:
+ if (common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0)
{
- status = end;
- break;
+ count = 1;
+ srcw[0] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE));
}
+ cc += 1 + LINK_SIZE + IMM2_SIZE;
+ break;
- switch(*cc)
- {
- case OP_KET:
- if (PRIVATE_DATA(cc) != 0)
- {
- count = 1;
- srcw[0] = PRIVATE_DATA(cc);
- SLJIT_ASSERT(PRIVATE_DATA(cc + 1) != 0);
- cc += PRIVATE_DATA(cc + 1);
- }
- cc += 1 + LINK_SIZE;
- break;
+ case OP_CBRAPOS:
+ case OP_SCBRAPOS:
+ count = 2;
+ srcw[0] = PRIVATE_DATA(cc);
+ srcw[1] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE));
+ SLJIT_ASSERT(srcw[0] != 0 && srcw[1] != 0);
+ cc += 1 + LINK_SIZE + IMM2_SIZE;
+ break;
- case OP_ASSERT:
- case OP_ASSERT_NOT:
- case OP_ASSERTBACK:
- case OP_ASSERTBACK_NOT:
- case OP_ONCE:
- case OP_ONCE_NC:
- case OP_BRAPOS:
- case OP_SBRA:
- case OP_SBRAPOS:
- case OP_SCOND:
+ case OP_COND:
+ /* Might be a hidden SCOND. */
+ alternative = cc + GET(cc, 1);
+ if (*alternative == OP_KETRMAX || *alternative == OP_KETRMIN)
+ {
count = 1;
srcw[0] = PRIVATE_DATA(cc);
SLJIT_ASSERT(srcw[0] != 0);
- cc += 1 + LINK_SIZE;
- break;
-
- case OP_CBRA:
- case OP_SCBRA:
- if (common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0)
- {
- count = 1;
- srcw[0] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE));
- }
- cc += 1 + LINK_SIZE + IMM2_SIZE;
- break;
+ }
+ cc += 1 + LINK_SIZE;
+ break;
- case OP_CBRAPOS:
- case OP_SCBRAPOS:
- count = 2;
+ CASE_ITERATOR_PRIVATE_DATA_1
+ if (PRIVATE_DATA(cc))
+ {
+ count = 1;
srcw[0] = PRIVATE_DATA(cc);
- srcw[1] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE));
- SLJIT_ASSERT(srcw[0] != 0 && srcw[1] != 0);
- cc += 1 + LINK_SIZE + IMM2_SIZE;
- break;
-
- case OP_COND:
- /* Might be a hidden SCOND. */
- alternative = cc + GET(cc, 1);
- if (*alternative == OP_KETRMAX || *alternative == OP_KETRMIN)
- {
- count = 1;
- srcw[0] = PRIVATE_DATA(cc);
- SLJIT_ASSERT(srcw[0] != 0);
- }
- cc += 1 + LINK_SIZE;
- break;
-
- CASE_ITERATOR_PRIVATE_DATA_1
- if (PRIVATE_DATA(cc))
- {
- count = 1;
- srcw[0] = PRIVATE_DATA(cc);
- }
- cc += 2;
+ }
+ cc += 2;
#ifdef SUPPORT_UTF
- if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
+ if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
#endif
- break;
+ break;
- CASE_ITERATOR_PRIVATE_DATA_2A
- if (PRIVATE_DATA(cc))
- {
- count = 2;
- srcw[0] = PRIVATE_DATA(cc);
- srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw);
- }
- cc += 2;
+ CASE_ITERATOR_PRIVATE_DATA_2A
+ if (PRIVATE_DATA(cc))
+ {
+ count = 2;
+ srcw[0] = PRIVATE_DATA(cc);
+ srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw);
+ }
+ cc += 2;
#ifdef SUPPORT_UTF
- if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
+ if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
#endif
- break;
+ break;
- CASE_ITERATOR_PRIVATE_DATA_2B
- if (PRIVATE_DATA(cc))
- {
- count = 2;
- srcw[0] = PRIVATE_DATA(cc);
- srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw);
- }
- cc += 2 + IMM2_SIZE;
+ CASE_ITERATOR_PRIVATE_DATA_2B
+ if (PRIVATE_DATA(cc))
+ {
+ count = 2;
+ srcw[0] = PRIVATE_DATA(cc);
+ srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw);
+ }
+ cc += 2 + IMM2_SIZE;
#ifdef SUPPORT_UTF
- if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
+ if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]);
#endif
- break;
+ break;
- CASE_ITERATOR_TYPE_PRIVATE_DATA_1
- if (PRIVATE_DATA(cc))
+ CASE_ITERATOR_TYPE_PRIVATE_DATA_1
+ if (PRIVATE_DATA(cc))
+ {
+ count = 1;
+ srcw[0] = PRIVATE_DATA(cc);
+ }
+ cc += 1;
+ break;
+
+ CASE_ITERATOR_TYPE_PRIVATE_DATA_2A
+ if (PRIVATE_DATA(cc))
+ {
+ count = 2;
+ srcw[0] = PRIVATE_DATA(cc);
+ srcw[1] = srcw[0] + sizeof(sljit_sw);
+ }
+ cc += 1;
+ break;
+
+ CASE_ITERATOR_TYPE_PRIVATE_DATA_2B
+ if (PRIVATE_DATA(cc))
+ {
+ count = 2;
+ srcw[0] = PRIVATE_DATA(cc);
+ srcw[1] = srcw[0] + sizeof(sljit_sw);
+ }
+ cc += 1 + IMM2_SIZE;
+ break;
+
+ case OP_CLASS:
+ case OP_NCLASS:
+#if defined SUPPORT_UTF || !defined COMPILE_PCRE8
+ case OP_XCLASS:
+ size = (*cc == OP_XCLASS) ? GET(cc, 1) : 1 + 32 / (int)sizeof(pcre_uchar);
+#else
+ size = 1 + 32 / (int)sizeof(pcre_uchar);
+#endif
+ if (PRIVATE_DATA(cc))
+ switch(get_class_iterator_size(cc + size))
{
+ case 1:
count = 1;
srcw[0] = PRIVATE_DATA(cc);
- }
- cc += 1;
- break;
+ break;
- CASE_ITERATOR_TYPE_PRIVATE_DATA_2A
- if (PRIVATE_DATA(cc))
- {
+ case 2:
count = 2;
srcw[0] = PRIVATE_DATA(cc);
srcw[1] = srcw[0] + sizeof(sljit_sw);
- }
- cc += 1;
- break;
+ break;
- CASE_ITERATOR_TYPE_PRIVATE_DATA_2B
- if (PRIVATE_DATA(cc))
- {
- count = 2;
- srcw[0] = PRIVATE_DATA(cc);
- srcw[1] = srcw[0] + sizeof(sljit_sw);
+ default:
+ SLJIT_UNREACHABLE();
+ break;
}
- cc += 1 + IMM2_SIZE;
- break;
-
- case OP_CLASS:
- case OP_NCLASS:
-#if defined SUPPORT_UTF || !defined COMPILE_PCRE8
- case OP_XCLASS:
- size = (*cc == OP_XCLASS) ? GET(cc, 1) : 1 + 32 / (int)sizeof(pcre_uchar);
-#else
- size = 1 + 32 / (int)sizeof(pcre_uchar);
-#endif
- if (PRIVATE_DATA(cc))
- switch(get_class_iterator_size(cc + size))
- {
- case 1:
- count = 1;
- srcw[0] = PRIVATE_DATA(cc);
- break;
-
- case 2:
- count = 2;
- srcw[0] = PRIVATE_DATA(cc);
- srcw[1] = srcw[0] + sizeof(sljit_sw);
- break;
-
- default:
- SLJIT_ASSERT_STOP();
- break;
- }
- cc += size;
- break;
-
- default:
- cc = next_opcode(common, cc);
- SLJIT_ASSERT(cc != NULL);
- break;
- }
+ cc += size;
break;
- case end:
- SLJIT_ASSERT_STOP();
+ default:
+ cc = next_opcode(common, cc);
+ SLJIT_ASSERT(cc != NULL);
break;
}
@@ -2312,7 +2303,7 @@ static SLJIT_INLINE void count_match(compiler_common *common)
{
DEFINE_COMPILER;
-OP2(SLJIT_SUB | SLJIT_SET_E, COUNT_MATCH, 0, COUNT_MATCH, 0, SLJIT_IMM, 1);
+OP2(SLJIT_SUB | SLJIT_SET_Z, COUNT_MATCH, 0, COUNT_MATCH, 0, SLJIT_IMM, 1);
add_jump(compiler, &common->calllimit, JUMP(SLJIT_ZERO));
}
@@ -2322,7 +2313,7 @@ static SLJIT_INLINE void allocate_stack(compiler_common *common, int size)
DEFINE_COMPILER;
SLJIT_ASSERT(size > 0);
-OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw));
+OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw));
#ifdef DESTROY_REGISTERS
OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 12345);
OP1(SLJIT_MOV, TMP3, 0, TMP1, 0);
@@ -2330,7 +2321,7 @@ OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP1, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, TMP1, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP1, 0);
#endif
-add_stub(common, CMP(SLJIT_GREATER, STACK_TOP, 0, STACK_LIMIT, 0));
+add_stub(common, CMP(SLJIT_LESS, STACK_TOP, 0, STACK_LIMIT, 0));
}
static SLJIT_INLINE void free_stack(compiler_common *common, int size)
@@ -2338,7 +2329,7 @@ static SLJIT_INLINE void free_stack(compiler_common *common, int size)
DEFINE_COMPILER;
SLJIT_ASSERT(size > 0);
-OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw));
+OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw));
}
static sljit_uw * allocate_read_only_data(compiler_common *common, sljit_uw size)
@@ -2396,7 +2387,7 @@ else
OP1(SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, length - 1);
loop = LABEL();
OP1(SLJIT_MOVU, SLJIT_MEM1(SLJIT_R1), sizeof(sljit_sw), SLJIT_R0, 0);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_R2, 0, SLJIT_R2, 0, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_R2, 0, SLJIT_R2, 0, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, loop);
}
}
@@ -2434,7 +2425,7 @@ else
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_IMM, length - 2);
loop = LABEL();
OP1(SLJIT_MOVU, SLJIT_MEM1(TMP2), sizeof(sljit_sw), TMP1, 0);
- OP2(SLJIT_SUB | SLJIT_SET_E, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, loop);
}
@@ -2452,22 +2443,22 @@ static sljit_sw SLJIT_CALL do_search_mark(sljit_sw *current, const pcre_uchar *s
{
while (current != NULL)
{
- switch (current[-2])
+ switch (current[1])
{
case type_then_trap:
break;
case type_mark:
- if (STRCMP_UC_UC(skip_arg, (pcre_uchar *)current[-3]) == 0)
- return current[-4];
+ if (STRCMP_UC_UC(skip_arg, (pcre_uchar *)current[2]) == 0)
+ return current[3];
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
- SLJIT_ASSERT(current > (sljit_sw*)current[-1]);
- current = (sljit_sw*)current[-1];
+ SLJIT_ASSERT(current[0] == 0 || current < (sljit_sw*)current[0]);
+ current = (sljit_sw*)current[0];
}
return -1;
}
@@ -2501,7 +2492,7 @@ OP2(SLJIT_ADD, SLJIT_S0, 0, SLJIT_S0, 0, SLJIT_IMM, sizeof(sljit_sw));
OP2(SLJIT_ASHR, SLJIT_S1, 0, SLJIT_S1, 0, SLJIT_IMM, UCHAR_SHIFT);
#endif
OP1(SLJIT_MOVU_S32, SLJIT_MEM1(SLJIT_R2), sizeof(int), SLJIT_S1, 0);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, 1);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, loop);
JUMPHERE(early_quit);
@@ -3106,8 +3097,8 @@ if (common->utf)
OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
/* Skip low surrogate if necessary. */
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xdc00);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xdc00);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP1, 0);
return;
@@ -3126,6 +3117,7 @@ struct sljit_jump *jump;
if (nltype == NLTYPE_ANY)
{
add_jump(compiler, &common->anynewline, JUMP(SLJIT_FAST_CALL));
+ sljit_set_current_flags(compiler, SLJIT_SET_Z);
add_jump(compiler, backtracks, JUMP(jumpifmatch ? SLJIT_NOT_ZERO : SLJIT_ZERO));
}
else if (nltype == NLTYPE_ANYCRLF)
@@ -3167,7 +3159,7 @@ OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0);
/* Searching for the first zero. */
-OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800);
+OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800);
jump = JUMP(SLJIT_NOT_ZERO);
/* Two byte sequence. */
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
@@ -3181,7 +3173,7 @@ OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6);
OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0);
-OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x10000);
+OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x10000);
jump = JUMP(SLJIT_NOT_ZERO);
/* Three byte sequence. */
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2));
@@ -3215,15 +3207,15 @@ OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0);
/* Searching for the first zero. */
-OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800);
+OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800);
jump = JUMP(SLJIT_NOT_ZERO);
/* Two byte sequence. */
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
sljit_emit_fast_return(compiler, RETURN_ADDR, 0);
JUMPHERE(jump);
-OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x400);
-OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_NOT_ZERO);
+OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x400);
+OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_NOT_ZERO);
/* This code runs only in 8 bit mode. No need to shift the value. */
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0);
OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1));
@@ -3246,7 +3238,7 @@ struct sljit_jump *compare;
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
-OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, 0x20);
+OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, 0x20);
jump = JUMP(SLJIT_NOT_ZERO);
/* Two byte sequence. */
OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0));
@@ -3287,10 +3279,30 @@ static void do_getucd(compiler_common *common)
/* Search the UCD record for the character comes in TMP1.
Returns chartype in TMP1 and UCD offset in TMP2. */
DEFINE_COMPILER;
+#ifdef COMPILE_PCRE32
+struct sljit_jump *jump;
+#endif
+
+#if defined SLJIT_DEBUG && SLJIT_DEBUG
+/* dummy_ucd_record */
+const ucd_record *record = GET_UCD(INVALID_UTF_CHAR);
+SLJIT_ASSERT(record->script == ucp_Common && record->chartype == ucp_Cn && record->gbprop == ucp_gbOther);
+SLJIT_ASSERT(record->caseset == 0 && record->other_case == 0);
+#endif
SLJIT_ASSERT(UCD_BLOCK_SIZE == 128 && sizeof(ucd_record) == 8);
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
+
+#ifdef COMPILE_PCRE32
+if (!common->utf)
+ {
+ jump = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0x10ffff + 1);
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, INVALID_UTF_CHAR);
+ JUMPHERE(jump);
+ }
+#endif
+
OP2(SLJIT_LSHR, TMP2, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_SHIFT);
OP1(SLJIT_MOV_U8, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(ucd_stage1));
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_MASK);
@@ -3365,8 +3377,8 @@ if (newlinecheck)
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
end = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0);
OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, common->newline & 0xff);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, common->newline & 0xff);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
#if defined COMPILE_PCRE16 || defined COMPILE_PCRE32
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, UCHAR_SHIFT);
#endif
@@ -3403,8 +3415,8 @@ if (common->utf)
{
singlechar = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800);
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0);
JUMPHERE(singlechar);
@@ -3853,7 +3865,7 @@ while (TRUE)
}
}
-#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86)
+#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) && !(defined SUPPORT_VALGRIND)
static sljit_s32 character_to_int32(pcre_uchar chr)
{
@@ -4004,12 +4016,12 @@ sljit_emit_op_custom(compiler, instruction, 4);
if (load_twice)
{
- OP1(SLJIT_MOV, TMP3, 0, TMP2, 0);
+ OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP2, 0);
instruction[3] = 0xc0 | (tmp2_ind << 3) | 1;
sljit_emit_op_custom(compiler, instruction, 4);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0);
- OP1(SLJIT_MOV, TMP2, 0, TMP3, 0);
+ OP1(SLJIT_MOV, TMP2, 0, RETURN_ADDR, 0);
}
OP2(SLJIT_ASHR, TMP1, 0, TMP1, 0, TMP2, 0);
@@ -4019,6 +4031,7 @@ instruction[0] = 0x0f;
instruction[1] = 0xbc;
instruction[2] = 0xc0 | (tmp1_ind << 3) | tmp1_ind;
sljit_emit_op_custom(compiler, instruction, 3);
+sljit_set_current_flags(compiler, SLJIT_SET_Z);
nomatch = JUMP(SLJIT_ZERO);
@@ -4119,6 +4132,7 @@ instruction[0] = 0x0f;
instruction[1] = 0xbc;
instruction[2] = 0xc0 | (tmp1_ind << 3) | tmp1_ind;
sljit_emit_op_custom(compiler, instruction, 3);
+sljit_set_current_flags(compiler, SLJIT_SET_Z);
JUMPTO(SLJIT_ZERO, start);
@@ -4155,18 +4169,8 @@ if (has_match_end)
OP1(SLJIT_MOV, TMP3, 0, STR_END, 0);
OP2(SLJIT_ADD, STR_END, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, SLJIT_IMM, IN_UCHARS(offset + 1));
-#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86)
- if (sljit_x86_is_cmov_available())
- {
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_END, 0, TMP3, 0);
- sljit_x86_emit_cmov(compiler, SLJIT_GREATER, STR_END, TMP3, 0);
- }
-#endif
- {
- quit = CMP(SLJIT_LESS_EQUAL, STR_END, 0, TMP3, 0);
- OP1(SLJIT_MOV, STR_END, 0, TMP3, 0);
- JUMPHERE(quit);
- }
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER, SLJIT_UNUSED, 0, STR_END, 0, TMP3, 0);
+ sljit_emit_cmov(compiler, SLJIT_GREATER, STR_END, TMP3, 0);
}
#if defined SUPPORT_UTF && !defined COMPILE_PCRE32
@@ -4174,11 +4178,11 @@ if (common->utf && offset > 0)
utf_start = LABEL();
#endif
-#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86)
+#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) && !(defined SUPPORT_VALGRIND)
/* SSE2 accelerated first character search. */
-if (sljit_x86_is_sse2_available())
+if (sljit_has_cpu_feature(SLJIT_HAS_SSE2))
{
fast_forward_first_char2_sse2(common, char1, char2);
@@ -4213,16 +4217,16 @@ if (sljit_x86_is_sse2_available())
if (offset > 0)
OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(offset));
}
- else if (sljit_x86_is_cmov_available())
- {
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_PTR, 0, STR_END, 0);
- sljit_x86_emit_cmov(compiler, SLJIT_GREATER_EQUAL, STR_PTR, has_match_end ? SLJIT_MEM1(SLJIT_SP) : STR_END, has_match_end ? common->match_end_ptr : 0);
- }
else
{
- quit = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0);
- OP1(SLJIT_MOV, STR_PTR, 0, has_match_end ? SLJIT_MEM1(SLJIT_SP) : STR_END, has_match_end ? common->match_end_ptr : 0);
- JUMPHERE(quit);
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER_EQUAL, SLJIT_UNUSED, 0, STR_PTR, 0, STR_END, 0);
+ if (has_match_end)
+ {
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr);
+ sljit_emit_cmov(compiler, SLJIT_GREATER_EQUAL, STR_PTR, TMP1, 0);
+ }
+ else
+ sljit_emit_cmov(compiler, SLJIT_GREATER_EQUAL, STR_PTR, STR_END, 0);
}
if (has_match_end)
@@ -4249,10 +4253,10 @@ else
}
else
{
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char1);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char2);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char1);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char2);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
found = JUMP(SLJIT_NOT_ZERO);
}
}
@@ -4571,8 +4575,8 @@ if (common->nltype == NLTYPE_FIXED && common->newline > 255)
firstchar = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP2, 0);
OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, IN_UCHARS(2));
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_PTR, 0, TMP1, 0);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER_EQUAL, SLJIT_UNUSED, 0, STR_PTR, 0, TMP1, 0);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_GREATER_EQUAL);
#if defined COMPILE_PCRE16 || defined COMPILE_PCRE32
OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, UCHAR_SHIFT);
#endif
@@ -4616,8 +4620,8 @@ if (common->nltype == NLTYPE_ANY || common->nltype == NLTYPE_ANYCRLF)
JUMPHERE(foundcr);
notfoundnl = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0);
OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, CHAR_NL);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, CHAR_NL);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
#if defined COMPILE_PCRE16 || defined COMPILE_PCRE32
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, UCHAR_SHIFT);
#endif
@@ -4670,7 +4674,7 @@ if (!check_class_ranges(common, start_bits, (start_bits[31] & 0x80) != 0, TRUE,
OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3);
OP1(SLJIT_MOV_U8, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)start_bits);
OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
found = JUMP(SLJIT_NOT_ZERO);
}
@@ -4692,8 +4696,8 @@ if (common->utf)
{
CMPTO(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800, start);
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0);
}
@@ -4780,31 +4784,31 @@ struct sljit_jump *jump;
struct sljit_label *mainloop;
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
-OP1(SLJIT_MOV, TMP1, 0, STACK_TOP, 0);
-GET_LOCAL_BASE(TMP3, 0, 0);
+OP1(SLJIT_MOV, TMP3, 0, STACK_TOP, 0);
+GET_LOCAL_BASE(TMP1, 0, 0);
/* Drop frames until we reach STACK_TOP. */
mainloop = LABEL();
-OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), 0);
-OP2(SLJIT_SUB | SLJIT_SET_S, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, 0);
-jump = JUMP(SLJIT_SIG_LESS_EQUAL);
-
-OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP3, 0);
-OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(TMP1), sizeof(sljit_sw));
-OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), sizeof(sljit_sw), SLJIT_MEM1(TMP1), 2 * sizeof(sljit_sw));
-OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 3 * sizeof(sljit_sw));
+OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), -sizeof(sljit_sw));
+jump = CMP(SLJIT_SIG_LESS_EQUAL, TMP2, 0, SLJIT_IMM, 0);
+
+OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP1, 0);
+OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(STACK_TOP), -2 * sizeof(sljit_sw));
+OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), sizeof(sljit_sw), SLJIT_MEM1(STACK_TOP), -3 * sizeof(sljit_sw));
+OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 3 * sizeof(sljit_sw));
JUMPTO(SLJIT_JUMP, mainloop);
JUMPHERE(jump);
-jump = JUMP(SLJIT_SIG_LESS);
-/* End of dropping frames. */
+jump = CMP(SLJIT_NOT_ZERO /* SIG_LESS */, TMP2, 0, SLJIT_IMM, 0);
+/* End of reverting values. */
+OP1(SLJIT_MOV, STACK_TOP, 0, TMP3, 0);
sljit_emit_fast_return(compiler, RETURN_ADDR, 0);
JUMPHERE(jump);
OP1(SLJIT_NEG, TMP2, 0, TMP2, 0);
-OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP3, 0);
-OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(TMP1), sizeof(sljit_sw));
-OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 2 * sizeof(sljit_sw));
+OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP1, 0);
+OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(STACK_TOP), -2 * sizeof(sljit_sw));
+OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 2 * sizeof(sljit_sw));
JUMPTO(SLJIT_JUMP, mainloop);
}
@@ -4837,11 +4841,11 @@ if (common->use_ucp)
jump = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_UNDERSCORE);
add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL));
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Nd - ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
JUMPHERE(jump);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP2, 0);
}
@@ -4881,11 +4885,11 @@ if (common->use_ucp)
jump = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_UNDERSCORE);
add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL));
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Nd - ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
JUMPHERE(jump);
}
else
@@ -4913,7 +4917,7 @@ else
}
set_jumps(skipread_list, LABEL());
-OP2(SLJIT_XOR | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1);
+OP2(SLJIT_XOR | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1);
sljit_emit_fast_return(compiler, SLJIT_MEM1(SLJIT_SP), LOCALS0);
}
@@ -5064,7 +5068,7 @@ switch(length)
return TRUE;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
return FALSE;
}
}
@@ -5077,22 +5081,22 @@ DEFINE_COMPILER;
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x0a);
-OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a);
-OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a);
+OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a);
+OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a);
#if defined SUPPORT_UTF || defined COMPILE_PCRE16 || defined COMPILE_PCRE32
#ifdef COMPILE_PCRE8
if (common->utf)
{
#endif
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x1);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a);
#ifdef COMPILE_PCRE8
}
#endif
#endif /* SUPPORT_UTF || COMPILE_PCRE16 || COMPILE_PCRE32 */
-OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
sljit_emit_fast_return(compiler, RETURN_ADDR, 0);
}
@@ -5103,34 +5107,34 @@ DEFINE_COMPILER;
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x09);
-OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x20);
-OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xa0);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x09);
+OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x20);
+OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xa0);
#if defined SUPPORT_UTF || defined COMPILE_PCRE16 || defined COMPILE_PCRE32
#ifdef COMPILE_PCRE8
if (common->utf)
{
#endif
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x1680);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x1680);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x2000);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x200A - 0x2000);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x202f - 0x2000);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x205f - 0x2000);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x3000 - 0x2000);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x200A - 0x2000);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x202f - 0x2000);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x205f - 0x2000);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x3000 - 0x2000);
#ifdef COMPILE_PCRE8
}
#endif
#endif /* SUPPORT_UTF || COMPILE_PCRE16 || COMPILE_PCRE32 */
-OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
sljit_emit_fast_return(compiler, RETURN_ADDR, 0);
}
@@ -5143,22 +5147,22 @@ DEFINE_COMPILER;
sljit_emit_fast_enter(compiler, RETURN_ADDR, 0);
OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x0a);
-OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a);
-OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
-OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a);
+OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a);
+OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
+OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a);
#if defined SUPPORT_UTF || defined COMPILE_PCRE16 || defined COMPILE_PCRE32
#ifdef COMPILE_PCRE8
if (common->utf)
{
#endif
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x1);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a);
#ifdef COMPILE_PCRE8
}
#endif
#endif /* SUPPORT_UTF || COMPILE_PCRE16 || COMPILE_PCRE32 */
-OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
sljit_emit_fast_return(compiler, RETURN_ADDR, 0);
}
@@ -5183,7 +5187,7 @@ label = LABEL();
OP1(MOVU_UCHAR, CHAR1, 0, SLJIT_MEM1(TMP1), IN_UCHARS(1));
OP1(MOVU_UCHAR, CHAR2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1));
jump = CMP(SLJIT_NOT_EQUAL, CHAR1, 0, CHAR2, 0);
-OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1));
+OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1));
JUMPTO(SLJIT_NOT_ZERO, label);
JUMPHERE(jump);
@@ -5227,7 +5231,7 @@ OP1(SLJIT_MOV_U8, CHAR2, 0, SLJIT_MEM2(LCC_TABLE, CHAR2), 0);
JUMPHERE(jump);
#endif
jump = CMP(SLJIT_NOT_EQUAL, CHAR1, 0, CHAR2, 0);
-OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1));
+OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1));
JUMPTO(SLJIT_NOT_ZERO, label);
JUMPHERE(jump);
@@ -5394,7 +5398,7 @@ do
#endif
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
context->ucharptr = 0;
@@ -5568,7 +5572,7 @@ while (*cc != XCL_END)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
cc += 2;
@@ -5592,7 +5596,7 @@ if ((cc[-1] & XCL_HASPROP) == 0)
OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3);
OP1(SLJIT_MOV_U8, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc);
OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
add_jump(compiler, &found, JUMP(SLJIT_NOT_ZERO));
}
@@ -5625,7 +5629,7 @@ else if ((cc[-1] & XCL_MAP) != 0)
OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3);
OP1(SLJIT_MOV_U8, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc);
OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
add_jump(compiler, list, JUMP(SLJIT_NOT_ZERO));
#ifdef COMPILE_PCRE8
@@ -5644,6 +5648,15 @@ if (needstype || needsscript)
if (needschar && !charsaved)
OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP1, 0);
+#ifdef COMPILE_PCRE32
+ if (!common->utf)
+ {
+ jump = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0x10ffff + 1);
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, INVALID_UTF_CHAR);
+ JUMPHERE(jump);
+ }
+#endif
+
OP2(SLJIT_LSHR, TMP2, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_SHIFT);
OP1(SLJIT_MOV_U8, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(ucd_stage1));
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_MASK);
@@ -5735,14 +5748,14 @@ while (*cc != XCL_END)
if (numberofcmps < 3 && (*cc == XCL_SINGLE || *cc == XCL_RANGE))
{
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
- OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, numberofcmps == 0 ? SLJIT_UNUSED : TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
+ OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
numberofcmps++;
}
else if (numberofcmps > 0)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
numberofcmps = 0;
}
@@ -5761,14 +5774,14 @@ while (*cc != XCL_END)
if (numberofcmps < 3 && (*cc == XCL_SINGLE || *cc == XCL_RANGE))
{
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
- OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, numberofcmps == 0 ? SLJIT_UNUSED : TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
+ OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
numberofcmps++;
}
else if (numberofcmps > 0)
{
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset));
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_LESS_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
numberofcmps = 0;
}
@@ -5793,12 +5806,12 @@ while (*cc != XCL_END)
break;
case PT_LAMP:
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - typeoffset);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ll - typeoffset);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lt - typeoffset);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - typeoffset);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ll - typeoffset);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lt - typeoffset);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
@@ -5820,33 +5833,33 @@ while (*cc != XCL_END)
case PT_SPACE:
case PT_PXSPACE:
SET_CHAR_OFFSET(9);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd - 0x9);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd - 0x9);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x9);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x9);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x9);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x9);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
SET_TYPE_OFFSET(ucp_Zl);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Zl);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Zl);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_LESS_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
case PT_WORD:
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_UNDERSCORE - charoffset));
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_UNDERSCORE - charoffset));
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
/* Fall through. */
case PT_ALNUM:
SET_TYPE_OFFSET(ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
- OP_FLAGS((*cc == PT_ALNUM) ? SLJIT_MOV : SLJIT_OR, TMP2, 0, (*cc == PT_ALNUM) ? SLJIT_UNUSED : TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - ucp_Ll);
+ OP_FLAGS((*cc == PT_ALNUM) ? SLJIT_MOV : SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
SET_TYPE_OFFSET(ucp_Nd);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_No - ucp_Nd);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_No - ucp_Nd);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_LESS_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
@@ -5868,8 +5881,8 @@ while (*cc != XCL_END)
OP2(SLJIT_ADD, TMP2, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)charoffset);
OP2(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_IMM, other_cases[1] ^ other_cases[0]);
}
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[1]);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[1]);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
other_cases += 2;
}
else if (is_powerof2(other_cases[2] ^ other_cases[1]))
@@ -5881,63 +5894,63 @@ while (*cc != XCL_END)
OP2(SLJIT_ADD, TMP2, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)charoffset);
OP2(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_IMM, other_cases[1] ^ other_cases[0]);
}
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[2]);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[2]);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(other_cases[0] - charoffset));
- OP_FLAGS(SLJIT_OR | ((other_cases[3] == NOTACHAR) ? SLJIT_SET_E : 0), TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(other_cases[0] - charoffset));
+ OP_FLAGS(SLJIT_OR | ((other_cases[3] == NOTACHAR) ? SLJIT_SET_Z : 0), TMP2, 0, SLJIT_EQUAL);
other_cases += 3;
}
else
{
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset));
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset));
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
}
while (*other_cases != NOTACHAR)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset));
- OP_FLAGS(SLJIT_OR | ((*other_cases == NOTACHAR) ? SLJIT_SET_E : 0), TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset));
+ OP_FLAGS(SLJIT_OR | ((*other_cases == NOTACHAR) ? SLJIT_SET_Z : 0), TMP2, 0, SLJIT_EQUAL);
}
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
case PT_UCNC:
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_DOLLAR_SIGN - charoffset));
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_COMMERCIAL_AT - charoffset));
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_GRAVE_ACCENT - charoffset));
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_DOLLAR_SIGN - charoffset));
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_COMMERCIAL_AT - charoffset));
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_GRAVE_ACCENT - charoffset));
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
SET_CHAR_OFFSET(0xa0);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(0xd7ff - charoffset));
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(0xd7ff - charoffset));
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_LESS_EQUAL);
SET_CHAR_OFFSET(0);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xe000 - 0);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_GREATER_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xe000 - 0);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_GREATER_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
case PT_PXGRAPH:
/* C and Z groups are the farthest two groups. */
SET_TYPE_OFFSET(ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER);
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_GREATER);
jump = CMP(SLJIT_NOT_EQUAL, typereg, 0, SLJIT_IMM, ucp_Cf - ucp_Ll);
/* In case of ucp_Cf, we overwrite the result. */
SET_CHAR_OFFSET(0x2066);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x2066);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x2066);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
JUMPHERE(jump);
jump = CMP(SLJIT_ZERO ^ invertcmp, TMP2, 0, SLJIT_IMM, 0);
@@ -5946,21 +5959,21 @@ while (*cc != XCL_END)
case PT_PXPRINT:
/* C and Z groups are the farthest two groups. */
SET_TYPE_OFFSET(ucp_Ll);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER);
+ OP2(SLJIT_SUB | SLJIT_SET_GREATER, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_GREATER);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Ll);
- OP_FLAGS(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_NOT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Ll);
+ OP_FLAGS(SLJIT_AND, TMP2, 0, SLJIT_NOT_EQUAL);
jump = CMP(SLJIT_NOT_EQUAL, typereg, 0, SLJIT_IMM, ucp_Cf - ucp_Ll);
/* In case of ucp_Cf, we overwrite the result. */
SET_CHAR_OFFSET(0x2066);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066);
- OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066);
+ OP_FLAGS(SLJIT_OR, TMP2, 0, SLJIT_EQUAL);
JUMPHERE(jump);
jump = CMP(SLJIT_ZERO ^ invertcmp, TMP2, 0, SLJIT_IMM, 0);
@@ -5968,21 +5981,21 @@ while (*cc != XCL_END)
case PT_PXPUNCT:
SET_TYPE_OFFSET(ucp_Sc);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Sc);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Sc);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS_EQUAL);
SET_CHAR_OFFSET(0);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x7f);
- OP_FLAGS(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x7f);
+ OP_FLAGS(SLJIT_AND, TMP2, 0, SLJIT_LESS_EQUAL);
SET_TYPE_OFFSET(ucp_Pc);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ps - ucp_Pc);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS_EQUAL, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ps - ucp_Pc);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_LESS_EQUAL);
jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp);
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
cc += 2;
@@ -6028,6 +6041,7 @@ switch(type)
case OP_NOT_WORD_BOUNDARY:
case OP_WORD_BOUNDARY:
add_jump(compiler, &common->wordboundary, JUMP(SLJIT_FAST_CALL));
+ sljit_set_current_flags(compiler, SLJIT_SET_Z);
add_jump(compiler, backtracks, JUMP(type == OP_NOT_WORD_BOUNDARY ? SLJIT_NOT_ZERO : SLJIT_ZERO));
return cc;
@@ -6043,10 +6057,10 @@ switch(type)
else
{
jump[1] = CMP(SLJIT_EQUAL, TMP2, 0, STR_END, 0);
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0);
- OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff);
- OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_NOT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_LESS, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0);
+ OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_LESS);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff);
+ OP_FLAGS(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, SLJIT_NOT_EQUAL);
add_jump(compiler, backtracks, JUMP(SLJIT_NOT_EQUAL));
check_partial(common, TRUE);
add_jump(compiler, backtracks, JUMP(SLJIT_JUMP));
@@ -6068,9 +6082,9 @@ switch(type)
OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0));
jump[1] = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR);
OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2));
- OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0);
+ OP2(SLJIT_SUB | SLJIT_SET_Z | SLJIT_SET_GREATER, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0);
jump[2] = JUMP(SLJIT_GREATER);
- add_jump(compiler, backtracks, JUMP(SLJIT_LESS));
+ add_jump(compiler, backtracks, JUMP(SLJIT_NOT_EQUAL) /* LESS */);
/* Equal. */
OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1));
jump[3] = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL);
@@ -6089,6 +6103,7 @@ switch(type)
read_char_range(common, common->nlmin, common->nlmax, TRUE);
add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, STR_END, 0));
add_jump(compiler, &common->anynewline, JUMP(SLJIT_FAST_CALL));
+ sljit_set_current_flags(compiler, SLJIT_SET_Z);
add_jump(compiler, backtracks, JUMP(SLJIT_ZERO));
OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1);
}
@@ -6204,7 +6219,7 @@ switch(type)
label = LABEL();
add_jump(compiler, backtracks, CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP3, 0));
skip_char_back(common);
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, TMP2, 0, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
}
else
@@ -6217,7 +6232,7 @@ switch(type)
check_start_used_ptr(common);
return cc + LINK_SIZE;
}
-SLJIT_ASSERT_STOP();
+SLJIT_UNREACHABLE();
return cc;
}
@@ -6250,7 +6265,7 @@ switch(type)
#endif
read_char8_type(common, type == OP_NOT_DIGIT);
/* Flip the starting bit in the negative case. */
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_digit);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_digit);
add_jump(compiler, backtracks, JUMP(type == OP_DIGIT ? SLJIT_ZERO : SLJIT_NOT_ZERO));
return cc;
@@ -6264,7 +6279,7 @@ switch(type)
else
#endif
read_char8_type(common, type == OP_NOT_WHITESPACE);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_space);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_space);
add_jump(compiler, backtracks, JUMP(type == OP_WHITESPACE ? SLJIT_ZERO : SLJIT_NOT_ZERO));
return cc;
@@ -6278,7 +6293,7 @@ switch(type)
else
#endif
read_char8_type(common, type == OP_NOT_WORDCHAR);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_word);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_word);
add_jump(compiler, backtracks, JUMP(type == OP_WORDCHAR ? SLJIT_ZERO : SLJIT_NOT_ZERO));
return cc;
@@ -6320,8 +6335,8 @@ switch(type)
#elif defined COMPILE_PCRE16
jump[0] = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800);
OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
- OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800);
+ OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_EQUAL);
OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0);
#endif
@@ -6383,6 +6398,7 @@ switch(type)
detect_partial_match(common, backtracks);
read_char_range(common, 0x9, 0x3000, type == OP_NOT_HSPACE);
add_jump(compiler, &common->hspace, JUMP(SLJIT_FAST_CALL));
+ sljit_set_current_flags(compiler, SLJIT_SET_Z);
add_jump(compiler, backtracks, JUMP(type == OP_NOT_HSPACE ? SLJIT_NOT_ZERO : SLJIT_ZERO));
return cc;
@@ -6392,6 +6408,7 @@ switch(type)
detect_partial_match(common, backtracks);
read_char_range(common, 0xa, 0x2029, type == OP_NOT_VSPACE);
add_jump(compiler, &common->vspace, JUMP(SLJIT_FAST_CALL));
+ sljit_set_current_flags(compiler, SLJIT_SET_Z);
add_jump(compiler, backtracks, JUMP(type == OP_NOT_VSPACE ? SLJIT_NOT_ZERO : SLJIT_ZERO));
return cc;
@@ -6418,7 +6435,7 @@ switch(type)
OP1(SLJIT_MOV_U32, TMP1, 0, SLJIT_MEM1(STACK_TOP), (sljit_sw)PRIV(ucp_gbtable));
OP1(SLJIT_MOV, STACK_TOP, 0, TMP2, 0);
OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
JUMPTO(SLJIT_NOT_ZERO, label);
OP1(SLJIT_MOV, STR_PTR, 0, TMP3, 0);
@@ -6587,7 +6604,7 @@ switch(type)
OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3);
OP1(SLJIT_MOV_U8, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc);
OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0);
- OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
+ OP2(SLJIT_AND | SLJIT_SET_Z, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0);
add_jump(compiler, backtracks, JUMP(SLJIT_ZERO));
#if defined SUPPORT_UTF || !defined COMPILE_PCRE8
@@ -6604,7 +6621,7 @@ switch(type)
return cc + GET(cc, 0) - 1;
#endif
}
-SLJIT_ASSERT_STOP();
+SLJIT_UNREACHABLE();
return cc;
}
@@ -6790,9 +6807,9 @@ else
#endif /* SUPPORT_UTF && SUPPORT_UCP */
{
if (ref)
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP1, 0);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP1, 0);
else
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw), TMP1, 0);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw), TMP1, 0);
if (withchecks)
jump = JUMP(SLJIT_ZERO);
@@ -6883,7 +6900,7 @@ switch(type)
cc += 1 + IMM2_SIZE + 1 + 2 * IMM2_SIZE;
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
@@ -6897,7 +6914,7 @@ if (!minimize)
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, 0);
/* Temporary release of STR_PTR. */
- OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
/* Handles both invalid and empty cases. Since the minimum repeat,
is zero the invalid case is basically the same as an empty case. */
if (ref)
@@ -6910,7 +6927,7 @@ if (!minimize)
zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw));
}
/* Restore if not zero length. */
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
}
else
{
@@ -7157,7 +7174,7 @@ return (*PUBL(callout))(callout_block);
(((int)sizeof(PUBL(callout_block)) + 7) & ~7)
#define CALLOUT_ARG_OFFSET(arg) \
- (-CALLOUT_ARG_SIZE + SLJIT_OFFSETOF(PUBL(callout_block), arg))
+ SLJIT_OFFSETOF(PUBL(callout_block), arg)
static SLJIT_INLINE pcre_uchar *compile_callout_matchingpath(compiler_common *common, pcre_uchar *cc, backtrack_common *parent)
{
@@ -7187,7 +7204,8 @@ OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(mark), (common->mark_pt
/* Needed to save important temporary registers. */
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, STACK_TOP, 0);
-OP2(SLJIT_SUB, SLJIT_R1, 0, STACK_TOP, 0, SLJIT_IMM, CALLOUT_ARG_SIZE);
+/* SLJIT_R0 = arguments */
+OP1(SLJIT_MOV, SLJIT_R1, 0, STACK_TOP, 0);
GET_LOCAL_BASE(SLJIT_R2, 0, OVECTOR_START);
sljit_emit_ijump(compiler, SLJIT_CALL3, SLJIT_IMM, SLJIT_FUNC_OFFSET(do_callout));
OP1(SLJIT_MOV_S32, SLJIT_RETURN_REG, 0, SLJIT_RETURN_REG, 0);
@@ -7195,12 +7213,12 @@ OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0);
free_stack(common, CALLOUT_ARG_SIZE / sizeof(sljit_sw));
/* Check return value. */
-OP2(SLJIT_SUB | SLJIT_SET_S, SLJIT_UNUSED, 0, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0);
+OP2(SLJIT_SUB | SLJIT_SET_Z | SLJIT_SET_SIG_GREATER, SLJIT_UNUSED, 0, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0);
add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_SIG_GREATER));
if (common->forced_quit_label == NULL)
- add_jump(compiler, &common->forced_quit, JUMP(SLJIT_SIG_LESS));
+ add_jump(compiler, &common->forced_quit, JUMP(SLJIT_NOT_EQUAL) /* SIG_LESS */);
else
- JUMPTO(SLJIT_SIG_LESS, common->forced_quit_label);
+ JUMPTO(SLJIT_NOT_EQUAL /* SIG_LESS */, common->forced_quit_label);
return cc + 2 + 2 * LINK_SIZE;
}
@@ -7321,7 +7339,7 @@ else
allocate_stack(common, framesize + extrasize);
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- OP2(SLJIT_SUB, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw));
+ OP2(SLJIT_ADD, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw));
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP2, 0);
if (needs_control_head)
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr);
@@ -7392,22 +7410,22 @@ while (1)
free_stack(common, extrasize);
if (needs_control_head)
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), 0);
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(-1));
}
else
{
if ((opcode != OP_ASSERT_NOT && opcode != OP_ASSERTBACK_NOT) || conditional)
{
/* We don't need to keep the STR_PTR, only the previous private_data_ptr. */
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw));
if (needs_control_head)
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), 0);
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(-1));
}
else
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
if (needs_control_head)
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), (framesize + 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(-framesize - 2));
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
}
}
@@ -7418,25 +7436,25 @@ while (1)
if (conditional)
{
if (extrasize > 0)
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), needs_control_head ? sizeof(sljit_sw) : 0);
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), needs_control_head ? STACK(-2) : STACK(-1));
}
else if (bra == OP_BRAZERO)
{
if (framesize < 0)
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(-extrasize));
else
{
- OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw));
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (framesize + extrasize - 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(-framesize - 1));
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(-framesize - extrasize));
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0);
}
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0);
}
else if (framesize >= 0)
{
/* For OP_BRA and OP_BRAMINZERO. */
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-framesize - 1));
}
}
add_jump(compiler, found, JUMP(SLJIT_JUMP));
@@ -7480,12 +7498,12 @@ if (common->positive_assert_quit != NULL)
set_jumps(common->positive_assert_quit, LABEL());
SLJIT_ASSERT(framesize != no_stack);
if (framesize < 0)
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, extrasize * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, extrasize * sizeof(sljit_sw));
else
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw));
}
JUMPHERE(jump);
}
@@ -7534,18 +7552,18 @@ if (opcode == OP_ASSERT || opcode == OP_ASSERTBACK)
{
/* We know that STR_PTR was stored on the top of the stack. */
if (extrasize > 0)
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(-extrasize));
/* Keep the STR_PTR on the top of the stack. */
if (bra == OP_BRAZERO)
{
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
if (extrasize == 2)
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0);
}
else if (bra == OP_BRAMINZERO)
{
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0);
}
}
@@ -7554,13 +7572,13 @@ if (opcode == OP_ASSERT || opcode == OP_ASSERTBACK)
if (bra == OP_BRA)
{
/* We don't need to keep the STR_PTR, only the previous private_data_ptr. */
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw));
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 2) * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(-extrasize + 1));
}
else
{
/* We don't need to keep the STR_PTR, only the previous private_data_ptr. */
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 2) * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 2) * sizeof(sljit_sw));
if (extrasize == 2)
{
OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0));
@@ -7588,7 +7606,7 @@ if (opcode == OP_ASSERT || opcode == OP_ASSERTBACK)
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-framesize - 1));
}
set_jumps(backtrack->common.topbacktracks, LABEL());
}
@@ -7675,23 +7693,23 @@ if (framesize < 0)
}
if (needs_control_head)
- OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), (ket != OP_KET || has_alternatives) ? sizeof(sljit_sw) : 0);
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), (ket != OP_KET || has_alternatives) ? STACK(-2) : STACK(-1));
/* TMP2 which is set here used by OP_KETRMAX below. */
if (ket == OP_KETRMAX)
- OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), 0);
+ OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(-1));
else if (ket == OP_KETRMIN)
{
/* Move the STR_PTR to the private_data_ptr. */
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), 0);
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-1));
}
}
else
{
stacksize = (ket != OP_KET || has_alternatives) ? 2 : 1;
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + stacksize) * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + stacksize) * sizeof(sljit_sw));
if (needs_control_head)
- OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), 0);
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(-1));
if (ket == OP_KETRMAX)
{
@@ -7927,7 +7945,7 @@ if (bra == OP_BRAMINZERO)
{
/* Except when the whole stack frame must be saved. */
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- braminzero = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), (BACKTRACK_AS(bracket_backtrack)->u.framesize + 1) * sizeof(sljit_sw));
+ braminzero = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), STACK(-BACKTRACK_AS(bracket_backtrack)->u.framesize - 2));
}
JUMPHERE(skip);
}
@@ -8000,7 +8018,7 @@ if (opcode == OP_ONCE)
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0);
if (BACKTRACK_AS(bracket_backtrack)->u.framesize == no_frame)
- OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, needs_control_head ? (2 * sizeof(sljit_sw)) : sizeof(sljit_sw));
+ OP2(SLJIT_ADD, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, needs_control_head ? (2 * sizeof(sljit_sw)) : sizeof(sljit_sw));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize + 1), TMP2, 0);
}
else if (ket == OP_KETRMAX || has_alternatives)
@@ -8018,7 +8036,7 @@ if (opcode == OP_ONCE)
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0);
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- OP2(SLJIT_SUB, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw));
+ OP2(SLJIT_ADD, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw));
stacksize = needs_control_head ? 1 : 0;
if (ket != OP_KET || has_alternatives)
@@ -8090,13 +8108,13 @@ if (opcode == OP_COND || opcode == OP_SCOND)
slot = common->name_table + GET2(matchingpath, 1) * common->name_entry_size;
OP1(SLJIT_MOV, TMP3, 0, STR_PTR, 0);
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1));
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(slot, 0) << 1), TMP1, 0);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(slot, 0) << 1), TMP1, 0);
slot += common->name_entry_size;
i--;
while (i-- > 0)
{
OP2(SLJIT_SUB, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(slot, 0) << 1), TMP1, 0);
- OP2(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, STR_PTR, 0);
+ OP2(SLJIT_OR | SLJIT_SET_Z, TMP2, 0, TMP2, 0, STR_PTR, 0);
slot += common->name_entry_size;
}
OP1(SLJIT_MOV, STR_PTR, 0, TMP3, 0);
@@ -8111,7 +8129,7 @@ if (opcode == OP_COND || opcode == OP_SCOND)
if (*matchingpath == OP_FAIL)
stacksize = 0;
- if (*matchingpath == OP_RREF)
+ else if (*matchingpath == OP_RREF)
{
stacksize = GET2(matchingpath, 1);
if (common->currententry == NULL)
@@ -8244,7 +8262,7 @@ if (ket == OP_KETRMAX)
{
if (has_alternatives)
BACKTRACK_AS(bracket_backtrack)->alternative_matchingpath = LABEL();
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, rmax_label);
/* Drop STR_PTR for greedy plus quantifier. */
if (opcode != OP_ONCE)
@@ -8274,7 +8292,7 @@ if (ket == OP_KETRMAX)
if (repeat_type == OP_EXACT)
{
count_match(common);
- OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, rmax_label);
}
else if (repeat_type == OP_UPTO)
@@ -8374,7 +8392,7 @@ switch(opcode)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
@@ -8452,7 +8470,7 @@ else
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
if (needs_control_head)
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr);
- OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, -STACK(stacksize - 1));
+ OP2(SLJIT_ADD, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw));
stack = 0;
if (!zero)
@@ -8524,7 +8542,7 @@ while (*cc != OP_KETRPOS)
{
if (offset != 0)
{
- OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, stacksize * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, stacksize * sizeof(sljit_sw));
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), cbraprivptr);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), cbraprivptr, STR_PTR, 0);
@@ -8535,10 +8553,10 @@ while (*cc != OP_KETRPOS)
else
{
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- OP2(SLJIT_ADD, STACK_TOP, 0, TMP2, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, TMP2, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw));
if (opcode == OP_SBRAPOS)
- OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw));
- OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw), STR_PTR, 0);
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), STACK(-framesize - 2));
+ OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), STACK(-framesize - 2), STR_PTR, 0);
}
/* Even if the match is empty, we need to reset the control head. */
@@ -8584,7 +8602,7 @@ while (*cc != OP_KETRPOS)
else
{
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(TMP2), STACK(-framesize - 2));
}
}
@@ -8601,7 +8619,7 @@ if (!zero)
if (framesize < 0)
add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), STACK(stacksize - 1), SLJIT_IMM, 0));
else /* TMP2 is set to [private_data_ptr] above. */
- add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(TMP2), (stacksize - 1) * sizeof(sljit_sw), SLJIT_IMM, 0));
+ add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(TMP2), STACK(-stacksize), SLJIT_IMM, 0));
}
/* None of them matched. */
@@ -8824,7 +8842,7 @@ if (exact > 1)
OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, exact);
label = LABEL();
compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, FALSE);
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
}
else
@@ -8832,7 +8850,7 @@ if (exact > 1)
OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, exact);
label = LABEL();
compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, TRUE);
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
}
}
@@ -8862,7 +8880,7 @@ switch(opcode)
if (opcode == OP_UPTO)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0);
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
jump = JUMP(SLJIT_ZERO);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0, TMP1, 0);
}
@@ -8924,7 +8942,7 @@ switch(opcode)
label = LABEL();
if (opcode == OP_UPTO)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_ZERO));
}
compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, FALSE);
@@ -8944,7 +8962,7 @@ switch(opcode)
OP1(SLJIT_MOV, base, offset1, STR_PTR, 0);
if (opcode == OP_UPTO)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
add_jump(compiler, &no_match, JUMP(SLJIT_ZERO));
}
@@ -8971,7 +8989,7 @@ switch(opcode)
if (opcode == OP_UPTO)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
}
else
@@ -9000,7 +9018,7 @@ switch(opcode)
if (opcode == OP_UPTO)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
}
else
@@ -9026,7 +9044,7 @@ switch(opcode)
compile_char1_matchingpath(common, type, cc, &no_char1_match, FALSE);
if (opcode == OP_UPTO)
{
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
}
@@ -9113,7 +9131,7 @@ switch(opcode)
label = LABEL();
compile_char1_matchingpath(common, type, cc, &no_match, TRUE);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1, STR_PTR, 0);
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
set_jumps(no_match, LABEL());
OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1);
@@ -9124,7 +9142,7 @@ switch(opcode)
label = LABEL();
detect_partial_match(common, &no_match);
compile_char1_matchingpath(common, type, cc, &no_char1_match, FALSE);
- OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1);
JUMPTO(SLJIT_NOT_ZERO, label);
OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1));
set_jumps(no_char1_match, LABEL());
@@ -9142,7 +9160,7 @@ switch(opcode)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
@@ -9264,7 +9282,7 @@ size = 3 + (size < 0 ? 0 : size);
OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr);
allocate_stack(common, size);
if (size > 3)
- OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0, SLJIT_IMM, (size - 3) * sizeof(sljit_sw));
+ OP2(SLJIT_ADD, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0, SLJIT_IMM, (size - 3) * sizeof(sljit_sw));
else
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(size - 1), SLJIT_IMM, BACKTRACK_AS(then_trap_backtrack)->start);
@@ -9569,7 +9587,7 @@ while (cc < ccend)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
return;
}
if (cc == NULL)
@@ -9677,7 +9695,7 @@ switch(opcode)
case OP_MINUPTO:
OP1(SLJIT_MOV, TMP1, 0, base, offset1);
OP1(SLJIT_MOV, STR_PTR, 0, base, offset0);
- OP2(SLJIT_SUB | SLJIT_SET_E, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
+ OP2(SLJIT_SUB | SLJIT_SET_Z, TMP1, 0, TMP1, 0, SLJIT_IMM, 1);
add_jump(compiler, &jumplist, JUMP(SLJIT_ZERO));
OP1(SLJIT_MOV, base, offset1, TMP1, 0);
@@ -9723,7 +9741,7 @@ switch(opcode)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
@@ -9831,7 +9849,7 @@ if (*cc == OP_ASSERT || *cc == OP_ASSERTBACK)
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(assert_backtrack)->private_data_ptr);
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(assert_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(assert_backtrack)->framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(assert_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-CURRENT_AS(assert_backtrack)->framesize - 1));
set_jumps(current->topbacktracks, LABEL());
}
@@ -9841,7 +9859,7 @@ else
if (bra == OP_BRAZERO)
{
/* We know there is enough place on the stack. */
- OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw));
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0);
JUMPTO(SLJIT_JUMP, CURRENT_AS(assert_backtrack)->matchingpath);
JUMPHERE(brajump);
@@ -9954,7 +9972,7 @@ else if (ket == OP_KETRMIN)
else
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr);
- CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), (CURRENT_AS(bracket_backtrack)->u.framesize + 1) * sizeof(sljit_sw), CURRENT_AS(bracket_backtrack)->recursive_matchingpath);
+ CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), STACK(-CURRENT_AS(bracket_backtrack)->u.framesize - 2), CURRENT_AS(bracket_backtrack)->recursive_matchingpath);
}
/* Drop STR_PTR for non-greedy plus quantifier. */
if (opcode != OP_ONCE)
@@ -10060,7 +10078,7 @@ if (SLJIT_UNLIKELY(opcode == OP_COND) || SLJIT_UNLIKELY(opcode == OP_SCOND))
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr);
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), assert->framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-assert->framesize - 1));
}
cond = JUMP(SLJIT_JUMP);
set_jumps(CURRENT_AS(bracket_backtrack)->u.assert->condfailed, LABEL());
@@ -10201,7 +10219,7 @@ if (has_alternatives)
{
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr);
add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), assert->framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-assert->framesize - 1));
}
JUMPHERE(cond);
}
@@ -10256,7 +10274,7 @@ else if (opcode == OP_ONCE)
JUMPHERE(once);
/* Restore previous private_data_ptr */
if (CURRENT_AS(bracket_backtrack)->u.framesize >= 0)
- OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(bracket_backtrack)->u.framesize * sizeof(sljit_sw));
+ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-CURRENT_AS(bracket_backtrack)->u.framesize - 1));
else if (ket == OP_KETRMIN)
{
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(1));
@@ -10346,7 +10364,7 @@ if (current->topbacktracks)
free_stack(common, CURRENT_AS(bracketpos_backtrack)->stacksize);
JUMPHERE(jump);
}
-OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(bracketpos_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(bracketpos_backtrack)->framesize * sizeof(sljit_sw));
+OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(bracketpos_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(-CURRENT_AS(bracketpos_backtrack)->framesize - 1));
}
static SLJIT_INLINE void compile_braminzero_backtrackingpath(compiler_common *common, struct backtrack_common *current)
@@ -10392,10 +10410,10 @@ if (opcode == OP_THEN || opcode == OP_THEN_ARG)
jump = JUMP(SLJIT_JUMP);
loop = LABEL();
- OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(STACK_TOP), -(int)sizeof(sljit_sw));
+ OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(STACK_TOP), STACK(0));
JUMPHERE(jump);
- CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), -(int)(2 * sizeof(sljit_sw)), TMP1, 0, loop);
- CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), -(int)(3 * sizeof(sljit_sw)), TMP2, 0, loop);
+ CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), STACK(1), TMP1, 0, loop);
+ CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), STACK(2), TMP2, 0, loop);
add_jump(compiler, &common->then_trap->quit, JUMP(SLJIT_JUMP));
return;
}
@@ -10645,7 +10663,7 @@ while (current)
break;
default:
- SLJIT_ASSERT_STOP();
+ SLJIT_UNREACHABLE();
break;
}
current = current->prev;
@@ -10684,7 +10702,7 @@ sljit_emit_fast_enter(compiler, TMP2, 0);
count_match(common);
allocate_stack(common, private_data_size + framesize + alternativesize);
OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(private_data_size + framesize + alternativesize - 1), TMP2, 0);
-copy_private_data(common, ccbegin, ccend, TRUE, private_data_size + framesize + alternativesize, framesize + alternativesize, needs_control_head);
+copy_private_data(common, ccbegin, ccend, TRUE, framesize + alternativesize, private_data_size + framesize + alternativesize, needs_control_head);
if (needs_control_head)
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, STACK_TOP, 0);
@@ -10737,9 +10755,9 @@ if (common->quit != NULL)
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr);
if (needs_frame)
{
- OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
- add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
+ add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
}
OP1(SLJIT_MOV, TMP3, 0, SLJIT_IMM, 0);
common->quit = NULL;
@@ -10750,32 +10768,32 @@ set_jumps(common->accept, LABEL());
OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr);
if (needs_frame)
{
- OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
- add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
+ add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL));
+ OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw));
}
OP1(SLJIT_MOV, TMP3, 0, SLJIT_IMM, 1);
JUMPHERE(jump);
if (common->quit != NULL)
set_jumps(common->quit, LABEL());
-copy_private_data(common, ccbegin, ccend, FALSE, private_data_size + framesize + alternativesize, framesize + alternativesize, needs_control_head);
+copy_private_data(common, ccbegin, ccend, FALSE, framesize + alternativesize, private_data_size + framesize + alternativesize, needs_control_head);
free_stack(common, private_data_size + framesize + alternativesize);
if (needs_control_head)
{
- OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), 2 * sizeof(sljit_sw));
- OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), sizeof(sljit_sw));
+ OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(-3));
+ OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(-2));
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, TMP1, 0);
OP1(SLJIT_MOV, TMP1, 0, TMP3, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, TMP2, 0);
}
else
{
- OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), sizeof(sljit_sw));
+ OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(-2));
OP1(SLJIT_MOV, TMP1, 0, TMP3, 0);
OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, TMP2, 0);
}
-sljit_emit_fast_return(compiler, SLJIT_MEM1(STACK_TOP), 0);
+sljit_emit_fast_return(compiler, SLJIT_MEM1(STACK_TOP), STACK(-1));
}
#undef COMPILE_BACKTRACKINGPATH
@@ -11237,7 +11255,7 @@ OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP2, 0);
OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0);
OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, stack));
OP1(SLJIT_MOV, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, top), STACK_TOP, 0);
-OP2(SLJIT_ADD, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, limit), SLJIT_IMM, STACK_GROWTH_RATE);
+OP2(SLJIT_SUB, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, limit), SLJIT_IMM, STACK_GROWTH_RATE);
sljit_emit_ijump(compiler, SLJIT_CALL2, SLJIT_IMM, SLJIT_FUNC_OFFSET(sljit_stack_resize));
jump = CMP(SLJIT_NOT_EQUAL, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0);
@@ -11391,10 +11409,10 @@ union {
sljit_u8 local_space[MACHINE_STACK_SIZE];
struct sljit_stack local_stack;
-local_stack.top = (sljit_sw)&local_space;
-local_stack.base = local_stack.top;
-local_stack.limit = local_stack.base + MACHINE_STACK_SIZE;
-local_stack.max_limit = local_stack.limit;
+local_stack.max_limit = local_space;
+local_stack.limit = local_space;
+local_stack.base = local_space + MACHINE_STACK_SIZE;
+local_stack.top = local_space + MACHINE_STACK_SIZE;
arguments->stack = &local_stack;
convert_executable_func.executable_func = executable_func;
return convert_executable_func.call_executable_func(arguments);
diff --git a/src/third_party/pcre-8.39/pcre_jit_test.c b/src/third_party/pcre-8.41/pcre_jit_test.c
index 9b61ec000fa..034cb52697f 100644
--- a/src/third_party/pcre-8.39/pcre_jit_test.c
+++ b/src/third_party/pcre-8.41/pcre_jit_test.c
@@ -687,6 +687,7 @@ static struct regression_test_case regression_test_cases[] = {
{ PCRE_FIRSTLINE | PCRE_NEWLINE_LF | PCRE_DOTALL, 0 | F_NOMATCH, "ab.", "ab" },
{ MUA | PCRE_FIRSTLINE, 1 | F_NOMATCH, "^[a-d0-9]", "\nxx\nd" },
{ PCRE_NEWLINE_ANY | PCRE_FIRSTLINE | PCRE_DOTALL, 0, "....a", "012\n0a" },
+ { MUA | PCRE_FIRSTLINE, 0, "[aC]", "a" },
/* Recurse. */
{ MUA, 0, "(a)(?1)", "aa" },
diff --git a/src/third_party/pcre-8.39/pcre_maketables.c b/src/third_party/pcre-8.41/pcre_maketables.c
index a44a6eaa905..a44a6eaa905 100644
--- a/src/third_party/pcre-8.39/pcre_maketables.c
+++ b/src/third_party/pcre-8.41/pcre_maketables.c
diff --git a/src/third_party/pcre-8.39/pcre_newline.c b/src/third_party/pcre-8.41/pcre_newline.c
index b8f5a4de19c..b8f5a4de19c 100644
--- a/src/third_party/pcre-8.39/pcre_newline.c
+++ b/src/third_party/pcre-8.41/pcre_newline.c
diff --git a/src/third_party/pcre-8.39/pcre_ord2utf8.c b/src/third_party/pcre-8.41/pcre_ord2utf8.c
index 95f1beb963e..95f1beb963e 100644
--- a/src/third_party/pcre-8.39/pcre_ord2utf8.c
+++ b/src/third_party/pcre-8.41/pcre_ord2utf8.c
diff --git a/src/third_party/pcre-8.39/pcre_printint.c b/src/third_party/pcre-8.41/pcre_printint.c
index 60dcb55efbf..60dcb55efbf 100644
--- a/src/third_party/pcre-8.39/pcre_printint.c
+++ b/src/third_party/pcre-8.41/pcre_printint.c
diff --git a/src/third_party/pcre-8.39/pcre_refcount.c b/src/third_party/pcre-8.41/pcre_refcount.c
index 79efa90f216..79efa90f216 100644
--- a/src/third_party/pcre-8.39/pcre_refcount.c
+++ b/src/third_party/pcre-8.41/pcre_refcount.c
diff --git a/src/third_party/pcre-8.39/pcre_scanner.cc b/src/third_party/pcre-8.41/pcre_scanner.cc
index 6be2be6829b..6be2be6829b 100644
--- a/src/third_party/pcre-8.39/pcre_scanner.cc
+++ b/src/third_party/pcre-8.41/pcre_scanner.cc
diff --git a/src/third_party/pcre-8.39/pcre_scanner.h b/src/third_party/pcre-8.41/pcre_scanner.h
index 5617e4515cb..5617e4515cb 100644
--- a/src/third_party/pcre-8.39/pcre_scanner.h
+++ b/src/third_party/pcre-8.41/pcre_scanner.h
diff --git a/src/third_party/pcre-8.39/pcre_scanner_unittest.cc b/src/third_party/pcre-8.41/pcre_scanner_unittest.cc
index c00312c4f63..623e2afda80 100644
--- a/src/third_party/pcre-8.39/pcre_scanner_unittest.cc
+++ b/src/third_party/pcre-8.41/pcre_scanner_unittest.cc
@@ -57,6 +57,7 @@
} while (0)
using std::vector;
+using std::string;
using pcrecpp::StringPiece;
using pcrecpp::Scanner;
diff --git a/src/third_party/pcre-8.39/pcre_string_utils.c b/src/third_party/pcre-8.41/pcre_string_utils.c
index 25eacc85073..25eacc85073 100644
--- a/src/third_party/pcre-8.39/pcre_string_utils.c
+++ b/src/third_party/pcre-8.41/pcre_string_utils.c
diff --git a/src/third_party/pcre-8.39/pcre_stringpiece.cc b/src/third_party/pcre-8.41/pcre_stringpiece.cc
index 67c0f1fc0e5..67c0f1fc0e5 100644
--- a/src/third_party/pcre-8.39/pcre_stringpiece.cc
+++ b/src/third_party/pcre-8.41/pcre_stringpiece.cc
diff --git a/src/third_party/pcre-8.39/pcre_stringpiece.h b/src/third_party/pcre-8.41/pcre_stringpiece.h
index cc3dc42963a..cb94f52a010 100644
--- a/src/third_party/pcre-8.39/pcre_stringpiece.h
+++ b/src/third_party/pcre-8.41/pcre_stringpiece.h
@@ -52,12 +52,12 @@
#include <pcre.h>
+namespace pcrecpp {
+
using std::memcmp;
using std::strlen;
using std::string;
-namespace pcrecpp {
-
class PCRECPP_EXP_DEFN StringPiece {
private:
const char* ptr_;
diff --git a/src/third_party/pcre-8.39/pcre_stringpiece.h.in b/src/third_party/pcre-8.41/pcre_stringpiece.h.in
index eb25826b453..f54f3f3b31b 100644
--- a/src/third_party/pcre-8.39/pcre_stringpiece.h.in
+++ b/src/third_party/pcre-8.41/pcre_stringpiece.h.in
@@ -52,12 +52,12 @@
#include <pcre.h>
+namespace pcrecpp {
+
using std::memcmp;
using std::strlen;
using std::string;
-namespace pcrecpp {
-
class PCRECPP_EXP_DEFN StringPiece {
private:
const char* ptr_;
diff --git a/src/third_party/pcre-8.39/pcre_stringpiece_unittest.cc b/src/third_party/pcre-8.41/pcre_stringpiece_unittest.cc
index 1c4759da3b0..88e73a1f976 100644
--- a/src/third_party/pcre-8.39/pcre_stringpiece_unittest.cc
+++ b/src/third_party/pcre-8.41/pcre_stringpiece_unittest.cc
@@ -24,6 +24,7 @@
} \
} while (0)
+using std::string;
using pcrecpp::StringPiece;
static void CheckSTLComparator() {
diff --git a/src/third_party/pcre-8.39/pcre_study.c b/src/third_party/pcre-8.41/pcre_study.c
index d9d4960d84e..d9d4960d84e 100644
--- a/src/third_party/pcre-8.39/pcre_study.c
+++ b/src/third_party/pcre-8.41/pcre_study.c
diff --git a/src/third_party/pcre-8.39/pcre_tables.c b/src/third_party/pcre-8.41/pcre_tables.c
index 4960af57c4d..5e18e8cf904 100644
--- a/src/third_party/pcre-8.39/pcre_tables.c
+++ b/src/third_party/pcre-8.41/pcre_tables.c
@@ -6,7 +6,7 @@
and semantics are as close as possible to those of the Perl 5 language.
Written by Philip Hazel
- Copyright (c) 1997-2012 University of Cambridge
+ Copyright (c) 1997-2017 University of Cambridge
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
@@ -161,7 +161,7 @@ const pcre_uint32 PRIV(ucp_gbtable[]) = {
(1<<ucp_gbExtend)|(1<<ucp_gbSpacingMark), /* 5 SpacingMark */
(1<<ucp_gbExtend)|(1<<ucp_gbSpacingMark)|(1<<ucp_gbL)| /* 6 L */
- (1<<ucp_gbL)|(1<<ucp_gbV)|(1<<ucp_gbLV)|(1<<ucp_gbLVT),
+ (1<<ucp_gbV)|(1<<ucp_gbLV)|(1<<ucp_gbLVT),
(1<<ucp_gbExtend)|(1<<ucp_gbSpacingMark)|(1<<ucp_gbV)| /* 7 V */
(1<<ucp_gbT),
diff --git a/src/third_party/pcre-8.39/pcre_ucd.c b/src/third_party/pcre-8.41/pcre_ucd.c
index 69c4fd42c34..f22f826c4c2 100644
--- a/src/third_party/pcre-8.39/pcre_ucd.c
+++ b/src/third_party/pcre-8.41/pcre_ucd.c
@@ -38,6 +38,20 @@ const pcre_uint16 PRIV(ucd_stage2)[] = {0};
const pcre_uint32 PRIV(ucd_caseless_sets)[] = {0};
#else
+/* If the 32-bit library is run in non-32-bit mode, character values
+greater than 0x10ffff may be encountered. For these we set up a
+special record. */
+
+#ifdef COMPILE_PCRE32
+const ucd_record PRIV(dummy_ucd_record)[] = {{
+ ucp_Common, /* script */
+ ucp_Cn, /* type unassigned */
+ ucp_gbOther, /* grapheme break property */
+ 0, /* case set */
+ 0, /* other case */
+ }};
+#endif
+
/* When recompiling tables with a new Unicode version, please check the
types in this structure definition from pcre_internal.h (the actual
field names will be different):
diff --git a/src/third_party/pcre-8.39/pcre_valid_utf8.c b/src/third_party/pcre-8.41/pcre_valid_utf8.c
index 3b0f6464a35..3b0f6464a35 100644
--- a/src/third_party/pcre-8.39/pcre_valid_utf8.c
+++ b/src/third_party/pcre-8.41/pcre_valid_utf8.c
diff --git a/src/third_party/pcre-8.39/pcre_version.c b/src/third_party/pcre-8.41/pcre_version.c
index ae86ff28bc8..ae86ff28bc8 100644
--- a/src/third_party/pcre-8.39/pcre_version.c
+++ b/src/third_party/pcre-8.41/pcre_version.c
diff --git a/src/third_party/pcre-8.39/pcre_xclass.c b/src/third_party/pcre-8.41/pcre_xclass.c
index ef759a589a6..ef759a589a6 100644
--- a/src/third_party/pcre-8.39/pcre_xclass.c
+++ b/src/third_party/pcre-8.41/pcre_xclass.c
diff --git a/src/third_party/pcre-8.39/pcrecpp.cc b/src/third_party/pcre-8.41/pcrecpp.cc
index d09c9abc516..d09c9abc516 100644
--- a/src/third_party/pcre-8.39/pcrecpp.cc
+++ b/src/third_party/pcre-8.41/pcrecpp.cc
diff --git a/src/third_party/pcre-8.39/pcrecpp.h b/src/third_party/pcre-8.41/pcrecpp.h
index 3e594b0d439..3e594b0d439 100644
--- a/src/third_party/pcre-8.39/pcrecpp.h
+++ b/src/third_party/pcre-8.41/pcrecpp.h
diff --git a/src/third_party/pcre-8.39/pcrecpp_internal.h b/src/third_party/pcre-8.41/pcrecpp_internal.h
index 827f9e04e2a..827f9e04e2a 100644
--- a/src/third_party/pcre-8.39/pcrecpp_internal.h
+++ b/src/third_party/pcre-8.41/pcrecpp_internal.h
diff --git a/src/third_party/pcre-8.39/pcrecpp_unittest.cc b/src/third_party/pcre-8.41/pcrecpp_unittest.cc
index 92cae8fbea5..4b15fbef1c3 100644
--- a/src/third_party/pcre-8.39/pcrecpp_unittest.cc
+++ b/src/third_party/pcre-8.41/pcrecpp_unittest.cc
@@ -43,6 +43,7 @@
#include <vector>
#include "pcrecpp.h"
+using std::string;
using pcrecpp::StringPiece;
using pcrecpp::RE;
using pcrecpp::RE_Options;
diff --git a/src/third_party/pcre-8.39/pcrecpparg.h b/src/third_party/pcre-8.41/pcrecpparg.h
index b4f9c3f4989..b4f9c3f4989 100644
--- a/src/third_party/pcre-8.39/pcrecpparg.h
+++ b/src/third_party/pcre-8.41/pcrecpparg.h
diff --git a/src/third_party/pcre-8.39/pcrecpparg.h.in b/src/third_party/pcre-8.41/pcrecpparg.h.in
index 61bcab5402c..61bcab5402c 100644
--- a/src/third_party/pcre-8.39/pcrecpparg.h.in
+++ b/src/third_party/pcre-8.41/pcrecpparg.h.in
diff --git a/src/third_party/pcre-8.39/pcredemo.c b/src/third_party/pcre-8.41/pcredemo.c
index 946aba45cdc..946aba45cdc 100644
--- a/src/third_party/pcre-8.39/pcredemo.c
+++ b/src/third_party/pcre-8.41/pcredemo.c
diff --git a/src/third_party/pcre-8.39/pcregexp.pas b/src/third_party/pcre-8.41/pcregexp.pas
index bb2b3da8f3d..bb2b3da8f3d 100644
--- a/src/third_party/pcre-8.39/pcregexp.pas
+++ b/src/third_party/pcre-8.41/pcregexp.pas
diff --git a/src/third_party/pcre-8.39/pcregrep.c b/src/third_party/pcre-8.41/pcregrep.c
index cd53c648da2..317f7454e13 100644
--- a/src/third_party/pcre-8.39/pcregrep.c
+++ b/src/third_party/pcre-8.41/pcregrep.c
@@ -1803,6 +1803,7 @@ while (ptr < endptr)
match = FALSE;
if (line_buffered) fflush(stdout);
rc = 0; /* Had some success */
+
startoffset = offsets[1]; /* Restart after the match */
if (startoffset <= oldstartoffset)
{
@@ -1812,6 +1813,22 @@ while (ptr < endptr)
if (utf8)
while ((matchptr[startoffset] & 0xc0) == 0x80) startoffset++;
}
+
+ /* If the current match ended past the end of the line (only possible
+ in multiline mode), we must move on to the line in which it did end
+ before searching for more matches. */
+
+ while (startoffset > (int)linelength)
+ {
+ matchptr = ptr += linelength + endlinelength;
+ filepos += (int)(linelength + endlinelength);
+ linenumber++;
+ startoffset -= (int)(linelength + endlinelength);
+ t = end_of_line(ptr, endptr, &endlinelength);
+ linelength = t - ptr - endlinelength;
+ length = (size_t)(endptr - ptr);
+ }
+
goto ONLY_MATCHING_RESTART;
}
}
@@ -3173,9 +3190,11 @@ for (j = 1, cp = patterns; cp != NULL; j++, cp = cp->next)
cp->hint = pcre_study(cp->compiled, study_options, &error);
if (error != NULL)
{
- char s[16];
- if (patterns->next == NULL) s[0] = 0; else sprintf(s, " number %d", j);
- fprintf(stderr, "pcregrep: Error while studying regex%s: %s\n", s, error);
+ if (patterns->next == NULL)
+ fprintf(stderr, "pcregrep: Error while studying regex: %s\n", error);
+ else
+ fprintf(stderr, "pcregrep: Error while studying regex number %d: %s\n",
+ j, error);
goto EXIT2;
}
#ifdef SUPPORT_PCREGREP_JIT
diff --git a/src/third_party/pcre-8.39/pcreposix.c b/src/third_party/pcre-8.41/pcreposix.c
index cf75588c40d..7b404a71100 100644
--- a/src/third_party/pcre-8.39/pcreposix.c
+++ b/src/third_party/pcre-8.41/pcreposix.c
@@ -6,7 +6,7 @@
and semantics are as close as possible to those of the Perl 5 language.
Written by Philip Hazel
- Copyright (c) 1997-2016 University of Cambridge
+ Copyright (c) 1997-2017 University of Cambridge
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
@@ -389,8 +389,8 @@ if (rc >= 0)
{
for (i = 0; i < (size_t)rc; i++)
{
- pmatch[i].rm_so = ovector[i*2];
- pmatch[i].rm_eo = ovector[i*2+1];
+ pmatch[i].rm_so = ovector[i*2] + so;
+ pmatch[i].rm_eo = ovector[i*2+1] + so;
}
if (allocated_ovector) free(ovector);
for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1;
diff --git a/src/third_party/pcre-8.39/pcreposix.h b/src/third_party/pcre-8.41/pcreposix.h
index c77c0b0523c..c77c0b0523c 100644
--- a/src/third_party/pcre-8.39/pcreposix.h
+++ b/src/third_party/pcre-8.41/pcreposix.h
diff --git a/src/third_party/pcre-8.39/pcretest.c b/src/third_party/pcre-8.41/pcretest.c
index 78ef5177df7..f1303037281 100644
--- a/src/third_party/pcre-8.39/pcretest.c
+++ b/src/third_party/pcre-8.41/pcretest.c
@@ -177,7 +177,7 @@ that differ in their output from isprint() even in the "C" locale. */
#define PRINTABLE(c) ((c) >= 32 && (c) < 127)
#endif
-#define PRINTOK(c) (locale_set? isprint(c) : PRINTABLE(c))
+#define PRINTOK(c) (locale_set? (((c) < 256) && isprint(c)) : PRINTABLE(c))
/* Posix support is disabled in 16 or 32 bit only mode. */
#if !defined SUPPORT_PCRE8 && !defined NOPOSIX
@@ -426,11 +426,11 @@ argument, the casting might be incorrectly applied. */
#define PCRE_COPY_NAMED_SUBSTRING32(rc, re, bptr, offsets, count, \
namesptr, cbuffer, size) \
rc = pcre32_copy_named_substring((pcre32 *)re, (PCRE_SPTR32)bptr, offsets, \
- count, (PCRE_SPTR32)namesptr, (PCRE_UCHAR32 *)cbuffer, size/2)
+ count, (PCRE_SPTR32)namesptr, (PCRE_UCHAR32 *)cbuffer, size/4)
#define PCRE_COPY_SUBSTRING32(rc, bptr, offsets, count, i, cbuffer, size) \
rc = pcre32_copy_substring((PCRE_SPTR32)bptr, offsets, count, i, \
- (PCRE_UCHAR32 *)cbuffer, size/2)
+ (PCRE_UCHAR32 *)cbuffer, size/4)
#define PCRE_DFA_EXEC32(count, re, extra, bptr, len, start_offset, options, \
offsets, size_offsets, workspace, size_workspace) \
@@ -1982,6 +1982,7 @@ return(result);
static int pchar(pcre_uint32 c, FILE *f)
{
int n = 0;
+char tempbuffer[16];
if (PRINTOK(c))
{
if (f != NULL) fprintf(f, "%c", c);
@@ -2003,6 +2004,8 @@ if (c < 0x100)
}
if (f != NULL) n = fprintf(f, "\\x{%02x}", c);
+ else n = sprintf(tempbuffer, "\\x{%02x}", c);
+
return n >= 0 ? n : 0;
}
@@ -4831,7 +4834,16 @@ while (!done)
continue;
case 'O':
- while(isdigit(*p)) n = n * 10 + *p++ - '0';
+ while(isdigit(*p))
+ {
+ if (n > (INT_MAX-10)/10) /* Hack to stop fuzzers */
+ {
+ printf("** \\O argument is too big\n");
+ yield = 1;
+ goto EXIT;
+ }
+ n = n * 10 + *p++ - '0';
+ }
if (n > size_offsets_max)
{
size_offsets_max = n;
@@ -5042,7 +5054,7 @@ while (!done)
if ((all_use_dfa || use_dfa) && find_match_limit)
{
- printf("**Match limit not relevant for DFA matching: ignored\n");
+ printf("** Match limit not relevant for DFA matching: ignored\n");
find_match_limit = 0;
}
@@ -5255,10 +5267,17 @@ while (!done)
if (do_allcaps)
{
- if (new_info(re, NULL, PCRE_INFO_CAPTURECOUNT, &count) < 0)
- goto SKIP_DATA;
- count++; /* Allow for full match */
- if (count * 2 > use_size_offsets) count = use_size_offsets/2;
+ if (all_use_dfa || use_dfa)
+ {
+ fprintf(outfile, "** Show all captures ignored after DFA matching\n");
+ }
+ else
+ {
+ if (new_info(re, NULL, PCRE_INFO_CAPTURECOUNT, &count) < 0)
+ goto SKIP_DATA;
+ count++; /* Allow for full match */
+ if (count * 2 > use_size_offsets) count = use_size_offsets/2;
+ }
}
/* Output the captured substrings. Note that, for the matched string,
diff --git a/src/third_party/pcre-8.39/perltest.pl b/src/third_party/pcre-8.41/perltest.pl
index 29b808b5293..29b808b5293 100755
--- a/src/third_party/pcre-8.39/perltest.pl
+++ b/src/third_party/pcre-8.41/perltest.pl
diff --git a/src/third_party/pcre-8.39/ucp.h b/src/third_party/pcre-8.41/ucp.h
index 2fa00296e42..2fa00296e42 100644
--- a/src/third_party/pcre-8.39/ucp.h
+++ b/src/third_party/pcre-8.41/ucp.h
diff --git a/src/third_party/scripts/pcre_get_sources.sh b/src/third_party/scripts/pcre_get_sources.sh
index da73652ef66..16c79a3e911 100644
--- a/src/third_party/scripts/pcre_get_sources.sh
+++ b/src/third_party/scripts/pcre_get_sources.sh
@@ -16,7 +16,7 @@ if [ "$#" -ne 0 ]; then
exit 1
fi
-VERSION=8.39
+VERSION=8.41
NAME=pcre
TARBALL=$NAME-$VERSION.tar.gz
TARBALL_DIR=$NAME-$VERSION
diff --git a/src/third_party/wiredtiger/build_linux/wiredtiger_config.h b/src/third_party/wiredtiger/build_linux/wiredtiger_config.h
index 1122e1e319d..6fffea61ad1 100644
--- a/src/third_party/wiredtiger/build_linux/wiredtiger_config.h
+++ b/src/third_party/wiredtiger/build_linux/wiredtiger_config.h
@@ -82,6 +82,9 @@
/* Define to 1 if you have the `posix_memalign' function. */
#define HAVE_POSIX_MEMALIGN 1
+/* Define to 1 if pthread condition variables support monotonic clocks. */
+#define HAVE_PTHREAD_COND_MONOTONIC 1
+
/* Define to 1 if you have the <pthread_np.h> header file. */
/* #undef HAVE_PTHREAD_NP_H */
diff --git a/src/third_party/wiredtiger/build_posix/configure.ac.in b/src/third_party/wiredtiger/build_posix/configure.ac.in
index 0fef587b4b8..415545a0d56 100644
--- a/src/third_party/wiredtiger/build_posix/configure.ac.in
+++ b/src/third_party/wiredtiger/build_posix/configure.ac.in
@@ -160,6 +160,44 @@ AS_CASE([$host_os], [darwin*], [], [AC_CHECK_FUNCS([fdatasync])])
# the generic declaration in AC_CHECK_FUNCS is incompatible.
AX_FUNC_POSIX_MEMALIGN
+# Check for POSIX condition variables with monotonic clock support
+AC_CACHE_CHECK([for condition waits with monotonic clock support],
+ [wt_cv_pthread_cond_monotonic],
+ [AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+
+int main()
+{
+ int ret;
+ pthread_condattr_t condattr;
+ pthread_cond_t cond;
+ pthread_mutex_t mtx;
+ struct timespec ts;
+
+ if ((ret = pthread_condattr_init(&condattr)) != 0) exit(1);
+ if ((ret = pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC)) != 0) exit(1);
+ if ((ret = pthread_cond_init(&cond, &condattr)) != 0) exit(1);
+ if ((ret = pthread_mutex_init(&mtx, NULL)) != 0) exit(1);
+ if ((ret = clock_gettime(CLOCK_MONOTONIC, &ts)) != 0) exit(1);
+ ts.tv_sec += 1;
+ if ((ret = pthread_mutex_lock(&mtx)) != 0) exit(1);
+ if ((ret = pthread_cond_timedwait(&cond, &mtx, &ts)) != 0 && ret != EINTR && ret != ETIMEDOUT) exit(1);
+
+ exit(0);
+}
+ ]])],
+ [wt_pthread_cond_monotonic=yes],
+ [wt_pthread_cond_monotonic=no],
+ [wt_pthread_cond_monotonic=no])])
+AC_MSG_RESULT($wt_pthread_cond_monotonic)
+if test "$wt_pthread_cond_monotonic" = "yes" ; then
+ AC_DEFINE([HAVE_PTHREAD_COND_MONOTONIC], [1],
+ [Define to 1 if pthread condition variables support monotonic clocks.])
+fi
+
AC_SYS_LARGEFILE
AC_C_BIGENDIAN
diff --git a/src/third_party/wiredtiger/build_win/wiredtiger_config.h b/src/third_party/wiredtiger/build_win/wiredtiger_config.h
index 78d2784cb70..8babdbfdc1b 100644
--- a/src/third_party/wiredtiger/build_win/wiredtiger_config.h
+++ b/src/third_party/wiredtiger/build_win/wiredtiger_config.h
@@ -79,6 +79,9 @@
/* Define to 1 if you have the <memory.h> header file. */
/* #undef HAVE_MEMORY_H */
+/* Define to 1 if pthread condition variables support monotonic clocks. */
+/* #undef HAVE_PTHREAD_COND_MONOTONIC */
+
/* Define to 1 if you have the `posix_fadvise' function. */
/* #undef HAVE_POSIX_FADVISE */
diff --git a/src/third_party/wiredtiger/dist/api_data.py b/src/third_party/wiredtiger/dist/api_data.py
index 22600dd5e29..596099647be 100644
--- a/src/third_party/wiredtiger/dist/api_data.py
+++ b/src/third_party/wiredtiger/dist/api_data.py
@@ -753,6 +753,9 @@ wiredtiger_open_common =\
Config('session_scratch_max', '2MB', r'''
maximum memory to cache in each session''',
type='int', undoc=True),
+ Config('session_table_cache', 'true', r'''
+ Maintain a per-session cache of tables''',
+ type='boolean'),
Config('transaction_sync', '', r'''
how to sync log records when the transaction commits''',
type='category', subconfig=[
diff --git a/src/third_party/wiredtiger/dist/filelist b/src/third_party/wiredtiger/dist/filelist
index 5a3348b940a..f53509e96ec 100644
--- a/src/third_party/wiredtiger/dist/filelist
+++ b/src/third_party/wiredtiger/dist/filelist
@@ -191,6 +191,7 @@ src/support/rand.c
src/support/scratch.c
src/support/stat.c
src/support/thread_group.c
+src/support/time.c
src/txn/txn.c
src/txn/txn_ckpt.c
src/txn/txn_ext.c
diff --git a/src/third_party/wiredtiger/dist/flags.py b/src/third_party/wiredtiger/dist/flags.py
index 64b5d789e72..1ce717f3586 100644
--- a/src/third_party/wiredtiger/dist/flags.py
+++ b/src/third_party/wiredtiger/dist/flags.py
@@ -32,7 +32,6 @@ flags = {
'READ_PREV',
'READ_RESTART_OK',
'READ_SKIP_INTL',
- 'READ_SKIP_LEAF',
'READ_TRUNCATE',
'READ_WONT_NEED',
],
@@ -111,6 +110,7 @@ flags = {
'CONN_SERVER_LSM',
'CONN_SERVER_STATISTICS',
'CONN_SERVER_SWEEP',
+ 'CONN_TABLE_CACHE',
'CONN_WAS_BACKUP',
],
'session' : [
diff --git a/src/third_party/wiredtiger/dist/s_string.ok b/src/third_party/wiredtiger/dist/s_string.ok
index f3852d00ac8..fad8c2f021a 100644
--- a/src/third_party/wiredtiger/dist/s_string.ok
+++ b/src/third_party/wiredtiger/dist/s_string.ok
@@ -807,6 +807,7 @@ intl
intnum
intpack
intptr
+intr
intrin
inuse
io
@@ -1060,6 +1061,7 @@ rebalancing
recno
recnos
reconfig
+reconfigures
reconfiguring
recsize
rectype
diff --git a/src/third_party/wiredtiger/dist/stat_data.py b/src/third_party/wiredtiger/dist/stat_data.py
index 512892eb44d..b66e95ce49b 100644
--- a/src/third_party/wiredtiger/dist/stat_data.py
+++ b/src/third_party/wiredtiger/dist/stat_data.py
@@ -431,11 +431,19 @@ connection_stats = [
##########################################
YieldStat('application_cache_time', 'application thread time waiting for cache (usecs)'),
YieldStat('application_evict_time', 'application thread time evicting (usecs)'),
+ YieldStat('child_modify_blocked_page', 'page reconciliation yielded due to child modification'),
+ YieldStat('conn_close_blocked_lsm', 'connection close yielded for lsm manager shutdown'),
+ YieldStat('dhandle_lock_blocked', 'data handle lock yielded'),
+ YieldStat('log_server_sync_blocked', 'log server sync yielded for log write'),
YieldStat('page_busy_blocked', 'page acquire busy blocked'),
+ YieldStat('page_del_rollback_blocked', 'page delete rollback yielded for instantiation'),
YieldStat('page_forcible_evict_blocked', 'page acquire eviction blocked'),
+ YieldStat('page_index_slot_blocked', 'reference for page index and slot yielded'),
YieldStat('page_locked_blocked', 'page acquire locked blocked'),
YieldStat('page_read_blocked', 'page acquire read blocked'),
YieldStat('page_sleep', 'page acquire time sleeping (usecs)'),
+ YieldStat('tree_descend_blocked', 'tree descend one level yielded for split page index update'),
+ YieldStat('txn_release_blocked', 'connection close blocked waiting for transaction state stabilization'),
]
connection_stats = sorted(connection_stats, key=attrgetter('desc'))
diff --git a/src/third_party/wiredtiger/import.data b/src/third_party/wiredtiger/import.data
index 03d5746ffde..e867147e59e 100644
--- a/src/third_party/wiredtiger/import.data
+++ b/src/third_party/wiredtiger/import.data
@@ -1,5 +1,5 @@
{
- "commit": "d2f2eae6d7718a53ac5bacf7141773ee0696f3c6",
+ "commit": "0b36171f4aa0bea6ab6118b0d3bcf6329a24939e",
"github": "wiredtiger/wiredtiger.git",
"vendor": "wiredtiger",
"branch": "mongodb-3.4"
diff --git a/src/third_party/wiredtiger/src/async/async_worker.c b/src/third_party/wiredtiger/src/async/async_worker.c
index 11f59ed14f1..89877652f1e 100644
--- a/src/third_party/wiredtiger/src/async/async_worker.c
+++ b/src/third_party/wiredtiger/src/async/async_worker.c
@@ -301,11 +301,10 @@ __wt_async_worker(void *arg)
WT_ERR(__async_op_dequeue(conn, session, &op));
if (op != NULL && op != &async->flush_op) {
/*
- * If an operation fails, we want the worker thread to
- * keep running, unless there is a panic.
+ * Operation failure doesn't cause the worker thread to
+ * exit.
*/
(void)__async_worker_op(session, op, &worker);
- WT_ERR(WT_SESSION_CHECK_PANIC(session));
} else if (async->flush_state == WT_ASYNC_FLUSHING) {
/*
* Worker flushing going on. Last worker to the party
diff --git a/src/third_party/wiredtiger/src/btree/bt_cursor.c b/src/third_party/wiredtiger/src/btree/bt_cursor.c
index f0aa632551b..28977eee9f5 100644
--- a/src/third_party/wiredtiger/src/btree/bt_cursor.c
+++ b/src/third_party/wiredtiger/src/btree/bt_cursor.c
@@ -52,15 +52,18 @@ __cursor_state_restore(WT_CURSOR *cursor, WT_CURFILE_STATE *state)
/*
* __cursor_page_pinned --
- * Return if we have a page pinned and it's not been flagged for forced
- * eviction (the forced eviction test is so we periodically release pages
- * grown too large).
+ * Return if we have a page pinned.
*/
static inline bool
-__cursor_page_pinned(WT_CURSOR_BTREE *cbt)
+__cursor_page_pinned(WT_CURSOR_BTREE *cbt, bool eviction_ok)
{
+ /*
+ * Optionally fail the page-pinned test when the page is flagged for
+ * forced eviction (so we periodically release pages grown too large).
+ * The test is optional as not all callers can release pinned pages.
+ */
return (F_ISSET(cbt, WT_CBT_ACTIVE) &&
- cbt->ref->page->read_gen != WT_READGEN_OLDEST);
+ (!eviction_ok || cbt->ref->page->read_gen != WT_READGEN_OLDEST));
}
/*
@@ -156,8 +159,10 @@ __cursor_disable_bulk(WT_SESSION_IMPL *session, WT_BTREE *btree)
* into a tree. Eviction is disabled when an empty tree is opened, and
* it must only be enabled once.
*/
- if (__wt_atomic_cas8(&btree->original, 1, 0))
+ if (__wt_atomic_cas8(&btree->original, 1, 0)) {
+ btree->evict_disabled_open = false;
__wt_evict_file_exclusive_off(session);
+ }
}
/*
@@ -443,7 +448,7 @@ __wt_btcur_search(WT_CURSOR_BTREE *cbt)
* from the root.
*/
valid = false;
- if (__cursor_page_pinned(cbt)) {
+ if (__cursor_page_pinned(cbt, true)) {
__wt_txn_cursor_op(session);
WT_ERR(btree->type == BTREE_ROW ?
@@ -535,7 +540,7 @@ __wt_btcur_search_near(WT_CURSOR_BTREE *cbt, int *exactp)
* existing record.
*/
valid = false;
- if (btree->type == BTREE_ROW && __cursor_page_pinned(cbt)) {
+ if (btree->type == BTREE_ROW && __cursor_page_pinned(cbt, true)) {
__wt_txn_cursor_op(session);
WT_ERR(__cursor_row_search(session, cbt, cbt->ref, true));
@@ -665,7 +670,7 @@ __wt_btcur_insert(WT_CURSOR_BTREE *cbt)
* configured for append aren't included, regardless of whether or not
* they meet all other criteria.
*/
- if (__cursor_page_pinned(cbt) &&
+ if (__cursor_page_pinned(cbt, true) &&
F_ISSET_ALL(cursor, WT_CURSTD_KEY_INT | WT_CURSTD_OVERWRITE) &&
!append_key) {
WT_ERR(__wt_txn_autocommit_check(session));
@@ -880,8 +885,22 @@ __wt_btcur_remove(WT_CURSOR_BTREE *cbt)
* removed, and the record must exist with a positioned cursor. The
* cursor won't be positioned on a page with an external key set, but
* be sure.
+ *
+ * There's trickiness in the page-pinned check. By definition a remove
+ * operation leaves a cursor positioned if it's initially positioned.
+ * However, if every item on the page is deleted and we unpin the page,
+ * eviction might delete the page and our search will re-instantiate an
+ * empty page for us. Cursor remove returns not-found whether or not
+ * that eviction/deletion happens and it's OK unless cursor-overwrite
+ * is configured (which means we return success even if there's no item
+ * to delete). In that case, we'll fail when we try to point the cursor
+ * at the key on the page to satisfy the positioned requirement. It's
+ * arguably safe to simply leave the key initialized in the cursor (as
+ * that's all a positioned cursor implies), but it's probably safer to
+ * avoid page eviction entirely in the positioned case.
*/
- if (__cursor_page_pinned(cbt) && F_ISSET(cursor, WT_CURSTD_KEY_INT)) {
+ if (__cursor_page_pinned(cbt, !positioned) &&
+ F_ISSET(cursor, WT_CURSTD_KEY_INT)) {
WT_ERR(__wt_txn_autocommit_check(session));
/*
@@ -1024,7 +1043,8 @@ __wt_btcur_update(WT_CURSOR_BTREE *cbt)
* cursor won't be positioned on a page with an external key set, but
* be sure.
*/
- if (__cursor_page_pinned(cbt) && F_ISSET(cursor, WT_CURSTD_KEY_INT)) {
+ if (__cursor_page_pinned(cbt, true) &&
+ F_ISSET(cursor, WT_CURSTD_KEY_INT)) {
WT_ERR(__wt_txn_autocommit_check(session));
/*
* The cursor position may not be exact (the cursor's comparison
diff --git a/src/third_party/wiredtiger/src/btree/bt_delete.c b/src/third_party/wiredtiger/src/btree/bt_delete.c
index b55ad291c5e..5c4625044d3 100644
--- a/src/third_party/wiredtiger/src/btree/bt_delete.c
+++ b/src/third_party/wiredtiger/src/btree/bt_delete.c
@@ -153,6 +153,7 @@ void
__wt_delete_page_rollback(WT_SESSION_IMPL *session, WT_REF *ref)
{
WT_UPDATE **upd;
+ uint64_t yield_count;
/*
* If the page is still "deleted", it's as we left it, reset the state
@@ -160,7 +161,7 @@ __wt_delete_page_rollback(WT_SESSION_IMPL *session, WT_REF *ref)
* instantiated or being instantiated. Loop because it's possible for
* the page to return to the deleted state if instantiation fails.
*/
- for (;; __wt_yield())
+ for (yield_count = 0;; yield_count++, __wt_yield())
switch (ref->state) {
case WT_REF_DISK:
case WT_REF_READING:
@@ -173,7 +174,7 @@ __wt_delete_page_rollback(WT_SESSION_IMPL *session, WT_REF *ref)
*/
if (__wt_atomic_casv32(
&ref->state, WT_REF_DELETED, WT_REF_DISK))
- return;
+ goto done;
break;
case WT_REF_LOCKED:
/*
@@ -203,8 +204,10 @@ __wt_delete_page_rollback(WT_SESSION_IMPL *session, WT_REF *ref)
*/
__wt_free(session, ref->page_del->update_list);
__wt_free(session, ref->page_del);
- return;
+ goto done;
}
+
+done: WT_STAT_CONN_INCRV(session, page_del_rollback_blocked, yield_count);
}
/*
diff --git a/src/third_party/wiredtiger/src/btree/bt_handle.c b/src/third_party/wiredtiger/src/btree/bt_handle.c
index a0da7df0998..8637a8cd751 100644
--- a/src/third_party/wiredtiger/src/btree/bt_handle.c
+++ b/src/third_party/wiredtiger/src/btree/bt_handle.c
@@ -66,7 +66,6 @@ __wt_btree_open(WT_SESSION_IMPL *session, const char *op_cfg[])
WT_DATA_HANDLE *dhandle;
WT_DECL_RET;
size_t root_addr_size;
- uint32_t mask;
uint8_t root_addr[WT_BTREE_MAX_ADDR_COOKIE];
const char *filename;
bool creation, forced_salvage, readonly;
@@ -75,15 +74,14 @@ __wt_btree_open(WT_SESSION_IMPL *session, const char *op_cfg[])
dhandle = session->dhandle;
/*
- * This may be a re-open of an underlying object and we have to clean
- * up. We can't clear the operation flags, however, they're set by the
- * connection handle software that called us.
+ * This may be a re-open, clean up the btree structure.
+ * Clear the fields that don't persist across a re-open.
+ * Clear all flags other than the operation flags (which are set by the
+ * connection handle software that called us).
*/
WT_RET(__btree_clear(session));
-
- mask = F_MASK(btree, WT_BTREE_SPECIAL_FLAGS);
- memset(btree, 0, sizeof(*btree));
- btree->flags = mask;
+ memset(btree, 0, WT_BTREE_CLEAR_SIZE);
+ F_CLR(btree, ~WT_BTREE_SPECIAL_FLAGS);
/* Set the data handle first, our called functions reasonably use it. */
btree->dhandle = dhandle;
@@ -185,13 +183,19 @@ __wt_btree_open(WT_SESSION_IMPL *session, const char *op_cfg[])
*
* Files that can still be bulk-loaded cannot be evicted.
* Permanently cache-resident files can never be evicted.
- * Special operations don't enable eviction. (The underlying commands
- * may turn on eviction, but it's their decision.)
+ * Special operations don't enable eviction. The underlying commands may
+ * turn on eviction (for example, verify turns on eviction while working
+ * a file to keep from consuming the cache), but it's their decision. If
+ * an underlying command reconfigures eviction, it must either clear the
+ * evict-disabled-open flag or restore the eviction configuration when
+ * finished so that handle close behaves correctly.
*/
if (btree->original ||
F_ISSET(btree, WT_BTREE_IN_MEMORY | WT_BTREE_REBALANCE |
- WT_BTREE_SALVAGE | WT_BTREE_UPGRADE | WT_BTREE_VERIFY))
+ WT_BTREE_SALVAGE | WT_BTREE_UPGRADE | WT_BTREE_VERIFY)) {
WT_ERR(__wt_evict_file_exclusive_on(session));
+ btree->evict_disabled_open = true;
+ }
if (0) {
err: WT_TRET(__wt_btree_close(session));
@@ -228,6 +232,15 @@ __wt_btree_close(WT_SESSION_IMPL *session)
return (0);
F_SET(btree, WT_BTREE_CLOSED);
+ /*
+ * If we turned eviction off and never turned it back on, do that now,
+ * otherwise the counter will be off.
+ */
+ if (btree->evict_disabled_open) {
+ btree->evict_disabled_open = false;
+ __wt_evict_file_exclusive_off(session);
+ }
+
/* Discard any underlying block manager resources. */
if ((bm = btree->bm) != NULL) {
btree->bm = NULL;
@@ -447,9 +460,11 @@ __btree_conf(WT_SESSION_IMPL *session, WT_CKPT *ckpt)
WT_RET(__wt_rwlock_init(session, &btree->ovfl_lock));
WT_RET(__wt_spin_init(session, &btree->flush_lock, "btree flush"));
- btree->checkpointing = WT_CKPT_OFF; /* Not checkpointing */
btree->modified = false; /* Clean */
- btree->write_gen = ckpt->write_gen; /* Write generation */
+
+ btree->checkpointing = WT_CKPT_OFF; /* Not checkpointing */
+ btree->write_gen = ckpt->write_gen; /* Write generation */
+ btree->checkpoint_gen = S2C(session)->txn_global.checkpoint_gen;
return (0);
}
diff --git a/src/third_party/wiredtiger/src/btree/bt_random.c b/src/third_party/wiredtiger/src/btree/bt_random.c
index c5948ec4ab5..b4f05c440ba 100644
--- a/src/third_party/wiredtiger/src/btree/bt_random.c
+++ b/src/third_party/wiredtiger/src/btree/bt_random.c
@@ -395,8 +395,7 @@ __wt_btcur_next_random(WT_CURSOR_BTREE *cbt)
*/
for (skip = cbt->next_random_leaf_skip; cbt->ref == NULL || skip > 0;) {
n = skip;
- WT_ERR(__wt_tree_walk_skip(session, &cbt->ref, &skip,
- WT_READ_NO_GEN | WT_READ_SKIP_INTL | WT_READ_WONT_NEED));
+ WT_ERR(__wt_tree_walk_skip(session, &cbt->ref, &skip));
if (n == skip) {
if (skip == 0)
break;
diff --git a/src/third_party/wiredtiger/src/btree/bt_walk.c b/src/third_party/wiredtiger/src/btree/bt_walk.c
index 86484feb7c9..c22b99c55d0 100644
--- a/src/third_party/wiredtiger/src/btree/bt_walk.c
+++ b/src/third_party/wiredtiger/src/btree/bt_walk.c
@@ -18,9 +18,16 @@ __ref_index_slot(WT_SESSION_IMPL *session,
{
WT_PAGE_INDEX *pindex;
WT_REF **start, **stop, **p, **t;
+ uint64_t yield_count;
uint32_t entries, slot;
- for (;;) {
+ /*
+ * If we don't find our reference, the page split and our home
+ * pointer references the wrong page. When internal pages
+ * split, their WT_REF structure home values are updated; yield
+ * and wait for that to happen.
+ */
+ for (yield_count = 0;; yield_count++, __wt_yield()) {
/*
* Copy the parent page's index value: the page can split at
* any time, but the index's value is always valid, even if
@@ -59,18 +66,13 @@ __ref_index_slot(WT_SESSION_IMPL *session,
}
}
- /*
- * If we don't find our reference, the page split and our home
- * pointer references the wrong page. When internal pages
- * split, their WT_REF structure home values are updated; yield
- * and wait for that to happen.
- */
- __wt_yield();
}
found: WT_ASSERT(session, pindex->index[slot] == ref);
*pindexp = pindex;
*slotp = slot;
+
+ WT_STAT_CONN_INCRV(session, page_index_slot_blocked, yield_count);
}
/*
@@ -177,12 +179,13 @@ __ref_descend_prev(
WT_SESSION_IMPL *session, WT_REF *ref, WT_PAGE_INDEX **pindexp)
{
WT_PAGE_INDEX *pindex;
+ uint64_t yield_count;
/*
* We're passed a child page into which we're descending, and on which
* we have a hazard pointer.
*/
- for (;; __wt_yield()) {
+ for (yield_count = 0;; yield_count++, __wt_yield()) {
/*
* There's a split race when a cursor moving backwards through
* the tree descends the tree. If we're splitting an internal
@@ -242,6 +245,7 @@ __ref_descend_prev(
break;
}
*pindexp = pindex;
+ WT_STAT_CONN_INCRV(session, tree_descend_blocked, yield_count);
}
/*
@@ -497,29 +501,21 @@ restart: /*
}
/*
- * Optionally skip leaf pages: skip all leaf pages if
- * WT_READ_SKIP_LEAF is set, when the skip-leaf-count
- * variable is non-zero, skip some count of leaf pages.
- * If this page is disk-based, crack the cell to figure
- * out it's a leaf page without reading it.
+ * Optionally skip leaf pages: when the skip-leaf-count
+ * variable is non-zero, skip some count of leaf pages,
+ * then take the next leaf page we can.
*
- * If skipping some number of leaf pages, decrement the
- * count of pages to zero, and then take the next leaf
- * page we can. Be cautious around the page decrement,
- * if for some reason don't take this particular page,
- * we can take the next one, and, there are additional
- * tests/decrements when we're about to return a leaf
- * page.
+ * The reason to do some of this work here (rather than
+ * in our caller), is because we can look at the cell
+ * and know it's a leaf page without reading it into
+ * memory. If this page is disk-based, crack the cell
+ * to figure out it's a leaf page without reading it.
*/
- if (skipleafcntp != NULL || LF_ISSET(WT_READ_SKIP_LEAF))
- if (__ref_is_leaf(ref)) {
- if (LF_ISSET(WT_READ_SKIP_LEAF))
- break;
- if (*skipleafcntp > 0) {
- --*skipleafcntp;
- break;
- }
- }
+ if (skipleafcntp != NULL &&
+ *skipleafcntp > 0 && __ref_is_leaf(ref)) {
+ --*skipleafcntp;
+ break;
+ }
ret = __wt_page_swap(session, couple, ref,
WT_READ_NOTFOUND_OK | WT_READ_RESTART_OK | flags);
@@ -626,34 +622,18 @@ descend: empty_internal = true;
session, ref, &pindex);
slot = pindex->entries - 1;
}
- } else {
- /*
- * At the lowest tree level (considering a leaf
- * page), turn off the initial-descent state.
- * Descent race tests are different when moving
- * through the tree vs. the initial descent.
- */
- initial_descent = false;
-
- /*
- * Optionally skip leaf pages, the second half.
- * We didn't have an on-page cell to figure out
- * if it was a leaf page, we had to acquire the
- * hazard pointer and look at the page.
- */
- if (skipleafcntp != NULL ||
- LF_ISSET(WT_READ_SKIP_LEAF)) {
- if (LF_ISSET(WT_READ_SKIP_LEAF))
- break;
- if (*skipleafcntp > 0) {
- --*skipleafcntp;
- break;
- }
- }
-
- *refp = ref;
- goto done;
+ continue;
}
+
+ /*
+ * The tree-walk restart code knows we return any leaf
+ * page we acquire (never hazard-pointer coupling on
+ * after acquiring a leaf page), and asserts no restart
+ * happens while holding a leaf page. This page must be
+ * returned to our caller.
+ */
+ *refp = ref;
+ goto done;
}
}
@@ -690,8 +670,29 @@ __wt_tree_walk_count(WT_SESSION_IMPL *session,
* of leaf pages before returning.
*/
int
-__wt_tree_walk_skip(WT_SESSION_IMPL *session,
- WT_REF **refp, uint64_t *skipleafcntp, uint32_t flags)
+__wt_tree_walk_skip(
+ WT_SESSION_IMPL *session, WT_REF **refp, uint64_t *skipleafcntp)
{
- return (__tree_walk_internal(session, refp, NULL, skipleafcntp, flags));
+ /*
+ * Optionally skip leaf pages, the second half. The tree-walk function
+ * didn't have an on-page cell it could use to figure out if the page
+ * was a leaf page or not, it had to acquire the hazard pointer and look
+ * at the page. The tree-walk code never acquires a hazard pointer on a
+ * leaf page without returning it, and it's not trivial to change that.
+ * So, the tree-walk code returns all leaf pages here and we deal with
+ * decrementing the count.
+ */
+ do {
+ WT_RET(__tree_walk_internal(session, refp, NULL, skipleafcntp,
+ WT_READ_NO_GEN | WT_READ_SKIP_INTL | WT_READ_WONT_NEED));
+
+ /*
+ * The walk skipped internal pages, any page returned must be a
+ * leaf page.
+ */
+ if (*skipleafcntp > 0)
+ --*skipleafcntp;
+ } while (*skipleafcntp > 0);
+
+ return (0);
}
diff --git a/src/third_party/wiredtiger/src/config/config_def.c b/src/third_party/wiredtiger/src/config/config_def.c
index f152fbacad4..c8a8c525751 100644
--- a/src/third_party/wiredtiger/src/config/config_def.c
+++ b/src/third_party/wiredtiger/src/config/config_def.c
@@ -733,6 +733,7 @@ static const WT_CONFIG_CHECK confchk_wiredtiger_open[] = {
{ "readonly", "boolean", NULL, NULL, NULL, 0 },
{ "session_max", "int", NULL, "min=1", NULL, 0 },
{ "session_scratch_max", "int", NULL, NULL, NULL, 0 },
+ { "session_table_cache", "boolean", NULL, NULL, NULL, 0 },
{ "shared_cache", "category",
NULL, NULL,
confchk_wiredtiger_open_shared_cache_subconfigs, 5 },
@@ -820,6 +821,7 @@ static const WT_CONFIG_CHECK confchk_wiredtiger_open_all[] = {
{ "readonly", "boolean", NULL, NULL, NULL, 0 },
{ "session_max", "int", NULL, "min=1", NULL, 0 },
{ "session_scratch_max", "int", NULL, NULL, NULL, 0 },
+ { "session_table_cache", "boolean", NULL, NULL, NULL, 0 },
{ "shared_cache", "category",
NULL, NULL,
confchk_wiredtiger_open_shared_cache_subconfigs, 5 },
@@ -904,6 +906,7 @@ static const WT_CONFIG_CHECK confchk_wiredtiger_open_basecfg[] = {
{ "readonly", "boolean", NULL, NULL, NULL, 0 },
{ "session_max", "int", NULL, "min=1", NULL, 0 },
{ "session_scratch_max", "int", NULL, NULL, NULL, 0 },
+ { "session_table_cache", "boolean", NULL, NULL, NULL, 0 },
{ "shared_cache", "category",
NULL, NULL,
confchk_wiredtiger_open_shared_cache_subconfigs, 5 },
@@ -986,6 +989,7 @@ static const WT_CONFIG_CHECK confchk_wiredtiger_open_usercfg[] = {
{ "readonly", "boolean", NULL, NULL, NULL, 0 },
{ "session_max", "int", NULL, "min=1", NULL, 0 },
{ "session_scratch_max", "int", NULL, NULL, NULL, 0 },
+ { "session_table_cache", "boolean", NULL, NULL, NULL, 0 },
{ "shared_cache", "category",
NULL, NULL,
confchk_wiredtiger_open_shared_cache_subconfigs, 5 },
@@ -1276,14 +1280,14 @@ static const WT_CONFIG_ENTRY config_entries[] = {
"file_max=100MB,path=\".\",prealloc=true,recover=on,"
"zero_fill=false),lsm_manager=(merge=true,worker_thread_max=4),"
"lsm_merge=true,mmap=true,multiprocess=false,readonly=false,"
- "session_max=100,session_scratch_max=2MB,shared_cache=(chunk=10MB"
- ",name=,quota=0,reserve=0,size=500MB),statistics=none,"
- "statistics_log=(json=false,on_close=false,path=\".\",sources=,"
- "timestamp=\"%b %d %H:%M:%S\",wait=0),"
+ "session_max=100,session_scratch_max=2MB,session_table_cache=true"
+ ",shared_cache=(chunk=10MB,name=,quota=0,reserve=0,size=500MB),"
+ "statistics=none,statistics_log=(json=false,on_close=false,"
+ "path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\",wait=0),"
"transaction_sync=(enabled=false,method=fsync),"
"use_environment=true,use_environment_priv=false,verbose=,"
"write_through=",
- confchk_wiredtiger_open, 40
+ confchk_wiredtiger_open, 41
},
{ "wiredtiger_open_all",
"async=(enabled=false,ops_max=1024,threads=2),buffer_alignment=-1"
@@ -1300,14 +1304,14 @@ static const WT_CONFIG_ENTRY config_entries[] = {
"file_max=100MB,path=\".\",prealloc=true,recover=on,"
"zero_fill=false),lsm_manager=(merge=true,worker_thread_max=4),"
"lsm_merge=true,mmap=true,multiprocess=false,readonly=false,"
- "session_max=100,session_scratch_max=2MB,shared_cache=(chunk=10MB"
- ",name=,quota=0,reserve=0,size=500MB),statistics=none,"
- "statistics_log=(json=false,on_close=false,path=\".\",sources=,"
- "timestamp=\"%b %d %H:%M:%S\",wait=0),"
+ "session_max=100,session_scratch_max=2MB,session_table_cache=true"
+ ",shared_cache=(chunk=10MB,name=,quota=0,reserve=0,size=500MB),"
+ "statistics=none,statistics_log=(json=false,on_close=false,"
+ "path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\",wait=0),"
"transaction_sync=(enabled=false,method=fsync),"
"use_environment=true,use_environment_priv=false,verbose=,"
"version=(major=0,minor=0),write_through=",
- confchk_wiredtiger_open_all, 41
+ confchk_wiredtiger_open_all, 42
},
{ "wiredtiger_open_basecfg",
"async=(enabled=false,ops_max=1024,threads=2),buffer_alignment=-1"
@@ -1323,12 +1327,13 @@ static const WT_CONFIG_ENTRY config_entries[] = {
"path=\".\",prealloc=true,recover=on,zero_fill=false),"
"lsm_manager=(merge=true,worker_thread_max=4),lsm_merge=true,"
"mmap=true,multiprocess=false,readonly=false,session_max=100,"
- "session_scratch_max=2MB,shared_cache=(chunk=10MB,name=,quota=0,"
- "reserve=0,size=500MB),statistics=none,statistics_log=(json=false"
- ",on_close=false,path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\""
- ",wait=0),transaction_sync=(enabled=false,method=fsync),verbose=,"
+ "session_scratch_max=2MB,session_table_cache=true,"
+ "shared_cache=(chunk=10MB,name=,quota=0,reserve=0,size=500MB),"
+ "statistics=none,statistics_log=(json=false,on_close=false,"
+ "path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\",wait=0),"
+ "transaction_sync=(enabled=false,method=fsync),verbose=,"
"version=(major=0,minor=0),write_through=",
- confchk_wiredtiger_open_basecfg, 35
+ confchk_wiredtiger_open_basecfg, 36
},
{ "wiredtiger_open_usercfg",
"async=(enabled=false,ops_max=1024,threads=2),buffer_alignment=-1"
@@ -1344,12 +1349,13 @@ static const WT_CONFIG_ENTRY config_entries[] = {
"path=\".\",prealloc=true,recover=on,zero_fill=false),"
"lsm_manager=(merge=true,worker_thread_max=4),lsm_merge=true,"
"mmap=true,multiprocess=false,readonly=false,session_max=100,"
- "session_scratch_max=2MB,shared_cache=(chunk=10MB,name=,quota=0,"
- "reserve=0,size=500MB),statistics=none,statistics_log=(json=false"
- ",on_close=false,path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\""
- ",wait=0),transaction_sync=(enabled=false,method=fsync),verbose=,"
+ "session_scratch_max=2MB,session_table_cache=true,"
+ "shared_cache=(chunk=10MB,name=,quota=0,reserve=0,size=500MB),"
+ "statistics=none,statistics_log=(json=false,on_close=false,"
+ "path=\".\",sources=,timestamp=\"%b %d %H:%M:%S\",wait=0),"
+ "transaction_sync=(enabled=false,method=fsync),verbose=,"
"write_through=",
- confchk_wiredtiger_open_usercfg, 34
+ confchk_wiredtiger_open_usercfg, 35
},
{ NULL, NULL, NULL, 0 }
};
diff --git a/src/third_party/wiredtiger/src/conn/conn_api.c b/src/third_party/wiredtiger/src/conn/conn_api.c
index 68d45678965..103e4a68f04 100644
--- a/src/third_party/wiredtiger/src/conn/conn_api.c
+++ b/src/third_party/wiredtiger/src/conn/conn_api.c
@@ -1086,6 +1086,41 @@ err: /*
WT_TRET(wt_session->close(wt_session, config));
}
+ /*
+ * Perform a system-wide checkpoint so that all tables are consistent
+ * with each other. Do this before shutting down all the subsystems.
+ * We have shut down all user sessions, but send in true for waiting
+ * for internal races.
+ */
+ if (!F_ISSET(conn, WT_CONN_IN_MEMORY | WT_CONN_READONLY)) {
+ s = NULL;
+ WT_TRET(__wt_open_internal_session(
+ conn, "close_ckpt", true, 0, &s));
+ if (s != NULL) {
+ const char *checkpoint_cfg[] = {
+ WT_CONFIG_BASE(session, WT_SESSION_checkpoint),
+ NULL
+ };
+ wt_session = &s->iface;
+ WT_TRET(__wt_txn_checkpoint(s, checkpoint_cfg, true));
+
+ /*
+ * Mark the metadata dirty so we flush it on close,
+ * allowing recovery to be skipped.
+ */
+ WT_WITH_DHANDLE(s, WT_SESSION_META_DHANDLE(s),
+ __wt_tree_modify_set(s));
+
+ WT_TRET(wt_session->close(wt_session, config));
+ }
+ }
+
+ if (ret != 0) {
+ __wt_err(session, ret,
+ "failure during close, disabling further writes");
+ F_SET(conn, WT_CONN_PANIC);
+ }
+
WT_TRET(__wt_connection_close(conn));
/* We no longer have a session, don't try to update it. */
@@ -2185,6 +2220,9 @@ wiredtiger_open(const char *home, WT_EVENT_HANDLER *event_handler,
WT_ERR(__wt_config_gets(session, cfg, "readonly", &cval));
if (cval.val)
F_SET(conn, WT_CONN_READONLY);
+ WT_ERR(__wt_config_gets(session, cfg, "session_table_cache", &cval));
+ if (cval.val)
+ F_SET(conn, WT_CONN_TABLE_CACHE);
/* Configure error messages so we get them right early. */
WT_ERR(__wt_config_gets(session, cfg, "error_prefix", &cval));
@@ -2475,8 +2513,15 @@ err: /* Discard the scratch buffers. */
__wt_scr_discard(session);
__wt_scr_discard(&conn->dummy_session);
- if (ret != 0)
+ if (ret != 0) {
+ /*
+ * Set panic if we're returning the run recovery error so that
+ * we don't try to checkpoint data handles.
+ */
+ if (ret == WT_RUN_RECOVERY)
+ F_SET(conn, WT_CONN_PANIC);
WT_TRET(__wt_connection_close(conn));
+ }
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/conn/conn_dhandle.c b/src/third_party/wiredtiger/src/conn/conn_dhandle.c
index 1816e66b0b7..181f12ab2dd 100644
--- a/src/third_party/wiredtiger/src/conn/conn_dhandle.c
+++ b/src/third_party/wiredtiger/src/conn/conn_dhandle.c
@@ -317,6 +317,9 @@ __wt_conn_btree_open(
WT_ASSERT(session,
!F_ISSET(S2C(session), WT_CONN_CLOSING_NO_MORE_OPENS));
+ /* Turn off eviction. */
+ WT_RET(__wt_evict_file_exclusive_on(session));
+
/*
* If the handle is already open, it has to be closed so it can be
* reopened with a new configuration.
@@ -330,11 +333,11 @@ __wt_conn_btree_open(
* in the tree that can block the close.
*/
if (F_ISSET(dhandle, WT_DHANDLE_OPEN))
- WT_RET(__wt_conn_btree_sync_and_close(session, false, false));
+ WT_ERR(__wt_conn_btree_sync_and_close(session, false, false));
/* Discard any previous configuration, set up the new configuration. */
__conn_btree_config_clear(session);
- WT_RET(__conn_btree_config_set(session));
+ WT_ERR(__conn_btree_config_set(session));
/* Set any special flags on the handle. */
F_SET(btree, LF_MASK(WT_BTREE_SPECIAL_FLAGS));
@@ -374,6 +377,8 @@ __wt_conn_btree_open(
err: F_CLR(btree, WT_BTREE_SPECIAL_FLAGS);
}
+ __wt_evict_file_exclusive_off(session);
+
return (ret);
}
@@ -577,7 +582,7 @@ __conn_dhandle_remove(WT_SESSION_IMPL *session, bool final)
WT_ASSERT(session,
F_ISSET(session, WT_SESSION_LOCKED_HANDLE_LIST_WRITE));
- WT_ASSERT(session, dhandle != conn->cache->evict_file_next);
+ WT_ASSERT(session, dhandle != conn->cache->walk_tree);
/* Check if the handle was reacquired by a session while we waited. */
if (!final &&
@@ -673,8 +678,8 @@ restart:
continue;
WT_WITH_DHANDLE(session, dhandle,
- WT_TRET(__wt_conn_dhandle_discard_single(
- session, true, F_ISSET(conn, WT_CONN_IN_MEMORY))));
+ WT_TRET(__wt_conn_dhandle_discard_single(session, true,
+ F_ISSET(conn, WT_CONN_IN_MEMORY | WT_CONN_PANIC))));
goto restart;
}
@@ -699,8 +704,8 @@ restart:
/* Close the metadata file handle. */
while ((dhandle = TAILQ_FIRST(&conn->dhqh)) != NULL)
WT_WITH_DHANDLE(session, dhandle,
- WT_TRET(__wt_conn_dhandle_discard_single(
- session, true, F_ISSET(conn, WT_CONN_IN_MEMORY))));
+ WT_TRET(__wt_conn_dhandle_discard_single(session, true,
+ F_ISSET(conn, WT_CONN_IN_MEMORY | WT_CONN_PANIC))));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/conn/conn_log.c b/src/third_party/wiredtiger/src/conn/conn_log.c
index d2ed314fd2e..8b47d3b04c0 100644
--- a/src/third_party/wiredtiger/src/conn/conn_log.c
+++ b/src/third_party/wiredtiger/src/conn/conn_log.c
@@ -375,6 +375,7 @@ __log_file_server(void *arg)
WT_LOG *log;
WT_LSN close_end_lsn, min_lsn;
WT_SESSION_IMPL *session;
+ uint64_t yield_count;
uint32_t filenum;
bool locked;
@@ -382,6 +383,7 @@ __log_file_server(void *arg)
conn = S2C(session);
log = conn->log;
locked = false;
+ yield_count = 0;
while (F_ISSET(conn, WT_CONN_SERVER_LOG)) {
/*
* If there is a log file to close, make sure any outstanding
@@ -512,6 +514,7 @@ __log_file_server(void *arg)
* thread a chance to run and try again in
* this case.
*/
+ yield_count++;
__wt_yield();
continue;
}
@@ -522,8 +525,9 @@ __log_file_server(void *arg)
}
if (0) {
-err: __wt_err(session, ret, "log close server error");
+err: WT_PANIC_MSG(session, ret, "log close server error");
}
+ WT_STAT_CONN_INCRV(session, log_server_sync_blocked, yield_count);
if (locked)
__wt_spin_unlock(session, &log->log_sync_lock);
return (WT_THREAD_RET_VALUE);
@@ -740,7 +744,8 @@ __log_wrlsn_server(void *arg)
WT_ERR(__wt_log_force_write(session, 1, NULL));
__wt_log_wrlsn(session, NULL);
if (0) {
-err: __wt_err(session, ret, "log wrlsn server error");
+err: WT_PANIC_MSG(session, ret, "log wrlsn server error");
+
}
return (WT_THREAD_RET_VALUE);
}
@@ -757,7 +762,7 @@ __log_server(void *arg)
WT_DECL_RET;
WT_LOG *log;
WT_SESSION_IMPL *session;
- uint64_t timediff;
+ uint64_t retry, timediff;
bool did_work, signalled;
session = arg;
@@ -783,6 +788,7 @@ __log_server(void *arg)
* takes to sync out an earlier file.
*/
did_work = true;
+ retry = 0;
while (F_ISSET(conn, WT_CONN_SERVER_LOG)) {
/*
* Slots depend on future activity. Force out buffered
@@ -827,7 +833,24 @@ __log_server(void *arg)
ret = __log_archive_once(session, 0);
__wt_writeunlock(
session, &log->log_archive_lock);
- WT_ERR(ret);
+ /*
+ * It is possible that an external
+ * process on some systems may prevent
+ * removal. If we get a permission
+ * error, retry a few times.
+ */
+ if (ret == EACCES &&
+ retry < WT_RETRY_MAX) {
+ retry++;
+ ret = 0;
+ } else {
+ /*
+ * Return the error if there is
+ * one or reset on success.
+ */
+ WT_ERR(ret);
+ retry = 0;
+ }
} else
__wt_verbose(session, WT_VERB_LOG,
"log_archive: Blocked due to open "
@@ -844,7 +867,7 @@ __log_server(void *arg)
}
if (0) {
-err: __wt_err(session, ret, "log server error");
+err: WT_PANIC_MSG(session, ret, "log server error");
}
return (WT_THREAD_RET_VALUE);
}
@@ -902,7 +925,7 @@ __wt_logmgr_create(WT_SESSION_IMPL *session, const char *cfg[])
WT_RET(__wt_cond_alloc(session, "log sync", &log->log_sync_cond));
WT_RET(__wt_cond_alloc(session, "log write", &log->log_write_cond));
WT_RET(__wt_log_open(session));
- WT_RET(__wt_log_slot_init(session));
+ WT_RET(__wt_log_slot_init(session, true));
return (0);
}
diff --git a/src/third_party/wiredtiger/src/conn/conn_open.c b/src/third_party/wiredtiger/src/conn/conn_open.c
index eb3c79422a0..649bfa7c81f 100644
--- a/src/third_party/wiredtiger/src/conn/conn_open.c
+++ b/src/third_party/wiredtiger/src/conn/conn_open.c
@@ -91,6 +91,7 @@ __wt_connection_close(WT_CONNECTION_IMPL *conn)
if (txn_global->oldest_id == txn_global->current &&
txn_global->metadata_pinned == txn_global->current)
break;
+ WT_STAT_CONN_INCR(session, txn_release_blocked);
__wt_yield();
}
@@ -143,7 +144,7 @@ __wt_connection_close(WT_CONNECTION_IMPL *conn)
* conditional because we allocate the log path so that printlog can
* run without running logging or recovery.
*/
- if (FLD_ISSET(conn->log_flags, WT_CONN_LOG_ENABLED) &&
+ if (ret == 0 && FLD_ISSET(conn->log_flags, WT_CONN_LOG_ENABLED) &&
FLD_ISSET(conn->log_flags, WT_CONN_LOG_RECOVER_DONE))
WT_TRET(__wt_txn_checkpoint_log(
session, true, WT_TXN_LOG_CKPT_STOP, NULL));
diff --git a/src/third_party/wiredtiger/src/cursor/cur_index.c b/src/third_party/wiredtiger/src/cursor/cur_index.c
index 6fc01c0421f..15c1271c992 100644
--- a/src/third_party/wiredtiger/src/cursor/cur_index.c
+++ b/src/third_party/wiredtiger/src/cursor/cur_index.c
@@ -382,7 +382,7 @@ __curindex_close(WT_CURSOR *cursor)
if (cindex->child != NULL)
WT_TRET(cindex->child->close(cindex->child));
- __wt_schema_release_table(session, cindex->table);
+ WT_TRET(__wt_schema_release_table(session, cindex->table));
/* The URI is owned by the index. */
cursor->internal_uri = NULL;
WT_TRET(__wt_cursor_close(cursor));
@@ -485,7 +485,7 @@ __wt_curindex_open(WT_SESSION_IMPL *session,
if ((ret = __wt_schema_open_index(
session, table, idxname, namesize, &idx)) != 0) {
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
WT_RET(__wt_calloc_one(session, &cindex));
diff --git a/src/third_party/wiredtiger/src/cursor/cur_join.c b/src/third_party/wiredtiger/src/cursor/cur_join.c
index 80afaf798dc..3681d36b452 100644
--- a/src/third_party/wiredtiger/src/cursor/cur_join.c
+++ b/src/third_party/wiredtiger/src/cursor/cur_join.c
@@ -325,7 +325,7 @@ __curjoin_close(WT_CURSOR *cursor)
JOINABLE_CURSOR_API_CALL(cursor, session, close, NULL);
- __wt_schema_release_table(session, cjoin->table);
+ WT_TRET(__wt_schema_release_table(session, cjoin->table));
/* This is owned by the table */
cursor->key_format = NULL;
if (cjoin->projection != NULL) {
diff --git a/src/third_party/wiredtiger/src/cursor/cur_table.c b/src/third_party/wiredtiger/src/cursor/cur_table.c
index f6855172e90..ffa9ca35926 100644
--- a/src/third_party/wiredtiger/src/cursor/cur_table.c
+++ b/src/third_party/wiredtiger/src/cursor/cur_table.c
@@ -786,7 +786,7 @@ __curtable_close(WT_CURSOR *cursor)
__wt_free(session, ctable->cg_cursors);
__wt_free(session, ctable->cg_valcopy);
__wt_free(session, ctable->idx_cursors);
- __wt_schema_release_table(session, ctable->table);
+ WT_TRET(__wt_schema_release_table(session, ctable->table));
/* The URI is owned by the table. */
cursor->internal_uri = NULL;
WT_TRET(__wt_cursor_close(cursor));
@@ -942,7 +942,7 @@ __wt_curtable_open(WT_SESSION_IMPL *session,
ret = __wt_open_cursor(session,
table->cgroups[0]->source, NULL, cfg, cursorp);
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
if (ret == 0) {
/* Fix up the public URI to match what was passed in. */
cursor = *cursorp;
diff --git a/src/third_party/wiredtiger/src/evict/evict_lru.c b/src/third_party/wiredtiger/src/evict/evict_lru.c
index 26bbf9f679b..6e850f67b3e 100644
--- a/src/third_party/wiredtiger/src/evict/evict_lru.c
+++ b/src/third_party/wiredtiger/src/evict/evict_lru.c
@@ -17,7 +17,7 @@ static int __evict_pass(WT_SESSION_IMPL *);
static int __evict_server(WT_SESSION_IMPL *, bool *);
static int __evict_tune_workers(WT_SESSION_IMPL *session);
static int __evict_walk(WT_SESSION_IMPL *, WT_EVICT_QUEUE *);
-static int __evict_walk_file(
+static int __evict_walk_tree(
WT_SESSION_IMPL *, WT_EVICT_QUEUE *, u_int, u_int *);
#define WT_EVICT_HAS_WORKERS(s) \
@@ -767,8 +767,10 @@ __evict_clear_walk(WT_SESSION_IMPL *session)
cache = S2C(session)->cache;
WT_ASSERT(session, F_ISSET(session, WT_SESSION_LOCKED_PASS));
- if (session->dhandle == cache->evict_file_next)
- cache->evict_file_next = NULL;
+ if (session->dhandle == cache->walk_tree) {
+ cache->walk_tree = NULL;
+ cache->walk_target = 0;
+ }
if ((ref = btree->evict_ref) == NULL)
return (0);
@@ -880,10 +882,8 @@ void
__wt_evict_file_exclusive_off(WT_SESSION_IMPL *session)
{
WT_BTREE *btree;
- WT_CACHE *cache;
btree = S2BT(session);
- cache = S2C(session)->cache;
/*
* We have seen subtle bugs with multiple threads racing to turn
@@ -891,12 +891,26 @@ __wt_evict_file_exclusive_off(WT_SESSION_IMPL *session)
*/
WT_DIAGNOSTIC_YIELD;
- /* Hold the walk lock to turn on eviction. */
- __wt_spin_lock(session, &cache->evict_walk_lock);
- WT_ASSERT(session,
- btree->evict_ref == NULL && btree->evict_disabled > 0);
- --btree->evict_disabled;
- __wt_spin_unlock(session, &cache->evict_walk_lock);
+ /*
+ * Atomically decrement the evict-disabled count, without acquiring the
+ * eviction walk-lock. We can't acquire that lock here because there's
+ * a potential deadlock. When acquiring exclusive eviction access, we
+ * acquire the eviction walk-lock and then the cache's pass-intr lock.
+ * The current eviction implementation can hold the pass-intr lock and
+ * call into this function (see WT-3303 for the details), which might
+ * deadlock with another thread trying to get exclusive eviction access.
+ */
+#if defined(HAVE_DIAGNOSTIC)
+ {
+ int32_t v;
+
+ WT_ASSERT(session, btree->evict_ref == NULL);
+ v = __wt_atomic_subi32(&btree->evict_disabled, 1);
+ WT_ASSERT(session, v >= 0);
+ }
+#else
+ (void)__wt_atomic_subi32(&btree->evict_disabled, 1);
+#endif
}
#define EVICT_TUNE_BATCH 1 /* Max workers to add each period */
@@ -941,6 +955,13 @@ __evict_tune_workers(WT_SESSION_IMPL *session)
conn = S2C(session);
cache = conn->cache;
+ /*
+ * If we have a fixed number of eviction threads, there is no value in
+ * calculating if we should do any tuning.
+ */
+ if (conn->evict_threads_max == conn->evict_threads_min)
+ return (0);
+
WT_ASSERT(session, conn->evict_threads.threads[0]->session == session);
pgs_evicted_cur = pgs_evicted_persec_cur = 0;
@@ -1371,19 +1392,22 @@ retry: while (slot < max_entries) {
* scan last time through. If we don't have a saved
* handle, start from the beginning of the list.
*/
- if ((dhandle = cache->evict_file_next) != NULL)
- cache->evict_file_next = NULL;
- else
+ if ((dhandle = cache->walk_tree) != NULL)
+ cache->walk_tree = NULL;
+ else {
dhandle = TAILQ_FIRST(&conn->dhqh);
+ cache->walk_target = 0;
+ }
} else {
if (incr) {
WT_ASSERT(session, dhandle->session_inuse > 0);
(void)__wt_atomic_subi32(
&dhandle->session_inuse, 1);
incr = false;
- cache->evict_file_next = NULL;
+ cache->walk_tree = NULL;
}
dhandle = TAILQ_NEXT(dhandle, q);
+ cache->walk_target = 0;
}
/* If we reach the end of the list, we're done. */
@@ -1445,29 +1469,26 @@ retry: while (slot < max_entries) {
/*
* Re-check the "no eviction" flag, used to enforce exclusive
- * access when a handle is being closed. If not set, remember
- * the file to visit first, next loop.
+ * access when a handle is being closed.
*
* Only try to acquire the lock and simply continue if we fail;
* the lock is held while the thread turning off eviction clears
* the tree's current eviction point, and part of the process is
* waiting on this thread to acknowledge that action.
+ *
+ * If a handle is being discarded, it will still be marked open,
+ * but won't have a root page.
*/
if (btree->evict_disabled == 0 &&
!__wt_spin_trylock(session, &cache->evict_walk_lock)) {
- if (btree->evict_disabled == 0) {
+ if (btree->evict_disabled == 0 &&
+ btree->root.page != NULL) {
/*
- * Assert the handle has a root page: eviction
- * should have been locked out if the tree is
- * being discarded or the root page is changing.
- * As this has not always been the case, assert
- * to debug that change.
+ * Remember the file to visit first, next loop.
*/
- WT_ASSERT(session, btree->root.page != NULL);
-
- cache->evict_file_next = dhandle;
+ cache->walk_tree = dhandle;
WT_WITH_DHANDLE(session, dhandle,
- ret = __evict_walk_file(
+ ret = __evict_walk_tree(
session, queue, max_entries, &slot));
WT_ASSERT(session, session->split_gen == 0);
@@ -1489,7 +1510,7 @@ retry: while (slot < max_entries) {
* candidates and we aren't finding more.
*/
if (slot < max_entries && (retries < 2 ||
- (retries < 10 &&
+ (retries < WT_RETRY_MAX &&
(slot == queue->evict_entries || slot > start_slot)))) {
start_slot = slot;
++retries;
@@ -1556,45 +1577,21 @@ __evict_push_candidate(WT_SESSION_IMPL *session,
}
/*
- * __evict_walk_file --
- * Get a few page eviction candidates from a single underlying file.
+ * __evict_walk_target --
+ * Calculate how many pages to queue for a given tree.
*/
-static int
-__evict_walk_file(WT_SESSION_IMPL *session,
- WT_EVICT_QUEUE *queue, u_int max_entries, u_int *slotp)
+static uint32_t
+__evict_walk_target(
+ WT_SESSION_IMPL *session, WT_EVICT_QUEUE *queue, u_int max_entries)
{
- WT_BTREE *btree;
WT_CACHE *cache;
- WT_CONNECTION_IMPL *conn;
- WT_DECL_RET;
- WT_EVICT_ENTRY *end, *evict, *start;
- WT_PAGE *page;
- WT_PAGE_MODIFY *mod;
- WT_REF *ref;
- WT_TXN_GLOBAL *txn_global;
- uint64_t btree_inuse, bytes_per_slot, cache_inuse, min_pages;
- uint64_t pages_seen, pages_queued, refs_walked;
- uint32_t remaining_slots, total_slots, walk_flags;
+ uint64_t btree_inuse, bytes_per_slot, cache_inuse;
uint32_t target_pages_clean, target_pages_dirty, target_pages;
- int internal_pages, restarts;
- bool give_up, modified, urgent_queued;
+ uint32_t total_slots;
- conn = S2C(session);
- btree = S2BT(session);
- cache = conn->cache;
- txn_global = &conn->txn_global;
- internal_pages = restarts = 0;
- give_up = urgent_queued = false;
-
- /*
- * Figure out how many slots to fill from this tree.
- * Note that some care is taken in the calculation to avoid overflow.
- */
- start = queue->evict_queue + *slotp;
- remaining_slots = max_entries - *slotp;
- total_slots = max_entries - queue->evict_entries;
- btree_inuse = cache_inuse = 0;
+ cache = S2C(session)->cache;
target_pages_clean = target_pages_dirty = 0;
+ total_slots = max_entries - queue->evict_entries;
/*
* The number of times we should fill the queue by the end of
@@ -1640,26 +1637,16 @@ __evict_walk_file(WT_SESSION_IMPL *session,
QUEUE_FILLS_PER_PASS;
/*
- * Randomly walk trees with a small fraction of the cache in case there
- * are so many trees that none of them use enough of the cache to be
- * allocated slots.
- *
- * The chance of walking a tree is equal to the chance that a random
- * byte in cache belongs to the tree, weighted by how many times we
- * want to fill queues during a pass through all the trees in cache.
+ * Walk trees with a small fraction of the cache in case there are so
+ * many trees that none of them use enough of the cache to be allocated
+ * slots. Only skip a tree if it has no bytes of interest.
*/
if (target_pages == 0) {
- if (F_ISSET(cache, WT_CACHE_EVICT_CLEAN)) {
- btree_inuse = __wt_btree_bytes_evictable(session);
- cache_inuse = __wt_cache_bytes_inuse(cache);
- } else {
- btree_inuse = __wt_btree_dirty_leaf_inuse(session);
- cache_inuse = __wt_cache_dirty_leaf_inuse(cache);
- }
- if (btree_inuse == 0 || cache_inuse == 0)
- return (0);
- if (__wt_random64(&session->rnd) % cache_inuse >
- btree_inuse * QUEUE_FILLS_PER_PASS)
+ btree_inuse = F_ISSET(cache, WT_CACHE_EVICT_CLEAN) ?
+ __wt_btree_bytes_evictable(session) :
+ __wt_btree_dirty_leaf_inuse(session);
+
+ if (btree_inuse == 0)
return (0);
}
@@ -1670,13 +1657,64 @@ __evict_walk_file(WT_SESSION_IMPL *session,
if (target_pages < MIN_PAGES_PER_TREE)
target_pages = MIN_PAGES_PER_TREE;
+ /* If the tree is dead, take a lot of pages. */
+ if (F_ISSET(session->dhandle, WT_DHANDLE_DEAD))
+ target_pages *= 10;
+
+ return (target_pages);
+}
+
+/*
+ * __evict_walk_tree --
+ * Get a few page eviction candidates from a single underlying file.
+ */
+static int
+__evict_walk_tree(WT_SESSION_IMPL *session,
+ WT_EVICT_QUEUE *queue, u_int max_entries, u_int *slotp)
+{
+ WT_BTREE *btree;
+ WT_CACHE *cache;
+ WT_CONNECTION_IMPL *conn;
+ WT_DECL_RET;
+ WT_EVICT_ENTRY *end, *evict, *start;
+ WT_PAGE *page;
+ WT_PAGE_MODIFY *mod;
+ WT_REF *ref;
+ WT_TXN_GLOBAL *txn_global;
+ uint64_t min_pages, pages_seen, pages_queued, refs_walked;
+ uint32_t remaining_slots, target_pages, walk_flags;
+ int internal_pages, restarts;
+ bool give_up, modified, urgent_queued;
+
+ conn = S2C(session);
+ btree = S2BT(session);
+ cache = conn->cache;
+ txn_global = &conn->txn_global;
+ internal_pages = restarts = 0;
+ give_up = urgent_queued = false;
+
/*
- * If the tree is dead or we're near the end of the queue, fill the
- * remaining slots.
+ * Figure out how many slots to fill from this tree.
+ * Note that some care is taken in the calculation to avoid overflow.
*/
- if (F_ISSET(session->dhandle, WT_DHANDLE_DEAD) ||
- target_pages > remaining_slots)
+ start = queue->evict_queue + *slotp;
+ remaining_slots = max_entries - *slotp;
+ if (cache->walk_target != 0) {
+ WT_ASSERT(session, cache->walk_progress <= cache->walk_target);
+ target_pages = cache->walk_target - cache->walk_progress;
+ } else {
+ target_pages = cache->walk_target =
+ __evict_walk_target(session, queue, max_entries);
+ cache->walk_progress = 0;
+ }
+
+ if (target_pages > remaining_slots)
target_pages = remaining_slots;
+
+ /* If we don't want any pages from this tree, move on. */
+ if (target_pages == 0)
+ return (0);
+
end = start + target_pages;
/*
@@ -1849,6 +1887,7 @@ fast: /* If the page can't be evicted, give up. */
continue;
++evict;
++pages_queued;
+ ++cache->walk_progress;
if (WT_PAGE_IS_INTERNAL(page))
++internal_pages;
@@ -1863,6 +1902,10 @@ fast: /* If the page can't be evicted, give up. */
WT_STAT_CONN_INCRV(
session, cache_eviction_pages_queued, (u_int)(evict - start));
+ __wt_verbose(session, WT_VERB_EVICTSERVER,
+ "%s walk: seen %" PRIu64 ", queued %" PRIu64,
+ session->dhandle->name, pages_seen, pages_queued);
+
/*
* If we couldn't find the number of pages we were looking for, skip
* the tree next time.
@@ -2445,14 +2488,23 @@ __wt_verbose_dump_cache(WT_SESSION_IMPL *session)
WT_CONNECTION_IMPL *conn;
WT_DATA_HANDLE *dhandle;
WT_DECL_RET;
+ u_int pct;
uint64_t total_bytes, total_dirty_bytes;
conn = S2C(session);
total_bytes = total_dirty_bytes = 0;
+ pct = 0; /* [-Werror=uninitialized] */
WT_RET(__wt_msg(session, "%s", WT_DIVIDER));
WT_RET(__wt_msg(session, "cache dump"));
+ WT_RET(__wt_msg(session,
+ "cache full: %s", __wt_cache_full(session) ? "yes" : "no"));
+ WT_RET(__wt_msg(session, "cache clean check: %s (%u%%)",
+ __wt_eviction_clean_needed(session, &pct) ? "yes" : "no", pct));
+ WT_RET(__wt_msg(session, "cache dirty check: %s (%u%%)",
+ __wt_eviction_dirty_needed(session, &pct) ? "yes" : "no", pct));
+
for (dhandle = NULL;;) {
WT_WITH_HANDLE_LIST_READ_LOCK(session,
WT_DHANDLE_NEXT(session, dhandle, &conn->dhqh, q));
diff --git a/src/third_party/wiredtiger/src/evict/evict_page.c b/src/third_party/wiredtiger/src/evict/evict_page.c
index 85689efd0b1..70a16442dc4 100644
--- a/src/third_party/wiredtiger/src/evict/evict_page.c
+++ b/src/third_party/wiredtiger/src/evict/evict_page.c
@@ -156,7 +156,7 @@ __wt_evict(WT_SESSION_IMPL *session, WT_REF *ref, bool closing)
/* Update the reference and discard the page. */
if (__wt_ref_is_root(ref))
__wt_ref_out(session, ref);
- else if ((clean_page && !LF_ISSET(WT_EVICT_IN_MEMORY)) || tree_dead)
+ else if ((clean_page && !F_ISSET(conn, WT_CONN_IN_MEMORY)) || tree_dead)
/*
* Pages that belong to dead trees never write back to disk
* and can't support page splits.
@@ -202,8 +202,8 @@ __evict_delete_ref(WT_SESSION_IMPL *session, WT_REF *ref, bool closing)
return (0);
/*
- * Avoid doing reverse splits when closing the file, it is
- * wasted work and some structure may already have been freed.
+ * Avoid doing reverse splits when closing the file, it is wasted work
+ * and some structures may have already been freed.
*/
if (!closing) {
parent = ref->home;
@@ -393,11 +393,13 @@ __evict_review(
WT_SESSION_IMPL *session, WT_REF *ref, uint32_t *flagsp, bool closing)
{
WT_CACHE *cache;
+ WT_CONNECTION_IMPL *conn;
WT_DECL_RET;
WT_PAGE *page;
uint32_t flags;
- bool lookaside_retry, modified;
+ bool lookaside_retry, *lookaside_retryp, modified;
+ conn = S2C(session);
flags = WT_EVICTING;
*flagsp = flags;
@@ -453,7 +455,7 @@ __evict_review(
* Clean pages can't be evicted when running in memory only. This
* should be uncommon - we don't add clean pages to the queue.
*/
- if (F_ISSET(S2C(session), WT_CONN_IN_MEMORY) && !modified && !closing)
+ if (F_ISSET(conn, WT_CONN_IN_MEMORY) && !modified && !closing)
return (EBUSY);
/* Check if the page can be evicted. */
@@ -519,11 +521,14 @@ __evict_review(
* Additionally, if we aren't trying to free space in the cache, scrub
* the page and keep it in memory.
*/
- cache = S2C(session)->cache;
+ cache = conn->cache;
+ lookaside_retry = false;
+ lookaside_retryp = NULL;
+
if (closing)
LF_SET(WT_VISIBILITY_ERR);
else if (!WT_PAGE_IS_INTERNAL(page)) {
- if (F_ISSET(S2C(session), WT_CONN_IN_MEMORY))
+ if (F_ISSET(conn, WT_CONN_IN_MEMORY))
LF_SET(WT_EVICT_IN_MEMORY |
WT_EVICT_SCRUB | WT_EVICT_UPDATE_RESTORE);
else {
@@ -531,21 +536,26 @@ __evict_review(
if (F_ISSET(cache, WT_CACHE_EVICT_SCRUB))
LF_SET(WT_EVICT_SCRUB);
+
+ /*
+ * Check if reconciliation suggests trying the
+ * lookaside table.
+ */
+ lookaside_retryp = &lookaside_retry;
}
}
/* Reconcile the page. */
- ret = __wt_reconcile(session, ref, NULL, flags, &lookaside_retry);
+ ret = __wt_reconcile(session, ref, NULL, flags, lookaside_retryp);
/*
- * If reconciliation fails, eviction is stuck and reconciliation reports
- * it might succeed if we use the lookaside table (the page didn't have
- * uncommitted updates, it was not-yet-globally visible updates causing
- * the problem), configure reconciliation to write those updates to the
- * lookaside table, allowing the eviction of pages we'd otherwise have
- * to retain in cache to support older readers.
+ * If reconciliation fails, eviction is stuck and reconciliation
+ * reports it might succeed if we use the lookaside table, then
+ * configure reconciliation to write those updates to the lookaside
+ * table, allowing the eviction of pages we'd otherwise have to retain
+ * in cache to support older readers.
*/
- if (ret == EBUSY && __wt_cache_stuck(session) && lookaside_retry) {
+ if (ret == EBUSY && lookaside_retry && __wt_cache_stuck(session)) {
LF_CLR(WT_EVICT_SCRUB | WT_EVICT_UPDATE_RESTORE);
LF_SET(WT_EVICT_LOOKASIDE);
ret = __wt_reconcile(session, ref, NULL, flags, NULL);
diff --git a/src/third_party/wiredtiger/src/include/btmem.h b/src/third_party/wiredtiger/src/include/btmem.h
index d0b21b17965..e965724dffe 100644
--- a/src/third_party/wiredtiger/src/include/btmem.h
+++ b/src/third_party/wiredtiger/src/include/btmem.h
@@ -714,7 +714,7 @@ struct __wt_page {
* Related information for fast-delete, on-disk pages.
*/
struct __wt_page_deleted {
- uint64_t txnid; /* Transaction ID */
+ volatile uint64_t txnid; /* Transaction ID */
WT_UPDATE **update_list; /* List of updates for abort */
};
@@ -904,7 +904,7 @@ struct __wt_ikey {
* list.
*/
WT_PACKED_STRUCT_BEGIN(__wt_update)
- uint64_t txnid; /* update transaction */
+ volatile uint64_t txnid; /* Transaction ID */
WT_UPDATE *next; /* forward-linked list */
diff --git a/src/third_party/wiredtiger/src/include/btree.h b/src/third_party/wiredtiger/src/include/btree.h
index 28fe1b94b23..5b1b4d68976 100644
--- a/src/third_party/wiredtiger/src/include/btree.h
+++ b/src/third_party/wiredtiger/src/include/btree.h
@@ -134,34 +134,44 @@ struct __wt_btree {
WT_BM *bm; /* Block manager reference */
u_int block_header; /* WT_PAGE_HEADER_BYTE_SIZE */
- uint64_t checkpoint_gen; /* Checkpoint generation */
- uint64_t rec_max_txn; /* Maximum txn seen (clean trees) */
uint64_t write_gen; /* Write generation */
+ uint64_t rec_max_txn; /* Maximum txn seen (clean trees) */
+ uint64_t checkpoint_gen; /* Checkpoint generation */
+ volatile enum {
+ WT_CKPT_OFF, WT_CKPT_PREPARE, WT_CKPT_RUNNING
+ } checkpointing; /* Checkpoint in progress */
uint64_t bytes_inmem; /* Cache bytes in memory. */
uint64_t bytes_dirty_intl; /* Bytes in dirty internal pages. */
uint64_t bytes_dirty_leaf; /* Bytes in dirty leaf pages. */
+ /*
+ * We flush pages from the tree (in order to make checkpoint faster),
+ * without a high-level lock. To avoid multiple threads flushing at
+ * the same time, lock the tree.
+ */
+ WT_SPINLOCK flush_lock; /* Lock to flush the tree's pages */
+
+ /*
+ * All of the following fields live at the end of the structure so it's
+ * easier to clear everything but the fields that persist.
+ */
+#define WT_BTREE_CLEAR_SIZE (offsetof(WT_BTREE, evict_ref))
+
+ /*
+ * Eviction information is maintained in the btree handle, but owned by
+ * eviction, not the btree code.
+ */
WT_REF *evict_ref; /* Eviction thread's location */
uint64_t evict_priority; /* Relative priority of cached pages */
u_int evict_walk_period; /* Skip this many LRU walks */
u_int evict_walk_saved; /* Saved walk skips for checkpoints */
u_int evict_walk_skips; /* Number of walks skipped */
- int evict_disabled; /* Eviction disabled count */
+ int32_t evict_disabled; /* Eviction disabled count */
+ bool evict_disabled_open;/* Eviction disabled on open */
volatile uint32_t evict_busy; /* Count of threads in eviction */
int evict_start_type; /* Start position for eviction walk
(see WT_EVICT_WALK_START). */
- enum {
- WT_CKPT_OFF, WT_CKPT_PREPARE, WT_CKPT_RUNNING
- } checkpointing; /* Checkpoint in progress */
-
- /*
- * We flush pages from the tree (in order to make checkpoint faster),
- * without a high-level lock. To avoid multiple threads flushing at
- * the same time, lock the tree.
- */
- WT_SPINLOCK flush_lock; /* Lock to flush the tree's pages */
-
/* Flags values up to 0xff are reserved for WT_DHANDLE_* */
#define WT_BTREE_ALLOW_SPLITS 0x000100 /* Allow splits, even with no evict */
#define WT_BTREE_BULK 0x000200 /* Bulk-load handle */
diff --git a/src/third_party/wiredtiger/src/include/cache.h b/src/third_party/wiredtiger/src/include/cache.h
index 04920c3585a..42a152e5f10 100644
--- a/src/third_party/wiredtiger/src/include/cache.h
+++ b/src/third_party/wiredtiger/src/include/cache.h
@@ -133,7 +133,8 @@ struct __wt_cache {
*/
WT_SPINLOCK evict_pass_lock; /* Eviction pass lock */
WT_SESSION_IMPL *walk_session; /* Eviction pass session */
- WT_DATA_HANDLE *evict_file_next;/* LRU next file to search */
+ WT_DATA_HANDLE *walk_tree; /* LRU walk current tree */
+ uint32_t walk_progress, walk_target;/* Progress in current tree */
WT_SPINLOCK evict_queue_lock; /* Eviction current queue lock */
WT_EVICT_QUEUE evict_queues[WT_EVICT_QUEUE_MAX];
diff --git a/src/third_party/wiredtiger/src/include/connection.h b/src/third_party/wiredtiger/src/include/connection.h
index f74732684f5..74611de1131 100644
--- a/src/third_party/wiredtiger/src/include/connection.h
+++ b/src/third_party/wiredtiger/src/include/connection.h
@@ -314,9 +314,10 @@ struct __wt_connection_impl {
#define WT_CONN_LOG_ARCHIVE 0x01 /* Archive is enabled */
#define WT_CONN_LOG_ENABLED 0x02 /* Logging is enabled */
#define WT_CONN_LOG_EXISTED 0x04 /* Log files found */
-#define WT_CONN_LOG_RECOVER_DONE 0x08 /* Recovery completed */
-#define WT_CONN_LOG_RECOVER_ERR 0x10 /* Error if recovery required */
-#define WT_CONN_LOG_ZERO_FILL 0x20 /* Manually zero files */
+#define WT_CONN_LOG_RECOVER_DIRTY 0x08 /* Recovering unclean */
+#define WT_CONN_LOG_RECOVER_DONE 0x10 /* Recovery completed */
+#define WT_CONN_LOG_RECOVER_ERR 0x20 /* Error if recovery required */
+#define WT_CONN_LOG_ZERO_FILL 0x40 /* Manually zero files */
uint32_t log_flags; /* Global logging configuration */
WT_CONDVAR *log_cond; /* Log server wait mutex */
WT_SESSION_IMPL *log_session; /* Log server session */
diff --git a/src/third_party/wiredtiger/src/include/extern.h b/src/third_party/wiredtiger/src/include/extern.h
index bf3279d0f94..3c45b290dd8 100644
--- a/src/third_party/wiredtiger/src/include/extern.h
+++ b/src/third_party/wiredtiger/src/include/extern.h
@@ -181,7 +181,7 @@ extern int __wt_verify_dsk_image(WT_SESSION_IMPL *session, const char *tag, cons
extern int __wt_verify_dsk(WT_SESSION_IMPL *session, const char *tag, WT_ITEM *buf) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_tree_walk(WT_SESSION_IMPL *session, WT_REF **refp, uint32_t flags) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_tree_walk_count(WT_SESSION_IMPL *session, WT_REF **refp, uint64_t *walkcntp, uint32_t flags) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
-extern int __wt_tree_walk_skip(WT_SESSION_IMPL *session, WT_REF **refp, uint64_t *skipleafcntp, uint32_t flags) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
+extern int __wt_tree_walk_skip( WT_SESSION_IMPL *session, WT_REF **refp, uint64_t *skipleafcntp) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_col_modify(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, uint64_t recno, WT_ITEM *value, WT_UPDATE *upd_arg, bool is_remove, bool exclusive) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_col_search(WT_SESSION_IMPL *session, uint64_t search_recno, WT_REF *leaf, WT_CURSOR_BTREE *cbt) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_row_leaf_keys(WT_SESSION_IMPL *session, WT_PAGE *page) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
@@ -369,6 +369,7 @@ extern int __wt_log_needs_recovery(WT_SESSION_IMPL *session, WT_LSN *ckp_lsn, bo
extern void __wt_log_written_reset(WT_SESSION_IMPL *session);
extern int __wt_log_get_all_files(WT_SESSION_IMPL *session, char ***filesp, u_int *countp, uint32_t *maxid, bool active_only) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_extract_lognum( WT_SESSION_IMPL *session, const char *name, uint32_t *id) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
+extern int __wt_log_reset(WT_SESSION_IMPL *session, uint32_t lognum) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_acquire(WT_SESSION_IMPL *session, uint64_t recsize, WT_LOGSLOT *slot) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_allocfile( WT_SESSION_IMPL *session, uint32_t lognum, const char *dest) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_remove(WT_SESSION_IMPL *session, const char *file_prefix, uint32_t lognum) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
@@ -405,7 +406,7 @@ extern int __wt_logop_row_truncate_print(WT_SESSION_IMPL *session, const uint8_t
extern int __wt_txn_op_printlog(WT_SESSION_IMPL *session, const uint8_t **pp, const uint8_t *end, uint32_t flags) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern void __wt_log_slot_activate(WT_SESSION_IMPL *session, WT_LOGSLOT *slot);
extern int __wt_log_slot_switch(WT_SESSION_IMPL *session, WT_MYSLOT *myslot, bool retry, bool forced, bool *did_work) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
-extern int __wt_log_slot_init(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
+extern int __wt_log_slot_init(WT_SESSION_IMPL *session, bool alloc) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_slot_destroy(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_log_slot_join(WT_SESSION_IMPL *session, uint64_t mysize, uint32_t flags, WT_MYSLOT *myslot) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int64_t __wt_log_slot_release(WT_SESSION_IMPL *session, WT_MYSLOT *myslot, int64_t size);
@@ -564,12 +565,13 @@ extern int __wt_schema_index_source(WT_SESSION_IMPL *session, WT_TABLE *table, c
extern int __wt_schema_create( WT_SESSION_IMPL *session, const char *uri, const char *config) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_drop(WT_SESSION_IMPL *session, const char *uri, const char *cfg[]) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_get_table(WT_SESSION_IMPL *session, const char *name, size_t namelen, bool ok_incomplete, WT_TABLE **tablep) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
-extern void __wt_schema_release_table(WT_SESSION_IMPL *session, WT_TABLE *table);
+extern int __wt_schema_release_table(WT_SESSION_IMPL *session, WT_TABLE *table) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern void __wt_schema_destroy_colgroup(WT_SESSION_IMPL *session, WT_COLGROUP **colgroupp);
extern int __wt_schema_destroy_index(WT_SESSION_IMPL *session, WT_INDEX **idxp) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_destroy_table(WT_SESSION_IMPL *session, WT_TABLE **tablep) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_remove_table(WT_SESSION_IMPL *session, WT_TABLE *table) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_close_tables(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
+extern int __wt_schema_sweep_tables(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_colgroup_name(WT_SESSION_IMPL *session, WT_TABLE *table, const char *cgname, size_t len, WT_ITEM *buf) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_open_colgroups(WT_SESSION_IMPL *session, WT_TABLE *table) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_schema_open_index(WT_SESSION_IMPL *session, WT_TABLE *table, const char *idxname, size_t len, WT_INDEX **indexp) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
@@ -691,7 +693,6 @@ extern uint32_t __wt_rduppo2(uint32_t n, uint32_t po2);
extern void __wt_random_init(WT_RAND_STATE volatile *rnd_state) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
extern void __wt_random_init_seed( WT_SESSION_IMPL *session, WT_RAND_STATE volatile *rnd_state) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
extern uint32_t __wt_random(WT_RAND_STATE volatile *rnd_state) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
-extern uint64_t __wt_random64(WT_RAND_STATE volatile *rnd_state) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
extern int __wt_buf_grow_worker(WT_SESSION_IMPL *session, WT_ITEM *buf, size_t size) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_buf_fmt(WT_SESSION_IMPL *session, WT_ITEM *buf, const char *fmt, ...) WT_GCC_FUNC_DECL_ATTRIBUTE((format (printf, 3, 4))) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_buf_catfmt(WT_SESSION_IMPL *session, WT_ITEM *buf, const char *fmt, ...) WT_GCC_FUNC_DECL_ATTRIBUTE((format (printf, 3, 4))) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
@@ -732,6 +733,8 @@ extern int __wt_thread_group_create( WT_SESSION_IMPL *session, WT_THREAD_GROUP *
extern int __wt_thread_group_destroy(WT_SESSION_IMPL *session, WT_THREAD_GROUP *group) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_group_start_one( WT_SESSION_IMPL *session, WT_THREAD_GROUP *group, bool wait) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_group_stop_one( WT_SESSION_IMPL *session, WT_THREAD_GROUP *group, bool wait) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
+extern void __wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
+extern void __wt_seconds(WT_SESSION_IMPL *session, time_t *timep);
extern void __wt_txn_release_snapshot(WT_SESSION_IMPL *session);
extern void __wt_txn_get_snapshot(WT_SESSION_IMPL *session);
extern int __wt_txn_update_oldest(WT_SESSION_IMPL *session, uint32_t flags) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
diff --git a/src/third_party/wiredtiger/src/include/extern_posix.h b/src/third_party/wiredtiger/src/include/extern_posix.h
index c0ed056c7b6..9e32e86e64c 100644
--- a/src/third_party/wiredtiger/src/include/extern_posix.h
+++ b/src/third_party/wiredtiger/src/include/extern_posix.h
@@ -28,5 +28,5 @@ extern int __wt_vsnprintf_len_incr( char *buf, size_t size, size_t *retsizep, co
extern int __wt_thread_create(WT_SESSION_IMPL *session, wt_thread_t *tidret, WT_THREAD_CALLBACK(*func)(void *), void *arg) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_join(WT_SESSION_IMPL *session, wt_thread_t tid) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_id(char *buf, size_t buflen) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default"))) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
-extern void __wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
+extern void __wt_epoch_raw(WT_SESSION_IMPL *session, struct timespec *tsp);
extern void __wt_yield(void) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("default")));
diff --git a/src/third_party/wiredtiger/src/include/extern_win.h b/src/third_party/wiredtiger/src/include/extern_win.h
index d548ee0b2ec..85db8175615 100644
--- a/src/third_party/wiredtiger/src/include/extern_win.h
+++ b/src/third_party/wiredtiger/src/include/extern_win.h
@@ -26,7 +26,7 @@ extern int __wt_vsnprintf_len_incr( char *buf, size_t size, size_t *retsizep, co
extern int __wt_thread_create(WT_SESSION_IMPL *session, wt_thread_t *tidret, WT_THREAD_CALLBACK(*func)(void *), void *arg) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_join(WT_SESSION_IMPL *session, wt_thread_t tid) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_thread_id(char *buf, size_t buflen) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
-extern void __wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp);
+extern void __wt_epoch_raw(WT_SESSION_IMPL *session, struct timespec *tsp);
extern int __wt_to_utf16_string( WT_SESSION_IMPL *session, const char*utf8, WT_ITEM **outbuf) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern int __wt_to_utf8_string( WT_SESSION_IMPL *session, const wchar_t*wide, WT_ITEM **outbuf) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result));
extern DWORD __wt_getlasterror(void);
diff --git a/src/third_party/wiredtiger/src/include/flags.h b/src/third_party/wiredtiger/src/include/flags.h
index f26a45c68f5..0e6cf7657ed 100644
--- a/src/third_party/wiredtiger/src/include/flags.h
+++ b/src/third_party/wiredtiger/src/include/flags.h
@@ -21,7 +21,8 @@
#define WT_CONN_SERVER_LSM 0x00008000
#define WT_CONN_SERVER_STATISTICS 0x00010000
#define WT_CONN_SERVER_SWEEP 0x00020000
-#define WT_CONN_WAS_BACKUP 0x00040000
+#define WT_CONN_TABLE_CACHE 0x00040000
+#define WT_CONN_WAS_BACKUP 0x00080000
#define WT_EVICTING 0x00000002
#define WT_EVICT_INMEM_SPLIT 0x00000004
#define WT_EVICT_IN_MEMORY 0x00000008
@@ -47,9 +48,8 @@
#define WT_READ_PREV 0x00000080
#define WT_READ_RESTART_OK 0x00000100
#define WT_READ_SKIP_INTL 0x00000200
-#define WT_READ_SKIP_LEAF 0x00000400
-#define WT_READ_TRUNCATE 0x00000800
-#define WT_READ_WONT_NEED 0x00001000
+#define WT_READ_TRUNCATE 0x00000400
+#define WT_READ_WONT_NEED 0x00000800
#define WT_SESSION_CAN_WAIT 0x00000001
#define WT_SESSION_INTERNAL 0x00000002
#define WT_SESSION_LOCKED_CHECKPOINT 0x00000004
diff --git a/src/third_party/wiredtiger/src/include/misc.i b/src/third_party/wiredtiger/src/include/misc.i
index fad10f01103..eb99de3dcab 100644
--- a/src/third_party/wiredtiger/src/include/misc.i
+++ b/src/third_party/wiredtiger/src/include/misc.i
@@ -41,45 +41,6 @@ __wt_strdup(WT_SESSION_IMPL *session, const char *str, void *retp)
}
/*
- * __wt_seconds --
- * Return the seconds since the Epoch.
- */
-static inline void
-__wt_seconds(WT_SESSION_IMPL *session, time_t *timep)
-{
- struct timespec t;
-
- __wt_epoch(session, &t);
-
- *timep = t.tv_sec;
-}
-
-/*
- * __wt_time_check_monotonic --
- * Check and prevent time running backward. If we detect that it has, we
- * set the time structure to the previous values, making time stand still
- * until we see a time in the future of the highest value seen so far.
- */
-static inline void
-__wt_time_check_monotonic(WT_SESSION_IMPL *session, struct timespec *tsp)
-{
- /*
- * Detect time going backward. If so, use the last
- * saved timestamp.
- */
- if (session == NULL)
- return;
-
- if (tsp->tv_sec < session->last_epoch.tv_sec ||
- (tsp->tv_sec == session->last_epoch.tv_sec &&
- tsp->tv_nsec < session->last_epoch.tv_nsec)) {
- WT_STAT_CONN_INCR(session, time_travel);
- *tsp = session->last_epoch;
- } else
- session->last_epoch = *tsp;
-}
-
-/*
* __wt_verbose --
* Verbose message.
*
diff --git a/src/third_party/wiredtiger/src/include/os.h b/src/third_party/wiredtiger/src/include/os.h
index 73d89268392..2c03b115e7c 100644
--- a/src/third_party/wiredtiger/src/include/os.h
+++ b/src/third_party/wiredtiger/src/include/os.h
@@ -34,9 +34,11 @@
(ret) = __wt_errno(); \
} while (0)
+#define WT_RETRY_MAX 10
+
#define WT_SYSCALL_RETRY(call, ret) do { \
int __retry; \
- for (__retry = 0; __retry < 10; ++__retry) { \
+ for (__retry = 0; __retry < WT_RETRY_MAX; ++__retry) { \
WT_SYSCALL(call, ret); \
switch (ret) { \
case EAGAIN: \
diff --git a/src/third_party/wiredtiger/src/include/session.h b/src/third_party/wiredtiger/src/include/session.h
index 0074b4a9d43..d05dee68641 100644
--- a/src/third_party/wiredtiger/src/include/session.h
+++ b/src/third_party/wiredtiger/src/include/session.h
@@ -98,6 +98,12 @@ struct __wt_session_impl {
*/
TAILQ_HEAD(__tables, __wt_table) tables;
+ /*
+ * Updated when the table cache is swept of all tables older than the
+ * current schema generation.
+ */
+ uint64_t table_sweep_gen;
+
/* Current rwlock for callback. */
WT_RWLOCK *current_rwlock;
uint8_t current_rwticket;
diff --git a/src/third_party/wiredtiger/src/include/stat.h b/src/third_party/wiredtiger/src/include/stat.h
index db48a841571..01e622a5695 100644
--- a/src/third_party/wiredtiger/src/include/stat.h
+++ b/src/third_party/wiredtiger/src/include/stat.h
@@ -475,11 +475,19 @@ struct __wt_connection_stats {
int64_t thread_write_active;
int64_t application_evict_time;
int64_t application_cache_time;
+ int64_t txn_release_blocked;
+ int64_t conn_close_blocked_lsm;
+ int64_t dhandle_lock_blocked;
+ int64_t log_server_sync_blocked;
int64_t page_busy_blocked;
int64_t page_forcible_evict_blocked;
int64_t page_locked_blocked;
int64_t page_read_blocked;
int64_t page_sleep;
+ int64_t page_del_rollback_blocked;
+ int64_t child_modify_blocked_page;
+ int64_t page_index_slot_blocked;
+ int64_t tree_descend_blocked;
int64_t txn_snapshots_created;
int64_t txn_snapshots_dropped;
int64_t txn_begin;
diff --git a/src/third_party/wiredtiger/src/include/txn.h b/src/third_party/wiredtiger/src/include/txn.h
index 7e802c188ab..fdf9c714afa 100644
--- a/src/third_party/wiredtiger/src/include/txn.h
+++ b/src/third_party/wiredtiger/src/include/txn.h
@@ -93,6 +93,8 @@ struct __wt_txn_global {
* the global transaction state.
*/
WT_RWLOCK scan_rwlock;
+ /* Protects logging, checkpoints and transaction visibility. */
+ WT_RWLOCK visibility_rwlock;
/*
* Track information about the running checkpoint. The transaction
diff --git a/src/third_party/wiredtiger/src/include/txn.i b/src/third_party/wiredtiger/src/include/txn.i
index 314c948e4d1..39273a1995c 100644
--- a/src/third_party/wiredtiger/src/include/txn.i
+++ b/src/third_party/wiredtiger/src/include/txn.i
@@ -148,16 +148,6 @@ __wt_txn_oldest_id(WT_SESSION_IMPL *session)
}
/*
- * __wt_txn_committed --
- * Return if a transaction has been committed.
- */
-static inline bool
-__wt_txn_committed(WT_SESSION_IMPL *session, uint64_t id)
-{
- return (WT_TXNID_LT(id, S2C(session)->txn_global.last_running));
-}
-
-/*
* __wt_txn_visible_all --
* Check if a given transaction ID is "globally visible". This is, if
* all sessions in the system will see the transaction ID including the
diff --git a/src/third_party/wiredtiger/src/include/wiredtiger.in b/src/third_party/wiredtiger/src/include/wiredtiger.in
index 821efdf5fa1..30bff37017a 100644
--- a/src/third_party/wiredtiger/src/include/wiredtiger.in
+++ b/src/third_party/wiredtiger/src/include/wiredtiger.in
@@ -2458,6 +2458,8 @@ struct __wt_connection {
* readonly for more information., a boolean flag; default \c false.}
* @config{session_max, maximum expected number of sessions (including server
* threads)., an integer greater than or equal to 1; default \c 100.}
+ * @config{session_table_cache, Maintain a per-session cache of tables., a
+ * boolean flag; default \c true.}
* @config{shared_cache = (, shared cache configuration options. A database
* should configure either a cache_size or a shared_cache not both. Enabling a
* shared cache uses a session from the configured session_max., a set of
@@ -4806,72 +4808,94 @@ extern int wiredtiger_extension_terminate(WT_CONNECTION *connection);
#define WT_STAT_CONN_APPLICATION_EVICT_TIME 1216
/*! thread-yield: application thread time waiting for cache (usecs) */
#define WT_STAT_CONN_APPLICATION_CACHE_TIME 1217
+/*!
+ * thread-yield: connection close blocked waiting for transaction state
+ * stabilization
+ */
+#define WT_STAT_CONN_TXN_RELEASE_BLOCKED 1218
+/*! thread-yield: connection close yielded for lsm manager shutdown */
+#define WT_STAT_CONN_CONN_CLOSE_BLOCKED_LSM 1219
+/*! thread-yield: data handle lock yielded */
+#define WT_STAT_CONN_DHANDLE_LOCK_BLOCKED 1220
+/*! thread-yield: log server sync yielded for log write */
+#define WT_STAT_CONN_LOG_SERVER_SYNC_BLOCKED 1221
/*! thread-yield: page acquire busy blocked */
-#define WT_STAT_CONN_PAGE_BUSY_BLOCKED 1218
+#define WT_STAT_CONN_PAGE_BUSY_BLOCKED 1222
/*! thread-yield: page acquire eviction blocked */
-#define WT_STAT_CONN_PAGE_FORCIBLE_EVICT_BLOCKED 1219
+#define WT_STAT_CONN_PAGE_FORCIBLE_EVICT_BLOCKED 1223
/*! thread-yield: page acquire locked blocked */
-#define WT_STAT_CONN_PAGE_LOCKED_BLOCKED 1220
+#define WT_STAT_CONN_PAGE_LOCKED_BLOCKED 1224
/*! thread-yield: page acquire read blocked */
-#define WT_STAT_CONN_PAGE_READ_BLOCKED 1221
+#define WT_STAT_CONN_PAGE_READ_BLOCKED 1225
/*! thread-yield: page acquire time sleeping (usecs) */
-#define WT_STAT_CONN_PAGE_SLEEP 1222
+#define WT_STAT_CONN_PAGE_SLEEP 1226
+/*! thread-yield: page delete rollback yielded for instantiation */
+#define WT_STAT_CONN_PAGE_DEL_ROLLBACK_BLOCKED 1227
+/*! thread-yield: page reconciliation yielded due to child modification */
+#define WT_STAT_CONN_CHILD_MODIFY_BLOCKED_PAGE 1228
+/*! thread-yield: reference for page index and slot yielded */
+#define WT_STAT_CONN_PAGE_INDEX_SLOT_BLOCKED 1229
+/*!
+ * thread-yield: tree descend one level yielded for split page index
+ * update
+ */
+#define WT_STAT_CONN_TREE_DESCEND_BLOCKED 1230
/*! transaction: number of named snapshots created */
-#define WT_STAT_CONN_TXN_SNAPSHOTS_CREATED 1223
+#define WT_STAT_CONN_TXN_SNAPSHOTS_CREATED 1231
/*! transaction: number of named snapshots dropped */
-#define WT_STAT_CONN_TXN_SNAPSHOTS_DROPPED 1224
+#define WT_STAT_CONN_TXN_SNAPSHOTS_DROPPED 1232
/*! transaction: transaction begins */
-#define WT_STAT_CONN_TXN_BEGIN 1225
+#define WT_STAT_CONN_TXN_BEGIN 1233
/*! transaction: transaction checkpoint currently running */
-#define WT_STAT_CONN_TXN_CHECKPOINT_RUNNING 1226
+#define WT_STAT_CONN_TXN_CHECKPOINT_RUNNING 1234
/*! transaction: transaction checkpoint generation */
-#define WT_STAT_CONN_TXN_CHECKPOINT_GENERATION 1227
+#define WT_STAT_CONN_TXN_CHECKPOINT_GENERATION 1235
/*! transaction: transaction checkpoint max time (msecs) */
-#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_MAX 1228
+#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_MAX 1236
/*! transaction: transaction checkpoint min time (msecs) */
-#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_MIN 1229
+#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_MIN 1237
/*! transaction: transaction checkpoint most recent time (msecs) */
-#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_RECENT 1230
+#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_RECENT 1238
/*! transaction: transaction checkpoint scrub dirty target */
-#define WT_STAT_CONN_TXN_CHECKPOINT_SCRUB_TARGET 1231
+#define WT_STAT_CONN_TXN_CHECKPOINT_SCRUB_TARGET 1239
/*! transaction: transaction checkpoint scrub time (msecs) */
-#define WT_STAT_CONN_TXN_CHECKPOINT_SCRUB_TIME 1232
+#define WT_STAT_CONN_TXN_CHECKPOINT_SCRUB_TIME 1240
/*! transaction: transaction checkpoint total time (msecs) */
-#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_TOTAL 1233
+#define WT_STAT_CONN_TXN_CHECKPOINT_TIME_TOTAL 1241
/*! transaction: transaction checkpoints */
-#define WT_STAT_CONN_TXN_CHECKPOINT 1234
+#define WT_STAT_CONN_TXN_CHECKPOINT 1242
/*!
* transaction: transaction checkpoints skipped because database was
* clean
*/
-#define WT_STAT_CONN_TXN_CHECKPOINT_SKIPPED 1235
+#define WT_STAT_CONN_TXN_CHECKPOINT_SKIPPED 1243
/*! transaction: transaction failures due to cache overflow */
-#define WT_STAT_CONN_TXN_FAIL_CACHE 1236
+#define WT_STAT_CONN_TXN_FAIL_CACHE 1244
/*!
* transaction: transaction fsync calls for checkpoint after allocating
* the transaction ID
*/
-#define WT_STAT_CONN_TXN_CHECKPOINT_FSYNC_POST 1237
+#define WT_STAT_CONN_TXN_CHECKPOINT_FSYNC_POST 1245
/*!
* transaction: transaction fsync duration for checkpoint after
* allocating the transaction ID (usecs)
*/
-#define WT_STAT_CONN_TXN_CHECKPOINT_FSYNC_POST_DURATION 1238
+#define WT_STAT_CONN_TXN_CHECKPOINT_FSYNC_POST_DURATION 1246
/*! transaction: transaction range of IDs currently pinned */
-#define WT_STAT_CONN_TXN_PINNED_RANGE 1239
+#define WT_STAT_CONN_TXN_PINNED_RANGE 1247
/*! transaction: transaction range of IDs currently pinned by a checkpoint */
-#define WT_STAT_CONN_TXN_PINNED_CHECKPOINT_RANGE 1240
+#define WT_STAT_CONN_TXN_PINNED_CHECKPOINT_RANGE 1248
/*!
* transaction: transaction range of IDs currently pinned by named
* snapshots
*/
-#define WT_STAT_CONN_TXN_PINNED_SNAPSHOT_RANGE 1241
+#define WT_STAT_CONN_TXN_PINNED_SNAPSHOT_RANGE 1249
/*! transaction: transaction sync calls */
-#define WT_STAT_CONN_TXN_SYNC 1242
+#define WT_STAT_CONN_TXN_SYNC 1250
/*! transaction: transactions committed */
-#define WT_STAT_CONN_TXN_COMMIT 1243
+#define WT_STAT_CONN_TXN_COMMIT 1251
/*! transaction: transactions rolled back */
-#define WT_STAT_CONN_TXN_ROLLBACK 1244
+#define WT_STAT_CONN_TXN_ROLLBACK 1252
/*!
* @}
diff --git a/src/third_party/wiredtiger/src/log/log.c b/src/third_party/wiredtiger/src/log/log.c
index 803d3e8dfab..da179400755 100644
--- a/src/third_party/wiredtiger/src/log/log.c
+++ b/src/third_party/wiredtiger/src/log/log.c
@@ -8,6 +8,7 @@
#include "wt_internal.h"
+static int __log_newfile(WT_SESSION_IMPL *, bool, bool *);
static int __log_openfile(
WT_SESSION_IMPL *, WT_FH **, const char *, uint32_t, uint32_t);
static int __log_write_internal(
@@ -442,6 +443,59 @@ __wt_log_extract_lognum(
}
/*
+ * __wt_log_reset --
+ * Reset the existing log file to after the given file number.
+ * Called from recovery when toggling logging back on, it was off
+ * the previous open but it was on earlier before that toggle.
+ */
+int
+__wt_log_reset(WT_SESSION_IMPL *session, uint32_t lognum)
+{
+ WT_CONNECTION_IMPL *conn;
+ WT_DECL_RET;
+ WT_LOG *log;
+ uint32_t old_lognum;
+ u_int i, logcount;
+ char **logfiles;
+
+ conn = S2C(session);
+ log = conn->log;
+
+ if (!FLD_ISSET(conn->log_flags, WT_CONN_LOG_ENABLED) ||
+ log->fileid > lognum)
+ return (0);
+
+ WT_ASSERT(session, F_ISSET(conn, WT_CONN_RECOVERING));
+ WT_ASSERT(session, !F_ISSET(conn, WT_CONN_READONLY));
+ /*
+ * We know we're single threaded and called from recovery only when
+ * toggling logging back on. Therefore the only log files we have are
+ * old and outdated and the new one created when logging opened before
+ * recovery. We have to remove all old log files first and then create
+ * the new one so that log file numbers are contiguous in the file
+ * system.
+ */
+ WT_RET(__wt_close(session, &log->log_fh));
+ WT_RET(__log_get_files(session,
+ WT_LOG_FILENAME, &logfiles, &logcount));
+ for (i = 0; i < logcount; i++) {
+ WT_ERR(__wt_log_extract_lognum(
+ session, logfiles[i], &old_lognum));
+ WT_ASSERT(session, old_lognum < lognum || lognum == 1);
+ WT_ERR(__wt_log_remove(session, WT_LOG_FILENAME, old_lognum));
+ }
+ log->fileid = lognum;
+
+ /* Send in true to update connection creation LSNs. */
+ WT_WITH_SLOT_LOCK(session, log,
+ ret = __log_newfile(session, true, NULL));
+ WT_ERR(__wt_log_slot_init(session, false));
+err: WT_TRET(
+ __wt_fs_directory_list_free(session, &logfiles, logcount));
+ return (ret);
+}
+
+/*
* __log_zero --
* Zero a log file.
*/
@@ -1611,11 +1665,6 @@ __wt_log_scan(WT_SESSION_IMPL *session, WT_LSN *lsnp, uint32_t flags,
if (func == NULL)
return (0);
- if (LF_ISSET(WT_LOGSCAN_RECOVER))
- __wt_verbose(session, WT_VERB_LOG,
- "__wt_log_scan truncating to %" PRIu32 "/%" PRIu32,
- log->trunc_lsn.l.file, log->trunc_lsn.l.offset);
-
if (log != NULL) {
allocsize = log->allocsize;
@@ -1709,9 +1758,14 @@ advance:
/*
* Truncate this log file before we move to the next.
*/
- if (LF_ISSET(WT_LOGSCAN_RECOVER))
+ if (LF_ISSET(WT_LOGSCAN_RECOVER) &&
+ __wt_log_cmp(&rd_lsn, &log->trunc_lsn) < 0) {
+ __wt_verbose(session, WT_VERB_LOG,
+ "Truncate end of log %" PRIu32 "/%" PRIu32,
+ rd_lsn.l.file, rd_lsn.l.offset);
WT_ERR(__log_truncate(session,
&rd_lsn, WT_LOG_FILENAME, 1));
+ }
/*
* If we had a partial record, we'll want to break
* now after closing and truncating. Although for now
diff --git a/src/third_party/wiredtiger/src/log/log_slot.c b/src/third_party/wiredtiger/src/log/log_slot.c
index 97e317ce68c..b23d589c8e2 100644
--- a/src/third_party/wiredtiger/src/log/log_slot.c
+++ b/src/third_party/wiredtiger/src/log/log_slot.c
@@ -401,7 +401,7 @@ __wt_log_slot_switch(WT_SESSION_IMPL *session,
* Initialize the slot array.
*/
int
-__wt_log_slot_init(WT_SESSION_IMPL *session)
+__wt_log_slot_init(WT_SESSION_IMPL *session, bool alloc)
{
WT_CONNECTION_IMPL *conn;
WT_DECL_RET;
@@ -423,15 +423,17 @@ __wt_log_slot_init(WT_SESSION_IMPL *session)
* switch log files very aggressively. Scale back the buffer for
* small log file sizes.
*/
- log->slot_buf_size = (uint32_t)WT_MIN(
- (size_t)conn->log_file_max / 10, WT_LOG_SLOT_BUF_SIZE);
- for (i = 0; i < WT_SLOT_POOL; i++) {
- WT_ERR(__wt_buf_init(session,
- &log->slot_pool[i].slot_buf, log->slot_buf_size));
- F_SET(&log->slot_pool[i], WT_SLOT_INIT_FLAGS);
+ if (alloc) {
+ log->slot_buf_size = (uint32_t)WT_MIN(
+ (size_t)conn->log_file_max / 10, WT_LOG_SLOT_BUF_SIZE);
+ for (i = 0; i < WT_SLOT_POOL; i++) {
+ WT_ERR(__wt_buf_init(session,
+ &log->slot_pool[i].slot_buf, log->slot_buf_size));
+ F_SET(&log->slot_pool[i], WT_SLOT_INIT_FLAGS);
+ }
+ WT_STAT_CONN_SET(session,
+ log_buffer_size, log->slot_buf_size * WT_SLOT_POOL);
}
- WT_STAT_CONN_SET(session,
- log_buffer_size, log->slot_buf_size * WT_SLOT_POOL);
/*
* Set up the available slot from the pool the first time.
*/
diff --git a/src/third_party/wiredtiger/src/lsm/lsm_manager.c b/src/third_party/wiredtiger/src/lsm/lsm_manager.c
index b7d9086d10e..62da094b5f7 100644
--- a/src/third_party/wiredtiger/src/lsm/lsm_manager.c
+++ b/src/third_party/wiredtiger/src/lsm/lsm_manager.c
@@ -295,8 +295,10 @@ __wt_lsm_manager_destroy(WT_SESSION_IMPL *session)
manager->lsm_workers == 0);
if (manager->lsm_workers > 0) {
/* Wait for the main LSM manager thread to finish. */
- while (!F_ISSET(manager, WT_LSM_MANAGER_SHUTDOWN))
+ while (!F_ISSET(manager, WT_LSM_MANAGER_SHUTDOWN)) {
+ WT_STAT_CONN_INCR(session, conn_close_blocked_lsm);
__wt_yield();
+ }
/* Clean up open LSM handles. */
ret = __wt_lsm_tree_close_all(session);
diff --git a/src/third_party/wiredtiger/src/meta/meta_table.c b/src/third_party/wiredtiger/src/meta/meta_table.c
index aca69d0e6a2..a970694b9d7 100644
--- a/src/third_party/wiredtiger/src/meta/meta_table.c
+++ b/src/third_party/wiredtiger/src/meta/meta_table.c
@@ -230,12 +230,23 @@ __wt_metadata_remove(WT_SESSION_IMPL *session, const char *key)
WT_RET_MSG(session, EINVAL,
"%s: remove not supported on the turtle file", key);
+ /*
+ * Take, release, and reacquire the metadata cursor. It's complicated,
+ * but that way the underlying meta-tracking function doesn't have to
+ * open a second metadata cursor, it can use the session's cached one.
+ */
WT_RET(__wt_metadata_cursor(session, &cursor));
cursor->set_key(cursor, key);
WT_ERR(cursor->search(cursor));
+ WT_ERR(__wt_metadata_cursor_release(session, &cursor));
+
if (WT_META_TRACKING(session))
WT_ERR(__wt_meta_track_update(session, key));
- WT_ERR(cursor->remove(cursor));
+
+ WT_ERR(__wt_metadata_cursor(session, &cursor));
+ cursor->set_key(cursor, key);
+ ret = cursor->remove(cursor);
+
err: WT_TRET(__wt_metadata_cursor_release(session, &cursor));
return (ret);
}
@@ -266,7 +277,9 @@ __wt_metadata_search(WT_SESSION_IMPL *session, const char *key, char **valuep)
* that Coverity complains a lot, add an error check to get some
* peace and quiet.
*/
- if ((ret = __wt_turtle_read(session, key, valuep)) != 0)
+ WT_WITH_TURTLE_LOCK(session,
+ ret = __wt_turtle_read(session, key, valuep));
+ if (ret != 0)
__wt_free(session, *valuep);
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/meta/meta_turtle.c b/src/third_party/wiredtiger/src/meta/meta_turtle.c
index 5a089471059..f7ea6fe99c5 100644
--- a/src/third_party/wiredtiger/src/meta/meta_turtle.c
+++ b/src/third_party/wiredtiger/src/meta/meta_turtle.c
@@ -246,6 +246,9 @@ __wt_turtle_read(WT_SESSION_IMPL *session, const char *key, char **valuep)
*valuep = NULL;
+ /* Require single-threading. */
+ WT_ASSERT(session, F_ISSET(session, WT_SESSION_LOCKED_TURTLE));
+
/*
* Open the turtle file; there's one case where we won't find the turtle
* file, yet still succeed. We create the metadata file before creating
@@ -302,6 +305,9 @@ __wt_turtle_update(WT_SESSION_IMPL *session, const char *key, const char *value)
fs = NULL;
+ /* Require single-threading. */
+ WT_ASSERT(session, F_ISSET(session, WT_SESSION_LOCKED_TURTLE));
+
/*
* Create the turtle setup file: we currently re-write it from scratch
* every time.
diff --git a/src/third_party/wiredtiger/src/os_posix/os_mtx_cond.c b/src/third_party/wiredtiger/src/os_posix/os_mtx_cond.c
index fe010b62305..e4a6683dee9 100644
--- a/src/third_party/wiredtiger/src/os_posix/os_mtx_cond.c
+++ b/src/third_party/wiredtiger/src/os_posix/os_mtx_cond.c
@@ -19,11 +19,19 @@ __wt_cond_alloc(WT_SESSION_IMPL *session, const char *name, WT_CONDVAR **condp)
WT_DECL_RET;
WT_RET(__wt_calloc_one(session, &cond));
-
WT_ERR(pthread_mutex_init(&cond->mtx, NULL));
- /* Initialize the condition variable to permit self-blocking. */
+#ifdef HAVE_PTHREAD_COND_MONOTONIC
+ {
+ pthread_condattr_t condattr;
+
+ WT_ERR(pthread_condattr_init(&condattr));
+ WT_ERR(pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC));
+ WT_ERR(pthread_cond_init(&cond->cond, &condattr));
+ }
+#else
WT_ERR(pthread_cond_init(&cond->cond, NULL));
+#endif
cond->name = name;
cond->waiters = 0;
@@ -79,7 +87,26 @@ __wt_cond_wait_signal(WT_SESSION_IMPL *session, WT_CONDVAR *cond,
goto skipping;
if (usecs > 0) {
- __wt_epoch(session, &ts);
+ /*
+ * Get the current time as the basis for calculating when the
+ * wait should end. Prefer a monotonic clock source to avoid
+ * unexpectedly long sleeps when the system clock is adjusted.
+ *
+ * Failing that, query the time directly and don't attempt to
+ * correct for the clock moving backwards, which would result
+ * in a sleep that is too long by however much the clock is
+ * updated. This isn't as good as a monotonic clock source but
+ * makes the window of vulnerability smaller (i.e., the
+ * calculated time is only incorrect if the system clock
+ * changes in between us querying it and waiting).
+ */
+#ifdef HAVE_PTHREAD_COND_MONOTONIC
+ WT_SYSCALL_RETRY(clock_gettime(CLOCK_MONOTONIC, &ts), ret);
+ if (ret != 0)
+ WT_PANIC_MSG(session, ret, "clock_gettime");
+#else
+ __wt_epoch_raw(session, &ts);
+#endif
ts.tv_sec += (time_t)
(((uint64_t)ts.tv_nsec + WT_THOUSAND * usecs) / WT_BILLION);
ts.tv_nsec = (long)
diff --git a/src/third_party/wiredtiger/src/os_posix/os_time.c b/src/third_party/wiredtiger/src/os_posix/os_time.c
index fe337fea7cf..25a08d62355 100644
--- a/src/third_party/wiredtiger/src/os_posix/os_time.c
+++ b/src/third_party/wiredtiger/src/os_posix/os_time.c
@@ -9,14 +9,12 @@
#include "wt_internal.h"
/*
- * __wt_epoch --
- * Return the time since the Epoch.
+ * __wt_epoch_raw --
+ * Return the time since the Epoch as reported by a system call.
*/
void
-__wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp)
- WT_GCC_FUNC_ATTRIBUTE((visibility("default")))
+__wt_epoch_raw(WT_SESSION_IMPL *session, struct timespec *tsp)
{
- struct timespec tmp;
WT_DECL_RET;
/*
@@ -28,19 +26,10 @@ __wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp)
tsp->tv_sec = 0;
tsp->tv_nsec = 0;
- /*
- * Read into a local variable so that we're comparing the correct
- * value when we check for monotonic increasing time. There are
- * many places we read into an unlocked global variable.
- */
#if defined(HAVE_CLOCK_GETTIME)
- WT_SYSCALL_RETRY(clock_gettime(CLOCK_REALTIME, &tmp), ret);
- if (ret == 0) {
- __wt_time_check_monotonic(session, &tmp);
- tsp->tv_sec = tmp.tv_sec;
- tsp->tv_nsec = tmp.tv_nsec;
+ WT_SYSCALL_RETRY(clock_gettime(CLOCK_REALTIME, tsp), ret);
+ if (ret == 0)
return;
- }
WT_PANIC_MSG(session, ret, "clock_gettime");
#elif defined(HAVE_GETTIMEOFDAY)
{
@@ -48,10 +37,8 @@ __wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp)
WT_SYSCALL_RETRY(gettimeofday(&v, NULL), ret);
if (ret == 0) {
- tmp.tv_sec = v.tv_sec;
- tmp.tv_nsec = v.tv_usec * WT_THOUSAND;
- __wt_time_check_monotonic(session, &tmp);
- *tsp = tmp;
+ tsp->tv_sec = v.tv_sec;
+ tsp->tv_nsec = v.tv_usec * WT_THOUSAND;
return;
}
WT_PANIC_MSG(session, ret, "gettimeofday");
diff --git a/src/third_party/wiredtiger/src/os_win/os_time.c b/src/third_party/wiredtiger/src/os_win/os_time.c
index ba71341ab22..84c06bed6e5 100644
--- a/src/third_party/wiredtiger/src/os_win/os_time.c
+++ b/src/third_party/wiredtiger/src/os_win/os_time.c
@@ -9,24 +9,23 @@
#include "wt_internal.h"
/*
- * __wt_epoch --
- * Return the time since the Epoch.
+ * __wt_epoch_raw --
+ * Return the time since the Epoch as reported by the system.
*/
void
-__wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp)
+__wt_epoch_raw(WT_SESSION_IMPL *session, struct timespec *tsp)
{
- struct timespec tmp;
FILETIME time;
uint64_t ns100;
+ WT_UNUSED(session);
+
GetSystemTimeAsFileTime(&time);
ns100 = (((int64_t)time.dwHighDateTime << 32) + time.dwLowDateTime)
- 116444736000000000LL;
- tmp.tv_sec = ns100 / 10000000;
- tmp.tv_nsec = (long)((ns100 % 10000000) * 100);
- __wt_time_check_monotonic(session, &tmp);
- *tsp = tmp;
+ tsp->tv_sec = ns100 / 10000000;
+ tsp->tv_nsec = (long)((ns100 % 10000000) * 100);
}
/*
diff --git a/src/third_party/wiredtiger/src/reconcile/rec_write.c b/src/third_party/wiredtiger/src/reconcile/rec_write.c
index e59d9796352..688efa10398 100644
--- a/src/third_party/wiredtiger/src/reconcile/rec_write.c
+++ b/src/third_party/wiredtiger/src/reconcile/rec_write.c
@@ -45,12 +45,14 @@ typedef struct {
uint64_t orig_btree_checkpoint_gen;
uint64_t orig_txn_checkpoint_gen;
- /* Track the page's maximum transaction ID. */
+ /* Track the oldest transaction running when reconciliation starts. */
+ uint64_t last_running;
+
uint64_t max_txn;
- /* Track if all updates were skipped. */
- uint64_t update_cnt;
- uint64_t update_skip_cnt;
+ uint64_t update_mem_all; /* Total update memory size */
+ uint64_t update_mem_saved; /* Saved update memory size */
+ uint64_t update_mem_uncommitted;/* Uncommitted update memory size */
/*
* When we can't mark the page clean (for example, checkpoint found some
@@ -338,7 +340,8 @@ static int __rec_split_write(WT_SESSION_IMPL *,
WT_RECONCILE *, WT_BOUNDARY *, WT_ITEM *, bool);
static int __rec_update_las(
WT_SESSION_IMPL *, WT_RECONCILE *, uint32_t, WT_BOUNDARY *);
-static int __rec_write_check_complete(WT_SESSION_IMPL *, WT_RECONCILE *);
+static int __rec_write_check_complete(
+ WT_SESSION_IMPL *, WT_RECONCILE *, bool *);
static int __rec_write_init(WT_SESSION_IMPL *,
WT_REF *, uint32_t, WT_SALVAGE_COOKIE *, void *);
static void __rec_write_page_status(WT_SESSION_IMPL *, WT_RECONCILE *);
@@ -437,7 +440,7 @@ __wt_reconcile(WT_SESSION_IMPL *session, WT_REF *ref,
/* Checks for a successful reconciliation. */
if (ret == 0)
- ret = __rec_write_check_complete(session, r);
+ ret = __rec_write_check_complete(session, r, lookaside_retryp);
/* Wrap up the page reconciliation. */
if (ret == 0 && (ret = __rec_write_wrapup(session, r, page)) == 0)
@@ -448,14 +451,6 @@ __wt_reconcile(WT_SESSION_IMPL *session, WT_REF *ref,
/* Release the reconciliation lock. */
WT_PAGE_UNLOCK(session, page);
- /*
- * If our caller can configure lookaside table reconciliation, flag if
- * that's worth trying. The lookaside table doesn't help if we skipped
- * updates, it can only help with older readers preventing eviction.
- */
- if (lookaside_retryp != NULL && r->update_cnt == r->update_skip_cnt)
- *lookaside_retryp = true;
-
/* Update statistics. */
WT_STAT_CONN_INCR(session, rec_pages);
WT_STAT_DATA_INCR(session, rec_pages);
@@ -558,13 +553,21 @@ __rec_las_checkpoint_test(WT_SESSION_IMPL *session, WT_RECONCILE *r)
/*
* __rec_write_check_complete --
- * Check that reconciliation should complete
+ * Check that reconciliation should complete.
*/
static int
-__rec_write_check_complete(WT_SESSION_IMPL *session, WT_RECONCILE *r)
+__rec_write_check_complete(
+ WT_SESSION_IMPL *session, WT_RECONCILE *r, bool *lookaside_retryp)
{
- WT_BOUNDARY *bnd;
- size_t i;
+ /*
+ * Tests in this function are lookaside tests and tests to decide if
+ * rewriting a page in memory is worth doing. In-memory configurations
+ * can't use a lookaside table, and we ignore page rewrite desirability
+ * checks for in-memory eviction because a small cache can force us to
+ * rewrite every possible page.
+ */
+ if (F_ISSET(r, WT_EVICT_IN_MEMORY))
+ return (0);
/*
* If we have used the lookaside table, check for a lookaside table and
@@ -574,19 +577,62 @@ __rec_write_check_complete(WT_SESSION_IMPL *session, WT_RECONCILE *r)
return (EBUSY);
/*
- * If we are doing update/restore based eviction, confirm part of the
- * page is being discarded, or at least 10% of the updates won't have
- * to be re-instantiated. Otherwise, it isn't progress, don't bother.
+ * Eviction can configure lookaside table reconciliation, consider if
+ * it's worth giving up this reconciliation attempt and falling back to
+ * using the lookaside table. We continue with evict/restore if
+ * switching to the lookaside doesn't make sense for any reason: we
+ * won't retry an evict/restore reconciliation until/unless the
+ * transactional system moves forward, so at worst it's a single wasted
+ * effort.
+ *
+ * First, check if the lookaside table is a possible alternative.
*/
- if (F_ISSET(r, WT_EVICT_UPDATE_RESTORE)) {
- for (bnd = r->bnd, i = 0; i < r->bnd_entries; ++bnd, ++i)
- if (bnd->supd == NULL)
- break;
- if (i == r->bnd_entries &&
- r->update_cnt / 10 >= r->update_skip_cnt)
- return (EBUSY);
- }
- return (0);
+ if (lookaside_retryp == NULL)
+ return (0);
+
+ /*
+ * We only suggest lookaside if currently in an evict/restore attempt
+ * and some updates were saved. Our caller sets the evict/restore flag
+ * based on various conditions (like if this is a leaf page), which is
+ * why we're testing that flag instead of a set of other conditions.
+ * If no updates were saved, eviction will succeed without needing to
+ * restore anything.
+ */
+ if (!F_ISSET(r, WT_EVICT_UPDATE_RESTORE) || r->bnd->supd == NULL)
+ return (0);
+
+ /*
+ * Check if this reconciliation attempt is making progress. If there's
+ * any sign of progress, don't fall back to the lookaside table.
+ *
+ * Check if the current reconciliation split, in which case we'll
+ * likely get to write at least one of the blocks. If that page is
+ * empty, that's also progress.
+ */
+ if (r->bnd_next != 1)
+ return (0);
+
+ /*
+ * Check if the current reconciliation applied some updates, in which
+ * case evict/restore should gain us some space.
+ */
+ if (r->update_mem_saved != r->update_mem_all)
+ return (0);
+
+ /*
+ * Check if lookaside eviction is possible. If any of the updates we
+ * saw were uncommitted, the lookaside table cannot be used: it only
+ * helps with older readers preventing eviction.
+ */
+ if (r->update_mem_uncommitted != 0)
+ return (0);
+
+ /*
+ * The current evict/restore approach shows no signs of being useful,
+ * lookaside is possible, suggest the lookaside table.
+ */
+ *lookaside_retryp = true;
+ return (EBUSY);
}
/*
@@ -849,6 +895,16 @@ __rec_write_init(WT_SESSION_IMPL *session,
WT_ORDERED_READ(r->orig_write_gen, page->modify->write_gen);
/*
+ * Cache the oldest running transaction ID. This is used to check
+ * whether updates seen by reconciliation have committed. We keep a
+ * cached copy to avoid races where a concurrent transaction could
+ * abort while reconciliation is examining its updates. This way, any
+ * transaction running when reconciliation starts is considered
+ * uncommitted.
+ */
+ WT_ORDERED_READ(r->last_running, S2C(session)->txn_global.last_running);
+
+ /*
* Lookaside table eviction is configured when eviction gets aggressive,
* adjust the flags for cases we don't support.
*/
@@ -891,7 +947,7 @@ __rec_write_init(WT_SESSION_IMPL *session,
r->max_txn = WT_TXN_NONE;
/* Track if all updates were skipped. */
- r->update_cnt = r->update_skip_cnt = 0;
+ r->update_mem_all = r->update_mem_saved = r->update_mem_uncommitted = 0;
/* Track if the page can be marked clean. */
r->leave_dirty = false;
@@ -1115,7 +1171,7 @@ __rec_txn_read(WT_SESSION_IMPL *session, WT_RECONCILE *r,
WT_DECL_ITEM(tmp);
WT_PAGE *page;
WT_UPDATE *append, *upd, *upd_list;
- size_t notused;
+ size_t notused, update_mem;
uint64_t max_txn, min_txn, txnid;
bool append_origv, skipped;
@@ -1136,36 +1192,64 @@ __rec_txn_read(WT_SESSION_IMPL *session, WT_RECONCILE *r,
} else
upd_list = ins->upd;
- ++r->update_cnt;
- for (skipped = false,
- max_txn = WT_TXN_NONE, min_txn = UINT64_MAX,
- upd = upd_list; upd != NULL; upd = upd->next) {
- if ((txnid = upd->txnid) == WT_TXN_ABORTED)
- continue;
+ skipped = false;
+ update_mem = 0;
+ max_txn = WT_TXN_NONE;
+ min_txn = UINT64_MAX;
- /* Track the largest/smallest transaction IDs on the list. */
- if (WT_TXNID_LT(max_txn, txnid))
- max_txn = txnid;
- if (WT_TXNID_LT(txnid, min_txn))
- min_txn = txnid;
+ if (F_ISSET(r, WT_EVICTING)) {
+ /* Discard obsolete updates. */
+ if ((upd = __wt_update_obsolete_check(
+ session, page, upd_list->next)) != NULL)
+ __wt_update_obsolete_free(session, page, upd);
+
+ for (upd = upd_list; upd != NULL; upd = upd->next) {
+ /* Track the total memory in the update chain. */
+ update_mem += WT_UPDATE_MEMSIZE(upd);
+
+ if ((txnid = upd->txnid) == WT_TXN_ABORTED)
+ continue;
- /*
- * Find the first update we can use.
- */
- if (F_ISSET(r, WT_EVICTING)) {
/*
+ * Track the largest/smallest transaction IDs on the
+ * list.
+ */
+ if (WT_TXNID_LT(max_txn, txnid))
+ max_txn = txnid;
+ if (WT_TXNID_LT(txnid, min_txn))
+ min_txn = txnid;
+
+ /*
+ * Find the first update we can use.
+ *
* Eviction can write any committed update.
*
* When reconciling for eviction, track whether any
* uncommitted updates are found.
+ *
+ * When reconciling for eviction, track the memory held
+ * by the update chain.
*/
- if (__wt_txn_committed(session, txnid)) {
- if (*updp == NULL)
- *updp = upd;
- } else
+ if (WT_TXNID_LE(r->last_running, txnid)) {
skipped = true;
- } else {
+ continue;
+ }
+
+ if (*updp == NULL)
+ *updp = upd;
+ }
+ } else
+ for (upd = upd_list; upd != NULL; upd = upd->next) {
+ if ((txnid = upd->txnid) == WT_TXN_ABORTED)
+ continue;
+
+ /* Track the largest transaction ID on the list. */
+ if (WT_TXNID_LT(max_txn, txnid))
+ max_txn = txnid;
+
/*
+ * Find the first update we can use.
+ *
* Checkpoint can only write updates visible as of its
* snapshot.
*
@@ -1180,7 +1264,8 @@ __rec_txn_read(WT_SESSION_IMPL *session, WT_RECONCILE *r,
skipped = true;
}
}
- }
+
+ r->update_mem_all += update_mem;
/*
* If all of the updates were aborted, quit. This test is not strictly
@@ -1227,12 +1312,6 @@ __rec_txn_read(WT_SESSION_IMPL *session, WT_RECONCILE *r,
txnid != S2C(session)->txn_global.checkpoint_txnid ||
WT_SESSION_IS_CHECKPOINT(session));
#endif
-
- /*
- * Track how many update chains we saw vs. how many update
- * chains had an entry we skipped.
- */
- ++r->update_skip_cnt;
return (0);
}
@@ -1276,6 +1355,23 @@ __rec_txn_read(WT_SESSION_IMPL *session, WT_RECONCILE *r,
if (skipped && !F_ISSET(r, WT_EVICT_UPDATE_RESTORE))
return (EBUSY);
+ /*
+ * Track the memory required by the update chain.
+ *
+ * A page with no uncommitted (skipped) updates, that can't be evicted
+ * because some updates aren't yet globally visible, can be evicted by
+ * writing previous versions of the updates to the lookaside file. That
+ * test is just checking if the skipped updates memory is zero.
+ *
+ * If that's not possible (there are skipped updates), we can rewrite
+ * the pages in-memory, but we don't want to unless there's memory to
+ * recover. That test is comparing the memory we'd recover to the memory
+ * we'd have to re-instantiate as part of the rewrite.
+ */
+ r->update_mem_saved += update_mem;
+ if (skipped)
+ r->update_mem_uncommitted += update_mem;
+
append_origv = false;
if (F_ISSET(r, WT_EVICT_UPDATE_RESTORE)) {
/*
@@ -1562,7 +1658,7 @@ __rec_child_modify(WT_SESSION_IMPL *session,
* not reserved for our exclusive use, there are other page states that
* must be considered.
*/
- for (;; __wt_yield())
+ for (;; __wt_yield()) {
switch (r->tested_ref_state = ref->state) {
case WT_REF_DISK:
/* On disk, not modified by definition. */
@@ -1673,6 +1769,8 @@ __rec_child_modify(WT_SESSION_IMPL *session,
WT_ILLEGAL_VALUE(session);
}
+ WT_STAT_CONN_INCR(session, child_modify_blocked_page);
+ }
in_memory:
/*
diff --git a/src/third_party/wiredtiger/src/schema/schema_alter.c b/src/third_party/wiredtiger/src/schema/schema_alter.c
index 26d800aa98e..edb3c6f77ae 100644
--- a/src/third_party/wiredtiger/src/schema/schema_alter.c
+++ b/src/third_party/wiredtiger/src/schema/schema_alter.c
@@ -61,13 +61,16 @@ __alter_colgroup(
{
WT_COLGROUP *colgroup;
WT_DECL_RET;
+ WT_TABLE *table;
WT_ASSERT(session, F_ISSET(session, WT_SESSION_LOCKED_TABLE));
/* If we can get the colgroup, perform any potential alterations. */
if ((ret = __wt_schema_get_colgroup(
- session, uri, false, NULL, &colgroup)) == 0)
+ session, uri, false, &table, &colgroup)) == 0) {
WT_TRET(__wt_schema_alter(session, colgroup->source, cfg));
+ WT_TRET(__wt_schema_release_table(session, table));
+ }
return (ret);
}
@@ -82,11 +85,14 @@ __alter_index(
{
WT_INDEX *idx;
WT_DECL_RET;
+ WT_TABLE *table;
/* If we can get the index, perform any potential alterations. */
if ((ret = __wt_schema_get_index(
- session, uri, false, NULL, &idx)) == 0)
+ session, uri, false, &table, &idx)) == 0) {
WT_TRET(__wt_schema_alter(session, idx->source, cfg));
+ WT_TRET(__wt_schema_release_table(session, table));
+ }
return (ret);
}
@@ -127,7 +133,8 @@ __alter_table(WT_SESSION_IMPL *session, const char *uri, const char *cfg[])
WT_ERR(__wt_schema_alter(
session, colgroup->source, cfg));
}
-err: __wt_schema_release_table(session, table);
+
+err: WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/schema/schema_create.c b/src/third_party/wiredtiger/src/schema/schema_create.c
index 0677fa711a5..49c0274994c 100644
--- a/src/third_party/wiredtiger/src/schema/schema_create.c
+++ b/src/third_party/wiredtiger/src/schema/schema_create.c
@@ -273,7 +273,7 @@ err: __wt_free(session, cgconf);
__wt_buf_free(session, &fmt);
__wt_buf_free(session, &namebuf);
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
@@ -540,7 +540,7 @@ err: __wt_free(session, idxconf);
__wt_buf_free(session, &fmt);
__wt_buf_free(session, &namebuf);
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
@@ -615,7 +615,7 @@ err: if (table != NULL) {
}
}
if (table != NULL)
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
__wt_free(session, cgname);
__wt_free(session, tableconf);
return (ret);
diff --git a/src/third_party/wiredtiger/src/schema/schema_drop.c b/src/third_party/wiredtiger/src/schema/schema_drop.c
index 49801e4e5f9..18817c4fa2d 100644
--- a/src/third_party/wiredtiger/src/schema/schema_drop.c
+++ b/src/third_party/wiredtiger/src/schema/schema_drop.c
@@ -67,6 +67,7 @@ __drop_colgroup(
session, uri, force, &table, &colgroup)) == 0) {
table->cg_complete = false;
WT_TRET(__wt_schema_drop(session, colgroup->source, cfg));
+ WT_TRET(__wt_schema_release_table(session, table));
}
WT_TRET(__wt_metadata_remove(session, uri));
@@ -90,6 +91,7 @@ __drop_index(
session, uri, force, &table, &idx)) == 0) {
table->idx_complete = false;
WT_TRET(__wt_schema_drop(session, idx->source, cfg));
+ WT_TRET(__wt_schema_release_table(session, table));
}
WT_TRET(__wt_metadata_remove(session, uri));
@@ -151,7 +153,7 @@ __drop_table(WT_SESSION_IMPL *session, const char *uri, const char *cfg[])
WT_ERR(__wt_metadata_remove(session, uri));
err: if (table != NULL)
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/schema/schema_list.c b/src/third_party/wiredtiger/src/schema/schema_list.c
index 74ef5135a4a..434038c6bc8 100644
--- a/src/third_party/wiredtiger/src/schema/schema_list.c
+++ b/src/third_party/wiredtiger/src/schema/schema_list.c
@@ -30,9 +30,12 @@ __schema_add_table(WT_SESSION_IMPL *session,
session, name, namelen, ok_incomplete, &table));
WT_RET(ret);
- bucket = table->name_hash % WT_HASH_ARRAY_SIZE;
- TAILQ_INSERT_HEAD(&session->tables, table, q);
- TAILQ_INSERT_HEAD(&session->tablehash[bucket], table, hashq);
+ if (!table->is_simple || F_ISSET(S2C(session), WT_CONN_TABLE_CACHE)) {
+ bucket = table->name_hash % WT_HASH_ARRAY_SIZE;
+ TAILQ_INSERT_HEAD(&session->tables, table, q);
+ TAILQ_INSERT_HEAD(&session->tablehash[bucket], table, hashq);
+ }
+
*tablep = table;
return (0);
@@ -112,11 +115,14 @@ __wt_schema_get_table(WT_SESSION_IMPL *session,
* __wt_schema_release_table --
* Release a table handle.
*/
-void
+int
__wt_schema_release_table(WT_SESSION_IMPL *session, WT_TABLE *table)
{
WT_ASSERT(session, table->refcnt > 0);
- --table->refcnt;
+ if (--table->refcnt == 0 &&
+ table->is_simple && !F_ISSET(S2C(session), WT_CONN_TABLE_CACHE))
+ WT_RET(__wt_schema_destroy_table(session, &table));
+ return (0);
}
/*
@@ -229,9 +235,11 @@ __wt_schema_remove_table(WT_SESSION_IMPL *session, WT_TABLE *table)
uint64_t bucket;
WT_ASSERT(session, table->refcnt <= 1);
- bucket = table->name_hash % WT_HASH_ARRAY_SIZE;
- TAILQ_REMOVE(&session->tables, table, q);
- TAILQ_REMOVE(&session->tablehash[bucket], table, hashq);
+ if (!table->is_simple || F_ISSET(S2C(session), WT_CONN_TABLE_CACHE)) {
+ bucket = table->name_hash % WT_HASH_ARRAY_SIZE;
+ TAILQ_REMOVE(&session->tables, table, q);
+ TAILQ_REMOVE(&session->tablehash[bucket], table, hashq);
+ }
return (__wt_schema_destroy_table(session, &table));
}
@@ -249,3 +257,34 @@ __wt_schema_close_tables(WT_SESSION_IMPL *session)
WT_TRET(__wt_schema_remove_table(session, table));
return (ret);
}
+
+/*
+ * __wt_schema_sweep_tables --
+ * Close all idle, obsolete tables in a session.
+ */
+int
+__wt_schema_sweep_tables(WT_SESSION_IMPL *session)
+{
+ WT_TABLE *table, *next;
+ uint64_t schema_gen;
+ bool old_table_busy;
+
+ WT_ORDERED_READ(schema_gen, S2C(session)->schema_gen);
+ if (schema_gen == session->table_sweep_gen)
+ return (0);
+
+ old_table_busy = false;
+ TAILQ_FOREACH_SAFE(table, &session->tables, q, next)
+ if (table->schema_gen != schema_gen) {
+ if (table->refcnt == 0)
+ WT_RET(__wt_schema_remove_table(
+ session, table));
+ else
+ old_table_busy = true;
+ }
+
+ if (!old_table_busy)
+ session->table_sweep_gen = schema_gen;
+
+ return (0);
+}
diff --git a/src/third_party/wiredtiger/src/schema/schema_open.c b/src/third_party/wiredtiger/src/schema/schema_open.c
index 44bd66e011a..2df6bae45f3 100644
--- a/src/third_party/wiredtiger/src/schema/schema_open.c
+++ b/src/third_party/wiredtiger/src/schema/schema_open.c
@@ -425,37 +425,40 @@ __schema_open_table(WT_SESSION_IMPL *session,
WT_DECL_RET;
WT_TABLE *table;
const char *tconfig;
- char *tablename;
*tablep = NULL;
cursor = NULL;
table = NULL;
- tablename = NULL;
WT_ASSERT(session, F_ISSET(session, WT_SESSION_LOCKED_TABLE));
+ WT_ERR(__wt_calloc_one(session, &table));
+ table->name_hash = __wt_hash_city64(name, namelen);
+
WT_ERR(__wt_scr_alloc(session, 0, &buf));
WT_ERR(__wt_buf_fmt(session, buf, "table:%.*s", (int)namelen, name));
- WT_ERR(__wt_strndup(session, buf->data, buf->size, &tablename));
+ WT_ERR(__wt_strndup(session, buf->data, buf->size, &table->name));
+ /*
+ * Don't hold the metadata cursor pinned, we call functions that use it
+ * to retrieve column group information.
+ */
WT_ERR(__wt_metadata_cursor(session, &cursor));
- cursor->set_key(cursor, tablename);
- WT_ERR(cursor->search(cursor));
- WT_ERR(cursor->get_value(cursor, &tconfig));
-
- WT_ERR(__wt_calloc_one(session, &table));
- table->name = tablename;
- tablename = NULL;
- table->name_hash = __wt_hash_city64(name, namelen);
-
- WT_ERR(__wt_config_getones(session, tconfig, "columns", &cval));
+ cursor->set_key(cursor, table->name);
+ if ((ret = cursor->search(cursor)) == 0 &&
+ (ret = cursor->get_value(cursor, &tconfig)) == 0)
+ ret = __wt_strdup(session, tconfig, &table->config);
+ WT_TRET(__wt_metadata_cursor_release(session, &cursor));
+ WT_ERR(ret);
- WT_ERR(__wt_config_getones(session, tconfig, "key_format", &cval));
+ WT_ERR(__wt_config_getones(session, table->config, "columns", &cval));
+ WT_ERR(__wt_config_getones(
+ session, table->config, "key_format", &cval));
WT_ERR(__wt_strndup(session, cval.str, cval.len, &table->key_format));
- WT_ERR(__wt_config_getones(session, tconfig, "value_format", &cval));
+ WT_ERR(__wt_config_getones(
+ session, table->config, "value_format", &cval));
WT_ERR(__wt_strndup(session, cval.str, cval.len, &table->value_format));
- WT_ERR(__wt_strdup(session, tconfig, &table->config));
/* Point to some items in the copy to save re-parsing. */
WT_ERR(__wt_config_getones(session, table->config,
@@ -491,7 +494,7 @@ __schema_open_table(WT_SESSION_IMPL *session,
if (table->ncolgroups > 0 && table->is_simple)
WT_ERR_MSG(session, EINVAL,
- "%s requires a table with named columns", tablename);
+ "%s requires a table with named columns", table->name);
WT_ERR(__wt_calloc_def(session, WT_COLGROUPS(table), &table->cgroups));
WT_ERR(__wt_schema_open_colgroups(session, table));
@@ -509,9 +512,7 @@ __schema_open_table(WT_SESSION_IMPL *session,
if (0) {
err: WT_TRET(__wt_schema_destroy_table(session, &table));
}
- WT_TRET(__wt_metadata_cursor_release(session, &cursor));
- __wt_free(session, tablename);
__wt_scr_free(session, &buf);
return (ret);
}
@@ -529,8 +530,8 @@ __wt_schema_get_colgroup(WT_SESSION_IMPL *session,
const char *tablename, *tend;
u_int i;
- if (tablep != NULL)
- *tablep = NULL;
+ WT_ASSERT(session, tablep != NULL);
+ *tablep = NULL;
*colgroupp = NULL;
tablename = uri;
@@ -547,15 +548,12 @@ __wt_schema_get_colgroup(WT_SESSION_IMPL *session,
colgroup = table->cgroups[i];
if (strcmp(colgroup->name, uri) == 0) {
*colgroupp = colgroup;
- if (tablep != NULL)
- *tablep = table;
- else
- __wt_schema_release_table(session, table);
+ *tablep = table;
return (0);
}
}
- __wt_schema_release_table(session, table);
+ WT_RET(__wt_schema_release_table(session, table));
if (quiet)
WT_RET(ENOENT);
WT_RET_MSG(session, ENOENT, "%s not found in table", uri);
@@ -575,8 +573,8 @@ __wt_schema_get_index(WT_SESSION_IMPL *session,
const char *tablename, *tend;
u_int i;
- if (tablep != NULL)
- *tablep = NULL;
+ WT_ASSERT(session, tablep != NULL);
+ *tablep = NULL;
*indexp = NULL;
tablename = uri;
@@ -591,11 +589,8 @@ __wt_schema_get_index(WT_SESSION_IMPL *session,
for (i = 0; i < table->nindices; i++) {
idx = table->indices[i];
if (idx != NULL && strcmp(idx->name, uri) == 0) {
- if (tablep != NULL)
- *tablep = table;
- else
- __wt_schema_release_table(session, table);
*indexp = idx;
+ *tablep = table;
return (0);
}
}
@@ -603,15 +598,12 @@ __wt_schema_get_index(WT_SESSION_IMPL *session,
/* Otherwise, open it. */
WT_ERR(__wt_schema_open_index(
session, table, tend + 1, strlen(tend + 1), indexp));
- if (tablep != NULL)
- *tablep = table;
+ *tablep = table;
+ return (0);
-err: __wt_schema_release_table(session, table);
+err: WT_TRET(__wt_schema_release_table(session, table));
WT_RET(ret);
- if (*indexp != NULL)
- return (0);
-
if (quiet)
WT_RET(ENOENT);
WT_RET_MSG(session, ENOENT, "%s not found in table", uri);
diff --git a/src/third_party/wiredtiger/src/schema/schema_rename.c b/src/third_party/wiredtiger/src/schema/schema_rename.c
index a374f4c2831..9effedd2cde 100644
--- a/src/third_party/wiredtiger/src/schema/schema_rename.c
+++ b/src/third_party/wiredtiger/src/schema/schema_rename.c
@@ -233,7 +233,7 @@ __rename_table(WT_SESSION_IMPL *session,
WT_ERR(__metadata_rename(session, uri, newuri));
err: if (table != NULL)
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/schema/schema_stat.c b/src/third_party/wiredtiger/src/schema/schema_stat.c
index 345f9164e9b..9bcd2439619 100644
--- a/src/third_party/wiredtiger/src/schema/schema_stat.c
+++ b/src/third_party/wiredtiger/src/schema/schema_stat.c
@@ -19,14 +19,17 @@ __wt_curstat_colgroup_init(WT_SESSION_IMPL *session,
WT_COLGROUP *colgroup;
WT_DECL_ITEM(buf);
WT_DECL_RET;
+ WT_TABLE *table;
- WT_RET(__wt_schema_get_colgroup(session, uri, false, NULL, &colgroup));
+ WT_RET(__wt_schema_get_colgroup(
+ session, uri, false, &table, &colgroup));
WT_RET(__wt_scr_alloc(session, 0, &buf));
WT_ERR(__wt_buf_fmt(session, buf, "statistics:%s", colgroup->source));
ret = __wt_curstat_init(session, buf->data, NULL, cfg, cst);
err: __wt_scr_free(session, &buf);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
@@ -41,14 +44,16 @@ __wt_curstat_index_init(WT_SESSION_IMPL *session,
WT_DECL_ITEM(buf);
WT_DECL_RET;
WT_INDEX *idx;
+ WT_TABLE *table;
- WT_RET(__wt_schema_get_index(session, uri, false, NULL, &idx));
+ WT_RET(__wt_schema_get_index(session, uri, false, &table, &idx));
WT_RET(__wt_scr_alloc(session, 0, &buf));
WT_ERR(__wt_buf_fmt(session, buf, "statistics:%s", idx->source));
ret = __wt_curstat_init(session, buf->data, NULL, cfg, cst);
err: __wt_scr_free(session, &buf);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
@@ -184,7 +189,7 @@ __wt_curstat_table_init(WT_SESSION_IMPL *session,
__wt_curstat_dsrc_final(cst);
-err: __wt_schema_release_table(session, table);
+err: WT_TRET(__wt_schema_release_table(session, table));
__wt_scr_free(session, &buf);
return (ret);
diff --git a/src/third_party/wiredtiger/src/schema/schema_truncate.c b/src/third_party/wiredtiger/src/schema/schema_truncate.c
index 563bafa8ffc..3046305e819 100644
--- a/src/third_party/wiredtiger/src/schema/schema_truncate.c
+++ b/src/third_party/wiredtiger/src/schema/schema_truncate.c
@@ -33,7 +33,7 @@ __truncate_table(WT_SESSION_IMPL *session, const char *uri, const char *cfg[])
WT_ERR(__wt_schema_truncate(
session, table->indices[i]->source, cfg));
-err: __wt_schema_release_table(session, table);
+err: WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/schema/schema_worker.c b/src/third_party/wiredtiger/src/schema/schema_worker.c
index 62cdd7d367b..9fb68720723 100644
--- a/src/third_party/wiredtiger/src/schema/schema_worker.c
+++ b/src/third_party/wiredtiger/src/schema/schema_worker.c
@@ -64,14 +64,17 @@ __wt_schema_worker(WT_SESSION_IMPL *session,
}
} else if (WT_PREFIX_MATCH(uri, "colgroup:")) {
WT_ERR(__wt_schema_get_colgroup(
- session, uri, false, NULL, &colgroup));
+ session, uri, false, &table, &colgroup));
WT_ERR(__wt_schema_worker(session,
colgroup->source, file_func, name_func, cfg, open_flags));
+ WT_ERR(__wt_schema_release_table(session, table));
} else if (WT_PREFIX_SKIP(tablename, "index:")) {
idx = NULL;
- WT_ERR(__wt_schema_get_index(session, uri, false, NULL, &idx));
+ WT_ERR(__wt_schema_get_index(
+ session, uri, false, &table, &idx));
WT_ERR(__wt_schema_worker(session, idx->source,
file_func, name_func, cfg, open_flags));
+ WT_ERR(__wt_schema_release_table(session, table));
} else if (WT_PREFIX_MATCH(uri, "lsm:")) {
WT_ERR(__wt_lsm_tree_worker(session,
uri, file_func, name_func, cfg, open_flags));
@@ -128,6 +131,6 @@ __wt_schema_worker(WT_SESSION_IMPL *session,
WT_ERR(__wt_bad_object_type(session, uri));
err: if (table != NULL)
- __wt_schema_release_table(session, table);
+ WT_TRET(__wt_schema_release_table(session, table));
return (ret);
}
diff --git a/src/third_party/wiredtiger/src/session/session_api.c b/src/third_party/wiredtiger/src/session/session_api.c
index 89a5a2c633d..386084b78d9 100644
--- a/src/third_party/wiredtiger/src/session/session_api.c
+++ b/src/third_party/wiredtiger/src/session/session_api.c
@@ -328,6 +328,7 @@ __session_open_cursor_int(WT_SESSION_IMPL *session, const char *uri,
WT_COLGROUP *colgroup;
WT_DATA_SOURCE *dsrc;
WT_DECL_RET;
+ WT_TABLE *table;
*cursorp = NULL;
@@ -355,9 +356,10 @@ __session_open_cursor_int(WT_SESSION_IMPL *session, const char *uri,
* the underlying data source.
*/
WT_RET(__wt_schema_get_colgroup(
- session, uri, false, NULL, &colgroup));
+ session, uri, false, &table, &colgroup));
WT_RET(__wt_open_cursor(
session, colgroup->source, owner, cfg, cursorp));
+ WT_RET(__wt_schema_release_table(session, table));
} else if (WT_PREFIX_MATCH(uri, "config:"))
WT_RET(__wt_curconfig_open(
session, uri, cfg, cursorp));
@@ -818,6 +820,8 @@ __session_reset(WT_SESSION *wt_session)
WT_TRET(__wt_session_reset_cursors(session, true));
+ WT_TRET(__wt_schema_sweep_tables(session));
+
/* Release common session resources. */
WT_TRET(__wt_session_release_resources(session));
@@ -1209,12 +1213,15 @@ __wt_session_range_truncate(WT_SESSION_IMPL *session,
done:
err: /*
- * Close any locally-opened start cursor. Reset application cursors,
- * they've possibly moved and the application cannot use them.
+ * Close any locally-opened start cursor.
+ *
+ * Reset application cursors, they've possibly moved and the
+ * application cannot use them. Note that we can make it here with a
+ * NULL start cursor (e.g., if the truncate range is empty).
*/
if (local_start)
WT_TRET(start->close(start));
- else
+ else if (start != NULL)
WT_TRET(start->reset(start));
if (stop != NULL)
WT_TRET(stop->reset(stop));
diff --git a/src/third_party/wiredtiger/src/session/session_dhandle.c b/src/third_party/wiredtiger/src/session/session_dhandle.c
index ffeb6137766..707e07ac11f 100644
--- a/src/third_party/wiredtiger/src/session/session_dhandle.c
+++ b/src/third_party/wiredtiger/src/session/session_dhandle.c
@@ -235,6 +235,7 @@ __wt_session_lock_dhandle(
lock_busy = true;
/* Give other threads a chance to make progress. */
+ WT_STAT_CONN_INCR(session, dhandle_lock_blocked);
__wt_yield();
}
}
@@ -597,7 +598,9 @@ __wt_session_lock_checkpoint(WT_SESSION_IMPL *session, const char *checkpoint)
* the underlying file are visible to the in-memory pages.
*/
WT_ERR(__wt_evict_file_exclusive_on(session));
- WT_ERR(__wt_cache_op(session, WT_SYNC_DISCARD));
+ ret = __wt_cache_op(session, WT_SYNC_DISCARD);
+ __wt_evict_file_exclusive_off(session);
+ WT_ERR(ret);
/*
* We lock checkpoint handles that we are overwriting, so the handle
diff --git a/src/third_party/wiredtiger/src/support/err.c b/src/third_party/wiredtiger/src/support/err.c
index 57efde72b23..f98b1943449 100644
--- a/src/third_party/wiredtiger/src/support/err.c
+++ b/src/third_party/wiredtiger/src/support/err.c
@@ -494,7 +494,18 @@ __wt_panic(WT_SESSION_IMPL *session)
WT_GCC_FUNC_ATTRIBUTE((cold))
WT_GCC_FUNC_ATTRIBUTE((visibility("default")))
{
- F_SET(S2C(session), WT_CONN_PANIC);
+ WT_CONNECTION_IMPL *conn;
+
+ conn = S2C(session);
+
+ /*
+ * If the connection has already be marked for panic, just return the
+ * error.
+ */
+ if (F_ISSET(conn, WT_CONN_PANIC))
+ return (WT_PANIC);
+
+ F_SET(conn, WT_CONN_PANIC);
__wt_err(session, WT_PANIC, "the process must exit and restart");
#if defined(HAVE_DIAGNOSTIC)
diff --git a/src/third_party/wiredtiger/src/support/rand.c b/src/third_party/wiredtiger/src/support/rand.c
index 4fae43edc8e..a5b229b9abc 100644
--- a/src/third_party/wiredtiger/src/support/rand.c
+++ b/src/third_party/wiredtiger/src/support/rand.c
@@ -120,15 +120,3 @@ __wt_random(WT_RAND_STATE volatile * rnd_state)
return ((z << 16) + (w & 65535));
}
-
-/*
- * __wt_random64 --
- * Return a 64-bit pseudo-random number.
- */
-uint64_t
-__wt_random64(WT_RAND_STATE volatile * rnd_state)
- WT_GCC_FUNC_ATTRIBUTE((visibility("default")))
-{
- return (((uint64_t)__wt_random(rnd_state) << 32) +
- __wt_random(rnd_state));
-}
diff --git a/src/third_party/wiredtiger/src/support/stat.c b/src/third_party/wiredtiger/src/support/stat.c
index 8b72e653658..c9e577ac3b6 100644
--- a/src/third_party/wiredtiger/src/support/stat.c
+++ b/src/third_party/wiredtiger/src/support/stat.c
@@ -842,11 +842,19 @@ static const char * const __stats_connection_desc[] = {
"thread-state: active filesystem write calls",
"thread-yield: application thread time evicting (usecs)",
"thread-yield: application thread time waiting for cache (usecs)",
+ "thread-yield: connection close blocked waiting for transaction state stabilization",
+ "thread-yield: connection close yielded for lsm manager shutdown",
+ "thread-yield: data handle lock yielded",
+ "thread-yield: log server sync yielded for log write",
"thread-yield: page acquire busy blocked",
"thread-yield: page acquire eviction blocked",
"thread-yield: page acquire locked blocked",
"thread-yield: page acquire read blocked",
"thread-yield: page acquire time sleeping (usecs)",
+ "thread-yield: page delete rollback yielded for instantiation",
+ "thread-yield: page reconciliation yielded due to child modification",
+ "thread-yield: reference for page index and slot yielded",
+ "thread-yield: tree descend one level yielded for split page index update",
"transaction: number of named snapshots created",
"transaction: number of named snapshots dropped",
"transaction: transaction begins",
@@ -1129,11 +1137,19 @@ __wt_stat_connection_clear_single(WT_CONNECTION_STATS *stats)
/* not clearing thread_write_active */
stats->application_evict_time = 0;
stats->application_cache_time = 0;
+ stats->txn_release_blocked = 0;
+ stats->conn_close_blocked_lsm = 0;
+ stats->dhandle_lock_blocked = 0;
+ stats->log_server_sync_blocked = 0;
stats->page_busy_blocked = 0;
stats->page_forcible_evict_blocked = 0;
stats->page_locked_blocked = 0;
stats->page_read_blocked = 0;
stats->page_sleep = 0;
+ stats->page_del_rollback_blocked = 0;
+ stats->child_modify_blocked_page = 0;
+ stats->page_index_slot_blocked = 0;
+ stats->tree_descend_blocked = 0;
stats->txn_snapshots_created = 0;
stats->txn_snapshots_dropped = 0;
stats->txn_begin = 0;
@@ -1475,12 +1491,25 @@ __wt_stat_connection_aggregate(
WT_STAT_READ(from, application_evict_time);
to->application_cache_time +=
WT_STAT_READ(from, application_cache_time);
+ to->txn_release_blocked += WT_STAT_READ(from, txn_release_blocked);
+ to->conn_close_blocked_lsm +=
+ WT_STAT_READ(from, conn_close_blocked_lsm);
+ to->dhandle_lock_blocked += WT_STAT_READ(from, dhandle_lock_blocked);
+ to->log_server_sync_blocked +=
+ WT_STAT_READ(from, log_server_sync_blocked);
to->page_busy_blocked += WT_STAT_READ(from, page_busy_blocked);
to->page_forcible_evict_blocked +=
WT_STAT_READ(from, page_forcible_evict_blocked);
to->page_locked_blocked += WT_STAT_READ(from, page_locked_blocked);
to->page_read_blocked += WT_STAT_READ(from, page_read_blocked);
to->page_sleep += WT_STAT_READ(from, page_sleep);
+ to->page_del_rollback_blocked +=
+ WT_STAT_READ(from, page_del_rollback_blocked);
+ to->child_modify_blocked_page +=
+ WT_STAT_READ(from, child_modify_blocked_page);
+ to->page_index_slot_blocked +=
+ WT_STAT_READ(from, page_index_slot_blocked);
+ to->tree_descend_blocked += WT_STAT_READ(from, tree_descend_blocked);
to->txn_snapshots_created +=
WT_STAT_READ(from, txn_snapshots_created);
to->txn_snapshots_dropped +=
diff --git a/src/third_party/wiredtiger/src/support/time.c b/src/third_party/wiredtiger/src/support/time.c
new file mode 100644
index 00000000000..0e4562c0234
--- /dev/null
+++ b/src/third_party/wiredtiger/src/support/time.c
@@ -0,0 +1,89 @@
+/*-
+ * Public Domain 2014-2017 MongoDB, Inc.
+ * Public Domain 2008-2014 WiredTiger, Inc.
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "wt_internal.h"
+
+/*
+ * __time_check_monotonic --
+ * Check and prevent time running backward. If we detect that it has, we
+ * set the time structure to the previous values, making time stand still
+ * until we see a time in the future of the highest value seen so far.
+ */
+static void
+__time_check_monotonic(WT_SESSION_IMPL *session, struct timespec *tsp)
+{
+ /*
+ * Detect time going backward. If so, use the last
+ * saved timestamp.
+ */
+ if (session == NULL)
+ return;
+
+ if (tsp->tv_sec < session->last_epoch.tv_sec ||
+ (tsp->tv_sec == session->last_epoch.tv_sec &&
+ tsp->tv_nsec < session->last_epoch.tv_nsec)) {
+ WT_STAT_CONN_INCR(session, time_travel);
+ *tsp = session->last_epoch;
+ } else
+ session->last_epoch = *tsp;
+}
+
+/*
+ * __wt_epoch --
+ * Return the time since the Epoch, adjusted so it never appears to go
+ * backwards.
+ */
+void
+__wt_epoch(WT_SESSION_IMPL *session, struct timespec *tsp)
+ WT_GCC_FUNC_ATTRIBUTE((visibility("default")))
+{
+ struct timespec tmp;
+
+ /*
+ * Read into a local variable so that we're comparing the correct
+ * value when we check for monotonic increasing time. There are
+ * many places we read into an unlocked global variable.
+ */
+ __wt_epoch_raw(session, &tmp);
+ __time_check_monotonic(session, &tmp);
+ *tsp = tmp;
+}
+
+/*
+ * __wt_seconds --
+ * Return the seconds since the Epoch.
+ */
+void
+__wt_seconds(WT_SESSION_IMPL *session, time_t *timep)
+{
+ struct timespec t;
+
+ __wt_epoch(session, &t);
+
+ *timep = t.tv_sec;
+}
diff --git a/src/third_party/wiredtiger/src/txn/txn.c b/src/third_party/wiredtiger/src/txn/txn.c
index ea7faa2e966..7665ba56adc 100644
--- a/src/third_party/wiredtiger/src/txn/txn.c
+++ b/src/third_party/wiredtiger/src/txn/txn.c
@@ -503,13 +503,17 @@ __wt_txn_commit(WT_SESSION_IMPL *session, const char *cfg[])
WT_CONNECTION_IMPL *conn;
WT_DECL_RET;
WT_TXN *txn;
+ WT_TXN_GLOBAL *txn_global;
WT_TXN_OP *op;
u_int i;
- bool did_update;
+ bool did_update, locked;
txn = &session->txn;
conn = S2C(session);
+ txn_global = &conn->txn_global;
did_update = txn->mod_count != 0;
+ locked = false;
+
WT_ASSERT(session, !F_ISSET(txn, WT_TXN_ERROR) || !did_update);
if (!F_ISSET(txn, WT_TXN_RUNNING))
@@ -580,6 +584,14 @@ __wt_txn_commit(WT_SESSION_IMPL *session, const char *cfg[])
* This is particularly important for checkpoints.
*/
__wt_txn_release_snapshot(session);
+ /*
+ * We hold the visibility lock for reading from the time
+ * we write our log record until the time we release our
+ * transaction so that the LSN any checkpoint gets will
+ * always reflect visible data.
+ */
+ __wt_readlock(session, &txn_global->visibility_rwlock);
+ locked = true;
ret = __wt_txn_log_commit(session, cfg);
}
@@ -590,6 +602,9 @@ __wt_txn_commit(WT_SESSION_IMPL *session, const char *cfg[])
* Nothing can fail after this point.
*/
if (ret != 0) {
+ if (locked)
+ __wt_readunlock(session,
+ &txn_global->visibility_rwlock);
WT_TRET(__wt_txn_rollback(session, cfg));
return (ret);
}
@@ -600,6 +615,8 @@ __wt_txn_commit(WT_SESSION_IMPL *session, const char *cfg[])
txn->mod_count = 0;
__wt_txn_release(session);
+ if (locked)
+ __wt_readunlock(session, &txn_global->visibility_rwlock);
return (0);
}
@@ -770,6 +787,7 @@ __wt_txn_global_init(WT_SESSION_IMPL *session, const char *cfg[])
&txn_global->id_lock, "transaction id lock"));
WT_RET(__wt_rwlock_init(session, &txn_global->scan_rwlock));
WT_RET(__wt_rwlock_init(session, &txn_global->nsnap_rwlock));
+ WT_RET(__wt_rwlock_init(session, &txn_global->visibility_rwlock));
txn_global->nsnap_oldest_id = WT_TXN_NONE;
TAILQ_INIT(&txn_global->nsnaph);
@@ -801,6 +819,7 @@ __wt_txn_global_destroy(WT_SESSION_IMPL *session)
__wt_spin_destroy(session, &txn_global->id_lock);
__wt_rwlock_destroy(session, &txn_global->scan_rwlock);
__wt_rwlock_destroy(session, &txn_global->nsnap_rwlock);
+ __wt_rwlock_destroy(session, &txn_global->visibility_rwlock);
__wt_free(session, txn_global->states);
}
diff --git a/src/third_party/wiredtiger/src/txn/txn_log.c b/src/third_party/wiredtiger/src/txn/txn_log.c
index 2931dc1ce82..09a8c4d9663 100644
--- a/src/third_party/wiredtiger/src/txn/txn_log.c
+++ b/src/third_party/wiredtiger/src/txn/txn_log.c
@@ -289,16 +289,20 @@ int
__wt_txn_checkpoint_log(
WT_SESSION_IMPL *session, bool full, uint32_t flags, WT_LSN *lsnp)
{
+ WT_CONNECTION_IMPL *conn;
WT_DECL_ITEM(logrec);
WT_DECL_RET;
WT_ITEM *ckpt_snapshot, empty;
WT_LSN *ckpt_lsn;
WT_TXN *txn;
+ WT_TXN_GLOBAL *txn_global;
uint8_t *end, *p;
size_t recsize;
uint32_t i, rectype = WT_LOGREC_CHECKPOINT;
const char *fmt = WT_UNCHECKED_STRING(IIIIu);
+ conn = S2C(session);
+ txn_global = &conn->txn_global;
txn = &session->txn;
ckpt_lsn = &txn->ckpt_lsn;
@@ -320,6 +324,15 @@ __wt_txn_checkpoint_log(
txn->full_ckpt = true;
WT_ERR(__wt_log_flush_lsn(session, ckpt_lsn, true));
/*
+ * We take and immediately release the visibility lock.
+ * Acquiring the write lock guarantees that any transaction
+ * that has written to the log has also made its transaction
+ * visible at this time.
+ */
+ __wt_writelock(session, &txn_global->visibility_rwlock);
+ __wt_writeunlock(session, &txn_global->visibility_rwlock);
+
+ /*
* We need to make sure that the log records in the checkpoint
* LSN are on disk. In particular to make sure that the
* current log file exists.
@@ -363,20 +376,20 @@ __wt_txn_checkpoint_log(
txn->ckpt_nsnapshot, ckpt_snapshot));
logrec->size += (uint32_t)recsize;
WT_ERR(__wt_log_write(session, logrec, lsnp,
- F_ISSET(S2C(session), WT_CONN_CKPT_SYNC) ?
+ F_ISSET(conn, WT_CONN_CKPT_SYNC) ?
WT_LOG_FSYNC : 0));
/*
* If this full checkpoint completed successfully and there is
- * no hot backup in progress and this is not recovery, tell
- * the logging subsystem the checkpoint LSN so that it can
- * archive. Do not update the logging checkpoint LSN if this
- * is during a clean connection close, only during a full
- * checkpoint. A clean close may not update any metadata LSN
- * and we do not want to archive in that case.
+ * no hot backup in progress and this is not an unclean
+ * recovery, tell the logging subsystem the checkpoint LSN so
+ * that it can archive. Do not update the logging checkpoint
+ * LSN if this is during a clean connection close, only during
+ * a full checkpoint. A clean close may not update any
+ * metadata LSN and we do not want to archive in that case.
*/
- if (!S2C(session)->hot_backup &&
- !F_ISSET(S2C(session), WT_CONN_RECOVERING) &&
+ if (!conn->hot_backup &&
+ !FLD_ISSET(conn->log_flags, WT_CONN_LOG_RECOVER_DIRTY) &&
txn->full_ckpt)
__wt_log_ckpt(session, ckpt_lsn);
diff --git a/src/third_party/wiredtiger/src/txn/txn_recover.c b/src/third_party/wiredtiger/src/txn/txn_recover.c
index 30932195b1e..29f4dee5199 100644
--- a/src/third_party/wiredtiger/src/txn/txn_recover.c
+++ b/src/third_party/wiredtiger/src/txn/txn_recover.c
@@ -20,6 +20,7 @@ typedef struct {
} *files;
size_t file_alloc; /* Allocated size of files array. */
u_int max_fileid; /* Maximum file ID seen. */
+ WT_LSN max_lsn; /* Maximum checkpoint LSN seen. */
u_int nfiles; /* Number of files in the metadata. */
WT_LSN ckpt_lsn; /* Start LSN for main recovery loop. */
@@ -342,6 +343,10 @@ __recovery_setup_file(WT_RECOVERY *r, const char *uri, const char *config)
"Recovering %s with id %" PRIu32 " @ (%" PRIu32 ", %" PRIu32 ")",
uri, fileid, lsn.l.file, lsn.l.offset);
+ if ((!WT_IS_MAX_LSN(&lsn) && !WT_IS_INIT_LSN(&lsn)) &&
+ (WT_IS_MAX_LSN(&r->max_lsn) || __wt_log_cmp(&lsn, &r->max_lsn) > 0))
+ r->max_lsn = lsn;
+
return (0);
}
@@ -428,6 +433,7 @@ __wt_txn_recover(WT_SESSION_IMPL *session)
WT_RET(__wt_open_internal_session(conn, "txn-recover",
false, WT_SESSION_NO_LOGGING, &session));
r.session = session;
+ WT_MAX_LSN(&r.max_lsn);
F_SET(conn, WT_CONN_RECOVERING);
WT_ERR(__wt_metadata_search(session, WT_METAFILE_URI, &config));
@@ -443,9 +449,29 @@ __wt_txn_recover(WT_SESSION_IMPL *session)
*/
if (!FLD_ISSET(S2C(session)->log_flags, WT_CONN_LOG_EXISTED) ||
WT_IS_MAX_LSN(&metafile->ckpt_lsn)) {
+ /*
+ * Detect if we're going from logging disabled to enabled.
+ * We need to know this to verify LSNs and start at the correct
+ * log file later. If someone ran with logging, then disabled
+ * it and removed all the log files and then turned logging back
+ * on, we have to start logging in the log file number that is
+ * larger than any checkpoint LSN we have from the earlier time.
+ */
WT_ERR(__recovery_file_scan(&r));
+ /*
+ * The array can be re-allocated in recovery_file_scan. Reset
+ * our pointer after scanning all the files.
+ */
+ metafile = &r.files[WT_METAFILE_ID];
conn->next_file_id = r.max_fileid;
- goto done;
+
+ if (FLD_ISSET(conn->log_flags, WT_CONN_LOG_ENABLED) &&
+ WT_IS_MAX_LSN(&metafile->ckpt_lsn) &&
+ !WT_IS_MAX_LSN(&r.max_lsn)) {
+ WT_ERR(__wt_log_reset(session, r.max_lsn.l.file));
+ goto ckpt;
+ } else
+ goto done;
}
/*
@@ -488,6 +514,11 @@ __wt_txn_recover(WT_SESSION_IMPL *session)
/* Scan the metadata to find the live files and their IDs. */
WT_ERR(__recovery_file_scan(&r));
+ /*
+ * Clear this out. We no longer need it and it could have been
+ * re-allocated when scanning the files.
+ */
+ metafile = NULL;
/*
* We no longer need the metadata cursor: close it to avoid pinning any
@@ -535,6 +566,8 @@ __wt_txn_recover(WT_SESSION_IMPL *session)
* this is not a read-only connection.
* We can consider skipping it in the future.
*/
+ if (needs_rec)
+ FLD_SET(conn->log_flags, WT_CONN_LOG_RECOVER_DIRTY);
if (WT_IS_INIT_LSN(&r.ckpt_lsn))
WT_ERR(__wt_log_scan(session, NULL,
WT_LOGSCAN_FIRST | WT_LOGSCAN_RECOVER,
@@ -554,11 +587,12 @@ __wt_txn_recover(WT_SESSION_IMPL *session)
* open is fast and keep the metadata up to date with the checkpoint
* LSN and archiving.
*/
- WT_ERR(session->iface.checkpoint(&session->iface, "force=1"));
+ckpt: WT_ERR(session->iface.checkpoint(&session->iface, "force=1"));
done: FLD_SET(conn->log_flags, WT_CONN_LOG_RECOVER_DONE);
err: WT_TRET(__recovery_free(&r));
__wt_free(session, config);
+ FLD_CLR(conn->log_flags, WT_CONN_LOG_RECOVER_DIRTY);
if (ret != 0)
__wt_err(session, ret, "Recovery failed");
diff --git a/src/third_party/wiredtiger/test/mciproject.yml b/src/third_party/wiredtiger/test/mciproject.yml
index 6456475aa00..50a910d9e58 100644
--- a/src/third_party/wiredtiger/test/mciproject.yml
+++ b/src/third_party/wiredtiger/test/mciproject.yml
@@ -157,20 +157,6 @@ buildvariants:
- name: unit-test
- name: fops
-- name: solaris
- display_name: Solaris
- run_on:
- - solaris
- expansions:
- make_command: PATH=/opt/mongodbtoolchain/bin:$PATH gmake
- test_env_vars: LD_LIBRARY_PATH=`pwd`/.libs
- smp_command: -j $(kstat cpu | sort -u | grep -c "^module")
- configure_env_vars: PATH=/opt/mongodbtoolchain/bin:$PATH CFLAGS="-m64"
- tasks:
- - name: compile
- - name: unit-test
- - name: fops
-
- name: windows-64
display_name: Windows 64-bit
run_on:
diff --git a/src/third_party/wiredtiger/test/recovery/random-abort.c b/src/third_party/wiredtiger/test/recovery/random-abort.c
index febe6530534..b53383e5730 100644
--- a/src/third_party/wiredtiger/test/recovery/random-abort.c
+++ b/src/third_party/wiredtiger/test/recovery/random-abort.c
@@ -47,9 +47,9 @@ static bool inmem;
#define RECORDS_FILE "records-%" PRIu32
#define ENV_CONFIG_DEF \
- "create,log=(file_max=10M,archive=false,enabled)"
+ "create,log=(file_max=10M,enabled)"
#define ENV_CONFIG_TXNSYNC \
- "create,log=(file_max=10M,archive=false,enabled)," \
+ "create,log=(file_max=10M,enabled)," \
"transaction_sync=(enabled,method=none)"
#define ENV_CONFIG_REC "log=(recover=on)"
#define MAX_VAL 4096
diff --git a/src/third_party/wiredtiger/test/suite/test_bug018.py b/src/third_party/wiredtiger/test/suite/test_bug018.py
new file mode 100644
index 00000000000..7d20ebcaacb
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_bug018.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Public Domain 2014-2017 MongoDB, Inc.
+# Public Domain 2008-2014 WiredTiger, Inc.
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+from helper import copy_wiredtiger_home
+import os
+import wiredtiger, wttest
+
+# test_bug018.py
+# JIRA WT-3590: if writing table data fails during close then tables
+# that were updated within the same transaction could get out of sync with
+# each other.
+class test_bug018(wttest.WiredTigerTestCase):
+ '''Test closing/reopening/recovering tables when writes fail'''
+
+ conn_config = 'log=(enabled)'
+
+ def setUp(self):
+ # This test uses Linux-specific code so skip on any other system.
+ if os.name != 'posix' or os.uname()[0] != 'Linux':
+ self.skipTest('Linux-specific test skipped on ' + os.name)
+ super(test_bug018, self).setUp()
+
+ def create_table(self, uri):
+ self.session.create(uri, 'key_format=S,value_format=S')
+ return self.session.open_cursor(uri)
+
+ def test_bug018(self):
+ '''Test closing multiple tables'''
+ basename = 'bug018.'
+ baseuri = 'file:' + basename
+ c1 = self.create_table(baseuri + '01.wt')
+ c2 = self.create_table(baseuri + '02.wt')
+
+ self.session.begin_transaction()
+ c1['key'] = 'value'
+ c2['key'] = 'value'
+ self.session.commit_transaction()
+
+ # Simulate a write failure by closing the file descriptor for the second
+ # table out from underneath WiredTiger. We do this right before
+ # closing the connection so that the write error happens during close
+ # when writing out the final data. Allow table 1 to succeed and force
+ # an erorr writing out table 2.
+ #
+ # This is Linux-specific code to figure out the file descriptor.
+ for f in os.listdir('/proc/self/fd'):
+ try:
+ if os.readlink('/proc/self/fd/' + f).endswith(basename + '02.wt'):
+ os.close(int(f))
+ except OSError:
+ pass
+
+ # Expect an error and messages, so turn off stderr checking.
+ with self.expectedStderrPattern(''):
+ try:
+ self.close_conn()
+ except wiredtiger.WiredTigerError:
+ self.conn = None
+
+ # Make a backup for forensics in case something goes wrong.
+ backup_dir = 'BACKUP'
+ copy_wiredtiger_home('.', backup_dir, True)
+
+ # After reopening and running recovery both tables should be in
+ # sync even though table 1 was successfully written and table 2
+ # had an error on close.
+ self.open_conn()
+ c1 = self.session.open_cursor(baseuri + '01.wt')
+ c2 = self.session.open_cursor(baseuri + '02.wt')
+ self.assertEqual(list(c1), list(c2))
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_inmem01.py b/src/third_party/wiredtiger/test/suite/test_inmem01.py
index 388485db29b..d280642942a 100644
--- a/src/third_party/wiredtiger/test/suite/test_inmem01.py
+++ b/src/third_party/wiredtiger/test/suite/test_inmem01.py
@@ -108,12 +108,15 @@ class test_inmem01(wttest.WiredTigerTestCase):
cursor.reset()
# Spin inserting to give eviction a chance to reclaim space
+ sleeps = 0
inserted = False
for i in range(1, 1000):
try:
cursor[ds.key(1)] = ds.value(1)
except wiredtiger.WiredTigerError:
cursor.reset()
+ sleeps = sleeps + 1
+ self.assertLess(sleeps, 60 * 5)
sleep(1)
continue
inserted = True
diff --git a/src/third_party/wiredtiger/test/suite/test_las.py b/src/third_party/wiredtiger/test/suite/test_las.py
new file mode 100644
index 00000000000..d0bd1d108fa
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_las.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# Public Domain 2014-2017 MongoDB, Inc.
+# Public Domain 2008-2014 WiredTiger, Inc.
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+import wiredtiger, wttest
+from wtdataset import SimpleDataSet
+
+# test_las.py
+# Smoke tests to ensure lookaside tables are working.
+class test_las(wttest.WiredTigerTestCase):
+ # Force a small cache.
+ def conn_config(self):
+ return 'cache_size=1GB'
+
+ @wttest.longtest('lookaside table smoke test')
+ def test_las(self):
+ # Create a small table.
+ uri = "table:test_las"
+ nrows = 100
+ ds = SimpleDataSet(self, uri, nrows, key_format="S")
+ ds.populate()
+
+ # Take a snapshot.
+ self.session.snapshot("name=xxx")
+
+ # Insert a large number of records, we'll hang if the lookaside table
+ # isn't doing its thing.
+ c = self.session.open_cursor(uri)
+ bigvalue = "abcde" * 100
+ for i in range(1, 1000000):
+ c.set_key(ds.key(nrows + i))
+ c.set_value(bigvalue)
+ self.assertEquals(c.insert(), 0)
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_txn02.py b/src/third_party/wiredtiger/test/suite/test_txn02.py
index 01626057b9e..76a325743e9 100644
--- a/src/third_party/wiredtiger/test/suite/test_txn02.py
+++ b/src/third_party/wiredtiger/test/suite/test_txn02.py
@@ -169,7 +169,6 @@ class test_txn02(wttest.WiredTigerTestCase, suite_subprocess):
try:
session = backup_conn.open_session()
finally:
- session.checkpoint("force")
self.check(backup_conn.open_session(), None, committed)
# Sleep long enough so that the archive thread is guaranteed
# to run before we close the connection.
diff --git a/src/third_party/wiredtiger/test/suite/test_txn05.py b/src/third_party/wiredtiger/test/suite/test_txn05.py
index 7aaff221ba4..7099bc972aa 100644
--- a/src/third_party/wiredtiger/test/suite/test_txn05.py
+++ b/src/third_party/wiredtiger/test/suite/test_txn05.py
@@ -134,12 +134,12 @@ class test_txn05(wttest.WiredTigerTestCase, suite_subprocess):
session = backup_conn.open_session()
finally:
self.check(session, None, committed)
- # Force a checkpoint because we don't record the recovery
- # checkpoint as available for archiving.
- session.checkpoint("force")
# Sleep long enough so that the archive thread is guaranteed
# to run before we close the connection.
time.sleep(1.0)
+ if count == 0:
+ first_logs = \
+ fnmatch.filter(os.listdir(self.backup_dir), "*Log*")
backup_conn.close()
count += 1
#
@@ -149,6 +149,11 @@ class test_txn05(wttest.WiredTigerTestCase, suite_subprocess):
#
cur_logs = fnmatch.filter(os.listdir(self.backup_dir), "*Log*")
for o in orig_logs:
+ # Creating the backup was effectively an unclean shutdown so
+ # even after sleeping, we should never archive log files
+ # because a checkpoint has not run. Later opens and runs of
+ # recovery will detect a clean shutdown and allow archiving.
+ self.assertEqual(True, o in first_logs)
if self.archive == 'true':
self.assertEqual(False, o in cur_logs)
else:
diff --git a/src/third_party/wiredtiger/test/suite/test_txn09.py b/src/third_party/wiredtiger/test/suite/test_txn09.py
index 768d714e248..b8a3d7f38ae 100644
--- a/src/third_party/wiredtiger/test/suite/test_txn09.py
+++ b/src/third_party/wiredtiger/test/suite/test_txn09.py
@@ -26,8 +26,8 @@
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
-# test_txn02.py
-# Transactions: commits and rollbacks
+# test_txn09.py
+# Transactions: recovery toggling logging
#
import fnmatch, os, shutil, time
diff --git a/src/third_party/wiredtiger/test/suite/test_txn16.py b/src/third_party/wiredtiger/test/suite/test_txn16.py
new file mode 100644
index 00000000000..929da2291c7
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_txn16.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+#
+# Public Domain 2014-2017 MongoDB, Inc.
+# Public Domain 2008-2014 WiredTiger, Inc.
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# test_txn16.py
+# Recovery: Test that toggling between logging and not logging does not
+# continue to generate more log files.
+#
+
+import fnmatch, os, shutil, time
+from suite_subprocess import suite_subprocess
+import wttest
+
+class test_txn16(wttest.WiredTigerTestCase, suite_subprocess):
+ t1 = 'table:test_txn16_1'
+ t2 = 'table:test_txn16_2'
+ t3 = 'table:test_txn16_3'
+ nentries = 1000
+ create_params = 'key_format=i,value_format=i'
+ # Set the log file size small so we generate checkpoints
+ # with LSNs in different files.
+ conn_config = 'config_base=false,' + \
+ 'log=(archive=false,enabled,file_max=100K),' + \
+ 'transaction_sync=(method=dsync,enabled)'
+ conn_on = 'config_base=false,' + \
+ 'log=(archive=false,enabled,file_max=100K),' + \
+ 'transaction_sync=(method=dsync,enabled)'
+ conn_off = 'config_base=false,log=(enabled=false)'
+
+ def populate_table(self, uri):
+ self.session.create(uri, self.create_params)
+ c = self.session.open_cursor(uri, None, None)
+ # Populate with an occasional checkpoint to generate
+ # some varying LSNs.
+ for i in range(self.nentries):
+ c[i] = i + 1
+ if i % 900 == 0:
+ self.session.checkpoint()
+ c.close()
+
+ def copy_dir(self, olddir, newdir):
+ ''' Simulate a crash from olddir and restart in newdir. '''
+ # with the connection still open, copy files to new directory
+ shutil.rmtree(newdir, ignore_errors=True)
+ os.mkdir(newdir)
+ for fname in os.listdir(olddir):
+ fullname = os.path.join(olddir, fname)
+ # Skip lock file on Windows since it is locked
+ if os.path.isfile(fullname) and \
+ "WiredTiger.lock" not in fullname and \
+ "Tmplog" not in fullname and \
+ "Preplog" not in fullname:
+ shutil.copy(fullname, newdir)
+ # close the original connection.
+ self.close_conn()
+
+ def run_toggle(self, homedir):
+ loop = 0
+ # Record original log files. There should never be overlap
+ # with these even after they're removed.
+ orig_logs = fnmatch.filter(os.listdir(homedir), "*Log*")
+ while loop < 3:
+ # Reopen with logging on to run recovery first time
+ on_conn = self.wiredtiger_open(homedir, self.conn_on)
+ on_conn.close()
+ if loop > 0:
+ # Get current log files.
+ cur_logs = fnmatch.filter(os.listdir(homedir), "*Log*")
+ scur = set(cur_logs)
+ sorig = set(orig_logs)
+ # There should never be overlap with the log files that
+ # were there originally. Mostly this checks that after
+ # opening with logging disabled and then re-enabled, we
+ # don't see log file 1.
+ self.assertEqual(scur.isdisjoint(sorig), True)
+ if loop > 1:
+ # We should be creating the same log files each time.
+ for l in cur_logs:
+ self.assertEqual(l in last_logs, True)
+ for l in last_logs:
+ self.assertEqual(l in cur_logs, True)
+ last_logs = cur_logs
+ loop += 1
+ # Remove all log files before opening without logging.
+ cur_logs = fnmatch.filter(os.listdir(homedir), "*Log*")
+ for l in cur_logs:
+ path=homedir + "/" + l
+ os.remove(path)
+ off_conn = self.wiredtiger_open(homedir, self.conn_off)
+ off_conn.close()
+
+ def test_recovery(self):
+ ''' Check log file creation when toggling. '''
+
+ # Here's the strategy:
+ # - With logging populate 4 tables. Checkpoint
+ # them at different times.
+ # - Copy to a new directory to simulate a crash.
+ # - Close the original connection.
+ # On both a "copy" to simulate a crash and the original (3x):
+ # - Record log files existing.
+ # - Reopen with logging to run recovery. Close connection.
+ # - Record log files existing.
+ # - Remove all log files.
+ # - Open connection with logging disabled.
+ # - Record log files existing. Verify we don't keep adding.
+ #
+ self.populate_table(self.t1)
+ self.populate_table(self.t2)
+ self.populate_table(self.t3)
+ self.copy_dir(".", "RESTART")
+ self.run_toggle(".")
+ self.run_toggle("RESTART")
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/version.json b/version.json
index aeb6f194474..16ecc44565a 100644
--- a/version.json
+++ b/version.json
@@ -1,4 +1,4 @@
{
- "githash": "cf38c1b8a0a8dca4a11737581beafef4fe120bcd",
- "version": "3.4.7"
+ "githash": "fd954412dfc10e4d1e3e2dd4fac040f8b476b268",
+ "version": "3.4.14"
} \ No newline at end of file