Marwan Tanager
2013-03-24 05:28:03 UTC
This patch tries to solve a serious problem with zathura leaking a horrendous
amount of memory during scrolling through a document.
The problem is that the current way of periodically reclaiming the cairo
surfaces of the pages widgets is not working at all. The only case in which it
seems to work is the first time (and "only" the first time) the timeout
function purge_pages is triggered, and even this first time sometimes doesn't
free the memory if it happened while scrolling through the document.
To see it in action, try opening a large document (e.g. 500+ pages), and
scroll through it by dragging the scrollbar or continuously pressing <shift-j>,
or <shift-k>. Meanwhile, watch the output of 'top $(pidof zathura)' until you
end up with a throttled system riddled with continuous swapping. Zathura will
fiercely eat all the physical system memory while scrolling through a
relatively big document.
I've spent a while trying to figure out why the memory isn't freed after the
first time the timeout function is triggered, but quite frankly, I couldn't
manage to get to any reasonable conclusion. Injecting debug messages in
zathura_page_widget_update_surface shows that all the pages, but the current
visible one, are eventually reclaimed. This means that cairo_surface_destroy
must have been called on every surface of a page that exceeded it's end-of-life
threshold, but without actually doing it's job of freeing the surface memory.
So, this patch bypasses this mechanism by freeing a surface as soon as it's
associated page becomes invisible. Try repeating the same experiment as above
with this patch, and watch the rate of memory consumption drops dramatically. I
scrolled the entire length of a 1300-pages document and the end result was 500
MB. And I don't think this is leaking memory due to cairo surfaces pixel
buffers, because it never increases after reaching this limit. Maybe it's some
internal allocations done by GTK or GDK. This doesn't seem very far-fetched for
an application with 1300+ GTK widgets in this case.
While browsing the bug tracker for related issues, I found the one at
http://bugs.pwmt.org/issue95 which points out the 0.0.8.5 version as not
suffering from this problem. I built it and experimented. The result was that
the total amount of memory consumed by scrolling through the length of the
1300-pages document was comparable to the one with this patch. So, there must
be something messy happening with cairo_surface_destroy when it's get called
from the timeout function while scrolling. Maybe it's non-reentrant and some
mess happens when it's called concurrently from both the timeout function and
the render thread. But if that's the case, then why doesn't it free the memory
after the first time, even when there is no scrolling? Also, I tried to
experiment with mutexes around the cairo_surface_destroy calls on the render
thread and zathura_page_widget_update_surface but nothing surprising has
happened.
So, what's your suggestions, ideas (or maybe patches :-))?
And what's the final destiny of the periodic page reclaiming code?
Marwan Tanager (1):
Fix a horrible memory leak.
callbacks.c | 3 +++
page-widget.c | 9 +++++++++
page-widget.h | 8 ++++++++
3 files changed, 20 insertions(+)
amount of memory during scrolling through a document.
The problem is that the current way of periodically reclaiming the cairo
surfaces of the pages widgets is not working at all. The only case in which it
seems to work is the first time (and "only" the first time) the timeout
function purge_pages is triggered, and even this first time sometimes doesn't
free the memory if it happened while scrolling through the document.
To see it in action, try opening a large document (e.g. 500+ pages), and
scroll through it by dragging the scrollbar or continuously pressing <shift-j>,
or <shift-k>. Meanwhile, watch the output of 'top $(pidof zathura)' until you
end up with a throttled system riddled with continuous swapping. Zathura will
fiercely eat all the physical system memory while scrolling through a
relatively big document.
I've spent a while trying to figure out why the memory isn't freed after the
first time the timeout function is triggered, but quite frankly, I couldn't
manage to get to any reasonable conclusion. Injecting debug messages in
zathura_page_widget_update_surface shows that all the pages, but the current
visible one, are eventually reclaimed. This means that cairo_surface_destroy
must have been called on every surface of a page that exceeded it's end-of-life
threshold, but without actually doing it's job of freeing the surface memory.
So, this patch bypasses this mechanism by freeing a surface as soon as it's
associated page becomes invisible. Try repeating the same experiment as above
with this patch, and watch the rate of memory consumption drops dramatically. I
scrolled the entire length of a 1300-pages document and the end result was 500
MB. And I don't think this is leaking memory due to cairo surfaces pixel
buffers, because it never increases after reaching this limit. Maybe it's some
internal allocations done by GTK or GDK. This doesn't seem very far-fetched for
an application with 1300+ GTK widgets in this case.
While browsing the bug tracker for related issues, I found the one at
http://bugs.pwmt.org/issue95 which points out the 0.0.8.5 version as not
suffering from this problem. I built it and experimented. The result was that
the total amount of memory consumed by scrolling through the length of the
1300-pages document was comparable to the one with this patch. So, there must
be something messy happening with cairo_surface_destroy when it's get called
from the timeout function while scrolling. Maybe it's non-reentrant and some
mess happens when it's called concurrently from both the timeout function and
the render thread. But if that's the case, then why doesn't it free the memory
after the first time, even when there is no scrolling? Also, I tried to
experiment with mutexes around the cairo_surface_destroy calls on the render
thread and zathura_page_widget_update_surface but nothing surprising has
happened.
So, what's your suggestions, ideas (or maybe patches :-))?
And what's the final destiny of the periodic page reclaiming code?
Marwan Tanager (1):
Fix a horrible memory leak.
callbacks.c | 3 +++
page-widget.c | 9 +++++++++
page-widget.h | 8 ++++++++
3 files changed, 20 insertions(+)
--
1.7.10.4
1.7.10.4