Rosetta 2 won’t let the undead die

You might think that apps are either running or they’re not, but there are actually four different states a macOS ap can be in (in addition to those a user shouldn’t directly encounter, such as suspended). I last looked at this three years ago, in Sierra and High Sierra, and on Intel Macs little seems to have changed. What has happened since is that Apple Silicon Macs have arrived, and this has become even more complicated, because of strange behaviour in Rosetta 2, which won’t let undead apps die.

To understand this, I’ll summarise what happened with apps until last month, with the aid of four small example apps, which you can download from here: StateTestDemo. Note that these aren’t notarized. If you want to spare yourself the bother of getting each through Gatekeeper, you may want to strip the quarantine flag from that Zip archive before unZipping it.

Each of those apps opens a single window, and does nothing useful. Open each in turn, then close each of those windows and switch to another app like Finder. A little while afterwards, you’ll see that two of them apparently quit of their own accord, while the other two are put into App Nap.

There are two flags in the apps’ Info.plist files which control this behaviour: NSAppSleepDisabled, which is supposed to control App Nap, and NSSupportsAutomaticTermination which determines whether they are quit when they don’t have a window open and go into the background. Since Sierra, the first of those seems to have been largely ignored by macOS, which puts apps into App Nap regardless.

It’s the NSSupportsAutomaticTermination which has greater effect: when set, it means that macOS will decide when to quit the app, not you. There was a time when many apps set this, and the moment that you weren’t looking, they’d quit, simply because they didn’t have a window open and got put into the background. Even Apple’s Xcode still does this.

This is the type of behaviour which is ideal for iOS and iPadOS, which can’t put dormant apps into cache and have limited physical memory. But on a Mac with 32 GB of RAM and almost unlimited cache, it’s taking control from the user. Some days I want Xcode sat ready in the background, and don’t want it to keep quitting like that just because it feels like a break.

For a while, opening such an app remains almost instant, as macOS doesn’t actually quit that app, but leaves it loaded and ready to run again, in a state which I termed undead. There is good sense behind this too, in that returning to an undead app can make it appear that it has loaded instantly, which for a large app like Xcode is impressive. What I object to is that this isn’t a behaviour that the user can readily control, as it’s baked into the Info.plist, which you can’t tamper with as it immediately breaks that app’s signature. When an app becomes undead, the simple way of forcing it to quit altogether is to bring up the Finder’s Force Quit Applications dialog, and kill it there.

So there are four app states:

  • Running and not napping,
  • Running, in App Nap, and still fully accessible,
  • Running, in App Nap (or shortly to go into it), but hidden from normal user access – undead,
  • Not running at all.

There’s another interesting aspect to these behaviours. Quit all other apps, launch the four test apps, close their windows, and bring Finder to the front. That should leave two of them in App Nap, and two undead. Now restart your Mac, and you’ll discover that the two in App Nap have been reopened for you, in their windowless App Nap state, while the two that were undead have been left alone. macOS has correctly restored app status following the restart, although you might not have been aware that this extends to apps which are napping.

As I wrote at the start, this has become more complex on M1 Macs with Rosetta 2. I’ve no reason to believe that, for native ARM apps, the behaviours described above continue, but that doesn’t seem to be the case when it comes to Intel apps running with Rosetta translation. They appear to persist far longer in an undead state than on Intel systems. I’m unsure whether this is deliberate, to minimise the cost of loading them again should the user decide to open that app once more, or a passing phase. But if you run many apps in Rosetta which join the ranks of the undead, it could get inconvenient. At least, as with Intel Macs, those undead apps are still listed in the Force Quit dialog, so you can have a clearout when you want.

Postscript:

Thanks to Felix Schwarz @felix_schwarz for discovering another interesting behaviour. When napping apps are restored after a restart, they aren’t fully loaded and put into App Nap. Instead, macOS starts to load them and then stops at _dyld_start, so they only take around 8 KB of memory and don’t open any of their other files, such as frameworks. Only when the user opens those apps do they complete loading, and can then be put back into normal App Nap again. You can test this behaviour for yourself using the supplied example apps.

This can cause strange problems with some apps which you may leave running in App Nap. When in App Nap, they can be awoken by different events as well as the user bringing them to the front. When they’re in this stopped state, they have insufficient code loaded to respond to events which would normally wake them from App Nap, unless you manually wake them up after starting up. One workaround for this is to leave a small window open in the app, which prevents them from being put into App Nap, and ensures that they will be loaded and running properly after the next startup.