Skip to content

Commit

Permalink
SERVER-19935 Make JSThread stacks more informative
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hanumantmk committed Aug 14, 2015
1 parent d156ce8 commit 35cb5b7
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/mongo/scripting/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
39 changes: 39 additions & 0 deletions src/mongo/scripting/mozjs/error.cpp
Original file line number Diff line number Diff line change
@@ -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 <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/scripting/mozjs/error.h"

namespace mongo {
namespace mozjs {

const char* const ErrorInfo::className = "Error";

} // namespace mozjs
} // namespace mongo
49 changes: 49 additions & 0 deletions src/mongo/scripting/mozjs/error.h
Original file line number Diff line number Diff line change
@@ -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 <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/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
11 changes: 11 additions & 0 deletions src/mongo/scripting/mozjs/exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
#include <jsfriendapi.h>
#include <limits>

#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 {
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions src/mongo/scripting/mozjs/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
16 changes: 16 additions & 0 deletions src/mongo/scripting/mozjs/implscope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -238,6 +244,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine)
_dbQueryProto(_context),
_dbProto(_context),
_dbRefProto(_context),
_errorProto(_context),
_jsThreadProto(_context),
_maxKeyProto(_context),
_minKeyProto(_context),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
9 changes: 9 additions & 0 deletions src/mongo/scripting/mozjs/implscope.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -186,6 +187,10 @@ class MozJSImplScope final : public Scope {
return _dbRefProto;
}

WrapType<ErrorInfo>& getErrorProto() {
return _errorProto;
}

WrapType<JSThreadInfo>& getJSThreadProto() {
return _jsThreadProto;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -312,6 +319,7 @@ class MozJSImplScope final : public Scope {
Status _status;
int _exitCode;
bool _quickExit;
std::string _parentStack;

WrapType<BinDataInfo> _binDataProto;
WrapType<BSONInfo> _bsonProto;
Expand All @@ -323,6 +331,7 @@ class MozJSImplScope final : public Scope {
WrapType<DBQueryInfo> _dbQueryProto;
WrapType<DBInfo> _dbProto;
WrapType<DBRefInfo> _dbRefProto;
WrapType<ErrorInfo> _errorProto;
WrapType<JSThreadInfo> _jsThreadProto;
WrapType<MaxKeyInfo> _maxKeyProto;
WrapType<MinKeyInfo> _minKeyProto;
Expand Down
19 changes: 15 additions & 4 deletions src/mongo/scripting/mozjs/jsthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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() {
Expand Down Expand Up @@ -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;
Expand All @@ -173,11 +182,13 @@ class JSThreadConfig {
try {
MozJSImplScope scope(static_cast<MozJSScriptEngine*>(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);
}
Expand Down

0 comments on commit 35cb5b7

Please sign in to comment.