Re: [PATCH 2/3] Cygwin: console: Fix NOFLSH mode a little
Takashi Yano <[email protected]> Mon, 29 Jun 2026 23:30:17 +0900
| Newsgroups | gmane.os.cygwin.patches |
|---|---|
| Message-ID | <[email protected]> |
Hi Johannes,
Thanks for finding that.
On Sat, 27 Jun 2026 09:27:12 +0200 (CEST)
Johannes Schindelin wrote:
> Hi Takashi, Mark,
>
> I have a fix for the issue below sitting in
> https://github.com/git-for-windows/msys2-runtime/pull/131/commits/0b0976a5e85de52312c17d21f1d3fc41dc572179
> that I should have sent earlier, but I was struggling to find the time to
> validate the fix via automated AutoHotKey-based tests. Sorry for the
> delay.
>
> On Thu, 11 Jun 2026, Takashi Yano wrote:
>
> > If you run "stty noflsh; cat" in "bash", and stop "cat" by Ctrl-C,
> > a stray ^C is passed to "bash". The current code calls tcflush() if
> > NOFLSH is not set, however, tcflush() is not called when NOFLSH is
> > set. So, Ctrl-C remains in console input buffer. This should be
> > discarded even in NOFLSH mode. This patch introduces a helper
> > function discard_key_events() and call it to erase Ctrl-C in the
> > console input buffer.
> >
> > Note that even with this patch, NOFLSH is not fully functional in
> > console because the readahead buffer is unique to process, so it
> > cannot be inherited to other processes. However, it should work
> > intra process.
> >
> > Fixes: 118e51be1d04 ("(tty_min::kill_pgrp): Handle tty flush when signal detected.")
> > Signed-off-by: Takashi Yano <[email protected]>
> > Reviewed-by:
> > ---
> > winsup/cygwin/fhandler/console.cc | 20 +++++++++++++++++---
> > winsup/cygwin/fhandler/termios.cc | 10 +++++++---
> > winsup/cygwin/local_includes/fhandler.h | 2 ++
> > 3 files changed, 26 insertions(+), 6 deletions(-)
> >
> > diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
> > index a5e6cd89d..9ac492980 100644
> > --- a/winsup/cygwin/fhandler/console.cc
> > +++ b/winsup/cygwin/fhandler/console.cc
> > @@ -1744,17 +1744,31 @@ out:
> > discard_len = 0;
> > if (discard_len)
> > {
> > - DWORD discarded;
> > acquire_attach_mutex (mutex_timeout);
> > DWORD resume_pid = attach_console (con.owner);
> > - ReadConsoleInputW (get_handle (), input_rec, discard_len, &discarded);
> > + discard_key_events (discard_len);
> > detach_console (resume_pid, con.owner);
> > release_attach_mutex ();
> > - con.num_processed -= min (con.num_processed, discarded);
> > }
> > return stat;
> > }
> >
> > +void
> > +fhandler_console::discard_key_events (size_t n)
> > +{
> > + DWORD discarded = 0;
> > + INPUT_RECORD input_rec[INREC_SIZE];
> > + DWORD n1 = min (INREC_SIZE, n);
> > + while (n)
> > + {
> > + ReadConsoleInputW (get_handle (), input_rec, n1, &n1);
> > + n -= n1;
> > + discarded += n1;
> > + n1 = min (INREC_SIZE, n);
> > + }
> > + con.num_processed -= min (con.num_processed, discarded);
> > +}
>
> `discard_key_events()` loops on `ReadConsoleInputW()` until it has
> consumed the requested count, and `ReadConsoleInputW()` blocks while the
> input buffer is empty. The `sigflush()` caller passes a hard-coded `1`
> with no guarantee that a record is actually queued: in the master-thread
> path the signalling record has already been read out of the buffer before
> `sigflush()` runs, so the call blocks until the user's next keystroke
> arrives, and then swallows it. And because `ReadConsoleInputW()`'s return
> is unchecked, a failed read leaves `n1` indeterminate, so `n -= n1` can
> underflow and spin.
The root cause of above situation is that the master-thread is not
disable even when line_edit() is called via read(). This may cause
other issues we have not seen yet. Usually, input_mutex is held by
the thread calling line_edit(), so the master-thread does not touch
the input events. However, the current code calls discard_key_events()
after release_input_mutex_if_necessary().
So, what about the patch (1/2) attached instead?
> The fix is to consume only what `GetNumberOfConsoleInputEvents()` reports
> as currently queued (so the helper never waits for input that is not
> there) and to bail on a failed or zero-length read (so it cannot
> underflow).
>
> One separate point worth flagging: the `process_input_message()` caller
> wraps `discard_key_events()` in `acquire_attach_mutex()` + `attach_console
> (con.owner)`, but the `sigflush()` call site does not, so the
> `ReadConsoleInputW()` there runs against whatever console the calling
> process happens to be attached to. With the guard above the worst case is
> a no-op when the calling process happens not to be attached, but it would
> be more correct to wrap the `sigflush()` discard the same way, or to move
> the attach into the helper itself.
Yeah, you are right. Then, what about the patch (2/2) attached?
Sorry for no commit message for both patches yet.
--
Takashi Yano <[email protected]>
0001-Cygwin-console-discard_key_events-patch-1-2.patch
(text/plain, 1.9 KB)
From 47377a1e286de55ea5b24529e35afdad6c747ea4 Mon Sep 17 00:00:00 2001 From: Takashi Yano <[email protected]> Date: Mon, 29 Jun 2026 23:22:05 +0900 Subject: [PATCH 1/2] Cygwin: console: discard_key_events patch (1/2) --- winsup/cygwin/fhandler/console.cc | 3 ++- winsup/cygwin/fhandler/termios.cc | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc index 730bb0b45..ccd087f92 100644 --- a/winsup/cygwin/fhandler/console.cc +++ b/winsup/cygwin/fhandler/console.cc @@ -1770,7 +1770,8 @@ fhandler_console::discard_key_events (size_t n) DWORD n1 = min (INREC_SIZE, n); while (n) { - ReadConsoleInputW (get_handle (), input_rec, n1, &n1); + if (!ReadConsoleInputW (get_handle (), input_rec, n1, &n1) || !n1) + break; n -= n1; discarded += n1; n1 = min (INREC_SIZE, n); diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index 605258731..a2b14d14b 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -449,6 +449,8 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fhandler_termios *fh) fh->eat_readahead (-1); fh->discard_input (); } + if (is_flush_sig (sig) && cygheap->ctty) + cygheap->ctty->discard_key_events (1); if (fh) fh->release_input_mutex_if_necessary (); ttyp->kill_pgrp (sig, pgid); @@ -666,13 +668,9 @@ fhandler_termios::sigflush () be NULL while this is alive. However, we can conceivably close a ctty while exiting and that will zero this. */ if ((!have_execed || have_execed_cygwin) && tc () - && (tc ()->getpgid () == myself->pgid)) - { - if (!(tc ()->ti.c_lflag & NOFLSH)) - tcflush (TCIFLUSH); - else - discard_key_events (1); - } + && (tc ()->getpgid () == myself->pgid) + && !(tc ()->ti.c_lflag & NOFLSH)) + tcflush (TCIFLUSH); } pid_t -- 2.51.0
0002-Cgywin-console-discard_key_events-patch-2-2.patch
(text/plain, 1.6 KB)
From 6b8f413c29345a902d2cd23bd9798597d5cbb548 Mon Sep 17 00:00:00 2001 From: Takashi Yano <[email protected]> Date: Mon, 29 Jun 2026 23:24:42 +0900 Subject: [PATCH 2/2] Cgywin: console: discard_key_events patch (2/2) --- winsup/cygwin/fhandler/console.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc index ccd087f92..c1497d97c 100644 --- a/winsup/cygwin/fhandler/console.cc +++ b/winsup/cygwin/fhandler/console.cc @@ -1752,13 +1752,7 @@ out: if (stat == input_signalled && !(ti->c_lflag & NOFLSH)) discard_len = 0; if (discard_len && (len || stat != input_ok)) - { - acquire_attach_mutex (mutex_timeout); - DWORD resume_pid = attach_console (con.owner); - discard_key_events (discard_len); - detach_console (resume_pid, con.owner); - release_attach_mutex (); - } + discard_key_events (discard_len); return stat; } @@ -1768,6 +1762,8 @@ fhandler_console::discard_key_events (size_t n) DWORD discarded = 0; INPUT_RECORD input_rec[INREC_SIZE]; DWORD n1 = min (INREC_SIZE, n); + acquire_attach_mutex (mutex_timeout); + DWORD resume_pid = attach_console (con.owner); while (n) { if (!ReadConsoleInputW (get_handle (), input_rec, n1, &n1) || !n1) @@ -1776,6 +1772,8 @@ fhandler_console::discard_key_events (size_t n) discarded += n1; n1 = min (INREC_SIZE, n); } + detach_console (resume_pid, con.owner); + release_attach_mutex (); con.num_processed -= min (con.num_processed, discarded); } -- 2.51.0