The multi-level accordion screen of the shipped all_widgets demo needs a draw-context depth of 10. GX_MAX_CONTEXT_NESTING defaults to 8 (common/inc/gx_api.h:206). When the stack runs out, the child widget and its whole subtree are never painted, and nothing tells the application.
This predates 8a35f690 (#158) and is unrelated to it.
The defect
_gx_widget_children_draw() pushes one draw context per level of the widget tree, and _gx_system_canvas_refresh() pushes two before the tree is walked (gx_system_canvas_refresh.c:333 for the root window, :373 for the dirty widget). On overflow, _gx_canvas_drawing_initiate() returns GX_DRAW_NESTING_EXCEEDED and gx_widget_children_draw.c:103 — correctly, by its own contract — skips the child entirely:
status = _gx_canvas_drawing_initiate(canvas, child, &overlap);
if (status == GX_SUCCESS)
{
child -> gx_widget_draw_function(child);
_gx_canvas_drawing_complete(canvas, GX_FALSE);
}
else
{
if (status == GX_NO_VIEWS)
{
_gx_canvas_drawing_complete(canvas, GX_FALSE);
}
}
_gx_system_error_process(GX_DRAW_NESTING_EXCEEDED) is called, but in the default configuration that is a no-op: the callback path is behind both GX_DISABLE_THREADX_BINDING and GX_ENABLE_ERROR_CALLBACK, and no test configuration defines either. So a subtree silently disappears.
The measurement
One run of guix_all_widgets_accordion_menu, breakpoint on the return GX_DRAW_NESTING_EXCEEDED; in gx_canvas_drawing_initiate.c:
| Caller |
Overflows |
_gx_widget_children_draw |
67 |
_gx_multi_line_text_view_text_draw |
1 |
The single MLTV overflow was #176's subject and is fixed. The 67 are this defect.
Peak depth required, from a scratch build with GX_MAX_CONTEXT_NESTING raised to 64 and a running maximum instrumented into _gx_canvas_drawing_initiate():
MAXNEST=10 overflow count: 0
The widget chain that exhausts the stack, printed from the overflowing widget upwards:
prompt_11_1 type= 30
menu_list type= 75
mla_menu_2_1_accordion type= 77
menu_list type= 75
mla_menu_2_accordion type= 77
menu_list type= 75
multi_level_accordion type= 77
menu_screen type=128
Primary type=129 (root window)
Each accordion level costs two contexts — the accordion widget and its menu_list child. Three nested accordion levels plus menu_screen plus the two the refresh already holds is 9; the deepest leaf makes 10.
Six golden frames record incompletely rendered output
Raising the limit and re-running against the unmodified golden data:
GX_MAX_CONTEXT_NESTING |
Overflows |
Frames differing from golden (of 21) |
| 8 (default) |
68 |
12 |
| 9 |
4 |
15, 16, 17, 18, 19, 20 |
| 10, 11, 12, 16, 64 |
0 |
15, 16, 17, 18, 19, 20 |
Frames 15–20 match the golden only while the paint is being dropped, and stop matching as soon as the children are actually drawn. Those are menu clicks 13 through 18. Frame 12 behaves the opposite way — it matches once nothing overflows, which is what proved #176's fix was needed rather than a golden regeneration.
Repository-wide cost of raising the default, measured on default_build_coverage at GX_MAX_CONTEXT_NESTING = 12, before #176 and #177 landed:
99% tests passed, 3 tests failed out of 732
108 - guix_all_widgets_accordion_menu (6 frames — this defect)
196 - guix_widget_children_draw (1 frame — see below)
247 - guix_ml_text_view_32bpp (unrelated; fixed by #177)
The third is now fixed, so two tests need attention. Worth re-measuring on current dev before acting, since that sweep predates both #176 and #177.
guix_widget_children_draw exists to test the limit
test/guix_test/regression_test/tests/validation_guix_widget_children_draw.c creates four nested prompts and captures one frame, commented:
gx_validation_set_frame_comment("Create nested widgets that exceed GX_MAX_CONTEXT_NESTING");
Its golden is the dropped-paint rendering at depth 8. Raising the default makes its hierarchy no longer exceed the limit, so the test stops testing what it was written to test and its golden changes.
If the limit is raised, this test should build its nesting relative to GX_MAX_CONTEXT_NESTING rather than with a hard-coded four levels, so it keeps working at any depth.
Options
(a) Raise the GX_MAX_CONTEXT_NESTING default. 10 is the measured minimum; 12 gives headroom without being generous. sizeof(GX_DRAW_CONTEXT) is 76 bytes in the 32-bit test build, so 8 → 12 costs 304 bytes of static RAM (_gx_system_draw_context_stack is a fixed array), a little more on 64-bit targets. GUIX is deliberately RAM-frugal and this is a public tunable that every existing user inherits, so the number is a product decision. Requires regenerating six frames of guix_all_widgets_accordion_menu and reworking guix_widget_children_draw.
(b) Reduce the demand instead of raising the supply. Two contexts per accordion level is the cost driver: the accordion widget and then its menu_list. If _gx_accordion_menu_draw() could draw its menu_list children without an intermediate context for the menu_list itself, three nested accordions would fit in the existing 8. This is a real change to widget drawing and has not been prototyped.
(c) Make the failure visible. Whatever else is done, silently discarding a subtree is the worst part of this. _gx_system_error_process() is already called but is inert by default. At minimum the limitation belongs in the documentation for GX_MAX_CONTEXT_NESTING, with the arithmetic — two contexts for the refresh plus one per widget-tree level — so an application author can compute the depth their UI needs. Docs live in rtos-docs-asciidoc.
(c) is cheap and independent of the others. (a) is the straightforward fix and is well-contained. (b) is the more ambitious one.
A note on regenerating those six frames
This is the one case in this cluster of defects where regenerating golden data is correct rather than a shortcut — the recorded frames capture a rendering bug. It is worth saying so explicitly in whatever PR does it, because the surrounding work (#176, #177) turned on the opposite conclusion: there, frames differed because the code had regressed, and regenerating would have cemented the regression. The distinction is whether the golden or the code is wrong, and here it is the golden.
Reproduction
cd test/guix_test/cmake
# baseline
cmake --build build/default_build_coverage --target guix_all_widgets_accordion_menu
build/default_build_coverage/regression/guix_all_widgets_accordion_menu \
-checksum -gpath ../../golden_files/ # Frame 12 is different (pre-#176)
# raise the limit and repeat
sed -i 's/^#define GX_MAX_CONTEXT_NESTING 8$/#define GX_MAX_CONTEXT_NESTING 12/' \
../../../common/inc/gx_api.h
cmake --build build/default_build_coverage --target guix_all_widgets_accordion_menu
build/default_build_coverage/regression/guix_all_widgets_accordion_menu \
-checksum -gpath ../../golden_files/ # Frames 15-20 are different
Restore common/inc/gx_api.h afterwards, then touch it and rebuild the whole configuration. Editing a header between single-target builds leaves objects compiled against the old header, which shows up as nonsensical assertion values.
Related
The multi-level accordion screen of the shipped
all_widgetsdemo needs a draw-context depth of 10.GX_MAX_CONTEXT_NESTINGdefaults to 8 (common/inc/gx_api.h:206). When the stack runs out, the child widget and its whole subtree are never painted, and nothing tells the application.This predates
8a35f690(#158) and is unrelated to it.The defect
_gx_widget_children_draw()pushes one draw context per level of the widget tree, and_gx_system_canvas_refresh()pushes two before the tree is walked (gx_system_canvas_refresh.c:333for the root window,:373for the dirty widget). On overflow,_gx_canvas_drawing_initiate()returnsGX_DRAW_NESTING_EXCEEDEDandgx_widget_children_draw.c:103— correctly, by its own contract — skips the child entirely:_gx_system_error_process(GX_DRAW_NESTING_EXCEEDED)is called, but in the default configuration that is a no-op: the callback path is behind bothGX_DISABLE_THREADX_BINDINGandGX_ENABLE_ERROR_CALLBACK, and no test configuration defines either. So a subtree silently disappears.The measurement
One run of
guix_all_widgets_accordion_menu, breakpoint on thereturn GX_DRAW_NESTING_EXCEEDED;ingx_canvas_drawing_initiate.c:_gx_widget_children_draw_gx_multi_line_text_view_text_drawThe single MLTV overflow was #176's subject and is fixed. The 67 are this defect.
Peak depth required, from a scratch build with
GX_MAX_CONTEXT_NESTINGraised to 64 and a running maximum instrumented into_gx_canvas_drawing_initiate():The widget chain that exhausts the stack, printed from the overflowing widget upwards:
Each accordion level costs two contexts — the accordion widget and its
menu_listchild. Three nested accordion levels plusmenu_screenplus the two the refresh already holds is 9; the deepest leaf makes 10.Six golden frames record incompletely rendered output
Raising the limit and re-running against the unmodified golden data:
GX_MAX_CONTEXT_NESTINGFrames 15–20 match the golden only while the paint is being dropped, and stop matching as soon as the children are actually drawn. Those are menu clicks 13 through 18. Frame 12 behaves the opposite way — it matches once nothing overflows, which is what proved #176's fix was needed rather than a golden regeneration.
Repository-wide cost of raising the default, measured on
default_build_coverageatGX_MAX_CONTEXT_NESTING = 12, before #176 and #177 landed:The third is now fixed, so two tests need attention. Worth re-measuring on current
devbefore acting, since that sweep predates both #176 and #177.guix_widget_children_drawexists to test the limittest/guix_test/regression_test/tests/validation_guix_widget_children_draw.ccreates four nested prompts and captures one frame, commented:Its golden is the dropped-paint rendering at depth 8. Raising the default makes its hierarchy no longer exceed the limit, so the test stops testing what it was written to test and its golden changes.
If the limit is raised, this test should build its nesting relative to
GX_MAX_CONTEXT_NESTINGrather than with a hard-coded four levels, so it keeps working at any depth.Options
(a) Raise the
GX_MAX_CONTEXT_NESTINGdefault. 10 is the measured minimum; 12 gives headroom without being generous.sizeof(GX_DRAW_CONTEXT)is 76 bytes in the 32-bit test build, so 8 → 12 costs 304 bytes of static RAM (_gx_system_draw_context_stackis a fixed array), a little more on 64-bit targets. GUIX is deliberately RAM-frugal and this is a public tunable that every existing user inherits, so the number is a product decision. Requires regenerating six frames ofguix_all_widgets_accordion_menuand reworkingguix_widget_children_draw.(b) Reduce the demand instead of raising the supply. Two contexts per accordion level is the cost driver: the accordion widget and then its
menu_list. If_gx_accordion_menu_draw()could draw itsmenu_listchildren without an intermediate context for themenu_listitself, three nested accordions would fit in the existing 8. This is a real change to widget drawing and has not been prototyped.(c) Make the failure visible. Whatever else is done, silently discarding a subtree is the worst part of this.
_gx_system_error_process()is already called but is inert by default. At minimum the limitation belongs in the documentation forGX_MAX_CONTEXT_NESTING, with the arithmetic — two contexts for the refresh plus one per widget-tree level — so an application author can compute the depth their UI needs. Docs live inrtos-docs-asciidoc.(c) is cheap and independent of the others. (a) is the straightforward fix and is well-contained. (b) is the more ambitious one.
A note on regenerating those six frames
This is the one case in this cluster of defects where regenerating golden data is correct rather than a shortcut — the recorded frames capture a rendering bug. It is worth saying so explicitly in whatever PR does it, because the surrounding work (#176, #177) turned on the opposite conclusion: there, frames differed because the code had regressed, and regenerating would have cemented the regression. The distinction is whether the golden or the code is wrong, and here it is the golden.
Reproduction
Restore
common/inc/gx_api.hafterwards, thentouchit and rebuild the whole configuration. Editing a header between single-target builds leaves objects compiled against the old header, which shows up as nonsensical assertion values.Related
_gx_multi_line_text_view_text_drawoverflow in the same test. The 67_gx_widget_children_drawoverflows are this issue.GX_NO_VIEWSrather thanGX_DRAW_NESTING_EXCEEDED).