views:

305

answers:

1

I am pretty sure I am suffering from memory leakage, but I havent 100% nailed down how its happening.

The application Iv'e written downloads 2 images from a url and queues each set of images, called a transaction, into a queue to be popped off by the user interface and displayed. The images are pretty big, averaging about 2.5MB. So as a way of speeding up the user interface and making it more responsive, I pre-load each transaction images into wxImage objects and store them.

When the user pops off another transaction, I feed the preloaded image into a window object that then converts the wxImage into a bitmap and DC blits to the window. The window object is then displayed on a panel.

When the transaction is finished by the user, I destroy the window object (presumably the window goes away, as does the bitmap) and the transaction data structure is overwritten with 'None'.

However, depending on how many images ive preloaded, whether the queue size is set large and its done all at once, or whether I let a small queue size sit over time, it eventually crashes. I really cant let this happen .. :)

Anyone see any obvious logical errors in what im doing? Does python garbage collect? I dont have much experience with having to deal with memory issues.

[edit] here is the code ;) This is the code related to the thread that downloads the images - it is instanced in the main thread the runs the GUI - the download thread's main function is the 'fill_queue' function:

def fill_queue(self):
    while True:
        if (self.len() < self.maxqueuesize):

            try:

                trx_data = self.download_transaction_data(self.get_url)

                for trx in trx_data:
                    self.download_transaction_images(trx)
                    if self.valid_images([trx['image_name_1'], trx['image_name_2']]):
                        trx = self.pre_load_images(trx)
                        self.append(trx)

            except IOError, error:
                print "Received IOError while trying to download transactions or images"
                print "Error Received: ", error
            except Exception, ex:
                print "Caught general exception while trying to download transactions or images"
                print "Error Received: ", ex

        else: 
            time.sleep(1)


def download_transaction_images(self, data):
    """ Method will download all the available images for the provided transaction """

    for(a, b) in data.items():
        if (b) and (a == "image_name_1" or a == "image_name_2"):
            modified_url   = self.images_url + self.path_from_filename(b)
            download_url   = modified_url + b
            local_filepath = self.cache_dir + b

            urllib.urlretrieve(download_url, local_filepath)
            urllib.urlcleanup()


def download_transaction_data(self, trx_location):
    """ Method will download transaction data and return a parsed list of hash structures """

    page = urllib.urlopen(trx_location)
    data = page.readlines()
    page.close()

    trx_list = []
    trx_data = {}
    for line in data:

        line = line.rstrip('|!\n')
        if re.search('id=', line):
            fields = re.split('\|', line)

            for jnd in fields:
                pairs = jnd.split('=')
                trx_data[pairs[0]] = pairs[1]

            trx_list.append(trx_data)

    return trx_list


def pre_load_images(self, trx):
    """ Method will create a wxImage and load it into memory to speed the image display """
    path1  = self.cache_dir + trx['image_name_1']
    path2  = self.cache_dir + trx['image_name_2']
    image1 = wx.Image(path1)
    image2 = wx.Image(path2)
    trx['loaded_image_1'] = image1
    trx['loaded_image_2'] = image2
    return trx        


def valid_images(self, images):
    """ Method verifies that the image path is valid and image is readable """
    retval = True
    for i in images:
        if re.search('jpg', i) or re.search('jpeg', i):
            imagepath = self.cache_dir + i
            if not os.path.exists(imagepath) or not wx.Image.CanRead(imagepath):
                retval = False
        else:
            retval = False

    return retval

Also, I'd like to add that sometimes, just before the crash I get peculiar errors in my console, they look like corrupt image errors but the images are not corrupted, the error has happened at all stages on all images.

Application transferred too few scanlines [2009-09-08 11:12:03] Error: JPEG: Couldn't load - file is probably corrupted. [2009-09-08 11:12:11] Debug: ....\src\msw\dib.cpp(134): 'CreateDIBSection' fail ed with error 0x00000000 (the operation completed successfully.).

These errors can happen a la carte, or all together. What I think is happening is that at some point the memory becomes corrupted and anything that happens next, if I load a new transaction, or image, or do a cropping operation - it takes a dive.


So unfortunately after trying out the suggestion of moving the pre-loading function call to wxImage into the main gui thread I am still getting the error - again it will occur after too many images have been loaded into memory or if they sit in memory for too long. Then when I attempt to crop an image the i get a memory error - something is corrupting, whether in the former case I am using too much or dont have enough (which makes no sense because I've increased my paging file size to astronomical proportions) or in the latter case where the length of time is causing a leak or corruption

The only way I think I can go at this point is to use a debugger - are there any easy ways to debug a wxPython application? I would like to see the memory usage in particular.

The main reason why I think I need to preload the images is because if I call wxImage on each image ( I show two at a time) each time i load a 'transaction' the interface from one transaction to the next is very slow and clunky - If I load them in memory its very fast - but then I get my memory error.

+2  A: 

Two thoughts:

  1. You do not mention if the downloading is running a separate thread (actually now I see that this is running in a separate thread, I should read more closely). I'm pretty sure that wx.Image is not thread-safe, so if you are instantiating wx.Images in a non-GUI thread, that could lead to trouble like this. (This is almost certainly the issue, most wx classes/objects/functions are not thread-safe).
  2. I've been bitten by nasty IncRef/DecRef bugs in wxPython (due to the underlying C++ bindings) before (mostly associated with wx.Grid and associated classes). While I don't know of any with wx.Image, it wouldn't surprise me to find out you may be required to manually manage memory like you have to in wx.Grid sometimes.

Edit You need to instantiate the wx.Image in the GUI thread, not the downloading thread (which your above code looks like you are currently instantiating in the non-GUI thread). In general this is almost always going to cause lots of problems in any GUI toolkit. You can search the wxPython mailing list for lots of emails where this is the case. Personally I would do this:

  1. Queue for download urls.
  2. Thread to download images.
  3. Have the downloading thread places a disk location (watch out for race conditions!) in a separate queue and post custom wx.Event(threadsafe) (threadsafe with wx.PostEvent function) to the App thread.
  4. Have the GUI thread pop the file locations and instantiate wx.Image ----> wx.Bitmap (maybe with wx.CallAfter to process when App is idle)
  5. Display (Blit) as needed.
DrBloodmoney
Ok this is a good start. I had found a few places in the main GUI thread where I was copying out the pre-loaded wxImage object and keeping it around until the next transaction - only to then overwrite it again with another copy. I am no C++ genius but to me that seemed sketchy - so I got rid of that. But its still happening at higher queue sizes - and it seems to happen more when the available physical memory is low.Any idea how to manually manage memory with wxImage? Can this be done from python or do I have to do a ctypes or C++ extension?
Matt1776
I'll edit my answer to reply to your comment:)
DrBloodmoney
Ah, I see. I did not even consider using CallAfter - i.e. I thought I was forced to do the preloading in the download thread because I needed them to be PRE loaded, but now I see I can do that when the GUI main thread isnt busy, for example, after I display the image and the user is typing. THANK YOU - I will give this a shot :)
Matt1776
I dont mean to uncomment the answer but how do I call attention to the post again otherwise?
Matt1776