diff --git a/Makefile b/Makefile index f2b9c32b65..52d90f0f95 100644 --- a/Makefile +++ b/Makefile @@ -61,17 +61,16 @@ test-http1: all test-valgrind: all $(PYTHON) tools/test.py --mode=release --valgrind simple message -node_modules/weak: +test/gc/node_modules/weak/build: @if [ ! -f node ]; then make all; fi - @if [ ! -d node_modules ]; then mkdir -p node_modules; fi - ./node deps/npm/bin/npm-cli.js install weak \ - --nodedir="$(shell pwd)" \ - --prefix="$(shell pwd)" --unsafe-perm # go ahead and run as root. + ./node deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \ + --directory="$(shell pwd)/test/gc/node_modules/weak" \ + --nodedir="$(shell pwd)" -test-gc: all node_modules/weak +test-gc: all test/gc/node_modules/weak/build $(PYTHON) tools/test.py --mode=release gc -test-all: all node_modules/weak +test-all: all test/gc/node_modules/weak/build $(PYTHON) tools/test.py --mode=debug,release make test-npm diff --git a/test/gc/node_modules/weak/LICENSE b/test/gc/node_modules/weak/LICENSE new file mode 100644 index 0000000000..834ec929b0 --- /dev/null +++ b/test/gc/node_modules/weak/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2011, Ben Noordhuis + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/test/gc/node_modules/weak/README.md b/test/gc/node_modules/weak/README.md new file mode 100644 index 0000000000..9e8a9166bc --- /dev/null +++ b/test/gc/node_modules/weak/README.md @@ -0,0 +1,114 @@ +node-weak +========= +### Make weak references to JavaScript Objects. +[![Build Status](https://secure.travis-ci.org/TooTallNate/node-weak.png)](http://travis-ci.org/TooTallNate/node-weak) + +On certain rarer occasions, you run into the need to be notified when a JavaScript +object is going to be garbage collected. This feature is exposed to V8's C++ API, +but not to JavaScript. + +That's where `node-weak` comes in! This module exports V8's `Persistent` +functionality to JavaScript. This allows you to create weak references, and +optionally attach a callback function to any arbitrary JS object. The callback +function will be invoked right before the Object is garbage collected (i.e. after +there are no more remaining references to the Object in JS-land). + +This module can, for example, be used for debugging; to determine whether or not +an Object is being garbage collected as it should. +Take a look at the example below for commented walkthrough scenario. + + +Installation +------------ + +Install with `npm`: + +``` bash +$ npm install weak +``` + + +Example +------- + +Here's an example of calling a `cleanup()` function on a Object before it gets +garbage collected: + +``` js +var weak = require('weak') + +// we are going to "monitor" this Object and invoke "cleanup" +// before the object is garbage collected +var obj = { + a: true + , foo: 'bar' +} + +// The function to call before Garbage Collection. +// Note that by the time this is called, 'obj' has been set to `null`. +function cleanup (o) { + delete o.a + delete o.foo +} + +// Here's where we set up the weak reference +var ref = weak(obj, function () { + // `this` inside the callback is the 'obj'. DO NOT store any new references + // to the object, and DO NOT use the object in any async functions. + cleanup(this) +}) + +// While `obj` is alive, `ref` proxies everything to it, so: +ref.a === obj.a +ref.foo === obj.foo + +// Clear out any references to the object, so that it will be GC'd at some point... +obj = null + +// +//// Time passes, and the garbage collector is run +// + +// `callback()` above is called, and `ref` now acts like an empty object. +typeof ref.foo === 'undefined' +``` + + +API +--- + +### weakref weak(Object obj [, Function callback]) + +The main exports is the function that creates the weak reference. +The first argument is the Object that should be monitored. +The Object can be a regular Object, an Array, a Function, a RegExp, or any of +the primitive types or constructor function created with `new`. +Optionally, you can set a callback function to be invoked +before the object is garbage collected. + + +### Object weak.get(weakref ref) + +`get()` returns the actual reference to the Object that this weak reference was +created with. If this is called with a dead reference, `undefined` is returned. + + +### Boolean weak.isDead(weakref ref) + +Checks to see if `ref` is a dead reference. Returns `true` if the original Object +has already been GC'd, `false` otherwise. + + +### null weak.addCallback(weakref ref, Function callback) + +Adds `callback` to the Array of callback functions that will be invoked before the +Objects gets garbage collected. The callbacks get executed in the order that they +are added. + + +### Array weak.callbacks(weakref ref) + +Returns the internal `Array` that `ref` iterates through to invoke the GC +callbacks. The array can be `push()`ed or `unshift()`ed onto, to have more control +over the execution order of the callbacks. This is similar in concept to node's +`EventEmitter#listeners()` function. diff --git a/test/gc/node_modules/weak/binding.gyp b/test/gc/node_modules/weak/binding.gyp new file mode 100644 index 0000000000..68e9eb1991 --- /dev/null +++ b/test/gc/node_modules/weak/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'weakref', + 'sources': [ 'src/weakref.cc' ] + } + ] +} diff --git a/test/gc/node_modules/weak/lib/weak.js b/test/gc/node_modules/weak/lib/weak.js new file mode 100644 index 0000000000..b32ac46423 --- /dev/null +++ b/test/gc/node_modules/weak/lib/weak.js @@ -0,0 +1,9 @@ +var bindings = require('../build/Release/weakref.node') +module.exports = bindings.create + +// backwards-compat with node-weakref +bindings.weaken = bindings.create + +Object.keys(bindings).forEach(function (name) { + module.exports[name] = bindings[name] +}) diff --git a/test/gc/node_modules/weak/package.json b/test/gc/node_modules/weak/package.json new file mode 100644 index 0000000000..f2e952bc87 --- /dev/null +++ b/test/gc/node_modules/weak/package.json @@ -0,0 +1,55 @@ +{ + "author": { + "name": "Ben Noordhuis", + "email": "info@bnoordhuis.nl" + }, + "contributors": [ + { + "name": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "url": "http://tootallnate.net" + } + ], + "name": "weak", + "description": "Make weak references to JavaScript Objects.", + "keywords": [ + "weak", + "reference", + "js", + "javascript", + "object", + "function", + "callback" + ], + "version": "0.2.1", + "repository": { + "type": "git", + "url": "git://github.com/TooTallNate/node-weak.git" + }, + "main": "./lib/weak.js", + "scripts": { + "test": "mocha -gc --reporter spec", + "install": "node-gyp rebuild" + }, + "engines": { + "node": "*" + }, + "dependencies": { + "bindings": "*" + }, + "devDependencies": { + "mocha": "> 0.7.0", + "should": "*" + }, + "_npmUser": { + "name": "tootallnate", + "email": "nathan@tootallnate.net" + }, + "_id": "weak@0.2.1", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.25", + "_nodeVersion": "v0.7.10", + "_defaultsLoaded": true, + "_from": "weak" +} diff --git a/test/gc/node_modules/weak/src/weakref.cc b/test/gc/node_modules/weak/src/weakref.cc new file mode 100644 index 0000000000..409b2154af --- /dev/null +++ b/test/gc/node_modules/weak/src/weakref.cc @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2011, Ben Noordhuis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "v8.h" +#include "node.h" + +using namespace v8; +using namespace node; + +namespace { + + +typedef struct proxy_container { + Persistent proxy; + Persistent target; + Persistent callbacks; +} proxy_container; + + +Persistent proxyClass; + + +bool IsDead(Handle proxy) { + assert(proxy->InternalFieldCount() == 1); + proxy_container *cont = reinterpret_cast( + proxy->GetPointerFromInternalField(0)); + return cont == NULL || cont->target.IsEmpty(); +} + + +Handle Unwrap(Handle proxy) { + assert(!IsDead(proxy)); + proxy_container *cont = reinterpret_cast( + proxy->GetPointerFromInternalField(0)); + return cont->target; +} + +Handle GetCallbacks(Handle proxy) { + proxy_container *cont = reinterpret_cast( + proxy->GetPointerFromInternalField(0)); + assert(cont != NULL); + return cont->callbacks; +} + + +#define UNWRAP \ + HandleScope scope; \ + Handle obj; \ + const bool dead = IsDead(info.This()); \ + if (!dead) obj = Unwrap(info.This()); \ + + +Handle WeakNamedPropertyGetter(Local property, + const AccessorInfo& info) { + UNWRAP + return dead ? Local() : obj->Get(property); +} + + +Handle WeakNamedPropertySetter(Local property, + Local value, + const AccessorInfo& info) { + UNWRAP + if (!dead) obj->Set(property, value); + return value; +} + + +Handle WeakNamedPropertyQuery(Local property, + const AccessorInfo& info) { + return HandleScope().Close(Integer::New(None)); +} + + +Handle WeakNamedPropertyDeleter(Local property, + const AccessorInfo& info) { + UNWRAP + return Boolean::New(!dead && obj->Delete(property)); +} + + +Handle WeakIndexedPropertyGetter(uint32_t index, + const AccessorInfo& info) { + UNWRAP + return dead ? Local() : obj->Get(index); +} + + +Handle WeakIndexedPropertySetter(uint32_t index, + Local value, + const AccessorInfo& info) { + UNWRAP + if (!dead) obj->Set(index, value); + return value; +} + + +Handle WeakIndexedPropertyQuery(uint32_t index, + const AccessorInfo& info) { + return HandleScope().Close(Integer::New(None)); +} + + +Handle WeakIndexedPropertyDeleter(uint32_t index, + const AccessorInfo& info) { + UNWRAP + return Boolean::New(!dead && obj->Delete(index)); +} + + +Handle WeakPropertyEnumerator(const AccessorInfo& info) { + UNWRAP + return HandleScope().Close(dead ? Array::New(0) : obj->GetPropertyNames()); +} + + +void AddCallback(Handle proxy, Handle callback) { + Handle callbacks = GetCallbacks(proxy); + callbacks->Set(Integer::New(callbacks->Length()), callback); +} + + +void TargetCallback(Persistent target, void* arg) { + HandleScope scope; + + assert(target.IsNearDeath()); + + proxy_container *cont = reinterpret_cast(arg); + + // invoke any listening callbacks + uint32_t len = cont->callbacks->Length(); + Handle argv[1]; + argv[0] = target; + for (uint32_t i=0; i cb = Handle::Cast( + cont->callbacks->Get(Integer::New(i))); + + TryCatch try_catch; + + cb->Call(target->ToObject(), 1, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } + } + + cont->proxy->SetPointerInInternalField(0, NULL); + cont->proxy.Dispose(); + cont->proxy.Clear(); + cont->target.Dispose(); + cont->target.Clear(); + cont->callbacks.Dispose(); + cont->callbacks.Clear(); + free(cont); +} + + +Handle Create(const Arguments& args) { + HandleScope scope; + + if (!args[0]->IsObject()) { + Local message = String::New("Object expected"); + return ThrowException(Exception::TypeError(message)); + } + + proxy_container *cont = (proxy_container *) + malloc(sizeof(proxy_container)); + + cont->target = Persistent::New(args[0]->ToObject()); + cont->callbacks = Persistent::New(Array::New()); + + cont->proxy = Persistent::New(proxyClass->NewInstance()); + cont->proxy->SetPointerInInternalField(0, cont); + + cont->target.MakeWeak(cont, TargetCallback); + + if (args.Length() >= 2) { + AddCallback(cont->proxy, Handle::Cast(args[1])); + } + + return cont->proxy; +} + +/** + * TODO: Make this better. + */ + +bool isWeakRef (Handle val) { + return val->IsObject() && val->ToObject()->InternalFieldCount() == 1; +} + +Handle IsWeakRef (const Arguments& args) { + HandleScope scope; + return Boolean::New(isWeakRef(args[0])); +} + +Handle Get(const Arguments& args) { + HandleScope scope; + + if (!isWeakRef(args[0])) { + Local message = String::New("Weakref instance expected"); + return ThrowException(Exception::TypeError(message)); + } + Local proxy = args[0]->ToObject(); + + const bool dead = IsDead(proxy); + if (dead) return Undefined(); + + Handle obj = Unwrap(proxy); + return scope.Close(obj); +} + +Handle IsNearDeath(const Arguments& args) { + HandleScope scope; + + if (!isWeakRef(args[0])) { + Local message = String::New("Weakref instance expected"); + return ThrowException(Exception::TypeError(message)); + } + Local proxy = args[0]->ToObject(); + + proxy_container *cont = reinterpret_cast( + proxy->GetPointerFromInternalField(0)); + assert(cont != NULL); + + Handle rtn = Boolean::New(cont->target.IsNearDeath()); + + return scope.Close(rtn); +} + +Handle IsDead(const Arguments& args) { + HandleScope scope; + + if (!isWeakRef(args[0])) { + Local message = String::New("Weakref instance expected"); + return ThrowException(Exception::TypeError(message)); + } + Local proxy = args[0]->ToObject(); + + const bool dead = IsDead(proxy); + return Boolean::New(dead); +} + + +Handle AddCallback(const Arguments& args) { + HandleScope scope; + + if (!isWeakRef(args[0])) { + Local message = String::New("Weakref instance expected"); + return ThrowException(Exception::TypeError(message)); + } + Local proxy = args[0]->ToObject(); + + AddCallback(proxy, Handle::Cast(args[1])); + + return Undefined(); +} + +Handle Callbacks(const Arguments& args) { + HandleScope scope; + + if (!isWeakRef(args[0])) { + Local message = String::New("Weakref instance expected"); + return ThrowException(Exception::TypeError(message)); + } + Local proxy = args[0]->ToObject(); + + return scope.Close(GetCallbacks(proxy)); +} + + +void Initialize(Handle target) { + HandleScope scope; + + proxyClass = Persistent::New(ObjectTemplate::New()); + proxyClass->SetNamedPropertyHandler(WeakNamedPropertyGetter, + WeakNamedPropertySetter, + WeakNamedPropertyQuery, + WeakNamedPropertyDeleter, + WeakPropertyEnumerator); + proxyClass->SetIndexedPropertyHandler(WeakIndexedPropertyGetter, + WeakIndexedPropertySetter, + WeakIndexedPropertyQuery, + WeakIndexedPropertyDeleter, + WeakPropertyEnumerator); + proxyClass->SetInternalFieldCount(1); + + NODE_SET_METHOD(target, "get", Get); + NODE_SET_METHOD(target, "create", Create); + NODE_SET_METHOD(target, "isWeakRef", IsWeakRef); + NODE_SET_METHOD(target, "isNearDeath", IsNearDeath); + NODE_SET_METHOD(target, "isDead", IsDead); + NODE_SET_METHOD(target, "callbacks", Callbacks); + NODE_SET_METHOD(target, "addCallback", AddCallback); + +} + +} // anonymous namespace + +NODE_MODULE(weakref, Initialize); diff --git a/vcbuild.bat b/vcbuild.bat index 20d4b03bde..8948d70212 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -28,6 +28,7 @@ set msi= set licensertf= set upload= set jslint= +set buildnodeweak= set noetw= set noetw_arg= set noetw_msi_arg= @@ -51,7 +52,8 @@ if /i "%1"=="test-internet" set test=test-internet&goto arg-ok if /i "%1"=="test-pummel" set test=test-pummel&goto arg-ok if /i "%1"=="test-simple" set test=test-simple&goto arg-ok if /i "%1"=="test-message" set test=test-message&goto arg-ok -if /i "%1"=="test-all" set test=test-all&goto arg-ok +if /i "%1"=="test-gc" set test=test-gc&set buildnodeweak=1&goto arg-ok +if /i "%1"=="test-all" set test=test-all&set buildnodeweak=1&goto arg-ok if /i "%1"=="test" set test=test&goto arg-ok if /i "%1"=="msi" set msi=1&set licensertf=1&goto arg-ok if /i "%1"=="upload" set upload=1&goto arg-ok @@ -62,6 +64,7 @@ echo Warning: ignoring invalid command line option `%1`. :arg-ok shift goto next-arg + :args-done if defined upload goto upload if defined jslint goto jslint @@ -139,13 +142,24 @@ if "%test%"=="test-internet" set test_args=%test_args% internet if "%test%"=="test-pummel" set test_args=%test_args% pummel if "%test%"=="test-simple" set test_args=%test_args% simple if "%test%"=="test-message" set test_args=%test_args% message +if "%test%"=="test-gc" set test_args=%test_args% gc if "%test%"=="test-all" set test_args=%test_args% +:build-node-weak +@rem Build node-weak if required +if "%buildnodeweak%"=="" goto run-tests +"%config%\node" deps\npm\node_modules\node-gyp\bin\node-gyp rebuild --directory="%~dp0test\gc\node_modules\weak" --nodedir="%~dp0." +if errorlevel 1 goto build-node-weak-failed +goto run-tests + +:build-node-weak-failed +echo Failed to build node-weak. +goto exit + +:run-tests echo running 'python tools/test.py %test_args%' python tools/test.py %test_args% - if "%test%"=="test" goto jslint - goto exit :create-msvs-files-failed