0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 17:10:48 +01:00
mongodb/scripting/v8_utils.cpp

306 lines
11 KiB
C++

// v8_utils.cpp
/* Copyright 2009 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "v8_utils.h"
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <boost/smart_ptr.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
#include "engine_v8.h"
using namespace std;
using namespace v8;
namespace mongo {
Handle<v8::Value> Print(const Arguments& args) {
bool first = true;
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope;
if (first) {
first = false;
} else {
printf(" ");
}
v8::String::Utf8Value str(args[i]);
printf("%s", *str);
}
printf("\n");
return v8::Undefined();
}
std::string toSTLString( const Handle<v8::Value> & o ){
v8::String::Utf8Value str(o);
const char * foo = *str;
std::string s(foo);
return s;
}
std::string toSTLString( const v8::TryCatch * try_catch ){
stringstream ss;
//while ( try_catch ){ // disabled for v8 bleeding edge
v8::String::Utf8Value exception(try_catch->Exception());
Handle<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
ss << *exception << endl;
}
else {
v8::String::Utf8Value filename(message->GetScriptResourceName());
int linenum = message->GetLineNumber();
ss << *filename << ":" << linenum << " " << *exception << endl;
v8::String::Utf8Value sourceline(message->GetSourceLine());
ss << *sourceline << endl;
int start = message->GetStartColumn();
for (int i = 0; i < start; i++)
ss << " ";
int end = message->GetEndColumn();
for (int i = start; i < end; i++)
ss << "^";
ss << endl;
}
//try_catch = try_catch->next_;
//}
return ss.str();
}
std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ){
v8::String::Utf8Value str(o);
s << *str;
return s;
}
std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){
HandleScope handle_scope;
v8::String::Utf8Value exception(try_catch->Exception());
Handle<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
s << *exception << endl;
}
else {
v8::String::Utf8Value filename(message->GetScriptResourceName());
int linenum = message->GetLineNumber();
cout << *filename << ":" << linenum << " " << *exception << endl;
v8::String::Utf8Value sourceline(message->GetSourceLine());
cout << *sourceline << endl;
int start = message->GetStartColumn();
for (int i = 0; i < start; i++)
cout << " ";
int end = message->GetEndColumn();
for (int i = start; i < end; i++)
cout << "^";
cout << endl;
}
//if ( try_catch->next_ ) // disabled for v8 bleeding edge
// s << try_catch->next_;
return s;
}
Handle<v8::Value> Version(const Arguments& args) {
HandleScope handle_scope;
return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) );
}
void ReportException(v8::TryCatch* try_catch) {
cout << try_catch << endl;
}
Handle< Context > baseContext_;
class JSThreadConfig {
public:
JSThreadConfig( const Arguments &args, bool newScope = false ) : started_(), done_(), newScope_( newScope ) {
jsassert( args.Length() > 0, "need at least one argument" );
jsassert( args[ 0 ]->IsFunction(), "first argument must be a function" );
Local< v8::Function > f = v8::Function::Cast( *args[ 0 ] );
f_ = Persistent< v8::Function >::New( f );
for( int i = 1; i < args.Length(); ++i )
args_.push_back( Persistent< Value >::New( args[ i ] ) );
}
~JSThreadConfig() {
f_.Dispose();
for( vector< Persistent< Value > >::iterator i = args_.begin(); i != args_.end(); ++i )
i->Dispose();
returnData_.Dispose();
}
void start() {
jsassert( !started_, "Thread already started" );
JSThread jt( *this );
thread_.reset( new boost::thread( jt ) );
started_ = true;
}
void join() {
jsassert( started_ && !done_, "Thread not running" );
Unlocker u;
thread_->join();
done_ = true;
}
Local< Value > returnData() {
if ( !done_ )
join();
return Local< Value >::New( returnData_ );
}
private:
class JSThread {
public:
JSThread( JSThreadConfig &config ) : config_( config ) {}
void operator()() {
Locker l;
HandleScope handle_scope;
Handle< Context > context;
Handle< v8::Function > fun;
auto_ptr< V8Scope > scope;
if ( config_.newScope_ ) {
scope.reset( dynamic_cast< V8Scope * >( globalScriptEngine->newScope() ) );
context = scope->context();
// A v8::Function tracks the context in which it was created, so we have to
// create a new function in the new context.
Context::Scope baseScope( baseContext_ );
string fCode = toSTLString( config_.f_->ToString() );
Context::Scope context_scope( context );
fun = scope->__createFunction( fCode.c_str() );
} else {
context = baseContext_;
Context::Scope context_scope( context );
fun = config_.f_;
}
Context::Scope context_scope( context );
boost::scoped_array< Local< Value > > argv( new Local< Value >[ config_.args_.size() ] );
for( unsigned int i = 0; i < config_.args_.size(); ++i )
argv[ i ] = Local< Value >::New( config_.args_[ i ] );
TryCatch try_catch;
Handle< Value > ret = fun->Call( context->Global(), config_.args_.size(), argv.get() );
if ( ret.IsEmpty() ) {
string e = toSTLString( &try_catch );
log() << "js thread raised exception: " << e << endl;
// v8 probably does something sane if ret is empty, but not going to assume that for now
ret = v8::Undefined();
}
config_.returnData_ = Persistent< Value >::New( ret );
}
private:
JSThreadConfig &config_;
};
bool started_;
bool done_;
bool newScope_;
Persistent< v8::Function > f_;
vector< Persistent< Value > > args_;
auto_ptr< boost::thread > thread_;
Persistent< Value > returnData_;
};
Handle< Value > ThreadInit( const Arguments &args ) {
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" ), External::New( new JSThreadConfig( args ) ) );
return v8::Undefined();
}
Handle< Value > ScopedThreadInit( const Arguments &args ) {
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" ), External::New( new JSThreadConfig( args, true ) ) );
return v8::Undefined();
}
JSThreadConfig *thisConfig( const Arguments &args ) {
Local< External > c = External::Cast( *(args.This()->GetHiddenValue( v8::String::New( "_JSThreadConfig" ) ) ) );
JSThreadConfig *config = (JSThreadConfig *)( c->Value() );
return config;
}
Handle< Value > ThreadStart( const Arguments &args ) {
thisConfig( args )->start();
return v8::Undefined();
}
Handle< Value > ThreadJoin( const Arguments &args ) {
thisConfig( args )->join();
return v8::Undefined();
}
Handle< Value > ThreadReturnData( const Arguments &args ) {
HandleScope handle_scope;
return handle_scope.Close( thisConfig( args )->returnData() );
}
Handle< Value > ThreadInject( const Arguments &args ) {
jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" );
jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" );
Local<v8::Object> o = args[0]->ToObject();
o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ThreadInit )->GetFunction() );
o->Set( v8::String::New( "start" ) , FunctionTemplate::New( ThreadStart )->GetFunction() );
o->Set( v8::String::New( "join" ) , FunctionTemplate::New( ThreadJoin )->GetFunction() );
o->Set( v8::String::New( "returnData" ) , FunctionTemplate::New( ThreadReturnData )->GetFunction() );
return v8::Undefined();
}
Handle< Value > ScopedThreadInject( const Arguments &args ) {
jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" );
jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" );
Local<v8::Object> o = args[0]->ToObject();
o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ScopedThreadInit )->GetFunction() );
// inheritance takes care of other member functions
return v8::Undefined();
}
void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) {
if ( baseContext_.IsEmpty() ) // if this is the shell, first call will be with shell context, otherwise don't expect to use fork() anyway
baseContext_ = context;
global->Set( v8::String::New( "_threadInject" ), FunctionTemplate::New( ThreadInject )->GetFunction() );
global->Set( v8::String::New( "_scopedThreadInject" ), FunctionTemplate::New( ScopedThreadInject )->GetFunction() );
}
}