Is there a memory leak in umqtt.simple or am I doing this wrong? #13602
-
I'm using a Pimoroni Inky Frame 4.0 with a Raspberry Pi Pico W running the pimoroni-pico build of MicroPython 1.21.0 to show the latest cat detected by Frigate over MQTT using So I was subscribed to topic def on_message_cb(topic, msg):
if topic == b"frigate/pi4/mycamera/cat/snapshot":
# Memory consuming code here
# Ignore every other topic, not making references to `msg` I had however great difficulty showing the smallest picture due to lack of memory with the final Now here is the part I don't understand: When I subscribe to Why does this happen? It should not matter. The messages in the ignored callbacks should simply be reclaimed by To me it feels like a memory leak, but I know in reality it is infinitely more probable that I made mistakes in my code. I hope someone can point that out. Here's the whole script I called import gc, io, os, time, machine, jpegdec
import inky_helper as ih
from umqtt.simple import MQTTClient
from json import dumps, loads
from binascii import hexlify, crc32
from helpers import gc_collect, file_exists
from secrets import secret
# just gc.collect() decorated with a print statement and gc.mem_free()
gc_collect('begin')
# umqtt topics need to be binary for successful comparison
TOPIC_SUB = b""
TOPIC_SUB_ETC = b""
CACHE_FILE='mqtt_img.json'
mqtt_client = None
# Pimoroni Inky Frame specific code
UPDATE_INTERVAL = 5
graphics = None
WIDTH = None
HEIGHT = None
leds = ih.LEDController()
# Called by Inky Frame launcher
def update():
client = connectMQTT()
loops = n = 24 # Checking two dozen messages should be enough
while n > 0:
print(f'Checking message ({loops-n+1})')
try:
client.check_msg()
except OSError as e:
if e.args[0] == 9:
# Done on purpose in the callback to conserve memory
print("MQTT Client was disconnected.")
break
n -= 1
if n == 0:
print(f'Parsed {loops} messages and nothing happened.')
else:
print('Client disconnected. Time to go home.')
def connectMQTT():
global mqtt_client
client_id = hexlify(machine.unique_id())
client_id = f"inky_frame-{client_id.decode('ascii')[:6]}"
client = MQTTClient(client_id=client_id,
server=b"mymqtt.localhost",
port=1883,
user=b"myuser",
password=b"mypassword",
keepalive=7200,
)
client.set_callback(on_message_cb)
client.connect()
client.subscribe(TOPIC_SUB)
print(f'Connected to MQTT broker, subscribed to {TOPIC_SUB}')
gc_collect('subscribed')
mqtt_client = client
return client
def on_message_cb(topic, msg):
global mqtt_client
if topic == TOPIC_SUB:
mqtt_client.disconnect()
buffer = io.BytesIO(msg)
# Calculate hash to compare with cached image
img_hash = crc32(msg)
gc_collect('hashed')
# Get old hash from cache
if file_exists(CACHE_FILE):
try:
data = loads(open(CACHE_FILE, "r").read())
if type(data) is dict:
# If the e-ink screen was already on this page, don't refresh old pictures
if data['imgHash'] == img_hash and ih.state['run'] == ih.state['prev']:
# No update needed; Go back to sleep
print('Cache hit. Nothing to do.')
return
else:
# File corrupted; Remove
os.remove('mqtt_img.json')
except OSError:
print('Unexpected OSError.')
pass
print('Cache compared, needs redraw')
# Write cache
with open(CACHE_FILE, "w") as f:
f.write(dumps({"imgHash": img_hash}))
f.flush()
# Rewind buffer and write to file
buffer.seek(0)
with open('mqtt_img.jpg', "wb") as f:
while True:
chunk = buffer.read(1024)
if not chunk:
break
f.write(chunk)
# We have a serious need for memory in order to draw the jpeg.
# Since we call draw(), these vars are still referenced.
del buffer, msg, img_hash, mqtt_client, data, chunk
gc_collect('img_draw')
draw(True)
def draw(got_img = False):
# Override call from main.py
if not got_img:
return
# Set LED status normally done by main.py
leds.on("warning")
gc_collect('jpeg')
jpeg = jpegdec.JPEG(graphics) ### MemoryError here ###
graphics.set_pen(1)
graphics.clear()
jpeg.open_file('mqtt_img.jpg')
gc_collect('jpdecode')
jpeg.decode()
graphics.update() To summarize: The script works as long as I subscribe directly to the snapshot topic containing one single snapshot. I run into memory errors if I subscribe to a parent topic that contains multiple snapshots, even if I ignore everything but the one I am looking for. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 11 replies
-
My first thought is that the other incoming messages would cause increased memory fragmentation, even though you ignore them. Just how fragmented the memory becomes would depend on the rate and pattern of message sizes you receive. If the memory becomes sufficiently fragmented, you will be unable to allocate larger blocks of memory. PS. Apologies in advance for some other unsolicited advice:
|
Beta Was this translation helpful? Give feedback.
-
you can get information on the memory map using |
Beta Was this translation helpful? Give feedback.
My first thought is that the other incoming messages would cause increased memory fragmentation, even though you ignore them. Just how fragmented the memory becomes would depend on the rate and pattern of message sizes you receive. If the memory becomes sufficiently fragmented, you will be unable to allocate larger blocks of memory.
PS. Apologies in advance for some other unsolicited advice:
client.wait_msg()
, notclient.check_msg()
inupdate()
- if I am reading your logic correctly.CACHE_FILE
inon_message_cb()
when you open it for reading