macoslaunchdlaunch-agent

launchd not recognizing manually started app


I'm trying to accomplish the following with my tray app:

  1. MyApp.app should restart on crash
  2. MyApp.app should not launch on user-login (ie user has to manually start the app)

I have a problem that if user manually starts the app, then listing in launchctl list com.myapp doesn't show the running app.

If, however, I let the LaunchAgent launch app on user-login, then launchctl list com.myapp properly shows the app, and will restarts it on crash (or any non-zero exit code).

Strangely enough, if user manually starts the app, launchd will try to launch it's own instance after several minutes. I can't even explain why this happens.

My LaunchAgent plist example:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
   <dict>
      <key>KeepAlive</key>
      <dict>
         <key>SuccessfulExit</key>
         <false/>
         <key>AfterInitialDemand</key>
         <true/>
      </dict>
      <key>RunAtLoad</key>
      <false/>
      <key>Label</key>
      <string>com.myapp</string>
      <key>ProgramArguments</key>
      <array>
         <string>/Applications/MyApp.app/Contents/MacOS/MyApp</string>
      </array>
   </dict>
</plist>

Solution

  • launchd is responsible for only launching your app. If you want to relaunch your app on crash, I would suggest that you create a separate app which only runs in background and is responsible for launching your current app. This way, it is able to track the app it launched and relaunch if it crashed. Here is some sample C code if you want to try:

        pid_t waitResult;
        pid_t processId = <Get process id here>
        int status = 0;
    
        do {
                waitResult = waitpid(processId, &status, WNOHANG | WUNTRACED);
                sleep(1);
            } while (waitResult == 0);
    
            if (waitResult == processId) {  //Process ends here
                if (WIFEXITED(status)) {
                    //Child app ended normally
                } else if (WIFSIGNALED(status) || WIFSTOPPED(status)) { 
                    //Child app crashed. Restart here again.
                }
            }