OSDN Git Service

fix leak test, idletask/strokemap code refactoring
authorMartin Renold <martinxyz@gmx.ch>
Sun, 25 Oct 2009 12:09:07 +0000 (13:09 +0100)
committerMartin Renold <martinxyz@gmx.ch>
Sun, 25 Oct 2009 12:19:53 +0000 (13:19 +0100)
The idle handler was leaking memory, because it added a callback closure
to the gtk idle handler that was never called during the test. That
closure held a reference to the surface, which in turn was never freed.

No impact when running with GUI, but it's fixed now anyway.

lib/idletask.py [new file with mode: 0644]
lib/strokemap.py
tests/test_mypaintlib.py
tests/test_performance.py

diff --git a/lib/idletask.py b/lib/idletask.py
new file mode 100644 (file)
index 0000000..688c98d
--- /dev/null
@@ -0,0 +1,47 @@
+# This file is part of MyPaint.
+# Copyright (C) 2009 by Martin Renold <martinxyz@gmx.ch>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+import gobject
+
+class Processor:
+    """
+    A queue of low priority tasks that are automatically processed
+    when gtk is idle, or on demand.
+    """
+    def __init__(self, max_pending):
+        self._queue = []
+        self.max_pending = max_pending
+
+    def add_work(self, func, weight=1.0):
+        if not self._queue:
+            gobject.idle_add(self._idle_cb)
+        func.__weight = weight
+        self._queue.append(func)
+        self.finish_downto(self.max_pending)
+
+    def finish_one(self):
+        print '.'
+        func = self._queue.pop(0)
+        func()
+
+    def finish_downto(self, max_pending):
+        while 1:
+            pending_weight = sum([func.__weight for func in self._queue])
+            if pending_weight <= max_pending:
+                return
+            self.finish_one()
+
+    def finish_all(self):
+        self.finish_downto(0)
+
+    def _idle_cb(self):
+        if not self._queue:
+            return False
+        self.finish_one()
+        return True
+
index 8035436..f0f6714 100644 (file)
@@ -7,19 +7,20 @@
 # (at your option) any later version.
 
 import time
-import gobject, zlib
+import zlib
 from numpy import *
 
-import tiledsurface, strokemap_pb2
+import tiledsurface, idletask, strokemap_pb2
 N = tiledsurface.N
 
+tasks = idletask.Processor(max_pending=6)
+
 class StrokeInfo:
     """
     This class stores permanent (saved with image) information about a
     single stroke. Mainly this is the stroke shape and the brush
     settings that were used. Needed to pick brush from canvas.
     """
-    processing_queue = [] # global (static) list
     def __init__(self):
         self.strokemap = {}
         self.brush = None
@@ -63,14 +64,7 @@ class StrokeInfo:
                 data_compressed = zlib.compress(data.tostring())
                 self.strokemap[tx, ty] = data_compressed
 
-            queue.append(work)
-        self.processing_queue.append(queue)
-
-        gobject.idle_add(self.idle_cb)
-
-        # make sure we never lag too much behind with processing
-        # (otherwise we waste way too much memory)
-        self.process_pending_strokes(max_pending_strokes=6)
+            tasks.add_work(work, weight=1.0/len(tiles_modified))
 
     def init_from_pb(self, stroke_pb, translate_x, translate_y):
         assert not self.strokemap
@@ -87,33 +81,15 @@ class StrokeInfo:
         assert translate_y % N == 0
         translate_x /= N
         translate_y /= N
-        self.process_pending_strokes()
+        tasks.finish_all()
         for (tx, ty), data in self.strokemap.iteritems():
             t = stroke_pb.tiles.add()
             t.tx, t.ty = tx + translate_x, ty + translate_y
             t.data_compressed = data
         stroke_pb.brush_string_compressed = zlib.compress(self.brush_string)
 
-    def process_one_item(self):
-        items = self.processing_queue[0]
-        if items:
-            func = items.pop(0)
-            func()
-        else:
-            self.processing_queue.pop(0)
-        
-    def process_pending_strokes(self, max_pending_strokes=0):
-        while len(self.processing_queue) > max_pending_strokes:
-            self.process_one_item()
-
-    def idle_cb(self):
-        if not self.processing_queue:
-            return False
-        self.process_one_item()
-        return True
-
     def touches_pixel(self, x, y):
-        self.process_pending_strokes()
+        tasks.finish_all()
         data = self.strokemap.get((x/N, y/N))
         if data:
             data = fromstring(zlib.decompress(data), dtype='uint8')
@@ -121,7 +97,7 @@ class StrokeInfo:
             return data[y%N, x%N]
 
     def render_overlay(self, surf):
-        self.process_pending_strokes()
+        tasks.finish_all()
         for (tx, ty), data in self.strokemap.iteritems():
             data = fromstring(zlib.decompress(data), dtype='uint8')
             data.shape = (N, N)
@@ -131,3 +107,5 @@ class StrokeInfo:
             rgba[:,:,0] = rgba[:,:,3]/2
             rgba[:,:,1] = rgba[:,:,3]/2
             rgba[:,:,2] = rgba[:,:,3]/2
+
+
index b5fe402..49d92b0 100755 (executable)
@@ -213,28 +213,22 @@ def leakTest_slow():
             doc.stroke_to(dtime, x, y, pressure)
 
     #gc.set_debug(gc.DEBUG_LEAK)
-    paint()
-    doc.clear()
-    m0 = mem()
 
-    paint()
-    assert mem() >= m0
-    doc.clear()
-    m1 = mem()
-
-    for i in range(100):
+    m = []
+    for i in range(20):
         paint()
         doc.clear()
         m2 = mem()
+        m.append(m2)
         print 'iteration %02d/100: %d pages used' % (i, m2)
 
-    assert m1 == m2, 'memory leak during paint/clear cycles'
-
-    paint()
-    doc.save('test_memory_leak.png', alpha=False)
-    #doc.clear()
+    for i in range(10,20):
+        assert m[i] == m[9], 'memory leak during paint/clear cycles'
 
-    m3 = mem()
+    #import objgraph
+    #from lib import strokemap
+    #objgraph.show_refs(doc)
+    #sys.exit(0)
 
     #assert m2 == m0, (m2-m0, m3-m2)
 
index f201c87..bc2ada2 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-import sys, os, tempfile, subprocess
+import sys, os, tempfile, subprocess, gc
 from time import time, sleep
 
 from pylab import *