Repository navigation
[Linux] Cancel active frame wait and defer GTK window destruction during multi-window teardown - #193126
[Linux] Cancel active frame wait and defer GTK window destruction during multi-window teardown#193126jtmcdole wants to merge 1 commit into
Conversation
|
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
Gemini suggested changes (investigating):
After #192137 ( Under Additionally, API References
Solution
|
|
This still failed - but the X11 tests are passing. Now the failure is in 2 tests that require a window manager on the headless bot. @robert-ancell - can you help out here? This is our last |
There was a problem hiding this comment.
Code Review
This pull request addresses OpenGL context state desynchronization under libglvnd by clearing the GdkGLContext before making the platform context current. Key changes include updating fl_opengl_manager_make_platform_current to clear the current GdkGLContext, clearing contexts in the compositor and shader, and adding corresponding unit tests. Feedback highlights a temporary DO NOT SUBMIT flag in .ci.yaml, warns of potential side effects from clearing the OpenGL context inside the setup_shader helper, and identifies a potential memory leak in the test due to overwriting the fixture's compositor member.
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, | ||
| GL_STATIC_DRAW); | ||
|
|
||
| fl_opengl_manager_clear_current(self->opengl_manager); |
There was a problem hiding this comment.
Clearing the OpenGL context inside a helper function like setup_shader can lead to unexpected side effects if the caller expects the context to remain current, or if the caller is responsible for managing the context lifecycle. It is generally cleaner and safer to have the caller (which made the context current) also clear it.
References
- Optimize for readability and maintainability by keeping context management balanced and predictable. (link)
I think this CI is all Google specific? So I'm not sure how it works. Looking at |
Thanks, I'll check in the morning if openbox is available or can be made available on the machines. One step closer to no bringup:true. |
7c91ba9 to
0e0e5e6
Compare
|
Adding some code to bring up |
|
Hit some built in flakes. (2 failures -> pass). still investigating but my longer term view is:
|
|
OK - trying to deflake this test by running it 16 times. I will say that this is very AI-agent delivered.
Summary
|
Well, that's nice. |
498dedd to
5353edd
Compare
| wmProcess = await startProcess('openbox', const <String>[ | ||
| '--sm-disable', | ||
| '--startup', | ||
| 'echo $readyMarker', | ||
| ]); |
There was a problem hiding this comment.
openbox is the windowmanager used by xvfb.py testing in the engine; it is available on the luci bots.
|
|
||
| // Determine once whether glBlitFramebuffer is available on this driver. | ||
| fl_opengl_manager_make_current(opengl_manager); | ||
| fl_opengl_manager_make_platform_current(opengl_manager); |
There was a problem hiding this comment.
This looks like a bug that was using the wrong context... switched to platform context instead
|
@robert-ancell / @mattkae - I believe this is ready for review. I am not a GTK expert though and would appreciate feedback. I ran ~100 tests on our CI and they were all non-flakey with these changes. |
5353edd to
91c7c66
Compare
|
force push to merge conflicting changes at master. |
robert-ancell
left a comment
There was a problem hiding this comment.
There's a lot going on in this PR. I think cherry picking out the individual fixes for the Linux embedder is the safer way to be sure on these and make them easier to understand and bisect on if we have future issues. If you don't want to do that I'm happy to do those and then this PR should slim right down to just enabling the tests on CI.
I have the three commits that I could stack. I'll do that and sent them out individually. |
91c7c66 to
d2df67d
Compare
d2df67d to
c27b022
Compare
c27b022 to
0b76221
Compare
…ing multi-window teardown When tearing down an active secondary window on Linux: 1. Calling gtk_widget_destroy() synchronously from a Dart platform task running inside fl_view_renderer_opengl_draw() -> wait_for_frame() -> fl_task_runner_wait() unrealized the GdkWindow and freed the GtkWindow while gtk_widget_draw_internal() was still active on the C call stack. Add fl_view_begin_destroy() and fl_view_renderer_opengl_cancel_wait() to abort wait_for_frame() immediately, and schedule gtk_widget_destroy() on the GLib main loop in fl_view_monitor_dispose() once the draw stack unwinds. 2. In BaseWindowControllerLinux.destroy(), unreference FlViewMonitor and FlWindowMonitor before closing their Dart NativeCallables so that GTK signals emitted during teardown do not invoke closed callbacks. 3. Suppress listener notifications in _ToplevelWindowControllerLinux once _destroyed is true to prevent window manager signals (such as notify::is-active) from triggering rebuilds on destroyed views.
0b76221 to
34067b1
Compare
mattkae
left a comment
There was a problem hiding this comment.
Small points, but looks good overall! Deferring to @robert-ancell for the final review
| if (FL_IS_VIEW_RENDERER_OPENGL(self->renderer)) { | ||
| fl_view_renderer_opengl_cancel_wait( | ||
| FL_VIEW_RENDERER_OPENGL(self->renderer)); | ||
| } |
There was a problem hiding this comment.
Instead of this check, should we just define fl_view_renderer_cancel_wait on each renderer, and then have it be a cancel for the GL renderer? Just thinking it might be a better pattern instead of basically static_casting
| final VoidCallback? onDestroy = _onDestroy; | ||
| _onDestroy = null; | ||
| onDestroy?.call(); |
There was a problem hiding this comment.
Can you leave a comment as to why you're capturing this first?
When tearing down an active secondary window on Linux:
Calling
gtk_widget_destroy()synchronously from a Dart platform task running insidefl_view_renderer_opengl_draw()->wait_for_frame()->fl_task_runner_wait()unrealized theGdkWindowand freed theGtkWindowwhilegtk_widget_draw_internal()was still active on the C call stack. Addfl_view_begin_destroy()andfl_view_renderer_opengl_cancel_wait()to abortwait_for_frame()immediately, and schedulegtk_widget_destroy()on the GLib main loop infl_view_monitor_dispose()once the draw stack unwinds.In
BaseWindowControllerLinux.destroy(), unreferenceFlViewMonitorandFlWindowMonitorbefore closing their DartNativeCallables so that GTK signals emitted during teardown do not invoke closed callbacks.Suppress listener notifications in
_ToplevelWindowControllerLinuxonce_destroyedis true to prevent window manager signals (such asnotify::is-active) from triggering rebuilds on destroyed views.sequenceDiagram participant Dart as Dart Isolate (GTK Thread) participant GTK as GTK3 / GLib Main Loop (GTK Thread) participant Raster as Flutter Engine Raster Thread rect Note over GTK,Raster: Phase 2: Frame Presentation & Re-entrant Draw Wait Raster->>Raster: get_renderable(view_id) [acquires g_object_ref(FlView)] Raster->>Raster: present_layers(): lock frame_mutex, composite into self->frame Raster->>GTK: fl_task_runner_stop_wait() + g_idle_add(redraw_cb) Raster->>Raster: g_object_unref(FlView) GTK->>GTK: gtk_widget_draw_internal() -> fl_view_renderer_opengl_draw() GTK->>GTK: wait_for_frame() -> fl_task_runner_wait() [pumps Dart tasks while draw is on stack!] end rect Note over Dart,GTK: Phase 3: Window Destroy Called Inside fl_task_runner_wait() Dart->>GTK: controller.destroy() -> _viewMonitor.unref() GTK->>GTK: fl_view_monitor_dispose() -> fl_view_begin_destroy() GTK->>GTK: fl_view_renderer_opengl_cancel_wait(): sets self->destroyed = TRUE & wakes task_runner GTK->>GTK: Queues g_idle_add_full(G_PRIORITY_HIGH, destroy_toplevel_cb, g_object_ref(toplevel)) GTK->>GTK: fl_window_monitor_dispose(): disconnects GTK signals Dart->>Dart: _notifyWindowDestroyed() -> dialogController.value = null GTK->>GTK: wait_for_frame() resumes, sees self->destroyed == TRUE, breaks immediately! GTK->>GTK: fl_view_renderer_opengl_draw() returns FALSE cleanly to gtk_widget_draw_internal() end rect Note over GTK,Raster: Phase 4: Deferred Unrealize, Dispose & Finalize GTK->>GTK: destroy_toplevel_cb() runs on clean main loop stack -> gtk_widget_destroy(toplevel) GTK->>GTK: cleanup_opengl_resources(): locks frame_mutex, clears render_context (if current), frees compositor & frame GTK->>GTK: fl_view_finalize() & fl_view_renderer_opengl_finalize(): frees task_runner & frame_mutex endfixes: #192653
Part of stack #193534.
Pre-launch Checklist
///).