playlist update code leaks memory

Bug #435338 reported by reacocard
16
This bug affects 3 people
Affects Status Importance Assigned to Milestone
Exaile
Fix Released
Critical
reacocard

Bug Description

every time xlgui.playlist.Playlist.refresh_changed_tracks is called, we leak some memory proportional to the number of tracks in the playlist. This is a severe problem because that method gets called after every track transition when the __playtime and __last_played tags get updated. On playlists containing several thousand tracks (eg. the Entire Library smart playlist) this can amount to leaks of several MB per track transition!

This affects both 0.3.0.1 and trunk.

Revision history for this message
reacocard (reacocard) wrote :

trunk/2563 and 0.3.0.x/2443 should alleviate this slightly (and speed up redraws), but the underlying leak appears to happen inside gtk's queue_draw itself, not sure how much we can do about that.

reacocard (reacocard)
Changed in exaile:
milestone: 0.3.0.2 → 0.3.0.3
Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

still doing it on version from bzr today.

It's not just next track; any redraw of the playlist is doing it I think - just scrolling my 'Entire playlist' window
leaks ~280kbyte.

Dave

Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

I tried taking the basictreeview.py in the pygtk examples and seeing if it leaks; it doesn't seem to.

That's with a 4000 entry ListStore with 5 columns of text each; it seems to be constant against scrolling and calling queue_draw.

Dave

Revision history for this message
d53richar (d53richar) wrote :

I did some digging about for this problem, there is also a semi-related problem in that you cannot turn off the ensure_visible attribute in settings.

THE PROBLEM
In xlgui playlist.py the functions
icon_data_func
stop_icon_data_func
set_cell_weight
get called for each cell whenever the playlist is scrolled or the mouse moves over the playlist. (why it does this I don't know, it was hard enough to find the why of the problem) with a playlist containing 1000 tracks and 5 columns then they each get called 5000 times.

THE SOLUTION

In stop_icon_data_func move the line window = gtk.Window() into the IF BLOCK i.e

if item == self.queue.stop_track:
window = gtk.Window()
image = window.render_icon('gtk-stop',
gtk.ICON_SIZE_MENU)
image = image.scale_simple(12, 12, gtk.gdk.INTERP_BILINEAR)

This solves the problem, however I feel that the original problem is the fact that these three routines get called to often.

THE PROBLEM #2
cannot turn off the ensure_visible attribute in settings (this stops the playlist from automatically scrolling when the track changes)
THE SOLUTION
in xlgui main.py in the function on_playback_start comment out or remove the line
gobject.idle_add(pl.list.set_cursor, path)

Right now this is fixed I can get back to modifying the multialarmclock plugin to be multiplaylist that is, automatically change the playlist at specified times on specified days so that it becomes an automated radio station.

Regards
David

Revision history for this message
reacocard (reacocard) wrote :

Excellent research on this David! I'm somewhat busy at present but I will take a look at this when I get a chance.

Changed in exaile:
assignee: nobody → Aren Olson (reacocard)
Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

You're right that it seems to be the window = gtk.Window();
In a separate little test script I did:

        for dummy in range(10000):
          window = gtk.Window()

and it eat a few MB. Shouldn't they get garbage collected?

Another problem is that this icon never seems to be shown; there's a typo in main.py which this seems to fix:

=== modified file 'xlgui/main.py'
--- xlgui/main.py 2009-12-13 16:00:23 +0000
+++ xlgui/main.py 2009-12-20 16:45:34 +0000
@@ -683,7 +683,7 @@
         if tr == self.queue.stop_track:
             self.queue.stop_track = None
         else:
- self.queue.stop_track = track
+ self.queue.stop_track = tr

         self.get_selected_playlist().list.queue_draw()

So I think the simple fix for the leak is to make stop_icon_data_func more like icon_data_func:

=== modified file 'xlgui/playlist.py'
--- xlgui/playlist.py 2009-12-10 05:06:07 +0000
+++ xlgui/playlist.py 2009-12-20 16:42:56 +0000
@@ -587,6 +587,10 @@
             gtk.ICON_SIZE_SMALL_TOOLBAR)
         self.pauseimg = img.scale_simple(18, 18,
             gtk.gdk.INTERP_BILINEAR)
+ img = window.render_icon('gtk-stop',
+ gtk.ICON_SIZE_MENU)
+ self.stopimg = img.scale_simple(12, 12,
+ gtk.gdk.INTERP_BILINEAR)

     def column_changed(self, *e):
         """
@@ -1040,11 +1044,8 @@
         item = model.get_value(iter, 0)
         image = None

- window = gtk.Window()
         if item == self.queue.stop_track:
- image = window.render_icon('gtk-stop',
- gtk.ICON_SIZE_MENU)
- image = image.scale_simple(12, 12, gtk.gdk.INTERP_BILINEAR)
+ image = self.stopimg

         cell.set_property('pixbuf', image)

But as stated that still leaves the question of why gtk.Window()'s don't garbage collect and as
David says why those functions get called so often.

Dave

Revision history for this message
d53richar (d53richar) wrote :

The fuctions get called because they are callbacks setup in
     def _setup_columns(self)
The treeview widget needs to call these functions (if they are defined) every time it thinks it needs to redraw the widget. A big pain but although there are lots of them there does not seem to much overhead. These callbacks show the playing/pause icon and set the text to bold.

see here for more info
http://scentric.net/tutorial/sec-treeview-col-celldatafunc.html

the actual text is :
You might notice that your cell data function is called at times even for rows that are not visible at the moment. This is because the tree view needs to know its total height, and in order to calculate this it needs to know the height of each and every single row, and it can only know that by having it measured, which is going to be slow when you have a lot of rows with different heights (if your rows all have the same height, there should not be any visible delay though).

I cannot think of an easier way to do this even after looking at the widget specs but then I have come across another problem with exaile which is taking my time up.

Regards
David

Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

You can help this by doing a :

        self.list.set_fixed_height_mode(True)

which is supposed to make it fixed height - but there is a catch; all of the columns have got to be fixed
size, and if we do that it kicks an assertion because one isn't.

I don't get why TreeView needs you to force the columns to be fixed size if you only anticipate changes in width.

Dave

Revision history for this message
reacocard (reacocard) wrote :

i've applied Dave's patch to trunk, hopefully that helps a bit.

Changed in exaile:
milestone: 0.3.0.3 → 0.3.1
status: Confirmed → In Progress
Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

Thanks,
  The guys on #pygtk say that toplevels are special and you always have to destroy them.
But an interesting point is that we just don't need the temporary anyway

i.e. it looks like you can use self.list.render_icon rather than
  window = gtk.Window()
  window.render_icon

that should be a little optimisation in _setup_tree

(Incidentally there seems to be a line 'playern' in xlgui/main.py - I guess it's a typo.)

Dave

Revision history for this message
reacocard (reacocard) wrote :

interesting. I've changed it to use self.list now, appears to work fine. Fixed the typo in main.py too.

For me at least, scrolling the playlist and playing tracks no longer appears to leak memory. Slight spike after each new track, but after playing a bit it settles back. Anyone want to confirm?

Revision history for this message
Dave Gilbert (ubuntu-treblig) wrote :

It certainly seems a lot happier; I still think there is a bit of a leak somewhere.

After about 2 hours of playing (mostly 2-3 min tracks - so I guess about 100-120 tracks?),
it's gone up from 644MB virt/83MB res at the start to 744MB virt/90MB res now.

There's an initial ramp from 644->668 in the first few tracks played, and then it's a very
slow creep. It's certainly no longer causing a problem for me (especially since the resident part
is staying low, unlike the previous leak where for some reason it ended up having to be resident).

Dave

Revision history for this message
reacocard (reacocard) wrote :

excellent. its clear there's still some leakage, but at least the topic of this bug, the playlist update code, appears to be mostly, if not completely, solved, so I'm going to mark this as fixed. Many thanks to everyone who helped track down and solve this bug!

Changed in exaile:
status: In Progress → Fix Committed
reacocard (reacocard)
Changed in exaile:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.