[wxPython] wxPython is leaking memory, how can I find out what and fix it?

I am writing a wxPython application that needs to be long-running.
Actually, continuously running: it is a data monitor for an electric
power trading system.

Now, I have found out that the application leaks memory, and grows
from an 8MB address space to a 100 MB space within 3 hours.

Monitoring it with windows' task manager, it appears that the number
of gdi objects grows continuously.

My application does not accumulate data: it builds its UI (a fairly
elaborate one)
and then one thread acquires data from a socket connection, creates
wxEvents,
passes them to the wxPyton UI, which updates text and graphics in widgets.
So
the only allocation is in the wxPens, wxFonts, wxBrushes that are created to
update the widgets. These are in local variable of the Draw methods of the
widgets',
and I would assume that they fall out of scope when the Draw methods
terminate,
and are the collected by the Python interpereter. If the underlying GDI
objects
are not reclaimed when the wxPython objects are collected, then accumulation
of GDI objects I observe could occur.

Is there any way to determine where the memory is leaking, or is there some
secret protocol for drawing in widgets that I am not observing? If someone
can
provide some clue leading me to a solution, I would be very appreciative.

Following are a couple of representative code extracts to show what
kinds of things my widgets do.

- Parzival

···

----------------------------
class Clock(wxControl,Widget):
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (40,40),
                    style = wxNO_BORDER, name=None):
        if not name: name = str(ID)
        wxControl.__init__(self, parent, ID, pos=pos, size=size,
style=style, name=name)
        if colours:
            self.SetColours(colours)
        else:
            self.SetColours(("grey","black","medium grey","medium red"))

        self.SetValue(time.time(),())
        EVT_SIZE(self, self.OnSize)
        EVT_PAINT(self, self.OnPaint)

    def __OnPaint(self, event):
        ''' Double bufferred paint '''
        cw, ch = self.GetClientSizeTuple()
        pdc = wxPaintDC(self)
        ddc = wxMemoryDC()
        ddc.SelectObject(wxEmptyBitmap(cw, ch))
        ddc.SetBackground(pdc.GetBackground())
        ddc.Clear()
        self.Draw(ddc)
        pdc.BeginDrawing()
        pdc.Blit(0, 0, cw, ch, ddc, 0, 0)
        pdc.EndDrawing()

    def OnPaint(self, event):
        self.Draw(wxPaintDC(self))

    def OnSize(self,event):
        pass

    def SetValue(self, value, args):
        self.timeTuple = time.localtime(int(value + 0.5))
        self.Refresh()

    def Draw(self,dc):
        cw, ch = self.GetClientSizeTuple()
        w = min(cw,ch)
        h = w
        dc.BeginDrawing()

        colr = self.GetColour(0)
        dc.SetPen(wxPen(colr))
        bw = w/20
        if bw > 1:
            dc.SetBrush(wxBrush(colr))
            dc.DrawRoundedRectangle(0, 0, w, h, max(1,w/5))
            w -= bw
            h -= bw

        facecolr = self.GetDefinedColour(4)
        if facecolr:
            dc.SetBrush(wxBrush(facecolr))
        else:
            dc.SetBrush(dc.GetBackground())

        dc.DrawRoundedRectangle(bw/3, bw/3, w, h, max(1,w/5))

        r1 = w/2 - 1
        r2 = r1 - max(1, r1/6)
        x = ch*0.5
        y = cw*0.5

        for hour in range(0,12):
            self.__DrawHand(dc,hour*5.0,self.GetColour(0),1,r1,r1-bw,x,y)

        hour, minute, second = self.timeTuple[3:6]

        rsec = r1
        rmin = r2
        rhour = r2 - (r1 - r2)

self.__DrawHand(dc,hour*5.0+minute/12.0,self.GetColour(1),3,rhour,0,x,y)
        self.__DrawHand(dc,minute,self.GetColour(2),2,rmin,-rmin/16,x,y)
        self.__DrawHand(dc,second,self.GetColour(3),1,rsec,-rsec/8,x,y)

        dc.EndDrawing()

    def __DrawHand(self,dc,minute,colour,width,r1,r2,x,y):
        fpen = wxPen(colour)
        fpen.SetWidth(width)
        dc.SetPen(fpen)
        a = minute*3.141592654/30.0
        cosa = math.cos(a)
        sina = math.sin(a)
        x1 = int(x + r1*sina + 0.5)
        x2 = int(x + r2*sina + 0.5)
        y1 = int(y - r1*cosa + 0.5)
        y2 = int(y - r2*cosa + 0.5)
        dc.DrawLine(x1,y1, x2,y2)

#---------------------------------------------------------------------------
----
class Gauge(_GaugeBase):
    ''' A horizontal gauge with an origin. '''
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (100,10),
                    style = wxSUNKEN_BORDER, name=None):
        _GaugeBase.__init__(self, parent, ID, colours, pos, size, style,
name)

    def SetBounds(self,lo,hi,mid=None):
        if lo >= hi:
            raise ValueError, ("invalid range",lo,hi)
        if mid == None:
            if hi <= 0:
                mid = hi
            elif lo >= 0:
                mid = lo
            else:
                mid = 0
        elif mid < lo or mid > hi:
            raise ValueError, ("mid out of range", lo, hi, mid)

        self.lo = lo
        self.range = float(hi-lo)

        self.rMid = (mid-lo)/self.range
        self.rValue = self.rMid

        self.txtlo = sstr(lo)
        self.txthi = sstr(hi)
        self.txtmid = sstr(mid)
        self.Refresh()

    def SetValue(self, value, args):
        self.rValue = (value-self.lo)/self.range
        self.Refresh()

    def Draw(self,dc):
        w, h = self.GetClientSizeTuple()
        dc.BeginDrawing()
        dc.SetFont(self.GetFont())
        if self.dolabels:
            ch = dc.GetCharHeight()
            hbar = max(4, h - ch)
            twlo,th = dc.GetTextExtent(self.txtlo)
            twhi,th = dc.GetTextExtent(self.txthi)
            xhitext = max(0, w-twhi)
            ytext = hbar
        else:
            hbar = h
        #dc.Clear()
        rValue = max(0.0, min(1.0,self.rValue))
        wr = int(w*rValue)
        wz = int(w*self.rMid)
        colour = self.GetColour(rValue < self.rMid)
        if rValue != self.rValue:
            br = wxBrush(colour,wxBDIAGONAL_HATCH)
        else:
            br = wxBrush(colour)
        dc.SetBrush(br)
        rr = max(2,min(20,int(hbar/4.0+0.5)))
        if rValue < self.rMid:
            #dc.DrawRectangle(wr,0, wz-wr, hbar)
            dc.DrawRoundedRectangle(wr,0, wz-wr, hbar, rr)
        else:
            #dc.DrawRectangle(wz,0, wr-wz, hbar)
            dc.DrawRoundedRectangle(wz,0, wr-wz, hbar, rr)
        if self.dolabels:
            twzr,th = dc.GetTextExtent(self.txtmid)
            if twlo < wz-th and wz+th < xhitext:
                dc.DrawText(self.txtmid, int(w*self.rMid - twzr*0.5), ytext)

            dc.DrawText(self.txthi, xhitext, ytext)
            if xhitext > twlo + ch:
                dc.DrawText(self.txtlo, 0, ytext)
        dc.EndDrawing()

#---------------------------------------------------------------------------
----
class Gauge(_GaugeBase):
    ''' A horizontal gauge with an origin. '''
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (100,10),
                    style = wxSUNKEN_BORDER, name=None):
        _GaugeBase.__init__(self, parent, ID, colours, pos, size, style,
name)

    def SetBounds(self,lo,hi,mid=None):
        if lo >= hi:
            raise ValueError, ("invalid range",lo,hi)
        if mid == None:
            if hi <= 0:
                mid = hi
            elif lo >= 0:
                mid = lo
            else:
                mid = 0
        elif mid < lo or mid > hi:
            raise ValueError, ("mid out of range", lo, hi, mid)

        self.lo = lo
        self.range = float(hi-lo)

        self.rMid = (mid-lo)/self.range
        self.rValue = self.rMid

        self.txtlo = sstr(lo)
        self.txthi = sstr(hi)
        self.txtmid = sstr(mid)
        self.Refresh()

    def SetValue(self, value, args):
        self.rValue = (value-self.lo)/self.range
        self.Refresh()

    def Draw(self,dc):
        w, h = self.GetClientSizeTuple()
        dc.BeginDrawing()
        dc.SetFont(self.GetFont())
        if self.dolabels:
            ch = dc.GetCharHeight()
            hbar = max(4, h - ch)
            twlo,th = dc.GetTextExtent(self.txtlo)
            twhi,th = dc.GetTextExtent(self.txthi)
            xhitext = max(0, w-twhi)
            ytext = hbar
        else:
            hbar = h
        #dc.Clear()
        rValue = max(0.0, min(1.0,self.rValue))
        wr = int(w*rValue)
        wz = int(w*self.rMid)
        colour = self.GetColour(rValue < self.rMid)
        if rValue != self.rValue:
            br = wxBrush(colour,wxBDIAGONAL_HATCH)
        else:
            br = wxBrush(colour)
        dc.SetBrush(br)
        rr = max(2,min(20,int(hbar/4.0+0.5)))
        if rValue < self.rMid:
            #dc.DrawRectangle(wr,0, wz-wr, hbar)
            dc.DrawRoundedRectangle(wr,0, wz-wr, hbar, rr)
        else:
            #dc.DrawRectangle(wz,0, wr-wz, hbar)
            dc.DrawRoundedRectangle(wz,0, wr-wz, hbar, rr)
        if self.dolabels:
            twzr,th = dc.GetTextExtent(self.txtmid)
            if twlo < wz-th and wz+th < xhitext:
                dc.DrawText(self.txtmid, int(w*self.rMid - twzr*0.5), ytext)

            dc.DrawText(self.txthi, xhitext, ytext)
            if xhitext > twlo + ch:
                dc.DrawText(self.txtlo, 0, ytext)
        dc.EndDrawing()

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

Just a momentary thought... but from the documentation for wxMemoryDC:Note
that the memory DC must be deleted (or the bitmap selected out of it) before
a bitmap can be reselected into another memory DC.

Note the bit in brackets... perhaps when the Python wrapper goes out of
scope, the underlying C++ object is not being deleted... there may well be
some other reason, of course, but have you tried what's suggested there,
namely selecting the bitmap out after blitting it from the MemoryDC to the
PaintDC?

Good luck, Chris

···

-------------------------------------------------------------------------
Chris Fama <mailto:Chris.Fama@whollysnakes.com> Phone:(07) 3870 5639
Brisbane, Australia Mobile:(0400) 833 700
-------------------------------------------------------------------------
"Ask, and it will be given to you; seek, and you will find; knock, and
it will be opened to you. For everyone who asks receives, and he who
seeks finds, and to him who knocks it will be opened. - Matthew 7:7-8

-----Original Message-----
From: wxpython-users-admin@lists.sourceforge.net
[mailto:wxpython-users-admin@lists.sourceforge.net]On Behalf Of Parzival
Herzog
Sent: Friday, 2 February 2001 2:36 PM
To: wxPython user list
Subject: [wxPython] wxPython is leaking memory, how can I find out what
and fix it?

I am writing a wxPython application that needs to be long-running.
Actually, continuously running: it is a data monitor for an electric
power trading system.

Now, I have found out that the application leaks memory, and grows
from an 8MB address space to a 100 MB space within 3 hours.

Monitoring it with windows' task manager, it appears that the number
of gdi objects grows continuously.

My application does not accumulate data: it builds its UI (a fairly
elaborate one)
and then one thread acquires data from a socket connection, creates
wxEvents,
passes them to the wxPyton UI, which updates text and graphics in widgets.
So
the only allocation is in the wxPens, wxFonts, wxBrushes that are
created to
update the widgets. These are in local variable of the Draw methods of the
widgets',
and I would assume that they fall out of scope when the Draw methods
terminate,
and are the collected by the Python interpereter. If the underlying GDI
objects
are not reclaimed when the wxPython objects are collected, then
accumulation
of GDI objects I observe could occur.

Is there any way to determine where the memory is leaking, or is
there some
secret protocol for drawing in widgets that I am not observing? If someone
can
provide some clue leading me to a solution, I would be very appreciative.

Following are a couple of representative code extracts to show what
kinds of things my widgets do.

- Parzival

----------------------------
class Clock(wxControl,Widget):
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (40,40),
                    style = wxNO_BORDER, name=None):
        if not name: name = str(ID)
        wxControl.__init__(self, parent, ID, pos=pos, size=size,
style=style, name=name)
        if colours:
            self.SetColours(colours)
        else:
            self.SetColours(("grey","black","medium grey","medium red"))

        self.SetValue(time.time(),())
        EVT_SIZE(self, self.OnSize)
        EVT_PAINT(self, self.OnPaint)

    def __OnPaint(self, event):
        ''' Double bufferred paint '''
        cw, ch = self.GetClientSizeTuple()
        pdc = wxPaintDC(self)
        ddc = wxMemoryDC()
        ddc.SelectObject(wxEmptyBitmap(cw, ch))
        ddc.SetBackground(pdc.GetBackground())
        ddc.Clear()
        self.Draw(ddc)
        pdc.BeginDrawing()
        pdc.Blit(0, 0, cw, ch, ddc, 0, 0)
        pdc.EndDrawing()

    def OnPaint(self, event):
        self.Draw(wxPaintDC(self))

    def OnSize(self,event):
        pass

    def SetValue(self, value, args):
        self.timeTuple = time.localtime(int(value + 0.5))
        self.Refresh()

    def Draw(self,dc):
        cw, ch = self.GetClientSizeTuple()
        w = min(cw,ch)
        h = w
        dc.BeginDrawing()

        colr = self.GetColour(0)
        dc.SetPen(wxPen(colr))
        bw = w/20
        if bw > 1:
            dc.SetBrush(wxBrush(colr))
            dc.DrawRoundedRectangle(0, 0, w, h, max(1,w/5))
            w -= bw
            h -= bw

        facecolr = self.GetDefinedColour(4)
        if facecolr:
            dc.SetBrush(wxBrush(facecolr))
        else:
            dc.SetBrush(dc.GetBackground())

        dc.DrawRoundedRectangle(bw/3, bw/3, w, h, max(1,w/5))

        r1 = w/2 - 1
        r2 = r1 - max(1, r1/6)
        x = ch*0.5
        y = cw*0.5

        for hour in range(0,12):
            self.__DrawHand(dc,hour*5.0,self.GetColour(0),1,r1,r1-bw,x,y)

        hour, minute, second = self.timeTuple[3:6]

        rsec = r1
        rmin = r2
        rhour = r2 - (r1 - r2)

self.__DrawHand(dc,hour*5.0+minute/12.0,self.GetColour(1),3,rhour,0,x,y)
        self.__DrawHand(dc,minute,self.GetColour(2),2,rmin,-rmin/16,x,y)
        self.__DrawHand(dc,second,self.GetColour(3),1,rsec,-rsec/8,x,y)

        dc.EndDrawing()

    def __DrawHand(self,dc,minute,colour,width,r1,r2,x,y):
        fpen = wxPen(colour)
        fpen.SetWidth(width)
        dc.SetPen(fpen)
        a = minute*3.141592654/30.0
        cosa = math.cos(a)
        sina = math.sin(a)
        x1 = int(x + r1*sina + 0.5)
        x2 = int(x + r2*sina + 0.5)
        y1 = int(y - r1*cosa + 0.5)
        y2 = int(y - r2*cosa + 0.5)
        dc.DrawLine(x1,y1, x2,y2)

#-----------------------------------------------------------------
----------
----
class Gauge(_GaugeBase):
    ''' A horizontal gauge with an origin. '''
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (100,10),
                    style = wxSUNKEN_BORDER, name=None):
        _GaugeBase.__init__(self, parent, ID, colours, pos, size, style,
name)

    def SetBounds(self,lo,hi,mid=None):
        if lo >= hi:
            raise ValueError, ("invalid range",lo,hi)
        if mid == None:
            if hi <= 0:
                mid = hi
            elif lo >= 0:
                mid = lo
            else:
                mid = 0
        elif mid < lo or mid > hi:
            raise ValueError, ("mid out of range", lo, hi, mid)

        self.lo = lo
        self.range = float(hi-lo)

        self.rMid = (mid-lo)/self.range
        self.rValue = self.rMid

        self.txtlo = sstr(lo)
        self.txthi = sstr(hi)
        self.txtmid = sstr(mid)
        self.Refresh()

    def SetValue(self, value, args):
        self.rValue = (value-self.lo)/self.range
        self.Refresh()

    def Draw(self,dc):
        w, h = self.GetClientSizeTuple()
        dc.BeginDrawing()
        dc.SetFont(self.GetFont())
        if self.dolabels:
            ch = dc.GetCharHeight()
            hbar = max(4, h - ch)
            twlo,th = dc.GetTextExtent(self.txtlo)
            twhi,th = dc.GetTextExtent(self.txthi)
            xhitext = max(0, w-twhi)
            ytext = hbar
        else:
            hbar = h
        #dc.Clear()
        rValue = max(0.0, min(1.0,self.rValue))
        wr = int(w*rValue)
        wz = int(w*self.rMid)
        colour = self.GetColour(rValue < self.rMid)
        if rValue != self.rValue:
            br = wxBrush(colour,wxBDIAGONAL_HATCH)
        else:
            br = wxBrush(colour)
        dc.SetBrush(br)
        rr = max(2,min(20,int(hbar/4.0+0.5)))
        if rValue < self.rMid:
            #dc.DrawRectangle(wr,0, wz-wr, hbar)
            dc.DrawRoundedRectangle(wr,0, wz-wr, hbar, rr)
        else:
            #dc.DrawRectangle(wz,0, wr-wz, hbar)
            dc.DrawRoundedRectangle(wz,0, wr-wz, hbar, rr)
        if self.dolabels:
            twzr,th = dc.GetTextExtent(self.txtmid)
            if twlo < wz-th and wz+th < xhitext:
                dc.DrawText(self.txtmid, int(w*self.rMid -
twzr*0.5), ytext)

            dc.DrawText(self.txthi, xhitext, ytext)
            if xhitext > twlo + ch:
                dc.DrawText(self.txtlo, 0, ytext)
        dc.EndDrawing()

#-----------------------------------------------------------------
----------
----
class Gauge(_GaugeBase):
    ''' A horizontal gauge with an origin. '''
    def __init__(self, parent, ID,
                    colours=None,
                    pos = wxDefaultPosition, size = (100,10),
                    style = wxSUNKEN_BORDER, name=None):
        _GaugeBase.__init__(self, parent, ID, colours, pos, size, style,
name)

    def SetBounds(self,lo,hi,mid=None):
        if lo >= hi:
            raise ValueError, ("invalid range",lo,hi)
        if mid == None:
            if hi <= 0:
                mid = hi
            elif lo >= 0:
                mid = lo
            else:
                mid = 0
        elif mid < lo or mid > hi:
            raise ValueError, ("mid out of range", lo, hi, mid)

        self.lo = lo
        self.range = float(hi-lo)

        self.rMid = (mid-lo)/self.range
        self.rValue = self.rMid

        self.txtlo = sstr(lo)
        self.txthi = sstr(hi)
        self.txtmid = sstr(mid)
        self.Refresh()

    def SetValue(self, value, args):
        self.rValue = (value-self.lo)/self.range
        self.Refresh()

    def Draw(self,dc):
        w, h = self.GetClientSizeTuple()
        dc.BeginDrawing()
        dc.SetFont(self.GetFont())
        if self.dolabels:
            ch = dc.GetCharHeight()
            hbar = max(4, h - ch)
            twlo,th = dc.GetTextExtent(self.txtlo)
            twhi,th = dc.GetTextExtent(self.txthi)
            xhitext = max(0, w-twhi)
            ytext = hbar
        else:
            hbar = h
        #dc.Clear()
        rValue = max(0.0, min(1.0,self.rValue))
        wr = int(w*rValue)
        wz = int(w*self.rMid)
        colour = self.GetColour(rValue < self.rMid)
        if rValue != self.rValue:
            br = wxBrush(colour,wxBDIAGONAL_HATCH)
        else:
            br = wxBrush(colour)
        dc.SetBrush(br)
        rr = max(2,min(20,int(hbar/4.0+0.5)))
        if rValue < self.rMid:
            #dc.DrawRectangle(wr,0, wz-wr, hbar)
            dc.DrawRoundedRectangle(wr,0, wz-wr, hbar, rr)
        else:
            #dc.DrawRectangle(wz,0, wr-wz, hbar)
            dc.DrawRoundedRectangle(wz,0, wr-wz, hbar, rr)
        if self.dolabels:
            twzr,th = dc.GetTextExtent(self.txtmid)
            if twlo < wz-th and wz+th < xhitext:
                dc.DrawText(self.txtmid, int(w*self.rMid -
twzr*0.5), ytext)

            dc.DrawText(self.txthi, xhitext, ytext)
            if xhitext > twlo + ch:
                dc.DrawText(self.txtlo, 0, ytext)
        dc.EndDrawing()

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

and fix it?

Just a momentary thought... but from the documentation for wxMemoryDC:Note
that the memory DC must be deleted (or the bitmap selected out of it)

before

a bitmap can be reselected into another memory DC.

Oops: sorry to mislead you, but that piec of code is in "__OnPaint", a
renamed
method that is never called. The actual code being executed is "OnPaint",
below.
I had been saving the double-buffering code for later experiments, as I
found that
it was not effective in reducing flashing as the screen was updated. So that
is
another problem, but it is not where my memory is vanishing to.

Note the bit in brackets... perhaps when the Python wrapper goes out of
scope, the underlying C++ object is not being deleted... there may well be
some other reason, of course, but have you tried what's suggested there,
namely selecting the bitmap out after blitting it from the MemoryDC to the
PaintDC?

Actually I read that too, and I assumed that (a) the wxWindows
documentation
relating to memory management was for C++ programmers of wxWindows,
who must follow protocols and conventions as to "who owns what" in
explicitly managed memory. (b) that since my wxMemoryDC is used once,
and only once before going out of scope and being deleted, that my code
would satisfy the listed requirements.

···

----- Original Message -----
From: "Chris Fama" <Chris.Fama@uq.net.au>
To: <wxpython-users@lists.sourceforge.net>
Sent: Thursday, February 01, 2001 11:38 PM
Subject: RE: [wxPython] wxPython is leaking memory, how can I find out what

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

I ran the wxPython demo. It too leaks memory. The "Threads" test shows this very well,
(on Win 2K). Without apparent bound, it consumes 4-6K per second. But the GDI object
count stays stable (unlike my application).

Any comment. I have an acctual obligation to meet, and I need to know if this is
a problem that can be worked around or fixed, or if I really need to abandon
wxPython for my long-running application.

Thanks,
    Parzival

···

----- Original Message -----
From: "Parzival Herzog" <parz@home.com>
To: "wxPython user list" <wxpython-users@lists.sourceforge.net>
Sent: Thursday, February 01, 2001 10:36 PM
Subject: [wxPython] wxPython is leaking memory, how can I find out what and fix it?

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

If you are serious about this, here's what I recommend:

(1) You need Visual C++ if you're on Windows, if not, you only need GCC.

(2) You need to re-compile Python in debug mode, and recompile wxPython in
debug mode.

(3) You need to put the following in config.h or python.h:
#define COUNT_ALLOCS
#define Py_REF_DEBUG
#define Py_TRACE_REFS

(4) It won't work if you don't rebuild all your binary extensions (.so or
.pyd) this way also.

At shutdown (or if you feel like working a little, you can modify the code
so that at any time), you can now see stats at shutdown of the total number
of various types of objects created, destroyed, and maximum currently
active. This might give you a clue, if the system is really (a) growing an
unbounded set of objects (possible in python), (b) leaking memory (malloc,
but no free, of unreferenced memory), and (c) using other tools like
electric-fence, or the Visual C++ Heap Debug Library, you can see exactly
what memory is leaked, and from where. I hope it's not a GDI leak, but
if none of the above yield your leak, perhaps some monitoring tools from
Microsoft might help find the GDI usage.

Warren Postma

···

_________________________________________________________
Do You Yahoo!?
Get your free @yahoo.com address at http://mail.yahoo.com

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

I have found the proximate cause of the GDI object leak in my wxPython code:

    def __DrawHand(self,dc,minute,colour,width,r1,r2,x,y):
        fpen = wxPen(colour)
        fpen.SetWidth(width) <--- without this call, the GDI leak disappears!
        dc.SetPen(fpen)
        a = minute*3.141592654/30.0
        cosa = math.cos(a)
        sina = math.sin(a)
        x1 = int(x + r1*sina + 0.5)
        x2 = int(x + r2*sina + 0.5)
        y1 = int(y - r1*cosa + 0.5)
        y2 = int(y - r2*cosa + 0.5)
        dc.DrawLine(x1,y1, x2,y2)

So what is ultimate cause? Is this a bug in wxPython, in my code?

The memory leak reported earlier occurs independent of the GDI object leak. Since
a similar leak also occurs in the wxPython Thread demo code, I assume it is a bug
in wxPython. If possible I would like to know of a workaround or fix to that.

···

----- Original Message -----
From: "Parzival Herzog" <parz@home.com>
To: "wxPython user list" <wxpython-users@lists.sourceforge.net>
Sent: Thursday, February 01, 2001 10:36 PM
Subject: [wxPython] wxPython is leaking memory, how can I find out what and fix it?

I am writing a wxPython application that needs to be long-running.
Actually, continuously running: it is a data monitor for an electric
power trading system.

Now, I have found out that the application leaks memory, and grows
from an 8MB address space to a 100 MB space within 3 hours.

Monitoring it with windows' task manager, it appears that the number
of gdi objects grows continuously.

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

I have found the proximate cause of the GDI object leak in my wxPython code:

    def __DrawHand(self,dc,minute,colour,width,r1,r2,x,y):
        fpen = wxPen(colour)
        fpen.SetWidth(width) <--- without this call, the GDI leak disappears!
        dc.SetPen(fpen)

Okay, I know exactly what the problem here is. It's something I've been thinking of changing, now I now that it really does need changed... Way back at the dawn of time the GDI objects in wxWindows were not reference counted, and so I had problems in wxPython if you did something like this:

    dc.SetPen(wxPen(colour))

because the Python object would be deleted immediately and would take the C++ object with it, leaving a NullPen selected into the DC. To get around this I made wxPython's version of wxPen (and wxFont and wxBrush) constructor to use wxThePenList->FindOrCreatePen internally. This keeps a list of pens and reuses old ones if possible, or creates a new one if needed. Using the PenList allowed me to let the pen live longer than the Python wrapper object so statements like the above would work.

The downside is when you do this repeatedly:

    fpen = wxPen(colour)
    fpen.SetWidth(width)

the first statement creates a new pen and puts it into the PenList, and then the second one changes it. When your drawing routine is called again the pen is created again because a matching pen is not found in the list.

A workaround for now is to do this instead:

    fpen = wxPen(colour, width)

so the pen will be found in the list the next time around.

As I implied above, GDI objects in wxWindows are now reference counted and so I should be able to get rid of the internal GDI object list hack and everything will still work as expected...

The memory leak reported earlier occurs independent of the GDI object leak. Since
a similar leak also occurs in the wxPython Thread demo code, I assume it is a bug
in wxPython. If possible I would like to know of a workaround or fix to that.

I've squashed a lot of memory leak bugs in the 2.2.5 release, but I still suspect a couple more that I havn't been able to track down yet. If you can isolate any in a small sample I would appreciate it.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

Robin, the timing of this new release is truly miraculous from my point of view!

I installed wxPython 2.2.5, and, confirming your comments, the GDI leak remains,
Without your workaround,) while the memory leakage rate is reduced by a factor
of 300: My app now consumes memory at 100K per hour, instead of 30MB per hour.
Since the app does essentially the same thing once per second, this would imply
about 30 bytes of lost memory per update cycle (say 1 small object?)

I'll see what I can do to isolate the leaking code. Right now I can say that the
leakage in my app is probably a consequence of drawing code responding to
EVT_PAINT events. (I am using only wxFrame, wxPanel and wxControl objects,
and only the wxControls are involved in continuous ongoing activity, corresponding
to continuous ongoing memory loss.)

The methods that are accessed in the drawing code are:

self.GetClientSizeTuple()
wxPaintDC(self)

dc.BeginDrawing()
dc.Clear()
dc.DrawLine(x1,y1, x2,y2)
dc.DrawPolygon(self.pts)
dc.DrawRectangle(0,0, w, h)
dc.DrawRoundedRectangle(0, 0, w, h, max(1,w/5))
dc.DrawText('5', x5, y5)
dc.EndDrawing()
dc.GetCharHeight()
dc.GetTextExtent('5')
dc.SetBrush(bbr)
dc.SetFont(font)
dc.SetPen(bpen)
dc.SetTextForeground(colr)

The drawing code is activated by wxEvents sent from another thread
with wxPostEvent:

wxPostEvent(self.Display,
    EmsWidget.Event(UpdateEventId,UpdateList))

where EmsWidget.Event is:

class Event(wxPyEvent):
    def __init__(self, id, value):
        wxPyEvent.__init__(self)
        self.SetEventType(id)
        self.value = value

and the event eventually is processed within a method such as:

    def SetValue(self, value, args):
        self.rValue = (value-self.lo)/self.range
        self.Refresh()

So that about sums up the wxPython calls that could be leaking.
Of course, a this point, I don't know whether other parts of my program are
doing the residual leaking (e.g. the socket or threading modules.)

- Parzival

···

----- Original Message -----
From: "Robin Dunn" <robin@alldunn.com>
To: <wxpython-users@lists.sourceforge.net>
Sent: Saturday, February 03, 2001 2:42 PM
Subject: Re: [wxPython] wxPython is leaking memory, how can I find out what and fix it?

I've squashed a lot of memory leak bugs in the 2.2.5 release, but I still suspect a couple more that I havn't been able to track
down yet. If you can isolate any in a small sample I would appreciate it.

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users

_______________________________________________
wxPython-users mailing list
wxPython-users@lists.sourceforge.net
http://lists.sourceforge.net/lists/listinfo/wxpython-users