[PATCH 3/4] Cygwin: pty: Prevent premature pseudo console teardown that amplifies oscillation

Takashi Yano takashi.yano@nifty.ne.jp
Thu Mar 19 10:50:34 GMT 2026


Hi Johannes,

On Mon, 02 Mar 2026 14:24:39 +0000
Johannes Schindelin wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> 
> The two preceding commits removed transfer code paths that stole
> readline's data during pseudo console oscillation.  This commit
> addresses the oscillation itself: a guard function that tears down
> active pseudo console sessions prematurely, causing more frequent
> oscillation cycles and thus more opportunities for the remaining
> (less harmful) timing issues to manifest.
> 
> The function reset_switch_to_nat_pipe() runs from bg_check() in the
> slave process.  Its purpose is to clean up the nat pipe state when
> no native process is using the pseudo console anymore.  Its guard
> logic was:
> 
>   if (!nat_pipe_owner_self(pid) && process_alive(pid))
>     return;   /* someone else owns it, don't reset */
>   /* fall through to destructive cleanup: clear pty_input_state,
>      nat_pipe_owner_pid, switch_to_nat_pipe, pcon_activated */
> 
> The nat_pipe_owner_pid is set to bash's own PID during
> setup_for_non_cygwin_app() (because bash is the process that calls
> exec() to launch the native program).  When bg_check() runs and
> calls reset_switch_to_nat_pipe(), nat_pipe_owner_self() returns
> true -- the first condition becomes false, the && short-circuits,
> and the function falls through to the destructive cleanup.  It
> clears pcon_activated, switch_to_nat_pipe, pty_input_state, and
> nat_pipe_owner_pid -- even though the native process is still
> alive and actively using the pseudo console.
> 
> This forced every subsequent code path to re-initialize the pseudo
> console from scratch, creating exactly the rapid oscillation
> described in the earlier commits.
> 
> Restructure the guard into two separate checks:
> 
>   if (process_alive(pid))
>     {
>       if (!nat_pipe_owner_self(pid))
>         return;   /* someone else owns it */
>       if (pcon_activated || switch_to_nat_pipe)
>         return;   /* we own it, but session is still active */
>     }
>   /* fall through: owner died or session ended */
> 
> When a different process owns the nat pipe, the behavior is
> unchanged.  When bash itself is the owner, the function now also
> returns early if pcon_activated or switch_to_nat_pipe is still set.
> Both flags are checked because during pseudo console handovers
> between parent and child native processes, pcon_activated is
> briefly false while switch_to_nat_pipe remains true.
> 
> The cleanup still runs when it should: when the owner process has
> exited or when both flags indicate the session has truly ended.
> 
> Regression note: this change is strictly more conservative -- it
> adds conditions that prevent cleanup, never removes them.  Every
> scenario where the original code returned early still returns early.
> 
> Addresses: https://github.com/git-for-windows/git/issues/5632
> Fixes: 919dea66d3ca ("Cygwin: pty: Fix a race issue in startup of pseudo console.")
> Assisted-by: Claude Opus 4.6
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  winsup/cygwin/fhandler/pty.cc | 21 ++++++++++++++++-----
>  1 file changed, 16 insertions(+), 5 deletions(-)
> 
> diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
> index 2450057c1..dd7ea9038 100644
> --- a/winsup/cygwin/fhandler/pty.cc
> +++ b/winsup/cygwin/fhandler/pty.cc
> @@ -1171,12 +1171,23 @@ fhandler_pty_slave::reset_switch_to_nat_pipe (void)
>    DWORD wait_ret = WaitForSingleObject (pipe_sw_mutex, mutex_timeout);
>    if (wait_ret == WAIT_TIMEOUT)
>      return;
> -  if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid)
> -      && process_alive (get_ttyp ()->nat_pipe_owner_pid))
> +  if (process_alive (get_ttyp ()->nat_pipe_owner_pid))
>      {
> -      /* There is a process which owns nat pipe. */
> -      ReleaseMutex (pipe_sw_mutex);
> -      return;
> +      if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid))
> +	{
> +	  /* There is a process which owns nat pipe. */
> +	  ReleaseMutex (pipe_sw_mutex);
> +	  return;
> +	}
> +      /* We are the nat pipe owner.  Don't reset while a native process
> +	 is still using the nat pipe -- check both pcon_activated and
> +	 switch_to_nat_pipe since the latter stays true during pcon
> +	 handovers when pcon_activated is briefly false. */

In what situation does the code path reach here?
Normal cygwin process usually cannot be a nat_pipe_owner.
Two exceptions are:
 1. GDB with non-cygwin inferior
 2. stub process for non-cygwin app

The stub process never calls reset_switch_to_nat_pipe(), therefore
we should consider only of GDB. However, the code for GDB exists
at the begining of reset_switch_to_nat_pipe().
(See 'if (h_gdb_inferior)' block.)

So, simplly

  if (process_alive (get_ttyp ()->nat_pipe_owner_pid))
    {
      /* There is a process which still owns nat pipe. */
      ReleaseMutex (pipe_sw_mutex);
      return;
    }

might be enough. Am I overlooking something?

> +      if (get_ttyp ()->pcon_activated || get_ttyp ()->switch_to_nat_pipe)
> +	{
> +	  ReleaseMutex (pipe_sw_mutex);
> +	  return;
> +	}
>      }
>    /* Clean up nat pipe state */
>    get_ttyp ()->pty_input_state = tty::to_cyg;
> -- 
> cygwingitgadget
> 


-- 
Takashi Yano <takashi.yano@nifty.ne.jp>


More information about the Cygwin-patches mailing list