summaryrefslogtreecommitdiff
path: root/src/mongo/scripting
diff options
context:
space:
mode:
authorDavid Percy <david.percy@mongodb.com>2014-12-03 13:14:22 -0500
committerDavid Percy <david.percy@mongodb.com>2014-12-10 11:13:10 -0500
commit2f1f5af37b460c45e399ee734f4c24134b4ac352 (patch)
treea9b21b43070db0c8a91e751bd3699d046f310b23 /src/mongo/scripting
parent326d27c5fb249f9d372cb46fd8b97d725b31553a (diff)
downloadmongo-2f1f5af37b460c45e399ee734f4c24134b4ac352.tar.gz
SERVER-16394 Fix JSThreadConfig leak
Diffstat (limited to 'src/mongo/scripting')
-rw-r--r--src/mongo/scripting/engine_v8-3.25.h2
-rw-r--r--src/mongo/scripting/engine_v8.h2
-rw-r--r--src/mongo/scripting/v8-3.25_utils.cpp144
-rw-r--r--src/mongo/scripting/v8_utils.cpp118
4 files changed, 161 insertions, 105 deletions
diff --git a/src/mongo/scripting/engine_v8-3.25.h b/src/mongo/scripting/engine_v8-3.25.h
index ac5dc6c95ea..c15a044051f 100644
--- a/src/mongo/scripting/engine_v8-3.25.h
+++ b/src/mongo/scripting/engine_v8-3.25.h
@@ -60,6 +60,7 @@ namespace mongo {
class V8ScriptEngine;
class V8Scope;
class BSONHolder;
+ class JSThreadConfig;
typedef v8::Local<v8::Value> (*v8Function)(V8Scope* scope,
const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -342,6 +343,7 @@ namespace mongo {
: conn(conn), cursor(cursor) { }
};
ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor;
+ ObjTracker<JSThreadConfig> jsThreadConfigTracker;
// These are all named after the JS constructor name + FT
v8::Local<v8::FunctionTemplate> ObjectIdFT() { return _ObjectIdFT.Get(_isolate); }
diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h
index 31eb2f0b0c1..31d63fa50b9 100644
--- a/src/mongo/scripting/engine_v8.h
+++ b/src/mongo/scripting/engine_v8.h
@@ -58,6 +58,7 @@ namespace mongo {
class V8ScriptEngine;
class V8Scope;
class BSONHolder;
+ class JSThreadConfig;
typedef v8::Handle<v8::Value> (*v8Function)(V8Scope* scope, const v8::Arguments& args);
@@ -335,6 +336,7 @@ namespace mongo {
: conn(conn), cursor(cursor) { }
};
ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor;
+ ObjTracker<JSThreadConfig> jsThreadConfigTracker;
// These are all named after the JS constructor name + FT
v8::Handle<v8::FunctionTemplate> ObjectIdFT() const { return _ObjectIdFT; }
diff --git a/src/mongo/scripting/v8-3.25_utils.cpp b/src/mongo/scripting/v8-3.25_utils.cpp
index cc4c11f1725..016d90834b4 100644
--- a/src/mongo/scripting/v8-3.25_utils.cpp
+++ b/src/mongo/scripting/v8-3.25_utils.cpp
@@ -102,7 +102,7 @@ namespace mongo {
bool newScope = false) :
_started(),
_done(),
- _newScope(newScope) {
+ _sharedData(new SharedData()) {
jsassert(args.Length() > 0, "need at least one argument");
jsassert(args[0]->IsFunction(), "first argument must be a function");
@@ -111,7 +111,7 @@ namespace mongo {
for(int i = 0; i < args.Length(); ++i) {
scope->v8ToMongoElement(b, "arg" + BSONObjBuilder::numStr(i), args[i]);
}
- _args = b.obj();
+ _sharedData->_args = b.obj();
}
~JSThreadConfig() {
@@ -129,29 +129,63 @@ namespace mongo {
_done = true;
}
+ /**
+ * Returns true if the JSThread terminated as a result of an error
+ * during its execution, and false otherwise. This operation does
+ * not block, nor does it require join() to have been called.
+ */
+ bool hasFailed() const {
+ jsassert(_started, "Thread not started");
+ return _sharedData->getErrored();
+ }
+
BSONObj returnData() {
if (!_done)
join();
- return _returnData;
+ return _sharedData->_returnData;
}
private:
+ /*
+ * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread
+ * garbage collects the JSThreadConfig before the JSThread has finished running), so any
+ * data shared between them has to go in a shared_ptr.
+ */
+ class SharedData {
+ public:
+ SharedData() : _errored(false) {}
+ BSONObj _args;
+ BSONObj _returnData;
+ void setErrored(bool value) {
+ boost::mutex::scoped_lock lck(_erroredMutex);
+ _errored = value;
+ }
+ bool getErrored() {
+ boost::mutex::scoped_lock lck(_erroredMutex);
+ return _errored;
+ }
+ private:
+ boost::mutex _erroredMutex;
+ bool _errored;
+ };
+
class JSThread {
public:
- JSThread(JSThreadConfig& config) : _config(config) {}
+ JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {}
void operator()() {
try {
- _config._scope.reset(static_cast<V8Scope*>(globalScriptEngine->newScope()));
- v8::Locker v8lock(_config._scope->getIsolate());
- v8::Isolate::Scope iscope(_config._scope->getIsolate());
- v8::HandleScope handle_scope(_config._scope->getIsolate());
- v8::Context::Scope context_scope(_config._scope->getContext());
-
- BSONObj args = _config._args;
+ scoped_ptr<V8Scope> scope(
+ static_cast<V8Scope*>(globalScriptEngine->newScope()));
+ v8::Locker v8lock(scope->getIsolate());
+ v8::Isolate::Scope iscope(scope->getIsolate());
+ v8::HandleScope handle_scope(scope->getIsolate());
+ v8::Context::Scope context_scope(scope->getContext());
+
+ BSONObj args = _sharedData->_args;
v8::Local<v8::Function> f =
v8::Local<v8::Function>::Cast( v8::Local<v8::Value>(
- _config._scope->mongoToV8Element(args.firstElement(), true)));
+ scope->mongoToV8Element(args.firstElement(), true)));
int argc = args.nFields() - 1;
// TODO SERVER-8016: properly allocate handles on the stack
@@ -160,85 +194,79 @@ namespace mongo {
it.next();
for(int i = 0; i < argc && i < 24; ++i) {
argv[i] = v8::Local<v8::Value>::New(
- _config._scope->getIsolate(),
- _config._scope->mongoToV8Element(*it, true));
+ scope->getIsolate(),
+ scope->mongoToV8Element(*it, true));
it.next();
}
v8::TryCatch try_catch;
v8::Local<v8::Value> ret =
- f->Call(_config._scope->getGlobal(), argc, argv);
+ f->Call(scope->getGlobal(), argc, argv);
if (ret.IsEmpty() || try_catch.HasCaught()) {
- string e = _config._scope->v8ExceptionToSTLString(&try_catch);
+ string e = scope->v8ExceptionToSTLString(&try_catch);
log() << "js thread raised js exception: " << e << endl;
- ret = v8::Undefined(_config._scope->getIsolate());
- // TODO propagate exceptions (or at least the fact that an exception was
- // thrown) to the calling js on either join() or returnData().
+ ret = v8::Undefined(scope->getIsolate());
+ _sharedData->setErrored(true);
}
// ret is translated to BSON to switch isolate
BSONObjBuilder b;
- _config._scope->v8ToMongoElement(b, "ret", ret);
- _config._returnData = b.obj();
+ scope->v8ToMongoElement(b, "ret", ret);
+ _sharedData->_returnData = b.obj();
}
catch (const DBException& e) {
// Keeping behavior the same as for js exceptions.
log() << "js thread threw c++ exception: " << e.toString();
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
catch (const std::exception& e) {
log() << "js thread threw c++ exception: " << e.what();
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
catch (...) {
log() << "js thread threw c++ non-exception";
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
}
private:
- JSThreadConfig& _config;
+ shared_ptr<SharedData> _sharedData;
};
bool _started;
bool _done;
- bool _newScope;
- BSONObj _args;
scoped_ptr<boost::thread> _thread;
- scoped_ptr<V8Scope> _scope;
- BSONObj _returnData;
+ shared_ptr<SharedData> _sharedData;
};
- v8::Local<v8::Value> ThreadInit(V8Scope* scope,
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- v8::Local<v8::Object> it = args.This();
- // NOTE I believe the passed JSThreadConfig will never be freed. If this
- // policy is changed, JSThread may no longer be able to store JSThreadConfig
- // by reference.
- it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"),
- v8::External::New(scope->getIsolate(),
- new JSThreadConfig(scope, args)));
- return v8::Undefined(scope->getIsolate());
- }
-
- v8::Local<v8::Value> ScopedThreadInit(V8Scope* scope,
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- v8::Local<v8::Object> it = args.This();
- // NOTE I believe the passed JSThreadConfig will never be freed. If this
- // policy is changed, JSThread may no longer be able to store JSThreadConfig
- // by reference.
- it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"),
- v8::External::New(scope->getIsolate(),
- new JSThreadConfig(scope, args, true)));
- return v8::Undefined(scope->getIsolate());
- }
-
JSThreadConfig *thisConfig(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::External> c = v8::Local<v8::External>::Cast(
args.This()->GetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(),
"_JSThreadConfig")));
- JSThreadConfig *config = (JSThreadConfig *)(c->Value());
+ JSThreadConfig *config = static_cast<boost::shared_ptr<JSThreadConfig>*>(c->Value())->get();
return config;
}
+ v8::Local<v8::Value> ThreadInit(V8Scope* scope,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ JSThreadConfig* config = new JSThreadConfig(scope, args);
+ v8::Local<v8::External> handle =
+ scope->jsThreadConfigTracker.track(scope->getIsolate(), args.This(), config);
+ args.This()->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"),
+ handle);
+
+ invariant(thisConfig(scope, args) == config);
+ return v8::Undefined(scope->getIsolate());
+ }
+
+ v8::Local<v8::Value> ScopedThreadInit(V8Scope* scope,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // NOTE: ScopedThread and Thread behave identically because a new V8 Isolate is always
+ // created.
+ return ThreadInit(scope, args);
+ }
+
v8::Local<v8::Value> ThreadStart(V8Scope* scope,
const v8::FunctionCallbackInfo<v8::Value>& args) {
thisConfig(scope, args)->start();
@@ -251,6 +279,13 @@ namespace mongo {
return v8::Undefined(scope->getIsolate());
}
+ // Indicates to the caller that the thread terminated as a result of an error.
+ v8::Local<v8::Value> ThreadHasFailed(V8Scope* scope,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ bool hasFailed = thisConfig(scope, args)->hasFailed();
+ return v8::Boolean::New(scope->getIsolate(), hasFailed);
+ }
+
v8::Local<v8::Value> ThreadReturnData(V8Scope* scope,
const v8::FunctionCallbackInfo<v8::Value>& args) {
BSONObj data = thisConfig(scope, args)->returnData();
@@ -268,6 +303,7 @@ namespace mongo {
scope->injectV8Function("init", ThreadInit, o);
scope->injectV8Function("start", ThreadStart, o);
scope->injectV8Function("join", ThreadJoin, o);
+ scope->injectV8Function("hasFailed", ThreadHasFailed, o);
scope->injectV8Function("returnData", ThreadReturnData, o);
return handle_scope.Escape(v8::Local<v8::Value>());
}
diff --git a/src/mongo/scripting/v8_utils.cpp b/src/mongo/scripting/v8_utils.cpp
index d31b3f94be5..e0113c07894 100644
--- a/src/mongo/scripting/v8_utils.cpp
+++ b/src/mongo/scripting/v8_utils.cpp
@@ -100,11 +100,10 @@ namespace mongo {
class JSThreadConfig {
public:
- JSThreadConfig(V8Scope* scope, const v8::Arguments& args, bool newScope = false) :
+ JSThreadConfig(V8Scope* scope, const v8::Arguments& args) :
_started(),
_done(),
- _errored(),
- _newScope(newScope) {
+ _sharedData(new SharedData()) {
jsassert(args.Length() > 0, "need at least one argument");
jsassert(args[0]->IsFunction(), "first argument must be a function");
@@ -113,7 +112,7 @@ namespace mongo {
for(int i = 0; i < args.Length(); ++i) {
scope->v8ToMongoElement(b, "arg" + BSONObjBuilder::numStr(i), args[i]);
}
- _args = b.obj();
+ _sharedData->_args = b.obj();
}
~JSThreadConfig() {
@@ -138,31 +137,55 @@ namespace mongo {
*/
bool hasFailed() const {
jsassert(_started, "Thread not started");
- return _errored;
+ return _sharedData->getErrored();
}
BSONObj returnData() {
if (!_done)
join();
- return _returnData;
+ return _sharedData->_returnData;
}
private:
+ /*
+ * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread
+ * garbage collects the JSThreadConfig before the JSThread has finished running), so any
+ * data shared between them has to go in a shared_ptr.
+ */
+ class SharedData {
+ public:
+ SharedData() : _errored(false) {}
+ BSONObj _args;
+ BSONObj _returnData;
+ void setErrored(bool value) {
+ boost::mutex::scoped_lock lck(_erroredMutex);
+ _errored = value;
+ }
+ bool getErrored() {
+ boost::mutex::scoped_lock lck(_erroredMutex);
+ return _errored;
+ }
+ private:
+ boost::mutex _erroredMutex;
+ bool _errored;
+ };
+
class JSThread {
public:
- JSThread(JSThreadConfig& config) : _config(config) {}
+ JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {}
void operator()() {
try {
- _config._scope.reset(static_cast<V8Scope*>(globalScriptEngine->newScope()));
- v8::Locker v8lock(_config._scope->getIsolate());
- v8::Isolate::Scope iscope(_config._scope->getIsolate());
+ scoped_ptr<V8Scope> scope(
+ static_cast<V8Scope*>(globalScriptEngine->newScope()));
+ v8::Locker v8lock(scope->getIsolate());
+ v8::Isolate::Scope iscope(scope->getIsolate());
v8::HandleScope handle_scope;
- v8::Context::Scope context_scope(_config._scope->getContext());
+ v8::Context::Scope context_scope(scope->getContext());
- BSONObj args = _config._args;
+ BSONObj args = _sharedData->_args;
v8::Local<v8::Function> f = v8::Function::Cast(
- *(_config._scope->mongoToV8Element(args.firstElement(), true)));
+ *(scope->mongoToV8Element(args.firstElement(), true)));
int argc = args.nFields() - 1;
// TODO SERVER-8016: properly allocate handles on the stack
@@ -171,53 +194,49 @@ namespace mongo {
it.next();
for(int i = 0; i < argc && i < 24; ++i) {
argv[i] = v8::Local<v8::Value>::New(
- _config._scope->mongoToV8Element(*it, true));
+ scope->mongoToV8Element(*it, true));
it.next();
}
v8::TryCatch try_catch;
v8::Handle<v8::Value> ret =
- f->Call(_config._scope->getContext()->Global(), argc, argv);
+ f->Call(scope->getContext()->Global(), argc, argv);
if (ret.IsEmpty() || try_catch.HasCaught()) {
- string e = _config._scope->v8ExceptionToSTLString(&try_catch);
+ string e = scope->v8ExceptionToSTLString(&try_catch);
log() << "js thread raised js exception: " << e << endl;
ret = v8::Undefined();
- _config._errored = true;
+ _sharedData->setErrored(true);
}
// ret is translated to BSON to switch isolate
BSONObjBuilder b;
- _config._scope->v8ToMongoElement(b, "ret", ret);
- _config._returnData = b.obj();
+ scope->v8ToMongoElement(b, "ret", ret);
+ _sharedData->_returnData = b.obj();
}
catch (const DBException& e) {
// Keeping behavior the same as for js exceptions.
log() << "js thread threw c++ exception: " << e.toString();
- _config._errored = true;
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
catch (const std::exception& e) {
log() << "js thread threw c++ exception: " << e.what();
- _config._errored = true;
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
catch (...) {
log() << "js thread threw c++ non-exception";
- _config._errored = true;
- _config._returnData = BSON("ret" << BSONUndefined);
+ _sharedData->setErrored(true);
+ _sharedData->_returnData = BSON("ret" << BSONUndefined);
}
}
private:
- JSThreadConfig& _config;
+ shared_ptr<SharedData> _sharedData;
};
bool _started;
bool _done;
- bool _errored;
- bool _newScope;
- BSONObj _args;
scoped_ptr<boost::thread> _thread;
- scoped_ptr<V8Scope> _scope;
- BSONObj _returnData;
+ shared_ptr<SharedData> _sharedData;
};
class CountDownLatchHolder {
@@ -308,31 +327,28 @@ namespace mongo {
return v8::Int32::New(globalCountDownLatchHolder.getCount(toSTLString(args[0])));
}
- v8::Handle<v8::Value> ThreadInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- // NOTE I believe the passed JSThreadConfig will never be freed. If this
- // policy is changed, JSThread may no longer be able to store JSThreadConfig
- // by reference.
- it->SetHiddenValue(v8::String::New("_JSThreadConfig"),
- v8::External::New(new JSThreadConfig(scope, args)));
- return v8::Undefined();
+ JSThreadConfig *thisConfig(V8Scope* scope, const v8::Arguments& args) {
+ v8::Local<v8::External> c = v8::External::Cast(
+ *(args.This()->GetHiddenValue(v8::String::New("_JSThreadConfig"))));
+ JSThreadConfig *config = static_cast<boost::shared_ptr<JSThreadConfig>*>(c->Value())->get();
+ return config;
}
- v8::Handle<v8::Value> ScopedThreadInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- // NOTE I believe the passed JSThreadConfig will never be freed. If this
- // policy is changed, JSThread may no longer be able to store JSThreadConfig
- // by reference.
- it->SetHiddenValue(v8::String::New("_JSThreadConfig"),
- v8::External::New(new JSThreadConfig(scope, args, true)));
+ v8::Handle<v8::Value> ThreadInit(V8Scope* scope, const v8::Arguments& args) {
+ v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This());
+
+ JSThreadConfig* config = new JSThreadConfig(scope, args);
+ v8::Local<v8::External> handle = scope->jsThreadConfigTracker.track(self, config);
+ args.This()->SetHiddenValue(v8::String::New("_JSThreadConfig"), handle);
+
+ invariant(thisConfig(scope, args) == config);
return v8::Undefined();
}
- JSThreadConfig *thisConfig(V8Scope* scope, const v8::Arguments& args) {
- v8::Local<v8::External> c = v8::External::Cast(
- *(args.This()->GetHiddenValue(v8::String::New("_JSThreadConfig"))));
- JSThreadConfig *config = (JSThreadConfig *)(c->Value());
- return config;
+ v8::Handle<v8::Value> ScopedThreadInit(V8Scope* scope, const v8::Arguments& args) {
+ // NOTE: ScopedThread and Thread behave identically because a new V8 Isolate is always
+ // created.
+ return ThreadInit(scope, args);
}
v8::Handle<v8::Value> ThreadStart(V8Scope* scope, const v8::Arguments& args) {