Skip to content
This repository was archived by the owner on Oct 30, 2026. It is now read-only.

Runtime: a struct holds one reference to its callback dict; timers and animations release it - #32

Merged
bdbarnett merged 1 commit into
mainfrom
fix-struct-cb
Oct 12, 2026
Merged

bdbarnett merged 1 commit into
mainfrom
fix-struct-cb

Conversation

@bdbarnett

Copy link
Copy Markdown
Contributor

First of three steps for #31 (animation and timer callbacks never released on CPython). This is the runtime half: registry entries carry a holder; a struct's callbacks share one reference, released with the struct (the Python wrapper's dealloc, or LVGL's ext_data free_cb for a timer or a running animation through lvpy_timer_bind / lvpy_anim_bind). The hooks are inert until the generated code calls them and lv_conf.h enables LV_USE_EXT_DATA for the CPython build; that is the lvgl-bindings change, and the sync that follows brings the test. The existing 13 tests pass on this commit.

…d animations release it

Callbacks set on a struct (lv_anim_t, lv_timer_t, ...) live in a dict in
the struct's user_data, like event callbacks. For events the dict is
released when the registration ends (#30); for structs nothing released
it, so every animation or timer callback leaked (#31).

Registry entries now carry the holder that owns the reference. An event
registration's holder is NULL and takes one reference per call, as
before. A struct's holder is the struct itself: the generated setters
pass it as containing_struct (lvgl-bindings), every callback set on the
struct shares one reference, and the reference goes when the struct's
owner lets go of it: py_lv_struct_dealloc for a struct the Python
wrapper owns; LVGL's ext_data free_cb for a timer (lvpy_timer_bind,
called after lv_timer_create and lv_timer_set_cb) and for a running
animation (lvpy_anim_bind, called after lv_anim_start, where the copy
LVGL made takes its own reference so the Python struct can start it
again). Those two hooks compile to nothing until lv_conf.h enables
LV_USE_EXT_DATA for the CPython build, which the bindings change does.

Inert on its own: the generated code calls none of it yet. The sync that
follows the bindings change brings the test.

Part of #31.
@bdbarnett
bdbarnett merged commit a701ef0 into main Oct 12, 2026
1 check passed
@bdbarnett
bdbarnett deleted the fix-struct-cb branch October 12, 2026 07:00
bdbarnett added a commit that referenced this pull request Oct 12, 2026
* Runtime: a struct holds one reference to its callback dict; timers and animations release it

Callbacks set on a struct (lv_anim_t, lv_timer_t, ...) live in a dict in
the struct's user_data, like event callbacks. For events the dict is
released when the registration ends (#30); for structs nothing released
it, so every animation or timer callback leaked (#31).

Registry entries now carry the holder that owns the reference. An event
registration's holder is NULL and takes one reference per call, as
before. A struct's holder is the struct itself: the generated setters
pass it as containing_struct (lvgl-bindings), every callback set on the
struct shares one reference, and the reference goes when the struct's
owner lets go of it: py_lv_struct_dealloc for a struct the Python
wrapper owns; LVGL's ext_data free_cb for a timer (lvpy_timer_bind,
called after lv_timer_create and lv_timer_set_cb) and for a running
animation (lvpy_anim_bind, called after lv_anim_start, where the copy
LVGL made takes its own reference so the Python struct can start it
again). Those two hooks compile to nothing until lv_conf.h enables
LV_USE_EXT_DATA for the CPython build, which the bindings change does.

Inert on its own: the generated code calls none of it yet. The sync that
follows the bindings change brings the test.

Part of #31.

* Sync bindings from 2137b6ce8baf4d988a289abef00c7ac3c7b36fb3: animation and timer callbacks are released

The generated struct setters now pass the struct as containing_struct,
lv_timer_create / lv_timer_set_cb call lvpy_timer_bind and lv_anim_start
calls lvpy_anim_bind, and lv_conf.h enables LV_USE_EXT_DATA for the
CPython build. With the runtime from #32 that means a callback set on an
lv.anim_t lives while the Python struct or a running copy of it exists,
and a timer's callback lives while the timer does; LVGL's ext_data
free_cb releases them on every path that frees the timer or animation.

tests/test_struct_callback_lifetime.py pins it: released after
completion once the struct is dropped; survives a dropped struct while
running; restart from the same struct; released on anim_delete_all after
deleted_cb ran; a timer released on delete, when its repeat count runs
out, and when it deletes itself from its own callback; set_cb replaces
without leaking; 50 animation plus 50 timer cycles with 20 KB per
callback grow the heap by under 64 KB (3 MB before). All nine checks fail
on 9.5.50. A variant where the running copy takes no reference segfaults
under MALLOC_PERTURB_ while the dropped struct's animation is still
running, which is why the copy holds its own reference.

For #31.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant