micropython.schedule inside class

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Drako
Posts: 12
Joined: Thu Oct 19, 2017 9:28 am

micropython.schedule inside class

Post by Drako » Thu Nov 09, 2017 12:35 pm

Hi everyone,

catching an CAN bus interrupt in a class and calling a scheduled function leads to a MemoryError.

I create a class which inherits from pyb.CAN. I initialize Can, and attach a callback function to it. In the callback function I schedule antoher function uising micropython.schedule(). When the schedule function is part of the class, I get an MemoryError. When it is not part of the class, it works:

Code: Select all

class CanInterface(CAN):
    # ------------------------------------------------------------------
    def __init__(self, itf):
        self.can = CAN(itf)
        self.init_can_bus()        

    # ------------------------------------------------------------------
    def close_me(self):
        can = self.can
        can.rxcallback(0, None)
        can.deinit()

    # ------------------------------------------------------------------
    def init_can_bus(self):
        can = self.can
        can.init(mode=CAN.NORMAL, extframe=False, prescaler=27, sjw=1, bs1=13, bs2=2)        
        can.setfilter(bank=0, mode=CAN.MASK16, fifo=0, params=(0x0600, 0xFF80, 0x0580, 0xFF80))        
        can.rxcallback(0, self.cb)

    # ------------------------------------------------------------------
    def cb(self, bus, reason):        
        #micropython.schedule(test_func, bus)				# works 
        micropython.schedule(self.test_func, bus)		                # raises MemoryError: memory allocation faild, heap is locked
        
    # ------------------------------------------------------------------
    def test_func(self, bus):
        print ("hello")

# ------------------------------------------------------------------
def test_func(bus):
    print ("hello")
When i put a heap.unlock() heap.lock() around the scheduled function, it works, but I am a little scared it might lead to some other problems:

Code: Select all

 # ------------------------------------------------------------------
    def cb(self, bus, reason):        
        micropython.heap_unlock()
        micropython.schedule(self.test_func, bus)				# works
        micropython.heap_lock()

I am not sure why this happens. I am doing something wrong? And do you guys think, using the heap.unlock -> lock workaround leads to any trouble?
Thanks in advance.

User avatar
pythoncoder
Posts: 3498
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: micropython.schedule inside class

Post by pythoncoder » Thu Nov 09, 2017 6:45 pm

I think you've found a bug here. I created a simple test case using a timer and schedule() fails when passed a bound method. I've raised an issue #3428.
Peter Hinch

User avatar
dhylands
Posts: 3202
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: micropython.schedule inside class

Post by dhylands » Thu Nov 09, 2017 7:44 pm

I think that creating the bound method requires allocating memory.

Create the bound method in your constructor (and perhaps store it in a member of the class) and pass that inside the ISR to move the memory allocation outside the ISR.

Drako
Posts: 12
Joined: Thu Oct 19, 2017 9:28 am

Re: micropython.schedule inside class

Post by Drako » Fri Nov 10, 2017 7:04 am

Yes,

Dave you are right again !

Code: Select all

class CanInterface(CAN):
    # ------------------------------------------------------------------
    def __init__(self, itf):
        self.can = CAN(itf)
        self.init_can_bus()        
        self.bound_method = self.test_func				# create method in constructor

    # ------------------------------------------------------------------
    def test_func(self, bus):
        print ("hello")
    
    # ------------------------------------------------------------------
    def close_me(self):
        can = self.can
        can.rxcallback(0, None)
        can.deinit()

    # ------------------------------------------------------------------
    def init_can_bus(self):
        can = self.can

        can.init(mode=CAN.NORMAL, extframe=False, prescaler=27, sjw=1, bs1=13, bs2=2)
        can.setfilter(bank=0, mode=CAN.MASK16, fifo=0, params=(0x0600, 0xFF80, 0x0580, 0xFF80))
        can.rxcallback(0, self.cb)

    # ------------------------------------------------------------------
    def cb(self, bus, reason):
        micropython.schedule(self.bound_method, bus)			# This works, because the method was created in the constructor

This works! Thanks for the fast help.

User avatar
pythoncoder
Posts: 3498
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: micropython.schedule inside class

Post by pythoncoder » Fri Nov 10, 2017 4:39 pm

dhylands wrote:
Thu Nov 09, 2017 7:44 pm
I think that creating the bound method requires allocating memory.

Create the bound method in your constructor (and perhaps store it in a member of the class) and pass that inside the ISR to move the memory allocation outside the ISR.
Dave, I'm utterly flummoxed by this. Please could you explain the behaviour of this script:

Code: Select all

from micropython import schedule, alloc_emergency_exception_buf
alloc_emergency_exception_buf(100)

class Foo():
    def __init__(self):
        self.x = 42
        self.dummy = self.bound_method
        tim = pyb.Timer(4)              # create a timer object using timer 4
        tim.init(freq=2)                # trigger at 2Hz
        tim.callback(self.cb)

    def bound_method(self, _):
        print('Bound method', self.x)

    def cb(self, t):
        schedule(self.dummy, 0)  # works
#        schedule(self.bound_method, 0)  # Fails with "heap is locked"

foo = Foo()
If the allocation takes place in the constructor, shouldn't either call work? Yet if it takes place on the call to schedule() shouldn't both fail? :? When the constructor runs it doesn't "know" how or if the ".dummy" reference is going to be used...
Peter Hinch

User avatar
dhylands
Posts: 3202
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: micropython.schedule inside class

Post by dhylands » Fri Nov 10, 2017 5:47 pm

The statement self.bound_method is what allocates the memory. Each time you do self.bound_method it will allocate a new bound method object (which is why all attempts to use self.bound_method from inside the ISR will fail).

The bound method object basically allocates memory to hold the pointer to self and the pointer to the method, so that when the bound method is called, self gets assigned properly.

By doing self.dummy = self.bound_method in the constructor, you're allocating a bound method object in the constructor. The statement self.dummy just returns the previously allocated bound method object, which doesn't require any additional allocations.

User avatar
pythoncoder
Posts: 3498
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: micropython.schedule inside class

Post by pythoncoder » Sat Nov 11, 2017 6:39 am

Got it :!: Thank you.
Peter Hinch

Post Reply