0

I created a tkinter program with a memory leak issue that originally caused it to lock up after about 40 minutes. I tried an initial fix which greatly improved performance, but it still slows down after sometime so I think there is a possible second memory leak and/or other issue that I wanted to check with the community on.

About the program: A back end script updates a data table every sixty seconds which is then pushed to a tkinter script that crafts the data table in a nice layout. (I made some example code below, its not the actual script which is much longer) Every time it is refreshed, the data table can have a different number of rows/columns. Thus, my tkinter script needs to dynamically create the table and bind a button function to each cell. I'm just getting into GUI program and choose Tkinter as the first library to test out. I wanted to use a GUI library instead of a charting library because: 1) I want to learn how to build basic GUIs and figure this would be a fun application; 2) I want to be able to click on any portion of my data table and have a window pop-up that will adjust inputs for the back-end script on its next refresh.

Evolution of my code to solve memory leak problem and potential additional memory leak: So originally, I assumed that the cells would just get erased when you write a new box over the grid position. Version 1.0 below:

import pandas as pd
import numpy as np
from tkinter import *

root = Tk()
root.configure(background='black')

#Placeholder for example code

def popupwindow():
    pass

def build():
    mydf = pd.DataFrame([np.arange(1, np.random.randint(3, 7)) * np.random.randint(1,10) for x in np.arange(1, np.random.randint(3, 7))])
    rowindex = 1
    for row in mydf.iterrows():
        colindex = 1
        for i in row[1]:
            label = Label(root, text=str(i), width=7)
            label.bind('<Button-1>', popupwindow)
            label.grid(row=rowindex,column=colindex)
            colindex += 1
        rowindex += 1

    #If grid is smaller than previous grid, remove old widgets
    for label in root.grid_slaves():
        if int(label.grid_info()['row']) > rowindex-1 or int(label.grid_info()['column']) > colindex-1:
            label.grid_forget()

def refresh():
    build()
    #For purpose of example code, I made refresh rate faster than my actual program's 60 seconds  
    root.after(5000, refresh)

refresh()
root.mainloop()

As I found out that is not the case, and that was causing the first memory leak problem. So I created Version 2.0 which ‘forgot’ all the grid slaves before recreating the grid (See the two new lines of code in build()). This substantially improved the performance but I still see a partial slowdown in Tkinter responsiveness overtime. Although, it has brought a second issue where after 30-40 minutes, the screen will go partially or entirely black (background for my frame is black) and will stop refreshing after:

def build():
    mydf = pd.DataFrame([np.arange(1, np.random.randint(3, 7)) * np.random.randint(1,10) for x in np.arange(1, np.random.randint(3, 7))])

    # I ADDED THESE TWO LINES OF CODE
    for label in root.grid_slaves():
        label.grid_forget()

    rowindex = 1
    for row in mydf.iterrows():
        colindex = 1
        for i in row[1]:
            label = Label(root, text=str(i), width=7)
            label.bind('<Button-1>', popupwindow)
            label.grid(row=rowindex,column=colindex)
            colindex += 1
        rowindex += 1

    # REMOVED THESE 3 LINES OF CODE AS NOW REDUNDANT WITH ADDED CODE ABOVE
    # for label in root.grid_slaves():
    #     if int(label.grid_info()['row']) > rowindex-1 or int(label.grid_info()['column']) > colindex-1:
    #         label.grid_forget()

After perusing the forums more I saw this post on Overstack (Tkinter - memory leak with canvas) , which possibly signaled that widget labels may never be garbage collected even if forgotten. Not sure if this is an accurate interpretation, but if it is then this could be another possible reason why my V2.0 slows down overtime as i'm always forgetting and never rewriting my labels. Therefore, my proposed solution for V3 would be to use an if else function to see if a label already exists at a given position, if it doesn’t create a new label, if it does exist then adjust it. My question is, is this how you would approach it? Is there another memory leak/performance issue you can see from my basic example? If you have additional proposed adjustments to my code, such as how I dynamically created the data table, feel free to provide any input/improvements! Since i'm new to programming, I am very open to different ideas and more efficient methods.

Thank you in advance for your help!

Whip
  • 133
  • 2
  • 10

1 Answers1

1

The bug isn't in grid, it's in your code. Every five seconds you are creating a new dataframe, and new labels for every row in that dataframe, and you never delete them. Instead, you just keep stacking them on top of each other.

Calling grid_forget or grid_remove only removes them from the view, the objects aren't deleted.

You need to either delete all of the old widgets on each call to refresh, or reuse the existing labels rather than creating new labels.

Therefore, my proposed solution for V3 would be to use an if else function to see if a label already exists at a given position, if it doesn’t create a new label, if it does exist then adjust it. My question is, is this how you would approach it?

Yes. That, and destroy the old widgets that are no longer being used, rather than just removing them from the grid.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks! I didn't mean to imply that it was in Tkinter. I knew my code was going to be the one with the issue. Your help is much appreciated! – Whip Dec 28 '19 at 00:31