Helper app is not relaunched after main app is updated from the Mac App Store

I have a macOS app that embeds a helper app in its bundle. That helper app is started by the main app, and from then on it runs independently.

I noticed that after updating the main app from the Mac App Store, while the helper app is running, it is not auto-restarted, unlike the main app.

What is the correct way to handle this?

The main app's bundle looks like this:

Main.app
- Contents
   - MacOS
      - Main
      - Helper.app
Answered by DTS Engineer in 802749022

Actually, there are two helper apps, one is the menu bar app (the main app configures it), and another one is the background service app refreshing data for the main app, the menu bar, and the system widgets where the app is available too.

The main app launches them with NSWorkspace.open(_ url: URL.

My expectation is that when I press the "Update" button on the Mac App Store, the helper apps will be relaunched since they were updated with its parent bundle's app.

Got it. Basically, no, the system doesn't do that. The system terminates "related apps" for the same reason it terminates your primary app, which is that it's the easiest way to minimize the potential for odd bugs/failures/issue*. However, the problem with relaunching secondary processes post-update is that it doesn't know how/why/what that process actually "does", so launching it could simply create more problems. So, again, it takes the easy/safe way out and does nothing.

*Yes, nothing actually REQUIRES an app to not be running when it's updated. Yes, it is possible to delete an actively running process/app. What can I say, Unix can be weird.

Looking at your specific helpers:

one is the menu bar app (the main app configures it),

Is the main app the only thing launching it, or is it set up as a login item? If it's set up as a login item and not being relaunched, then there are two more points I'd add here:

  1. Please file a bug on the fact it's not being relaunched and then post the bug number back here. Relaunching it seems reasonable (though I could argue both sides of that...) and, while I'm not sure we'll fix it (see #2), bugs are always a good idea.

  2. Assuming you (and your users) want this to be running "all the time" then I'd suggest moving to a SMAppService and agent(plistName:). The launchd plist gives you a lot more configurability, including the ability to have launchd relaunch you if/when your process stops running. See the manpage for "launchd.plist" for more details on that.

Login Items vs LaunchAgents

As some background here, you may be wondering why the system has two different names/mechanisms for what are basically "the same thing". Basically, this is a case of two completely unrelated mechanisms that were designed for very different problems, which have slowly evolved together. More specifically:

-Login Items were a user level feature that came from MacOS classic. The user told us what he wanted to launch and we'd launch that app for them. Eventually, app started adding themselves to the plist (so user didn't have to) and SMLoginItemSetEnabled was then added so sandboxed apps could provide that kind of functionality.

-LaunchAgents came later (10.4) and are the launchd architecture for managing processes that are tied to the "user session". Things like the Finder and the Dock are actually managed as LaunchAgents by launchd. The reason they relaunch when you terminate them is because that's what they're plist file told launchd to do. If you're curious, you can find their plist in "/System/Library/LaunchAgents/".

Eventually, launchd also took over launching Login Items and then, most recently, we created a common API for both of them in SMAppService. In any case, the main thing to understand here is that since SMAppService was introduced, a Login Item is basically just a less configurable variant of a LaunchAgent. If it does what you need, feel free to use it.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

What is the correct way to handle this?

Lots of questions before I can really answer that:

  • What's the helper actually "doing"?

  • How does the main app launch it?

  • When/how do you expect it to continue running (or relaunching)?

__
Kevin Elliott
DTS Engineer, CoreOS/Haredware

Kevin, thanks for replying!

  • Actually, there are two helper apps, one is the menu bar app (the main app configures it), and another one is the background service app refreshing data for the main app, the menu bar, and the system widgets where the app is available too.
  • The main app launches them with NSWorkspace.open(_ url: URL.
  • My expectation is that when I press the "Update" button on the Mac App Store, the helper apps will be relaunched since they were updated with its parent bundle's app.

Actually, there are two helper apps, one is the menu bar app (the main app configures it), and another one is the background service app refreshing data for the main app, the menu bar, and the system widgets where the app is available too.

The main app launches them with NSWorkspace.open(_ url: URL.

My expectation is that when I press the "Update" button on the Mac App Store, the helper apps will be relaunched since they were updated with its parent bundle's app.

Got it. Basically, no, the system doesn't do that. The system terminates "related apps" for the same reason it terminates your primary app, which is that it's the easiest way to minimize the potential for odd bugs/failures/issue*. However, the problem with relaunching secondary processes post-update is that it doesn't know how/why/what that process actually "does", so launching it could simply create more problems. So, again, it takes the easy/safe way out and does nothing.

*Yes, nothing actually REQUIRES an app to not be running when it's updated. Yes, it is possible to delete an actively running process/app. What can I say, Unix can be weird.

Looking at your specific helpers:

one is the menu bar app (the main app configures it),

Is the main app the only thing launching it, or is it set up as a login item? If it's set up as a login item and not being relaunched, then there are two more points I'd add here:

  1. Please file a bug on the fact it's not being relaunched and then post the bug number back here. Relaunching it seems reasonable (though I could argue both sides of that...) and, while I'm not sure we'll fix it (see #2), bugs are always a good idea.

  2. Assuming you (and your users) want this to be running "all the time" then I'd suggest moving to a SMAppService and agent(plistName:). The launchd plist gives you a lot more configurability, including the ability to have launchd relaunch you if/when your process stops running. See the manpage for "launchd.plist" for more details on that.

Login Items vs LaunchAgents

As some background here, you may be wondering why the system has two different names/mechanisms for what are basically "the same thing". Basically, this is a case of two completely unrelated mechanisms that were designed for very different problems, which have slowly evolved together. More specifically:

-Login Items were a user level feature that came from MacOS classic. The user told us what he wanted to launch and we'd launch that app for them. Eventually, app started adding themselves to the plist (so user didn't have to) and SMLoginItemSetEnabled was then added so sandboxed apps could provide that kind of functionality.

-LaunchAgents came later (10.4) and are the launchd architecture for managing processes that are tied to the "user session". Things like the Finder and the Dock are actually managed as LaunchAgents by launchd. The reason they relaunch when you terminate them is because that's what they're plist file told launchd to do. If you're curious, you can find their plist in "/System/Library/LaunchAgents/".

Eventually, launchd also took over launching Login Items and then, most recently, we created a common API for both of them in SMAppService. In any case, the main thing to understand here is that since SMAppService was introduced, a Login Item is basically just a less configurable variant of a LaunchAgent. If it does what you need, feel free to use it.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for another reply!

Yes, the menu bar app is launched by the main app. Also, at the login, it adds itself by using SMAppService.

The key difference between what it actually is for me, and what you describe is: When the main app is updated from the Mac App Store, while the menu bar app is running, the system DOES NOT terminate the menu bar app. It just keeps running, and some of its functionality, where it invokes an embedded command line tool stops working until the next restart. I'm not sure if it is a bug or not, but I need to find a way to relaunch it, so the correct, updated version is running.

Thanks for another reply!

Yes, the menu bar app is launched by the main app. Also, at the login, it adds itself by using SMAppService.

Which kind? Are you setting it up as an agent or loginItem?

The key difference between what it actually is for me, and what you describe is: When the main app is updated from the Mac App Store, while the menu bar app is running, the system DOES NOT terminate the menu bar app. It just keeps running, and some of its functionality, where it invokes an embedded command line tool stops working until the next restart.

How is it finding and running that tool? When the app was updated, what basically happened was that the all of the existing versions files were deleted, but your menu bar item kept running (because that's how Unix works). However, when it was deleted that "invalidated" (for lack of a better word) it's own "reference" to the file system. It can't find things like helper tools that would be in it's bundle directory because it's entire bundle structure is no longer valid.

I'm not sure if it is a bug or not,

It's not a bug as such but, yes, the system doesn't do a lot to help you sort this stuff out.

but I need to find a way to relaunch it, so the correct, updated version is running.

A few different things to know here:

  • You can use NSWorkspace.urlForApplication(withBundleIdentifier:) to look up "your apps" location, which can let you find the new app version after the old one has been deleted. As a side note there, I would only lookup your main apps bundle ID, not your helper tools. For complicated historical reasons, looking up embedded bundle IDs has never been entirely reliable.

  • Assuming your app is installed in "/Applications/" (which it probably is), you could also find it by looking for it by path/name/etc. This works fine, even in sandboxed apps.

  • If your app is an agent and you've configured your plist correctly, you should be able to simply exit() and let launchd open your new version again.

  • You can also find your app (see above) and then relaunch "yourself" using openApplication(at:configuration:completionHandler:) and "NSOpenConfiguration.createsNewApplicationInstance" to create a duplicate app run, then calling "exit". That could cause some oddness in the menubar (both items in the menu at the same time), but there are ways you can address that as well.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks a lot! Gives me a much better understanding of what I need to do.

Is there a reliable and efficient way to observe when the app loses its own reference to the file system? Or just to observe when the app's bundle is updated?

Accepted Answer

Reversing the questions for clarity...

Or just to observe when the app's bundle is updated?

To clarify here, part of what makes this complicated is that the running app's bundle wasn't actually updated. The standard "incremental" (meaning only parts of the bundle are replaced) basically looks like this:

  1. Create a copy of the existing app "somewhere else".

  2. Apply the changes to the copy.

  3. Atomically exchange/replace the original app bundle with the new app bundle.

It works this way because it minimizes the possibility of damaging the app if something goes wrong during the update process. There's not risk until #3 and #3 is either atomic (safe) on APFS or is as safe as possible anywhere else.

Is there a reliable and efficient way to observe when the app loses its own reference to the file system?

No. In terms of the files you have open, standard Unix semantics are that it's perfectly reasonable to delete a file that another process has open. Basically, the file is left in "around" until the last open occurs, at which point the file is fully deleted. Similarly, the low level system doesn't know that your app has a "special" relationship with the files it it's bundle, so it doesn't know there is something worth telling you about. In theory we could have some higher level "app tracking" API that would support something like this, but we don't currently have anything like that.

You could certainly do this monitoring yourself using FSEvents or by simply polling periodically. However, I think I'd approach the issue slightly differently and focus instead on "hiding" the issue more broadly. Something like:

  • Identify what (if any) problems your helper(s) has with running after deletion and resolve/mitigate those. Many apps don't actually have any issue with running post-deletion. They loaded or left open every component they needed when the first launched, so it doesn't matter that those components are now gone. If you are having an issue, then you may find that simple tweaks to how you load/manage things remove the issue. This obviously doesn't work for large/complicated app, but most helpers are small/simple enough that it will work.

  • Allow the old version to simple to continue running, only checking/quitting/relaunching at specific "moments" that are either essential (for example, your main app runs and needs the new version) or inherently non-disruptive (for example, immediately after the user has interacted with your menu bar app and you've finished whatever work they've triggered).

Basically, I think it's easier to focus on minimizing any disruption instead of trying to aggressively "track" what's happening in the system and immediately updating.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Okay, I got it! Thanks a lot for taking the time and providing great explanations :)

You're very welcome and good luck!

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Helper app is not relaunched after main app is updated from the Mac App Store
 
 
Q