Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

zeppenwolf

macrumors regular
Original poster
Nov 17, 2009
129
3
MyKillerApp implements a "Relaunch" function. If the "ok" button is pressed, the app launches a tiny standalone app via NSTask, then terminates itself. The standalone app simply does a sleep(1) while() the process which invoked it still exists, (IOW, MyKillerApp that launched it), then calls launchApplication: on [MyKillerApp's bundlePath], which was passed in as a parameter.

Code:
int main( int argc , char *argv[] ) {

    SAutoDrain      draino;
    
    pid_t parentPID = atoi(argv[2]);
    if ( parentPID ) {
        ProcessSerialNumber psn;

        while ( GetProcessForPID(parentPID, &psn) != procNotFound )
            sleep(1);
    }
    else
        NSLog( @"Relaunch::parentPID is zero" );

    // zero parentPID is accepted, to allow running the executable from XCode
    NSString*   appPath = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];

    if ( [[NSWorkspace sharedWorkspace] launchApplication:[appPath stringByExpandingTildeInPath]] )
        return 0;

    NSLog(@"Relaunch: could not launchApplication with path: %@", [appPath stringByExpandingTildeInPath] );
    return -1;
}

It works, in fact it's a beautiful thing... but it only works some of the time. At other times, launchApplication: fails, and I get this output:
Code:
2015-02-22 17:15:52.804 Relaunch[10743:707] LSOpenFromURLSpec() returned -10827 for application /Volumes/temp/builds/MyKillerApp/Debug/MyKillerApp.app path (null).
2015-02-22 17:15:52.805 Relaunch[10743:707] Relaunch: could not launchApplication with path: /Volumes/temp/builds/MyKillerApp/Debug/MyKillerApp.app

From Launch Services:
Code:
  kLSNoExecutableErr            = -10827, /* The executable is missing*/

Taken at face value, the error is clearly absurd, since the path is exactly the same app which just launched the standalone a second ago. ( verified by copy & paste into the shell )

Since my app is sandboxed, I immediately suspected that I'm breaking "the rules", but on reflection...

If it were Sandbox which was offended, then I wouldn't expect the exe to work some or even most of the time, I'd expect Sandbox to come down on me like the zero-tolerance ton of bricks it seems to be in other ways, and my exe would never work at all.

Moreover, if it were Sandbox, I would expect to be denied when I tried to launch the standalone. But I have *never* failed to launch it, it is just an error *in* the standalone when, of all things, I just want to relaunch the app...

Sorry! I'm probably yakking too much already:

Q) Can I implement this kind of relaunch without offending sandbox, and if so, how? Can it be done without resorting to the unappetizing "SMJobBless" sample code paradigm, which requires a user password just to get going ?
 
Couldn't your app just launch a second instance of itself directly and then have either instance close the first once the second is done starting up?
 
Couldn't your app just launch a second instance of itself directly and then have either instance close the first once the second is done starting up?

Well, that's a great idea. Good thinking. There's a problem with it tho.

The reason I'm doing Relaunch the way I am is that I'm leveraging the NSWindowResoration protocol. So what happens is the that the "old" app quits and is replaced by the "new" app with all the open windows in exactly the same place...

The average user would never suspect that the app had literally quit then relaunched-- when the system is responsive, it looks only like a "refresh"-- it happens in the blink of an eye, which is what I need.

Now, your suggestion would work fine, if and only if I can figure some way to [NSWindowResoration flushChanges]. Actually, your method would work better, since it would be possible to have some overlap between the old and the new, and the user would not even witness windows disappearing, even for the merest second...

But you might be aware, when implementing NSWR, the changes are saved by the OS... when it feels like it has time on its hands. Which is generally great, but if my "new" is launched before the most recent changes in my "old" are saved, it will defeat the magic of looking like a refresh when window positions or object values very recently changed.

I'm looking through NSWindowResoration and I don't see any way to flush()...

Well, nobody has chimed in on the literal problem described, so I'll try to implement the AoW paradigm and see what happens-- possibly, *hopefully*, if "old" app sends a terminate message to itself, then when the appWillTerminate() in the delegate is called, the NSWR will have *already* flushed itself, in which case I have a better solution than I was implementing in the first place.

Fingers xed, thx.
 
The symptoms suggest that some OS component wants exclusive access to your app bundle for a short time as it terminates the app. Whatever mechanism it uses, it has the effect of making your app temporarily "invisible".

This looks like a classic race condition. Two things are happening concurrently, and you can't really control the timing and order of the events. In this case, you have almost no control over one of the activities -- the app termination details are handled by the OS.

Your while loop is an attempt to synchronize your own actions to the stuff happing as part of app termination. From the symptoms, you must be checking the "wrong" condition. Crucial work is still happening for some time after your while test becomes true. Until that work is complete, you can't relaunch. But you probably don't know, and may not be able to determine, the "right" condition to check, that will guarantee that you will be able to relaunch successfully.

There are (at least) two ways to move forward:
1) Try to understand exactly what's happening. Do more experiments, dig into documentation, seek out technical expertise at Apple, etc. But this is probably not a great use of your time, and whatever you learn may well change in some future OS release. If it's not a published and documented OS feature, you shouldn't assume it will remain the same.

2) Testing "will it work" conditions in concurrent code is often futile anyway. The condition may be true when you test it, and no longer true when the very next line of code tries to do "it". Avoid the "will it work" pattern whenever you can. Instead, aim for the "did it work" pattern: just try "it", then check to see if "it" worked. If it failed, try again, probably after a short sleep.

In your case, I would just put the "launchApplication" call in a loop, repeating it a handful of times until it succeeds. If it fails too many times, you should give up.

Another suggestion: try calling "launchApplicationAtURL: options: configuration: error:" instead of "launchApplication". If you NSLog the resulting error object, you may get more detail about the failure.

One more idea: is there a notification (associated with the app termination) you could register for, so you have a way to know when the app has "really" terminated?

Do you have any evidence that the problem has anything to do with sandboxing? Can you try building the app without sandboxing and see if the symptoms change?
 
The symptoms suggest that some OS component wants exclusive access to your app bundle for a short time as it terminates the app.

I believe that you're barking up the wrong tree there. But before I go any farther, thank you for posting, I definitely appreciate the way you outlined how you look at the problem.

I, also, was uncomfortable about the issue of knowing *when* an application quits: What does that mean, really? To what part of the OS? Is it when the unix executable quits? Or does the UI server maintain a list of running apps, which is updated sometime after the exe quits, or... ?

But anyhow, I think I've proven that this is not the problem. I outlined before how I originally attempted to implement my paradigm using the small Relaunch executable... MyKillerApp launches Relaunch.exe, MyKillerApp calls terminate:, Relaunch waits for MyKillerApp to die, Relaunch calls open: on MyKillerApp's bundle...

Can we agree that MyKillerApp, after calling terminate:, should cease to exist a full five seconds later, with 99% probability ? I think so.

So IF I prepend my Relaunch.exe code with a call to sleep(5), AND the call to launchApplication: still fails with regularity, THEN the problem just isn't about *when* the OS considers an application to be terminated.

Agreed? Well, that's what I did, and that's what happened-- it still fails.

I also tacked on your other suggestion, of not taking "no" for an answer, so to speak, and I attempted ( *after* waiting five seconds ), to launch the "new" app 20 times, if errors persisted. ( I tried that in both methodologies, my original one, and the one AoW suggested above ). Still no go-- it just fails 20 times.

One more idea: is there a notification (associated with the app termination) you could register for, so you have a way to know when the app has "really" terminated?

Yes, there is, BTW. It so happens that my app makes enormous use of that notification in other places... but for this issue it seems provable that it doesn't matter for the reasons listed above.

Do you have any evidence that the problem has anything to do with sandboxing? Can you try building the app without sandboxing and see if the symptoms change?

Yes. The score is:

WITHOUT SANDBOXING
Relaunch.exe method: 100% success
App clones itself method: 100% success

WITH SANDBOXING
Relaunch.exe method: 50% success
App clones itself method: 0% success

Interestingly, (perhaps?!?), whenever the attempt fails, I get the same error code, as listed above. But the Relaunch method gets a different error *string* than the Clone method... The Clone method talks about "corruption", whereas the Relaunch method talks about an "executable missing".

Anyway, what a farce!! Your comments welcome, of course, but it surely looks like I now have to go where the Apple gurus hang out...

This whole sandboxing thing really is a bridge too far... Safety is great, but what happened to the idea of respecting the user's choice ? Oops, I better stop there-- rant meter starting to smoke...
 
Just for fun, try replacing the "sleep(1)" here:
Code:
        while ( GetProcessForPID(parentPID, &psn) != procNotFound )
            sleep(1);
with a shorter delay. Try "usleep(500000)", and then reduce the delay in steps and see if the failure probability changes.

When I first read your problem description, I thought you weren't waiting long enough. Now I wonder if you're waiting too long.
 
Have you read the section "Launching Helpers with Launch Services" in the "App Sandbox Design Guide" manual?

I see this potential error in the text:
"The operation couldn’t be completed. (OSStatus error -10827.)"

Also, in the section "Security-Scoped Bookmarks and Persistent Resource Access", I see this text:
"Your app’s access to file-system locations outside of its container—as granted to your app by way of user intent, such as through Powerbox—does not automatically persist across app launches or system restarts. When your app reopens, you have to start over. (The one exception to this is for files open at the time that your app terminates, which remain in your sandbox thanks to the OS X Resume feature)."

This suggests there is some special stuff happening around App termination.

Does the system treat your path as "outside of its container" because of the way it's formed? Maybe you need to use a security-scoped bookmark.

My comments may be useless and obvious, since I haven't built anything sandboxed yet.
 
Have you read the section "Launching Helpers with Launch Services" in the "App Sandbox Design Guide" manual?

I had not read that section, because the Sandbox Guide pdf on my hard drive didn't have that section. Heh. But now that I've got the most current version of the guide, I can see that the part you're pointing out is exactly where the problem is. I just don't even doubt it:

A sandboxed app is allowed to launch a helper using Launch Services if at least one of these conditions has been met:

Both the app and helper pass the Gatekeeper assessment. By default that means both are signed by the Mac App Store or with a Developer ID.

* Note: This does not include your development ("Mac Developer") or distribution ("3rd Party Mac Developer Application") signing identities.

I've been using the "Mac Developer" signature to, er, develop. So clearly I need to change that if I want to get this business to work at this point. ( The other option Apple seems to be giving me is to write all my code, then after it's accepted and signed by the Mac Store then it will work. Right! )

Well, I've spent all day trying to sign my app with the "Developer ID" one, and I just can't get it to take. Round and round and round, to the Apple site to try generating a new provisioning profile, to Keychain Access to delete ghost entries, maybe, to mucking around in Xcode...

It's the same old story for me, as far as this code signing stuff goes.

But regardless, at least I know where the problem is now. Thanks so much, er, "YouFromThere". :)
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.