moveHandler: Remove the monitor grace period timer when a grab ends - #461
Conversation
aleasto
left a comment
There was a problem hiding this comment.
Additionally guard the callback with window.get_compositor_private(). global.display.is_grabbed() is a property of the display and says nothing about this window still being managed; the same idiom is already used in focusHint.js:290.
I don't expect this change to be necessary, since if all goes well _onMoveFinished will be fired to cancel the timer before the window is unmanaged -- but it doesn't hurt to be overly protective so I'm ok with that.
Could we also silence the window.assertExistence() stacktrace dump since we're able to handle it gracefully?
| // is_grabbed() is about the display, not about `window` | ||
| // still being managed. |
There was a problem hiding this comment.
this comment is not helpful. this reasoning could go in a commit message, not in an in-line comment.
here you could say // check that the window still exists, and a grab is still active
There was a problem hiding this comment.
Done, and the rationale moved to the commit message.
2ae1f71 to
10693e0
Compare
When a drag crosses a monitor boundary, _edgeTilingPreview() arms a 150 ms timer that captures the dragged window and re-enters _edgeTilingPreview() when it fires. Nothing removes that source when the grab ends, so a window destroyed within those 150 ms leaves it pending. It then reaches window.get_work_area_for_monitor(), which asserts in meta_window_get_workspaces() on an unmanaging window and aborts the shell. Remove the pending source in the finally block of _onMoveFinished(), and guard the callback with get_compositor_private(), since is_grabbed() is a property of the display rather than of the window. Closes ubuntu#435
10693e0 to
c799dbb
Compare
|
Rebased on current The For what it's worth, that dump isn't much of a signal: my journal has ~180 of those And you're right that |
There was a problem hiding this comment.
🟢 Approval recommended
The cleanup and guard directly address the crash, with only a minor inaccurate comment remaining.
Pull request overview
Prevents stale monitor grace-period callbacks from accessing destroyed windows after drag operations.
Changes:
- Removes pending monitor timers when a grab ends.
- Guards timer callbacks against unmanaged windows.
- Suppresses expected destroyed-window errors during cleanup.
File summaries
| File | Description |
|---|---|
moveHandler.js |
Safely cleans up monitor-lock timers and validates callback windows. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| _onMoveFinished(window) { | ||
| try { | ||
| window.assertExistence(); | ||
| // Nested so that the finally block still runs for a destroyed window |
_onMoveFinished() is a try/finally with no catch, so the Error thrown by window.assertExistence() escapes the grab-op-end handler and GJS dumps a stack trace, even though the finally block already handles the case. Catch it around the assertion only, so errors from the tiling code below are still reported, like _onWindowWorkspaceChanged() does.
c799dbb to
730fdd9
Compare
|
Thank you! |
Fixes the GNOME Shell abort reported in #435, which several of us have hit on Ubuntu 26.04 (@YannickMG, @cpaelzer and myself, plus the original report on Debian 13 / GNOME 48).
The bug
_edgeTilingPreview()arms a 150 ms grace period timer when a drag crosses a monitor boundary, so the tile preview sticks to the previous monitor for a moment:The callback captures
window, but the source is only ever removed indestroy()— never when the grab it belongs to ends._onMoveFinished()'sfinallyblock tears down everything else (_posChangedId, the tile preview,_isGrabOp) and leaves this one pending.So if the dragged window is destroyed within those 150 ms — tearing off or dropping a browser tab does exactly that — the orphaned timer still fires and re-enters
_edgeTilingPreview(), reaching:meta_window_get_work_area_for_monitor()→meta_window_get_workspaces()→g_assert_not_reached()on an unmanaging window →SIGABRT. Being a C assertion, no JS guard can contain it, and the whole Wayland session dies.The
unmanagingJS error logged from_onMoveFinished()in every report is a separate, non-fatal event that merely signals the window is gone. In my logs the two are consistently ~140 ms apart, and on 2026-07-28 that error occurred with no abort at all — the session survived two more days.The fix
finallyblock of_onMoveFinished(), so it cannot outlive the operation that armed it. This is what actually fixes the crash.window.get_compositor_private().global.display.is_grabbed()is a property of the display and says nothing about this window still being managed; the same idiom is already used infocusHint.js:290.Notes
monitor-switch-grace-period falseworks as a user-side workaround on released versions.if (this._lastMonitorNr !== currMonitorNr), which matches every report in Wayland crash report #435 being multi-monitor.finallycleanup and drop the second guard.