Files
tinygo/testdata/wasmexport-finalizer.js
T
Felipe Gené 31fff2c9a3 runtime: run syscall/js finalizers on wasm without a manual GC (#5545)
* runtime: run syscall/js finalizers on wasm without a manual GC

* runtime: address review feedback on finalizer idle GC

* runtime: clear a finished task's args pointer so its arguments are collectable

* runtime: skip the finalizer scan with a per-block registration bit

* runtime: guard the finalizer registration bitmap with gcLock

* testdata: cover finalizer invariants on every scheduler

* main_test: limit the finalizer scheduler variants to linux and darwin

* testdata: wait for the finalizer queue to drain before asserting

* testdata: make the finalizer counters atomic and wait for a known drain count

* runtime: add finalizer bookkeeping asserts under runtime_asserts

* runtime: address finalizer GC review feedback

* testdata: strengthen blocked stack finalizer test

* runtime: fix finalizer cleanup edge cases

* runtime: decouple wasm export scheduling from finalizers

* runtime: avoid redundant wakeups for re-entrant wasm exports

* runtime: simplify finalizer comments
2026-08-26 20:03:44 +02:00

75 lines
2.3 KiB
JavaScript

const fs = require('fs');
require('../targets/wasm_exec.js');
let finalized = 0;
let backgroundRuns = 0;
let instance;
const go = new Go();
const sleepTicks = go.importObject.gojs['runtime.sleepTicks'];
let zeroRearms = 0;
go.importObject.gojs['runtime.sleepTicks'] = timeout => {
if (Number(timeout) === 0) {
zeroRearms++;
}
return sleepTicks(timeout);
};
go.importObject.tester = {
finalizerRan: () => {
finalized++;
},
backgroundRan: () => {
backgroundRuns++;
},
callNestedExport: () => {
instance.exports.launchBackground();
},
};
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async result => {
instance = result.instance;
await go.run(instance);
const topLevelRearms = zeroRearms;
instance.exports.launchBackground();
if (backgroundRuns !== 0) {
throw new Error('wasm export ran a background goroutine before returning');
}
if (zeroRearms !== topLevelRearms + 1) {
throw new Error('top-level wasm export did not schedule exactly one wakeup');
}
for (let i = 0; i < 500 && backgroundRuns === 0; i++) {
await new Promise(resolve => setTimeout(resolve, 1));
}
if (backgroundRuns !== 1) {
throw new Error('wasm scheduler did not resume after an export without finalizers');
}
instance.exports.installNestedCallback();
const nestedRearms = zeroRearms;
global.nestedExportCallback();
if (zeroRearms !== nestedRearms) {
throw new Error('re-entrant wasm export scheduled a redundant wakeup');
}
if (backgroundRuns !== 2) {
throw new Error('outer scheduler did not drain work from a re-entrant wasm export');
}
instance.exports.registerFinalizers();
if (backgroundRuns !== 2) {
throw new Error('wasm export ran an unrelated goroutine before returning');
}
for (let i = 0; i < 500 && (finalized === 0 || backgroundRuns < 3); i++) {
await new Promise(resolve => setTimeout(resolve, 1));
}
if (finalized === 0) {
throw new Error('no wasm-export finalizer ran after returning to JavaScript');
}
if (backgroundRuns !== 3) {
throw new Error('wasm scheduler did not resume after a finalizer export');
}
}).catch(err => {
console.error(err);
process.exit(1);
});