Re: [PATCH/RFC] console: avoid master-thread suspend request from non-master processes
Johannes Schindelin <[email protected]>
| Newsgroups | gmane.os.cygwin.patches |
|---|---|
| Message-ID | <[email protected]> |
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/[email protected]/) 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 >