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
|
patch: 0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch
|
||||||
author: Martin Sukany <martin@sukany.cz>
|
author: Martin Sukany <martin@sukany.cz>
|
||||||
files: src/nsterm.h (+108 lines)
|
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
|
OVERVIEW
|
||||||
@@ -159,6 +159,30 @@ THREADING MODEL
|
|||||||
return result;
|
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:
|
Cached data written on main thread and read from any thread:
|
||||||
- cachedText (NSString *): written by ensureTextCache on main.
|
- cachedText (NSString *): written by ensureTextCache on main.
|
||||||
- visibleRuns (ns_ax_visible_run *): written by ensureTextCache.
|
- visibleRuns (ns_ax_visible_run *): written by ensureTextCache.
|
||||||
@@ -171,7 +195,11 @@ THREADING MODEL
|
|||||||
NOTIFICATION STRATEGY
|
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
|
which runs on the main thread after every redisplay cycle. The
|
||||||
method detects three mutually exclusive events:
|
method detects three mutually exclusive events:
|
||||||
|
|
||||||
@@ -443,6 +471,18 @@ KEY DESIGN DECISIONS
|
|||||||
calls ns_update_end -> postAccessibilityUpdates. The BOOL flag
|
calls ns_update_end -> postAccessibilityUpdates. The BOOL flag
|
||||||
breaks this recursion.
|
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 *.
|
7. lispWindow (Lisp_Object) instead of raw struct window *.
|
||||||
struct window pointers can become dangling after delete-window.
|
struct window pointers can become dangling after delete-window.
|
||||||
Storing the Lisp_Object and using WINDOW_LIVE_P + XWINDOW at the
|
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.
|
22. Delete a window with C-x 0. No crash should occur.
|
||||||
23. Switch buffers with C-x b. VoiceOver should read new buffer.
|
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:
|
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.
|
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.
|
folded (invisible) text is not announced during navigation.
|
||||||
|
|
||||||
-- end of README --
|
-- end of README --
|
||||||
|
|||||||
Reference in New Issue
Block a user