fix(layout): end a leaf at its start once the visibility retry budget is spent - #351
Conversation
|
I have borrowed my son's computer (x86_64 architecture) and installed latest Ubuntu LTS in a partition. Will test on that now. (Only 32 GB of RAM, so still not enough computing power) |
eval-exec
left a comment
There was a problem hiding this comment.
Reviewed the diff and the corresponding GNU Emacs xdisp.c buffer-selection and recenter paths. Selecting the window buffer during display motion addresses the incorrect property lookup, and distinguishing semantic starts from measurement probes makes the exhausted-budget handling appropriate for preventing this retry loop. I support this bounded freeze fix with the declared point-visibility limitation. A follow-up should reconcile screen-line motion with the actual rows produced by multiline display replacements so fallback placement can reliably bring point into view. Review scope: source inspection; I have not independently run the regression tests.
|
Independently reproduced the underlying layout hang on Linux using this PR’s regression fixtures against local main (
A debugger sample during the hang showed the layout path repeatedly reaching Scope: this reproduces the engine-level failure on Linux, not the complete interactive Vertico GUI scenario. For the longer-term follow-up, display motion and rendering should share display-row traversal semantics, especially for multiline display replacement strings. Regression coverage should require both bounded completion and point visibility; the documented possibility of finishing with point off-screen remains a separate issue. Thanks for the focused regression fixtures! |
Run display-motion queries in the window's buffer so an unrelated current buffer cannot invalidate property lookups or point placement. Distinguish semantic viewport starts from measurement probes, and terminate exhausted visibility retries at a semantic start instead of repeatedly attempting placement without progress. Add regressions for foreign-current-buffer motion and exhausted visibility budgets. This fixes the retry hang. Reliable point visibility with multiline display replacement strings remains follow-up work.
aa2edea to
6bcce6f
Compare
Summary
Entering the minibuffer with vertico (
M-x) while the selected window was small (a freshly started frame, or one tiled to half the screen) froze the GUI at 100% CPU: no key,C-gorEscwas accepted and the process had to be killed.-Qdid not reproduce; the trigger is a completion UI growing the minibuffer so the selected window (here the dashboard) shrinks under point, plus a display-row-collapsing property between the visible rows and point andscroll-conservatively> 0.A
sampleof the hung process showed the evaluator thread insidelayout_frame_rust_for_purpose_inner, re-laying the same leaf forever with nothing logged (the leaf loop bypasses the 12-retryFrameLayoutCoordinator). Two defects combined:Unbounded leaf retry.
BufferSourceRetryPlan::viewport_resolutionturned aNeedMoreMeasurementdecision with no visibility retries left into aPlaceRelativeToPointrequest; the engine resolved it to the start it already had, zeroed the budget "to stop the chain", and issued aRetryanyway. GNU bounds this search:try_scrollinggives up pastscroll_maxlines (src/xdisp.c:19445-19446, emacs-31.0.90) and, after therecenter:start has been laid out,redisplay_windowre-recenters at most twice beforedone:(xdisp.c:21360-21398).ViewportDecision::resolve_with_budgetnow owns that rule, keyed on the typedViewportAttemptStartthe rows were produced from: a spent budget ends the leaf at a semantic start; a measurement probe, which is never publishable, still falls back to the policy placement, committed with no retries left. The old table also finished on a probe start when a placement request arrived with no budget; that path now places instead.Placement scanned the wrong buffer.
Context::redisplay_start_before_point_by_display_rowswalked the window's buffer while the minibuffer was current; the invisible-newline check incurrent_screen_line_start_with_truncationprobes Lisp positions in the current buffer, so every probe past the minibuffer's end signalledargs-out-of-range, the placement returnedNone, and the fallback was the unchanged start that fed defect 1. GNU reachesrecenter:insideredisplay_windowafterset_buffer_internal_1 (XBUFFER (w->contents))(xdisp.c:20532-20535); the helper now selects the window's buffer for the scan and restores the caller's (thewith_frame_display_contextidiom).Declared divergence: GNU answers
SCROLLING_FAILEDwithrecenter:(xdisp.c:21097-21108) and always ends with a start placed around point. When core display motion cannot improve on the start already laid out, the leaf now ends there and point may be off-window for that redisplay instead of looping. The same terminal rule now applies to the passes whose budget is forced to zero on purpose (scroll replay and one-row transient windows inwindow_render.rs, which previously contradicted their own comments by placing).Tests (red first)
xdisp::tests::redisplay_start_before_point_scans_the_window_buffer_not_the_current_one:Nonebefore,Some(CharPos0(4500))after; also pins that the caller's buffer is restored.engine::tests::point_placement_with_a_foreign_current_buffer_terminates_and_shows_point: reproduces the report (foreign current buffer, hidden span,scroll-conservatively30); hung before both fixes.engine::tests::spent_visibility_budget_ends_the_leaf_at_its_start: pins defect 1 alone with a thirty-rowdisplaystring that the producer lays out as rows while screen-line motion counts it as one line, so every placement clamps back to the window start. Runs the layout on a helper thread; under the old decision table it fails on its 120 s timeout, with the fix it finishes in 0.5 s.viewport_resolution::tests::spent_budget_*andbudget_left_*: the decision table per attempt start and budget.Verification
cargo fmt --checkclean;cargo clippy -p neomacs-layout-engine --testsand-p neovm-corewith no errors.cargo test -p neomacs-layout-engine --liband the corexdisp::/indent::/window::tests with--no-fail-fast, baselined againstmainon the same machine: every test that failed only on this branch passes in isolation; the remaining failures are the run-to-run flaky setmainshows here too (font/glyph/display_row tests).cargo xtask --release,M-xopens both in the fresh frame and after tiling, a heartbeat timer keeps firing, andC-greturns to the dashboard. Nofailed to convergein the log.Not run: Linux, TTY.
🤖 Generated with Claude Code
https://claude.ai/code/session_01B3wJN81Y4miZnPBNY9o1Xv