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 when destroying a surface #225

Open
Cruor opened this issue Apr 13, 2018 · 10 comments
Open

Memory leak when destroying a surface #225

Cruor opened this issue Apr 13, 2018 · 10 comments

Comments

@Cruor
Copy link

Cruor commented Apr 13, 2018

destroy() on surface (CairoARGBSurface, to be exact) doesn't seem to get removed properly.
This is tested on windows running

julia> Cairo.VERSION
v"0.6.0"
julia> Pkg.installed()["Cairo"]
v"0.5.1"

and Linux with Cairo.VERSION v"0.6.2" and v"0.6.0".

using Cairo

l = [CairoARGBSurface(512, 512) for i in 1:20000]
while length(l) > 0
    destroy(pop!(l))
end

# Just to be sure
gc()
Cairo.GC.gc()

This is far worse on Linux where this uses 2.7GB of RAM after running the snippet, but only seems to add 50MB on windows. Either way it just keeps leaking on both of them (when making new surfaces later on down the road).

Is there any obvious way to fix this? The reason this happens in my code is because I need to reset or resize surfaces. Remaking the surface has been the least painful way to get this done for now, but leads to this problem. From what I can see in the source code the resize function for surfaces is also commented out, so I'm guessing this problem has been encountered for sane usages as well?

@lobingera
Copy link
Contributor

How do you measure the memory usage (leak) and why are you sure, that's the surfaces?

@Cruor
Copy link
Author

Cruor commented Apr 13, 2018

Measured with Task Manager on Windows and htop on Linux. The surfaces are the only objects being created, and the actual code would have bigger issues if length() or pop! was leaking.

@lobingera
Copy link
Contributor

A 512x512 ARGB surface should be around 1MB, so the 50M on windows shows already a reduction from your 20000 instances and the 2.7GB in linux might be the full set of allocations. I'm currently away from my development box, so I cannot test if this is reproducible but some people reported similar issues when both a destroy call and a finalizer call is defined. Overall libcairo has its own memory management based on reference counting and based on system calls and in linux it can happen that 'freed' memory shows up as allocated until new allocation. But I'm no expert here.

btw: could you explain a little bit what your code tries to do? in libcairo surfaces are more output devices than intermediate data and resizing is actually not a defined operation.

@Cruor
Copy link
Author

Cruor commented Apr 14, 2018

I am using multiple Cairo surfaces (as layers) and drawing the combination of them onto a GTK Surface. Many of the operations requires me to actually redraw the whole canvas, instead of just doing small redraws the last drawing operation.

The GTK Surface is essentially just a "viewpoint" of the layers, but the size of the complete render might need to change sizes due to user input. I'm not familiar with Cairo, and could not seem to find any methods that reset the surface back to the "newly instantiated" state. The reason I haven't looked to much into actually reseting the surface is because the program would leak memory either way, if the user was too request a differently sized complete render.

Some actions of the program therefor produce a huge amount of canvases (that are being destroyed from my code in the same way as above) for repainting, and while this is almost fine on Windows (memory usage slowly goes up, resets, goes up higher than before, so its still "leaking") it is exponentially worse on Linux and never seems to be freed, leading to all memory being consumed after a few minutes.

@Vexatos
Copy link

Vexatos commented Apr 14, 2018

For what it's worth, I can easily reproduce this issue on my linux machine using the provided sample code.

@Cruor
Copy link
Author

Cruor commented Apr 14, 2018

Testing repeated mass creation and deletion of surfaces on windows with the information in the original comment seems to actually not be leaking, and is freed properly.

using Cairo

while true 
    l = [CairoARGBSurface(512, 512) for i in 1:10000]
    destroy.(l)
    
    gc()
    Cairo.GC.gc()
end

However it doesn't look like its freed at all on Linux.

Maybe the issue is Linux / Cairo version specific?

@lobingera
Copy link
Contributor

As written above, I cannot test code right now and I would doubt, that libcairo has some inbuilt memory problems on linux systems. And the the same thing could be tested in C.
Cairo is only a thin layer of Julia ccalls to libcairo and in this case there should be clearly a sequence of cairo_surface_create and cairo_surface_destroy calls. If you know how to do, you could run Julia in a gdb session, set breakpoints to libcairo and see, if this sequence happens like this.

For handling layering in Cairo, some time ago I wrote an interactive tool with pygtk/pycairo and I used also image surfaces as intermediate drawing area, I just allocated them big enough (screen size) and did reset by just filling the surface with color 0,0,0,0 = RGBA. I don't know the steps to clear vector surfaces. maybe they have to be destroyed/recreated. A lot of drawing operations - especially in updates can be handled by proper clipping, this is one of the USPs of Cairo.

@Cruor
Copy link
Author

Cruor commented Apr 14, 2018

Filling the layers with (0, 0, 0, 0) (with the correct operators set) seems to be working fine.

The problem is that the surface can literally be any size, and not limited by window/screen size. A workaround I could do is cache NxN surfaces and double them in size when needed, but that seems like a dirty hack to work around the problem.

Thanks for the help though, it's way less violent on Linux now. I'm not familiar with the GDB stuff you mention though. I'll keep the issue open so somebody that knows what they are doing can actually debug and fix the problem.

@lobingera
Copy link
Contributor

I spend yesterday some minutes on that, could reproduce a bigger memory impact on linux (but only on VIRT) and could see the destroy calls on gdb. I'll look into this again, but i looks like an observer side problem -> the memory isn't complete freed, but is not allocated in the sense you cannot use it.

@lobingera
Copy link
Contributor

Short update. I tested this today and can reproduce it on 0.6.4 but not on 1.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants