mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-31 17:59:03 +00:00
31fff2c9a3
* 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
88 lines
2.0 KiB
Go
88 lines
2.0 KiB
Go
package main
|
|
|
|
// Test finalizer invariants that do not require unreachable objects to be collected.
|
|
// runtime_asserts checks the finalizer table, count, and bitmap.
|
|
|
|
import (
|
|
"runtime"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type obj struct{ x int }
|
|
|
|
const batch = 8
|
|
|
|
// Finalizers may run concurrently with main under the threads and cores
|
|
// schedulers, so all observations shared with a finalizer are atomic.
|
|
var (
|
|
clearedRan atomic.Int32
|
|
replacedRan atomic.Int32
|
|
reachedRan atomic.Int32
|
|
ranTwice atomic.Int32
|
|
seen [batch]atomic.Int32
|
|
reachable []*obj
|
|
)
|
|
|
|
//go:noinline
|
|
func dropCleared() {
|
|
p := &obj{}
|
|
runtime.SetFinalizer(p, func(*obj) { clearedRan.Add(1) })
|
|
runtime.SetFinalizer(p, nil)
|
|
}
|
|
|
|
//go:noinline
|
|
func dropReplaced(id int) {
|
|
p := &obj{}
|
|
runtime.SetFinalizer(p, func(*obj) { replacedRan.Add(1) })
|
|
runtime.SetFinalizer(p, func(*obj) {
|
|
if seen[id].Add(1) > 1 {
|
|
ranTwice.Add(1)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:noinline
|
|
func keepReachable(id int) {
|
|
p := &obj{x: id}
|
|
runtime.SetFinalizer(p, func(*obj) { reachedRan.Add(1) })
|
|
reachable = append(reachable, p)
|
|
}
|
|
|
|
func main() {
|
|
for i := 0; i < batch; i++ {
|
|
dropCleared()
|
|
dropReplaced(i)
|
|
keepReachable(i)
|
|
}
|
|
|
|
// Run GC to check bookkeeping and give finalizers bounded opportunities to run.
|
|
// The checks do not require finalization of an unreachable object.
|
|
for i := 0; i < 8; i++ {
|
|
runtime.GC()
|
|
runtime.Gosched()
|
|
}
|
|
|
|
// Keep the reachable objects live across every collection above.
|
|
total := 0
|
|
for _, p := range reachable {
|
|
total += p.x
|
|
}
|
|
if total != batch*(batch-1)/2 {
|
|
println("FAIL: reachable set corrupted:", total)
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case clearedRan.Load() != 0:
|
|
println("FAIL: cleared finalizer ran:", clearedRan.Load())
|
|
case replacedRan.Load() != 0:
|
|
println("FAIL: replaced finalizer ran:", replacedRan.Load())
|
|
case reachedRan.Load() != 0:
|
|
println("FAIL: reachable object was finalized:", reachedRan.Load())
|
|
case ranTwice.Load() != 0:
|
|
println("FAIL: finalizer ran more than once:", ranTwice.Load())
|
|
default:
|
|
println("ok")
|
|
}
|
|
}
|