LiveText: announce large bursts via a pumped generator#20177
Conversation
|
This looks like a super useful fix for anyone regularly running CLI-based AI agents. Might be worth taking this PR for a spin with a similar test case right away. |
|
@cary-rowen I don't use agents myself, but I'd love more testing of it since I don't have a lot of cases to test this on my system atm. One candidate I'm 99 percent sure this solves is things like |
|
@cary-rowen Just confirmed: this fixes the console flood problem with jorunalctl at least. |
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
|
can this please get a change log entry and more clearer testing steps in the PR description? |
There was a problem hiding this comment.
Pull request overview
This PR targets a long-standing responsiveness issue where large bursts of live-region text (notably terminal output) can block NVDA’s main thread while announcing many lines. It changes LiveText line reporting to truncate very large bursts and to announce lines via a queued, yielding generator so the main loop can continue pumping input/events.
Changes:
- Add a per-burst cap on announced live-text lines (default 100) and announce how many lines were skipped when truncation occurs.
- Replace synchronous per-line reporting with a
queueHandler-registered generator that yields every N lines to keep NVDA responsive. - Cancel any in-flight live-text reporting generator when speech is canceled or when a new burst supersedes it.
|
@seanbudd I can do the change log at least, but I'm not really sure what a "better testing strategy" entails. Like, you can validate it by SSH'ing into a server that's been up for a month or something and doing |
|
I was asking for a clearer testing strategy, not a better one. The section currently states
Can you provide brief step by step instructions on what you did to test? |
… into live-text-queue-fixes
|
Your merging method seems to be problematic. The differences in this PR are very large; please do not use the squash method to merge. |
|
@wmhn1872265132 I mean, okay, if that's what you want... |
|
Is there anything else that is required for this PR? Just checking since I'm pretty sure people are clamoring for this to get merged. :D I hope all the testing has worked out well (it seems to work fantastically for me). |
|
Thanks @ethindp |
Co-authored-by: wencong <manchen_0528@outlook.com>
Co-authored-by: wencong <manchen_0528@outlook.com>
|
Will do |
|
@cary-rowen All done. |
|
I'm not entirely certain why the system checks are failing. Two of the runs can't find the Chrome window and the third fails with a speech timeout. are they blockers to this getting merged? |
|
Thanks for working on this. Something still doesn't make sense for me here, though. When we use LiveText, we should only be diffing a full screen at maximum, which means, say, 60 lines at an absolute maximum. Just in case this helps anyone, another thing I discovered a while back when looking into this is that having logging set to i/o or lower can cause severe lag in this case because each line of speech gets logged and the log is then flushed to disk. This PR should help that too because we're ultimately logging less lines, but the only real solution there is to avoid setting the log level like this if you're doing a lot of spammy terminal stuff. |
Not any more: the |
|
Yep fairly certain it was alt buffer related, see #12669 and microsoft/terminal#5406. |
That feels like something we should look at fixing, even if we just limit it to the last 100 lines or similar. Diffing that many lines is much more expensive and it's ultimately pointless, since even a sighted user can only perceive what's changing on screen, not what's changing outside of it. |
That was done in this PR. |
|
This PR limits what we report, not what we diff. We're still potentially (expensively) calculating diffs which we later throw away. |
|
I'm guessing the calculation of diffs on larger blocks of text is probably also more expensive even if the actual diff is minimal, though I can't substantiate that with any data, so I could be wrong. Still, it might be premature optimisation unless proven otherwise. |
Yes, you're right, and I did try bounding the diff at say the last 100 lines ages ago. (See a quick implementation at the limit-diffs branch of my fork). The problem is that if a large block (say 10,000 lines) is printed, NVDA will drop all but the last 100 at first, but subsequent commands in the session sometimes get stuck reading very old output because DMP doesn't have enough context. I'll see if I can come up with clear repro steps. |
Diffing and such may have been done on a background thread, but the actual line update reporting was not. Instead if you diff against this PR and a commit before it was merged, you'll see that |
To be more concrete, were you seeing (and still seeing) calls to That said, I'm fairly sure I've heard of complaints about this from folks with the UIA console disabled. That case could not have been sending more than 60 or so lines at a time. There would have been a huge number of calls, but each would have naturally yielded to the queue after 60 lines or so. |
Link to issue number:
Fixes #6291. Partially addresses #15786, #15850, #11002, and #14189. All of these describe the same general class of "NVDA stops responding during console flood" problem but also include symptoms (UIA event flooding, NVDA crashes destroying the console window, etc.) that are out of scope for this PR and were partly addressed in #14888 and #14067 anyway. Related to #2977 and #875.
Most of these are already closed, but the underlying root cause (which is the main thread blocking during line announcement) was never actually addressed.
Summary of the issue:
When a live-text region (such as a terminal) produces a large burst of new text in a short window, NVDA's main thread blocks announcing every line sequentially, becoming unresponsive to keyboard input until the burst finishes. In some cases this lasts tens of seconds. In other (more drastic) cases, it can last so long that the user has to sign out, restart, or force-kill NVDA.
Description of user facing changes:
NVDA no longer freezes when large bursts of text are reported in a live region.
Description of developer facing changes:
LiveText._reportNewLinesis no longer guaranteed to report lines synchronously. Internally, it now registers a generator withqueueHandlerwhich yields between batches of lines so the main thread can service input during long announcements.A new class attribute
LiveText.MAX_LINEScaps the number of lines announced per burst. We drop everything before that on the floor.When a new burst arrives while a previous one is still being announced, the previous generator is cancelled and the new burst supersedes it.
Description of development approach:
Live-text regions announce new text via
LiveText._reportNewLines, which was invoked synchronously from the main thread's event queue. For workloads producing substantial amounts of new lines in a short window, every line traverses the full speech pipeline sequentially without yielding, which can leave the main thread unresponsive to keyboard input for tens of seconds or longer.This PR addresses the responsiveness problem at the announcement layer rather than at the diff or event-source layer, which were previously addressed by #14888 and #14067. Specifically:
(1) Bursts larger than
MAX_LINESare truncated to the most recentNlines. Skipping the oldest lines was chosen over skipping the newest because most flooding scenarios carry their actionable content at the end.(2) Reporting now happens via a generator registered with
queueHandler.registerGeneratorObject, yielding every 5 lines. This is the same primitive previously used byspeech.speakSpellingand the originalsayAll. Between yields, the main thread services the event queue, which keeps NVDA responsive.(3) A handler is registered with
pre_speechCanceledso that pressing control (or any other speech-cancel trigger) also cancels the in-flight generator. Perceptibly this may not cancel immediately and up to 5 lines may still be announced past the cancel, but internally the generator won't proceed past that iteration.Testing strategy:
Reproducing this bug is rather tricky and I'm not sure what the most reliable reproducer is. However, here are some things that trigger it for me:
uv sync --upgrade. Another alternative is to justuv adda bunch of packages. UV constantly sends ASCII control sequences and redraw commands to the terminal, and NVDA will freeze trying to process every single one.Get-EventLog -LogName System. Unless PowerShell has changed this command, this should freeze NVDA for at least a second.journalctl -xfe, ordnf install texlive-*.I've no doubt there are many other ways this bug can be triggered. Essentially, any action that sends a huge number of speak requests to NVDA extremely quickly should do it.
Known issues with pull request:
This PR does not address the case where a single line in an editable text control is extremely long (e.g., the Notepad/Notepad++ scenario in #13432). The bottleneck there is
getTextWithFieldsinNVDAObjects/window/edit.py, which does COM round-trips to extract the text and per-character formatting from the host process. In theory it could be addressed by chunking the speech and adding a fast path for uniform-format ranges, but that's a different PR with its own design questions.The supersession behavior silently drops content from the previous burst when a new one arrives. In practice this is what users want for the common flooding scenarios (most recent state is most relevant), but I'm open to feedback if reviewers think a different policy is appropriate.
Code Review Checklist: