mirror of https://github.com/lukechilds/node.git
Nathan Rajlich
13 years ago
committed by
Bert Belder
8 changed files with 537 additions and 10 deletions
@ -0,0 +1,13 @@ |
|||
Copyright (c) 2011, Ben Noordhuis <info@bnoordhuis.nl> |
|||
|
|||
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. |
@ -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<Object>` |
|||
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. |
@ -0,0 +1,8 @@ |
|||
{ |
|||
'targets': [ |
|||
{ |
|||
'target_name': 'weakref', |
|||
'sources': [ 'src/weakref.cc' ] |
|||
} |
|||
] |
|||
} |
@ -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] |
|||
}) |
@ -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" |
|||
} |
@ -0,0 +1,315 @@ |
|||
/*
|
|||
* Copyright (c) 2011, Ben Noordhuis <info@bnoordhuis.nl> |
|||
* |
|||
* 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 <stdlib.h> |
|||
#include "v8.h" |
|||
#include "node.h" |
|||
|
|||
using namespace v8; |
|||
using namespace node; |
|||
|
|||
namespace { |
|||
|
|||
|
|||
typedef struct proxy_container { |
|||
Persistent<Object> proxy; |
|||
Persistent<Object> target; |
|||
Persistent<Array> callbacks; |
|||
} proxy_container; |
|||
|
|||
|
|||
Persistent<ObjectTemplate> proxyClass; |
|||
|
|||
|
|||
bool IsDead(Handle<Object> proxy) { |
|||
assert(proxy->InternalFieldCount() == 1); |
|||
proxy_container *cont = reinterpret_cast<proxy_container*>( |
|||
proxy->GetPointerFromInternalField(0)); |
|||
return cont == NULL || cont->target.IsEmpty(); |
|||
} |
|||
|
|||
|
|||
Handle<Object> Unwrap(Handle<Object> proxy) { |
|||
assert(!IsDead(proxy)); |
|||
proxy_container *cont = reinterpret_cast<proxy_container*>( |
|||
proxy->GetPointerFromInternalField(0)); |
|||
return cont->target; |
|||
} |
|||
|
|||
Handle<Array> GetCallbacks(Handle<Object> proxy) { |
|||
proxy_container *cont = reinterpret_cast<proxy_container*>( |
|||
proxy->GetPointerFromInternalField(0)); |
|||
assert(cont != NULL); |
|||
return cont->callbacks; |
|||
} |
|||
|
|||
|
|||
#define UNWRAP \ |
|||
HandleScope scope; \ |
|||
Handle<Object> obj; \ |
|||
const bool dead = IsDead(info.This()); \ |
|||
if (!dead) obj = Unwrap(info.This()); \ |
|||
|
|||
|
|||
Handle<Value> WeakNamedPropertyGetter(Local<String> property, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
return dead ? Local<Value>() : obj->Get(property); |
|||
} |
|||
|
|||
|
|||
Handle<Value> WeakNamedPropertySetter(Local<String> property, |
|||
Local<Value> value, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
if (!dead) obj->Set(property, value); |
|||
return value; |
|||
} |
|||
|
|||
|
|||
Handle<Integer> WeakNamedPropertyQuery(Local<String> property, |
|||
const AccessorInfo& info) { |
|||
return HandleScope().Close(Integer::New(None)); |
|||
} |
|||
|
|||
|
|||
Handle<Boolean> WeakNamedPropertyDeleter(Local<String> property, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
return Boolean::New(!dead && obj->Delete(property)); |
|||
} |
|||
|
|||
|
|||
Handle<Value> WeakIndexedPropertyGetter(uint32_t index, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
return dead ? Local<Value>() : obj->Get(index); |
|||
} |
|||
|
|||
|
|||
Handle<Value> WeakIndexedPropertySetter(uint32_t index, |
|||
Local<Value> value, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
if (!dead) obj->Set(index, value); |
|||
return value; |
|||
} |
|||
|
|||
|
|||
Handle<Integer> WeakIndexedPropertyQuery(uint32_t index, |
|||
const AccessorInfo& info) { |
|||
return HandleScope().Close(Integer::New(None)); |
|||
} |
|||
|
|||
|
|||
Handle<Boolean> WeakIndexedPropertyDeleter(uint32_t index, |
|||
const AccessorInfo& info) { |
|||
UNWRAP |
|||
return Boolean::New(!dead && obj->Delete(index)); |
|||
} |
|||
|
|||
|
|||
Handle<Array> WeakPropertyEnumerator(const AccessorInfo& info) { |
|||
UNWRAP |
|||
return HandleScope().Close(dead ? Array::New(0) : obj->GetPropertyNames()); |
|||
} |
|||
|
|||
|
|||
void AddCallback(Handle<Object> proxy, Handle<Function> callback) { |
|||
Handle<Array> callbacks = GetCallbacks(proxy); |
|||
callbacks->Set(Integer::New(callbacks->Length()), callback); |
|||
} |
|||
|
|||
|
|||
void TargetCallback(Persistent<Value> target, void* arg) { |
|||
HandleScope scope; |
|||
|
|||
assert(target.IsNearDeath()); |
|||
|
|||
proxy_container *cont = reinterpret_cast<proxy_container*>(arg); |
|||
|
|||
// invoke any listening callbacks
|
|||
uint32_t len = cont->callbacks->Length(); |
|||
Handle<Value> argv[1]; |
|||
argv[0] = target; |
|||
for (uint32_t i=0; i<len; i++) { |
|||
|
|||
Handle<Function> cb = Handle<Function>::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<Value> Create(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsObject()) { |
|||
Local<String> message = String::New("Object expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
|
|||
proxy_container *cont = (proxy_container *) |
|||
malloc(sizeof(proxy_container)); |
|||
|
|||
cont->target = Persistent<Object>::New(args[0]->ToObject()); |
|||
cont->callbacks = Persistent<Array>::New(Array::New()); |
|||
|
|||
cont->proxy = Persistent<Object>::New(proxyClass->NewInstance()); |
|||
cont->proxy->SetPointerInInternalField(0, cont); |
|||
|
|||
cont->target.MakeWeak(cont, TargetCallback); |
|||
|
|||
if (args.Length() >= 2) { |
|||
AddCallback(cont->proxy, Handle<Function>::Cast(args[1])); |
|||
} |
|||
|
|||
return cont->proxy; |
|||
} |
|||
|
|||
/**
|
|||
* TODO: Make this better. |
|||
*/ |
|||
|
|||
bool isWeakRef (Handle<Value> val) { |
|||
return val->IsObject() && val->ToObject()->InternalFieldCount() == 1; |
|||
} |
|||
|
|||
Handle<Value> IsWeakRef (const Arguments& args) { |
|||
HandleScope scope; |
|||
return Boolean::New(isWeakRef(args[0])); |
|||
} |
|||
|
|||
Handle<Value> Get(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!isWeakRef(args[0])) { |
|||
Local<String> message = String::New("Weakref instance expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
Local<Object> proxy = args[0]->ToObject(); |
|||
|
|||
const bool dead = IsDead(proxy); |
|||
if (dead) return Undefined(); |
|||
|
|||
Handle<Object> obj = Unwrap(proxy); |
|||
return scope.Close(obj); |
|||
} |
|||
|
|||
Handle<Value> IsNearDeath(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!isWeakRef(args[0])) { |
|||
Local<String> message = String::New("Weakref instance expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
Local<Object> proxy = args[0]->ToObject(); |
|||
|
|||
proxy_container *cont = reinterpret_cast<proxy_container*>( |
|||
proxy->GetPointerFromInternalField(0)); |
|||
assert(cont != NULL); |
|||
|
|||
Handle<Boolean> rtn = Boolean::New(cont->target.IsNearDeath()); |
|||
|
|||
return scope.Close(rtn); |
|||
} |
|||
|
|||
Handle<Value> IsDead(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!isWeakRef(args[0])) { |
|||
Local<String> message = String::New("Weakref instance expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
Local<Object> proxy = args[0]->ToObject(); |
|||
|
|||
const bool dead = IsDead(proxy); |
|||
return Boolean::New(dead); |
|||
} |
|||
|
|||
|
|||
Handle<Value> AddCallback(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!isWeakRef(args[0])) { |
|||
Local<String> message = String::New("Weakref instance expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
Local<Object> proxy = args[0]->ToObject(); |
|||
|
|||
AddCallback(proxy, Handle<Function>::Cast(args[1])); |
|||
|
|||
return Undefined(); |
|||
} |
|||
|
|||
Handle<Value> Callbacks(const Arguments& args) { |
|||
HandleScope scope; |
|||
|
|||
if (!isWeakRef(args[0])) { |
|||
Local<String> message = String::New("Weakref instance expected"); |
|||
return ThrowException(Exception::TypeError(message)); |
|||
} |
|||
Local<Object> proxy = args[0]->ToObject(); |
|||
|
|||
return scope.Close(GetCallbacks(proxy)); |
|||
} |
|||
|
|||
|
|||
void Initialize(Handle<Object> target) { |
|||
HandleScope scope; |
|||
|
|||
proxyClass = Persistent<ObjectTemplate>::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); |
Loading…
Reference in new issue