objc: stopping the app has to WAKE the loop, or it never stops - #11
Merged
Merged
Conversation
-[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.
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
-[NSApplication stop:]only sets a flag. AppKit reads it after it finishes processing an event, so an application sitting in-nextEventMatchingMask:untilDate:distantFuturewith 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_msgunder_DPSNextEvent, every other thread parked.StopAppsets 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.CFRunLoopStopis 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.WakeMainRunLoopis 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.