pycom / pycom-micropython-sigfox

A fork of MicroPython with the ESP32 port customized to run on Pycom's IoT multi-network modules.
MIT License
196 stars 167 forks source link

Memory leak using Pin (callback) #53

Closed syndycat closed 6 years ago

syndycat commented 6 years ago

Hello,

LOPY 1.7.9.b3.

If you run this piece of code you will see that memory is decreasing ... in the end it stops with 'MemoryError: memory allocation failed' . Comparing to previous timer memory leak issue, I believe there is ALWAYS a LEAK when you are using function callbacks.

import gc
import utime
from machine import Pin

class Object:

    def __init__(self): 
        print ("Object INIT ", self)

    def start(self):    

        def _call(arg):
            print('PIN changed')

        self._analogPin=Pin('P15',mode=Pin.IN,pull=Pin.PULL_UP)    
        self._analogPin.callback(Pin.IRQ_FALLING , _call)

    def __del__(self):
        print ("Object DEL ", self)

    def stop(self):    
        self._analogPin.callback(Pin.IRQ_FALLING , None)
        del self._analogPin
        self._analogPin = None

print("Start memory %s" %(gc.mem_free()))

SLEEP = 0.1

gc.enable()
while True:
    obj = Object()
    obj.start()
    utime.sleep(SLEEP)
    obj.stop()
    del obj 
    obj = None

    gc.collect()
    print("\t\tmemory %s" %(gc.mem_free()))
robert-hh commented 6 years ago

I guess the reason is the same as in the issue #52 you raised before. Either the old callback object is not cleared when you register a new one, or the memory gets too much fragmented. Besides that, I would consider it as pretty unusual to register a callback over & over again. With issue #52, I tried to register the callback outside once and not put all of that in a class, and then there was no memory drain. Only the code did not look nice.

syndycat commented 6 years ago

If you go with a callback outside object and 2 or more objects are using it, might be unpredictable result by trigger it in the same time . That's why each object shall use a dedicated callback. You agree with me that freeing the object ... shall free everything. LoPy has limited memory so we need to free objects in order to create others. I don't know what means fragmented memory , but the term should not exist in embedded devices.

robert-hh commented 6 years ago

I agree the freeing the object does should free everything, which means, that the object is declared as collectible, once garbage collect is running. Memory fragmentations exists, since the code creates dynamically memory objects, while it's running, and these are placed on the head. You code for instance would in the loop first create a class instance, then a PIN object, then within the class a memory object for self._analogPin, and so on. If these objects are not used any mode, gc.collect() can pack them together. I'm guessing, that one the the objects are not tagged as free, and I think it's the function called by the callback, because it is almost impossible for the interpreter to tell, when it is no needed any more. I do not understand your statement of "might be unpredictable result by trigger it in the same time". Because whatever your code is, if you have two events at the same time you must tell which is which, and putting them in different classes does not help. Anyhow, there's something strange with memory management. It looks a little bit as if alarm.cancel() would not release the memory used for storing the callback function data.

syndycat commented 6 years ago

Right. We shall post also this callback problem to George Damien in micropython git.

In our case, if we go with the approach of having one callback for multiple objects... then we know the timer or PIN in that callback parameter ... but we don't know the object that instantiate the timer or the pin. In other words, after the callback is triggered we need to call methods from that object that instantiate timer, Pin... and this can be done only with a dictionary between timer/pin and parent object but looks totally unprofessionally and non-python way. That's why we put the callback inside the class/object.

robert-hh commented 6 years ago

Some comments about it in issue #52. No need to ask George, especially since the implementation is different for PyBoard and ESP8266.

robert-hh commented 6 years ago

Seems to be fixed now.

danicampora commented 6 years ago

thanks @robert-hh , solved here: https://github.com/pycom/pycom-micropython-sigfox/commit/01cc316bff96fd42a8560f3de76c8fd11ce84c0c