summaryrefslogtreecommitdiff
path: root/scripting/engine_v8.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scripting/engine_v8.cpp')
-rw-r--r--scripting/engine_v8.cpp323
1 files changed, 199 insertions, 124 deletions
diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp
index 08826b1..cd186b4 100644
--- a/scripting/engine_v8.cpp
+++ b/scripting/engine_v8.cpp
@@ -1,4 +1,4 @@
-//engine_v8.cpp
+//engine_v8.cpp
/* Copyright 2009 10gen Inc.
*
@@ -21,54 +21,74 @@
#include "v8_utils.h"
#include "v8_db.h"
-#define V8_SIMPLE_HEADER Locker l; HandleScope handle_scope; Context::Scope context_scope( _context );
+#define V8_SIMPLE_HEADER V8Lock l; HandleScope handle_scope; Context::Scope context_scope( _context );
namespace mongo {
+ // guarded by v8 mutex
+ map< unsigned, int > __interruptSpecToThreadId;
+
// --- engine ---
V8ScriptEngine::V8ScriptEngine() {}
-
- V8ScriptEngine::~V8ScriptEngine(){
+
+ V8ScriptEngine::~V8ScriptEngine() {
}
- void ScriptEngine::setup(){
- if ( !globalScriptEngine ){
+ void ScriptEngine::setup() {
+ if ( !globalScriptEngine ) {
globalScriptEngine = new V8ScriptEngine();
}
}
+ void V8ScriptEngine::interrupt( unsigned opSpec ) {
+ v8::Locker l;
+ if ( __interruptSpecToThreadId.count( opSpec ) ) {
+ V8::TerminateExecution( __interruptSpecToThreadId[ opSpec ] );
+ }
+ }
+ void V8ScriptEngine::interruptAll() {
+ v8::Locker l;
+ vector< int > toKill; // v8 mutex could potentially be yielded during the termination call
+ for( map< unsigned, int >::const_iterator i = __interruptSpecToThreadId.begin(); i != __interruptSpecToThreadId.end(); ++i ) {
+ toKill.push_back( i->second );
+ }
+ for( vector< int >::const_iterator i = toKill.begin(); i != toKill.end(); ++i ) {
+ V8::TerminateExecution( *i );
+ }
+ }
+
// --- scope ---
-
- V8Scope::V8Scope( V8ScriptEngine * engine )
- : _engine( engine ) ,
- _connectState( NOT ){
- Locker l;
- HandleScope handleScope;
+ V8Scope::V8Scope( V8ScriptEngine * engine )
+ : _engine( engine ) ,
+ _connectState( NOT ) {
+
+ V8Lock l;
+ HandleScope handleScope;
_context = Context::New();
Context::Scope context_scope( _context );
_global = Persistent< v8::Object >::New( _context->Global() );
_this = Persistent< v8::Object >::New( v8::Object::New() );
- _global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)->GetFunction() );
- _global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)->GetFunction() );
+ _global->Set(v8::String::New("print"), newV8Function< Print >()->GetFunction() );
+ _global->Set(v8::String::New("version"), newV8Function< Version >()->GetFunction() );
_global->Set(v8::String::New("load"),
- v8::FunctionTemplate::New(loadCallback, v8::External::New(this))->GetFunction() );
-
+ v8::FunctionTemplate::New( v8Callback< loadCallback >, v8::External::New(this))->GetFunction() );
+
_wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate()->GetFunction() );
-
- _global->Set(v8::String::New("gc"), v8::FunctionTemplate::New(GCV8)->GetFunction() );
+
+ _global->Set(v8::String::New("gc"), newV8Function< GCV8 >()->GetFunction() );
installDBTypes( _global );
}
- V8Scope::~V8Scope(){
- Locker l;
- Context::Scope context_scope( _context );
+ V8Scope::~V8Scope() {
+ V8Lock l;
+ Context::Scope context_scope( _context );
_wrapper.Dispose();
_this.Dispose();
for( unsigned i = 0; i < _funcs.size(); ++i )
@@ -79,7 +99,7 @@ namespace mongo {
}
Handle< Value > V8Scope::nativeCallback( const Arguments &args ) {
- Locker l;
+ V8Lock l;
HandleScope handle_scope;
Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) );
NativeFunction function = (NativeFunction)(f->Value());
@@ -93,16 +113,18 @@ namespace mongo {
BSONObj ret;
try {
ret = function( nativeArgs );
- } catch( const std::exception &e ) {
+ }
+ catch( const std::exception &e ) {
return v8::ThrowException(v8::String::New(e.what()));
- } catch( ... ) {
- return v8::ThrowException(v8::String::New("unknown exception"));
+ }
+ catch( ... ) {
+ return v8::ThrowException(v8::String::New("unknown exception"));
}
return handle_scope.Close( mongoToV8Element( ret.firstElement() ) );
}
Handle< Value > V8Scope::loadCallback( const Arguments &args ) {
- Locker l;
+ V8Lock l;
HandleScope handle_scope;
Handle<External> field = Handle<External>::Cast(args.Data());
void* ptr = field->Value();
@@ -120,46 +142,46 @@ namespace mongo {
// ---- global stuff ----
- void V8Scope::init( BSONObj * data ){
- Locker l;
+ void V8Scope::init( const BSONObj * data ) {
+ V8Lock l;
if ( ! data )
return;
-
+
BSONObjIterator i( *data );
- while ( i.more() ){
+ while ( i.more() ) {
BSONElement e = i.next();
setElement( e.fieldName() , e );
}
}
-
- void V8Scope::setNumber( const char * field , double val ){
+
+ void V8Scope::setNumber( const char * field , double val ) {
V8_SIMPLE_HEADER
_global->Set( v8::String::New( field ) , v8::Number::New( val ) );
}
- void V8Scope::setString( const char * field , const char * val ){
+ void V8Scope::setString( const char * field , const char * val ) {
V8_SIMPLE_HEADER
_global->Set( v8::String::New( field ) , v8::String::New( val ) );
}
- void V8Scope::setBoolean( const char * field , bool val ){
+ void V8Scope::setBoolean( const char * field , bool val ) {
V8_SIMPLE_HEADER
_global->Set( v8::String::New( field ) , v8::Boolean::New( val ) );
}
- void V8Scope::setElement( const char *field , const BSONElement& e ){
+ void V8Scope::setElement( const char *field , const BSONElement& e ) {
V8_SIMPLE_HEADER
_global->Set( v8::String::New( field ) , mongoToV8Element( e ) );
}
- void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly){
+ void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly) {
V8_SIMPLE_HEADER
// Set() accepts a ReadOnly parameter, but this just prevents the field itself
// from being overwritten and doesn't protect the object stored in 'field'.
_global->Set( v8::String::New( field ) , mongoToV8( obj, false, readOnly) );
}
- int V8Scope::type( const char *field ){
+ int V8Scope::type( const char *field ) {
V8_SIMPLE_HEADER
Handle<Value> v = get( field );
if ( v->IsNull() )
@@ -178,7 +200,7 @@ namespace mongo {
return NumberInt;
if ( v->IsNumber() )
return NumberDouble;
- if ( v->IsExternal() ){
+ if ( v->IsExternal() ) {
uassert( 10230 , "can't handle external yet" , 0 );
return -1;
}
@@ -190,36 +212,36 @@ namespace mongo {
throw UserException( 12509, (string)"don't know what this is: " + field );
}
- v8::Handle<v8::Value> V8Scope::get( const char * field ){
+ v8::Handle<v8::Value> V8Scope::get( const char * field ) {
return _global->Get( v8::String::New( field ) );
}
- double V8Scope::getNumber( const char *field ){
+ double V8Scope::getNumber( const char *field ) {
V8_SIMPLE_HEADER
return get( field )->ToNumber()->Value();
}
- int V8Scope::getNumberInt( const char *field ){
+ int V8Scope::getNumberInt( const char *field ) {
V8_SIMPLE_HEADER
return get( field )->ToInt32()->Value();
}
- long long V8Scope::getNumberLongLong( const char *field ){
+ long long V8Scope::getNumberLongLong( const char *field ) {
V8_SIMPLE_HEADER
return get( field )->ToInteger()->Value();
}
- string V8Scope::getString( const char *field ){
+ string V8Scope::getString( const char *field ) {
V8_SIMPLE_HEADER
return toSTLString( get( field ) );
}
- bool V8Scope::getBoolean( const char *field ){
+ bool V8Scope::getBoolean( const char *field ) {
V8_SIMPLE_HEADER
return get( field )->ToBoolean()->Value();
}
-
- BSONObj V8Scope::getObject( const char * field ){
+
+ BSONObj V8Scope::getObject( const char * field ) {
V8_SIMPLE_HEADER
Handle<Value> v = get( field );
if ( v->IsNull() || v->IsUndefined() )
@@ -227,21 +249,28 @@ namespace mongo {
uassert( 10231 , "not an object" , v->IsObject() );
return v8ToMongo( v->ToObject() );
}
-
+
// --- functions -----
- Local< v8::Function > V8Scope::__createFunction( const char * raw ){
- for(; isspace( *raw ); ++raw ); // skip whitespace
+ bool hasFunctionIdentifier( const string& code ) {
+ if ( code.size() < 9 || code.find( "function" ) != 0 )
+ return false;
+
+ return code[8] == ' ' || code[8] == '(';
+ }
+
+ Local< v8::Function > V8Scope::__createFunction( const char * raw ) {
+ raw = jsSkipWhiteSpace( raw );
string code = raw;
- if ( code.find( "function" ) == string::npos ){
- if ( code.find( "\n" ) == string::npos &&
- ! hasJSReturn( code ) &&
- ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ){
+ if ( !hasFunctionIdentifier( code ) ) {
+ if ( code.find( "\n" ) == string::npos &&
+ ! hasJSReturn( code ) &&
+ ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ) {
code = "return " + code;
}
code = "function(){ " + code + "}";
}
-
+
int num = _funcs.size() + 1;
string fn;
@@ -250,29 +279,30 @@ namespace mongo {
ss << "_funcs" << num;
fn = ss.str();
}
-
+
code = fn + " = " + code;
TryCatch try_catch;
- Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) ,
- v8::String::New( fn.c_str() ) );
- if ( script.IsEmpty() ){
+ // this might be time consuming, consider allowing an interrupt
+ Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) ,
+ v8::String::New( fn.c_str() ) );
+ if ( script.IsEmpty() ) {
_error = (string)"compile error: " + toSTLString( &try_catch );
log() << _error << endl;
return Local< v8::Function >();
}
-
+
Local<Value> result = script->Run();
- if ( result.IsEmpty() ){
+ if ( result.IsEmpty() ) {
_error = (string)"compile error: " + toSTLString( &try_catch );
log() << _error << endl;
return Local< v8::Function >();
- }
-
+ }
+
return v8::Function::Cast( *_global->Get( v8::String::New( fn.c_str() ) ) );
}
-
- ScriptingFunction V8Scope::_createFunction( const char * raw ){
+
+ ScriptingFunction V8Scope::_createFunction( const char * raw ) {
V8_SIMPLE_HEADER
Local< Value > ret = __createFunction( raw );
if ( ret.IsEmpty() )
@@ -284,9 +314,9 @@ namespace mongo {
return num;
}
- void V8Scope::setThis( const BSONObj * obj ){
+ void V8Scope::setThis( const BSONObj * obj ) {
V8_SIMPLE_HEADER
- if ( ! obj ){
+ if ( ! obj ) {
_this = Persistent< v8::Object >::New( v8::Object::New() );
return;
}
@@ -296,57 +326,80 @@ namespace mongo {
argv[0] = v8::External::New( createWrapperHolder( obj , true , false ) );
_this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) );
}
-
- int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ){
+
+ void V8Scope::rename( const char * from , const char * to ) {
+ V8_SIMPLE_HEADER;
+ v8::Local<v8::String> f = v8::String::New( from );
+ v8::Local<v8::String> t = v8::String::New( to );
+ _global->Set( t , _global->Get( f ) );
+ _global->Set( f , v8::Undefined() );
+ }
+
+ int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ) {
V8_SIMPLE_HEADER
Handle<Value> funcValue = _funcs[func-1];
-
- TryCatch try_catch;
+
+ TryCatch try_catch;
int nargs = argsObject.nFields();
scoped_array< Handle<Value> > args;
- if ( nargs ){
+ if ( nargs ) {
args.reset( new Handle<Value>[nargs] );
BSONObjIterator it( argsObject );
- for ( int i=0; i<nargs; i++ ){
+ for ( int i=0; i<nargs; i++ ) {
BSONElement next = it.next();
args[i] = mongoToV8Element( next );
}
setObject( "args", argsObject, true ); // for backwards compatibility
- } else {
+ }
+ else {
_global->Set( v8::String::New( "args" ), v8::Undefined() );
}
+ if ( globalScriptEngine->interrupted() ) {
+ stringstream ss;
+ ss << "error in invoke: " << globalScriptEngine->checkInterrupt();
+ _error = ss.str();
+ log() << _error << endl;
+ return 1;
+ }
+ enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable
Local<Value> result = ((v8::Function*)(*funcValue))->Call( _this , nargs , args.get() );
-
- if ( result.IsEmpty() ){
+ disableV8Interrupt();
+
+ if ( result.IsEmpty() ) {
stringstream ss;
- ss << "error in invoke: " << toSTLString( &try_catch );
+ if ( try_catch.HasCaught() && !try_catch.CanContinue() ) {
+ ss << "error in invoke: " << globalScriptEngine->checkInterrupt();
+ }
+ else {
+ ss << "error in invoke: " << toSTLString( &try_catch );
+ }
_error = ss.str();
log() << _error << endl;
return 1;
}
- if ( ! ignoreReturn ){
+ if ( ! ignoreReturn ) {
_global->Set( v8::String::New( "return" ) , result );
}
return 0;
}
- bool V8Scope::exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){
- if ( timeoutMs ){
+ bool V8Scope::exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) {
+ if ( timeoutMs ) {
static bool t = 1;
- if ( t ){
- log() << "timeoutMs not support for v8 yet" << endl;
+ if ( t ) {
+ log() << "timeoutMs not support for v8 yet code: " << code << endl;
t = 0;
}
}
-
+
V8_SIMPLE_HEADER
-
+
TryCatch try_catch;
-
- Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) ,
- v8::String::New( name.c_str() ) );
+
+ Handle<Script> script = v8::Script::Compile( v8::String::New( code.data() ) ,
+ v8::String::New( name.c_str() ) );
if (script.IsEmpty()) {
stringstream ss;
ss << "compile error: " << toSTLString( &try_catch );
@@ -356,65 +409,87 @@ namespace mongo {
if ( assertOnError )
uassert( 10233 , _error , 0 );
return false;
- }
-
+ }
+
+ if ( globalScriptEngine->interrupted() ) {
+ _error = (string)"exec error: " + globalScriptEngine->checkInterrupt();
+ if ( reportError ) {
+ log() << _error << endl;
+ }
+ if ( assertOnError ) {
+ uassert( 13475 , _error , 0 );
+ }
+ return false;
+ }
+ enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable
Handle<v8::Value> result = script->Run();
- if ( result.IsEmpty() ){
- _error = (string)"exec error: " + toSTLString( &try_catch );
+ disableV8Interrupt();
+ if ( result.IsEmpty() ) {
+ if ( try_catch.HasCaught() && !try_catch.CanContinue() ) {
+ _error = (string)"exec error: " + globalScriptEngine->checkInterrupt();
+ }
+ else {
+ _error = (string)"exec error: " + toSTLString( &try_catch );
+ }
if ( reportError )
log() << _error << endl;
if ( assertOnError )
uassert( 10234 , _error , 0 );
return false;
- }
-
+ }
+
_global->Set( v8::String::New( "__lastres__" ) , result );
- if ( printResult && ! result->IsUndefined() ){
+ if ( printResult && ! result->IsUndefined() ) {
cout << toSTLString( result ) << endl;
}
-
+
return true;
}
-
- void V8Scope::injectNative( const char *field, NativeFunction func ){
+
+ void V8Scope::injectNative( const char *field, NativeFunction func ) {
V8_SIMPLE_HEADER
-
- Handle< FunctionTemplate > f( v8::FunctionTemplate::New( nativeCallback ) );
+
+ Handle< FunctionTemplate > f( newV8Function< nativeCallback >() );
f->Set( v8::String::New( "_native_function" ), External::New( (void*)func ) );
_global->Set( v8::String::New( field ), f->GetFunction() );
- }
-
+ }
+
void V8Scope::gc() {
cout << "in gc" << endl;
- Locker l;
- while( V8::IdleNotification() );
+ V8Lock l;
+ while( !V8::IdleNotification() );
}
// ----- db access -----
- void V8Scope::localConnect( const char * dbName ){
- V8_SIMPLE_HEADER
+ void V8Scope::localConnect( const char * dbName ) {
+ {
+ V8_SIMPLE_HEADER
+
+ if ( _connectState == EXTERNAL )
+ throw UserException( 12510, "externalSetup already called, can't call externalSetup" );
+ if ( _connectState == LOCAL ) {
+ if ( _localDBName == dbName )
+ return;
+ throw UserException( 12511, "localConnect called with a different name previously" );
+ }
- if ( _connectState == EXTERNAL )
- throw UserException( 12510, "externalSetup already called, can't call externalSetup" );
- if ( _connectState == LOCAL ){
- if ( _localDBName == dbName )
- return;
- throw UserException( 12511, "localConnect called with a different name previously" );
- }
+ // needed for killop / interrupt support
+ v8::Locker::StartPreemption( 50 );
- //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() );
- _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() );
- exec( jsconcatcode , "localConnect 1" , false , true , true , 0 );
- exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 );
- exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 );
- _connectState = LOCAL;
- _localDBName = dbName;
+ //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() );
+ _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() );
+ execCoreFiles();
+ exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 );
+ exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 );
+ _connectState = LOCAL;
+ _localDBName = dbName;
+ }
loadStored();
}
-
- void V8Scope::externalSetup(){
+
+ void V8Scope::externalSetup() {
V8_SIMPLE_HEADER
if ( _connectState == EXTERNAL )
return;
@@ -423,18 +498,18 @@ namespace mongo {
installFork( _global, _context );
_global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( false )->GetFunction() );
- exec( jsconcatcode , "shell setup" , false , true , true , 0 );
+ execCoreFiles();
_connectState = EXTERNAL;
}
// ----- internal -----
- void V8Scope::reset(){
+ void V8Scope::reset() {
_startCall();
}
- void V8Scope::_startCall(){
+ void V8Scope::_startCall() {
_error = "";
}
-
+
} // namespace mongo