diff options
author | James Wahlin <james@mongodb.com> | 2020-01-24 14:15:09 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-02-07 14:07:37 +0000 |
commit | 0f36fdce71df08aa7c0703bda69a7045f96f41c8 (patch) | |
tree | 8e6f7a5c90b1a69c91a88ac1b07e42fce6d321fb /src/mongo | |
parent | 3d821c25e2944668b7359a0bf6e586cc8625b9a2 (diff) | |
download | mongo-0f36fdce71df08aa7c0703bda69a7045f96f41c8.tar.gz |
SERVER-45456 Allow JavaScript on mongos
create mode 100644 jstests/noPassthrough/mr_noscripting.js
delete mode 100644 jstests/noPassthroughWithMongod/mr_noscripting.js
create mode 100644 jstests/sharding/agg_js_on_mongos.js
create mode 100644 jstests/sharding/javascript_heap_limit.js
Diffstat (limited to 'src/mongo')
26 files changed, 155 insertions, 49 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index e26c3ad4aec..c94521e3886 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -543,6 +543,7 @@ mongos = env.Program( 'db/commands/server_status_core', 'db/commands/server_status_servers', 'db/curop', + 'db/dbdirectclient', 'db/ftdc/ftdc_mongos', 'db/initialize_server_security_state', 'db/logical_session_cache', diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 8c1e299fdb1..4519c4e3c2d 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -107,6 +107,7 @@ boost::intrusive_ptr<ExpressionContext> makeExpressionContext( false, // needsMerge queryRequest.allowDiskUse(), false, // bypassDocumentValidation + false, // isMapReduceCommand queryRequest.nss(), queryRequest.getRuntimeConstants(), std::move(collator), diff --git a/src/mongo/db/commands/map_reduce_agg.cpp b/src/mongo/db/commands/map_reduce_agg.cpp index 214c930046e..02fa93232b0 100644 --- a/src/mongo/db/commands/map_reduce_agg.cpp +++ b/src/mongo/db/commands/map_reduce_agg.cpp @@ -89,6 +89,7 @@ auto makeExpressionContext(OperationContext* opCtx, false, // needsmerge true, // allowDiskUse parsedMr.getBypassDocumentValidation().get_value_or(false), + true, // isMapReduceCommand parsedMr.getNamespace(), runtimeConstants, std::move(resolvedCollator), diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index 2905a7d808b..fa27da05155 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -64,6 +64,7 @@ env.Library( LIBDEPS=[ 'aggregation_request', '$BUILD_DIR/mongo/db/query/collation/collator_factory_interface', + '$BUILD_DIR/mongo/db/query/query_knobs', '$BUILD_DIR/mongo/db/service_context', '$BUILD_DIR/mongo/scripting/scripting', '$BUILD_DIR/mongo/util/intrusive_counter', diff --git a/src/mongo/db/pipeline/aggregation_request.cpp b/src/mongo/db/pipeline/aggregation_request.cpp index f7e9710dac9..f5c417e30e9 100644 --- a/src/mongo/db/pipeline/aggregation_request.cpp +++ b/src/mongo/db/pipeline/aggregation_request.cpp @@ -222,6 +222,13 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON( << typeName(elem.type())}; } request.setUseNewUpsert(elem.boolean()); + } else if (fieldName == kIsMapReduceCommand) { + if (elem.type() != BSONType::Bool) { + return {ErrorCodes::TypeMismatch, + str::stream() << kIsMapReduceCommand << " must be a boolean, not a " + << typeName(elem.type())}; + } + request.setIsMapReduceCommand(elem.boolean()); } else if (!isGenericArgument(fieldName)) { return {ErrorCodes::FailedToParse, str::stream() << "unrecognized field '" << elem.fieldName() << "'"}; @@ -261,7 +268,7 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON( } return request; -} +} // namespace mongo NamespaceString AggregationRequest::parseNs(const std::string& dbname, const BSONObj& cmdObj) { auto firstElement = cmdObj.firstElement(); @@ -322,6 +329,7 @@ Document AggregationRequest::serializeToCommandObj() const { {kRuntimeConstants, _runtimeConstants ? Value(_runtimeConstants->toBSON()) : Value()}, {kUse44SortKeys, _use44SortKeys ? Value(true) : Value()}, {kUseNewUpsert, _useNewUpsert ? Value(true) : Value()}, + {kIsMapReduceCommand, _isMapReduceCommand ? Value(true) : Value()}, }; } } // namespace mongo diff --git a/src/mongo/db/pipeline/aggregation_request.h b/src/mongo/db/pipeline/aggregation_request.h index 19ad92859b1..eb2594e7765 100644 --- a/src/mongo/db/pipeline/aggregation_request.h +++ b/src/mongo/db/pipeline/aggregation_request.h @@ -65,6 +65,7 @@ public: static constexpr StringData kRuntimeConstants = "runtimeConstants"_sd; static constexpr StringData kUse44SortKeys = "use44SortKeys"_sd; static constexpr StringData kUseNewUpsert = "useNewUpsert"_sd; + static constexpr StringData kIsMapReduceCommand = "isMapReduceCommand"_sd; static constexpr long long kDefaultBatchSize = 101; @@ -226,6 +227,10 @@ public: return _useNewUpsert; } + bool getIsMapReduceCommand() const { + return _isMapReduceCommand; + } + // // Setters for optional fields. // @@ -298,6 +303,10 @@ public: _useNewUpsert = useNewUpsert; } + void setIsMapReduceCommand(bool isMapReduce) { + _isMapReduceCommand = isMapReduce; + } + private: // Required fields. const NamespaceString _nss; @@ -355,5 +364,8 @@ private: // Indicates whether the aggregation may use the new 'upsertSupplied' mechanism when running // $merge stages. All 4.4 mongoS and some versions of 4.2 set this flag. bool _useNewUpsert = false; + + // True when an aggregation was invoked by the MapReduce command. + bool _isMapReduceCommand = false; }; } // namespace mongo diff --git a/src/mongo/db/pipeline/aggregation_request_test.cpp b/src/mongo/db/pipeline/aggregation_request_test.cpp index 8e0b3168c9d..c635a316bfb 100644 --- a/src/mongo/db/pipeline/aggregation_request_test.cpp +++ b/src/mongo/db/pipeline/aggregation_request_test.cpp @@ -61,7 +61,7 @@ TEST(AggregationRequestTest, ShouldParseAllKnownOptions) { "needsMerge: true, bypassDocumentValidation: true, collation: {locale: 'en_US'}, cursor: " "{batchSize: 10}, hint: {a: 1}, maxTimeMS: 100, readConcern: {level: 'linearizable'}, " "$queryOptions: {$readPreference: 'nearest'}, exchange: {policy: " - "'roundrobin', consumers:NumberInt(2)}}"); + "'roundrobin', consumers:NumberInt(2)}, isMapReduceCommand: true}"); auto request = unittest::assertGet(AggregationRequest::parseFromBSON(nss, inputBson)); ASSERT_FALSE(request.getExplain()); ASSERT_TRUE(request.shouldAllowDiskUse()); @@ -81,6 +81,7 @@ TEST(AggregationRequestTest, ShouldParseAllKnownOptions) { BSON("$readPreference" << "nearest")); ASSERT_TRUE(request.getExchangeSpec().is_initialized()); + ASSERT_TRUE(request.getIsMapReduceCommand()); } TEST(AggregationRequestTest, ShouldParseExplicitExplainTrue) { @@ -159,6 +160,7 @@ TEST(AggregationRequestTest, ShouldNotSerializeOptionalValuesIfEquivalentToDefau request.setMaxTimeMS(0u); request.setUnwrappedReadPref(BSONObj()); request.setReadConcern(BSONObj()); + request.setIsMapReduceCommand(false); auto expectedSerialization = Document{{AggregationRequest::kCommandName, nss.coll()}, @@ -187,6 +189,7 @@ TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) { const auto readConcernObj = BSON("level" << "linearizable"); request.setReadConcern(readConcernObj); + request.setIsMapReduceCommand(true); auto expectedSerialization = Document{{AggregationRequest::kCommandName, nss.coll()}, @@ -201,7 +204,8 @@ TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) { {AggregationRequest::kHintName, hintObj}, {repl::ReadConcernArgs::kReadConcernFieldName, readConcernObj}, {QueryRequest::kUnwrappedReadPrefField, readPrefObj}, - {QueryRequest::cmdOptionMaxTimeMS, 10}}; + {QueryRequest::cmdOptionMaxTimeMS, 10}, + {AggregationRequest::kIsMapReduceCommand, true}}; ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization); } @@ -370,6 +374,13 @@ TEST(AggregationRequestTest, ShouldRejectNonBoolAllowDiskUse) { ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); } +TEST(AggregationRequestTest, ShouldRejectNonBoolIsMapReduceCommand) { + NamespaceString nss("a.collection"); + const BSONObj inputBson = + fromjson("{pipeline: [{$match: {a: 'abc'}}], cursor: {}, isMapReduceCommand: 1}"); + ASSERT_NOT_OK(AggregationRequest::parseFromBSON(nss, inputBson).getStatus()); +} + TEST(AggregationRequestTest, ShouldRejectNoCursorNoExplain) { NamespaceString nss("a.collection"); const BSONObj inputBson = fromjson("{pipeline: [{$match: {a: 'abc'}}]}"); diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp index 563b03569ab..2e4908d8f7e 100644 --- a/src/mongo/db/pipeline/expression_context.cpp +++ b/src/mongo/db/pipeline/expression_context.cpp @@ -57,6 +57,7 @@ ExpressionContext::ExpressionContext(OperationContext* opCtx, request.needsMerge(), request.shouldAllowDiskUse(), request.shouldBypassDocumentValidation(), + request.getIsMapReduceCommand(), request.getNamespaceString(), request.getRuntimeConstants(), std::move(collator), @@ -67,6 +68,12 @@ ExpressionContext::ExpressionContext(OperationContext* opCtx, // has the 'useNewUpsert' flag set, can use the new upsertSupplied mechanism for $merge. // TODO SERVER-44884: Remove this flag after we branch for 4.5. useNewUpsert = request.getUseNewUpsert() || !request.isFromMongos(); + + if (request.getIsMapReduceCommand()) { + // mapReduce command JavaScript invocation is only subject to the server global + // 'jsHeapLimitMB' limit. + jsHeapLimitMB = boost::none; + } } ExpressionContext::ExpressionContext( @@ -76,6 +83,7 @@ ExpressionContext::ExpressionContext( bool needsMerge, bool allowDiskUse, bool bypassDocumentValidation, + bool isMapReduce, const NamespaceString& ns, const boost::optional<RuntimeConstants>& runtimeConstants, std::unique_ptr<CollatorInterface> collator, @@ -101,10 +109,15 @@ ExpressionContext::ExpressionContext( _valueComparator(_unownedCollator), _resolvedNamespaces(std::move(resolvedNamespaces)) { - if (runtimeConstants) + if (runtimeConstants) { variables.setRuntimeConstants(*runtimeConstants); - else + } else { variables.setDefaultRuntimeConstants(opCtx); + } + + if (!isMapReduce) { + jsHeapLimitMB = internalQueryJavaScriptHeapSizeLimitMB.load(); + } // Any request which did not originate from a mongoS can use the new upsertSupplied mechanism. // This is used to set 'useNewUpsert' when constructing a MR context on mongoS or mongoD. The MR @@ -128,6 +141,8 @@ ExpressionContext::ExpressionContext(OperationContext* opCtx, if (runtimeConstants) { variables.setRuntimeConstants(*runtimeConstants); } + + jsHeapLimitMB = internalQueryJavaScriptHeapSizeLimitMB.load(); } void ExpressionContext::checkForInterrupt() { @@ -191,6 +206,7 @@ intrusive_ptr<ExpressionContext> ExpressionContext::copyWith( needsMerge, allowDiskUse, bypassDocumentValidation, + false, // isMapReduce ns, boost::none, // runtimeConstants std::move(collator), @@ -203,6 +219,7 @@ intrusive_ptr<ExpressionContext> ExpressionContext::copyWith( expCtx->subPipelineDepth = subPipelineDepth; expCtx->tempDir = tempDir; expCtx->useNewUpsert = useNewUpsert; + expCtx->jsHeapLimitMB = jsHeapLimitMB; // ExpressionContext is used both universally in Agg and in Find within a $expr. In the case // that this context is for use in $expr, the collator will be unowned and we will pass nullptr diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h index b0f4df41e05..559e6cd6596 100644 --- a/src/mongo/db/pipeline/expression_context.h +++ b/src/mongo/db/pipeline/expression_context.h @@ -49,6 +49,7 @@ #include "mongo/db/query/collation/collator_interface.h" #include "mongo/db/query/datetime/date_time_support.h" #include "mongo/db/query/explain_options.h" +#include "mongo/db/query/query_knobs_gen.h" #include "mongo/db/query/tailable_mode.h" #include "mongo/db/server_options.h" #include "mongo/util/intrusive_counter.h" @@ -120,6 +121,7 @@ public: bool needsMerge, bool allowDiskUse, bool bypassDocumentValidation, + bool isMapReduceCommand, const NamespaceString& ns, const boost::optional<RuntimeConstants>& runtimeConstants, std::unique_ptr<CollatorInterface> collator, @@ -245,7 +247,8 @@ public: getGlobalScriptEngine()); RuntimeConstants runtimeConstants = getRuntimeConstants(); const boost::optional<mongo::BSONObj>& scope = runtimeConstants.getJsScope(); - return JsExecution::get(opCtx, scope.get_value_or(BSONObj()), ns.db()); + return JsExecution::get( + opCtx, scope.get_value_or(BSONObj()), ns.db(), inMongos, jsHeapLimitMB); } // The explain verbosity requested by the user, or boost::none if no explain was requested. @@ -267,6 +270,11 @@ public: OperationContext* opCtx; + // When set restricts the global JavaScript heap size limit for any Scope returned by + // getJsExecWithScope(). This limit is ignored if larger than the global limit dictated by the + // 'jsHeapLimitMB' server parameter. + boost::optional<int> jsHeapLimitMB; + // An interface for accessing information or performing operations that have different // implementations on mongod and mongos, or that only make sense on one of the two. // Additionally, putting some of this functionality behind an interface prevents aggregation diff --git a/src/mongo/db/pipeline/expression_context_for_test.h b/src/mongo/db/pipeline/expression_context_for_test.h index f5cbf88b799..10d71ef185a 100644 --- a/src/mongo/db/pipeline/expression_context_for_test.h +++ b/src/mongo/db/pipeline/expression_context_for_test.h @@ -58,6 +58,7 @@ public: false, // needsMerge, false, // allowDiskUse, false, // bypassDocumentValidation, + false, // isMapReduce nss, RuntimeConstants(Date_t::now(), Timestamp(1, 0)), {}, // collator diff --git a/src/mongo/db/pipeline/javascript_execution.cpp b/src/mongo/db/pipeline/javascript_execution.cpp index 720fdad7f8e..7b00a6e4d7e 100644 --- a/src/mongo/db/pipeline/javascript_execution.cpp +++ b/src/mongo/db/pipeline/javascript_execution.cpp @@ -37,12 +37,21 @@ namespace { const auto getExec = OperationContext::declareDecoration<std::unique_ptr<JsExecution>>(); } // namespace -JsExecution* JsExecution::get(OperationContext* opCtx, const BSONObj& scope, StringData database) { +JsExecution* JsExecution::get(OperationContext* opCtx, + const BSONObj& scope, + StringData database, + bool inMongos, + boost::optional<int> jsHeapLimitMB) { auto& exec = getExec(opCtx); if (!exec) { - exec = std::make_unique<JsExecution>(scope); + exec = std::make_unique<JsExecution>(scope, jsHeapLimitMB); exec->getScope()->setLocalDB(database); - exec->getScope()->loadStored(opCtx, true); + + // TODO SERVER-45457: Remove this check and the "inMongos" argument to this method once we + // are no longer loading system.js for $function use outside of mapReduce and $where. + if (!inMongos) { + exec->getScope()->loadStored(opCtx, true); + } } return exec.get(); } diff --git a/src/mongo/db/pipeline/javascript_execution.h b/src/mongo/db/pipeline/javascript_execution.h index 7e5a2ad51d4..544c2eae554 100644 --- a/src/mongo/db/pipeline/javascript_execution.h +++ b/src/mongo/db/pipeline/javascript_execution.h @@ -51,13 +51,17 @@ public: * and reading the return value. This will load all stored procedures from database unless * 'disableLoadStored' is set on the global ScriptEngine. */ - static JsExecution* get(OperationContext* opCtx, const BSONObj& scope, StringData database); + static JsExecution* get(OperationContext* opCtx, + const BSONObj& scope, + StringData database, + bool inMongos, + boost::optional<int> jsHeapLimitMB); /** * Construct with a thread-local scope and initialize with the given scope variables. */ - explicit JsExecution(const BSONObj& scopeVars) - : _scope(getGlobalScriptEngine()->newScopeForCurrentThread()) { + explicit JsExecution(const BSONObj& scopeVars, boost::optional<int> jsHeapLimitMB = boost::none) + : _scope(getGlobalScriptEngine()->newScopeForCurrentThread(jsHeapLimitMB)) { _scopeVars = scopeVars.getOwned(); _scope->init(&_scopeVars); _scope->registerOperation(Client::getCurrent()->getOperationContext()); diff --git a/src/mongo/db/query/query_knobs.idl b/src/mongo/db/query/query_knobs.idl index 09824ee3922..7516de7b407 100644 --- a/src/mongo/db/query/query_knobs.idl +++ b/src/mongo/db/query/query_knobs.idl @@ -330,3 +330,13 @@ server_parameters: expr: 100 * 1024 * 1024 validator: gt: 0 + + internalQueryJavaScriptHeapSizeLimitMB: + description: "Limits the JavaScript heap size used in aggregation. Will defer to the global 'jsHeapLimitMB' limit if the global limit is smaller." + set_at: [ startup, runtime ] + cpp_varname: "internalQueryJavaScriptHeapSizeLimitMB" + cpp_vartype: AtomicWord<int> + default: + expr: 100 + validator: + gt: 0 diff --git a/src/mongo/db/views/resolved_view.cpp b/src/mongo/db/views/resolved_view.cpp index 632be798d6a..ff4442dc85e 100644 --- a/src/mongo/db/views/resolved_view.cpp +++ b/src/mongo/db/views/resolved_view.cpp @@ -112,6 +112,7 @@ AggregationRequest ResolvedView::asExpandedViewAggregation( expandedRequest.setBypassDocumentValidation(request.shouldBypassDocumentValidation()); expandedRequest.setAllowDiskUse(request.shouldAllowDiskUse()); expandedRequest.setUseNewUpsert(request.getUseNewUpsert()); + expandedRequest.setIsMapReduceCommand(request.getIsMapReduceCommand()); // 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 diff --git a/src/mongo/s/commands/cluster_map_reduce_agg.cpp b/src/mongo/s/commands/cluster_map_reduce_agg.cpp index 54d71a81b4c..79fc399e987 100644 --- a/src/mongo/s/commands/cluster_map_reduce_agg.cpp +++ b/src/mongo/s/commands/cluster_map_reduce_agg.cpp @@ -93,6 +93,7 @@ auto makeExpressionContext(OperationContext* opCtx, false, // needsmerge true, // allowDiskUse parsedMr.getBypassDocumentValidation().get_value_or(false), + true, // isMapReduceCommand nss, runtimeConstants, std::move(resolvedCollator), @@ -114,6 +115,7 @@ Document serializeToCommand(BSONObj originalCmd, const MapReduce& parsedMr, Pipe translatedCmd[AggregationRequest::kFromMongosName] = Value(true); translatedCmd[AggregationRequest::kRuntimeConstants] = Value(pipeline->getContext()->getRuntimeConstants().toBSON()); + translatedCmd[AggregationRequest::kIsMapReduceCommand] = Value(true); if (shouldBypassDocumentValidationForCommand(originalCmd)) { translatedCmd[bypassDocumentValidationCommandOption()] = Value(true); diff --git a/src/mongo/s/mongos_options.cpp b/src/mongo/s/mongos_options.cpp index a54f7cf1fbe..295c22418e4 100644 --- a/src/mongo/s/mongos_options.cpp +++ b/src/mongo/s/mongos_options.cpp @@ -90,6 +90,21 @@ Status canonicalizeMongosOptions(moe::Environment* params) { return ret; } + // "security.javascriptEnabled" comes from the config file, so override it if "noscripting" + // is set since that comes from the command line. + if (params->count("noscripting")) { + auto status = params->set("security.javascriptEnabled", + moe::Value(!(*params)["noscripting"].as<bool>())); + if (!status.isOK()) { + return status; + } + + status = params->remove("noscripting"); + if (!status.isOK()) { + return status; + } + } + return Status::OK(); } @@ -106,9 +121,8 @@ Status storeMongosOptions(const moe::Environment& params) { } } - if (params.count("noscripting") || params.count("security.javascriptEnabled")) { - warning() << "The Javascript enabled/disabled options are not supported for mongos. " - "(\"noscripting\" and/or \"security.javascriptEnabled\" are set.)"; + if (params.count("security.javascriptEnabled")) { + mongosGlobalParams.scriptingEnabled = params["security.javascriptEnabled"].as<bool>(); } if (!params.count("sharding.configDB")) { diff --git a/src/mongo/s/mongos_options.h b/src/mongo/s/mongos_options.h index 97c3bc53e34..a827d04550d 100644 --- a/src/mongo/s/mongos_options.h +++ b/src/mongo/s/mongos_options.h @@ -46,6 +46,9 @@ class Environment; namespace moe = mongo::optionenvironment; struct MongosGlobalParams { + bool scriptingEnabled = true; // Use "security.javascriptEnabled" to set this variable. Or use + // --noscripting which will set it to false. + // The config server connection string ConnectionString configdbs; }; diff --git a/src/mongo/s/mongos_options.idl b/src/mongo/s/mongos_options.idl index f92d2474fad..a05d30b3d36 100644 --- a/src/mongo/s/mongos_options.idl +++ b/src/mongo/s/mongos_options.idl @@ -52,26 +52,11 @@ configs: short_name: "test" arg_vartype: Switch source: [ cli, ini ] -# -# Javascript Options -# As a general rule, js enable/disable options are ignored for mongos. -# However, we define and hide these options so that if someone -# were to use these args in a set of options meant for both -# mongos and mongod runs, the mongos won't fail on an unknown argument. -# -# These options have no affect on how the mongos runs. -# Setting either or both to *any* value will provoke a warning message -# and nothing more. -# + "noscripting": + description: "Disable scripting engine" + arg_vartype: Switch + source: [ cli, ini ] "security.javascriptEnabled": - section: "General options" description: "Enable javascript execution" arg_vartype: Bool source: [ yaml ] - hidden: true - "noscripting": - description: "disable scripting engine" - short_name: "noscripting" - arg_vartype: Switch - source: [ cli, ini ] - hidden: true diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp index 84f9a779fb0..bf6a06cb11d 100644 --- a/src/mongo/s/server.cpp +++ b/src/mongo/s/server.cpp @@ -49,6 +49,7 @@ #include "mongo/db/auth/authz_manager_external_state_s.h" #include "mongo/db/auth/user_cache_invalidator_job.h" #include "mongo/db/client.h" +#include "mongo/db/dbdirectclient.h" #include "mongo/db/ftdc/ftdc_mongos.h" #include "mongo/db/initialize_server_global_state.h" #include "mongo/db/initialize_server_security_state.h" @@ -93,6 +94,8 @@ #include "mongo/s/sharding_uptime_reporter.h" #include "mongo/s/transaction_router.h" #include "mongo/s/version_mongos.h" +#include "mongo/scripting/dbdirectclient_factory.h" +#include "mongo/scripting/engine.h" #include "mongo/stdx/thread.h" #include "mongo/transport/transport_layer_manager.h" #include "mongo/util/admin_access.h" @@ -593,6 +596,10 @@ ExitCode runMongosServer(ServiceContext* serviceContext) { startMongoSFTDC(); + if (mongosGlobalParams.scriptingEnabled) { + ScriptEngine::setup(); + } + Status status = AuthorizationManager::get(serviceContext)->initialize(opCtx); if (!status.isOK()) { error() << "Initializing authorization data failed: " << status; diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h index 72ec298af9a..434b38ff921 100644 --- a/src/mongo/scripting/engine.h +++ b/src/mongo/scripting/engine.h @@ -217,8 +217,12 @@ public: return createScope(); } - virtual Scope* newScopeForCurrentThread() { - return createScopeForCurrentThread(); + virtual Scope* newScopeForCurrentThread(boost::optional<int> jsHeapLimitMB) { + return createScopeForCurrentThread(jsHeapLimitMB); + } + + Scope* newScopeForCurrentThread() { + return newScopeForCurrentThread(boost::none); } virtual void runTest() = 0; @@ -273,7 +277,7 @@ public: protected: virtual Scope* createScope() = 0; - virtual Scope* createScopeForCurrentThread() = 0; + virtual Scope* createScopeForCurrentThread(boost::optional<int> jsHeapLimitMB) = 0; void (*_scopeInitCallback)(Scope&); private: diff --git a/src/mongo/scripting/mozjs/engine.cpp b/src/mongo/scripting/mozjs/engine.cpp index bac89aaef86..766c2fce637 100644 --- a/src/mongo/scripting/mozjs/engine.cpp +++ b/src/mongo/scripting/mozjs/engine.cpp @@ -77,8 +77,8 @@ mongo::Scope* MozJSScriptEngine::createScope() { return new MozJSProxyScope(this); } -mongo::Scope* MozJSScriptEngine::createScopeForCurrentThread() { - return new MozJSImplScope(this); +mongo::Scope* MozJSScriptEngine::createScopeForCurrentThread(boost::optional<int> jsHeapLimitMB) { + return new MozJSImplScope(this, jsHeapLimitMB); } void MozJSScriptEngine::interrupt(unsigned opId) { diff --git a/src/mongo/scripting/mozjs/engine.h b/src/mongo/scripting/mozjs/engine.h index 688d476282f..c8aecdf7fb0 100644 --- a/src/mongo/scripting/mozjs/engine.h +++ b/src/mongo/scripting/mozjs/engine.h @@ -52,7 +52,7 @@ public: ~MozJSScriptEngine() override; mongo::Scope* createScope() override; - mongo::Scope* createScopeForCurrentThread() override; + mongo::Scope* createScopeForCurrentThread(boost::optional<int> jsHeapLimitMB) override; void runTest() override {} diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index e78d462d7e7..6faa2467125 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -261,14 +261,18 @@ void MozJSImplScope::ASANHandles::removePointer(void* ptr) {} #endif -MozJSImplScope::MozRuntime::MozRuntime(const MozJSScriptEngine* engine) { +MozJSImplScope::MozRuntime::MozRuntime(const MozJSScriptEngine* engine, + boost::optional<int> jsHeapLimitMB) { /** * The maximum amount of memory to be given out per thread to mozilla. We * manage this by trapping all calls to malloc, free, etc. and keeping track of - * counts in some thread locals + * counts in some thread locals. If 'jsHeapLimitMB' is specified then we use this instead of the + * engine limit, given it does not exceed the engine limit. */ + const auto engineJsHeapLimit = engine->getJSHeapLimitMB(); + const auto jsHeapLimit = + jsHeapLimitMB ? std::min(*jsHeapLimitMB, engineJsHeapLimit) : engineJsHeapLimit; - const auto jsHeapLimit = engine->getJSHeapLimitMB(); if (jsHeapLimit != 0 && jsHeapLimit < 10) { warning() << "JavaScript may not be able to initialize with a heap limit less than 10MB."; } @@ -360,9 +364,9 @@ MozJSImplScope::MozRuntime::MozRuntime(const MozJSScriptEngine* engine) { } } -MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) +MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB) : _engine(engine), - _mr(engine), + _mr(engine, jsHeapLimitMB), _context(_mr._context.get()), _globalProto(_context), _global(_globalProto.getProto()), diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index c763640e5b9..c774efd3bd3 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -88,7 +88,7 @@ class MozJSImplScope final : public Scope { MozJSImplScope& operator=(const MozJSImplScope&) = delete; public: - explicit MozJSImplScope(MozJSScriptEngine* engine); + explicit MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB); ~MozJSImplScope(); void init(const BSONObj* data) override; @@ -377,7 +377,7 @@ private: */ struct MozRuntime { public: - MozRuntime(const MozJSScriptEngine* engine); + MozRuntime(const MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB); std::thread _thread; // NOLINT std::unique_ptr<JSRuntime, std::function<void(JSRuntime*)>> _runtime; diff --git a/src/mongo/scripting/mozjs/jsthread.cpp b/src/mongo/scripting/mozjs/jsthread.cpp index d39172bb02b..9e89d1bb1ea 100644 --- a/src/mongo/scripting/mozjs/jsthread.cpp +++ b/src/mongo/scripting/mozjs/jsthread.cpp @@ -184,7 +184,8 @@ private: auto thisv = static_cast<JSThread*>(priv); try { - MozJSImplScope scope(static_cast<MozJSScriptEngine*>(getGlobalScriptEngine())); + MozJSImplScope scope(static_cast<MozJSScriptEngine*>(getGlobalScriptEngine()), + boost::none /* Don't override global jsHeapLimitMB */); scope.setParentStack(thisv->_sharedData->_stack); thisv->_sharedData->_returnData = scope.callThreadArgs(thisv->_sharedData->_args); diff --git a/src/mongo/scripting/mozjs/proxyscope.cpp b/src/mongo/scripting/mozjs/proxyscope.cpp index e4fce4699f4..a474eb1a719 100644 --- a/src/mongo/scripting/mozjs/proxyscope.cpp +++ b/src/mongo/scripting/mozjs/proxyscope.cpp @@ -345,7 +345,8 @@ void MozJSProxyScope::implThread(MozJSProxyScope* proxy) { // This will leave _status set for the first noop runOnImplThread(), which // captures the startup exception that way try { - scope.reset(new MozJSImplScope(proxy->_engine)); + scope.reset(new MozJSImplScope(proxy->_engine, + boost::none /* Don't override global jsHeapLimitMB */)); proxy->_implScope = scope.get(); } catch (...) { proxy->_status = exceptionToStatus(); |