@ohos-ports/weak-napi 2.0.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/History.md ADDED
@@ -0,0 +1,60 @@
1
+
2
+ 1.0.1 / 2016-01-03
3
+ ==================
4
+
5
+ * add missing HandleScope in callback (#67, @laverdet)
6
+ * add Travis support for v4.x, v5.x (#60, @robcolburn)
7
+ * appveyor: place .bin into the $PATH
8
+
9
+ 1.0.0 / 2015-08-17
10
+ ==================
11
+
12
+ * added `removeCallback()` and `removeCallbacks()`
13
+ * appveyor: attempt to fix node v0.8
14
+ * appveyor: test x86 and x64
15
+ * travis: attempt to fix node v0.8
16
+ * travis: test "iojs"
17
+ * travis: run on new infrastructure
18
+ * package: update to NAN 2.0 (@kkoopa)
19
+ * package: stricter "bindings" version number
20
+ * package: specify "MIT" license
21
+ * README: update for API change
22
+
23
+ 0.4.1 / 2015-05-09
24
+ ==================
25
+
26
+ * Update to nan ~1.8.4 (#47, @imyller)
27
+ * appveyor: test node v0.12 instead of v0.11
28
+
29
+ 0.4.0 / 2015-02-18
30
+ ==================
31
+
32
+ * travis: test node v0.12
33
+ * package: update "nan" to v1.6.2 for Node v0.12 compatibility (#40, @GitStarInc)
34
+ * src: call callback directly to avoid unwanted preemption (#36)
35
+
36
+ 0.3.4 / 2015-01-27
37
+ ==================
38
+
39
+ * Update dependencies to also work with IO.js #39 (#40, @GitStarInc)
40
+ * Revert "appveyor: attempt to test x86 and x64, Debug and Release configs"
41
+ * appveyor: attempt to test x86 and x64, Debug and Release configs
42
+
43
+ 0.3.3 / 2014-06-04
44
+ ==================
45
+
46
+ * appveyor: more generic comment
47
+ * package: update "mocha" to v1.20.1
48
+ * package: update "nan" to v1.2.0
49
+
50
+ 0.3.2 / 2014-05-25
51
+ ==================
52
+
53
+ * add appveyor.yml file for Windows testing
54
+ * README: use SVG Travis badge
55
+ * README: add appveyor build badge
56
+ * README: correct docs for the callback (#29, @metamatt)
57
+ * .travis: don't test node v0.9.x
58
+ * weakref: fix deprecation warning after nan upgrade
59
+ * weakref: remove "printf()" calls
60
+ * weakref: update for nan v1 API changes
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2011, Ben Noordhuis <info@bnoordhuis.nl>
2
+ Copyright (c) 2017, Anna Henningsen <anna@addaleax.net> (N-API port)
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted, provided that the above
6
+ copyright notice and this permission notice appear in all copies.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ weak-napi
2
+ =========
3
+ ### Make weak references to JavaScript Objects.
4
+
5
+ [![Greenkeeper badge](https://badges.greenkeeper.io/node-ffi-napi/weak-napi.svg)](https://greenkeeper.io/)
6
+
7
+ [![NPM Version](https://img.shields.io/npm/v/weak-napi.svg?style=flat)](https://npmjs.org/package/weak-napi)
8
+ [![NPM Downloads](https://img.shields.io/npm/dm/weak-napi.svg?style=flat)](https://npmjs.org/package/weak-napi)
9
+ [![Build Status](https://travis-ci.org/node-ffi-napi/weak-napi.svg?style=flat&branch=master)](https://travis-ci.org/node-ffi-napi/weak-napi?branch=master)
10
+ [![Coverage Status](https://coveralls.io/repos/node-ffi-napi/weak-napi/badge.svg?branch=master)](https://coveralls.io/r/node-ffi-napi/weak-napi?branch=master)
11
+ [![Dependency Status](https://david-dm.org/node-ffi-napi/weak-napi.svg?style=flat)](https://david-dm.org/node-ffi-napi/weak-napi)
12
+
13
+ On certain rarer occasions, you run into the need to be notified when a JavaScript
14
+ object is going to be garbage collected. This feature is exposed to V8's C++ API,
15
+ but not to JavaScript.
16
+
17
+ That's where `weak-napi` comes in! This module exports the JS engine's GC tracking
18
+ functionality to JavaScript. This allows you to create weak references, and
19
+ optionally attach a callback function to any arbitrary JS object. The callback
20
+ function will be invoked right after the Object is garbage collected (i.e. after
21
+ there are no more remaining references to the Object in JS-land).
22
+
23
+ This module can, for example, be used for debugging; to determine whether or not
24
+ an Object is being garbage collected as it should.
25
+ Take a look at the example below for commented walkthrough scenario.
26
+
27
+
28
+ Installation
29
+ ------------
30
+
31
+ Install with `npm`:
32
+
33
+ ``` bash
34
+ $ npm install weak-napi
35
+ ```
36
+
37
+ Differences from node-weak
38
+ --------------------------
39
+
40
+ This module exports the full `node-weak` API. The main differences are:
41
+
42
+ - This module uses N-API! Yay. That’s a good thing – you don’t need to worry
43
+ about re-compiling your code anymore when upgrading Node.
44
+ - GC callbacks are invoked at a suitable time (after the actual GC run has
45
+ finished). **This is nice because it means your process won’t
46
+ potentially crash.**
47
+ - This module works on Node 6+ only, since it uses a `Proxy` object to give a
48
+ more complete referral of properties.
49
+ - `isNearDeath()` is not supported (always returns false).
50
+
51
+ That’s it!
52
+
53
+ Example
54
+ -------
55
+
56
+ Here's an example of calling a `cleanup()` function on a Object after it gets
57
+ garbage collected:
58
+
59
+ ``` js
60
+ var weak = require('weak-napi')
61
+
62
+ // we are going to "monitor" this Object and invoke "cleanup"
63
+ // before the object is garbage collected
64
+ var obj = {
65
+ a: true
66
+ , foo: 'bar'
67
+ }
68
+
69
+ // Here's where we set up the weak reference
70
+ var ref = weak(obj, function () {
71
+ // `this` inside the callback is the EventEmitter.
72
+ console.log('"obj" has been garbage collected!')
73
+ })
74
+
75
+ // While `obj` is alive, `ref` proxies everything to it, so:
76
+ ref.a === obj.a
77
+ ref.foo === obj.foo
78
+
79
+ // Clear out any references to the object, so that it will be GC'd at some point...
80
+ obj = null
81
+
82
+ //
83
+ //// Time passes, and the garbage collector is run
84
+ //
85
+
86
+ // `callback()` above is called, and `ref` now acts like an empty object.
87
+ typeof ref.foo === 'undefined'
88
+ ```
89
+
90
+
91
+ Weak Callback Function "Best Practices"
92
+ ---------------------------------------
93
+
94
+ It's important to be careful when using the "callbacks" feature of `weak-napi`,
95
+ otherwise you can end up in a situation where the watched object will never
96
+ be garbage collected.
97
+
98
+ You _should **not**_ define the callback function in the same scope as the
99
+ object that is being watched. It's often best to define the callback function
100
+ at the highest scope possible (top-level being the best). Named functions
101
+ work really well for this:
102
+
103
+ ``` js
104
+ var http = require('http')
105
+ , weak = require('weak-napi')
106
+
107
+ http.createServer(function (req, res) {
108
+ weak(req, gcReq)
109
+ weak(res, gcRes)
110
+ res.end('Hello World\n')
111
+ }).listen(3000)
112
+
113
+ function gcReq () {
114
+ console.log('GC\'d `req` object')
115
+ }
116
+
117
+ function gcRes () {
118
+ console.log('GC\'d `res` object')
119
+ }
120
+ ```
121
+
122
+
123
+ API
124
+ ---
125
+
126
+ ### Weakref weak(Object obj [, Function callback])
127
+
128
+ The main exports is the function that creates the weak reference.
129
+ The first argument is the Object that should be monitored.
130
+ The Object can be a regular Object, an Array, a Function, a RegExp, or any of
131
+ the primitive types or constructor function created with `new`.
132
+
133
+ Optionally, you can set a callback function to be invoked
134
+ before the object is garbage collected.
135
+
136
+
137
+ ### Object weak.get(Weakref ref)
138
+
139
+ `get()` returns the actual reference to the Object that this weak reference was
140
+ created with. If this is called with a dead reference, `undefined` is returned.
141
+
142
+
143
+ ### Boolean weak.isDead(Weakref ref)
144
+
145
+ Checks to see if `ref` is a dead reference. Returns `true` if the original Object
146
+ has already been GC'd, `false` otherwise.
147
+
148
+
149
+ ### Boolean weak.isNearDeath(Weakref ref)
150
+
151
+ *Note* The N-API port of this module does not implement this and
152
+ always returns `false`.
153
+
154
+
155
+ ### Boolean weak.isWeakRef(Object obj)
156
+
157
+ Checks to see if `obj` is "weak reference" instance. Returns `true` if the
158
+ passed in object is a "weak reference", `false` otherwise.
159
+
160
+
161
+ ### EventEmitter weak.addCallback(Weakref ref, Function callback)
162
+
163
+ Adds `callback` to the Array of callback functions that will be invoked before the
164
+ Object gets garbage collected. The callbacks get executed in the order that they
165
+ are added.
166
+
167
+
168
+ ### EventEmitter weak.removeCallback(Weakref ref, Function callback)
169
+
170
+ Removes `callback` from the Array of callback functions that will be invoked before
171
+ the Object gets garbage collected.
172
+
173
+
174
+ ### EventEmitter weak.removeCallbacks(Weakref ref)
175
+
176
+ Empties the Array of callback functions that will be invoked before the Object gets
177
+ garbage collected.
178
+
179
+
180
+ ### Array weak.callbacks(Weakref ref)
181
+
182
+ Returns an Array that `ref` iterates through to invoke the GC callbacks. This
183
+ utilizes node's `EventEmitter#listeners()` function and therefore returns a copy
184
+ in node 0.10 and newer.
package/binding.gyp ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ 'targets': [{
3
+ 'target_name': 'weakref',
4
+ 'sources': [ 'src/weakref.cc' ],
5
+ 'include_dirs': ["<!@(node -p \"require('node-addon-api').include\")",
6
+ "<!@(node -p \"require('setimmediate-napi').include\")"],
7
+ 'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
8
+ 'cflags!': [ '-fno-exceptions' ],
9
+ 'cflags_cc!': [ '-fno-exceptions' ],
10
+ 'xcode_settings': {
11
+ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
12
+ 'CLANG_CXX_LIBRARY': 'libc++',
13
+ 'MACOSX_DEPLOYMENT_TARGET': '10.7',
14
+ },
15
+ 'msvs_settings': {
16
+ 'VCCLCompilerTool': { 'ExceptionHandling': 1 },
17
+ },
18
+ }]
19
+ }
Binary file
package/lib/weak.js ADDED
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+ const EventEmitter = require('events');
3
+ const path = require('path');
4
+ const { WeakTag, ObjectInfo } = require('node-gyp-build')(path.join(__dirname, '..'));
5
+ Object.setPrototypeOf(ObjectInfo, EventEmitter);
6
+ Object.setPrototypeOf(ObjectInfo.prototype, EventEmitter.prototype);
7
+
8
+ const kInfo = Symbol('kInfo');
9
+
10
+ const map = new WeakMap();
11
+
12
+ function weak(obj, callback) {
13
+ const info = new ObjectInfo(obj, onGarbageCollect);
14
+ const tag = new WeakTag(info);
15
+ const list = map.get(obj);
16
+ if (list === undefined)
17
+ map.set(obj, [tag]);
18
+ else
19
+ list.push(tag);
20
+ if (typeof callback === 'function')
21
+ info.on('dead', callback);
22
+ return makeProxy(info);
23
+ }
24
+ weak.create = weak;
25
+ module.exports = weak;
26
+
27
+ function onGarbageCollect() {
28
+ this.emit('dead');
29
+ }
30
+
31
+ weak.get = function(ref) {
32
+ return ref[kInfo].target;
33
+ };
34
+
35
+ weak.isDead = function(ref) {
36
+ return weak.get(ref) === undefined;
37
+ };
38
+
39
+ weak.isNearDeath = function(ref) {
40
+ return false;
41
+ };
42
+
43
+ weak.isWeakRef = function(ref) {
44
+ return !!ref[kInfo];
45
+ };
46
+
47
+ weak.addCallback = function(ref, callback) {
48
+ ref[kInfo].addListener('dead', callback);
49
+ };
50
+
51
+ weak.removeCallback = function(ref, callback) {
52
+ ref[kInfo].removeListener('dead', callback);
53
+ };
54
+
55
+ weak.removeCallbacks = function(ref) {
56
+ ref[kInfo].removeAllListeners('dead');
57
+ };
58
+
59
+ weak.callbacks = function(ref) {
60
+ return ref[kInfo].listeners('dead');
61
+ };
62
+
63
+ const proxyHandler = {
64
+ getPrototypeOf(info) {
65
+ const target = info.target;
66
+ return target === undefined ? null : Object.getPrototypeOf(target);
67
+ },
68
+ setPrototypeOf(info, proto) {
69
+ const target = info.target;
70
+ return target === undefined ? false : Object.setPrototypeOf(target, proto);
71
+ },
72
+ isExtensible(info) {
73
+ return Object.isExtensible(info); // For invariant keeping
74
+ },
75
+ preventExtensions(info) {
76
+ const target = info.target;
77
+ if (target !== undefined) {
78
+ Object.preventExtensions(target);
79
+ }
80
+ Object.preventExtensions(info); // For invariant keeping
81
+ return true;
82
+ },
83
+ getOwnPropertyDescriptor(info, key) {
84
+ const target = info.target;
85
+ return target === undefined ? undefined :
86
+ Object.getOwnPropertyDescriptor(target, key);
87
+ },
88
+ defineProperty(info, key, descriptor) {
89
+ const target = info.target;
90
+ return target === undefined ? false :
91
+ Object.defineProperty(target, key, descriptor);
92
+ },
93
+ has(info, key) {
94
+ const target = info.target;
95
+ return target === undefined ? false : key in target;
96
+ },
97
+ get(info, key) {
98
+ if (key === kInfo) return info;
99
+ const target = info.target;
100
+ return target === undefined ? undefined : target[key];
101
+ },
102
+ set(info, key, value) {
103
+ const target = info.target;
104
+ if (target !== undefined) target[key] = value;
105
+ return true;
106
+ },
107
+ deleteProperty(info, key) {
108
+ const target = info.target;
109
+ if (target !== undefined) delete target[key];
110
+ return true;
111
+ },
112
+ ownKeys(info) {
113
+ const target = info.target;
114
+ return target === undefined ? [] :
115
+ Object.getOwnPropertyNames(target).concat(
116
+ Object.getOwnPropertySymbols(target));
117
+ }
118
+ };
119
+
120
+ function makeProxy(info) {
121
+ return new Proxy(info, proxyHandler);
122
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "author": "Anna Henningsen <anna@addaleax.net>",
3
+ "contributors": [
4
+ "Ben Noordhuis <info@bnoordhuis.nl>",
5
+ "Nathan Rajlich <nathan@tootallnate.net>"
6
+ ],
7
+ "name": "@ohos-ports/weak-napi",
8
+ "license": "MIT",
9
+ "description": "Make weak references to JavaScript Objects.",
10
+ "keywords": [
11
+ "weak",
12
+ "reference",
13
+ "js",
14
+ "javascript",
15
+ "object",
16
+ "function",
17
+ "callback",
18
+ "napi"
19
+ ],
20
+ "version": "2.0.2-beta.0",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
24
+ "directory": "ports/weak-napi/2.0.2"
25
+ },
26
+ "main": "lib/weak.js",
27
+ "scripts": {
28
+ "test": "nyc mocha --expose-gc",
29
+ "install": "node-gyp-build",
30
+ "prebuild": "prebuildify --napi --tag-armv --tag-uv"
31
+ },
32
+ "dependencies": {
33
+ "node-addon-api": "^3.0.0",
34
+ "node-gyp-build": "^4.2.1",
35
+ "setimmediate-napi": "^1.0.3"
36
+ },
37
+ "devDependencies": {
38
+ "mocha": "^7.1.1",
39
+ "nyc": "^15.0.0",
40
+ "prebuildify": "^3.0.4",
41
+ "prebuildify-ci": "^1.0.5"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/ohos-ports/ohos-ports/issues"
45
+ },
46
+ "files": [
47
+ "lib/",
48
+ "src/",
49
+ "test/",
50
+ "prebuilds/",
51
+ "build/Release/weakref.node",
52
+ "binding.gyp",
53
+ "LICENSE",
54
+ "README.md",
55
+ "History.md"
56
+ ]
57
+ }
package/src/weakref.cc ADDED
@@ -0,0 +1,98 @@
1
+ #include <napi.h>
2
+ #include <setimmediate.h>
3
+
4
+ using namespace Napi;
5
+
6
+ namespace {
7
+
8
+ // Node.js v12+ (and the ohos node v26.8.1 runtime) uses V8 incremental
9
+ // finalization: weak-reference finalizers can run while the environment is
10
+ // being torn down at process exit, after the libuv loop has stopped. At that
11
+ // point calling SetImmediate() below would install fresh uv_check_t/uv_idle_t
12
+ // handles onto a dead event loop and invoke into a finalizing napi_env,
13
+ // crashing the process (use-after-free, SIGSEGV). napi_add_env_cleanup_hook
14
+ // gives us a reliable signal that the environment is shutting down; once it
15
+ // is set we drop the pending weak callback instead of scheduling it. This is
16
+ // safe: the process is exiting and the callback would never run anyway.
17
+ bool g_env_tearing_down = false;
18
+
19
+ void OnEnvTeardown(void* /*arg*/) {
20
+ g_env_tearing_down = true;
21
+ }
22
+
23
+ class ObjectInfo : public ObjectWrap<ObjectInfo> {
24
+ public:
25
+ ObjectInfo(const CallbackInfo& args) : ObjectWrap(args) {
26
+ if (!args[0].IsObject() && !args[0].IsFunction())
27
+ throw Error::New(Env(), "target should be object");
28
+ if (!args[1].IsFunction())
29
+ throw Error::New(Env(), "callback should be function");
30
+ Ref();
31
+ target_.Reset(args[0].As<Object>(), 0);
32
+ callback_.Reset(args[1].As<Function>(), 1);
33
+ }
34
+
35
+ void OnFree() {
36
+ if (g_env_tearing_down)
37
+ return;
38
+ SetImmediate(Env(), [this]() {
39
+ callback_.MakeCallback(Value(), {});
40
+ callback_.Reset();
41
+ // Release the strong persistent reference so the JS ObjectInfo wrapper
42
+ // becomes collectible (the JS Proxy created by lib/weak.js keeps it
43
+ // alive as long as the user holds it). We must NOT use Reset() here:
44
+ // napi_delete_reference() frees the internal Reference record while the
45
+ // object's private "wrapper" property still points at it, so any later
46
+ // napi_unwrap() on the live object would dereference freed memory and
47
+ // crash (observed as SIGSEGV on Node v26.8.1). Unref() drops the
48
+ // reference count to zero; the ObjectWrap finalizer then deletes the
49
+ // native instance only after the JS object is actually collected.
50
+ Unref();
51
+ });
52
+ }
53
+
54
+ Napi::Value GetTarget(const CallbackInfo&) {
55
+ return target_.IsEmpty() ? Env().Undefined() : target_.Value();
56
+ }
57
+
58
+ static Function GetClass(Napi::Env env) {
59
+ return DefineClass(env, "ObjectInfo", {
60
+ ObjectInfo::InstanceAccessor("target", &ObjectInfo::GetTarget, nullptr),
61
+ });
62
+ }
63
+
64
+ ObjectReference target_;
65
+ FunctionReference callback_;
66
+ };
67
+
68
+ class WeakTag : public ObjectWrap<WeakTag> {
69
+ public:
70
+ WeakTag(const CallbackInfo& args) : ObjectWrap(args) {
71
+ if (args[0].IsObject())
72
+ info_ = ObjectInfo::Unwrap(args[0].As<Object>());
73
+ if (info_ == nullptr)
74
+ throw Error::New(Env(), "First argument needs to be ObjectInfo");
75
+ }
76
+
77
+ ~WeakTag() {
78
+ if (info_ != nullptr)
79
+ info_->OnFree();
80
+ }
81
+
82
+ static Function GetClass(Napi::Env env) {
83
+ return DefineClass(env, "WeakTag", {});
84
+ }
85
+
86
+ ObjectInfo* info_ = nullptr;
87
+ };
88
+
89
+ Object Init(Env env, Object exports) {
90
+ napi_add_env_cleanup_hook(env, OnEnvTeardown, nullptr);
91
+ exports["WeakTag"] = WeakTag::GetClass(env);
92
+ exports["ObjectInfo"] = ObjectInfo::GetClass(env);
93
+ return exports;
94
+ }
95
+
96
+ } // anonymous namespace
97
+
98
+ NODE_API_MODULE(weakref, Init)
package/test/buffer.js ADDED
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+ const assert = require('assert');
3
+ const weak = require('../');
4
+
5
+ // On modern V8 (Node.js >= 12, including the ohos node v26.8.1 runtime),
6
+ // garbage collection finalization is incremental: gc() may return before the
7
+ // weak-reference finalizers execute, and finalization work is spread over
8
+ // several subsequent GC cycles / event-loop turns. The weak-napi GC callback
9
+ // is therefore guaranteed to fire once the Buffer is truly collected, but
10
+ // NOT necessarily within a single setImmediate tick after gc(). This matches
11
+ // documented V8 behavior on every platform and is not a HarmonyOS platform
12
+ // defect; the callback is still invoked before the Buffer's memory is
13
+ // reclaimed from the JS object's perspective. See test/callback.js for the
14
+ // full explanation shared by both test files.
15
+ function waitForGcCallback(isDone, done, maxAttempts) {
16
+ let attempts = 0;
17
+ maxAttempts = maxAttempts || 200;
18
+ (function poll() {
19
+ gc();
20
+ attempts++;
21
+ if (isDone()) return done();
22
+ if (attempts >= maxAttempts) {
23
+ return done(new Error(
24
+ 'weak GC callback did not fire after ' + attempts + ' gc attempts'));
25
+ }
26
+ setTimeout(poll, 0);
27
+ })();
28
+ }
29
+
30
+ describe('weak()', function () {
31
+ afterEach(gc);
32
+
33
+ describe('Buffer', function () {
34
+ it('should invoke callback when Buffer is garbage collected', function(done) {
35
+ let called = false;
36
+ weak(Buffer('test'), function (buf) {
37
+ called = true;
38
+ });
39
+
40
+ assert(!called);
41
+ waitForGcCallback(() => called, done);
42
+ });
43
+ });
44
+ });
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+ const assert = require('assert');
3
+ const weak = require('../');
4
+
5
+ // On modern V8 (Node.js >= 12, including the ohos node v26.8.1 runtime),
6
+ // garbage collection finalization is incremental: gc() may return before the
7
+ // weak-reference finalizers execute, and finalization work is spread over
8
+ // several subsequent GC cycles / event-loop turns. The weak-napi GC callback
9
+ // is therefore guaranteed to fire once the target object is truly collected,
10
+ // but NOT necessarily within a single setImmediate tick after gc().
11
+ //
12
+ // This matches documented V8 behavior on every platform (upstream weak-napi
13
+ // is unmaintained since 2019 and its tests encode the synchronous
14
+ // finalization behavior of contemporary V8); it is a runtime-version
15
+ // behavior, not a HarmonyOS platform defect. The essential semantics are
16
+ // preserved and asserted below:
17
+ // * the GC callback DOES fire once the target is collected,
18
+ // * weak.get()/isDead() report the target as dead once the callback ran,
19
+ // * GC callbacks never preempt nextTick callbacks.
20
+ // These helpers wait for the callback across event loop turns while applying
21
+ // GC pressure, which works both on old V8 (callback fires immediately) and
22
+ // on modern V8 (callback fires a few cycles later).
23
+ function waitForGcCallback(isDone, done, maxAttempts) {
24
+ let attempts = 0;
25
+ maxAttempts = maxAttempts || 200;
26
+ (function poll() {
27
+ gc();
28
+ attempts++;
29
+ if (isDone()) return done();
30
+ if (attempts >= maxAttempts) {
31
+ return done(new Error(
32
+ 'weak GC callback did not fire after ' + attempts + ' gc attempts'));
33
+ }
34
+ setTimeout(poll, 0);
35
+ })();
36
+ }
37
+
38
+ describe('weak()', function() {
39
+ afterEach(gc);
40
+
41
+ describe('garbage collection callback', function() {
42
+ it('should accept a function as second argument', function() {
43
+ const r = weak({}, function () {});
44
+ assert.strictEqual(1, weak.callbacks(r).length);
45
+ });
46
+
47
+ it('should invoke the callback once the target is gc\'d', function(done) {
48
+ let called = false;
49
+ weak({}, function() {
50
+ called = true
51
+ });
52
+ assert(!called);
53
+ waitForGcCallback(() => called, done);
54
+ });
55
+
56
+ it('should invoke *all* callbacks in the internal "callback" Array',
57
+ function(done) {
58
+ const r = weak({});
59
+ let called1 = false;
60
+ let called2 = false;
61
+ weak.addCallback(r, function() {
62
+ called1 = true
63
+ });
64
+ weak.addCallback(r, function() {
65
+ called2 = true
66
+ });
67
+ waitForGcCallback(() => called1 && called2, done);
68
+ });
69
+
70
+ it('should invoke *all* callbacks from different weak references',
71
+ function(done) {
72
+ let obj = {};
73
+ const r1 = weak(obj);
74
+ const r2 = weak(obj);
75
+ assert.strictEqual(weak.get(r1), obj);
76
+ assert.strictEqual(weak.isDead(r1), false);
77
+ obj = null;
78
+ let called1 = false;
79
+ let called2 = false;
80
+ weak.addCallback(r1, function() {
81
+ called1 = true
82
+ });
83
+ weak.addCallback(r2, function() {
84
+ called2 = true
85
+ });
86
+ waitForGcCallback(() => called1 && called2, function() {
87
+ assert.strictEqual(weak.get(r1), undefined);
88
+ assert.strictEqual(weak.isDead(r1), true);
89
+ done();
90
+ });
91
+ });
92
+
93
+ it('should not preempt nextTick callbacks with the GC callback',
94
+ function(done) {
95
+ const events = [];
96
+ weak({}, function() {
97
+ events.push('gcb');
98
+ });
99
+
100
+ process.nextTick(function() {
101
+ events.push('tick');
102
+ });
103
+
104
+ waitForGcCallback(() => events.indexOf('gcb') !== -1, function() {
105
+ assert(events.indexOf('tick') !== -1);
106
+ assert(events.indexOf('tick') < events.indexOf('gcb'));
107
+ done();
108
+ });
109
+ });
110
+ });
111
+ });
112
+
113
+ describe('callbacks()', function() {
114
+ it('should return the Weakref\'s "callback" Array', function () {
115
+ const r = weak({}, function() {});
116
+ const callbacks = weak.callbacks(r);
117
+ assert(Array.isArray(callbacks));
118
+ assert.strictEqual(1, callbacks.length);
119
+ });
120
+ });
121
+
122
+ describe('removeCallback()', function() {
123
+ it('removed callbacks should not be called', function() {
124
+ let called = false;
125
+ const fn = function() { called = true };
126
+ const r = weak({}, fn);
127
+ weak.removeCallback(r, fn);
128
+ gc();
129
+ assert(!called);
130
+ });
131
+ });
132
+
133
+ describe('removeCallbacks()', function() {
134
+ it('removed callbacks should not be called', function() {
135
+ let called = false;
136
+ const fn = function() { called = true };
137
+ const r = weak({}, fn);
138
+ weak.removeCallbacks(r);
139
+ gc();
140
+ assert(!called);
141
+ });
142
+ });
143
+
144
+ describe('isNearDeath()', function() {
145
+ it('returns false', function() {
146
+ assert(!weak.isNearDeath('whatever'));
147
+ });
148
+ });
package/test/create.js ADDED
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+ const assert = require('assert');
3
+ const weak = require('../');
4
+
5
+ describe('create()', function() {
6
+ afterEach(gc)
7
+
8
+ it('should throw on non-"object" values', function() {
9
+ [
10
+ 0,
11
+ 0.0,
12
+ true,
13
+ false,
14
+ null,
15
+ undefined,
16
+ 'foo',
17
+ Symbol(),
18
+ ].forEach((val) => {
19
+ assert.throws(function() {
20
+ weak.create(val);
21
+ });
22
+ });
23
+ });
24
+
25
+ it('should accept "object" values', function() {
26
+ [
27
+ {},
28
+ function() {},
29
+ () => {},
30
+ [],
31
+ Buffer(''),
32
+ new ArrayBuffer(10),
33
+ new Int32Array(new ArrayBuffer(12)),
34
+ Promise.resolve(),
35
+ new WeakMap(),
36
+ ].forEach((val) => {
37
+ assert.doesNotThrow(() => {
38
+ assert.ok(weak.create(val));
39
+ }, Error, String(val));
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+ const assert = require('assert');
3
+ const weak = require('../');
4
+
5
+ function checkFunction(prop) {
6
+ it(`should have a function "${prop}"`, function () {
7
+ assert.strictEqual(typeof weak[prop], 'function');
8
+ })
9
+ }
10
+
11
+ describe('exports', function () {
12
+ afterEach(gc);
13
+
14
+ it('should be a function', function() {
15
+ assert.strictEqual(typeof weak, 'function');
16
+ });
17
+
18
+ checkFunction('get');
19
+ checkFunction('create');
20
+ checkFunction('isWeakRef');
21
+ checkFunction('isNearDeath');
22
+ checkFunction('isDead');
23
+ checkFunction('callbacks');
24
+ checkFunction('addCallback');
25
+ checkFunction('removeCallback');
26
+ checkFunction('removeCallbacks');
27
+
28
+ it('should be a circular reference to "create"', function () {
29
+ assert.strictEqual(weak, weak.create);
30
+ });
31
+ });
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+ const assert = require('assert');
3
+ const weak = require('../');
4
+
5
+ describe('Weakref', function() {
6
+ afterEach(gc);
7
+
8
+ it('weak() should return a `Weakref` instance', function() {
9
+ var ref = weak({});
10
+ assert(weak.isWeakRef(ref));
11
+ });
12
+
13
+ it('should proxy named gets to the target', function() {
14
+ const o = { foo: 'bar' };
15
+ const r = weak(o);
16
+ assert.strictEqual(r.foo, 'bar');
17
+ });
18
+
19
+ it('should proxy named sets to the target', function() {
20
+ const o = {};
21
+ const r = weak(o);
22
+ r.foo = 'bar';
23
+ assert.strictEqual(r.foo, 'bar');
24
+ });
25
+
26
+ it('should proxy named deletes to the target', function() {
27
+ const o = { foo: 'bar' };
28
+ const r = weak(o);
29
+ delete r.foo;
30
+ assert(!r.foo);
31
+ });
32
+
33
+ it('should proxy indexed gets to the target', function() {
34
+ const a = [ 'foo' ];
35
+ const r = weak(a)
36
+ assert.strictEqual(1, a.length);
37
+ assert.strictEqual(1, r.length);
38
+ assert.strictEqual('foo', r[0]);
39
+ });
40
+
41
+ it('should proxy indexed sets to the target', function() {
42
+ const a = [];
43
+ const r = weak(a);
44
+ assert.strictEqual(0, a.length);
45
+ assert.strictEqual(0, r.length);
46
+ r[0] = 'foo';
47
+ assert.strictEqual(1, a.length);
48
+ assert.strictEqual('foo', a[0]);
49
+ r.push('bar');
50
+ assert.strictEqual(2, a.length);
51
+ assert.strictEqual('bar', a[1]);
52
+ });
53
+
54
+ it('should proxy indexed deletes to the target', function() {
55
+ const a = [ 'foo' ];
56
+ const r = weak(a);
57
+ delete r[0];
58
+ assert.strictEqual('undefined', typeof a[0]);
59
+ });
60
+
61
+ it('should proxy enumeration', function() {
62
+ const o = { a: 'a', b: 'b', c: 'c', d: 'd' };
63
+ const r = weak(o);
64
+ assert.deepEqual(Object.keys(o), Object.keys(r))
65
+ });
66
+
67
+ it('should act like an empty object after target is gc\'d', function() {
68
+ let o = { foo: 'bar' };
69
+ const r = weak(o);
70
+ o = null;
71
+ assert.strictEqual('bar', r.foo);
72
+ gc();
73
+ assert(!r.foo);
74
+ assert.strictEqual(0, Object.keys(r).length);
75
+ });
76
+
77
+ it('relays prototype set/gets', function() {
78
+ const p = {};
79
+ let o = { foo: 'bar' };
80
+ const r = weak(o);
81
+ Object.setPrototypeOf(r, p);
82
+ assert.strictEqual(Object.getPrototypeOf(r), p);
83
+ });
84
+
85
+ it('voids prototype set/gets for gc\'ed objects', function() {
86
+ const p = {};
87
+ let o = { foo: 'bar' };
88
+ const r = weak(o);
89
+ Object.setPrototypeOf(r, p);
90
+ assert.strictEqual(Object.getPrototypeOf(r), p);
91
+ o = null;
92
+ gc();
93
+ assert.throws(() => {
94
+ Object.setPrototypeOf(r, p);
95
+ });
96
+ assert.strictEqual(Object.getPrototypeOf(r), null);
97
+ });
98
+
99
+ it('relays extensibility restrictions', function() {
100
+ const p = {};
101
+ const o = { foo: 'bar' };
102
+ const r = weak(o);
103
+ assert(Object.isExtensible(r));
104
+ assert(Object.isExtensible(o));
105
+ Object.preventExtensions(r);
106
+ assert(!Object.isExtensible(r));
107
+ assert(!Object.isExtensible(o));
108
+ });
109
+
110
+ it('voids extensibility restrictions for gc\'ed objects', function() {
111
+ const p = {};
112
+ let o = { foo: 'bar' };
113
+ const r = weak(o);
114
+ assert(Object.isExtensible(r));
115
+ assert(Object.isExtensible(o));
116
+ o = null;
117
+ gc();
118
+ Object.preventExtensions(r);
119
+ assert(!Object.isExtensible(r));
120
+ });
121
+
122
+ it('forwards object descriptor definitions', function() {
123
+ const p = {};
124
+ let o = { foo: 'bar' };
125
+ const r = weak(o);
126
+ assert(Object.defineProperty(r, 'foo', { value: 42 }));
127
+ assert.strictEqual(r.foo, 42);
128
+ assert('foo' in r);
129
+ o = null;
130
+ gc();
131
+ assert.strictEqual(r.foo, undefined);
132
+ r.foo = 9;
133
+ assert.strictEqual(r.foo, undefined);
134
+ assert.throws(() => {
135
+ assert(Object.defineProperty(r, 'foo', { value: 42 }));
136
+ });
137
+ assert(!('foo' in r));
138
+ assert.strictEqual(r.foo, undefined);
139
+ assert.strictEqual(Object.getOwnPropertyDescriptor(r, 'foo'), undefined);
140
+ });
141
+
142
+ it('forwards property deletions', function() {
143
+ const p = {};
144
+ let o = { foo: 'bar' };
145
+ const r = weak(o);
146
+ assert.strictEqual(r.foo, 'bar');
147
+ o = null;
148
+ gc();
149
+ assert.strictEqual(delete r.foo, true);
150
+ });
151
+ });