Skip to content

Sparkle does not restart app after update completes (sandboxed app with SUEnableInstallerLauncherService=true) #2725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jeremyhu opened this issue May 14, 2025 · 4 comments · May be fixed by #2726

Comments

@jeremyhu
Copy link

Description of the problem

Sparkle does not restart app after update completes.

Do you use Sandboxing in your app?

My app is sandboxed. It has SUEnableInstallerLauncherService=true set in Info.plist and sanbox exceptions to connect to the following ports:

    <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>

The update works fine. But users need to manually quit and relaunch the app.

Version of Sparkle.framework in the latest version of your app

2.6.4

Version of Sparkle.framework in the old version of app that your users have (or N/A)

2.6.4

Sparkle's output from Console.app

2025-05-14 01:20:05.169512-0700 0x22177f   Info        0xca1a25             77180  0    Redacted: (SparkleUpdater) [Redacted:Redacted] Sparkle Update will install update
2025-05-14 01:20:10.097188-0700 0x22177f   Info        0xca1a25             77180  0    Redacted: (SparkleUpdater) [Redacted:Redacted] Sparkle Update finished update cycle for update error: nil
2025-05-14 01:21:25.097836-0700 0x226b65   Info        0xcb51a0             77939  0    Redacted: (SparkleUpdater) [Redacted:Redacted] Sparkle Update finished downloading
2025-05-14 01:21:27.403964-0700 0x226f8b   Default     0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] Extracting using '/usr/bin/ditto' '-x' '-k' '-' < '/var/root/Library/Caches/Redacted/org.sparkle-project.Sparkle/Installation/rz09IAFKF/Redacted..zip' '/var/root/Library/Caches/Redacted/org.sparkle-project.Sparkle/Installation/rz09IAFKF/c8893zK1s'
2025-05-14 01:21:29.805432-0700 0x226f89   Default     0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] OK: EdDSA signature is correct
2025-05-14 01:21:29.826133-0700 0x226f89   Error       0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] Code signature of the new version doesn't match the old version: identifier RedactedTrampoline and certificate root = H"0c2b0a9baf21fd7ce52e50555bfd347deafbcad5". Please ensure that old and new app is signed using exactly the same certificate.
2025-05-14 01:21:29.830408-0700 0x226f89   Default     0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] old info: {
<redacted>
}
2025-05-14 01:21:29.831057-0700 0x226f89   Default     0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] new info: {
<redacted>
}
2025-05-14 01:21:30.043080-0700 0x226fe2   Default     0xcb51a0             77971  0    Autoupdate: [org.sparkle-project.Sparkle:Sparkle] Skipping atomic rename/swap and gatekeeper scan because Autoupdate is not signed with same identity as the new update Redacted.app
2025-05-14 01:21:30.064454-0700 0x226b65   Info        0xcb51a0             77939  0    Redacted: (SparkleUpdater) [Redacted:Redacted] Sparkle Update will install update
2025-05-14 01:21:38.898940-0700 0x226b65   Info        0xcb51a0             77939  0    Redacted: (SparkleUpdater) [Redacted:Redacted] Sparkle Update finished update cycle for update error: nil%

Note the SparkleUpdater logs there are from our SPUUpdaterDelegate (which was only emitting logging to help track things down here...)

Steps to reproduce the behavior

I check for updates and am presented with the update release notes.
I click Install Update in the release notes window.

Expected (based on behavior I see in other apps):
I should get a dialog with a "Install and Relaunch" button.
Tapping on "Install and Relaunch" should prompt for authentication if required, install, and relaunch the app.

Observed:
I am immediately prompted to auth for the install
I see an Extracting Update dialog and the soon after an 'Update Installed' dialog. The app does not relaunch, and the user is not told to relaunch it.

--

I debugged this a bit in lldb and what I observed is that we're getting a SPUInstallationFinishedStage3 message while _relaunch is set to NO.

It looks like _relaunch is only set to YES in -installWithToolAndRelaunch:displayingUserInterface:, but that isn't getting called.

That in turn is because finishInstallationWithResponse:displayingUserInterface: isn't being called...

That looks to be usually called in -basicDriverDidFindUpdateWithAppcastItem:secondaryAppcastItem: when state is SPUUserUpdateStageInstalling , but the only time I ever see that called is for SPUUserUpdateStageNotDownloaded.

It looks like this is just a gap in how SUEnableInstallerLauncherService=true support was implemented. That seems to completely bypass these function calls, which results in _relaunch never getting set to YES, so we end up passing relaunch=no when receiving SPUInstallationFinishedStage3...

@jeremyhu
Copy link
Author

-installerDidFinishPreparationAndWillInstallImmediately:silently: is being called, but since willInstallImmediately is set to YES, that also doesn't call into -finishInstallationWithResponse:displayingUserInterface: ...

I suspect the fix here is to set _relaunch (and maybe other state?) in-installerDidFinishPreparationAndWillInstallImmediately:silently: for the willInstallImmediately case.

@jeremyhu
Copy link
Author

Looking in Updater, I see we get sent sendTerminationSignal, but _targetRunningApplication is nil:

(lldb) bt
* thread #4: tid = 0x29f81b, function: -[InstallerProgressAppController sendTerminationSignal] , stop reason = breakpoint 2.1 3.1
  * frame #0: sp: 0x000000016b762100 fp: 0x000000016b762130 size:    48 pc: 0x00000001048420f0 Updater`-[InstallerProgressAppController sendTerminationSignal](self=0x0000600001a84060, _cmd=<unavailable>) + 12 at InstallerProgressAppController.m:363 [opt]
...
(lldb) print self
(InstallerProgressAppController *) 0x0000600001a84060 {
  NSObject = {
    isa = InstallerProgressAppController
  }
  _application = 0x000000012f206e60
  _targetRunningApplication = nil
  _connection = 0x0000600000f8c000
  _oldHost = 0x0000600003e88ba0
  _oldHostBundlePath = 0x0000600003088a80 @"/Applications/Redacted.app"
  _statusInfo = 0x0000600003088e40
  _applicationBundle = 0x0000600001d8c500 "/Applications/Redacted.app"
  _normalizedPath = nil
  _delegate = 0x0000600003084000
  _terminationCompletionHandler = nil
  _connected = true
  _repliedToRegistration = true
  _shouldRelaunchHostBundle = false
  _systemDomain = true
  _submittedLauncherJob = false
  _willTerminate = false
  _applicationInitiallyAlive = false
}

So something seems wrong with -runningApplicationsWithBundle:

(lldb) expr [self runningApplicationsWithBundle:self->_applicationBundle]
(__NSArray0 *) $0 = 0x00000001f59f6cf8 @"0 elements"
(lldb) expr [NSRunningApplication runningApplicationsWithBundleIdentifier:self->_applicationBundle.bundleIdentifier]
(__NSSingleObjectArrayI *) $1 = 0x0000600003c88450 @"1 element"

@jeremyhu
Copy link
Author

(lldb) p runningApplication
(NSRunningApplication *) 0x0000600000d68000 {
  NSObject = {
    isa = NSRunningApplication
  }
  _asn = 0x0000600002369f80
  _helpers = 0x0000000000000000
  _obsInfo = nil
  _lock = 0x000060000076c240
  _bundleID = 0x0000600002d68300 @"redacted but expected"
  _localizedName = 0x8a8b1315455318ca @"Redacted"
  _bundleURL = 0x0000600000760120 @"file:///Applications/Redacted.app/Contents/MacOS/Redacted/"
  _executableURL = nil
  _launchDate = nil
  _icon = nil
  _pid = 92388
  _lastSeed = 542
  _activeSeed = 0
  _staleSeed = 3
  _obsMask = 0
}

Note: 

  _bundleURL = 0x0000600000760120 @"file:///Applications/Redacted.app/Contents/MacOS/Redacted/"

NOT file:///Applications/Redacted.app


So sparkle fails both the expected `[candidatePathComponents isEqualToArray:bundlePathComponents]` check:

* thread #1: tid = 0x2b91ab, function: -[InstallerProgressAppController runningApplicationsWithBundle:] , stop reason = breakpoint 2.1 3.1
    frame #0: sp: 0x000000016baadc70 fp: 0x000000016baaddb0 size:   320 pc: 0x0000000104355818 Updater`-[InstallerProgressAppController runningApplicationsWithBundle:](self=<unavailable>, bundle=<unavailable>) + 512 at InstallerProgressAppController.m:241 [opt]
   238 	                if ([candidatePathComponents isEqualToArray:bundlePathComponents]) {
   239 	                    [matchedRunningApplications addObject:runningApplication];
   240 	                } else if (matchedRunningApplications.count == 0 && candidatePathComponents.count > 0 && bundlePathComponents.count > 0) {
-> 241 	                    NSString *lastBundlePathComponent = bundlePathComponents.lastObject;
   242 	                    NSString *lastCandidatePathComponent = candidatePathComponents.lastObject;
   243 	                    if (lastBundlePathComponent != nil && lastCandidatePathComponent != nil && [lastBundlePathComponent isEqualToString:lastCandidatePathComponent] && [candidatePathComponents containsObject:@"AppTranslocation"]) {
   244 	                        [potentialMatchingTranslocatedRunningApplications addObject:runningApplication];
Target 0: (Updater) stopped.
(lldb) p bundlePathComponents
(__NSArrayM *) 0x0000600001bf0330 @"3 elements" {
  [0] = 0xa1916a096bd38b3d @"/"
  [1] = 0x00000001f5a89c50 @"Applications"
  [2] = 0x00006000015f07e0 @"Redacted.app"
}
(lldb) p candidatePathComponents
(__NSArrayM *) 0x0000600001bf0150 @"6 elements" {
  [0] = 0xa1916a096bd38b3d @"/"
  [1] = 0x00000001f5a89c50 @"Applications"
  [2] = 0x00006000015f0840 @"Redacted.app"
  [3] = 0xa1a7736b6b8b1af5 @"Contents"
  [4] = 0xa19143aeda633a1d @"MacOS"
  [5] = 0x9823dbbdd0e13e8d @"Redacted"
}

I think there's an OS bug at play here, which is tickled by how our app is bundled (we have two executables in Contents/MacOS, one which needs to possibly do some work before then exec()ing the other...)

Can we add in a special case to handle ths as well... perhaps ignoring the last three of n-2,n-1 are Contents/MacOS?

jeremyhu added a commit to jeremyhu/Sparkle that referenced this issue May 14, 2025
… bundle path of a running application contains Contents/MacOS/Executable

Fixed: sparkle-project#2725
Signed-off-by: Jeremy Huddleston Sequoia <[email protected]>
@zorgiepoo
Copy link
Member

I think working around this sounds good in general, left a couple small comments on the PR.

(Great job on debugging, it's a bit of a maze but not debugged to this extent often.)

jeremyhu added a commit to jeremyhu/Sparkle that referenced this issue May 28, 2025
… bundle path of a running application contains Contents/MacOS/Executable

Fixed: sparkle-project#2725
Signed-off-by: Jeremy Huddleston Sequoia <[email protected]>
jeremyhu added a commit to jeremyhu/Sparkle that referenced this issue May 28, 2025
… bundle path of a running application contains Contents/MacOS/Executable

Fixed: sparkle-project#2725
Signed-off-by: Jeremy Huddleston Sequoia <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants