зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1587096 - Part 4: Support FinlalizationGroups in the shell and add tests r=sfink
Differential Revision: https://phabricator.services.mozilla.com/D49947 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f3b48418af
Коммит
08d1fba1d5
|
@ -22,6 +22,11 @@ function assertThrowsTypeError(thunk) {
|
|||
// 3.1 The FinalizationGroup Constructor
|
||||
assertEq(typeof this.FinalizationGroup, "function");
|
||||
|
||||
// 3.1.1 FinalizationGroup ( cleanupCallback )
|
||||
assertThrowsTypeError(() => new FinalizationGroup());
|
||||
assertThrowsTypeError(() => new FinalizationGroup(1));
|
||||
new FinalizationGroup(x => 0);
|
||||
|
||||
// 3.2 Properties of the FinalizationGroup Constructor
|
||||
assertEq(Object.getPrototypeOf(FinalizationGroup), Function.prototype);
|
||||
|
||||
|
@ -35,10 +40,249 @@ assertEq(Object.getPrototypeOf(proto), Object.prototype);
|
|||
// 3.3.1 FinalizationGroup.prototype.constructor
|
||||
assertEq(proto.constructor, FinalizationGroup);
|
||||
|
||||
//3.3.5 FinalizationGroup.prototype [ @@toStringTag ]
|
||||
// 3.3.2 FinalizationGroup.prototype.register ( target , holdings [, unregisterToken ] )
|
||||
assertEq(proto.hasOwnProperty('register'), true);
|
||||
assertEq(typeof proto.register, 'function');
|
||||
|
||||
// 3.3.3 FinalizationGroup.prototype.unregister ( unregisterToken )
|
||||
assertEq(proto.hasOwnProperty('unregister'), true);
|
||||
assertEq(typeof proto.unregister, 'function');
|
||||
|
||||
// 3.3.4 FinalizationGroup.prototype.cleanupSome ( [ callback ] )
|
||||
assertEq(proto.hasOwnProperty('cleanupSome'), true);
|
||||
assertEq(typeof proto.cleanupSome, 'function');
|
||||
|
||||
// 3.3.5 FinalizationGroup.prototype [ @@toStringTag ]
|
||||
assertEq(proto[Symbol.toStringTag], "FinalizationGroup");
|
||||
checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true);
|
||||
|
||||
assertThrowsTypeError(() => new FinalizationGroup());
|
||||
assertThrowsTypeError(() => new FinalizationGroup(1));
|
||||
let group = new FinalizationGroup(x => 1);
|
||||
// 3.4 Properties of FinalizationGroup Instances
|
||||
let group = new FinalizationGroup(x => 0);
|
||||
assertEq(Object.getPrototypeOf(group), proto);
|
||||
assertEq(Object.getOwnPropertyNames(group).length, 0);
|
||||
|
||||
// Get a cleanup iterator.
|
||||
let iterator;
|
||||
new FinalizationGroup(it => iterator = it).register({}, 0);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(typeof iterator, 'object');
|
||||
|
||||
// 3.5.2 The %FinalizationGroupCleanupIteratorPrototype% Object
|
||||
let arrayIterator = [][Symbol.iterator]();
|
||||
let iteratorProto = arrayIterator.__proto__.__proto__;
|
||||
proto = iterator.__proto__;
|
||||
assertEq(typeof proto, "object");
|
||||
assertEq(proto.__proto__, iteratorProto);
|
||||
|
||||
// 3.5.2.1 %FinalizationGroupCleanupIteratorPrototype%.next()
|
||||
assertEq(proto.hasOwnProperty("next"), true);
|
||||
assertEq(typeof proto.next, "function");
|
||||
|
||||
// 3.5.2.2 %FinalizationGroupCleanupIteratorPrototype% [ @@toStringTag ]
|
||||
assertEq(proto[Symbol.toStringTag], "FinalizationGroup Cleanup Iterator");
|
||||
checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true);
|
||||
|
||||
// 3.5.3 Properties of FinalizationGroup Cleanup Iterator Instances
|
||||
assertEq(Object.getOwnPropertyNames(iterator).length, 0);
|
||||
|
||||
let holdings = [];
|
||||
group = new FinalizationGroup(iterator => {
|
||||
for (const holding of iterator) {
|
||||
holdings.push(holding);
|
||||
}
|
||||
});
|
||||
|
||||
// Test a single target.
|
||||
holdings = [];
|
||||
group.register({}, 42);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], 42);
|
||||
|
||||
// Test multiple targets.
|
||||
holdings = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
group.register({}, i);
|
||||
}
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 100);
|
||||
holdings = holdings.sort((a, b) => a - b);
|
||||
for (let i = 0; i < 100; i++) {
|
||||
assertEq(holdings[i], i);
|
||||
}
|
||||
|
||||
// Test a single object in multiple groups
|
||||
holdings = [];
|
||||
let holdings2 = [];
|
||||
let group2 = new FinalizationGroup(iterator => {
|
||||
for (const holding of iterator) {
|
||||
holdings2.push(holding);
|
||||
}
|
||||
});
|
||||
{
|
||||
let object = {};
|
||||
group.register(object, 1);
|
||||
group2.register(object, 2);
|
||||
object = null;
|
||||
}
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], 1);
|
||||
assertEq(holdings2.length, 1);
|
||||
assertEq(holdings2[0], 2);
|
||||
|
||||
// Unregister a single target.
|
||||
holdings = [];
|
||||
let token = {};
|
||||
group.register({}, 1, token);
|
||||
group.unregister(token);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 0);
|
||||
|
||||
// Unregister multiple targets.
|
||||
holdings = [];
|
||||
let token2 = {};
|
||||
group.register({}, 1, token);
|
||||
group.register({}, 2, token2);
|
||||
group.register({}, 3, token);
|
||||
group.register({}, 4, token2);
|
||||
group.unregister(token);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 2);
|
||||
holdings = holdings.sort((a, b) => a - b);
|
||||
assertEq(holdings[0], 2);
|
||||
assertEq(holdings[1], 4);
|
||||
|
||||
// OOM test.
|
||||
if ('oomTest' in this) {
|
||||
oomTest(() => new FinalizationGroup(x => 0));
|
||||
|
||||
let token = {};
|
||||
oomTest(() => group.register({}, 1, token));
|
||||
oomTest(() => group.unregister(token));
|
||||
}
|
||||
|
||||
// Watch object in another global.
|
||||
let other = newGlobal({newCompartment: true});
|
||||
holdings = [];
|
||||
group.register(evalcx('({})', other), 1);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], 1);
|
||||
|
||||
// Pass holdings from another global.
|
||||
let holding = evalcx('{}', other);
|
||||
holdings = [];
|
||||
group.register({}, holding);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], holding);
|
||||
|
||||
// Pass unregister token from another global.
|
||||
token = evalcx('({})', other);
|
||||
holdings = [];
|
||||
group.register({}, 1, token);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], 1);
|
||||
holdings = [];
|
||||
group.register({}, 1, token);
|
||||
group.unregister(token);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 0);
|
||||
|
||||
// FinalizationGroup is designed to be subclassable.
|
||||
class MyGroup extends FinalizationGroup {
|
||||
constructor(callback) {
|
||||
super(callback);
|
||||
}
|
||||
}
|
||||
let g2 = new MyGroup(iterator => {
|
||||
for (const holding of iterator) {
|
||||
holdings.push(holding);
|
||||
}
|
||||
});
|
||||
holdings = [];
|
||||
g2.register({}, 42);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], 42);
|
||||
|
||||
// Test trying to use iterator after the callback.
|
||||
iterator = undefined;
|
||||
let g3 = new FinalizationGroup(i => iterator = i);
|
||||
g3.register({}, 1);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
assertEq(typeof iterator, 'object');
|
||||
assertThrowsTypeError(() => iterator.next());
|
||||
|
||||
// Test trying to use the wrong iterator inside the callback.
|
||||
let g4 = new FinalizationGroup(x => {
|
||||
assertThrowsTypeError(() => iterator.next());
|
||||
});
|
||||
g4.register({}, 1);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
|
||||
// Test cleanupSome.
|
||||
holdings = [];
|
||||
let g5 = new FinalizationGroup(i => holdings = [...i]);
|
||||
g5.register({}, 1);
|
||||
g5.register({}, 2);
|
||||
g5.register({}, 3);
|
||||
gc();
|
||||
g5.cleanupSome();
|
||||
assertEq(holdings.length, 3);
|
||||
holdings = holdings.sort((a, b) => a - b);
|
||||
assertEq(holdings[0], 1);
|
||||
assertEq(holdings[1], 2);
|
||||
assertEq(holdings[2], 3);
|
||||
|
||||
// Test trying to call cleanupSome in callback.
|
||||
let g6 = new FinalizationGroup(x => {
|
||||
assertThrowsTypeError(() => g6.cleanupSome());
|
||||
});
|
||||
g6.register({}, 1);
|
||||
gc();
|
||||
drainJobQueue();
|
||||
|
||||
// Test combinations of arguments in different compartments.
|
||||
function ccw() {
|
||||
return evalcx('({})', newGlobal({newCompartment: true}));
|
||||
}
|
||||
function incrementalGC() {
|
||||
startgc(1);
|
||||
while (gcstate() !== "NotActive") {
|
||||
gcslice(1000);
|
||||
}
|
||||
}
|
||||
for (let x of [false, true]) {
|
||||
for (let y of [false, true]) {
|
||||
for (let z of [false, true]) {
|
||||
let target = x ? ccw() : {};
|
||||
let holding = x ? ccw() : {};
|
||||
let token = x ? ccw() : {};
|
||||
group.register(target, holding, token);
|
||||
group.unregister(token);
|
||||
group.register(target, holding, token);
|
||||
target = undefined;
|
||||
incrementalGC();
|
||||
holdings = [];
|
||||
group.cleanupSome();
|
||||
assertEq(holdings.length, 1);
|
||||
assertEq(holdings[0], holding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -638,7 +638,8 @@ ShellContext::ShellContext(JSContext* cx)
|
|||
readLineBufPos(0),
|
||||
errFilePtr(nullptr),
|
||||
outFilePtr(nullptr),
|
||||
offThreadMonitor(mutexid::ShellOffThreadState) {}
|
||||
offThreadMonitor(mutexid::ShellOffThreadState),
|
||||
finalizationGroupsToCleanUp(cx) {}
|
||||
|
||||
ShellContext::~ShellContext() { MOZ_ASSERT(offThreadJobs.empty()); }
|
||||
|
||||
|
@ -1015,6 +1016,38 @@ static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
|
|||
return JS_CallFunction(cx, nullptr, importFun, args, &value);
|
||||
}
|
||||
|
||||
static void ShellCleanupFinalizationGroupCallback(JSObject* group, void* data) {
|
||||
// In the browser this queues a task. Shell jobs correspond to microtasks so
|
||||
// we arrange for cleanup to happen after all jobs/microtasks have run.
|
||||
auto sc = static_cast<ShellContext*>(data);
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!sc->finalizationGroupsToCleanUp.append(group)) {
|
||||
oomUnsafe.crash("ShellCleanupFinalizationGroupCallback");
|
||||
}
|
||||
}
|
||||
|
||||
static void MaybeRunFinalizationGroupCleanupTasks(JSContext* cx) {
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
MOZ_ASSERT(!sc->quitting);
|
||||
|
||||
Rooted<ShellContext::ObjectVector> groups(cx);
|
||||
std::swap(groups.get(), sc->finalizationGroupsToCleanUp.get());
|
||||
|
||||
RootedObject group(cx);
|
||||
for (const auto& g : groups) {
|
||||
group = g;
|
||||
|
||||
{
|
||||
AutoReportException are(cx);
|
||||
mozilla::Unused << JS::CleanupQueuedFinalizationGroup(cx, group);
|
||||
}
|
||||
|
||||
if (sc->quitting) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
|
@ -1029,6 +1062,22 @@ static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return js::EnqueueJob(cx, job);
|
||||
}
|
||||
|
||||
static void RunShellJobs(JSContext* cx) {
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
if (sc->quitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run microtasks.
|
||||
js::RunJobs(cx);
|
||||
if (sc->quitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run tasks (only finalization group clean tasks are possible).
|
||||
MaybeRunFinalizationGroupCleanupTasks(cx);
|
||||
}
|
||||
|
||||
static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
|
@ -1038,7 +1087,7 @@ static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
js::RunJobs(cx);
|
||||
RunShellJobs(cx);
|
||||
|
||||
if (GetShellContext(cx)->quitting) {
|
||||
return false;
|
||||
|
@ -1403,10 +1452,7 @@ static MOZ_MUST_USE bool ReadEvalPrintLoop(JSContext* cx, FILE* in,
|
|||
stderr);
|
||||
}
|
||||
|
||||
if (!GetShellContext(cx)->quitting) {
|
||||
js::RunJobs(cx);
|
||||
}
|
||||
|
||||
RunShellJobs(cx);
|
||||
} while (!hitEOF && !sc->quitting);
|
||||
|
||||
if (gOutFile->isOpen()) {
|
||||
|
@ -4054,6 +4100,9 @@ static void WorkerMain(WorkerInput* input) {
|
|||
|
||||
js::UseInternalJobQueues(cx);
|
||||
|
||||
JS::SetHostCleanupFinalizationGroupCallback(
|
||||
cx, ShellCleanupFinalizationGroupCallback, sc);
|
||||
|
||||
if (!JS::InitSelfHostedCode(cx)) {
|
||||
return;
|
||||
}
|
||||
|
@ -10838,9 +10887,7 @@ static int Shell(JSContext* cx, OptionParser* op, char** envp) {
|
|||
* tasks before the main thread JSRuntime is torn down. Drain after
|
||||
* uncaught exceptions have been reported since draining runs callbacks.
|
||||
*/
|
||||
if (!GetShellContext(cx)->quitting) {
|
||||
js::RunJobs(cx);
|
||||
}
|
||||
RunShellJobs(cx);
|
||||
|
||||
// Only if there's no other error, report unhandled rejections.
|
||||
if (!result && !sc->exitCode) {
|
||||
|
@ -11428,6 +11475,9 @@ int main(int argc, char** argv, char** envp) {
|
|||
|
||||
js::UseInternalJobQueues(cx);
|
||||
|
||||
JS::SetHostCleanupFinalizationGroupCallback(
|
||||
cx, ShellCleanupFinalizationGroupCallback, sc.get());
|
||||
|
||||
auto shutdownShellThreads = MakeScopeExit([cx] {
|
||||
KillWatchdog(cx);
|
||||
KillWorkerThreads(cx);
|
||||
|
|
|
@ -182,6 +182,10 @@ struct ShellContext {
|
|||
// Off-thread parse state.
|
||||
js::Monitor offThreadMonitor;
|
||||
Vector<OffThreadJob*, 0, SystemAllocPolicy> offThreadJobs;
|
||||
|
||||
// Queued finalization group cleanup jobs.
|
||||
using ObjectVector = GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
JS::PersistentRooted<ObjectVector> finalizationGroupsToCleanUp;
|
||||
};
|
||||
|
||||
extern ShellContext* GetShellContext(JSContext* cx);
|
||||
|
|
Загрузка…
Ссылка в новой задаче