summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/engine_v8.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/scripting/engine_v8.cpp')
-rw-r--r--src/mongo/scripting/engine_v8.cpp326
1 files changed, 96 insertions, 230 deletions
diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp
index f5adc04fd67..484b748b975 100644
--- a/src/mongo/scripting/engine_v8.cpp
+++ b/src/mongo/scripting/engine_v8.cpp
@@ -17,6 +17,7 @@
#include "mongo/scripting/engine_v8.h"
+#include "mongo/platform/unordered_set.h"
#include "mongo/scripting/v8_db.h"
#include "mongo/scripting/v8_utils.h"
#include "mongo/util/base64.h"
@@ -52,10 +53,15 @@ namespace mongo {
return (BSONHolder*)ptr;
}
+ static v8::Handle<v8::Object> unwrapObject(const v8::Handle<v8::Object>& obj) {
+ return obj->GetInternalField(1).As<v8::Object>();
+ }
+
v8::Persistent<v8::Object> V8Scope::wrapBSONObject(v8::Local<v8::Object> obj,
BSONHolder* data) {
data->_scope = this;
- obj->SetInternalField(0, v8::External::New(data));
+ obj->SetInternalField(0, v8::External::New(data)); // Holder
+ obj->SetInternalField(1, v8::Object::New()); // Object
v8::Persistent<v8::Object> p = v8::Persistent<v8::Object>::New(obj);
bsonHolderTracker.track(p, data);
return p;
@@ -66,9 +72,10 @@ namespace mongo {
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
try {
- if (info.This()->HasRealNamedProperty(name)) {
- // value already cached
- return handle_scope.Close(info.This()->GetRealNamedProperty(name));
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ if (realObject->HasOwnProperty(name)) {
+ // value already cached or added
+ return handle_scope.Close(realObject->Get(name));
}
string key = toSTLString(name);
@@ -83,8 +90,12 @@ namespace mongo {
v8::Local<v8::External> scp = v8::External::Cast(*info.Data());
V8Scope* scope = (V8Scope*)(scp->Value());
- val = scope->mongoToV8Element(elmt, false);
- info.This()->ForceSet(name, val, v8::DontEnum);
+ val = scope->mongoToV8Element(elmt, holder->_readOnly);
+
+ if (obj.objsize() > 128 || val->IsObject()) {
+ // Only cache if expected to help (large BSON) or is required due to js semantics
+ realObject->Set(name, val);
+ }
if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) {
// if accessing a subobject, it may get modified and base obj would not know
@@ -103,6 +114,9 @@ namespace mongo {
static v8::Handle<v8::Value> namedGetRO(v8::Local<v8::String> name,
const v8::AccessorInfo &info) {
+ return namedGet(name, info);
+ // Rest of function is unused but left in to ease backporting of SERVER-9267
+
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
string key = toSTLString(name);
@@ -130,25 +144,25 @@ namespace mongo {
string key = toSTLString(name);
BSONHolder* holder = unwrapHolder(info.Holder());
holder->_removed.erase(key);
- holder->_extra.push_back(key);
holder->_modified = true;
- // set into JS object
- return v8::Handle<v8::Value>();
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ realObject->Set(name, value_obj);
+ return value_obj;
}
static v8::Handle<v8::Array> namedEnumerator(const v8::AccessorInfo &info) {
v8::HandleScope handle_scope;
BSONHolder* holder = unwrapHolder(info.Holder());
BSONObj obj = holder->_obj;
- v8::Handle<v8::Array> arr = v8::Handle<v8::Array>(v8::Array::New(obj.nFields()));
- int i = 0;
+ v8::Handle<v8::Array> out = v8::Array::New();
+ int outIndex = 0;
v8::Local<v8::External> scp = v8::External::Cast(*info.Data());
V8Scope* scope = (V8Scope*)(scp->Value());
- set<string> added;
+ unordered_set<string> added;
// note here that if keys are parseable number, v8 will access them using index
- for (BSONObjIterator it(obj); it.more(); ++i) {
+ for (BSONObjIterator it(obj); it.more();) {
const BSONElement& f = it.next();
string sname = f.fieldName();
if (holder->_removed.count(sname))
@@ -156,17 +170,21 @@ namespace mongo {
v8::Handle<v8::String> name = scope->v8StringData(sname);
added.insert(sname);
- arr->Set(i, name);
+ out->Set(outIndex++, name);
}
- for (list<string>::iterator it = holder->_extra.begin();
- it != holder->_extra.end(); it++) {
- string sname = *it;
+
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ v8::Handle<v8::Array> fields = realObject->GetOwnPropertyNames();
+ const int len = fields->Length();
+ for (int field=0; field < len; field++) {
+ v8::Handle<v8::String> name = fields->Get(field).As<v8::String>();
+ string sname = toSTLString(name);
if (added.count(sname))
continue;
- arr->Set(i++, scope->v8StringData(sname));
+ out->Set(outIndex++, name);
}
- return handle_scope.Close(arr);
+ return handle_scope.Close(out);
}
v8::Handle<v8::Boolean> namedDelete(v8::Local<v8::String> name, const v8::AccessorInfo& info) {
@@ -174,26 +192,25 @@ namespace mongo {
string key = toSTLString(name);
BSONHolder* holder = unwrapHolder(info.Holder());
holder->_removed.insert(key);
- holder->_extra.remove(key);
holder->_modified = true;
- // also delete in JS obj
- return handle_scope.Close(v8::Handle<v8::Boolean>());
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ realObject->Delete(name);
+ return v8::True();
}
static v8::Handle<v8::Value> indexedGet(uint32_t index, const v8::AccessorInfo &info) {
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
try {
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ if (realObject->Has(index)) {
+ // value already cached or added
+ return handle_scope.Close(realObject->Get(index));
+ }
string key = str::stream() << index;
v8::Local<v8::External> scp = v8::External::Cast(*info.Data());
V8Scope* scope = (V8Scope*)(scp->Value());
- v8::Handle<v8::String> name = scope->v8StringData(key);
-
- if (info.This()->HasRealIndexedProperty(index)) {
- // value already cached
- return handle_scope.Close(info.This()->GetRealNamedProperty(name));
- }
BSONHolder* holder = unwrapHolder(info.Holder());
if (holder->_removed.count(key))
@@ -203,8 +220,8 @@ namespace mongo {
BSONElement elmt = obj.getField(key);
if (elmt.eoo())
return handle_scope.Close(v8::Handle<v8::Value>());
- val = scope->mongoToV8Element(elmt, false);
- info.This()->ForceSet(name, val, v8::DontEnum);
+ val = scope->mongoToV8Element(elmt, holder->_readOnly);
+ realObject->Set(index, val);
if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) {
// if accessing a subobject, it may get modified and base obj would not know
@@ -226,14 +243,18 @@ namespace mongo {
string key = str::stream() << index;
BSONHolder* holder = unwrapHolder(info.Holder());
holder->_removed.insert(key);
- holder->_extra.remove(key);
holder->_modified = true;
// also delete in JS obj
- return v8::Handle<v8::Boolean>();
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ realObject->Delete(index);
+ return v8::True();
}
static v8::Handle<v8::Value> indexedGetRO(uint32_t index, const v8::AccessorInfo &info) {
+ return indexedGet(index, info);
+ // Rest of function is unused but left in-place to ease backporting of SERVER-9267
+
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
try {
@@ -265,11 +286,11 @@ namespace mongo {
string key = str::stream() << index;
BSONHolder* holder = unwrapHolder(info.Holder());
holder->_removed.erase(key);
- holder->_extra.push_back(key);
holder->_modified = true;
- // set into JS object
- return v8::Handle<v8::Value>();
+ v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ realObject->Set(index, value_obj);
+ return value_obj;
}
v8::Handle<v8::Value> NamedReadOnlySet(v8::Local<v8::String> property,
@@ -298,6 +319,15 @@ namespace mongo {
return v8::Boolean::New(false);
}
+ /**
+ * GC Prologue and Epilogue constants (used to display description constants)
+ */
+ struct GCPrologueState { static const char* name; };
+ const char* GCPrologueState::name = "prologue";
+ struct GCEpilogueState { static const char* name; };
+ const char* GCEpilogueState::name = "epilogue";
+
+ template <typename _GCState>
void gcCallback(v8::GCType type, v8::GCCallbackFlags flags) {
const int verbosity = 1; // log level for stat collection
if (logLevel < verbosity)
@@ -306,12 +336,13 @@ namespace mongo {
v8::HeapStatistics stats;
v8::V8::GetHeapStatistics(&stats);
- LOG(verbosity) << "V8 GC heap stats - "
- << " total: " << stats.total_heap_size()
- << " exec: " << stats.total_heap_size_executable()
- << " used: " << stats.used_heap_size()<< " limit: "
- << stats.heap_size_limit()
- << endl;
+ log() << "V8 GC " << _GCState::name
+ << " heap stats - "
+ << " total: " << stats.total_heap_size()
+ << " exec: " << stats.total_heap_size_executable()
+ << " used: " << stats.used_heap_size()<< " limit: "
+ << stats.heap_size_limit()
+ << endl;
}
V8ScriptEngine::V8ScriptEngine() :
@@ -455,12 +486,6 @@ namespace mongo {
_isolate = v8::Isolate::New();
v8::Isolate::Scope iscope(_isolate);
- // resource constraints must be set on isolate, before any call or lock
- v8::ResourceConstraints rc;
- rc.set_max_young_space_size(4 * 1024 * 1024);
- rc.set_max_old_space_size(64 * 1024 * 1024);
- v8::SetResourceConstraints(&rc);
-
// lock the isolate and enter the context
v8::Locker l(_isolate);
v8::HandleScope handleScope;
@@ -468,7 +493,8 @@ namespace mongo {
v8::Context::Scope context_scope(_context);
// display heap statistics on MarkAndSweep GC run
- v8::V8::AddGCPrologueCallback(gcCallback, v8::kGCTypeMarkSweepCompact);
+ v8::V8::AddGCPrologueCallback(gcCallback<GCPrologueState>, v8::kGCTypeMarkSweepCompact);
+ v8::V8::AddGCEpilogueCallback(gcCallback<GCEpilogueState>, v8::kGCTypeMarkSweepCompact);
// if the isolate runs out of heap space, raise a flag on the StackGuard instead of
// calling abort()
@@ -479,7 +505,7 @@ namespace mongo {
// initialize lazy object template
lzObjectTemplate = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- lzObjectTemplate->SetInternalFieldCount(1);
+ lzObjectTemplate->SetInternalFieldCount(2);
lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete,
namedEnumerator, v8::External::New(this));
lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete,
@@ -490,7 +516,7 @@ namespace mongo {
v8::DontEnum);
roObjectTemplate = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- roObjectTemplate->SetInternalFieldCount(1);
+ roObjectTemplate->SetInternalFieldCount(2);
roObjectTemplate->SetNamedPropertyHandler(namedGetRO, NamedReadOnlySet, 0,
NamedReadOnlyDelete, namedEnumerator,
v8::External::New(this));
@@ -1250,188 +1276,13 @@ namespace mongo {
return handle_scope.Close(idCons->NewInstance(1, argv));
}
- v8::Local<v8::Object> V8Scope::mongoToV8(const BSONObj& m, bool array, bool readOnly) {
- v8::HandleScope handle_scope;
- v8::Handle<v8::Value> argv[3]; // arguments for v8 instance constructors
- v8::Local<v8::ObjectTemplate> readOnlyObjects;
- v8::Local<v8::Object> o;
-
- // handle DBRef. needs to come first. isn't it? (metagoto)
- static string ref = "$ref";
- if (ref == m.firstElement().fieldName()) {
- const BSONElement& id = m["$id"];
- if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ?
- v8::Function* dbRef = getNamedCons("DBRef");
- o = dbRef->NewInstance();
- }
- }
-
- if (!o.IsEmpty()) {
- readOnly = false;
- }
- else if (array) {
- // NOTE Looks like it's impossible to add interceptors to v8 arrays.
- // so array itself will never be read only, but its values can be
- o = v8::Array::New();
- }
- else if (!readOnly) {
- o = v8::Object::New();
- }
- else {
- // NOTE Our readOnly implemention relies on undocumented ObjectTemplate
- // functionality that may be fragile, but it still seems like the best option
- // for now -- fwiw, the v8 docs are pretty sparse. I've determined experimentally
- // that when property handlers are set for an object template, they will attach
- // to objects previously created by that template. To get this to work, though,
- // it is necessary to initialize the template's property handlers before
- // creating objects from the template (as I have in the following few lines
- // of code).
- // NOTE In my first attempt, I configured the permanent property handlers before
- // constructiong the object and replaced the Set() calls below with ForceSet().
- // However, it turns out that ForceSet() only bypasses handlers for named
- // properties and not for indexed properties.
- readOnlyObjects = v8::ObjectTemplate::New();
- // NOTE This internal field will store type info for special db types. For
- // regular objects the field is unnecessary - for simplicity I'm creating just
- // one readOnlyObjects template for objects where the field is & isn't necessary,
- // assuming that the overhead of an internal field is slight.
- readOnlyObjects->SetInternalFieldCount(1);
- readOnlyObjects->SetNamedPropertyHandler(0);
- readOnlyObjects->SetIndexedPropertyHandler(0);
- o = readOnlyObjects->NewInstance();
- }
-
- mongo::BSONObj sub;
-
- for (BSONObjIterator i(m); i.more();) {
- const BSONElement& f = i.next();
-
- v8::Local<v8::Value> v;
- v8::Handle<v8::String> name = v8StringData(f.fieldName());
-
- switch (f.type()) {
- case mongo::Code:
- o->ForceSet(name, newFunction(f.valuestr()));
- break;
- case CodeWScope:
- if (!f.codeWScopeObject().isEmpty())
- log() << "warning: CodeWScope doesn't transfer to db.eval" << endl;
- o->ForceSet(name, newFunction(f.codeWScopeCode()));
- break;
- case mongo::String:
- o->ForceSet(name, v8::String::New(f.valuestr()));
- break;
- case mongo::jstOID: {
- v8::Function * idCons = getObjectIdCons();
- argv[0] = v8::String::New(f.__oid().str().c_str());
- o->ForceSet(name, idCons->NewInstance(1, argv));
- break;
- }
- case mongo::NumberDouble:
- case mongo::NumberInt:
- o->ForceSet(name, v8::Number::New(f.number()));
- break;
- case mongo::Array:
- sub = f.embeddedObject();
- o->ForceSet(name, mongoToV8(sub, true, readOnly));
- break;
- case mongo::Object:
- sub = f.embeddedObject();
- o->ForceSet(name, mongoToLZV8(sub, readOnly));
- break;
- case mongo::Date:
- o->ForceSet(name, v8::Date::New((double) ((long long)f.date().millis)));
- break;
- case mongo::Bool:
- o->ForceSet(name, v8::Boolean::New(f.boolean()));
- break;
- case mongo::jstNULL:
- case mongo::Undefined: // duplicate sm behavior
- o->ForceSet(name, v8::Null());
- break;
- case mongo::RegEx: {
- v8::Function * regex = getNamedCons("RegExp");
- argv[0] = v8::String::New(f.regex());
- argv[1] = v8::String::New(f.regexFlags());
- o->ForceSet(name, regex->NewInstance(2, argv));
- break;
- }
- case mongo::BinData: {
- int len;
- const char *data = f.binData(len);
- stringstream ss;
- base64::encode(ss, data, len);
- argv[0] = v8::Number::New(f.binDataType());
- argv[1] = v8::String::New(ss.str().c_str());
- o->ForceSet(name, getNamedCons("BinData")->NewInstance(2, argv));
- break;
- }
- case mongo::Timestamp: {
- v8::Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() :
- internalFieldObjects->NewInstance();
- sub->ForceSet(v8::String::New("t"), v8::Number::New(f.timestampTime() / 1000));
- sub->ForceSet(v8::String::New("i"), v8::Number::New(f.timestampInc()));
- sub->SetInternalField(0, v8::Uint32::New(f.type()));
- o->ForceSet(name, sub);
- break;
- }
- case mongo::NumberLong: {
- unsigned long long val = f.numberLong();
- v8::Function* numberLong = getNamedCons("NumberLong");
- double floatApprox = (double)(long long)val;
- // values above 2^53 are not accurately represented in JS
- if ((long long)val == (long long)floatApprox && val < 9007199254740992ULL) {
- argv[0] = v8::Number::New(floatApprox);
- o->ForceSet(name, numberLong->NewInstance(1, argv));
- }
- else {
- argv[0] = v8::Number::New(floatApprox);
- argv[1] = v8::Integer::New(val >> 32);
- argv[2] = v8::Integer::New((unsigned long)(val & 0x00000000ffffffff));
- o->ForceSet(name, numberLong->NewInstance(3, argv));
- }
- break;
- }
- case mongo::MinKey: {
- o->ForceSet(name, newMinKeyInstance());
- break;
- }
- case mongo::MaxKey: {
- o->ForceSet(name, newMaxKeyInstance());
- break;
- }
- case mongo::DBRef: {
- v8::Function* dbPointer = getNamedCons("DBPointer");
- argv[0] = v8StringData(f.dbrefNS());
- argv[1] = newId(f.dbrefOID());
- o->ForceSet(name, dbPointer->NewInstance(2, argv));
- break;
- }
- default:
- cout << "can't handle type: ";
- cout << f.type() << " ";
- cout << f.toString();
- cout << endl;
- break;
- }
- }
-
- if (!array && readOnly) {
- readOnlyObjects->SetNamedPropertyHandler(0, NamedReadOnlySet,
- 0, NamedReadOnlyDelete);
- readOnlyObjects->SetIndexedPropertyHandler(0, IndexedReadOnlySet,
- 0, IndexedReadOnlyDelete);
- }
-
- return handle_scope.Close(o);
- }
-
/**
* converts a BSONObj to a Lazy V8 object
*/
v8::Persistent<v8::Object> V8Scope::mongoToLZV8(const BSONObj& m, bool readOnly) {
v8::Local<v8::Object> o;
BSONHolder* own = new BSONHolder(m);
+ own->_readOnly = readOnly;
if (readOnly) {
o = roObjectTemplate->NewInstance();
@@ -1519,6 +1370,7 @@ namespace mongo {
if (!elem.codeWScopeObject().isEmpty())
log() << "warning: CodeWScope doesn't transfer to db.eval" << endl;
return newFunction(elem.codeWScopeCode());
+ case mongo::Symbol:
case mongo::String:
return v8::String::New(elem.valuestr());
case mongo::jstOID:
@@ -1526,14 +1378,22 @@ namespace mongo {
case mongo::NumberDouble:
case mongo::NumberInt:
return v8::Number::New(elem.number());
- case mongo::Array:
+ case mongo::Array: {
// NB: This comment may no longer be accurate.
// for arrays it's better to use non lazy object because:
// - the lazy array is not a true v8 array and requires some v8 src change
// for all methods to work
// - it made several tests about 1.5x slower
// - most times when an array is accessed, all its values will be used
- return mongoToV8(elem.embeddedObject(), true, readOnly);
+
+ // It is faster to allow the v8::Array to grow than call nFields() on the array
+ v8::Handle<v8::Array> array = v8::Array::New();
+ int i = 0;
+ BSONForEach(subElem, elem.embeddedObject()) {
+ array->Set(i++, mongoToV8Element(subElem, readOnly));
+ }
+ return array;
+ }
case mongo::Object:
return mongoToLZV8(elem.embeddedObject(), readOnly);
case mongo::Date:
@@ -1745,8 +1605,14 @@ namespace mongo {
return;
}
if (value->IsArray()) {
- BSONObj sub = v8ToMongo(value->ToObject(), depth);
- b.appendArray(sname, sub);
+ // Note: can't use BSONArrayBuilder because need to call recursively
+ BSONObjBuilder arrBuilder(b.subarrayStart(sname));
+ v8::Handle<v8::Array> array = value.As<v8::Array>();
+ const int len = array->Length();
+ for (int i=0; i < len; i++) {
+ const string name = BSONObjBuilder::numStr(i);
+ v8ToMongoElement(arrBuilder, name, array->Get(i), depth+1, originalParent);
+ }
return;
}
if (value->IsDate()) {