question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Hello Peter, first, thank you very much for this incredible library. Asyncio on micropython has been game changer for embedded applications.

Our application is fairly complex, we use context managers to build up and tear down the application in a controlled way. We are noticing we have a memory leak, and I believe I’ve narrowed it down to Delay_ms.

Give it a try:

import sys
import machine
import gc
import uasyncio as asyncio
from primitives.delay_ms import Delay_ms

async def main():
    async def scoped():
        nop = Delay_ms()
        await asyncio.sleep(.25)
    while True:
        await scoped()
        gc.collect()
        print(gc.mem_free())

try:
    asyncio.run(main())
except KeyboardInterrupt:
    pass
except Exception as err:
    sys.print_exception(err)
finally:
    asyncio.new_event_loop()
    gc.collect()

In primitives/Delay_ms, last line of init, the task is created but never canceled. If you simply comment out that line, and re-run the test above, you’ll see there’s no loss.

def __init__(self, func=None, args=(), duration=1000):
        self._func = func
        self._args = args
        self._durn = duration  # Default duration
        self._retn = None  # Return value of launched callable
        self._tend = None  # Stop time (absolute ms).
        self._busy = False
        self._trig = asyncio.ThreadSafeFlag()
        self._tout = asyncio.Event()  # Timeout event
        self.wait = self._tout.wait  # Allow: await wait_ms.wait()
        self._ttask = self._fake  # Timer task
        asyncio.create_task(self._run())    ############### TASK CREATED BUT NEVER KILLED

The fix for me…

In my code, I’ve done the following, in init self._mtask = asyncio.create_task(self._run()) #Main task

and created a new function:

def deinit(self):
        self._mtask.cancel()

New Test case:

import sys
import machine
import gc
import uasyncio as asyncio
from primitives.delay_ms import Delay_ms

async def main():
    async def scoped():
        try:
            nop = Delay_ms()
        finally:
            nop.deinit()
        await asyncio.sleep(.25)
    while True:
        await scoped()
        gc.collect()
        print(gc.mem_free())

try:
    asyncio.run(main())
except KeyboardInterrupt:
    pass
except Exception as err:

try:
    asyncio.run(main())
except KeyboardInterrupt:
    pass
except Exception as err:
    sys.print_exception(err)
finally:
    asyncio.new_event_loop()
    gc.collect()

I’m not 100% clear on del operation in micropython, if it’s guaranteed, but I think being explicit with a deinit function seems to make sense as other micropython objects also deinit.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
stephanelsmithcommented, Jan 28, 2022

Documentation update is great, I would have made sense of this. Thank you again for your attention to details and taking up this improvement.

0reactions
peterhinchcommented, Jan 27, 2022

I have made a minor change. I was concerned that, after issuing .deinit, the timer was in a non-working state. Attempts to retrigger it would fail silently. Now, in this event, a RuntimeError is raised. This is tested in tests/delay_test.py. The tutorial now has a brief explanation of the issue.

The following note is so that I have a record of the design criteria.

Design

I investigated a design which avoided a ._run() task, starting and cancelling a timer task as required. This would have had the advantage that an inactive instance which went out of scope would leave no running task. However maintaining the ability to trigger in a hard ISR proved problematic. Doubtless the issues with micropython.schedule could be fixed, but the code was starting to look rather elaborate. Further, it didn’t fix the problem in the general case, as a running delay which went our of scope would still leave a running task. It would still be necessary to issue .stop to ensure clean termination.

I have therefore retained the existing design.

Read more comments on GitHub >

github_iconTop Results From Across the Web

JavaScript Promise Chaining Memory Leak Pattern - Cribl
In this post we'll walk through a memory leak pattern we recently encountered when using Javascript Promises.
Read more >
Memory leak · Issue #47 · Lemoncode/redux-chat-front · GitHub
After 1.000 connect, send a message and disconnect you get read and write sagas tasks are not removed. Here's the profiler after the...
Read more >
Understanding How DelayMS Function Works With PIC18F ...
Understanding How DelayMS Function Works With PIC18F Microcontroller ... How important is the radiator suspected of leaking on the Soyuz?
Read more >
Diff - aa79bef^! - platform/frameworks/av - Git at Google
Failing to do so causes a leak of patch descriptors in audio flinger. Bug: 19032387. ... Disable wait for status if delay is...
Read more >
Memory leak while using SDL - Simple Directmedia Layer
SDL_DestroyTexture(backgroundTexture); endMs = SDL_GetTicks(); delayMs = (endMs - startMs); //SDL_Delay(delayMs); }//end of main render ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found