Skip to content

objc: stopping the app has to WAKE the loop, or it never stops - #11

Merged
tannevaled merged 2 commits into
mainfrom
stop-has-to-wake-the-loop
Aug 31, 2026
Merged

tannevaled merged 2 commits into
mainfrom
stop-has-to-wake-the-loop

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

-[NSApplication stop:] only sets a flag. AppKit reads it after it finishes processing an event, so an application sitting in -nextEventMatchingMask:untilDate:distantFuture with nothing happening does not stop at all: it keeps waiting for an event that is never coming, and -[NSApplication run] never returns.

That is not a corner case — it is the ordinary one for a menu-bar program. Measured today: a desk waiting for a pair of glasses ran its tray's loop while it waited, found the glasses, asked the loop to stop, and hung at 0% CPU until somebody moved the mouse. Nobody was moving the mouse; the whole point of that program is that it starts by itself. The sample showed the main thread in mach_msg under _DPSNextEvent, every other thread parked.

StopApp sets the flag and then stops the main thread's run loop, which makes AppKit's event wait return, which lets it read the flag it was given. CFRunLoopStop is one of the few CoreFoundation calls documented as safe from another thread, which is what makes this callable from wherever the decision to stop was taken. WakeMainRunLoop is the same thing without the flag, for a caller that wants the loop back for one turn rather than for good.

The test asks a run loop to run for ten seconds and requires it to return anyway — it comes back in fifty milliseconds. That framing matters: an empty run loop returns at once whatever anyone does to it, so waking one would have proved nothing.

Also covered: stopping when nothing is running (a no-op twice over, which is what a program tidying up after a failure does), a CoreFoundation that will not open, and a main run loop that is not there — none of which may panic on the nil function pointers a failed load leaves behind.

-[NSApplication stop:] only sets a flag. AppKit reads it after it
finishes processing an event, so an application sitting in
-nextEventMatchingMask:untilDate:distantFuture with nothing happening
does not stop at all: it keeps waiting for an event that is never
coming, and -[NSApplication run] never returns.

That is not a corner case, it is the ordinary one for a menu-bar
program. Measured today: a desk waiting for a pair of glasses ran its
tray's loop while it waited, found the glasses, asked the loop to stop
-- and hung at 0% CPU until somebody moved the mouse. Nobody was moving
the mouse; the whole point of that program is that it starts by itself.
The sample showed the main thread in mach_msg under _DPSNextEvent with
every other thread parked.

StopApp sets the flag and then stops the MAIN thread's run loop, which
makes AppKit's event wait return, which lets it read the flag it was
given. CFRunLoopStop is one of the few CoreFoundation calls documented
as safe from another thread, which is what makes this callable from
wherever the decision to stop was taken. WakeMainRunLoop is the same
thing without the flag, for a caller that wants the loop back for one
turn rather than for good.

The test attaches nothing but asks a run loop to run for TEN SECONDS and
requires it to return anyway: it comes back in fifty milliseconds. An
empty run loop returns at once whatever anyone does to it, so waking one
would have proved nothing.
The race detector caught it: the goroutine that wakes the loop reads
package variables that the test's cleanup writes, and nothing said which
happened first. It is joined now.
@tannevaled
tannevaled merged commit 37fa4b8 into main Aug 31, 2026
3 checks passed
@tannevaled
tannevaled deleted the stop-has-to-wake-the-loop branch August 31, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant