beeware / beeware/toga

GTK Image Memory Leak

Open
#4,684 1 comment 0 reactions 0 assignees View on GitHub
bug linux
Dominant language
Python
Stars
5.4k
Forks
827
Avg merge
15h 39m
Merged PRs (30d)
40

Description

### Describe the bug

GTK backend's Image implementation leaks memory even after GC on the Python side. When I repeatedly create Image instances under GTK backend and explicitly call gc.collect, memory usage still increases each time I create a new batch of instances.

### How to reproduce the bug

1. Save a large image (say an old movie poster) to `large_image.png` in the current directory
2. Create the file in the minimum example code section.
3. Run the file, and click the "Load 100 images" button 2-3 times
4. Watch the memory usage grow between each press

### Minimum example code

```shell
import asyncio
import os
import urllib.request
import psutil
import toga
import gc
from toga.constants import COLUMN
from toga.style import Pack

class WebMemoryLeakMonitor(toga.App):

def startup(self):
self.process = psutil.Process(os.getpid())
self.main_window = toga.MainWindow()
main_box = toga.Box(style=Pack(direction=COLUMN, padding=15))
self.memory_label = toga.Label(
f"Current RAM Usage: {self.get_memory_string()}",
style=Pack(padding_bottom=15)
)
self.load_button = toga.Button(
"Load 100 Images",
on_press=self.load_images_batch,
style=Pack(padding_bottom=10)
)
self.image_view = toga.ImageView(flex=1)
main_box.add(self.memory_label)
main_box.add(self.load_button)
main_box.add(self.image_view)
self.main_window.content = main_box
self.main_window.show()

def get_memory_string(self):
mem_bytes = self.process.memory_info().rss
return f"{mem_bytes / (1024 * 1024):.2f} MB"

def load_images_batch(self, widget):
for _ in range(100):
new_image = toga.Image(src="large_image.png")
self.image_view.image = new_image
for _ in range(10):
gc.collect()
self.memory_label.text = f"Current RAM Usage: {self.get_memory_string()}"

if __name__ == "__main__":
app = WebMemoryLeakMonitor("Web Leak Monitor", "org.example.web-leak")
app.main_loop()
```

### Screenshots

Eventually this type of thing will happen:

Image

### Environment details

- Ubuntu 25.04
- Python version: 3.13.3
- Software versions:
- Toga: https://github.com/beeware/toga/commit/d3425374f029a174083464d8f448af299ed935a3
- GTK 3.24.49

### Logs

_No response_

### Additional context

- Same reproducer runs but bug does not occur on macOS.

- The issue may be caused by the use of Gio.MemoryInputStream.new_from_data: https://stackoverflow.com/questions/45838863/gio-memoryinputstream-does-not-free-memory-when-closed

https://github.com/beeware/toga/blob/d3425374f029a174083464d8f448af299ed935a3/gtk/src/toga_gtk/images.py#L15C32-L15C67

Switching to new_from_bytes, as suggested by the StackOverflow answer, may fix this issue.

- AI disclosure: Found this bug with the assistance of Gemini by asking it to find bugs in a GUI codebase and pasting in random segments of Toga it asks for. i can confirm manually that this is a legitmate issue.

Contributor guide

Open the contributing guide

Research direction

Start by reading gtk/src/toga_gtk/images.py at the linked MemoryInputStream call, then run the supplied GTK reproducer with a large_image.png and monitor memory across repeated batches. Investigate the suggested new_from_bytes alternative; done means repeated image loading no longer shows the reported memory growth while GTK image display still works.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.