Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leak during continuous image updates #30

Open
hungry opened this issue Dec 6, 2024 · 8 comments
Open

Memory Leak during continuous image updates #30

hungry opened this issue Dec 6, 2024 · 8 comments
Assignees
Labels

Comments

@hungry
Copy link

hungry commented Dec 6, 2024

Hello!

I have encountered a memory leak issue when using the Spot library to develop a program that functions similarly to the magnifier tool in Windows. The program continuously captures a portion of the screen, magnifies it, and displays it in a window. However, over time, the memory usage of the program keeps increasing, leading to a memory leak.

Code Snippet

package main

import (
	"image"
	"image/color"
	"log"
	"math/rand"
	"runtime"
	"time"

	"github.com/roblillack/spot"
	"github.com/roblillack/spot/ui"
)

func logMemoryUsage() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	log.Printf("TotalAlloc = %v MiB", m.TotalAlloc/1024/1024)
}

func main() {
	width, height := 5, 5
	zoom := 100

	ui.Init()

	spot.MountFn(func(ctx *spot.RenderContext) spot.Component {
		canvas, SetCanvas := spot.UseState(ctx, image.NewRGBA(image.Rect(0, 0, width*zoom, height*zoom)))

		spot.UseEffect(ctx, func() {
			go func() {
				i := 0
				for {
					for x := 0; x < width; x++ {
						for y := 0; y < height; y++ {
							g := uint8(rand.Intn(256))
							color := color.RGBA{g, g, g, 255}
							for zx := 0; zx < zoom; zx++ {
								for zy := 0; zy < zoom; zy++ {
									canvas.Set(x*zoom+zx, y*zoom+zy, color)
								}
							}
						}
					}
					SetCanvas(canvas)

					if i%10 == 0 {
						logMemoryUsage()
					}
					i += 1

					time.Sleep(300 * time.Millisecond)
				}
			}()
		}, []any{})

		return &ui.Window{
			Title:  "Title",
			Width:  canvas.Rect.Dx(),
			Height: canvas.Rect.Dy(),
			Children: []spot.Component{
				&ui.Image{
					X: 0, Y: 0,
					Width:  canvas.Rect.Dx(),
					Height: canvas.Rect.Dy(),
					Image:  canvas,
				},
			},
		}
	})

	ui.Run()
}

Invocation
go build -ldflags "-s -w" && main.exe

Program output

2024/12/06 12:53:20 TotalAlloc = 2 MiB
2024/12/06 12:53:23 TotalAlloc = 22 MiB
2024/12/06 12:53:26 TotalAlloc = 41 MiB
2024/12/06 12:53:29 TotalAlloc = 60 MiB
2024/12/06 12:53:32 TotalAlloc = 79 MiB
2024/12/06 12:53:35 TotalAlloc = 99 MiB
2024/12/06 12:53:39 TotalAlloc = 118 MiB
2024/12/06 12:53:42 TotalAlloc = 137 MiB
2024/12/06 12:53:45 TotalAlloc = 156 MiB
2024/12/06 12:53:48 TotalAlloc = 175 MiB
2024/12/06 12:53:51 TotalAlloc = 194 MiB
2024/12/06 12:53:55 TotalAlloc = 214 MiB
2024/12/06 12:53:58 TotalAlloc = 233 MiB
2024/12/06 12:54:01 TotalAlloc = 252 MiB
2024/12/06 12:54:04 TotalAlloc = 271 MiB
2024/12/06 12:54:07 TotalAlloc = 290 MiB
2024/12/06 12:54:11 TotalAlloc = 309 MiB
2024/12/06 12:54:14 TotalAlloc = 328 MiB
2024/12/06 12:54:17 TotalAlloc = 348 MiB
2024/12/06 12:54:20 TotalAlloc = 367 MiB

Expected Behavior
The program should continuously update the displayed image without a significant increase in memory usage.

Actual Behavior
The program's memory usage keeps increasing over time, indicating a memory leak.

Additional Information
Go version: go1.22.5 windows/amd64
Spot library version: v0.3.2
Operating System: Windows 10

I would appreciate any guidance or fixes to resolve this memory leak issue. Thank you!

@hungry
Copy link
Author

hungry commented Dec 6, 2024

To isolate the problem, the provided code uses randomly generated images instead of actual screen captures.

@roblillack
Copy link
Owner

Hey @hungry, thanks for the awesome test case! Alas, by outputting TotalAlloc you will see the total number of bytes accumulated over time, not the amount of bytes currently allocated. Changing this to HeapAlloc should result in a rather constant use of ~1 MiB.

Also to improve memory pressure, you might want to add image.NewRGBA(image.Rect(0, 0, width*zoom, height*zoom)) to a variable what is not re-created (and then discarded) for every render:

	emptyImg := image.NewRGBA(image.Rect(0, 0, width*zoom, height*zoom))

	ui.Init()

	spot.MountFn(func(ctx *spot.RenderContext) spot.Component {
		canvas, SetCanvas := spot.UseState(ctx, emptyImg)
		spot.UseEffect(ctx, func() {
        ...

@hungry
Copy link
Author

hungry commented Dec 6, 2024

Thanks for the quick reply!
I've applied the optimizations you suggest, but as far as I can tell, the problem persists.

Please see this screenshot:
Screenshot 2024-12-06 170015

HeapAlloc indeed reports rather small amounts, but Windows Task Manager reports constantly growing size of memory used, which correlates to TotalAlloc. Eventually, the program is unable to allocate more memory and crashes -- that's why I even noticed the problem and started digging into it.

@roblillack
Copy link
Owner

Might be a weird question, but you did not turn of Go's garbage collection using some environment variable, no?

@hungry
Copy link
Author

hungry commented Dec 6, 2024

Neither intentionally, nor otherwise:

>SET | grep -i -e "^go"
GOPATH=C:\Users\user\go

@hungry
Copy link
Author

hungry commented Dec 6, 2024

I also tried running runtime.GC() manually on each cycle, was of no use.

@roblillack
Copy link
Owner

roblillack commented Dec 7, 2024

Hey @hungry, I created a PR which should resolve this issue -- tested it on Linux and macOS. Please give it a try and report back:

@hungry
Copy link
Author

hungry commented Dec 7, 2024

This is awesome! Now memory usage is stable, at least for me and this particular test case. Thanks a lot @roblillack ❤️

roblillack added a commit that referenced this issue Dec 19, 2024
* cocoa: Add NSImages and NSBitmapImageReps into autorelease pool. #30

* fltk: Destroy old RgbImage when setting a new one. #30
roblillack added a commit that referenced this issue Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants