diff --git a/db/curop.h b/db/curop.h index eac982a46cb..c21478c1608 100644 --- a/db/curop.h +++ b/db/curop.h @@ -24,6 +24,7 @@ #include "../util/concurrency/spin_lock.h" #include "../util/time_support.h" #include "db.h" +#include "../scripting/engine.h" namespace mongo { @@ -348,15 +349,26 @@ namespace mongo { enum { Off, On, All } state; AtomicUInt toKill; public: - void killAll() { state = All; } - void kill(AtomicUInt i) { toKill = i; state = On; } + // the kill functions are not called with a mutex + void killAll() { state = All; interruptJs( 0 ); } + void kill(AtomicUInt i) { toKill = i; state = On; interruptJs( &i ); } + + // this is called without a mutex also, which means we can miss some + // kill requests; it's no worse than what the code was doing before, + // though, so I don't feel too bad about adding this function. + // hopefully we will have time for SERVER-1816 to fix this + void finishOp() { + if( state == On && cc().curop()->opNum() == toKill ) { + state = Off; + } + } void checkForInterrupt() { if( state != Off ) { - if( state == All ) + if( state == All ) { uasserted(11600,"interrupted at shutdown"); + } if( cc().curop()->opNum() == toKill ) { - state = Off; uasserted(11601,"interrupted"); } } @@ -367,11 +379,21 @@ namespace mongo { if( state == All ) return "interrupted at shutdown"; if( cc().curop()->opNum() == toKill ) { - state = Off; return "interrupted"; } } return ""; } + + void interruptJs( AtomicUInt *op ) { + if ( !globalScriptEngine ) { + return; + } + if ( !op ) { + globalScriptEngine->interruptAll(); + } else { + globalScriptEngine->interrupt( *op ); + } + } } killCurrentOp; } diff --git a/db/db.cpp b/db/db.cpp index b03651ef5b9..51aaea84372 100644 --- a/db/db.cpp +++ b/db/db.cpp @@ -458,7 +458,7 @@ sendmore: } void flushDiagLog(); - + /** * does background async flushes of mmapped files */ @@ -505,6 +505,10 @@ sendmore: return killCurrentOp.checkForInterruptNoAssert(); } + unsigned jsGetInterruptSpecCallback() { + return cc().curop()->opNum(); + } + void _initAndListen(int listenPort, const char *appserverLoc = NULL) { bool is32bit = sizeof(int*) == 4; @@ -563,6 +567,7 @@ sendmore: if ( useJNI ) { ScriptEngine::setup(); globalScriptEngine->setCheckInterruptCallback( jsInterruptCallback ); + globalScriptEngine->setGetInterruptSpecCallback( jsGetInterruptSpecCallback ); } repairDatabases(); diff --git a/db/instance.cpp b/db/instance.cpp index 1031c7f0c7b..ee2db5b151b 100644 --- a/db/instance.cpp +++ b/db/instance.cpp @@ -340,6 +340,7 @@ namespace mongo { } currentOp.ensureStarted(); currentOp.done(); + killCurrentOp.finishOp(); int ms = currentOp.totalTimeMillis(); log = log || (logLevel >= 2 && ++ctr % 512 == 0); diff --git a/jstests/evalc.js b/jstests/evalc.js index 59c94677d47..8a9e889c6ef 100644 --- a/jstests/evalc.js +++ b/jstests/evalc.js @@ -7,20 +7,6 @@ for( i = 0; i < 10; ++i ) { // SERVER-1610 -function op() { - uri = db.runCommand( "whatsmyuri" ).you; - printjson( uri ); - p = db.currentOp().inprog; - for ( var i in p ) { - var o = p[ i ]; - if ( o.client == uri ) { - print( "found it" ); - return o.opid; - } - } - return -1; -} - s = startParallelShell( "print( 'starting forked:' + Date() ); for ( i=0; i<500000; i++ ){ db.currentOp(); } print( 'ending forked:' + Date() ); " ) print( "starting eval: " + Date() ) diff --git a/jstests/evald.js b/jstests/evald.js index dd92357404c..8a76292cf1f 100644 --- a/jstests/evald.js +++ b/jstests/evald.js @@ -1,11 +1,8 @@ t = db.jstests_evald; t.drop(); -// only run in spidermonkey, not in v8 - see SERVER-387 -if ( typeof _threadInject == "undefined" ){ - function debug( x ) { -// print( x ); +// printjson( x ); } for( i = 0; i < 10; ++i ) { @@ -13,30 +10,62 @@ for( i = 0; i < 10; ++i ) { } db.getLastError(); -ev = "while( 1 ) { db.jstests_evald.count( {i:10} ); }" - -function op() { +function op( ev, where ) { p = db.currentOp().inprog; + debug( p ); for ( var i in p ) { var o = p[ i ]; - if ( o.active && o.query && o.query.$eval && o.query.$eval == ev ) { - return o.opid; + if ( where ) { + if ( o.active && o.query && o.query.query && o.query.query.$where && o.ns == "test.jstests_evald" ) { + return o.opid; + } + } else { + if ( o.active && o.query && o.query.$eval && o.query.$eval == ev ) { + return o.opid; + } } } return -1; } -s = startParallelShell( "db.eval( '" + ev + "' )" ); +function doIt( ev, wait, where ) { -o = null; -assert.soon( function() { o = op(); return o != -1 } ); + if ( where ) { + s = startParallelShell( ev ); + } else { + s = startParallelShell( "db.eval( '" + ev + "' )" ); + } -debug( "going to kill" ); + o = null; + assert.soon( function() { o = op( ev, where ); return o != -1 } ); -db.killOp( o ); + if ( wait ) { + sleep( 5000 ); + } -debug( "sent kill" ); + debug( "going to kill" ); -s(); + db.killOp( o ); -} \ No newline at end of file + debug( "sent kill" ); + + s(); + +} + +doIt( "db.jstests_evald.count( { $where: function() { while( 1 ) { ; } } } )", true, true ); +doIt( "db.jstests_evald.count( { $where: function() { while( 1 ) { ; } } } )", false, true ); +doIt( "while( true ) {;}", false ); +doIt( "while( true ) {;}", true ); +doIt( "while( 1 ) { db.jstests_evald.count( {i:10} ); }", true ); +doIt( "while( 1 ) { db.jstests_evald.count( {i:10} ); }", false ); +doIt( "while( 1 ) { db.jstests_evald.count(); }", true ); +doIt( "while( 1 ) { db.jstests_evald.count(); }", false ); + +// these two are SERVER-1841 +//doIt( "while( 1 ) { try { db.jstests_evald.count( {i:10} ); } catch ( e ) { } }", true ); +//doIt( "while( 1 ) { try { while( 1 ) { ; } } catch ( e ) { } }", true ); + +// the following won't work in v8 currently, see SERVER-1840 +//doIt( "while( 1 ) { db.jstests_evald.count( {$where:function(){ while( 1 ) {;}} } ); }", false ); +//doIt( "while( 1 ) { db.jstests_evald.count( {$where:function(){ while( 1 ) {;}} } ); }", true ); diff --git a/scripting/engine.cpp b/scripting/engine.cpp index a75bf25fc7b..ae67b469029 100644 --- a/scripting/engine.cpp +++ b/scripting/engine.cpp @@ -444,8 +444,9 @@ namespace mongo { void ( *ScriptEngine::_connectCallback )( DBClientWithCommands & ) = 0; const char * ( *ScriptEngine::_checkInterruptCallback )() = 0; + unsigned ( *ScriptEngine::_getInterruptSpecCallback )() = 0; - ScriptEngine * globalScriptEngine; + ScriptEngine * globalScriptEngine = 0; bool hasJSReturn( const string& code ){ size_t x = code.find( "return" ); diff --git a/scripting/engine.h b/scripting/engine.h index b8265aa4b2e..e9ffd98caba 100644 --- a/scripting/engine.h +++ b/scripting/engine.h @@ -197,11 +197,30 @@ namespace mongo { if ( _connectCallback ) _connectCallback( c ); } + + // engine implementation may either respond to interrupt events or + // poll for interrupts + + // the interrupt functions must not wait indefinitely on a lock + virtual void interrupt( unsigned opSpec ) {} + virtual void interruptAll() {} + + static void setGetInterruptSpecCallback( unsigned ( *func )() ) { _getInterruptSpecCallback = func; } + static bool haveGetInterruptSpecCallback() { return _getInterruptSpecCallback; } + static unsigned getInterruptSpec() { + massert( 13474, "no _getInterruptSpecCallback", _getInterruptSpecCallback ); + return _getInterruptSpecCallback(); + } + static void setCheckInterruptCallback( const char * ( *func )() ) { _checkInterruptCallback = func; } static bool haveCheckInterruptCallback() { return _checkInterruptCallback; } static const char * checkInterrupt() { return _checkInterruptCallback ? _checkInterruptCallback() : ""; } + static bool interrupted() { + const char *r = checkInterrupt(); + return r && r[ 0 ]; + } protected: virtual Scope * createScope() = 0; @@ -210,6 +229,7 @@ namespace mongo { void ( *_scopeInitCallback )( Scope & ); static void ( *_connectCallback )( DBClientWithCommands & ); static const char * ( *_checkInterruptCallback )(); + static unsigned ( *_getInterruptSpecCallback )(); }; bool hasJSReturn( const string& s ); diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp index 217ca7f4977..92312008af4 100644 --- a/scripting/engine_v8.cpp +++ b/scripting/engine_v8.cpp @@ -21,10 +21,13 @@ #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() {} @@ -37,6 +40,23 @@ namespace mongo { 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 --- @@ -44,7 +64,7 @@ namespace mongo { : _engine( engine ) , _connectState( NOT ){ - Locker l; + V8Lock l; HandleScope handleScope; _context = Context::New(); Context::Scope context_scope( _context ); @@ -67,7 +87,7 @@ namespace mongo { } V8Scope::~V8Scope(){ - Locker l; + V8Lock l; Context::Scope context_scope( _context ); _wrapper.Dispose(); _this.Dispose(); @@ -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()); @@ -102,7 +122,7 @@ namespace mongo { } Handle< Value > V8Scope::loadCallback( const Arguments &args ) { - Locker l; + V8Lock l; HandleScope handle_scope; Handle field = Handle::Cast(args.Data()); void* ptr = field->Value(); @@ -121,7 +141,7 @@ namespace mongo { // ---- global stuff ---- void V8Scope::init( BSONObj * data ){ - Locker l; + V8Lock l; if ( ! data ) return; @@ -254,6 +274,7 @@ namespace mongo { code = fn + " = " + code; TryCatch try_catch; + // this might be time consuming, consider allowing an interrupt Handle