Skip to content

SIGBUS in gc.collect() after an lv.deinit()/lv.init() cycle (finaliser walk hits a freed LVGL object) #10

Description

@bdbarnett

Found during jpegio Phase 2 (2026-09-03) on a unix build containing only lvgl-bindings aa6c6bc + lvgl-micropython d7ea193 (no displayif, so not jpegio's). Reproduces on MicroPython v1.28.0 unix, stock upstream, no cmods patches.

Repro (repro.py; pass cycle to trigger):

import sys, gc
import lvgl as lv
W, H = 320, 240
def setup():
    disp = lv.display_create(W, H)
    disp.set_color_format(lv.COLOR_FORMAT.RGB565)
    buf = lv.draw_buf_create(W, H, lv.COLOR_FORMAT.RGB565, 0)
    disp.set_draw_buffers(buf, None)
    disp.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL)
    disp.set_flush_cb(lambda d, a, c: d.flush_ready())
    return disp, buf
lv.init()
disp, buf = setup()
if "cycle" in sys.argv:
    print("cycle"); lv.deinit(); lv.init(); disp, buf = setup()
lbl = lv.label(lv.screen_active()); lbl.set_text("hello")
for i in range(3):
    lbl.invalidate(); lv.refr_now(disp); print("refresh", i, "ok")
print("collect..."); gc.collect(); print("collect ok", gc.mem_free())

Without cycle: passes. With cycle: the three refreshes print, then gc.collect() dies with SIGBUS.

Backtrace (unstripped build, STRIP=):
mp_load_method_maybe (obj=0x7ffff7a08560, attr=__del__) py/runtime.c:1201gc_sweep_run_finalisers py/gc.c:639gc_collect_endgc_collectpy_gc_collect.

So the sweep's finaliser walk reaches an object whose type pointer is no longer valid after the first lv.deinit() freed the LVGL side: something the binding still holds (or that the GC still sees as having a finaliser) from the first lv.init() lifetime survives the cycle. Suspects: the display/draw-buffer wrappers from the first setup() (still referenced by Python until rebound) whose C objects lv.deinit() freed, or the callback registry (set_flush_cb closure) retained across the cycle — see the widget-retention lesson from the v9.5.22 callback fixes.

Not blocking anything: displayif's LVGL test orders its deinit/init section last and avoids gc.collect() after it for this reason (displayif tests/jpegio/test_jpegio_lvgl.py, section 7). Worth fixing because lv.deinit()/lv.init() is the documented way to reset LVGL in a long-running REPL session.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions