[newlib-cygwin] Cygwin: pty: bound the cursor-sync round-trip so an exiting process cannot hang
Takashi Yano via Cygwin-cvs <[email protected]> Tue, 30 Jun 2026 13:09:41 +0000 (GMT)
| Newsgroups | gmane.os.cygwin.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3D24430fb239c= 1783bfd6b0135e8a529d11f487db3 commit 24430fb239c1783bfd6b0135e8a529d11f487db3 Author: Johannes Schindelin <[email protected]> Date: Thu Jun 25 13:41:42 2026 +0200 Cygwin: pty: bound the cursor-sync round-trip so an exiting process can= not hang =20 The cursor-position fixup added in "Cygwin: pty: Fixup pty state after a cygwin app exits" runs from cleanup() on every foreground Cygwin-app exit while a pseudo console is active, and it waits on two unbounded loops for the master to answer the "ESC[6n" it just sent: one that spins until the pcon_start_pid slot is free, and one that spins until the master clears the slot again. pcon_start_pid is only ever cleared once master::write() parses the terminal's reply, so if that reply never comes, because the terminal is going away, the forwarding pipe is broken, or a previous requester died mid-handshake, the exiting process spins on yield() forever and never exits. =20 Bound both waits with a three second deadline using GetTickCount64(), and on timeout clear our own pcon_start_pid slot, but only if it is still ours, so a give-up does not stomp a later requester. Also restore the pcon_start and pcon_start_pid reset that the same commit removed from close_pseudoconsole(); it is the backstop that keeps a requester which died without clearing its slot from wedging the next one. The worst case is now a slightly stale cursor after a timeout rather than a process that refuses to exit. =20 Fixes: b34394d456b6 ("Cygwin: pty: Fixup pty state after a cygwin app e= xits") Assisted-by: Opus 4.8 Signed-off-by: Johannes Schindelin <[email protected]> Co-authored-by: Takashi Yano <[email protected]> Reviewed-by: Takashi Yano <[email protected]> Diff: --- winsup/cygwin/fhandler/pty.cc | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc index c1de25baa..ca85ae679 100644 --- a/winsup/cygwin/fhandler/pty.cc +++ b/winsup/cygwin/fhandler/pty.cc @@ -391,6 +391,7 @@ atexit_func (void) void fhandler_pty_slave::req_fixup_pcon_state (void) { + ULONGLONG deadline =3D GetTickCount64 () + 3000; while (true) { WaitForSingleObject (input_mutex, mutex_timeout); @@ -398,6 +399,10 @@ fhandler_pty_slave::req_fixup_pcon_state (void) break; /* Another request is on going. */ ReleaseMutex (input_mutex); + if (GetTickCount64 () > deadline) + /* A previous requester is stuck; give up this sync rather than + spin forever. */ + return; yield (); } =20 @@ -410,9 +415,25 @@ fhandler_pty_slave::req_fixup_pcon_state (void) get_ttyp ()->pcon_start_pid =3D myself->pid; WriteFile (get_output_handle (), "\033[6n", 4, &n, NULL); ReleaseMutex (input_mutex); - while (get_ttyp ()->pcon_start_pid) + deadline =3D GetTickCount64 () + 3000; + while (get_ttyp ()->pcon_start_pid && GetTickCount64 () <=3D deadline) /* wait for completion of fixing-up in master::write(). */ yield (); + /* If the master never answered (e.g. the terminal is going away), + clear our own request so a stale pcon_start_pid cannot wedge the + next requester. */ + if (get_ttyp ()->pcon_start_pid =3D=3D (pid_t) myself->pid) + { + WaitForSingleObject (input_mutex, mutex_timeout); + if (get_ttyp ()->pcon_start_pid =3D=3D (pid_t) myself->pid) + { + get_ttyp ()->req_fixup_pcon_cur_pos =3D false; + get_ttyp ()->req_xfer_input =3D false; + get_ttyp ()->pcon_start =3D false; + get_ttyp ()->pcon_start_pid =3D 0; + } + ReleaseMutex (input_mutex); + } } =20 void @@ -4217,6 +4238,13 @@ fhandler_pty_slave::close_pseudoconsole (tty *ttyp, = DWORD force_switch_to) ttyp->pcon_activated =3D false; ttyp->switch_to_nat_pipe =3D false; ttyp->nat_pipe_owner_pid =3D 0; + /* Safety net: if a req_fixup_pcon_state() requester died without + clearing its slot, do not leave pcon_start_pid set forever. */ + if (ttyp->pcon_start_pid =3D=3D myself->pid) + { + ttyp->pcon_start =3D false; + ttyp->pcon_start_pid =3D 0; + } } if (ttyp->pcon_handle_ready_event) {