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