[PATCH/RFC] console: avoid master-thread suspend request from non-master processes

Johannes Schindelin Johannes.Schindelin@gmx.de
Thu Aug 13 09:43:08 GMT 2026


Hi Chris,

On Thu, 13 Aug 2026, 飛飛飛 wrote:

> Hi,
> I have been investigating a reproducible hang in highly parallel builds on
> Windows 11, especially while building LibreOffice with multiple jobs.
> The hang appears to involve fhandler_console::close() calling:
> 
> set_disable_master_thread (true, this);
> 
> 
> even from processes that do not have a local console master thread.
> 
> set_disable_master_thread() updates the shared disable_master_thread state
> and then waits for:
> 
> while (con.master_thread_suspended != x)
>   Sleep (1);
> 
> During testing I captured a case where one process published a suspend
> request (true) and started waiting for
> 
>  master_thread_suspended == true,
> 
> but another process later published false into the same shared state before
> the first request had completed.
> 
> The first process was then left waiting indefinitely.
> In the captured trace, both requesting processes had:
> 
> master_thread_started = 0
> 
> while the actual console owner/master-thread process was a different
> process.

Thanks for the trace; it exposes a real race:
`set_disable_master_thread()` publishes shared state, drops `input_mutex`,
then waits, allowing another process to overwrite the request.

> A minimal guard in
>  fhandler_console::close() avoids this:
> 
> if (master_thread_started)
>   set_disable_master_thread (true, this);
> 
> This prevents processes without a local master thread from entering the
> master-thread suspend request/wait path.
> The intent is not to change Windows process/thread scheduling. It only
> restricts Cygwin's shared console master-thread control to a process which
> actually has a local master thread.
> This also avoids non-master processes entering a wait loop whose completion
> condition may be invalidated by another process modifying the same shared
> state.

The proposed guard does not fix this bug. `master_thread_started` is
process-local and false in every non-owner, so it says nothing about the
owner's master thread. It would also leave the following `tty::restore`
changes running while that thread remains active, undoing the ordering
introduced by `733d5a953fa9`. Other callers could still overwrite the
shared state, too.

Could you capture, on unpatched HEAD, the stuck process' call site and
stack, `con.owner` and whether it is alive, plus both shared flags?

Cross-process request serialization is likely needed, and I believe that
Takashi's patch (now at v12, see
https://inbox.sourceware.org/cygwin-patches/20260807121459.3116-1-takashi.yano@nifty.ne.jp/)
is the right approach, even if v12 still has a known lock-order deadlock.

Ciao,
Johannes

> Test results:
> 
> Windows 11
> AMD Ryzen 7 9800X3D
> Intel i7-13620
> parallel LibreOffice builds
> 
> repeated high-parallelism testing
> full LibreOffice build completed successfully with the guard applied
> Without the guard, I was able to reproduce the hang and capture the request
> overwrite described above.
> With the guard restored, the full LibreOffice build completed successfully
> with exit code 0.
> 
> One possible trade-off is that processes without a local master thread can
> no longer request suspension of the console master thread through this
> path. So this narrows some existing cross-process master-thread control
> behavior.
> 
> So far I have not observed a functional regression from this restriction on
> the tested Windows 11 systems.
> My current understanding is that the important behavioral change is very
> small: processes without master_thread_started no longer enter the
> potentially unbounded set_disable_master_thread() wait path.
> I would appreciate feedback on whether this ownership restriction is
> consistent with the intended console master-thread design.
> Thanks.
> 
> 
> 
> 
> 🫆Trace Process Log🫆
> 
> Process A:
>   started=0
>   publish disable=1
>   wait for suspended=1
> 
> Process B:
>   started=0
>   publish disable=0
>   completes immediately
> 
> Process A:
>   never completes
> 


More information about the Cygwin-patches mailing list