Subject: Re: bug#76186: 31.0.50; (recenter 0) sometimes does not recenter as expected

From: Stephen Berman <[email-masked]>
Date: Sat, 22 Feb 2025 22:06:18 +0100

On Sat, 22 Feb 2025 16:05:19 +0200 Eli Zaretskii <[email-masked]> wrote:

>> From: Stephen Berman <[email-masked]>
>> Cc: [email-masked],  [email-masked]
>> Date: Sat, 22 Feb 2025 14:46:34 +0100
>>
>> On Sat, 22 Feb 2025 15:32:28 +0200 Eli Zaretskii <[email-masked]> wrote:
[...]
>> > Some parts of what you describe seem to indicate that the problem is
>> > not with 'recenter' itself, but with the following redisplay cycle,
>> > which, for some reason, considers the window-start position
>> > inappropriate and recenters point on display.  But I have no idea why,
>> > and it is impossible to debug this when the problem happens as part of
>> > 500 iterations.
>>
>> I agree, and the apparent ineffectiveness of Edebug in this case is also
>> frustrating.  I could try using gdb with the test configuration where I
>> consistly get the "unexpected recentering" after one iteration; can you
>> suggest where to set the break point and any other input that may be
>> helpful?
>
> 'recenter' takes effect in two parts: first Emacs determines where to
> put window-start, and then redisplay redraws the window according to
> that.
>
> The first part is in window.c, in Frecenter, where we have this:
>
>   /* Set the new window start.  */
>   set_marker_both (w->start, w->contents, charpos, bytepos);
>
> So first we should determine whether this code determines the
> window-start point as expected.  If not, the reason is inside the
> implementation of 'recenter'.
>
> The second part is in xdisp.c:redisplay_window, where it attempts to
> obey the optional_new_start flag of the window.  It starts here:
>
>   /* If someone specified a new starting point but did not insist,
>      check whether it can be used.  */
>   if ((w->optional_new_start || window_frozen_p (w))
>       && CHARPOS (startp) >= BEGV
>       && CHARPOS (startp) <= ZV)
>     {
>       ptrdiff_t it_charpos;
>
>       w->optional_new_start = false;
>
> This code should set the w->force_start flag.  Does it?  If not, what
> prevents that?
>
> Next, we have this:
>
>  force_start:
>
>   /* Handle case where place to start displaying has been specified,
>      unless the specified location is outside the accessible range.  */
>   if (w->force_start)
>     {
>
> The code after this tries to use the specified window-start point, and
> the question is: why it eventually rejects it (as what you see seems
> to suggest)?
>
> HTH
>
> P.S. It could be that the problem is entirely elsewhere, so don't be
> surprised if you step through all of that code and find that Emacs
> does indeed succeed to use the window-start point the scenario expects
> it to use.

Thanks for the guidance.  Here is the gdb session transcript produced
for the test configuration where the first and third invocations of
`redisplay' are commented out and only the second invocation is
executed, with the result that, in all tests I've made with this
configuration, the loop stops immediately after one iteration (one test
series) with the message "window-start=671, point-max=793 in run 1" and
the same display as after the loop stops when moving the mouse over the
frame, i.e. a case of "unexpected recentering":

steve [ ~/build/emacs-master/src ]$ gdb ./emacs
GNU gdb (GDB) 15.2
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./emacs...
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) [answered Y; input not from terminal]
DISPLAY = :0.0
TERM = dumb
Breakpoint 1 at 0x14f8d1: file /home/steve/src/emacs/emacs-master/src/emacs.c, line 424.
Breakpoint 2 at 0x113d4e: file /home/steve/src/emacs/emacs-master/src/xterm.c, line 27089.
(gdb) break window.c:7172
Breakpoint 3 at 0xbc850: file /home/steve/src/emacs/emacs-master/src/window.c, line 7172.
(gdb) break xdisp.c:20269
Breakpoint 4 at 0x5555555f90c6: file /home/steve/src/emacs/emacs-master/src/xdisp.c, line 20269.
(gdb) run -Q -l ~/comp/Emacs/bug#76186.el
Starting program: /home/steve/build/emacs-master/src/emacs -Q -l ~/comp/Emacs/bug#76186.el
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
[New Thread 0x7fffdffd86c0 (LWP 16527)]
[New Thread 0x7fffdf6486c0 (LWP 16528)]
[New Thread 0x7fffdecb86c0 (LWP 16529)]
[New Thread 0x7fffde1706c0 (LWP 16530)]
[New Thread 0x7fffdd7366c0 (LWP 16531)]

Thread 1 "emacs" hit Breakpoint 3, Frecenter (arg=<optimized out>,
    redisplay=<optimized out>)
    at /home/steve/src/emacs/emacs-master/src/window.c:7172
7172	  set_marker_both (w->start, w->contents, charpos, bytepos);
(gdb) n
7178	  w->vscroll = 0;
(gdb) p w->start
$2 = XIL(0x555555d2419d)
(gdb) pr $2
[Thread 0x7fffde1706c0 (LWP 16530) exited]
#<marker at 793 in sample>
(gdb) c
Continuing.

Thread 1 "emacs" hit Breakpoint 4, redisplay_window (window=XIL(0x555555cdd175),
    just_this_one_p=just_this_one_p@entry=false)
    at /home/steve/src/emacs/emacs-master/src/xdisp.c:20269
20269	  if ((w->optional_new_start || window_frozen_p (w))
(gdb) n
20275	      w->optional_new_start = false;
(gdb) p w->force_start
$3 = false
(gdb) n
20276	      if (!w->force_start)
(gdb)
20278		  start_display (&it, w, startp);
(gdb)
20279		  move_it_to (&it, PT, 0, it.last_visible_y, -1,
(gdb)
20283		  it_charpos = IT_CHARPOS (it);
(gdb)
20289		  if (it.current_y == 0 || line_bottom_y (&it) < it.last_visible_y)
(gdb)
20291		      if (it_charpos == PT)
(gdb)
20292			w->force_start = true;
(gdb) p w->force_start
$4 = false
(gdb) n
20309	  if (!BASE_LINE_NUMBER_VALID_P (w))
(gdb) p w->force_start
$5 = true
(gdb) n
20317	  if (w->force_start)
(gdb)
20322	      w->force_start = false;
(gdb)
20327	      if (!w->preserve_vscroll_p && !window_frozen_p (w))
(gdb)
20328		w->vscroll = 0;
(gdb)
20330	      w->preserve_vscroll_p = false;
(gdb)
20331	      w->window_end_valid = false;
(gdb)
20340	      if (!update_mode_line
(gdb)
20348	      if (CHARPOS (startp) < BEGV)
(gdb)
20350	      else if (CHARPOS (startp) > ZV)
(gdb) p ZV
$6 = 793
(gdb) n
20355	      if (!window_start_acceptable_p (window, CHARPOS (startp)))
(gdb) p CHARPOS (startp)
$7 = <optimized out>
(gdb) n
20363	      clear_glyph_matrix (w->desired_matrix);
(gdb)
20364	      if (!try_window (window, startp, 0))
(gdb)
20371	      if (w->cursor.vpos < 0)
(gdb)
20403	      if (!cursor_row_fully_visible_p (w, false, false, false))
(gdb)
20421	      else if (w->cursor.vpos >= 0)
(gdb)
20426	          int pixel_margin = margin * frame_line_height;
(gdb) p w->cursor.vpos
$8 = 0
(gdb) n
20427		  bool tab_line = window_wants_tab_line (w);
(gdb)
20428		  bool header_line = window_wants_header_line (w);
(gdb)
20434		  if (w->cursor.vpos < margin + tab_line + header_line)
(gdb)
20442		      int window_height = window_box_height (w);
(gdb)
20444		      if (tab_line)
(gdb)
20446		      if (header_line)
(gdb)
20448		      if (w->cursor.y >= window_height - pixel_margin)
(gdb)
20459	      if (new_vpos >= 0)
(gdb)
20502	      if (w->cursor.vpos < 0
(gdb)
21017	  SET_TEXT_POS_FROM_MARKER (startp, w->start);
(gdb) p startp
$9 = <optimized out>
(gdb) p w->start
$10 = XIL(0x555555d2419d)
(gdb) pr $10
#<marker at 793 in sample>
(gdb) n
21018	  w->start_at_line_beg = (CHARPOS (startp) == BEGV
(gdb)
21022	  if ((update_mode_line
(gdb) p w->start_at_line_beg
$11 = true
(gdb) n
21040	      specpdl_ref count1 = SPECPDL_INDEX ();
(gdb)
21042	      specbind (Qinhibit_quit, Qt);
(gdb)
21043	      display_mode_lines (w);
(gdb)
21044	      unbind_to (count1, Qnil);
(gdb)
21048	      if (window_wants_mode_line (w)
(gdb)
21059	      if (window_wants_tab_line (w)
(gdb)
21070	      if (window_wants_header_line (w)
(gdb)
21079	      if (f->fonts_changed)
(gdb)
21083	  if (!line_number_displayed && w->base_line_pos != -1)
(gdb)
21093	  if (update_mode_line
(gdb)
21098	      if (FRAME_WINDOW_P (f))
(gdb)
21101		  redisplay_menu_p = FRAME_EXTERNAL_MENU_BAR (f);
(gdb)
21109	      if (redisplay_menu_p)
(gdb)
21110	        display_menu_bar (w);
(gdb)
21113	      if (FRAME_WINDOW_P (f))
(gdb)
21115		  if (WINDOWP (f->tab_bar_window)
(gdb)
21122		  if (FRAME_EXTERNAL_TOOL_BAR (f))
(gdb)
21123		    update_frame_tool_bar (f);
(gdb)
21138	      gui_consider_frame_title (w->frame);
(gdb)
21146	  if (FRAME_WINDOW_P (f)
(gdb)
21164	  if (WINDOW_BOTTOM_DIVIDER_WIDTH (w))
(gdb)
493	  return XUNTAG (a, Lisp_Vectorlike, struct window);
(gdb)
1623	  return frame_dimension (f->bottom_divider_width);
(gdb)
21176	   if (WINDOW_HAS_VERTICAL_SCROLL_BAR (w) || WINDOW_HAS_HORIZONTAL_SCROLL_BAR (w))
(gdb)
21178	      if (WINDOW_HAS_VERTICAL_SCROLL_BAR (w))
(gdb)
21180		set_vertical_scroll_bar (w);
(gdb)
21182	      if (WINDOW_HAS_HORIZONTAL_SCROLL_BAR (w))
(gdb)
21188	      if (FRAME_TERMINAL (f)->redeem_scroll_bar_hook)
(gdb)
21189	        (*FRAME_TERMINAL (f)->redeem_scroll_bar_hook) (w);
(gdb)
21195	  if (CHARPOS (opoint) < BEGV)
(gdb)
21197	  else if (CHARPOS (opoint) > ZV)
(gdb)
21199	  else if (ochars_modiff == CHARS_MODIFF)
(gdb)
21200	    TEMP_SET_PT_BOTH (CHARPOS (opoint), BYTEPOS (opoint));
(gdb)
21211	  set_buffer_internal_1 (old);
(gdb)
21214	  if (CHARPOS (lpoint) <= ZV)
(gdb)
21216	      if (lchars_modiff == CHARS_MODIFF)
(gdb)
21217		TEMP_SET_PT_BOTH (CHARPOS (lpoint), BYTEPOS (lpoint));
(gdb)
21222	  unbind_to (count, Qnil);
(gdb)
redisplay_window_0 (window=window@entry=XIL(0x555555cdd175))
    at /home/steve/src/emacs/emacs-master/src/xdisp.c:18126
18126	  return Qnil;
(gdb) c
Continuing.

Thread 1 "emacs" hit Breakpoint 4, redisplay_window (window=XIL(0x555555cdd175),
    just_this_one_p=just_this_one_p@entry=false)
    at /home/steve/src/emacs/emacs-master/src/xdisp.c:20269
20269	  if ((w->optional_new_start || window_frozen_p (w))
(gdb) n
20309	  if (!BASE_LINE_NUMBER_VALID_P (w))
(gdb) p w->force_start
$12 = false
(gdb) n
20311	    w->base_line_number = 0;
(gdb)
20317	  if (w->force_start)
(gdb)
20520	  if (current_matrix_up_to_date_p
(gdb)
20540	  else if (w->start_at_line_beg
(gdb)
20556	  else if ((tem = try_window_id (w)) != 0)
(gdb)
20570	  else if (CHARPOS (startp) >= BEGV
(gdb)
20677	  if (!update_mode_line)
(gdb)
20684	  if ((0 < scroll_conservatively
(gdb)
20732	  init_iterator (&it, w, PT, PT_BYTE, NULL, DEFAULT_FACE_ID);
(gdb)
20733	  it.current_y = it.last_visible_y;
(gdb)
20734	  if (centering_position < 0)
(gdb)
20736	      ptrdiff_t margin_pos = CHARPOS (startp);
(gdb)
20742	      if (margin
(gdb)
20760	      scrolling_up = PT > margin_pos;
(gdb)
20761	      aggressive =
(gdb)
20766	      if (!MINI_WINDOW_P (w)
(gdb)
20805		centering_position = window_box_height (w) / 2;
(gdb)
20807	  if (current_buffer->long_line_optimizations_p
(gdb)
20820	    move_it_vertically_backward (&it, centering_position);
(gdb)
20829	  if (it.current_y <= 0)
(gdb)
20836	  it.current_x = it.wrap_prefix_width = it.hpos = 0;
(gdb)
20841	  set_marker_both (w->start, Qnil, IT_CHARPOS (it), IT_BYTEPOS (it));
(gdb)
20844	  startp = run_window_scroll_functions (window, it.current.pos);
(gdb)
20849	  itdata = bidi_shelve_cache ();
(gdb)
20853	  if (!current_matrix_up_to_date_p
(gdb)
20863	    use_desired_matrix = (try_window (window, startp, 0) == 1);
(gdb)
20865	  bidi_unshelve_cache (itdata, false);
(gdb)
20870	  if (f->fonts_changed)
(gdb)
20878	  if (w->cursor.vpos < 0)
(gdb)
20924	  if (w->cursor.vpos < 0)
(gdb)
20977	  if (!cursor_row_fully_visible_p (w, false, false, false))
(gdb)
21017	  SET_TEXT_POS_FROM_MARKER (startp, w->start);
(gdb)
21018	  w->start_at_line_beg = (CHARPOS (startp) == BEGV
(gdb) p w->start
$13 = XIL(0x555555d2419d)
(gdb) pr $13
#<marker at 671 in sample>
(gdb) p ZV
$14 = 793
(gdb) c
Continuing.

Thread 1 "emacs" hit Breakpoint 4, redisplay_window (window=XIL(0x555555cdd175),
    just_this_one_p=just_this_one_p@entry=false)
    at /home/steve/src/emacs/emacs-master/src/xdisp.c:20269
20269	  if ((w->optional_new_start || window_frozen_p (w))
(gdb) c
Continuing.

Thread 1 "emacs" hit Breakpoint 4, redisplay_window (window=XIL(0x555555cdd175),
    just_this_one_p=just_this_one_p@entry=false)
    at /home/steve/src/emacs/emacs-master/src/xdisp.c:20269
20269	  if ((w->optional_new_start || window_frozen_p (w))
(gdb) c
Continuing.

Thread 1 "emacs" received signal SIGPIPE, Broken pipe.
0x00007ffff31407ef in __GI___libc_write (fd=13, buf=0x555555d07070, nbytes=568)
    at ../sysdeps/unix/sysv/linux/write.c:26
warning: 26	../sysdeps/unix/sysv/linux/write.c: No such file or directory
(gdb)
Continuing.
[Thread 0x7fffdd7366c0 (LWP 16531) exited]
[Thread 0x7fffdf6486c0 (LWP 16528) exited]
[Thread 0x7fffdffd86c0 (LWP 16527) exited]
[Thread 0x7fffefe110c0 (LWP 16526) exited]
[Thread 0x7fffdecb86c0 (LWP 16529) exited]
[New process 16526]
[Inferior 1 (process 16526) exited normally]
(gdb)

As you can see, after hitting the window.c breakpoint and stepping over
set_marker_both, window-start is at 793, i.e. point-max, which is the
expected position after invoking `(recenter 0)'.  After continuing and
hitting the xdisp.c breakpoint, I stepped through the code and saw
w->force_start get set to true, then stepped further until
redisplay_window_0, then continued and hit the breakpoint again, and now
w->force_start was false.  Then I stepped further until
SET_TEXT_POS_FROM_MARKER (startp, w->start), after which window-start
was now at 671, the value displayed in the message after one iteration,
indicating the unexpected recentering.  I cannot tell what caused
window-start to change from 793 to 671.

Steve Berman