bug#72496: 31.0.50 macOS: freezes without beach ball
Eli Zaretskii <[email protected]> Mon, 03 Aug 2026 14:29:37 +0300
| Newsgroups | gmane.emacs.bugs |
|---|---|
| Message-ID | <[email protected]> |
> Date: Sun, 2 Aug 2026 15:39:41 +0100 > From: plug.rifling_5y--- via "Bug reports for GNU Emacs, > the Swiss army knife of text editors" <[email protected]> > > I think I have found why the app-defined events get lost, and it can be > reproduced on demand. > > 'ns_send_appdefined' poststhe event with > > windowNumber: [[NSApp mainWindow] windowNumber] > > While the frame is miniaturized -- or while key/main status is being handed > over -- [NSApp mainWindow] is nil, so that argument is 0. AppKit discards > the event, and [NSApp run] never returns. > > That explains why it is permanent rather than intermittent. Nothing > retries: 'send_appdefined' has already been set to NO by the same call, so > every later EV_TRAILER re-post is suppressed, and 'timed_entry' has just > been invalidated. 'ns_read_socket_1', unlike 'ns_select_1', never arms a > 'timed_entry', so no timer can wake the loop either. A single lost event > wedges Emacs for good. > > Evidence from a live hung process > --------------------------------- > > macOS 26.5.1, Emacs 30.2, built from source so lldb could attach. Emacs > had been hung for 59 minutes: > > [NSApp isRunning] YES -- so stop: was never called > [NSApp mainWindow] nil > [NSApp keyWindow] nil > send_appdefined NO -- latched; no re-post possible > timed_entry NULL -- no timer wake-up either > n_emacs_events_pending 99, and climbing > > The stack was: > > wait_reading_process_output process.c:5499 > redisplay_preserve_echo_area (13) xdisp.c:17757 > flush_frame > ns_flush_display > ns_read_socket_1 > -[EmacsApp run] -> [super run] <- blocked here > > n_emacs_events_pending climbing is the telling part: AppKit was delivering > input normally and Emacs was queueing it. Nothing was blocked or starved. > The only thing missing was the event that ends [NSApp run]. > > A/B test on that same hung process > ---------------------------------- > > 1. Running Emacs' own code inside the hung process -- > send_appdefined = YES; ns_send_appdefined (-1); -- changed nothing. > mainWindow was still nil, so the window number was 0 again. > > 2. Posting a byte-identical NSEventTypeApplicationDefined event that > differed only in carrying a real window number unfroze Emacs > immediately. > > The window number is the entire difference. > > Reproducer > ---------- > > Miniaturizing makes both mainWindow and keyWindow nil, deterministically. > Any subprocess status change then goes status_notify -> > redisplay_preserve_echo_area (13) -> flush_frame -> ns_flush_display -> > ns_read_socket_1. So: > > (run-at-time 3 3 (lambda () (start-process "probe" nil "sleep" "1"))) > > then miniaturize the frame and wait. Emacs is dead, and deminiaturizing > does not bring it back. > > This also matches the reports of freezes on app activation and on the green > zoom button's tiling menu: those take main/key status away too. > > Verification of the patch > ------------------------- > > With the patch applied, in the same miniaturized state ([NSApp mainWindow] > == nil), a breakpoint on -[NSApplication postEvent:atStart:] shows: > > type = 15 (NSEventTypeApplicationDefined) > windowNumber = 3516 > data1 = -1 > > and 80 seconds of the reproducer above, with about 27 subprocess exits, > produced no freeze. > > Note that keyWindow is nil while miniaturized as well, so it is the scan > over [NSApp windows] that actually does the work here; a fallback that only > tried keyWindow would not fix anything. > > The patch is against master (the function is unchanged from Emacs 30.2, and > it applies to both). > > I have not signed FSF copyright papers. The change is about 12 lines of > code plus a comment; I am happy to shorten it, or for someone to rewrite it > on the strength of the diagnosis alone if that is easier. Alan and Stéphane, any comments or suggestions? > From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 > From: Anton Dergunov <[email protected]> > Subject: [PATCH] Fix a permanent hang on macOS when no window is main > > 'ns_send_appdefined' posted the event that ends [NSApp run] with > windowNumber: [[NSApp mainWindow] windowNumber]. While the frame is > miniaturized, or while key/main status is being handed over, [NSApp > mainWindow] is nil and the window number is 0; AppKit discards such an > event, so [NSApp run] never returns and Emacs never gets back to its > command loop. > > The hang is permanent rather than intermittent because nothing retries: > 'send_appdefined' has already been cleared by this same call, so every > later EV_TRAILER re-post is suppressed, and 'timed_entry' has just been > invalidated. Unlike 'ns_select_1', 'ns_read_socket_1' never arms a > 'timed_entry', so no timer can wake the loop either. > > Note that [NSApp keyWindow] is nil while miniaturized too, so the scan > over [NSApp windows] is the fallback that actually applies. > > * src/nsterm.m (ns_send_appdefined): Address the app-defined event to a > window that exists. (Bug#72496) > --- > --- a/src/nsterm.m > +++ b/src/nsterm.m > @@ -4686,6 +4686,7 @@ > if (send_appdefined) > { > NSEvent *nxev; > + NSWindow *dest; > > /* We only need one NX_APPDEFINED event to stop NXApp from running. */ > send_appdefined = NO; > @@ -4697,12 +4698,29 @@ > [timed_entry release]; > timed_entry = nil; > } > + > + /* Address the event to a window that actually exists. With no main > + window -- miniaturized, or mid handover of key/main status -- the > + window number would be 0 and AppKit would silently discard the > + event. That is fatal here: send_appdefined has just been cleared > + and timed_entry invalidated, so nothing would ever end [NSApp run] > + again, and Emacs would hang forever with its UI unresponsive. */ > + dest = [NSApp mainWindow]; > + if (dest == nil) > + dest = [NSApp keyWindow]; > + if (dest == nil) > + for (NSWindow *cand in [NSApp windows]) > + if ([cand windowNumber] > 0) > + { > + dest = cand; > + break; > + } > > nxev = [NSEvent otherEventWithType: NSEventTypeApplicationDefined > location: NSMakePoint (0, 0) > modifierFlags: 0 > timestamp: 0 > - windowNumber: [[NSApp mainWindow] windowNumber] > + windowNumber: [dest windowNumber] > context: [NSApp context] > subtype: 0 > data1: value > -- > 2.39.5