patches: update README — document async notification posting
Add deadlock prevention section to THREADING MODEL, note async posting in NOTIFICATION STRATEGY, add design decision 6a, and add deadlock regression test case (#24) to testing checklist.
This commit is contained in:
@@ -3,7 +3,7 @@ EMACS NS VOICEOVER ACCESSIBILITY PATCH
|
||||
patch: 0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch
|
||||
author: Martin Sukany <martin@sukany.cz>
|
||||
files: src/nsterm.h (+108 lines)
|
||||
src/nsterm.m (+2561 ins, -140 del, +2421 net)
|
||||
src/nsterm.m (+2588 ins, -140 del, +2448 net)
|
||||
|
||||
|
||||
OVERVIEW
|
||||
@@ -159,6 +159,30 @@ THREADING MODEL
|
||||
return result;
|
||||
}
|
||||
|
||||
Async notification posting (deadlock prevention):
|
||||
|
||||
NSAccessibilityPostNotification may synchronously invoke VoiceOver
|
||||
callbacks from a private AX server thread. Those callbacks call
|
||||
AX getters which dispatch_sync back to the main queue. If the
|
||||
main thread is still inside the notification-posting method (e.g.,
|
||||
postAccessibilityUpdates called from ns_update_end), the
|
||||
dispatch_sync deadlocks: the main thread waits for VoiceOver to
|
||||
finish processing the notification, while VoiceOver's thread waits
|
||||
for the main queue to become available.
|
||||
|
||||
To break this cycle, all notification posting goes through two
|
||||
static inline wrappers:
|
||||
|
||||
ns_ax_post_notification(element, name)
|
||||
ns_ax_post_notification_with_info(element, name, info)
|
||||
|
||||
These wrappers defer the actual NSAccessibilityPostNotification
|
||||
call via dispatch_async(dispatch_get_main_queue(), ^{ ... }).
|
||||
The current method returns first, freeing the main queue, so
|
||||
VoiceOver's dispatch_sync calls can proceed without deadlock.
|
||||
Block captures retain ObjC objects (element, info dictionary)
|
||||
for the lifetime of the deferred block.
|
||||
|
||||
Cached data written on main thread and read from any thread:
|
||||
- cachedText (NSString *): written by ensureTextCache on main.
|
||||
- visibleRuns (ns_ax_visible_run *): written by ensureTextCache.
|
||||
@@ -171,7 +195,11 @@ THREADING MODEL
|
||||
NOTIFICATION STRATEGY
|
||||
---------------------
|
||||
|
||||
Notifications are posted from -postAccessibilityNotificationsForFrame:
|
||||
All notifications are posted asynchronously via
|
||||
ns_ax_post_notification / ns_ax_post_notification_with_info
|
||||
(dispatch_async wrappers -- see THREADING MODEL for rationale).
|
||||
|
||||
Notifications are generated by -postAccessibilityNotificationsForFrame:
|
||||
which runs on the main thread after every redisplay cycle. The
|
||||
method detects three mutually exclusive events:
|
||||
|
||||
@@ -443,6 +471,18 @@ KEY DESIGN DECISIONS
|
||||
calls ns_update_end -> postAccessibilityUpdates. The BOOL flag
|
||||
breaks this recursion.
|
||||
|
||||
6a. Async notification posting (dispatch_async wrappers).
|
||||
NSAccessibilityPostNotification can synchronously trigger
|
||||
VoiceOver queries from a background AX server thread. Those
|
||||
queries dispatch_sync to the main queue. If the main thread
|
||||
is still inside postAccessibilityUpdates (or windowDidBecomeKey,
|
||||
or setAccessibilityFocused:), the dispatch_sync deadlocks.
|
||||
All 14 notification sites use ns_ax_post_notification / _with_info
|
||||
wrappers that defer posting via dispatch_async, freeing the main
|
||||
queue before VoiceOver's callbacks arrive. This follows the same
|
||||
pattern used by WebKit's AXObjectCacheMac (deferred posting via
|
||||
performSelector:withObject:afterDelay:0).
|
||||
|
||||
7. lispWindow (Lisp_Object) instead of raw struct window *.
|
||||
struct window pointers can become dangling after delete-window.
|
||||
Storing the Lisp_Object and using WINDOW_LIVE_P + XWINDOW at the
|
||||
@@ -554,10 +594,15 @@ TESTING CHECKLIST
|
||||
22. Delete a window with C-x 0. No crash should occur.
|
||||
23. Switch buffers with C-x b. VoiceOver should read new buffer.
|
||||
|
||||
Deadlock regression (async notifications):
|
||||
24. With VoiceOver on: M-x, type partial command, M-v to
|
||||
*Completions*, Tab to a candidate, Enter to execute, then
|
||||
C-x o to switch windows. Emacs must not hang.
|
||||
|
||||
Stress test:
|
||||
24. Open a large file (>5000 lines). Navigate with C-v / M-v.
|
||||
26. Open a large file (>5000 lines). Navigate with C-v / M-v.
|
||||
Verify no significant lag in VoiceOver speech response.
|
||||
25. Open an org-mode file with many folded sections. Verify that
|
||||
27. Open an org-mode file with many folded sections. Verify that
|
||||
folded (invisible) text is not announced during navigation.
|
||||
|
||||
-- end of README --
|
||||
|
||||
Reference in New Issue
Block a user