Problem
When a single application stops responding, NVDA's core thread blocks on synchronous cross-process accessibility calls into it, freezing NVDA itself — sometimes until the app is killed or NVDA is restarted. This is the deeper, architectural layer behind #16749 and the long-standing #1408.
Evidence (observed blocking call sites, all on the core thread)
- UIA client:
IUIAutomation::ElementFromHandleBuildCache / getFocusedElementBuildCache; UiaHasServerSideProvider (per-winEvent, cache-miss).
- MSAA:
IAccessible::accParent during the focus ancestor walk; oleacc.AccessibleObjectFromEvent for shell/ghost windows entangled with a hung UIA app (observed ~65s freeze).
- Word object model:
nvdaInProcUtils_winword_getTextInRange on a huge "select-all → delete".
- The generic
NVDAObject property pipeline (_getPropertyViaCache → app getters): e.g. isProtected, name, states.
Root cause
NVDA's object model fetches data by calling synchronously into the application from the core thread; the only backstop is the watchdog's multi-second cancel, which is slow and repeats — so a single hung application serialises NVDA.
Proposed approach (incremental, in-pattern)
- Add an optional bounded
timeout to the existing watchdog.cancellableExecute / CancellableCallThread (default unchanged) — a small, reusable primitive: the core waits at most N seconds, then abandons the call exactly like a watchdog cancel.
- Adopt it at the blocking chokepoints (UIA client calls,
AccessibleObjectFromEvent, winword text retrieval), with a per-subsystem "client jammed" breaker plus exponential backoff.
- A near-zero-cost shared "hung mode" signal gating a central
NVDAObject._getPropertyViaCache guard so that, only for a not-responding application's own objects, properties serve cached-or-None instead of blocking. Normal operation is byte-identical.
Scope / known limitations
This keeps NVDA responsive and self-recovering when an application hangs. It does not make UIA-only surfaces (e.g. the Windows 11 Alt+Tab switcher) readable while a UIA application jams the shared UIA client — that is an inherent Microsoft UI Automation limitation, present in stock NVDA too. Known pre-existing trade-offs amplified by the short timeout (abandoned-worker thread/handle churn under a permanent hang; byref buffers handed to an abandoned worker) are called out for discussion in the accompanying pull request.
Relates to
#16749, #1408
A pull request implementing this incrementally (stacked on #20168) will follow.
Problem
When a single application stops responding, NVDA's core thread blocks on synchronous cross-process accessibility calls into it, freezing NVDA itself — sometimes until the app is killed or NVDA is restarted. This is the deeper, architectural layer behind #16749 and the long-standing #1408.
Evidence (observed blocking call sites, all on the core thread)
IUIAutomation::ElementFromHandleBuildCache/getFocusedElementBuildCache;UiaHasServerSideProvider(per-winEvent, cache-miss).IAccessible::accParentduring the focus ancestor walk;oleacc.AccessibleObjectFromEventfor shell/ghost windows entangled with a hung UIA app (observed ~65s freeze).nvdaInProcUtils_winword_getTextInRangeon a huge "select-all → delete".NVDAObjectproperty pipeline (_getPropertyViaCache→ app getters): e.g.isProtected,name,states.Root cause
NVDA's object model fetches data by calling synchronously into the application from the core thread; the only backstop is the watchdog's multi-second cancel, which is slow and repeats — so a single hung application serialises NVDA.
Proposed approach (incremental, in-pattern)
timeoutto the existingwatchdog.cancellableExecute/CancellableCallThread(default unchanged) — a small, reusable primitive: the core waits at most N seconds, then abandons the call exactly like a watchdog cancel.AccessibleObjectFromEvent, winword text retrieval), with a per-subsystem "client jammed" breaker plus exponential backoff.NVDAObject._getPropertyViaCacheguard so that, only for a not-responding application's own objects, properties serve cached-or-Noneinstead of blocking. Normal operation is byte-identical.Scope / known limitations
This keeps NVDA responsive and self-recovering when an application hangs. It does not make UIA-only surfaces (e.g. the Windows 11 Alt+Tab switcher) readable while a UIA application jams the shared UIA client — that is an inherent Microsoft UI Automation limitation, present in stock NVDA too. Known pre-existing trade-offs amplified by the short timeout (abandoned-worker thread/handle churn under a permanent hang;
byrefbuffers handed to an abandoned worker) are called out for discussion in the accompanying pull request.Relates to
#16749, #1408
A pull request implementing this incrementally (stacked on #20168) will follow.