Quit helper app when main app terminates

  • What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.

    Thanks,

    Jerry Krinock
  • The best way? I have no idea… This is what I do, and it works.

    I store the path to my helper app in 'path', and do the following when my app is about to quit:

    //---------------------------------------------------------------------
    - (void)applicationWillTerminate:(NSNotification *)aNotification
    //---------------------------------------------------------------------
    {
    [super applicationWillTerminate:aNotification];

    AppleEvent tAppleEvent, tReply;
    AEBuildError tAEBuildError;
    ProcessSerialNumber currentProcessPSN;
    NSNumber *psnHi = nil;
    NSNumber *psnLo = nil;

    NSArray * runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];

    for(NSDictionary * appInfo in runningApps){
      if( [path isEqualToString:[appInfo objectForKey:@"NSApplicationPath"]] ){
      psnHi = [appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"];
      psnLo = [appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"];
      break;
      }
    }

    if(psnHi == nil) return;

    currentProcessPSN.highLongOfPSN = [psnHi unsignedIntegerValue];
    currentProcessPSN.lowLongOfPSN = [psnLo unsignedIntegerValue];

    OSStatus result = AEBuildAppleEvent( kCoreEventClass, kAEQuitApplication, typeProcessSerialNumber, &currentProcessPSN,
              sizeof(ProcessSerialNumber), kAutoGenerateReturnID, kAnyTransactionID, &tAppleEvent, &tAEBuildError,"");
    result = AESend( &tAppleEvent, &tReply, kAEAlwaysInteract+kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil );
    }

    --Rob

    On Jul 12, 2012, at 12:33 PM, Jerry Krinock <jerry...> wrote:

    > What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.
    >
    > Thanks,
    >
    > Jerry Krinock
  • One thing I've done in the past is get the PID of the main app before launching the helper, and pass it as a command line argument to the helper (or something similar) when it's launched. The helper app then periodically polls for the existence of that PID, and when it discovers it's no longer there, quits.

    This is nice because it's resilient against crashes as well as normal termination. Of course (assuming you have some communication pipe between your helper and your main app), when your main app quits normally you should attempt to inform your helper that it should quit. This is a good strategy to implement in addition to that so that it can clean itself up after a crash.

    -Matt

    Sent from my iPhone

    On Jul 12, 2012, at 10:27 AM, Robert Martin <robmartin...> wrote:

    > The best way? I have no idea… This is what I do, and it works.
    >
    > I store the path to my helper app in 'path', and do the following when my app is about to quit:
    >
    >
    > //---------------------------------------------------------------------
    > - (void)applicationWillTerminate:(NSNotification *)aNotification
    > //---------------------------------------------------------------------
    > {
    > [super applicationWillTerminate:aNotification];
    >
    > AppleEvent tAppleEvent, tReply;
    > AEBuildError tAEBuildError;
    > ProcessSerialNumber currentProcessPSN;
    > NSNumber *psnHi = nil;
    > NSNumber *psnLo = nil;
    >
    > NSArray * runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
    >
    > for(NSDictionary * appInfo in runningApps){
    > if( [path isEqualToString:[appInfo objectForKey:@"NSApplicationPath"]] ){
    > psnHi = [appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"];
    > psnLo = [appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"];
    > break;
    > }
    > }
    >
    > if(psnHi == nil) return;
    >
    > currentProcessPSN.highLongOfPSN = [psnHi unsignedIntegerValue];
    > currentProcessPSN.lowLongOfPSN = [psnLo unsignedIntegerValue];
    >
    > OSStatus result = AEBuildAppleEvent( kCoreEventClass, kAEQuitApplication, typeProcessSerialNumber, &currentProcessPSN,
    > sizeof(ProcessSerialNumber), kAutoGenerateReturnID, kAnyTransactionID, &tAppleEvent, &tAEBuildError,"");
    > result = AESend( &tAppleEvent, &tReply, kAEAlwaysInteract+kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil );
    > }
    >
    >
    > --Rob
    >
    >
    > On Jul 12, 2012, at 12:33 PM, Jerry Krinock <jerry...> wrote:
    >
    >> What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.
    >>
    >> Thanks,
    >>
    >> Jerry Krinock

  • If you're on 10.6 or later:

    - (void)applicationWillTerminate:(NSNotification *)aNotification
    {
          NSRunningApplication* helperApp = [NSRunningApplication
    runningApplicationsWithBundleIdentifier:@"bundle.id"];
          [helperApp terminate];
    }

    Sadly, you can't send a terminate command if you're helper app is
    sandboxed, so if you have to sandbox -- you'd likely need to reverse
    the process.
    Your helper app gets the NSRunningApplication object of your main app,
    then observes the terminated property of it.
    When you are notified of it terminating, you terminate yourself.

    Another alternative, use the SMLoginItemsSetEnabled to launch and
    terminate your helper app.

    Don't think that was what the API was intended actually for, but when
    you're stuck in a sandbox, you gotta make due with cheap plastic
    shovel that you find in it.

    On Thu, Jul 12, 2012 at 10:27 AM, Robert Martin
    <robmartin...> wrote:
    > The best way? I have no idea… This is what I do, and it works.
    >
    > I store the path to my helper app in 'path', and do the following when my app is about to quit:
    >
    >
    > //---------------------------------------------------------------------
    > - (void)applicationWillTerminate:(NSNotification *)aNotification
    > //---------------------------------------------------------------------
    > {
    > [super applicationWillTerminate:aNotification];
    >
    > AppleEvent tAppleEvent, tReply;
    > AEBuildError tAEBuildError;
    > ProcessSerialNumber currentProcessPSN;
    > NSNumber *psnHi = nil;
    > NSNumber *psnLo = nil;
    >
    > NSArray * runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
    >
    > for(NSDictionary * appInfo in runningApps){
    > if( [path isEqualToString:[appInfo objectForKey:@"NSApplicationPath"]] ){
    > psnHi = [appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"];
    > psnLo = [appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"];
    > break;
    > }
    > }
    >
    > if(psnHi == nil) return;
    >
    > currentProcessPSN.highLongOfPSN = [psnHi unsignedIntegerValue];
    > currentProcessPSN.lowLongOfPSN = [psnLo unsignedIntegerValue];
    >
    > OSStatus result = AEBuildAppleEvent( kCoreEventClass, kAEQuitApplication, typeProcessSerialNumber, &currentProcessPSN,
    > sizeof(ProcessSerialNumber), kAutoGenerateReturnID, kAnyTransactionID, &tAppleEvent, &tAEBuildError,"");
    > result = AESend( &tAppleEvent, &tReply, kAEAlwaysInteract+kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil );
    > }
    >
    >
    > --Rob
    >
    >
    > On Jul 12, 2012, at 12:33 PM, Jerry Krinock <jerry...> wrote:
    >
    >> What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.
    >>
    >> Thanks,
    >>
    >> Jerry Krinock


    --
    Mark Munz
    unmarked software
    http://www.unmarked.com/
  • On Jul 12, 2012, at 12:33 PM, Jerry Krinock wrote:

    > What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.

    I create a pthread with this code

    pid_t ppid = getppid ();  // get parent pid

    if (ppid > 1) {
      int kq = kqueue ();
      if (kq != -1) {
      struct kevent procEvent; // wait for parent to exit
      EV_SET (&procEvent,  // kevent
        ppid,  // ident
        EVFILT_PROC, // filter
        EV_ADD,  // flags
        NOTE_EXIT,  // fflags
        0,    // data
        0);    // udata

      kevent (kq, &procEvent, 1, &procEvent, 1, 0);
      }
    }
    exit (0);
  • On Thu, 12 Jul 2012 10:54:13 -0700, Mark Munz said:

    > If you're on 10.6 or later:
    >
    > - (void)applicationWillTerminate:(NSNotification *)aNotification
    > {
    > NSRunningApplication* helperApp = [NSRunningApplication
    > runningApplicationsWithBundleIdentifier:@"bundle.id"];
    > [helperApp terminate];
    > }

    Although rare, it is possible to have more than one app with the same bundle id running at the same time.

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On Thu, Jul 12, 2012 at 9:33 AM, Jerry Krinock <jerry...> wrote:
    > What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.

    A solution that does not involve killing the helper application from
    the main application:

    Observe the end of the main application from the helper application.

    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
    selector:@selector(apocalypseNow:)
    name:NSWorkspaceDidTerminateApplicationNotification];

    [...]

    - (void) apocalypseNow:(NSNotification *) inNotification
    {
            NSRunningApplication * tRunningApplication=[[inNotification
    userInfo] objectForKey:NSWorkspaceApplicationKey];

            if ([[tRunningApplication bundleIdentifier]
    isEqualToString:@"the.bundle.identifier.of.the.main.application"]==YES)
                        [NSApp terminate:self];
    }
  • > Although rare, it is possible to have more than one app with the same bundle id running at the same time.

    Sorry - I wrote that code in email and mixed up the ProcessIdentifier
    return type with the BundleIdentifier return type in my head.

    Correct code for bundle ID should look like this:

        NSArray* helperAppList = [NSRunningApplication
    runningApplicationsWithBundleIdentifier:@"bundle.id"];
        for (NSRunningApplication* app in helperAppList)
        {
            [app terminate];
        }

    This will terminate all the helpers that share the same bundle ID.

    You can also use process id variation to get a specific helper.
    + (NSRunningApplication *)runningApplicationWithProcessIdentifier:(pid_t)pid

    The real sticking issue -- is the sandbox. If you stuck in the
    sandbox, you can't terminate helper apps.

    On Thu, Jul 12, 2012 at 12:50 PM, Sean McBride <sean...> wrote:
    > On Thu, 12 Jul 2012 10:54:13 -0700, Mark Munz said:
    >
    >> If you're on 10.6 or later:
    >>
    >> - (void)applicationWillTerminate:(NSNotification *)aNotification
    >> {
    >> NSRunningApplication* helperApp = [NSRunningApplication
    >> runningApplicationsWithBundleIdentifier:@"bundle.id"];
    >> [helperApp terminate];
    >> }
    >
    > Although rare, it is possible to have more than one app with the same bundle id running at the same time.
    >
    > --
    > ____________________________________________________________
    > Sean McBride, B. Eng                <sean...>
    > Rogue Research                        www.rogue-research.com
    > Mac Software Developer              Montréal, Québec, Canada
    >
    >

    --
    Mark Munz
    unmarked software
    http://www.unmarked.com/
  • Thanks for all the ideas.

    I thought maybe someone would suggest opening up some snazzy inter application communication channel and making the helper exit when it broke (indicating that the main app had terminated).  But since that was not offered, I used a combination of the suggestions and some code I had lying around.  It's all done in the helper…

    (1) Register for NSWorkspaceDidTerminateApplicationNotification
    (2) In case the main app terminates before that notification gets going, during launch, check that the main app is still running.

    This might even work in a sandboxed helper, although, Yay! I'm not sandboxed in this particular case.

    On 2012 Jul 12, at 12:50, Sean McBride wrote:

    > Although rare, it is possible to have more than one app with the same bundle id running at the same time.

    Yes, in case someone launches two instances of my app and quits one, I'd have two helpers running.  But I think that's OK because all the helper does is listen for and forward myapp:// url events, and the system will only send such url event to one of them.

    Jerry

    The following code assumes that the MyApp-Helper.app is actually a .app with a run loop, etc., and it is packaged in the main app in Contents/<Something>/

    @implementation MyHelperAppDelegate

    /* Other methods go here */

    - (NSString*)mainAppBundleIdentifier {
        NSString* bundlePath = [[NSBundle mainBundle] bundlePath] ;
        // bundlePath = /Applications/MyApp.app/Contents/Helpers/MyApp-Helper.app"
        bundlePath = [bundlePath stringByDeletingLastPathComponent] ;
        // bundlePath = /Applications/MyApp.app/Contents/Helpers/"
        bundlePath = [bundlePath stringByDeletingLastPathComponent] ;
        // bundlePath = /Applications/MyApp.app/Contents/"
        bundlePath = [bundlePath stringByDeletingLastPathComponent] ;
        // bundlePath = /Applications/MyApp.app/"

        NSBundle* bundle = [NSBundle bundleWithPath:bundlePath] ;
        return [bundle bundleIdentifier] ;
    }

    - (void)handleAppQuit:(NSNotification*)appTerminateNotification {
        NSString* osx_10_5_quitAppBundleIdentifier = [[appTerminateNotification userInfo] objectForKey:@"NSApplicationBundleIdentifier"] ;
        NSString* osx_10_6_quitAppBundleIdentifier = [[[appTerminateNotification userInfo] objectForKey:NSWorkspaceApplicationKey] bundleIdentifier] ;

        NSString *mainAppBundleIdentifier = [self mainAppBundleIdentifier] ;

        if (
            [osx_10_6_quitAppBundleIdentifier isEqualToString:mainAppBundleIdentifier]
            ||
            [osx_10_5_quitAppBundleIdentifier isEqualToString:mainAppBundleIdentifier]
            ) {
            [NSApp terminate:self] ;
        }
    }

    - (pid_t)pidOfThisUsersAppWithBundleIdentifier:(NSString*)bundleIdentifier {
        pid_t pid = 0 ; // not found

        if (bundleIdentifier) {
    #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
            // Running the main run loop is necessary for -runningApplications to
            // update.  The next line is actually necessary in tools which may be lacking
            // a running run loop, and it actually works.
            [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]] ;
            NSArray* runningApps = [[NSWorkspace sharedWorkspace] runningApplications] ;
            for (NSRunningApplication* runningApp in runningApps) {
                if ([[runningApp bundleIdentifier] isEqualToString:bundleIdentifier]) {
                    pid = [runningApp processIdentifier] ;
                    break ;
                }
            }
    #else
            NSArray* appDicts = [[NSWorkspace sharedWorkspace] launchedApplications] ;
            // Note that the above method returns only applications launched by the
            // current user, not other users.  (Not documented, determined by experiment
            // in Mac OS 10.5.5).  Also it returns only "applications", defined as
            // "things which can appear in the Dock that are not documents and are launched
            // by the Finder or Dock"
            // (See documentation of ProcessSerialNumber).
            for (NSDictionary* appDict in [appDicts objectEnumerator]) {
                if ([[appDict objectForKey:@"NSApplicationBundleIdentifier"] isEqualToString:bundleIdentifier]) {
                    pid = [[appDict objectForKey:@"NSApplicationProcessIdentifier"] unsignedLongValue] ;
                    break ;
                }
            }
    #endif
        }

        return pid ;
    }

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
        // Begin watching for main app to quit
        NSNotificationCenter* notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter] ;
        [notificationCenter addObserver:self
                              selector:@selector(handleAppQuit:)
                                  name:NSWorkspaceDidTerminateApplicationNotification
                                object:nil] ;
        // Tested in Mac OS X 10.7: That notification is received whether the application
        // quits normally, crashes, or is terminated by a unix signal.

        // Make sure main app did not terminate before we got here
        pid_t pid = [self pidOfThisUsersAppWithBundleIdentifier:[self mainAppBundleIdentifier]] ;
        if (pid == 0) {
            // Main app must have terminated before we finished launching
            [NSApp terminate:self] ;
        }

        // Other code goes here
    }

    @end
  • On Jul 12, 2012, at 9:33 AM, Jerry Krinock <jerry...> wrote:

    > What is the best way to make a helper app quit when the associated main app quits (or crashes)?  No harm will be done if it keeps running for a minute or so.

    Take a look at
    http://stackoverflow.com/questions/284325/how-to-make-child-process-die-aft
    er-parent-exits


    —Jens
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today