From 35cb5b7a665e8dddb885ee126e08dcc8f765f378 Mon Sep 17 00:00:00 2001 From: Jason Carey Date: Mon, 10 Aug 2015 10:30:53 -0400 Subject: [PATCH] SERVER-19935 Make JSThread stacks more informative We have some pretty poor error reporting for interrupted $where's and map reduces. This fixes the interruption bit and accumulates stack's across threads. --- src/mongo/scripting/SConscript | 1 + src/mongo/scripting/mozjs/error.cpp | 39 ++++++++++++++++++++ src/mongo/scripting/mozjs/error.h | 49 +++++++++++++++++++++++++ src/mongo/scripting/mozjs/exception.cpp | 11 ++++++ src/mongo/scripting/mozjs/exception.h | 5 +++ src/mongo/scripting/mozjs/implscope.cpp | 16 ++++++++ src/mongo/scripting/mozjs/implscope.h | 9 +++++ src/mongo/scripting/mozjs/jsthread.cpp | 19 ++++++++-- 8 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 src/mongo/scripting/mozjs/error.cpp create mode 100644 src/mongo/scripting/mozjs/error.h diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index ec55b82356860..5db8dd522285f 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -123,6 +123,7 @@ elif usemozjs: 'mozjs/dbquery.cpp', 'mozjs/dbref.cpp', 'mozjs/engine.cpp', + 'mozjs/error.cpp', 'mozjs/exception.cpp', 'mozjs/global.cpp', 'mozjs/idwrapper.cpp', diff --git a/src/mongo/scripting/mozjs/error.cpp b/src/mongo/scripting/mozjs/error.cpp new file mode 100644 index 0000000000000..11cc0631662f9 --- /dev/null +++ b/src/mongo/scripting/mozjs/error.cpp @@ -0,0 +1,39 @@ +/** + * 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 . + * + * 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/scripting/mozjs/error.h" + +namespace mongo { +namespace mozjs { + +const char* const ErrorInfo::className = "Error"; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/error.h b/src/mongo/scripting/mozjs/error.h new file mode 100644 index 0000000000000..56f29686dea9f --- /dev/null +++ b/src/mongo/scripting/mozjs/error.h @@ -0,0 +1,49 @@ +/** + * 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 . + * + * 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/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "Error" Javascript object. + * + * Note that this installs over native. We only use this to grab the error + * prototype early in case users overwrite it. + */ +struct ErrorInfo : public BaseInfo { + static const char* const className; + + static const InstallType installType = InstallType::OverNative; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/exception.cpp b/src/mongo/scripting/mozjs/exception.cpp index d4b70e4f7ea19..8350d4f3ef112 100644 --- a/src/mongo/scripting/mozjs/exception.cpp +++ b/src/mongo/scripting/mozjs/exception.cpp @@ -33,7 +33,9 @@ #include #include +#include "mongo/scripting/mozjs/implscope.h" #include "mongo/scripting/mozjs/jsstringwrapper.h" +#include "mongo/scripting/mozjs/objectwrapper.h" #include "mongo/util/assert_util.h" namespace mongo { @@ -73,6 +75,15 @@ void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd) { JS_ReportErrorNumber(cx, callback, nullptr, JSErr_Limit + code, sd.rawData()); } +std::string currentJSStackToString(JSContext* cx) { + auto scope = getScope(cx); + + JS::RootedValue error(cx); + scope->getErrorProto().newInstance(&error); + + return ObjectWrapper(cx, error).getString("stack"); +} + Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason) { JS::RootedValue vp(cx); if (!JS_GetPendingException(cx, &vp)) diff --git a/src/mongo/scripting/mozjs/exception.h b/src/mongo/scripting/mozjs/exception.h index b3dcbfa67bbc0..820e389f0e0f0 100644 --- a/src/mongo/scripting/mozjs/exception.h +++ b/src/mongo/scripting/mozjs/exception.h @@ -62,6 +62,11 @@ Status JSErrorReportToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason); +/** + * Returns the current stack as a string + */ +std::string currentJSStackToString(JSContext* cx); + /** * Turns the current JS exception into a C++ exception * diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index 23b7d4c05fb09..4982ee4cc42f7 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -44,6 +44,7 @@ #include "mongo/stdx/mutex.h" #include "mongo/util/concurrency/threadlocal.h" #include "mongo/util/log.h" +#include "mongo/util/scopeguard.h" using namespace mongoutils; @@ -156,6 +157,9 @@ OperationContext* MozJSImplScope::getOpContext() const { bool MozJSImplScope::_interruptCallback(JSContext* cx) { auto scope = getScope(cx); + JS_SetInterruptCallback(scope->_runtime, nullptr); + auto guard = MakeGuard([&]() { JS_SetInterruptCallback(scope->_runtime, _interruptCallback); }); + if (scope->_pendingGC.load()) { scope->_pendingGC.store(false); JS_GC(scope->_runtime); @@ -168,6 +172,8 @@ bool MozJSImplScope::_interruptCallback(JSContext* cx) { if (kill) { scope->_engine->getDeadlineMonitor().stopDeadline(scope); scope->unregisterOperation(); + + scope->_status = Status(ErrorCodes::JSInterpreterFailure, "Interrupted by the host"); } return !kill; @@ -238,6 +244,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _dbQueryProto(_context), _dbProto(_context), _dbRefProto(_context), + _errorProto(_context), _jsThreadProto(_context), _maxKeyProto(_context), _minKeyProto(_context), @@ -664,6 +671,7 @@ void MozJSImplScope::installBSONTypes() { _bsonProto.install(_global); _dbPointerProto.install(_global); _dbRefProto.install(_global); + _errorProto.install(_global); _maxKeyProto.install(_global); _minKeyProto.install(_global); _nativeFunctionProto.install(_global); @@ -748,5 +756,13 @@ void MozJSImplScope::setOOM() { _status = Status(ErrorCodes::JSInterpreterFailure, "Out of memory"); } +void MozJSImplScope::setParentStack(std::string parentStack) { + _parentStack = std::move(parentStack); +} + +const std::string& MozJSImplScope::getParentStack() const { + return _parentStack; +} + } // namespace mozjs } // namespace mongo diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index 0c813e03960ff..a01365fa2d154 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -42,6 +42,7 @@ #include "mongo/scripting/mozjs/dbquery.h" #include "mongo/scripting/mozjs/dbref.h" #include "mongo/scripting/mozjs/engine.h" +#include "mongo/scripting/mozjs/error.h" #include "mongo/scripting/mozjs/global.h" #include "mongo/scripting/mozjs/jsthread.h" #include "mongo/scripting/mozjs/maxkey.h" @@ -186,6 +187,10 @@ class MozJSImplScope final : public Scope { return _dbRefProto; } + WrapType& getErrorProto() { + return _errorProto; + } + WrapType& getJSThreadProto() { return _jsThreadProto; } @@ -250,6 +255,8 @@ class MozJSImplScope final : public Scope { static MozJSImplScope* getThreadScope(); void setOOM(); + void setParentStack(std::string); + const std::string& getParentStack() const; private: void _MozJSCreateFunction(const char* raw, @@ -312,6 +319,7 @@ class MozJSImplScope final : public Scope { Status _status; int _exitCode; bool _quickExit; + std::string _parentStack; WrapType _binDataProto; WrapType _bsonProto; @@ -323,6 +331,7 @@ class MozJSImplScope final : public Scope { WrapType _dbQueryProto; WrapType _dbProto; WrapType _dbRefProto; + WrapType _errorProto; WrapType _jsThreadProto; WrapType _maxKeyProto; WrapType _minKeyProto; diff --git a/src/mongo/scripting/mozjs/jsthread.cpp b/src/mongo/scripting/mozjs/jsthread.cpp index 4a3b441a8854a..0813a369dc28c 100644 --- a/src/mongo/scripting/mozjs/jsthread.cpp +++ b/src/mongo/scripting/mozjs/jsthread.cpp @@ -78,6 +78,8 @@ class JSThreadConfig { public: JSThreadConfig(JSContext* cx, JS::CallArgs args) : _started(false), _done(false), _sharedData(new SharedData()) { + auto scope = getScope(cx); + uassert(ErrorCodes::JSInterpreterFailure, "need at least one argument", args.length() > 0); uassert(ErrorCodes::JSInterpreterFailure, "first argument must be a function", @@ -93,6 +95,12 @@ class JSThreadConfig { } _sharedData->_args = b.obj(); + + _sharedData->_stack = currentJSStackToString(cx); + + if (!scope->getParentStack().empty()) { + _sharedData->_stack = _sharedData->_stack + scope->getParentStack(); + } } void start() { @@ -150,12 +158,13 @@ class JSThreadConfig { } /** - * These two members aren't protected in any way, so you have to be - * mindful about how they're used. I.e. _args needs to be set before - * start() and _returnData can't be touched until after join(). + * These three members aren't protected in any way, so you have to be + * mindful about how they're used. I.e. _args/_stack need to be set + * before start() and _returnData can't be touched until after join(). */ BSONObj _args; BSONObj _returnData; + std::string _stack; private: stdx::mutex _erroredMutex; @@ -173,11 +182,13 @@ class JSThreadConfig { try { MozJSImplScope scope(static_cast(globalScriptEngine)); + scope.setParentStack(_sharedData->_stack); _sharedData->_returnData = scope.callThreadArgs(_sharedData->_args); } catch (...) { auto status = exceptionToStatus(); - log() << "js thread raised js exception: " << status.reason(); + log() << "js thread raised js exception: " << status.reason() + << _sharedData->_stack; _sharedData->setErrored(true); _sharedData->_returnData = BSON("ret" << BSONUndefined); }