6
\$\begingroup\$

Below Python code generates memory leak.

Challenge: Fix the memory leak with minimal changes to the code as measured by Levenshtein distance to the original code. You may only change the code before t is initialised. When the del t line is removed the modified program should print I'm still alive! every second.

PS: I already know the answer (found it after several hours of debugging). This is not the programming help question! Hopefully this would be interesting to folks.

import threading, time

# this class takes callback and calls it every 1 sec
class MyTimer:
    def __init__(self, callback):
        self.callback = callback
        self.th = threading.Thread(target=self.run,daemon=True)
        self.th.start()
    def run(self):
        while(True):
            self.callback()
            time.sleep(1)

class TestMyTimer:
    def __init__(self):
        self.timer = MyTimer(self.is_alive)
    def is_alive(self):
        print("I'm still alive")

# test code for MyTimer
t = TestMyTimer()
del t

# just to be sure to collect all dead objects
import gc
gc.collect()

# if you are still seeing messages then we have memory leak!
time.sleep(60)

Here's repl.it to play with this code online.

\$\endgroup\$
  • 1
    \$\begingroup\$ Ah, rereading this, I see that you're aware of the answer to your problem. However, on PPCG, the puzzles tend to be challenges for which we write code to solve the criteria, rather than solve a puzzle in a piece of code. The closest tag I can see for this is the rarely used programming-puzzle (which is often mistagged), but you would still need an objective winning criterion (i.e. what makes one answer to your question better than a different answer) to make this challenge on-topic \$\endgroup\$ – Jo King Feb 26 at 3:58
  • 2
    \$\begingroup\$ In the future, it might be best to look through and answer some existing challenges, and make sure to post future challenges in the Sandbox to gauge community reactions before posting to main. \$\endgroup\$ – Jo King Feb 26 at 3:58
  • 3
    \$\begingroup\$ I think this could be tagged as programming-puzzle with the winning criterion being minimal editing distance to get a solution without memory leak. \$\endgroup\$ – Laikoni Feb 26 at 7:56
  • 5
    \$\begingroup\$ An additional thought: you might want to enforce that the fixed code produce a particular output, otherwise the best solution is likely to be to comment out everything (distance 6). This does prevent leaks but doesn't require any skill and probably is not what you were thinking. \$\endgroup\$ – Sriotchilism O'Zaic Feb 27 at 3:10
  • 2
    \$\begingroup\$ I've added the code-challenge as all challenges require a tag. I apologise for initially dismissing the challenge as a generic programming help question, but we get those quite often here. Voting to reopen. \$\endgroup\$ – Jo King Feb 27 at 4:33
6
\$\begingroup\$

Taking inspiration from the comments and from the question, just by adding 3 characters I "fixed" the memory leak.

Granted, I broke everything else at the same time

import threading, time

# this class takes callback and calls it every 1 sec
class MyTimer:
    def __init__(self, callback):
        self.callback = callback
        self.th = threading.Thread(target=self.run,daemon=True)
        self.th.start()
    def run(self):
        while(True):
            self.callback()
            time.sleep(1)

class TestMyTimer:
    def __init__(self):
        self.timer = MyTimer(self.is_alive)
    def is_alive(self):
        print("I'm still alive")

# test code for MyTimer
#t = TestMyTimer()
#del t

# just to be sure to collect all dead objects
import gc
#gc.collect()

# if you are still seeing messages then we have memory leak!
time.sleep(60)

Edit: The challenge has now been properly updated :)

\$\endgroup\$
  • 1
    \$\begingroup\$ LOL. Not quite there :) \$\endgroup\$ – Shital Shah Feb 28 at 7:35
0
\$\begingroup\$
import threading, time

# this class takes callback and calls it every 1 sec
class MyTimer:
    def __init__(self, callback):
        self.a = True
        self.callback = callback
        self.th = threading.Thread(target=self.run,daemon=True)
        self.th.start()
    def run(self):
        while(True):
            self.callback(self.a)
            time.sleep(1)


class TestMyTimer:
    def __init__(self):
        self.timer = MyTimer(self.is_alive)
    def is_alive(self, k):
        print("I'm still alive")
        if not k:
            raise SystemExit()


# test code for MyTimer
t = TestMyTimer()
t.timer.a = 0
del t.timer, t

# just to be sure to collect all dead objects
import gc
gc.collect()

# if you are still seeing messages then we have memory leak!
time.sleep(60)
\$\endgroup\$
  • \$\begingroup\$ Memory leak is still there. You can check this by using objgraph library and then doing objgraph.show_backrefs(objgraph.by_type('TestMyTimer'), refcounts=True). You just stopped the timer to print the messages. \$\endgroup\$ – Shital Shah Mar 2 at 0:10
  • \$\begingroup\$ Fair enough, I've edited my answer to raise the SystemExit. \$\endgroup\$ – Dan Greenhalgh Mar 15 at 10:01
  • \$\begingroup\$ Please add your score to your submission \$\endgroup\$ – Jo King Mar 15 at 10:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.