Skip to content

Fix iogate.getc input byte order shuffled bug#891

Open
tompng wants to merge 1 commit intoruby:masterfrom
tompng:fix_iogate_getc
Open

Fix iogate.getc input byte order shuffled bug#891
tompng wants to merge 1 commit intoruby:masterfrom
tompng:fix_iogate_getc

Conversation

@tompng
Copy link
Copy Markdown
Member

@tompng tompng commented Mar 27, 2026

Fixes this bug:
image
Sometimes, something like "[10;16R" is input to reline, very rarely when a computer(macOS) wakes up from sleep mode, in Terminal.app. I think additional situation (external display triggers WIHCN) is needed to reproduce.

Here's a timeline of a situation that this bug happens. (found by inserting many debug log output to ansi.rb)

  1. Reline calls IOGate.getc
  2. WINCH signal is received more than 3 times with more than 0.01s intervals
  3. IOGate.cursor_pos is called for each WINCH signal. Reline sends "\e[6n" (CPR, Cursor Position Request) and waits for 0.5sec
  4. First cursor_pos timed out
  5. Second cursor_pos timed out
  6. Third cursor_pos receives "\e[1;1R\e[2;2R" (response to the first and second CPR)
  7. IOGate consumes first "\e[1;1R" and put "\e[2;2R" to @buf
  8. IOGate reads "\e", the leading byte of "\e[3;3R" (response to the third CPR)
  9. Reline reads "\e\e[2;2R", resolved to ALT_KEY + unbound CSI escape sequence
  10. Reline reads "3;3R", resolved to normal ascii key input

Input to Reline is "\e[1;1R\e[2;2R\e[3;3R" but Reline thinks the input is "\e[1;1R\e\e[2;2R[3;3R".

Fix: Always check @buf.empty? after calling Reline.core.line_editor.handle_signal

When iogate.cursor_pos is called while handling signal inside iogate.getc, getc return bytes in wrong order.
We need to always check `@buf` after `Reline.core.line_editor.handle_signal` is called.
@tompng
Copy link
Copy Markdown
Member Author

tompng commented Mar 27, 2026

Failed irb head ubuntu-latest job contains irb(main):002* [40;6R'foo'.
It may look like the bug hasn't been fixed, but I think that's not actually the case.

Failure: test_show_cmds_with_pager_can_quit_with_ctrl_c(IRB::RenderingTest::TestVTerm):
  </foobar/> was expected to be =~
  <"Multi-irb (DEPRECATED)\n" +
  "'foo' + 'bar'    Start a child IRB.\n" +
  "irb(main):002* [40;6R'foo' + 'bar'\n" +

What happened is:

  1. Pager starts
  2. \C-c is sent to IRB
  3. Pager receives SIGTERM sent by IRB. IRB thinks pager has stopped
  4. Reline tries to read next input with raw stdin mode, start retrieving cursor position
  5. Pager terminates. Pager change stdin mode from raw to cooked before termination
  6. Cursor position report "\e[40;6R" is sent in cooked mode, "\e" is somehow gone.

I'll fix IRB adding Process.wait pager_pid

@tompng
Copy link
Copy Markdown
Member Author

tompng commented Mar 27, 2026

To make a similar situation, type this to IRB. Reproducible in Reline >= 0.6.1

Thread.new{
  sleep 0.5;
  stdin = Reline::IOGate.instance_eval{@input}
  "\e[1;1R\e[2;2R".bytes.reverse_each{stdin.ungetc _1}
  Process.kill :WINCH, $$;
}
$ irb
irb(main):001* Thread.new{
irb(main):002*   sleep 0.5;
irb(main):003*   stdin = Reline::IOGate.instance_eval{@input}
irb(main):004*   "\e[1;1R\e[2;2R".bytes.reverse_each{stdin.ungetc _1}
irb(main):005*   Process.kill :WINCH, $$;
irb(main):006> }
=> #<Thread:0x000000013563c7f8 (irb):1 run>
irb(main):007* [43;16R

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant