diff options
Diffstat (limited to 'scripting/engine_v8.cpp')
-rw-r--r-- | scripting/engine_v8.cpp | 323 |
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 |