[newlib-cygwin/cygwin-3_6-branch] Cygwin: console: Correct previous NOFLSH fix
Takashi Yano
tyan0@sourceware.org
Wed Jul 8 18:09:04 GMT 2026
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=c31308936b67cb07bb0fdc45c8ba16e8bfdf0787
commit c31308936b67cb07bb0fdc45c8ba16e8bfdf0787
Author: Takashi Yano <takashi.yano@nifty.ne.jp>
Date: Tue Jun 30 11:11:31 2026 +0900
Cygwin: console: Correct previous NOFLSH fix
The previous fix for NOFLSH mode does not work as intended.
discard_key_events(), added in "Cygwin: console: Fix NOFLSH behaviour a
bit", loops on ReadConsoleInputW() until it has consumed the requested
number of records, but ReadConsoleInputW() blocks while the console
input buffer is empty. sigflush() calls it with a hard-coded count of
one and 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, and then
swallows, the user's next keystroke.
To avoid this, this patch does not discard input when process_sigs()
is called from cons_master_thread, where the value of `fh` is NULL,
because discarding will be done in cons_master_thread.
And because the ReadConsoleInputW() return value is unchecked, a failed
read leaves the count indeterminate, so "n -= n1" can underflow and spin.
Check return value of ReadConsoleInputW() and abort if it fails.
Moreover, discard_key_event(1) does not work as intended if the first
key event is not a bKeyDown event correspoding to the signalling key.
Use discard_key_events(0) instead. This means discarding input events
to the current position processed. Since the key-strokes prior to the
signalling key are already in the readahead buffer, so this call discards
only the signalling key. The important point here is to discard input
before releasing input_mutex by release_input_mutex_if_necessary(),
because, if not, cons_master_thread starts to process key events before
discarding signalling key event because the thread can acquire
input_mutex. This causes the signalling key is processed twice.
One separate point: 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, so
it would be more correct to move the attach into the helper itself.
This patch also fixes two more special cases. One is done_with_debugger
case. When `gdb cat` is executed and the `cat` is running, Ctrl-C
discards all the key events including the events after Ctrl-C. This
is because tcflush() is used for the purpose. Use discard_key_events(0)
instead. The other case is not_signalled_but_done case. Previously,
when `cat | non-cygwin-app` is executed and Ctrl-C is pressed, but
the `Ctrl-C` is not VINTR, line_edit() wrongly returned
line_edit_signalled even though `cat` is not signalled by Ctrl-C.
In this case, `cat` should receive Ctrl-C as a input char, while
`non-cygwin-app` has been killed by Ctrl-C. Fix this in line_edit().
In the case of not_signalled_but_done case, setting `sawsig` flag
and releasing `output_stopped` has been skipped with this patch,
because this (Ctrl-C) is not a signal key in the case above.
Fixes: 66324edf64a9 ("Cygwin: console: Fix NOFLSH behaviour a bit")
Co-authored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
(cherry picked from commit 0d516c2b1f4d7e4abcf4be55056b5cd87f566e5e)
Diff:
---
winsup/cygwin/fhandler/console.cc | 28 +++++++++++++++++-----------
winsup/cygwin/fhandler/termios.cc | 30 ++++++++++++++++++------------
winsup/cygwin/local_includes/fhandler.h | 1 +
3 files changed, 36 insertions(+), 23 deletions(-)
diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
index dc1eb0644..3de1bcada 100644
--- a/winsup/cygwin/fhandler/console.cc
+++ b/winsup/cygwin/fhandler/console.cc
@@ -1665,6 +1665,7 @@ fhandler_console::process_input_message (size_t len)
continue;
}
+ num_input_events_processed = i + 1;
num_chars += nread;
if (toadd)
{
@@ -1695,17 +1696,11 @@ out:
/* Discard processed recored. */
DWORD discard_len = min (total_read, i + 1);
/* If input is signalled, do not discard input here because
- tcflush() is already called from line_edit(). */
- if (stat == input_signalled && !(ti->c_lflag & NOFLSH))
+ discard_key_events() is already called from line_edit(). */
+ if (stat == input_signalled)
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;
}
@@ -1713,15 +1708,25 @@ void
fhandler_console::discard_key_events (size_t n)
{
DWORD discarded = 0;
+ if (n == 0)
+ {
+ n = num_input_events_processed;
+ num_input_events_processed = 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)
{
- 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);
}
+ detach_console (resume_pid, con.owner);
+ release_attach_mutex ();
con.num_processed -= min (con.num_processed, discarded);
}
@@ -2305,7 +2310,8 @@ fhandler_console::tcgetattr (struct termios *t)
fhandler_console::fhandler_console (fh_devices devunit) :
fhandler_termios (), input_ready (false), thread_sync_event (NULL),
- input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV)
+ input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV),
+ num_input_events_processed (0)
{
dev_referred_via = (dev_t) devunit;
if (devunit > 0)
diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc
index 650807850..e16b29619 100644
--- a/winsup/cygwin/fhandler/termios.cc
+++ b/winsup/cygwin/fhandler/termios.cc
@@ -353,7 +353,10 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fhandler_termios *fh)
fhandler_pty_common::attach_console_temporarily (p->dwProcessId);
if (fh && p == myself && being_debugged ())
{ /* Avoid deadlock in gdb on console. */
- fh->tcflush(TCIFLUSH);
+ if (fh->is_console ())
+ fh->discard_key_events (0 /* to current position */);
+ else
+ fh->tcflush(TCIFLUSH);
fh->release_input_mutex_if_necessary ();
}
/* CTRL_C_EVENT does not work for the process started with
@@ -444,10 +447,14 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fhandler_termios *fh)
goto not_a_sig;
termios_printf ("got interrupt %d, sending signal %d", c, sig);
- if (!(ti.c_lflag & NOFLSH) && fh)
+ if (fh)
{
- fh->eat_readahead (-1);
- fh->discard_input ();
+ if (!(ti.c_lflag & NOFLSH))
+ {
+ fh->eat_readahead (-1);
+ fh->discard_input ();
+ }
+ fh->discard_key_events (0 /* to current position */);
}
if (fh)
fh->release_input_mutex_if_necessary ();
@@ -460,6 +467,8 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fhandler_termios *fh)
not_a_sig:
if ((ti.c_lflag & ISIG) && need_discard_input)
{
+ if (need_send_sig)
+ return not_signalled;
if (!(ti.c_lflag & NOFLSH) && fh)
{
fh->eat_readahead (-1);
@@ -525,10 +534,11 @@ fhandler_termios::line_edit (const char *rptr, size_t nread, termios& ti,
switch (process_sigs (c, get_ttyp (), this))
{
case signalled:
- case not_signalled_but_done:
case done_with_debugger:
sawsig = true;
get_ttyp ()->output_stopped = false;
+ fallthrough;
+ case not_signalled_but_done:
continue;
case not_signalled_with_nat_reader:
disable_eof_key = true;
@@ -666,13 +676,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
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_includes/fhandler.h
index 2a7f8b308..0a3e35db8 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2200,6 +2200,7 @@ private:
HANDLE input_mutex, output_mutex;
handle_set_t handle_set;
_minor_t unit;
+ size_t num_input_events_processed;
/* Used when we encounter a truncated multi-byte sequence. The
lead bytes are stored here and revisited in the next write call. */
More information about the Cygwin-cvs
mailing list