mirror of https://github.com/tc39/test262.git
Some edgy cases for weakrefs/finalizationgroups
This commit is contained in:
parent
ea359a1d81
commit
3c293f0e6c
|
@ -0,0 +1,135 @@
|
|||
// Copyright (C) 2019 Leo Balter. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-finalization-group-target
|
||||
description: >
|
||||
cleanupCallback has only one optional chance to be called for a GC that cleans up
|
||||
a registered target.
|
||||
info: |
|
||||
FinalizationGroup ( cleanupCallback )
|
||||
|
||||
FinalizationGroup.prototype.cleanupSome ( [ callback ] )
|
||||
|
||||
...
|
||||
4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
|
||||
5. Perform ? CleanupFinalizationGroup(finalizationGroup, callback).
|
||||
6. Return undefined.
|
||||
|
||||
Execution
|
||||
|
||||
At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
|
||||
|
||||
1. For each WeakRef ref such that ref.[[Target]] is obj,
|
||||
a. Set ref.[[Target]] to empty.
|
||||
2. For each FinalizationGroup fg such that fg.[[Cells]] contains cell, such that cell.[[Target]] is obj,
|
||||
a. Set cell.[[Target]] to empty.
|
||||
b. Optionally, perform ! HostCleanupFinalizationGroup(fg).
|
||||
features: [FinalizationGroup, async-function, host-gc-required]
|
||||
flags: [async]
|
||||
---*/
|
||||
|
||||
var cleanupCallback = 0;
|
||||
var called = 0;
|
||||
|
||||
// both this cb and the fg callback are not exhausting the iterator to prevent
|
||||
// the target cell from being removed from the finalizationGroup.[[Cells]].
|
||||
// More info at %FinalizationGroupCleanupIteratorPrototype%.next ( )
|
||||
function cb() {
|
||||
called += 1;
|
||||
};
|
||||
|
||||
var fg = new FinalizationGroup(function() {
|
||||
cleanupCallback += 1;
|
||||
});
|
||||
|
||||
function emptyCells() {
|
||||
(function() {
|
||||
var a = {};
|
||||
fg.register(a, 'a');
|
||||
})();
|
||||
|
||||
// GC is called here
|
||||
$262.gc();
|
||||
}
|
||||
|
||||
emptyCells();
|
||||
|
||||
// Let's add some async ticks, as the cleanupCallback is not meant to interrupt
|
||||
// synchronous operations.
|
||||
async function fn() {
|
||||
await Promise.resolve(1);
|
||||
|
||||
fg.cleanupSome(cb);
|
||||
// This asserts the registered object was emptied in the previous GC.
|
||||
assert.sameValue(called, 1, 'cleanupSome callback for the first time');
|
||||
|
||||
// At this point, we can't assert if cleanupCallback was called, because it's
|
||||
// optional. Although, we can finally assert it's not gonna be called anymore
|
||||
// for the other executions of the Garbage Collector.
|
||||
// The chance of having it called only happens right after the
|
||||
// cell.[[Target]] is set to empty.
|
||||
assert(cleanupCallback >= 0, 'cleanupCallback might be 0');
|
||||
assert(cleanupCallback <= 1, 'cleanupCallback might be 1');
|
||||
|
||||
// Restoring the cleanupCallback variable to 0 will help us asserting the fg
|
||||
// callback is not called again.
|
||||
cleanupCallback = 0;
|
||||
|
||||
$262.gc();
|
||||
await Promise.resolve(2); // tick
|
||||
|
||||
fg.cleanupSome(cb);
|
||||
|
||||
// This cb is called again because fg still holds an emptied cell, but it's
|
||||
// not yet removed from the
|
||||
assert.sameValue(called, 2, 'cleanupSome callback for the second time');
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #1');
|
||||
|
||||
$262.gc();
|
||||
await Promise.resolve(3); // tick
|
||||
|
||||
fg.cleanupSome(cb);
|
||||
|
||||
assert.sameValue(called, 3, 'cleanupSome callback for the third time');
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #2');
|
||||
|
||||
$262.gc();
|
||||
}
|
||||
|
||||
fn()
|
||||
.then(async function() { // tick
|
||||
await Promise.resolve(4); // tick
|
||||
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #3');
|
||||
|
||||
fg.cleanupSome(cb);
|
||||
|
||||
assert.sameValue(called, 4, 'cleanupSome callback for the fourth time');
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #4');
|
||||
|
||||
$262.gc();
|
||||
|
||||
// Now we are exhausting the iterator, so cleanupSome callback will also not be called.
|
||||
fg.cleanupSome(iterator => {
|
||||
var exhausted = [...iterator];
|
||||
assert.sameValue(exhausted.length, 1);
|
||||
assert.sameValue(exhausted[0], 'a');
|
||||
called += 1;
|
||||
});
|
||||
|
||||
assert.sameValue(called, 5, 'cleanupSome callback for the fifth time');
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #4');
|
||||
|
||||
$262.gc();
|
||||
|
||||
await Promise.resolve(5); // tick
|
||||
await await Promise.resolve(6); // more ticks
|
||||
await await await Promise.resolve(7); // even more ticks
|
||||
|
||||
fg.cleanupSome(cb);
|
||||
|
||||
assert.sameValue(called, 5, 'cleanupSome callback is not called anymore, no empty cells');
|
||||
assert.sameValue(cleanupCallback, 0, 'cleanupCallback is not called again #5');
|
||||
})
|
||||
.then($DONE, $DONE);
|
89
test/built-ins/FinalizationGroup/prototype/cleanupSome/cleanup-prevented-with-unregister.js
vendored
Normal file
89
test/built-ins/FinalizationGroup/prototype/cleanupSome/cleanup-prevented-with-unregister.js
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright (C) 2019 Leo Balter. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-finalization-group.prototype.cleanupSome
|
||||
description: Cleanup might be prevented with an unregister usage
|
||||
info: |
|
||||
FinalizationGroup.prototype.cleanupSome ( [ callback ] )
|
||||
|
||||
1. Let finalizationGroup be the this value.
|
||||
2. If Type(finalizationGroup) is not Object, throw a TypeError exception.
|
||||
3. If finalizationGroup does not have a [[Cells]] internal slot, throw a TypeError exception.
|
||||
4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
|
||||
5. Perform ? CleanupFinalizationGroup(finalizationGroup, callback).
|
||||
6. Return undefined.
|
||||
|
||||
FinalizationGroup.prototype.unregister ( unregisterToken )
|
||||
|
||||
1. Let removed be false.
|
||||
2. For each Record { [[Target]], [[Holdings]], [[UnregisterToken]] } cell that is an element of finalizationGroup.[[Cells]], do
|
||||
a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
|
||||
i. Remove cell from finalizationGroup.[[Cells]].
|
||||
ii. Set removed to true.
|
||||
3. Return removed.
|
||||
features: [FinalizationGroup, host-gc-required]
|
||||
---*/
|
||||
|
||||
var holdingsList;
|
||||
var fg = new FinalizationGroup(function() {});
|
||||
|
||||
var unregA = {};
|
||||
var unregB = {};
|
||||
var unregC = {};
|
||||
var unregDE = {};
|
||||
var unregDE = {};
|
||||
|
||||
function emptyCells() {
|
||||
(function() {
|
||||
var a = {};
|
||||
var b = {};
|
||||
var c = {};
|
||||
var d = {};
|
||||
var e = {};
|
||||
fg.register(a, 'a', unregA);
|
||||
fg.register(b, 'b', unregB);
|
||||
fg.register(c, 'c', unregC);
|
||||
fg.register(d, 'd', unregDE);
|
||||
fg.register(e, 'e', unregDE);
|
||||
})();
|
||||
|
||||
var res = fg.unregister(unregC); // unregister 'c' before GC
|
||||
assert.sameValue(res, true, 'unregister c before GC');
|
||||
|
||||
$262.gc();
|
||||
}
|
||||
|
||||
emptyCells();
|
||||
|
||||
var res = fg.unregister(unregDE);
|
||||
assert.sameValue(res, true, 'unregister d and e after GC');
|
||||
|
||||
fg.cleanupSome(function cb(iterator) {
|
||||
var res = fb.unregister(unregA);
|
||||
assert.sameValue(res, true, 'unregister a before the iterator is consumed.');
|
||||
|
||||
holdingsList = [...iterator];
|
||||
|
||||
// It's not possible to verify an unregister during the iterator consumption
|
||||
// as it's .next() does not have any specific order to follow for each item.
|
||||
});
|
||||
|
||||
assert.sameValue(holdingsList[0], 'b');
|
||||
assert.sameValue(holdingsList.length, 1);
|
||||
|
||||
// Second run
|
||||
res = fg.unregister(unregB); // let's empty the cells
|
||||
assert.sameValue(res, true, 'unregister B for cleanup');
|
||||
holdingsList = undefined;
|
||||
emptyCells();
|
||||
|
||||
fg.cleanupSome(function cb(iterator) {
|
||||
var res = fb.unregister(unregDE);
|
||||
assert.sameValue(res, true, 'unregister d and e before the iterator is consumed.');
|
||||
holdingsList = [...iterator];
|
||||
});
|
||||
|
||||
assert(holdingsList.includes('b'));
|
||||
assert(holdingsList.includes('a'), 'just like the first run, now without removing a');
|
||||
assert.sameValue(holdingsList.length, 2);
|
51
test/built-ins/FinalizationGroup/prototype/cleanupSome/gc-cleanup-not-prevented-with-wr-deref.js
vendored
Normal file
51
test/built-ins/FinalizationGroup/prototype/cleanupSome/gc-cleanup-not-prevented-with-wr-deref.js
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright (C) 2019 Leo Balter. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-finalization-group.prototype.cleanupSome
|
||||
description: WeakRef deref should not prevent a GC event
|
||||
info: |
|
||||
FinalizationGroup.prototype.cleanupSome ( [ callback ] )
|
||||
|
||||
...
|
||||
4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
|
||||
5. Perform ? CleanupFinalizationGroup(finalizationGroup, callback).
|
||||
6. Return undefined.
|
||||
|
||||
WeakRef.prototype.deref ( )
|
||||
|
||||
...
|
||||
4. Let target be the value of weakRef.[[Target]].
|
||||
5. If target is not empty,
|
||||
a. Perform ! KeepDuringJob(target).
|
||||
b. Return target.
|
||||
6. Return undefined.
|
||||
features: [FinalizationGroup, WeakRef, host-gc-required]
|
||||
---*/
|
||||
|
||||
var holdingsList;
|
||||
function cb(iterator) {
|
||||
holdingsList = [...iterator];
|
||||
};
|
||||
var fg = new FinalizationGroup(function() {});
|
||||
|
||||
var deref = false;
|
||||
|
||||
function emptyCells() {
|
||||
var wr;
|
||||
(function() {
|
||||
var a = {};
|
||||
wr = new WeakRef(a);
|
||||
fg.register(a, 'a');
|
||||
})();
|
||||
$262.gc();
|
||||
deref = wr.deref();
|
||||
}
|
||||
|
||||
emptyCells();
|
||||
fg.cleanupSome(cb);
|
||||
|
||||
assert.sameValue(holdingsList.length, 1);
|
||||
assert.sameValue(holdingsList[0], 'a');
|
||||
|
||||
assert.sameValue(deref, undefined, 'deref handles an empty wearRef.[[Target]] returning undefined');
|
33
test/built-ins/WeakRef/prototype/deref/gc-cleanup-not-prevented-with-wr-deref.js
vendored
Normal file
33
test/built-ins/WeakRef/prototype/deref/gc-cleanup-not-prevented-with-wr-deref.js
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (C) 2019 Leo Balter. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-weak-ref.prototype.deref
|
||||
description: WeakRef deref should not prevent a GC event
|
||||
info: |
|
||||
WeakRef.prototype.deref ( )
|
||||
|
||||
...
|
||||
4. Let target be the value of weakRef.[[Target]].
|
||||
5. If target is not empty,
|
||||
a. Perform ! KeepDuringJob(target).
|
||||
b. Return target.
|
||||
6. Return undefined.
|
||||
features: [WeakRef, host-gc-required]
|
||||
---*/
|
||||
|
||||
var deref = false;
|
||||
|
||||
function emptyCells() {
|
||||
var wr;
|
||||
(function() {
|
||||
var a = {};
|
||||
wr = new WeakRef(a);
|
||||
})();
|
||||
$262.gc();
|
||||
deref = wr.deref();
|
||||
}
|
||||
|
||||
emptyCells();
|
||||
|
||||
assert.sameValue(deref, undefined);
|
Loading…
Reference in New Issue