summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorJames Wahlin <james@mongodb.com>2020-01-24 14:15:09 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-02-07 14:07:37 +0000
commit0f36fdce71df08aa7c0703bda69a7045f96f41c8 (patch)
tree8e6f7a5c90b1a69c91a88ac1b07e42fce6d321fb /src/mongo
parent3d821c25e2944668b7359a0bf6e586cc8625b9a2 (diff)
downloadmongo-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')
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/db/commands/find_cmd.cpp1
-rw-r--r--src/mongo/db/commands/map_reduce_agg.cpp1
-rw-r--r--src/mongo/db/pipeline/SConscript1
-rw-r--r--src/mongo/db/pipeline/aggregation_request.cpp10
-rw-r--r--src/mongo/db/pipeline/aggregation_request.h12
-rw-r--r--src/mongo/db/pipeline/aggregation_request_test.cpp15
-rw-r--r--src/mongo/db/pipeline/expression_context.cpp21
-rw-r--r--src/mongo/db/pipeline/expression_context.h10
-rw-r--r--src/mongo/db/pipeline/expression_context_for_test.h1
-rw-r--r--src/mongo/db/pipeline/javascript_execution.cpp15
-rw-r--r--src/mongo/db/pipeline/javascript_execution.h10
-rw-r--r--src/mongo/db/query/query_knobs.idl10
-rw-r--r--src/mongo/db/views/resolved_view.cpp1
-rw-r--r--src/mongo/s/commands/cluster_map_reduce_agg.cpp2
-rw-r--r--src/mongo/s/mongos_options.cpp20
-rw-r--r--src/mongo/s/mongos_options.h3
-rw-r--r--src/mongo/s/mongos_options.idl23
-rw-r--r--src/mongo/s/server.cpp7
-rw-r--r--src/mongo/scripting/engine.h10
-rw-r--r--src/mongo/scripting/mozjs/engine.cpp4
-rw-r--r--src/mongo/scripting/mozjs/engine.h2
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp14
-rw-r--r--src/mongo/scripting/mozjs/implscope.h4
-rw-r--r--src/mongo/scripting/mozjs/jsthread.cpp3
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.cpp3
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();