internal/atlas: bug fix: a finalizer was never called

As the finalizer function had a reference to the target image, the
image's reference count never became 0. Then, the image was never
finalized.

This change fixes this issue by using a member function pointer instead
of an anonymous function.

Closes #2897
This commit is contained in:
Hajime Hoshi 2024-01-29 20:38:17 +09:00
parent 48f3d43189
commit 9a8dde6503
3 changed files with 29 additions and 8 deletions

View File

@ -67,3 +67,9 @@ func (i *Image) EnsureIsolatedFromSourceForTesting(backends []*backend) {
var FlushDeferredForTesting = flushDeferred
var FloorPowerOf2 = floorPowerOf2
func DeferredFuncCountForTesting() int {
deferredM.Lock()
defer deferredM.Unlock()
return len(deferred)
}

View File

@ -715,6 +715,15 @@ func (i *Image) canBePutOnAtlas() bool {
return i.width+i.paddingSize() <= maxSize && i.height+i.paddingSize() <= maxSize
}
func (i *Image) finalize() {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
i.deallocate()
runtime.SetFinalizer(i, nil)
})
}
func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
if !graphicsDriverInitialized {
panic("atlas: graphics driver must be ready at allocate but not")
@ -724,14 +733,7 @@ func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
panic("atlas: the image is already allocated")
}
runtime.SetFinalizer(i, func(image *Image) {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
image.deallocate()
runtime.SetFinalizer(i, nil)
})
})
runtime.SetFinalizer(i, (*Image).finalize)
if i.imageType == ImageTypeScreen {
if asSource {

View File

@ -810,4 +810,17 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
}
}
func TestGC(t *testing.T) {
c0 := atlas.DeferredFuncCountForTesting()
img := atlas.NewImage(16, 16, atlas.ImageTypeRegular)
img.WritePixels(make([]byte, 4*16*16), image.Rect(0, 0, 16, 16))
_ = img
runtime.GC()
c1 := atlas.DeferredFuncCountForTesting()
if got, want := c1, c0+1; got != want {
t.Errorf("got: %d, want: %d", got, want)
}
}
// TODO: Add tests to extend image on an atlas out of the main loop