bug#72496: 31.0.50 macOS: freezes without beach ball

plug.rifling_5y--- via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <[email protected]> Sun, 2 Aug 2026 15:39:41 +0100
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
--Apple-Mail=_186F9EC5-7651-43F5-8BC0-146FDBE30F00
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=us-ascii

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.


--Apple-Mail=_186F9EC5-7651-43F5-8BC0-146FDBE30F00
Content-Disposition: attachment;
	filename=0001-Fix-a-permanent-hang-on-macOS-when-no-window-is-main.patch
Content-Type: application/octet-stream;
	x-unix-mode=0644;
	name="0001-Fix-a-permanent-hang-on-macOS-when-no-window-is-main.patch"
Content-Transfer-Encoding: quoted-printable

=46rom=200000000000000000000000000000000000000000=20Mon=20Sep=2017=20=
00:00:00=202001=0AFrom:=20Anton=20Dergunov=20<[email protected]>=0A=
Subject:=20[PATCH]=20Fix=20a=20permanent=20hang=20on=20macOS=20when=20no=20=
window=20is=20main=0A=0A'ns_send_appdefined'=20posted=20the=20event=20=
that=20ends=20[NSApp=20run]=20with=0AwindowNumber:=20[[NSApp=20=
mainWindow]=20windowNumber].=20=20While=20the=20frame=20is=0A=
miniaturized,=20or=20while=20key/main=20status=20is=20being=20handed=20=
over,=20[NSApp=0AmainWindow]=20is=20nil=20and=20the=20window=20number=20=
is=200;=20AppKit=20discards=20such=20an=0Aevent,=20so=20[NSApp=20run]=20=
never=20returns=20and=20Emacs=20never=20gets=20back=20to=20its=0Acommand=20=
loop.=0A=0AThe=20hang=20is=20permanent=20rather=20than=20intermittent=20=
because=20nothing=20retries:=0A'send_appdefined'=20has=20already=20been=20=
cleared=20by=20this=20same=20call,=20so=20every=0Alater=20EV_TRAILER=20=
re-post=20is=20suppressed,=20and=20'timed_entry'=20has=20just=20been=0A=
invalidated.=20=20Unlike=20'ns_select_1',=20'ns_read_socket_1'=20never=20=
arms=20a=0A'timed_entry',=20so=20no=20timer=20can=20wake=20the=20loop=20=
either.=0A=0ANote=20that=20[NSApp=20keyWindow]=20is=20nil=20while=20=
miniaturized=20too,=20so=20the=20scan=0Aover=20[NSApp=20windows]=20is=20=
the=20fallback=20that=20actually=20applies.=0A=0A*=20src/nsterm.m=20=
(ns_send_appdefined):=20Address=20the=20app-defined=20event=20to=20a=0A=
window=20that=20exists.=20=20(Bug#72496)=0A---=0A---=20a/src/nsterm.m=0A=
+++=20b/src/nsterm.m=0A@@=20-4686,6=20+4686,7=20@@=0A=20=20=20if=20=
(send_appdefined)=0A=20=20=20=20=20{=0A=20=20=20=20=20=20=20NSEvent=20=
*nxev;=0A+=20=20=20=20=20=20NSWindow=20*dest;=0A=20=0A=20=20=20=20=20=20=20=
/*=20We=20only=20need=20one=20NX_APPDEFINED=20event=20to=20stop=20NXApp=20=
from=20running.=20=20*/=0A=20=20=20=20=20=20=20send_appdefined=20=3D=20=
NO;=0A@@=20-4697,12=20+4698,29=20@@=0A=20=20=20=20=20=20=20=20=20=20=20=
[timed_entry=20release];=0A=20=20=20=20=20=20=20=20=20=20=20timed_entry=20=
=3D=20nil;=0A=20=20=20=20=20=20=20=20=20}=0A+=0A+=20=20=20=20=20=20/*=20=
Address=20the=20event=20to=20a=20window=20that=20actually=20exists.=20=20=
With=20no=20main=0A+=20=20=20=20=20=20=20=20=20window=20--=20=
miniaturized,=20or=20mid=20handover=20of=20key/main=20status=20--=20the=0A=
+=20=20=20=20=20=20=20=20=20window=20number=20would=20be=200=20and=20=
AppKit=20would=20silently=20discard=20the=0A+=20=20=20=20=20=20=20=20=20=
event.=20=20That=20is=20fatal=20here:=20send_appdefined=20has=20just=20=
been=20cleared=0A+=20=20=20=20=20=20=20=20=20and=20timed_entry=20=
invalidated,=20so=20nothing=20would=20ever=20end=20[NSApp=20run]=0A+=20=20=
=20=20=20=20=20=20=20again,=20and=20Emacs=20would=20hang=20forever=20=
with=20its=20UI=20unresponsive.=20=20*/=0A+=20=20=20=20=20=20dest=20=3D=20=
[NSApp=20mainWindow];=0A+=20=20=20=20=20=20if=20(dest=20=3D=3D=20nil)=0A=
+=20=20=20=20=20=20=20=20dest=20=3D=20[NSApp=20keyWindow];=0A+=20=20=20=20=
=20=20if=20(dest=20=3D=3D=20nil)=0A+=20=20=20=20=20=20=20=20for=20=
(NSWindow=20*cand=20in=20[NSApp=20windows])=0A+=20=20=20=20=20=20=20=20=20=
=20if=20([cand=20windowNumber]=20>=200)=0A+=20=20=20=20=20=20=20=20=20=20=
=20=20{=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20dest=20=3D=20cand;=0A=
+=20=20=20=20=20=20=20=20=20=20=20=20=20=20break;=0A+=20=20=20=20=20=20=20=
=20=20=20=20=20}=0A=20=0A=20=20=20=20=20=20=20nxev=20=3D=20[NSEvent=20=
otherEventWithType:=20NSEventTypeApplicationDefined=0A=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20location:=20NSMakePoint=20(0,=200)=0A=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20modifierFlags:=200=0A=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20timestamp:=200=0A-=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20windowNumber:=20[[NSApp=20=
mainWindow]=20windowNumber]=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20windowNumber:=20[dest=20=
windowNumber]=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20context:=20[NSApp=20=
context]=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20subtype:=200=0A=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20data1:=20value=0A--=20=0A2.39.5=0A=0A=

--Apple-Mail=_186F9EC5-7651-43F5-8BC0-146FDBE30F00
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=us-ascii



--Apple-Mail=_186F9EC5-7651-43F5-8BC0-146FDBE30F00--