launchd keeps restarting my helper tool ?

My app needs to write files at restricted places on the filesystem, namely : /Library/Application Support.

Following the guidelines, and based on Apple sample code (SMJobBless, BetterAuthorizationSample, EvenBetterAuthorizationSample), I wrote a second, tiny application whose sole purpose is to install those files.


Everything is up and running for this part, I get the helper tool installed using SMJobBless(), after requesting authorization.

At first, I didn't get the helper tool running at all. This was solved by adding the correct keys and values in the helper launchd plist (namely RunAtLoad, and KeepAlive), that is documented in the launchd.plist (5) man page.

The second problem was that after exiting, I couldn't get the helper tool to be run a second time (even with those keys in the plist). I implemented the solution provided here (http://atnan.com/blog/2012/02/29/modern-privileged-helper-tools-using-smjobbless-plus-xpc/) to be able to wake my helper tool on demand, which works.


Now here is finally my problem :


No matter what keys and values I put in the launchd plist, launchd keeps restarting the helper process, which isn't killed or terminated in any abnormal way. The helper tool just exists upon receiving a particuliar kind of message I send to it from the main application.

I don't want the helper tool to be running forever, doing nothing but waiting (even when the main application isn't running). I want a truly 'On Demand' helper, that I can wake up and shutdown as needed.


So my question(s) is(are) :


- Why would launchd restart my helper tool every time it exit (success, or failure doesn't change the deal) ?

- How can I have a truly On Demand helper tool.


Some precisions :


I observe a similar behavior when running Apple sample code (or the one in the link), the helper tool process keeps around forever, no way is provided to tell it to exit.

I use Boost interprocess message queues to communicate from the main app to the helper (don't think it matters, but who knows)

I run OS X 10.11 and Xcode 7.0.1.


Thank you for any help !

Vince.

Accepted Reply

On modern systems launchd does not idle exit jobs. Rather, it cleans up idle jobs when the system is under load. So you won’t see launchd stop your job unless you load up the system.

As to why it keeps relaunching, there are really only two reasons for that:

  • your job is telling launchd to keep it alive

  • there’s demand for your job

To offer any suggestions for the first item, I’d need to see what your final launchd property list looks like.

With regards the second item, have you played with

launchctl blame
. In many cases it will tell you why the job was launched. For example:
$ launchctl blame user/2000/com.apple.twitter.xpc
ipc (mach)

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

On modern systems launchd does not idle exit jobs. Rather, it cleans up idle jobs when the system is under load. So you won’t see launchd stop your job unless you load up the system.

As to why it keeps relaunching, there are really only two reasons for that:

  • your job is telling launchd to keep it alive

  • there’s demand for your job

To offer any suggestions for the first item, I’d need to see what your final launchd property list looks like.

With regards the second item, have you played with

launchctl blame
. In many cases it will tell you why the job was launched. For example:
$ launchctl blame user/2000/com.apple.twitter.xpc
ipc (mach)

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you very much for taking time to reply.

I finally got the job to exit and restart on demand thank to you and your suggestions.


To answer your question, here is what the final launchd plist look like :


<?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/>
  </dict>
  <key>Label</key>
  <string>com.mycompany.MyHelper</string>
  <key>MachServices</key>
  <dict>
  <key>com.mycompany.MyHelper</key>
  <true/>
  </dict>
  <key>Program</key>
  <string>/Library/PrivilegedHelperTools/com.mycompany.MyHelper</string>
  <key>ProgramArguments</key>
  <array>
  <string>/Library/PrivilegedHelperTools/com.mycompany.MyHelper</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>


So this is a very classic plist, and as you can see, I instruct launchd not to restart my job if it exits successfuly.

This eliminates the first reason.


I didn't know about launchctl blame, I ran it and it gave me :

ipc (mach)


No doubt, the second reason is the right one. There is clearly demand for the job.

At that point, it was still unclear why, because I only used XPC to wake the job up by sending a unique message. I even took care to close the connection upon sending a final instruction to the helper job, telling it to stop.


In my case, the helper job consist in a main() function, where I create an object that will wait for instructions (and block the program execution).

The example code I based mine on include a call to dispatch_main(). This function never returns, and since my object waits for instructions, I didn't include the call in my main() function.


This is the key.


The job didn't ever received the message (logging in the connection event handler on the job side confirmed that, no log output anywhere), and launchd restarted the job in the hope that the message would be delivered.

So considering I can't call dispatch_main() (because I want the job to exit at some point), I create a serial dispatch queue to handle those messages and pass it to the connection create function.


It now works as expected, the job is truly 'on demand'.


So again, thank you very much. You have been a great help here !


Vince.